blob: 15cbe6df71553dcee85c1567f271187c1560be7c [file] [log] [blame] [edit]
/**
* @fileoverview This file contains utilities for converting binary,
* wire-format protocol buffers into Javascript data structures.
*
* jspb's BinaryReader class wraps the BinaryDecoder class to add methods
* that understand the protocol buffer syntax and can do the type checking and
* bookkeeping necessary to parse trees of nested messages.
*
* Major caveat - Users of this library _must_ keep their Javascript proto
* parsing code in sync with the original .proto file - presumably you'll be
* using the typed jspb code generator, but if you bypass that you'll need
* to keep things in sync by hand.
*
* @author aappleby@google.com (Austin Appleby)
*/
goog.module('jspb.binary.reader');
goog.module.declareLegacyNamespace();
const BinaryConstants = goog.require('jspb.BinaryConstants');
const asserts = goog.require('goog.asserts');
const errors = goog.require('jspb.binary.errors');
const utils = goog.require('jspb.utils');
const { BinaryDecoder } = goog.require('jspb.binary.decoder');
const { ByteSource } = goog.require('jspb.binary.bytesource');
const { ByteString } = goog.requireType('jspb.bytestring');
/**
* Whether to enforce that string fields are valid utf8.
*
* <p>Currently set to `ALWAYS`, can be set to `DEPRECATED_PROTO3_ONLY` to only
* enforce utf8 for proto3 string fields, for proto2 string fields it will use
* replacement characters when encoding errors are found.
*
* <p>TODO: Remove the flag, simplify BinaryReader to remove
* readStringRequireUtf8 and related support in the code generator et. al.
*
* @define {string}
*/
const ENFORCE_UTF8 = goog.define('jspb.binary.ENFORCE_UTF8', 'ALWAYS');
// Constrain the set of values to only these two.
asserts.assert(
ENFORCE_UTF8 === 'DEPRECATED_PROTO3_ONLY' || ENFORCE_UTF8 === 'ALWAYS');
const /** boolean */ UTF8_PARSING_ERRORS_ARE_FATAL = ENFORCE_UTF8 === 'ALWAYS';
/**
* Describes options for BinaryReaders.
*
* @record
*/
class BinaryReaderOptions {
constructor() {
/**
* Whether to ignore unknown fields found when parsing.
*
* Normally, if unknown tag numbers are encountered when parsing a message,
* the tag and value are stored in the message instance and then written
* back out when the message is serialized. This allows applications to
* preserve data in messages that have new field definitions which they
* don't yet know about. However, this behavior can have performance
* implications. This property disables this behavior during parsing.
*
* @type {boolean|undefined}
*/
this.discardUnknownFields;
/**
* When set to `true` bytes fields will be views into the original buffer
* instead of being copies.
*
* This allows teams to reduce copies at the cost of pinning the original
* `ByteSource` in memory.
*
* How this works ultimate depends on how `bytes` fields are parsed.
*
* If `bytes` fields are read as `Uint8Array` via the `readBytes` method (as
* is done by jsproto and immutablejs): `readBytes` will return views onto
* the original buffer as `Uint8Array` objects. Additionally, because
* Uint8Array objects are mutable this may allow unexpected mutations of the
* `ByteSource` or for mutations of the bytesource to affect later read
* operations. If the source is a ByteString, this option is ignored in
* order to preserve the immutability semantics of ByteStrings.
*
* If the `bytes` are read as `ByteString` via the `readByteString` method,
* then this option is only effective if the
* source is also a `ByteString`, otherwise copies need to be made to
* preserve the immutability of the produced `ByteString` objects.
*
* The default is `false`
* @type {boolean|undefined}
*/
this.aliasBytesFields;
/**
* Whether we should treat newly deserialized data as being immutable.
*
* With this option, we treat newly deserialized data (e.g. from a
* base64-encoded string) as being immutable, so that it can be safely
* aliased in a ByteString. If you set this option, you then cannot alias
* into a Uint8Array, as its underlying ArrayBuffer could be unwrapped.
*
* @type {boolean|undefined}
*/
this.treatNewDataAsImmutable;
}
}
/*
* Handling Errors
*
* There are two classes of errors that should be considered below.
*
* Simple consistency checks: use `goog.asserts`
*
* These are cases related to function invariants. For example, in order to
* call `readString` the current field must have WireType.DELIMITED. Since we
* also control all the callsites it is reasonable to rely on our own tests to
* catch mistakes.
*
* Data based conditions: use `jspb.binary.invalid_encoding_errors`
*
* These are conditions where the _data_ is wrong in some way. For example, a
* varint is overlong, a delimited field overflows/underflows. These cases
* cannot be eliminated by better testing since they depend on the structure of
* data supplied to the parsing routines, so `goog.asserts` are not a good
* approach
*/
/**
* BinaryReader implements the decoders for all the wire types specified in
* https://developers.google.com/protocol-buffers/docs/encoding.
*
* @struct
* @final
*/
class BinaryReader {
/**
* @param {?ByteSource|!ByteString=} bytes The bytes we're reading from.
* @param {number=} start The optional offset to start reading at.
* @param {number=} length The optional length of the block to read -
* we'll throw an assertion if we go off the end of the block.
* @param {!BinaryReaderOptions=} options Options for this BinaryReader.
*/
constructor(bytes, start, length, options) {
/**
* Current options for this reader
* @private {boolean}
*/
this.discardUnknownFields;
/**
* Wire-format decoder.
* @const {!BinaryDecoder}
*/
this.decoder_ = BinaryDecoder.alloc(bytes, start, length, options);
/**
* Cursor immediately before the field tag.
* @private {number}
*/
this.fieldCursor_ = this.decoder_.getCursor();
/**
* Field number of the next field in the buffer, filled in by nextField().
* @private {number}
*/
this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER;
/**
* The combined wire-type and field number of the next field in the buffer,
* filled in by nextField().
* @private {number}
*/
this.nextTag_ = BinaryConstants.INVALID_TAG;
/**
* Wire type of the next proto field in the buffer, filled in by
* nextField().
* @private {!BinaryConstants.WireType}
*/
this.nextWireType_ = BinaryConstants.WireType.INVALID;
this.setOptions(options);
}
/**
* @param {!BinaryReaderOptions=} options options for this decoder.
* @private
*/
setOptions({ discardUnknownFields = false } = {}) {
this.discardUnknownFields = discardUnknownFields;
}
/**
* Pops an instance off the instance cache, or creates one if the cache is
* empty.
* @param {?ByteSource|!ByteString=} bytes The bytes we're reading from.
* @param {number=} start The optional offset to start reading at.
* @param {number=} length The optional length of the block to read -
* we'll throw an assertion if we go off the end of the block.
* @param {!BinaryReaderOptions=} options
* @return {!BinaryReader}
* @suppress {visibility} accesses private properties of decoder
*/
static alloc(bytes, start, length, options) {
if (BinaryReader.instanceCache_.length) {
const newReader = BinaryReader.instanceCache_.pop();
newReader.setOptions(options);
newReader.decoder_.init(bytes, start, length, options);
return newReader;
} else {
return new BinaryReader(bytes, start, length, options);
}
}
/**
* Puts this instance back in the instance cache.
*/
free() {
this.decoder_.clear();
this.nextTag_ = BinaryConstants.INVALID_TAG;
this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER;
this.nextWireType_ = BinaryConstants.WireType.INVALID;
if (BinaryReader.instanceCache_.length < 100) {
BinaryReader.instanceCache_.push(this);
}
}
/**
* Returns the cursor immediately before the current field's tag.
* @return {number} The internal read cursor.
*/
getFieldCursor() {
return this.fieldCursor_;
}
/**
* Returns the internal read cursor.
* @return {number} The internal read cursor.
*/
getCursor() {
return this.decoder_.getCursor();
}
/** @return {boolean} */
dataIsImmutable() {
return this.decoder_.dataIsImmutable();
}
/**
* Returns the raw buffer.
*
* Throws if the internal buffer is immutable.
*
* @return {?Uint8Array} The raw buffer.
*/
getBuffer() {
return this.decoder_.getBuffer();
}
/**
* Returns the raw buffer as a byte string.
*
* Throws if the internal buffer is mutable.
*
* @return {?ByteString} The raw buffer.
*/
getBufferAsByteString() {
return this.decoder_.getBufferAsByteString();
}
/**
* @return {number} The combined wire type and field number of the next field
* in the buffer, or INVALID_TAG if there is no next field. This is an
* unsigned 32-bit integer value with the lower three bits the wire type
* and the upper 29 bits the field number.
*/
getTag() {
return this.nextTag_;
}
/**
* @return {number} The field number of the next field in the buffer, or
* INVALID_FIELD_NUMBER if there is no next field.
*/
getFieldNumber() {
return this.nextField_;
}
/**
* @return {!BinaryConstants.WireType} The wire type of the next field
* in the stream, or WireType.INVALID if there is no next field.
*/
getWireType() {
return this.nextWireType_;
}
/**
* @return {boolean} Whether the current wire type is an end-group tag. Used
* as
* an exit condition in decoder loops in generated code.
*/
isEndGroup() {
return this.nextWireType_ == BinaryConstants.WireType.END_GROUP;
}
/**
* @return {boolean} Whether the current wire type is a delimited field. Used
* to
* conditionally parse packed repeated fields.
*/
isDelimited() {
return this.nextWireType_ == BinaryConstants.WireType.DELIMITED;
}
/**
* Rewinds the stream cursor to the beginning of the buffer and resets all
* internal state.
* @package
*/
reset() {
this.decoder_.reset();
this.fieldCursor_ = this.decoder_.getCursor();
this.nextTag_ = BinaryConstants.INVALID_TAG;
this.nextField_ = BinaryConstants.INVALID_FIELD_NUMBER;
this.nextWireType_ = BinaryConstants.WireType.INVALID;
}
/**
* Advances the stream cursor by the given number of bytes.
* @param {number} count The number of bytes to advance by.
*/
advance(count) {
this.decoder_.advance(count);
}
/**
* Reads the next field header in the stream if there is one, returns true if
* we saw a valid field header or false if we've read the whole stream.
* Throws an error if we encountered a deprecated START_GROUP/END_GROUP field.
* @return {boolean} True if the stream contains more fields.
*/
nextField() {
// If we're at the end of the block, there are no more fields.
if (this.decoder_.atEnd()) {
return false;
}
this.assertPriorFieldWasRead();
// No need to check cursor position here, as that is readUnsignedVarint32s
// responsibility.
// Otherwise just read the header of the next field.
this.fieldCursor_ = this.decoder_.getCursor();
const header = BinaryDecoder.readUnsignedVarint32(this.decoder_);
const nextField = parseFieldNumber(header);
const nextWireType = parseWireType(header);
// If the wire type isn't one of the valid ones, something's broken.
if (!BinaryConstants.isValidWireType(nextWireType)) {
throw errors.invalidWireTypeError(nextWireType, this.fieldCursor_);
}
// Zero is not a valid field number and we should never see a negative as
// we've already shifted right unsigned.
if (nextField < 1) {
throw errors.invalidFieldNumberError(nextField, this.fieldCursor_);
}
this.nextTag_ = header;
this.nextField_ = nextField;
this.nextWireType_ = nextWireType;
return true;
}
/**
* If the stream contains a following field with the specified tag (field
* number and wire type), reads its field header and makes the reader ready to
* read its value. If the stream does not contain another field, or its tag is
* different, does not change the state of the stream.
*
* TIP: methods like `readMessage` may destroy the reader state for the tag,
* so call `getTag()` outside a loop, before calling reader methods to pass
* into this method.
*
* @param {number} tag The field tag to look for.
* @return {boolean} Whether another instance of the current tag is read.
*/
nextFieldIfTagEqualTo(tag) {
this.assertPriorFieldWasRead();
asserts.assert(
BinaryConstants.isValidWireType(parseWireType(tag)) &&
parseFieldNumber(tag) > 0,
'Must pass a valid tag.');
const fieldCursorOrNegative =
this.decoder_.readUnsignedVarint32IfEqualTo(tag);
const matched = fieldCursorOrNegative >= 0;
if (matched) {
this.fieldCursor_ = fieldCursorOrNegative;
this.nextTag_ = tag;
this.nextField_ = parseFieldNumber(tag);
this.nextWireType_ = parseWireType(tag);
}
return matched;
}
/**
* Helper to ensure that the prior field was correctly read by advancing the
* cursor.
* @private
*/
assertPriorFieldWasRead() {
if (asserts.ENABLE_ASSERTS &&
this.nextTag_ !== BinaryConstants.INVALID_TAG) {
// If we aren't at the first field, make sure that the previous caller of
// nextField actually read the field (by calling an appropriate `read*` or
// `skip*` method).
// To do this we go back and redo the work to move cursor to where it was
// at the end of the last nextField() call and just ensure that we have
// advanced beyond that
const currentCursor = this.decoder_.getCursor();
this.decoder_.setCursor(this.fieldCursor_);
BinaryDecoder.readUnsignedVarint32(this.decoder_);
if (this.nextWireType_ === BinaryConstants.WireType.END_GROUP ||
this.nextWireType_ === BinaryConstants.WireType.START_GROUP) {
// This case should be impossible, as every read* skip* method would
// likely throw. Direct mutation of the cursor could do it.
asserts.assert(
currentCursor === this.decoder_.getCursor(),
'Expected to not advance the cursor. Group tags do not have values.');
} else {
asserts.assert(
currentCursor > this.decoder_.getCursor(),
'Expected to read the field, did you forget to call a read or skip method?');
}
this.decoder_.setCursor(currentCursor);
}
}
/**
* Skips over the next varint field in the binary stream.
*/
skipVarintField() {
// This case should be impossible but jsproto calls this method without
// first checking wiretype
if (this.nextWireType_ != BinaryConstants.WireType.VARINT) {
asserts.fail('Invalid wire type for skipVarintField');
this.skipField();
return;
}
this.decoder_.skipVarint();
}
/**
* Skips over the next delimited field in the binary stream.
* @return {number} The length of the delimited field payload, not including
* the field header.
*/
skipDelimitedField() {
// This case should be impossible but jsproto calls this method without
// first checking wiretype
if (this.nextWireType_ != BinaryConstants.WireType.DELIMITED) {
asserts.fail('Invalid wire type for skipDelimitedField');
this.skipField();
return 0;
}
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
this.decoder_.advance(length);
return length;
}
/**
* Skips over the next fixed32 field in the binary stream.
* @private
*/
skipFixed32Field() {
asserts.assert(this.nextWireType_ === BinaryConstants.WireType.FIXED32);
this.decoder_.advance(4);
}
/**
* Skips over the next fixed64 field in the binary stream.
* @private
*/
skipFixed64Field() {
asserts.assert(this.nextWireType_ === BinaryConstants.WireType.FIXED64);
this.decoder_.advance(8);
}
/**
* Skips over the next group field in the binary stream.
* @private
*/
skipGroup() {
const previousField = this.nextField_;
do {
if (!this.nextField()) {
throw errors.unmatchedStartGroupEofError();
}
if (this.nextWireType_ == BinaryConstants.WireType.END_GROUP) {
// Group end: check that it matches top-of-stack.
if (this.nextField_ != previousField) {
throw errors.unmatchedStartGroupError();
}
return;
}
this.skipField();
} while (true);
}
/**
* Skips over the next field in the binary stream - this is useful if we're
* decoding a message that contain unknown fields.
*/
skipField() {
switch (this.nextWireType_) {
case BinaryConstants.WireType.VARINT:
this.skipVarintField();
break;
case BinaryConstants.WireType.FIXED64:
this.skipFixed64Field();
break;
case BinaryConstants.WireType.DELIMITED:
this.skipDelimitedField();
break;
case BinaryConstants.WireType.FIXED32:
this.skipFixed32Field();
break;
case BinaryConstants.WireType.START_GROUP:
this.skipGroup();
break;
default:
throw errors.invalidWireTypeError(
this.nextWireType_, this.fieldCursor_);
}
}
/**
* Skips over the entire content to the end of the stream.
*/
skipToEnd() {
this.decoder_.setCursor(this.decoder_.getEnd());
}
/**
* Reads a single field as an uninterpreted bytestring.
* @return {!ByteString|undefined}
*/
readUnknownField() {
// read the field cursor prior to calling skipField, otherwise skipping
// a group will reset the field cursor.
const begin = this.getFieldCursor();
this.skipField();
return this.readUnknownFieldsStartingFrom(begin);
}
/**
* Reads a range of unknown field(s) as a single bytestring.
* @param {number} fieldOffset
* @return {!ByteString|undefined}
*/
readUnknownFieldsStartingFrom(fieldOffset) {
if (!this.discardUnknownFields) {
// It is important that this is actually an immutable reference since the
// unknownFieldset takes 'ownership' over the data. So we read as a
// ByteString.
const currentOffset = this.decoder_.getCursor();
const fieldLength = currentOffset - fieldOffset;
this.decoder_.setCursor(fieldOffset);
const unknownField = this.decoder_.readByteString(fieldLength);
// double check our own math.
asserts.assert(currentOffset == this.decoder_.getCursor());
// lazily instantiate the unknown fields structure.
return unknownField;
}
return undefined;
}
/**
* Reads a field of any valid non-message type from the binary stream.
*
* Returns `null` if the type could not be parsed because the data on the wire
* doesn't match the `fieldType`.
* @param {!BinaryConstants.FieldType} fieldType
* @return {boolean|number|string|!Uint8Array|null}
*/
readAny(fieldType) {
// If the wire types don't match just return null, it means that there is an
// invalid value on the wire. In normal apps/jspb we handle this as an
// 'unknown field' but in this API we just skip over it as we have no place
// to store unknowns
if (BinaryConstants.FieldTypeToWireType(fieldType) !== this.nextWireType_) {
return null;
}
const fieldTypes = BinaryConstants.FieldType;
switch (fieldType) {
case fieldTypes.DOUBLE:
return this.readDouble();
case fieldTypes.FLOAT:
return this.readFloat();
case fieldTypes.INT64:
return this.readInt64();
case fieldTypes.UINT64:
return this.readUint64();
case fieldTypes.INT32:
return this.readInt32();
case fieldTypes.FIXED64:
return this.readFixed64();
case fieldTypes.FIXED32:
return this.readFixed32();
case fieldTypes.BOOL:
return this.readBool();
case fieldTypes.STRING:
return this.readString();
case fieldTypes.GROUP:
asserts.fail('Group field type not supported in readAny()');
case fieldTypes.MESSAGE:
asserts.fail('Message field type not supported in readAny()');
case fieldTypes.BYTES:
return this.readBytes();
case fieldTypes.UINT32:
return this.readUint32();
case fieldTypes.ENUM:
return this.readEnum();
case fieldTypes.SFIXED32:
return this.readSfixed32();
case fieldTypes.SFIXED64:
return this.readSfixed64();
case fieldTypes.SINT32:
return this.readSint32();
case fieldTypes.SINT64:
return this.readSint64();
default:
asserts.fail('Invalid field type in readAny()');
}
return null;
}
/**
* Deserialize a proto into the provided message object using the provided
* reader function. This function is templated as we currently have one client
* who is using manual deserialization instead of the code-generated versions.
* @template T,A,B,C
* @param {T} message
* @param {function(T, !BinaryReader, A, B, C)} reader
* @param {A=} contextA
* @param {B=} contextB
* @param {C=} contextC
* @return {T}
*/
readMessage(message, reader, contextA, contextB, contextC) {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
// Save the current endpoint of the decoder and move it to the end of the
// embedded message.
const oldEnd = this.decoder_.getEnd();
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
const newEnd = this.decoder_.getCursor() + length;
let underflowLength = newEnd - oldEnd;
if (underflowLength <= 0) {
this.decoder_.setEnd(newEnd);
// Deserialize the embedded message.
reader(message, this, contextA, contextB, contextC);
underflowLength = newEnd - this.decoder_.getCursor();
}
if (underflowLength) {
throw errors.messageLengthMismatchError(length, length - underflowLength);
}
// Advance the decoder past the embedded message and restore the endpoint.
this.decoder_.setCursor(newEnd);
this.decoder_.setEnd(oldEnd);
return message;
}
/**
* Deserialize a proto into the provided message object using the provided
* reader function, assuming that the message is serialized as a group
* with the given tag.
* @template T
* @param {number} field
* @param {T} message
* @param {function(T, !BinaryReader)} reader
* @return {T}
*/
readGroup(field, message, reader) {
// Ensure that the wire type is correct.
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.START_GROUP);
// Ensure that the field number is correct.
asserts.assert(this.nextField_ == field);
// Deserialize the message. The deserialization will stop at an END_GROUP
// tag.
reader(message, this);
if (this.nextWireType_ !== BinaryConstants.WireType.END_GROUP) {
// This case should be impossible assuming we trust all the reader
// callbacks. In other words this really means the `reader` did the
// wrong thing.
throw errors.groupDidNotEndWithEndGroupError();
} else if (this.nextField_ !== field) {
// this can happen if start and end group tags are nested improperly
throw errors.unmatchedStartGroupError();
}
return message;
}
/**
* Whether the current field could be a valid start of a message set group.
* @return {boolean}
*/
isMessageSetGroup() {
return this.getTag() === MESSAGE_SET_START_GROUP_TAG;
}
/**
* Deserialize a message-set wire-format group, calling the provided callaback
* with the type ID and reader to read the message with.
*
* See go/messageset-wire-format
* @param {function(number, !BinaryReader): void} readerCallback Function
* called with the type ID and a reader for the message content.
*/
readMessageSetGroup(readerCallback) {
asserts.assert(this.isMessageSetGroup());
// A message set group is encodes like:
// repeated group Item = 1 {
// required uint32 typeId = 2;
// required bytes message = 3;
// }
//
let typeId = 0;
// The offset to the message payload, or -1 if consumed.
let messageCursor = 0;
while (this.nextField() && !this.isEndGroup()) {
// See go/malformed-message-set-parsing If malformed messages repeat the
// typeId or message fields, only use the first value of either within
// the group.
if (this.getTag() === MESSAGE_SET_TYPE_ID_TAG && !typeId) {
typeId = this.readUint32();
if (messageCursor) {
asserts.assert(messageCursor > 0);
// Backup the parsing to the message payload.
// No need to restore the position, we'll simply reread the type id
// again, but skip doing anything because its already been read once.
// Reset these. Because we are going to modify the cursor we need to
// skip the consistency checks in nextField() and pretend like this is
// a fresh BinaryReader instance.
if (asserts.ENABLE_ASSERTS) {
this.nextTag_ = BinaryConstants.INVALID_TAG;
this.nextWireType_ = BinaryConstants.WireType.INVALID;
}
// Reset our cursor to where the message was, and pretend like we had
// not seen the message payload yet.
this.decoder_.setCursor(messageCursor);
messageCursor = 0;
}
} else if (this.getTag() === MESSAGE_SET_MESSAGE_TAG && !messageCursor) {
if (typeId) {
messageCursor = -1;
this.readMessage(typeId, readerCallback);
} else {
// Save the cursor to read the message
// after we have the type ID.
messageCursor = this.getFieldCursor();
this.skipDelimitedField();
}
} else {
// Either we have already read the payload or this is not a valid
// messageset member. As a practical matter we don't have a place to
// store this unknown field and simply skipping is consistent with the
// Java and C++ impls
this.skipField();
}
}
// If we do not have an end tag, if we did not have a message, or if we
// did not have a field number, drop out.
if (this.getTag() !== MESSAGE_SET_END_TAG || !messageCursor || !typeId) {
throw errors.malformedBinaryBytesForMessageSet();
}
}
/**
* Reads a signed 32-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the signed 32-bit integer field.
*/
readInt32() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSignedVarint32(this.decoder_);
}
/**
* Reads a signed 64-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the signed 64-bit integer field.
*/
readInt64() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSignedVarint64(this.decoder_);
}
/**
* Reads a signed 64-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* Returns the value as a string.
*
* @return {string} The value of the signed 64-bit integer field as a decimal
* string.
*/
readInt64String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSignedVarint64String(this.decoder_);
}
/**
* Reads an unsigned 32-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the unsigned 32-bit integer field.
*/
readUint32() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readUnsignedVarint32(this.decoder_);
}
/**
* Reads an unsigned 64-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the unsigned 64-bit integer field.
*/
readUint64() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readUnsignedVarint64(this.decoder_);
}
/**
* Reads an unsigned 64-bit integer field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* Returns the value as a string.
*
* @return {string} The value of the unsigned 64-bit integer field as a
* decimal string.
*/
readUint64String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readUnsignedVarint64String(this.decoder_);
}
/**
* Reads a signed zigzag-encoded 32-bit integer field from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {number} The value of the signed 32-bit integer field.
*/
readSint32() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readZigzagVarint32(this.decoder_);
}
/**
* Reads a signed zigzag-encoded 64-bit integer field from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {number} The value of the signed 64-bit integer field.
*/
readSint64() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readZigzagVarint64(this.decoder_);
}
/**
* Reads a signed zigzag-encoded 64-bit integer field from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {string} The value of the signed 64-bit integer field as a decimal
* string.
*/
readSint64String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readZigzagVarint64String(this.decoder_);
}
/**
* Reads an unsigned 32-bit fixed-length integer fiield from the binary
* stream, or throws an error if the next field in the stream is not of the
* correct wire type.
*
* @return {number} The value of the double field.
*/
readFixed32() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32);
return BinaryDecoder.readUint32(this.decoder_);
}
/**
* Reads an unsigned 64-bit fixed-length integer fiield from the binary
* stream, or throws an error if the next field in the stream is not of the
* correct wire type.
*
* @return {number} The value of the float field.
*/
readFixed64() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readUint64(this.decoder_);
}
/**
* Reads an unsigned 64-bit integer field from the binary stream as a string,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* Returns the value as a string.
*
* @return {string} The value of the unsigned 64-bit integer field as a
* decimal string.
*/
readFixed64String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readUint64String(this.decoder_);
}
/**
* Reads a signed 32-bit fixed-length integer fiield from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {number} The value of the signed 32-bit integer field.
*/
readSfixed32() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32);
return BinaryDecoder.readInt32(this.decoder_);
}
/**
* Reads a signed 32-bit fixed-length integer fiield from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {string} The value of the signed 32-bit integer field as a decimal
* string.
*/
readSfixed32String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32);
return BinaryDecoder.readInt32(this.decoder_).toString();
}
/**
* Reads a signed 64-bit fixed-length integer fiield from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* @return {number} The value of the sfixed64 field.
*/
readSfixed64() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readInt64(this.decoder_);
}
/**
* Reads a signed 64-bit fixed-length integer field from the binary stream,
* or throws an error if the next field in the stream is not of the correct
* wire type.
*
* Returns the value as a string.
*
* @return {string} The value of the sfixed64 field as a decimal string.
*/
readSfixed64String() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readInt64String(this.decoder_);
}
/**
* Reads a 32-bit floating-point field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the float field.
*/
readFloat() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED32);
return BinaryDecoder.readFloat(this.decoder_);
}
/**
* Reads a 64-bit floating-point field from the binary stream, or throws an
* error if the next field in the stream is not of the correct wire type.
*
* @return {number} The value of the double field.
*/
readDouble() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readDouble(this.decoder_);
}
/**
* Reads a boolean field from the binary stream, or throws an error if the
* next field in the stream is not of the correct wire type.
*
* @return {boolean} The value of the boolean field.
*/
readBool() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readBool(this.decoder_);
}
/**
* Reads an enum field from the binary stream, or throws an error if the next
* field in the stream is not of the correct wire type.
*
* @return {number} The value of the enum field.
*/
readEnum() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSignedVarint32(this.decoder_);
}
/**
* Reads a string field from the binary stream, or throws an error if the next
* field in the stream is not of the correct wire type.
*
* @return {string} The value of the string field.
*/
readString() {
// delegate to the other reader so that inlining can eliminate this method
// in the common case.
if (UTF8_PARSING_ERRORS_ARE_FATAL) {
return this.readStringRequireUtf8();
}
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
return this.decoder_.readString(length, /*parsingErrorsAreFatal=*/ false);
}
/**
* Reads a string field from the binary stream, or throws an error if the next
* field in the stream is not of the correct wire type, or if the string is
* not valid utf8.
*
* @return {string} The value of the string field.
*/
readStringRequireUtf8() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
return this.decoder_.readString(length, /*parsingErrorsAreFatal=*/ true);
}
/**
* Reads a length-prefixed block of bytes from the binary stream, or returns
* null if the next field in the stream has an invalid length value.
*
* @return {!Uint8Array} The block of bytes.
*/
readBytes() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
return this.decoder_.readBytes(length);
}
/**
* Reads a length-prefixed block of bytes from the binary stream, or returns
* null if the next field in the stream has an invalid length value.
*
* @return {!ByteString} The block of bytes.
*/
readByteString() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
const length = BinaryDecoder.readUnsignedVarint32(this.decoder_);
return this.decoder_.readByteString(length);
}
/**
* Reads a 64-bit varint field from the stream and invokes `convert` to
* produce the return value, or throws an error if the next field in the
* stream is not of the correct wire type.
*
* @param {function(number, number): T} convert Conversion function to produce
* the result value, takes parameters (lowBits, highBits).
* @return {T}
* @template T
*/
readSplitVarint64(convert) {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSplitVarint64(this.decoder_, convert);
}
/**
* Reads a 64-bit zig-zag varint field from the stream and invokes `convert`
* to produce the return value, or throws an error if the next field in the
* stream is not of the correct wire type.
*
* @param {function(number, number): T} convert Conversion function to produce
* the result value, takes parameters (lowBits, highBits).
* @return {T}
* @template T
*/
readSplitZigzagVarint64(convert) {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.VARINT);
return BinaryDecoder.readSplitVarint64(
this.decoder_,
(lowBits, highBits) => utils.fromZigzag64(lowBits, highBits, convert));
}
/**
* Reads a 64-bit fixed64 field from the stream and invokes `convert`
* to produce the return value, or throws an error if the next field in the
* stream is not of the correct wire type.
*
* @param {function(number, number): T} convert Conversion function to produce
* the result value, takes parameters (lowBits, highBits).
* @return {T}
* @template T
*/
readSplitFixed64(convert) {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.FIXED64);
return BinaryDecoder.readSplitFixed64(this.decoder_, convert);
}
/**
* Reads a packed scalar field using the supplied raw reader function.
* @param {function(!BinaryDecoder):T} decodeMethod
* @param {!Array<T>} dst
* @template T
* @private
*/
readPackedFieldInto_(decodeMethod, dst) {
const length = this.readPackedFieldLength_();
const end = this.decoder_.getCursor() + length;
while (this.decoder_.getCursor() < end) {
dst.push(decodeMethod(this.decoder_));
}
}
/**
* Reads the length of a packed field.
* @return {number}
* @private
*/
readPackedFieldLength_() {
asserts.assert(this.nextWireType_ == BinaryConstants.WireType.DELIMITED);
return BinaryDecoder.readUnsignedVarint32(this.decoder_);
}
/**
* Reads an int32 field that might be packed
* @param {!Array<number>} dst
*/
readPackableInt32Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readSignedVarint32, dst);
} else {
dst.push(this.readInt32());
}
}
/**
* Reads an int64 field that might be packed
* @param {!Array<number>}dst
*/
readPackableInt64Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readSignedVarint64, dst);
} else {
dst.push(this.readInt64());
}
}
/**
* Reads an int64 field that might be packed
* @param {!Array<string>} dst
*/
readPackableInt64StringInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readSignedVarint64String, dst);
} else {
dst.push(this.readInt64String());
}
}
/**
* Reads an int32 field that might be packed
* @param {!Array<number>} dst
*/
readPackableUint32Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint32, dst);
} else {
dst.push(this.readUint32());
}
}
/**
* Reads an uint64 field that might be packed
* @param {!Array<number>} dst
*/
readPackableUint64Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint64, dst);
} else {
dst.push(this.readUint64());
}
}
/**
* Reads an uint64 field that might be packed
* @param {!Array<string>} dst
*/
readPackableUint64StringInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUnsignedVarint64String, dst);
} else {
dst.push(this.readUint64String());
}
}
/**
* Reads a possibly packed sint32 field,
* @param {!Array<number>} dst
*/
readPackableSint32Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint32, dst);
} else {
dst.push(this.readSint32());
}
}
/**
* Reads a possibly packed sint64 field,
* @param {!Array<number>} dst
*/
readPackableSint64Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint64, dst);
} else {
dst.push(this.readSint64());
}
}
/**
* Reads a possibly packed sint64 field as an array of strings.
* @param {!Array<string>} dst
*/
readPackableSint64StringInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readZigzagVarint64String, dst);
} else {
dst.push(this.readSint64String());
}
}
/**
* Reads a possibly packed fixed32 field.
* @param {!Array<number>} dst
*/
readPackableFixed32Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUint32, dst);
} else {
dst.push(this.readFixed32());
}
}
/**
* Reads a possibly packed fixed64 field.
* @param {!Array<number>} dst
*/
readPackableFixed64Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUint64, dst);
} else {
dst.push(this.readFixed64());
}
}
/**
* Reads a possibly packed fixed64 field as an array fo strings
* @param {!Array<string>} dst
*/
readPackableFixed64StringInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readUint64String, dst);
} else {
dst.push(this.readFixed64String());
}
}
/**
* Reads a possibly packed sfixed32
* @param {!Array<number>} dst
*/
readPackableSfixed32Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readInt32, dst);
} else {
dst.push(this.readSfixed32());
}
}
/**
* Reads a possibly packed sfixed64 field.
* @param {!Array<number>} dst
*/
readPackableSfixed64Into(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readInt64, dst);
} else {
dst.push(this.readSfixed64());
}
}
/**
* Reads a possibly packed sfixed64 field as an array of strings
* @param {!Array<string>} dst
*/
readPackableSfixed64StringInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readInt64String, dst);
} else {
dst.push(this.readSfixed64String());
}
}
/**
* Reads a packed fixed32 field, which consists of a length header and a list
* of unsigned 32-bit ints.
* @return {!Array<number>}
* @export
*/
readPackedFixed32() {
const values = [];
this.readPackableFixed32Into(values);
return values;
}
/**
* Reads a packed fixed64 field, which consists of a length header and a list
* of unsigned 64-bit ints.
* @return {!Array<number>}
* @export
*/
readPackedFixed64() {
const values = [];
this.readPackableFixed64Into(values);
return values;
}
/**
* Reads a packed fixed64 field, which consists of a length header and a list
* of unsigned 64-bit ints. Returns a list of strings.
* @return {!Array<string>}
* @export
*/
readPackedFixed64String() {
const values = [];
this.readPackableFixed64StringInto(values);
return values;
}
/**
* Reads a packed sfixed32 field, which consists of a length header and a list
* of 32-bit ints.
* @return {!Array<number>}
* @export
*/
readPackedSfixed32() {
const values = [];
this.readPackableSfixed32Into(values);
return values;
}
/**
* Reads a packed sfixed64 field, which consists of a length header and a list
* of 64-bit ints. Returns a list of strings.
* @return {!Array<string>}
* @export
*/
readPackedSfixed64String() {
const values = [];
this.readPackableSfixed64StringInto(values);
return values;
}
/**
* Reads a possibly packed float field
* @param {!Array<number>} dst
*/
readPackableFloatInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readFloat, dst);
} else {
dst.push(this.readFloat());
}
}
/**
* Reads a possibly packed double field and appends it to `dst`.
* @param {!Array<number>} dst
*/
readPackableDoubleInto(dst) {
if (this.isDelimited()) {
this.decoder_.readDoubleArrayInto(this.readPackedFieldLength_() / 8, dst);
} else {
dst.push(this.readDouble());
}
}
/**
* Reads a possibly packed bool field
* @param {!Array<boolean>} dst
*/
readPackableBoolInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readBool, dst);
} else {
dst.push(this.readBool());
}
}
/**
* Reads a possibly packed enum field
* @param {!Array<number>} dst
*/
readPackableEnumInto(dst) {
if (this.isDelimited()) {
this.readPackedFieldInto_(BinaryDecoder.readEnum, dst);
} else {
dst.push(this.readEnum());
}
}
/**
* Visible for testing.
* @package
*/
static resetInstanceCache() {
BinaryReader.instanceCache_ = [];
}
/**
* Visible for testing.
* @return {!Array<!BinaryReader>}
* @package
*/
static getInstanceCache() {
return BinaryReader.instanceCache_;
}
}
/**
* @param {number} tag
* @return {!BinaryConstants.WireType}
*/
function parseWireType(tag) {
return /** @type {!BinaryConstants.WireType} */ (tag & 0x7);
}
/**
* @param {number} tag
* @return {number}
*/
function parseFieldNumber(tag) {
return tag >>> 3;
}
/**
* Global pool of BinaryReader instances.
* @private {!Array<!BinaryReader>}
*/
BinaryReader.instanceCache_ = [];
const /** number */ MESSAGE_SET_START_GROUP_TAG = utils.makeTag(
BinaryConstants.MESSAGE_SET_GROUP_NUMBER,
BinaryConstants.WireType.START_GROUP);
const /** number */ MESSAGE_SET_TYPE_ID_TAG = utils.makeTag(
BinaryConstants.MESSAGE_SET_TYPE_ID_FIELD_NUMBER,
BinaryConstants.WireType.VARINT);
const /** number */ MESSAGE_SET_MESSAGE_TAG = utils.makeTag(
BinaryConstants.MESSAGE_SET_MESSAGE_FIELD_NUMBER,
BinaryConstants.WireType.DELIMITED);
const /** number */ MESSAGE_SET_END_TAG = utils.makeTag(
BinaryConstants.MESSAGE_SET_GROUP_NUMBER,
BinaryConstants.WireType.END_GROUP);
exports = {
BinaryReader,
BinaryReaderOptions,
UTF8_PARSING_ERRORS_ARE_FATAL,
};