| /** |
| * @fileoverview A buffer implementation that can decode data for protobufs. |
| */ |
| |
| goog.module('protobuf.binary.BufferDecoder'); |
| |
| const ByteString = goog.require('protobuf.ByteString'); |
| const functions = goog.require('goog.functions'); |
| const {POLYFILL_TEXT_ENCODING, checkCriticalPositionIndex, checkCriticalState, checkState} = goog.require('protobuf.internal.checks'); |
| const {byteStringFromUint8ArrayUnsafe} = goog.require('protobuf.byteStringInternal'); |
| const {concatenateByteArrays} = goog.require('protobuf.binary.uint8arrays'); |
| const {decode} = goog.require('protobuf.binary.textencoding'); |
| |
| /** |
| * Returns a valid utf-8 decoder function based on TextDecoder if available or |
| * a polyfill. |
| * Some of the environments we run in do not have TextDecoder defined. |
| * TextDecoder is faster than our polyfill so we prefer it over the polyfill. |
| * @return {function(!DataView): string} |
| */ |
| function getStringDecoderFunction() { |
| if (goog.global['TextDecoder']) { |
| const textDecoder = new goog.global['TextDecoder']('utf-8', {fatal: true}); |
| return bytes => textDecoder.decode(bytes); |
| } |
| if (POLYFILL_TEXT_ENCODING) { |
| return decode; |
| } else { |
| throw new Error( |
| 'TextDecoder is missing. ' + |
| 'Enable protobuf.defines.POLYFILL_TEXT_ENCODING.'); |
| } |
| } |
| |
| /** @type {function(): function(!DataView): string} */ |
| const stringDecoderFunction = |
| functions.cacheReturnValue(() => getStringDecoderFunction()); |
| |
| /** @type {function(): !DataView} */ |
| const emptyDataView = |
| functions.cacheReturnValue(() => new DataView(new ArrayBuffer(0))); |
| |
| class BufferDecoder { |
| /** |
| * @param {!Array<!BufferDecoder>} bufferDecoders |
| * @return {!BufferDecoder} |
| */ |
| static merge(bufferDecoders) { |
| const uint8Arrays = bufferDecoders.map(b => b.asUint8Array()); |
| const bytesArray = concatenateByteArrays(uint8Arrays); |
| return BufferDecoder.fromArrayBuffer(bytesArray.buffer); |
| } |
| |
| /** |
| * @param {!ArrayBuffer} arrayBuffer |
| * @return {!BufferDecoder} |
| */ |
| static fromArrayBuffer(arrayBuffer) { |
| return new BufferDecoder( |
| new DataView(arrayBuffer), 0, arrayBuffer.byteLength); |
| } |
| |
| /** |
| * @param {!DataView} dataView |
| * @param {number} startIndex |
| * @param {number} length |
| * @private |
| */ |
| constructor(dataView, startIndex, length) { |
| /** @private @const {!DataView} */ |
| this.dataView_ = dataView; |
| /** @private @const {number} */ |
| this.startIndex_ = startIndex; |
| /** @private @const {number} */ |
| this.endIndex_ = startIndex + length; |
| /** @private {number} */ |
| this.cursor_ = startIndex; |
| } |
| |
| /** |
| * Returns the start index of the underlying buffer. |
| * @return {number} |
| */ |
| startIndex() { |
| return this.startIndex_; |
| } |
| |
| /** |
| * Returns the end index of the underlying buffer. |
| * @return {number} |
| */ |
| endIndex() { |
| return this.endIndex_; |
| } |
| |
| /** |
| * Returns the length of the underlying buffer. |
| * @return {number} |
| */ |
| length() { |
| return this.endIndex_ - this.startIndex_; |
| } |
| |
| /** |
| * Returns the start position of the next data, i.e. end position of the last |
| * read data + 1. |
| * @return {number} |
| */ |
| cursor() { |
| return this.cursor_; |
| } |
| |
| /** |
| * Sets the cursor to the specified position. |
| * @param {number} position |
| */ |
| setCursor(position) { |
| this.cursor_ = position; |
| } |
| |
| /** |
| * Returns if there is more data to read after the current cursor position. |
| * @return {boolean} |
| */ |
| hasNext() { |
| return this.cursor_ < this.endIndex_; |
| } |
| |
| /** |
| * Returns a float32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getFloat32(index) { |
| this.cursor_ = index + 4; |
| return this.dataView_.getFloat32(index, true); |
| } |
| |
| /** |
| * Returns a float64 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getFloat64(index) { |
| this.cursor_ = index + 8; |
| return this.dataView_.getFloat64(index, true); |
| } |
| |
| /** |
| * Returns an int32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getInt32(index) { |
| this.cursor_ = index + 4; |
| return this.dataView_.getInt32(index, true); |
| } |
| |
| /** |
| * Returns a uint32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getUint32(index) { |
| this.cursor_ = index + 4; |
| return this.dataView_.getUint32(index, true); |
| } |
| |
| /** |
| * Returns two JS numbers each representing 32 bits of a 64 bit number. Also |
| * sets the cursor to the start of the next block of data. |
| * @param {number} index |
| * @return {{lowBits: number, highBits: number}} |
| */ |
| getVarint(index) { |
| this.cursor_ = index; |
| let lowBits = 0; |
| let highBits = 0; |
| |
| for (let shift = 0; shift < 28; shift += 7) { |
| const b = this.dataView_.getUint8(this.cursor_++); |
| lowBits |= (b & 0x7F) << shift; |
| if ((b & 0x80) === 0) { |
| return {lowBits, highBits}; |
| } |
| } |
| |
| const middleByte = this.dataView_.getUint8(this.cursor_++); |
| |
| // last four bits of the first 32 bit number |
| lowBits |= (middleByte & 0x0F) << 28; |
| |
| // 3 upper bits are part of the next 32 bit number |
| highBits = (middleByte & 0x70) >> 4; |
| |
| if ((middleByte & 0x80) === 0) { |
| return {lowBits, highBits}; |
| } |
| |
| |
| for (let shift = 3; shift <= 31; shift += 7) { |
| const b = this.dataView_.getUint8(this.cursor_++); |
| highBits |= (b & 0x7F) << shift; |
| if ((b & 0x80) === 0) { |
| return {lowBits, highBits}; |
| } |
| } |
| |
| checkCriticalState(false, 'Data is longer than 10 bytes'); |
| |
| return {lowBits, highBits}; |
| } |
| |
| /** |
| * Returns an unsigned int32 number at the current cursor position. The upper |
| * bits are discarded if the varint is longer than 32 bits. Also sets the |
| * cursor to the start of the next block of data. |
| * @return {number} |
| */ |
| getUnsignedVarint32() { |
| let b = this.dataView_.getUint8(this.cursor_++); |
| let result = b & 0x7F; |
| if ((b & 0x80) === 0) { |
| return result; |
| } |
| |
| b = this.dataView_.getUint8(this.cursor_++); |
| result |= (b & 0x7F) << 7; |
| if ((b & 0x80) === 0) { |
| return result; |
| } |
| |
| b = this.dataView_.getUint8(this.cursor_++); |
| result |= (b & 0x7F) << 14; |
| if ((b & 0x80) === 0) { |
| return result; |
| } |
| |
| b = this.dataView_.getUint8(this.cursor_++); |
| result |= (b & 0x7F) << 21; |
| if ((b & 0x80) === 0) { |
| return result; |
| } |
| |
| // Extract only last 4 bits |
| b = this.dataView_.getUint8(this.cursor_++); |
| result |= (b & 0x0F) << 28; |
| |
| for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) { |
| b = this.dataView_.getUint8(this.cursor_++); |
| } |
| |
| checkCriticalState((b & 0x80) === 0, 'Data is longer than 10 bytes'); |
| |
| // Result can be have 32 bits, convert it to unsigned |
| return result >>> 0; |
| } |
| |
| /** |
| * Returns an unsigned int32 number at the specified index. The upper bits are |
| * discarded if the varint is longer than 32 bits. Also sets the cursor to the |
| * start of the next block of data. |
| * @param {number} index |
| * @return {number} |
| */ |
| getUnsignedVarint32At(index) { |
| this.cursor_ = index; |
| return this.getUnsignedVarint32(); |
| } |
| |
| /** |
| * Seeks forward by the given amount. |
| * @param {number} skipAmount |
| * @package |
| */ |
| skip(skipAmount) { |
| this.cursor_ += skipAmount; |
| checkCriticalPositionIndex(this.cursor_, this.endIndex_); |
| } |
| |
| /** |
| * Skips over a varint at a given index. |
| * @param {number} index Start of the data. |
| * @package |
| */ |
| skipVarint(index) { |
| this.cursor_ = index; |
| while (this.dataView_.getUint8(this.cursor_++) & 0x80) { |
| } |
| checkCriticalPositionIndex(this.cursor_, index + 10); |
| } |
| |
| /** |
| * @param {number} startIndex |
| * @param {number} length |
| * @return {!BufferDecoder} |
| */ |
| subBufferDecoder(startIndex, length) { |
| checkState( |
| startIndex >= this.startIndex(), |
| `Current start: ${this.startIndex()}, subBufferDecoder start: ${ |
| startIndex}`); |
| checkState(length >= 0, `Length: ${length}`); |
| checkState( |
| startIndex + length <= this.endIndex(), |
| `Current end: ${this.endIndex()}, subBufferDecoder start: ${ |
| startIndex}, subBufferDecoder length: ${length}`); |
| return new BufferDecoder(this.dataView_, startIndex, length); |
| } |
| |
| /** |
| * Returns the buffer as a string. |
| * @return {string} |
| */ |
| asString() { |
| // TODO: Remove this check when we no longer need to support IE |
| const stringDataView = this.length() === 0 ? |
| emptyDataView() : |
| new DataView(this.dataView_.buffer, this.startIndex_, this.length()); |
| return stringDecoderFunction()(stringDataView); |
| } |
| |
| /** |
| * Returns the buffer as a ByteString. |
| * @return {!ByteString} |
| */ |
| asByteString() { |
| return byteStringFromUint8ArrayUnsafe(this.asUint8Array()); |
| } |
| |
| /** |
| * Returns the DataView as an Uint8Array. DO NOT MODIFY or expose the |
| * underlying buffer. |
| * |
| * @package |
| * @return {!Uint8Array} |
| */ |
| asUint8Array() { |
| return new Uint8Array( |
| this.dataView_.buffer, this.startIndex_, this.length()); |
| } |
| } |
| |
| exports = BufferDecoder; |