iw6-mod/deps/protobuf/js/experimental/runtime/kernel/buffer_decoder.js
2024-02-27 01:34:37 -05:00

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;