Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
8.9 KiB
343 lines
8.9 KiB
/** |
|
* @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;
|
|
|