| /** |
| * @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, checkCriticalElementIndex, checkCriticalPositionIndex, 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_ = this.startIndex_ + length; |
| } |
| |
| /** |
| * 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 a float32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getFloat32(index) { |
| return this.dataView_.getFloat32(index, true); |
| } |
| |
| /** |
| * Returns a float64 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getFloat64(index) { |
| return this.dataView_.getFloat64(index, true); |
| } |
| |
| /** |
| * Returns an int32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getInt32(index) { |
| return this.dataView_.getInt32(index, true); |
| } |
| |
| /** |
| * @param {number} index |
| * @return {number} |
| */ |
| getUint8(index) { |
| return this.dataView_.getUint8(index); |
| } |
| |
| /** |
| * Returns a uint32 from a given index |
| * @param {number} index |
| * @return {number} |
| */ |
| getUint32(index) { |
| return this.dataView_.getUint32(index, true); |
| } |
| |
| /** |
| * Returns two JS numbers each representing 32 bits of a 64 bit number. |
| * @param {number} index |
| * @return {{lowBits: number, highBits: number, dataStart: number}} |
| */ |
| getVarint(index) { |
| let start = index; |
| let lowBits = 0; |
| let highBits = 0; |
| |
| for (let shift = 0; shift < 28; shift += 7) { |
| const b = this.dataView_.getUint8(start++); |
| lowBits |= (b & 0x7F) << shift; |
| if ((b & 0x80) === 0) { |
| return {lowBits, highBits, dataStart: start}; |
| } |
| } |
| |
| const middleByte = this.dataView_.getUint8(start++); |
| |
| // 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, dataStart: start}; |
| } |
| |
| |
| for (let shift = 3; shift <= 31; shift += 7) { |
| const b = this.dataView_.getUint8(start++); |
| highBits |= (b & 0x7F) << shift; |
| if ((b & 0x80) === 0) { |
| return {lowBits, highBits, dataStart: start}; |
| } |
| } |
| |
| checkState(false, 'Data is longer than 10 bytes'); |
| return {lowBits, highBits, dataStart: start}; |
| } |
| |
| /** |
| * Skips over a varint at a given index and returns the next position. |
| * @param {number} index Start of the data. |
| * @return {number} Position of the first byte after the varint. |
| * @package |
| */ |
| skipVarint(index) { |
| let cursor = index; |
| checkCriticalElementIndex(cursor, this.endIndex()); |
| while (this.dataView_.getUint8(cursor++) & 0x80) { |
| checkCriticalElementIndex(cursor, this.endIndex()); |
| } |
| checkCriticalPositionIndex(cursor, index + 10); |
| return cursor; |
| } |
| |
| /** |
| * @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; |