344 lines
8.9 KiB
JavaScript
344 lines
8.9 KiB
JavaScript
|
/**
|
||
|
* @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 from the current cursor position.
|
||
|
* @package
|
||
|
*/
|
||
|
skipVarint() {
|
||
|
const startIndex = this.cursor_;
|
||
|
while (this.dataView_.getUint8(this.cursor_++) & 0x80) {
|
||
|
}
|
||
|
checkCriticalPositionIndex(this.cursor_, startIndex + 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;
|