| /** |
| * @fileoverview This file contains utilities for decoding primitive values |
| * (signed and unsigned integers, varints, booleans, enums, hashes, strings, |
| * and raw bytes) embedded in Uint8Arrays into their corresponding Javascript |
| * types. |
| * |
| * Major caveat - Javascript is unable to accurately represent integers larger |
| * than 2^53 due to its use of a double-precision floating point format or all |
| * numbers. If you need to guarantee that 64-bit values survive with all bits |
| * intact, you _must_ read them using one of the split methods, which return |
| * numbers. |
| * |
| * @author [email protected] (Austin Appleby) |
| */ |
| |
| goog.module('jspb.binary.decoder'); |
| goog.module.declareLegacyNamespace(); |
| |
| const asserts = goog.require('goog.asserts'); |
| const errors = goog.require('jspb.binary.errors'); |
| const utils = goog.require('jspb.utils'); |
| const {BinaryReaderOptions} = goog.requireType('jspb.binary.reader'); |
| const {Buffer, bufferFromSource} = goog.require('jspb.binary.internal_buffer'); |
| const {ByteSource} = goog.require('jspb.binary.bytesource'); |
| const {ByteString} = goog.require('jspb.bytestring'); |
| const {decodeUtf8} = goog.require('jspb.binary.utf8'); |
| const {unsafeByteStringFromUint8Array} = goog.require('jspb.unsafe_bytestring'); |
| |
| |
| /** |
| * The maximum number of bytes in a varint. |
| * |
| * Every byte in a varint provides 7 bits and the largest number requires 64 |
| * bits which implies it would take up to 10 bytes to provide all the bits. `10 |
| * == Math.ceil(64/7)` |
| */ |
| const /** number */ MAX_VARINT_SIZE = 10; |
| |
| class BinaryDecoder { |
| /** |
| * BinaryDecoder implements the decoders for all the wire types specified in |
| * https://developers.google.com/protocol-buffers/docs/encoding. |
| * |
| * @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 decoder. |
| */ |
| constructor(bytes, start, length, options) { |
| /** |
| * Typed byte-wise view of the source buffer. |
| * @private {?Uint8Array} |
| */ |
| this.bytes_ = null; |
| |
| /** |
| * Typed byte-wise view of the source buffer, if the data is immutable. |
| * @private {?Buffer} |
| */ |
| this.buffer_ = null; |
| |
| /** @private {boolean} */ |
| this.bytesAreImmutable_ = false; |
| |
| if (ASSUME_DATAVIEW_IS_FAST) { |
| /** |
| * DataView of the source buffer. |
| * @private {?DataView} |
| */ |
| this.dataView_ = null; |
| } |
| |
| /** |
| * Start point of the block to read. |
| * @private {number} |
| */ |
| this.start_ = 0; |
| |
| /** |
| * End point of the block to read, this is exclusive. |
| * @private {number} |
| */ |
| this.end_ = 0; |
| |
| /** |
| * Current read location in bytes_. |
| * @private {number} |
| */ |
| this.cursor_ = 0; |
| |
| /** |
| * Set to true if this decoder should use subarray instead of slice. |
| * @private {boolean} |
| */ |
| this.aliasBytesFields; |
| |
| /** |
| * Whether we should treat newly deserialized data as being immutable. |
| * |
| * @private {boolean} |
| */ |
| this.treatNewDataAsImmutable; |
| |
| this.init(bytes, start, length, options); |
| } |
| |
| /** |
| * @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 decoder. |
| * @private |
| */ |
| init( |
| bytes, start, length, |
| { aliasBytesFields = false, treatNewDataAsImmutable = false } = {}) { |
| this.aliasBytesFields = aliasBytesFields; |
| this.treatNewDataAsImmutable = treatNewDataAsImmutable; |
| if (bytes) { |
| this.setBlock(bytes, start, length); |
| } |
| } |
| |
| /** |
| * 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 options for this decoder. |
| * @return {!BinaryDecoder} |
| * @export |
| */ |
| static alloc(bytes, start, length, options) { |
| if (BinaryDecoder.instanceCache_.length) { |
| const newDecoder = BinaryDecoder.instanceCache_.pop(); |
| newDecoder.init(bytes, start, length, options); |
| return newDecoder; |
| } else { |
| return new BinaryDecoder(bytes, start, length, options); |
| } |
| } |
| |
| /** |
| * Puts this instance back in the instance cache. |
| * @export |
| */ |
| free() { |
| this.clear(); |
| if (BinaryDecoder.instanceCache_.length < 100) { |
| BinaryDecoder.instanceCache_.push(this); |
| } |
| } |
| |
| /** |
| * Clears the decoder. |
| * @export |
| */ |
| clear() { |
| this.bytes_ = null; |
| this.buffer_ = null; |
| this.bytesAreImmutable_ = false; |
| if (ASSUME_DATAVIEW_IS_FAST) { |
| this.dataView_ = null; |
| } |
| this.start_ = 0; |
| this.end_ = 0; |
| this.cursor_ = 0; |
| this.aliasBytesFields = false; |
| } |
| |
| /** |
| * @export |
| * @return {boolean} |
| */ |
| dataIsImmutable() { |
| return this.bytesAreImmutable_; |
| } |
| |
| /** |
| * Returns the raw buffer. |
| * |
| * Throws if the internal buffer is immutable |
| * |
| * @export |
| * @return {?Uint8Array} The raw buffer. |
| */ |
| getBuffer() { |
| if (this.bytesAreImmutable_) { |
| throw goog.DEBUG ? |
| new Error( |
| 'cannot access the buffer of decoders over immutable data.') : |
| new Error(); |
| } |
| return this.bytes_; |
| } |
| |
| /** |
| * Returns the raw buffer. |
| * |
| * Throws if the internal buffer is mutable |
| * |
| * @export |
| * @return {?ByteString} The buffer. |
| */ |
| getBufferAsByteString() { |
| if (this.buffer_ == null) return null; |
| if (!this.bytesAreImmutable_) { |
| throw goog.DEBUG ? |
| new Error( |
| 'cannot access the buffer of decoders over immutable data.') : |
| new Error(); |
| } |
| return this.buffer_.getBufferAsByteStringIfImmutable(); |
| } |
| |
| /** |
| * Changes the block of bytes we're decoding. |
| * @param {!ByteSource|!ByteString} data 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. |
| * |
| * @export |
| */ |
| setBlock(data, start, length) { |
| const unpackedData = bufferFromSource(data, this.treatNewDataAsImmutable); |
| this.buffer_ = unpackedData; |
| this.bytes_ = unpackedData.buffer; |
| this.bytesAreImmutable_ = unpackedData.isImmutable; |
| if (ASSUME_DATAVIEW_IS_FAST) this.dataView_ = null; |
| this.start_ = start || 0; |
| this.end_ = |
| (length !== undefined) ? this.start_ + length : this.bytes_.length; |
| this.cursor_ = this.start_; |
| } |
| |
| /** |
| * @export |
| * @return {number} |
| */ |
| getEnd() { |
| return this.end_; |
| } |
| |
| |
| /** |
| * @param {number} end |
| * @export |
| */ |
| setEnd(end) { |
| this.end_ = end; |
| } |
| |
| /** |
| * Moves the read cursor back to the start of the block. |
| * @export |
| */ |
| reset() { |
| this.cursor_ = this.start_; |
| } |
| |
| |
| /** |
| * Returns the internal read cursor. |
| * @export |
| * @return {number} The internal read cursor. |
| */ |
| getCursor() { |
| return this.cursor_; |
| } |
| |
| /** |
| * Returns the internal read cursor. |
| * @param {number} cursor The new cursor. |
| * @export |
| */ |
| setCursor(cursor) { |
| this.cursor_ = cursor; |
| } |
| |
| |
| /** |
| * Advances the stream cursor by the given number of bytes. |
| * @param {number} count The number of bytes to advance by. |
| * @export |
| */ |
| advance(count) { |
| const newCursor = this.cursor_ + count; |
| this.setCursorAndCheck(newCursor); |
| } |
| |
| /** |
| * Returns true if this decoder is at the end of the block. |
| * @return {boolean} |
| * @export |
| */ |
| atEnd() { |
| return this.cursor_ == this.end_; |
| } |
| |
| |
| /** |
| * Returns true if this decoder is at the end of the block. |
| * @return {boolean} |
| * @export |
| */ |
| pastEnd() { |
| return this.cursor_ > this.end_; |
| } |
| |
| /** |
| * Reads an unsigned varint from the binary stream and invokes the conversion |
| * function with the value in two signed 32 bit integers to produce the |
| * result. Since this does not convert the value to a number, no precision is |
| * lost. |
| * |
| * It's possible for an unsigned varint to be incorrectly encoded - more than |
| * 64 bits' worth of data could be present. If this happens, this method will |
| * throw an error. |
| * |
| * Decoding varints requires doing some funny base-128 math - for more |
| * details on the format, see |
| * https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @param {function(number, number): T} convert Conversion function to produce |
| * the result value, takes parameters (lowBits, highBits). |
| * @return {T} |
| * @export |
| * @template T |
| */ |
| static readSplitVarint64(decoder, convert) { |
| let temp = 0; |
| let lowBits = 0; |
| let highBits = 0; |
| let shift = 0; |
| const bytes = decoder.bytes_; |
| let cursor = decoder.cursor_; |
| |
| // Read the first five bytes of the varint, stopping at the terminator if we |
| // see it. |
| do { |
| temp = bytes[cursor++]; |
| lowBits |= (temp & 0x7F) << shift; |
| shift += 7; |
| } while (shift < 32 && temp & 0x80); |
| |
| if (shift > 32) { |
| // The fifth byte was read, which straddles the low and high dwords, |
| // Save its contribution to the high dword. |
| highBits |= (temp & 0x7F) >> 4; |
| } |
| |
| // Read the sixth through tenth byte. |
| for (shift = 3; shift < 32 && temp & 0x80; shift += 7) { |
| temp = bytes[cursor++]; |
| highBits |= (temp & 0x7F) << shift; |
| } |
| |
| decoder.setCursorAndCheck(cursor); |
| |
| if (temp < 128) { |
| return convert(lowBits >>> 0, highBits >>> 0); |
| } |
| |
| // If we did not see the terminator, the encoding was invalid. |
| throw errors.invalidVarintError(); |
| } |
| |
| /** |
| * Reads a signed zigzag encoded varint from the binary stream and invokes |
| * the conversion function with the value in two signed 32 bit integers to |
| * produce the result. Since this does not convert the value to a number, no |
| * precision is lost. |
| * |
| * It's possible for an unsigned varint to be incorrectly encoded - more than |
| * 64 bits' worth of data could be present. If this happens, this method will |
| * throw an error. |
| * |
| * Zigzag encoding is a modification of varint encoding that reduces the |
| * storage overhead for small negative integers - for more details on the |
| * format, see https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @param {function(number, number): T} convert Conversion function to produce |
| * the result value, takes parameters (lowBits, highBits). |
| * @return {T} |
| * @export |
| * @template T |
| */ |
| static readSplitZigzagVarint64(decoder, convert) { |
| return BinaryDecoder.readSplitVarint64( |
| decoder, (low, high) => utils.fromZigzag64(low, high, convert)); |
| } |
| |
| /** |
| * Reads a 64-bit fixed-width value from the stream and invokes the conversion |
| * function with the value in two signed 32 bit integers to produce the |
| * result. Since this does not convert the value to a number, no precision is |
| * lost. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @param {function(number, number): T} convert Conversion function to produce |
| * the result value, takes parameters (lowBits, highBits). |
| * @return {T} |
| * @export |
| * @template T |
| */ |
| static readSplitFixed64(decoder, convert) { |
| const bytes = decoder.bytes_; |
| const cursor = decoder.cursor_; |
| decoder.advance(8); |
| let lowBits = 0; |
| let highBits = 0; |
| for (let i = cursor + 7; i >= cursor; i--) { |
| lowBits = (lowBits << 8) | bytes[i]; |
| highBits = (highBits << 8) | bytes[i + 4]; |
| } |
| return convert(lowBits, highBits); |
| } |
| |
| /** |
| * Skips over a varint in the block without decoding it. |
| * @export |
| */ |
| skipVarint() { |
| // readBool does only trivial decoding, delegate to it. |
| BinaryDecoder.readBool(this); |
| } |
| |
| /** |
| * Asserts that our cursor is in bounds. |
| * |
| * @private |
| * @param {number} cursor |
| * @return {void} |
| */ |
| setCursorAndCheck(cursor) { |
| this.cursor_ = cursor; |
| if (cursor > this.end_) { |
| throw errors.readTooFarError(this.end_, cursor); |
| } |
| } |
| |
| /** |
| * Reads a 32-bit varint from the binary stream. |
| * |
| * This function is called vastly more frequently than any other in |
| * BinaryDecoder, so it has been unrolled and tweaked for performance. |
| * |
| * Decoding varints requires doing some funny base-128 math - for more |
| * details on the format, see |
| * https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded unsigned 32-bit varint. |
| * @export |
| */ |
| static readSignedVarint32(decoder) { |
| const bytes = decoder.bytes_; |
| let cursor = decoder.cursor_; |
| |
| let temp = bytes[cursor++]; |
| let x = (temp & 0x7F); |
| |
| if (temp & 0x80) { |
| temp = bytes[cursor++]; |
| x |= (temp & 0x7F) << 7; |
| |
| if (temp & 0x80) { |
| temp = bytes[cursor++]; |
| x |= (temp & 0x7F) << 14; |
| |
| if (temp & 0x80) { |
| temp = bytes[cursor++]; |
| x |= (temp & 0x7F) << 21; |
| |
| if (temp & 0x80) { |
| temp = bytes[cursor++]; |
| // We're reading the high bits of an unsigned varint. The byte we |
| // just read also contains bits 33 through 35, which we're going to |
| // discard. |
| x |= temp << 28; |
| |
| if (temp & 0x80) { |
| // If we get here, we need to truncate coming bytes. However we |
| // need to make sure cursor place is correct. |
| if (bytes[cursor++] & 0x80 && bytes[cursor++] & 0x80 && |
| bytes[cursor++] & 0x80 && bytes[cursor++] & 0x80 && |
| bytes[cursor++] & 0x80) { |
| // If we get here, the varint is too long. |
| throw errors.invalidVarintError(); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| decoder.setCursorAndCheck(cursor); |
| return x; |
| } |
| |
| /** |
| * Reads an unsigned 32-bit varint from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded signed 32-bit varint. |
| * @export |
| */ |
| static readUnsignedVarint32(decoder) { |
| return BinaryDecoder.readSignedVarint32(decoder) >>> 0; |
| } |
| |
| /** |
| * Peeks into the binary stream. If the value is an unsigned varint matching |
| * `expected`, returns the initial cursor position and advances the cursor |
| * past the varint. If the value is different, or the stream ends, returns |
| * -1. |
| * |
| * Designed for peeking to see the field header tag for the next field. |
| * |
| * Will only match minimal encodings, will only read up to 35 bits (5 7-bit |
| * bytes), and will not match over-long encodings even if they are short. |
| * |
| * A) These methods are optional optimizations, if they're conservative in |
| * matching, nothing should break, we'll just see another repeated field the |
| * normal way if the tag is encoded strangely. |
| * |
| * B) We don't want to reuse the "normal" read method, because it will throw |
| * on invalid formats and reading past the end, which we don't want to fail. |
| * |
| * C) If we somehow start to expect a difference in varint encoding, this |
| * decoder method likely needs to be aware of what information is encoded in |
| * that deviation anyway. |
| * |
| * @param {number} expected |
| * @return {number} |
| * @export |
| */ |
| readUnsignedVarint32IfEqualTo(expected) { |
| asserts.assert(expected === expected >>> 0); |
| const initialCursor = this.cursor_; |
| let cursor = initialCursor; |
| const end = this.end_; |
| const bytes = this.bytes_; |
| while (cursor < end) { |
| if (expected > 0x7F) { |
| const expectedByte = 0x80 | (expected & 0x7F); |
| if (bytes[cursor++] !== expectedByte) { |
| return -1; |
| } |
| expected >>>= 7; |
| } else { |
| // Last byte of the expected value. |
| if (bytes[cursor++] === expected) { |
| this.cursor_ = cursor; |
| return initialCursor; |
| } else { |
| return -1; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| |
| /** |
| * Reads a signed, zigzag-encoded 32-bit varint from the binary stream. |
| * |
| * Zigzag encoding is a modification of varint encoding that reduces the |
| * storage overhead for small negative integers - for more details on the |
| * format, see https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded signed, zigzag-encoded 32-bit varint. |
| * @export |
| */ |
| static readZigzagVarint32(decoder) { |
| return utils.fromZigzag32(BinaryDecoder.readUnsignedVarint32(decoder)); |
| } |
| |
| /** |
| * Reads an unsigned 64-bit varint from the binary stream. Note that since |
| * Javascript represents all numbers as double-precision floats, there will be |
| * precision lost if the absolute value of the varint is larger than 2^53. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded unsigned varint. Precision will be lost if the |
| * integer exceeds 2^53. |
| * @export |
| */ |
| static readUnsignedVarint64(decoder) { |
| return BinaryDecoder.readSplitVarint64(decoder, utils.joinUint64); |
| } |
| |
| /** |
| * Reads an unsigned 64-bit varint from the binary stream and returns the |
| * value as a decimal string. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {string} The decoded unsigned varint as a decimal string. |
| * @export |
| */ |
| static readUnsignedVarint64String(decoder) { |
| return BinaryDecoder.readSplitVarint64( |
| decoder, utils.joinUnsignedDecimalString); |
| } |
| |
| /** |
| * Reads a signed 64-bit varint from the binary stream. Note that since |
| * Javascript represents all numbers as double-precision floats, there will be |
| * precision lost if the absolute value of the varint is larger than 2^53. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded signed varint. Precision will be lost if the |
| * integer exceeds 2^53. |
| * @export |
| */ |
| static readSignedVarint64(decoder) { |
| return BinaryDecoder.readSplitVarint64(decoder, utils.joinInt64); |
| } |
| |
| /** |
| * Reads an signed 64-bit varint from the binary stream and returns the value |
| * as a decimal string. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {string} The decoded signed varint as a decimal string. |
| * @export |
| */ |
| static readSignedVarint64String(decoder) { |
| return BinaryDecoder.readSplitVarint64( |
| decoder, utils.joinSignedDecimalString); |
| } |
| |
| /** |
| * Reads a signed, zigzag-encoded 64-bit varint from the binary stream. Note |
| * that since Javascript represents all numbers as double-precision floats, |
| * there will be precision lost if the absolute value of the varint is larger |
| * than 2^53. |
| * |
| * Zigzag encoding is a modification of varint encoding that reduces the |
| * storage overhead for small negative integers - for more details on the |
| * format, see https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The decoded zigzag varint. Precision will be lost if the |
| * integer exceeds 2^53. |
| * @export |
| */ |
| static readZigzagVarint64(decoder) { |
| return BinaryDecoder.readSplitVarint64(decoder, utils.joinZigzag64); |
| } |
| |
| |
| /** |
| * Reads a signed, zigzag-encoded 64-bit varint from the binary stream and |
| * returns its value as a string. |
| * |
| * Zigzag encoding is a modification of varint encoding that reduces the |
| * storage overhead for small negative integers - for more details on the |
| * format, see https://developers.google.com/protocol-buffers/docs/encoding |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {string} The decoded signed, zigzag-encoded 64-bit varint as a |
| * string. |
| * @export |
| */ |
| static readZigzagVarint64String(decoder) { |
| return BinaryDecoder.readSplitZigzagVarint64( |
| decoder, utils.joinSignedDecimalString); |
| } |
| |
| /** |
| * Reads a raw unsigned 8-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The unsigned 8-bit integer read from the binary stream. |
| * @export |
| */ |
| static readUint8(decoder) { |
| const a = decoder.bytes_[decoder.cursor_ + 0]; |
| decoder.advance(1); |
| return a; |
| } |
| |
| |
| /** |
| * Reads a raw unsigned 16-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The unsigned 16-bit integer read from the binary stream. |
| * @export |
| */ |
| static readUint16(decoder) { |
| const a = decoder.bytes_[decoder.cursor_ + 0]; |
| const b = decoder.bytes_[decoder.cursor_ + 1]; |
| decoder.advance(2); |
| return (a << 0) | (b << 8); |
| } |
| |
| |
| /** |
| * Reads a raw unsigned 32-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The unsigned 32-bit integer read from the binary stream. |
| * @export |
| */ |
| static readUint32(decoder) { |
| const bytes = decoder.bytes_; |
| const cursor = decoder.cursor_; |
| const a = bytes[cursor + 0]; |
| const b = bytes[cursor + 1]; |
| const c = bytes[cursor + 2]; |
| const d = bytes[cursor + 3]; |
| decoder.advance(4); |
| return ((a << 0) | (b << 8) | (c << 16) | (d << 24)) >>> 0; |
| } |
| |
| /** |
| * Reads a raw unsigned 64-bit integer from the binary stream. Note that since |
| * Javascript represents all numbers as double-precision floats, there will be |
| * precision lost if the absolute value of the integer is larger than 2^53. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The unsigned 64-bit integer read from the binary stream. |
| * Precision will be lost if the integer exceeds 2^53. |
| * @export |
| */ |
| static readUint64(decoder) { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = BinaryDecoder.readUint32(decoder); |
| return utils.joinUint64(bitsLow, bitsHigh); |
| } |
| |
| /** |
| * Reads a raw unsigned 64-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {string} The unsigned 64-bit integer read from the binary stream. |
| * @export |
| */ |
| static readUint64String(decoder) { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = BinaryDecoder.readUint32(decoder); |
| return utils.joinUnsignedDecimalString(bitsLow, bitsHigh); |
| } |
| |
| /** |
| * Reads a raw signed 8-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The signed 8-bit integer read from the binary stream. |
| * @export |
| */ |
| static readInt8(decoder) { |
| const a = decoder.bytes_[decoder.cursor_ + 0]; |
| decoder.advance(1); |
| return (a << 24) >> 24; |
| } |
| |
| |
| /** |
| * Reads a raw signed 16-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The signed 16-bit integer read from the binary stream. |
| * @export |
| */ |
| static readInt16(decoder) { |
| const a = decoder.bytes_[decoder.cursor_ + 0]; |
| const b = decoder.bytes_[decoder.cursor_ + 1]; |
| decoder.advance(2); |
| return (((a << 0) | (b << 8)) << 16) >> 16; |
| } |
| |
| |
| /** |
| * Reads a raw signed 32-bit integer from the binary stream. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The signed 32-bit integer read from the binary stream. |
| * @export |
| */ |
| static readInt32(decoder) { |
| const bytes = decoder.bytes_; |
| const cursor = decoder.cursor_; |
| const a = bytes[cursor + 0]; |
| const b = bytes[cursor + 1]; |
| const c = bytes[cursor + 2]; |
| const d = bytes[cursor + 3]; |
| decoder.advance(4); |
| return (a << 0) | (b << 8) | (c << 16) | (d << 24); |
| } |
| |
| /** |
| * Reads a raw signed 64-bit integer from the binary stream. Note that since |
| * Javascript represents all numbers as double-precision floats, there will be |
| * precision lost if the absolute value of the integer is larger than 2^53. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The signed 64-bit integer read from the binary stream. |
| * Precision will be lost if the integer exceeds 2^53. |
| * @export |
| */ |
| static readInt64(decoder) { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = BinaryDecoder.readUint32(decoder); |
| return utils.joinInt64(bitsLow, bitsHigh); |
| } |
| |
| /** |
| * Reads a raw signed 64-bit integer from the binary stream and returns it as |
| * a string. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {string} The signed 64-bit integer read from the binary stream. |
| * Precision will be lost if the integer exceeds 2^53. |
| * @export |
| */ |
| static readInt64String(decoder) { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = BinaryDecoder.readUint32(decoder); |
| return utils.joinSignedDecimalString(bitsLow, bitsHigh); |
| } |
| |
| /** |
| * Reads a 32-bit floating-point number from the binary stream, using the |
| * temporary buffer to realign the data. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The float read from the binary stream. |
| * @export |
| */ |
| static readFloat(decoder) { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = 0; |
| return utils.joinFloat32(bitsLow, bitsHigh); |
| } |
| |
| /** |
| * Reads a 64-bit floating-point number from the binary stream, using the |
| * temporary buffer to realign the data. |
| * |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The double read from the binary stream. |
| * @export |
| */ |
| static readDouble(decoder) { |
| if (ASSUME_DATAVIEW_IS_FAST) { |
| const result = decoder.getDataView().getFloat64( |
| decoder.cursor_, true /* little endian */); |
| decoder.advance(8); |
| return result; |
| } else { |
| const bitsLow = BinaryDecoder.readUint32(decoder); |
| const bitsHigh = BinaryDecoder.readUint32(decoder); |
| return utils.joinFloat64(bitsLow, bitsHigh); |
| } |
| } |
| |
| /** |
| * Reads an array of 64-bit floating-point numbers from the binary stream. |
| * |
| * @param {number} len The number of doubles to read. |
| * @param {!Array<number>} dst Where to append the result to. |
| * @export |
| */ |
| readDoubleArrayInto(len, dst) { |
| const cursor = this.cursor_; |
| const byteLength = 8 * len; |
| if (cursor + byteLength > this.end_) { |
| throw errors.readTooFarError(byteLength, this.end_ - cursor); |
| } |
| |
| const bytes = this.bytes_; |
| const bufferStart = cursor + bytes.byteOffset; |
| |
| if (ASSUME_DATAVIEW_IS_FAST) { |
| // We create a subview that we'll traverse in its entirety, so that our |
| // "end of loop" condition matches the bounds check that the JIT will |
| // insert, in the hopes that the JIT only needs to run one of those checks |
| // instead of both. This seems to help benchmarks. |
| this.cursor_ += byteLength; |
| const subView = new DataView(bytes.buffer, bufferStart, byteLength); |
| let i = 0; |
| while (true) { |
| let next_i = i + 8; |
| if (next_i > subView.byteLength) { |
| break; |
| } |
| dst.push(subView.getFloat64(i, /*littleEndian=*/ true)); |
| i = next_i; |
| } |
| } else if (OPTIMIZE_LITTLE_ENDIAN_MACHINES && isLittleEndian()) { |
| // We can use Float64Array to directly interpret the bytes as float64 |
| // values, since this is a little endian machine. However, Float64Array |
| // will reject non-multiple-of-8 byte offsets, so we create a copy of the |
| // underlying ArrayBuffer. |
| this.cursor_ += byteLength; |
| const doubleArray = new Float64Array( |
| bytes.buffer.slice(bufferStart, bufferStart + byteLength)); |
| for (let i = 0; i < doubleArray.length; i++) { |
| dst.push(doubleArray[i]); |
| } |
| } else { |
| for (let i = 0; i < len; i++) { |
| dst.push(BinaryDecoder.readDouble( |
| this, |
| )); |
| } |
| } |
| } |
| |
| /** |
| * Reads a boolean value from the binary stream. |
| * @param {!BinaryDecoder} decoder |
| * @return {boolean} The boolean read from the binary stream. |
| * @export |
| */ |
| static readBool(decoder) { |
| let varintBits = 0; |
| let cursor = decoder.cursor_; |
| const invalidCursor = cursor + MAX_VARINT_SIZE; |
| const bytes = decoder.bytes_; |
| while (cursor < invalidCursor) { |
| // scan for the first byte where the uppermost bit is 0 which signals the |
| // end of the varint |
| const byte = bytes[cursor++]; |
| varintBits |= byte; |
| if ((byte & 0x80) === 0) { |
| decoder.setCursorAndCheck(cursor); |
| // Varints store their 'values' in the lower 7 bits, so if we |
| // accumulated any non-zero bits then the varint is non-zero and the |
| // bool is true. |
| return !!(varintBits & 0x7f); |
| } |
| } |
| throw errors.invalidVarintError(); |
| } |
| |
| /** |
| * Reads an enum value from the binary stream, which are always encoded as |
| * signed varints. |
| * @param {!BinaryDecoder} decoder |
| * @return {number} The enum value read from the binary stream. |
| * @export |
| */ |
| static readEnum(decoder) { |
| return BinaryDecoder.readSignedVarint32(decoder); |
| } |
| |
| /** |
| * @return {number} original cursor. |
| * @private |
| */ |
| checkReadLengthAndAdvance(/** number */ length) { |
| if (length < 0) { |
| throw errors.negativeByteLengthError(length); |
| } |
| const cursor = this.cursor_; |
| const newCursor = cursor + length; |
| if (newCursor > this.end_) { |
| throw errors.readTooFarError(length, this.end_ - cursor); |
| } |
| this.cursor_ = newCursor; |
| return cursor; |
| } |
| |
| /** |
| * Reads and parses a UTF-8 encoded unicode string from the stream. |
| * The code is inspired by maps.vectortown.parse.StreamedDataViewReader. |
| * Supports codepoints from U+0000 up to U+10FFFF. |
| * (http://en.wikipedia.org/wiki/UTF-8). |
| * @param {number} length The length of the string to read. |
| * @param {boolean} parsingErrorsAreFatal Whether to throw when invalid utf8 |
| * is found. |
| * @return {string} The decoded string. |
| * @export |
| */ |
| readString(length, parsingErrorsAreFatal) { |
| const cursor = this.checkReadLengthAndAdvance(length); |
| const result = decodeUtf8( |
| asserts.assert(this.bytes_), cursor, length, parsingErrorsAreFatal); |
| return result; |
| } |
| |
| |
| /** |
| * Reads a block of raw bytes from the binary stream. |
| * |
| * @param {number} length The number of bytes to read. |
| * @return {!Uint8Array} The decoded block of bytes. |
| * @export |
| */ |
| readBytes(length) { |
| const cursor = this.checkReadLengthAndAdvance(length); |
| // Take care not to return mutable references to immutable data. |
| const result = this.aliasBytesFields && !this.bytesAreImmutable_ ? |
| this.bytes_.subarray(cursor, cursor + length) : |
| utils.sliceUint8Array( |
| asserts.assert(this.bytes_), cursor, cursor + length); |
| return result; |
| } |
| |
| /** |
| * Reads a block of raw bytes from the binary stream as a ByteString |
| * |
| * @param {number} length The number of bytes to read. |
| * @return {!ByteString} The decoded block of bytes. |
| * @export |
| */ |
| readByteString(length) { |
| if (length == 0) { |
| // Special case because slice is relatively slow, even on empty slices. |
| return ByteString.empty(); |
| } |
| const cursor = this.checkReadLengthAndAdvance(length); |
| // We can return a view if the source is also immutable |
| const result = this.aliasBytesFields && this.bytesAreImmutable_ ? |
| this.bytes_.subarray(cursor, cursor + length) : |
| utils.sliceUint8Array( |
| asserts.assert(this.bytes_), cursor, cursor + length); |
| // no need to checkCursor, we already checked above |
| // This unsafe call is actually safe because we either have sliced the array |
| // above, or we know the source of the 'subarray' view is immutable and so |
| // we can reference it from an immutable ByteString |
| return unsafeByteStringFromUint8Array(result); |
| } |
| |
| /** |
| * @return {!DataView} |
| * @private |
| */ |
| getDataView() { |
| let dataView = this.dataView_; |
| if (!dataView) { |
| const bytes = this.bytes_; |
| dataView = this.dataView_ = |
| new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); |
| } |
| return dataView; |
| } |
| |
| /** |
| * Visible for testing. |
| * @export |
| * @package |
| */ |
| static resetInstanceCache() { |
| BinaryDecoder.instanceCache_ = []; |
| } |
| |
| /** |
| * Visible for testing. |
| * @return {!Array<!BinaryDecoder>} |
| * @export |
| * @package |
| */ |
| static getInstanceCache() { |
| return BinaryDecoder.instanceCache_; |
| } |
| } |
| |
| |
| /** |
| * Global pool of BinaryDecoder instances. |
| * @private {!Array<!BinaryDecoder>} |
| */ |
| BinaryDecoder.instanceCache_ = []; |
| |
| /** |
| * Is this machine little endian? |
| * @return {boolean} |
| */ |
| function isLittleEndian() { |
| if (isLittleEndianCache === undefined) { |
| // Do a trial memory load. |
| isLittleEndianCache = |
| new Uint16Array(new Uint8Array([1, 2]).buffer)[0] == (1 + 256 * 2); |
| } |
| return asserts.assertBoolean(isLittleEndianCache); |
| } |
| |
| /** Global cache of the isLittleEndian check. */ |
| let isLittleEndianCache = undefined; |
| |
| /** |
| * @define {boolean} |
| * Whether DataView is known to be present and has a fast implementation. |
| * Chrome's implementation was slow until late 2018: |
| * https://v8.dev/blog/dataview. Unsure of the performance on other browsers. |
| */ |
| const ASSUME_DATAVIEW_IS_FAST = goog.define( |
| 'jspb.BinaryDecoder.ASSUME_DATAVIEW_IS_FAST', goog.FEATURESET_YEAR >= 2019); |
| |
| /** |
| * @define {boolean} If true, on little endian machines we'll enable the fast |
| * path for float64 decoding. |
| * |
| * Only exists for testing. |
| */ |
| const OPTIMIZE_LITTLE_ENDIAN_MACHINES = |
| goog.define('jspb.BinaryDecoder.OPTIMIZE_LITTLE_ENDIAN_MACHINES', true); |
| |
| |
| exports = { |
| BinaryDecoder, |
| }; |