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.
192 lines
5.3 KiB
192 lines
5.3 KiB
/** |
|
* @fileoverview Utilities to index a binary proto by fieldnumbers without |
|
* relying on strutural proto information. |
|
*/ |
|
goog.module('protobuf.binary.indexer'); |
|
|
|
const BufferDecoder = goog.require('protobuf.binary.BufferDecoder'); |
|
const Storage = goog.require('protobuf.binary.Storage'); |
|
const WireType = goog.require('protobuf.binary.WireType'); |
|
const {Field} = goog.require('protobuf.binary.field'); |
|
const {checkCriticalPositionIndex, checkCriticalState} = goog.require('protobuf.internal.checks'); |
|
|
|
/** |
|
* Appends a new entry in the index array for the given field number. |
|
* @param {!Storage<!Field>} storage |
|
* @param {number} fieldNumber |
|
* @param {!WireType} wireType |
|
* @param {number} startIndex |
|
*/ |
|
function addIndexEntry(storage, fieldNumber, wireType, startIndex) { |
|
const field = storage.get(fieldNumber); |
|
if (field !== undefined) { |
|
field.addIndexEntry(wireType, startIndex); |
|
} else { |
|
storage.set(fieldNumber, Field.fromFirstIndexEntry(wireType, startIndex)); |
|
} |
|
} |
|
|
|
/** |
|
* Returns wire type stored in a tag. |
|
* Protos store the wire type as the first 3 bit of a tag. |
|
* @param {number} tag |
|
* @return {!WireType} |
|
*/ |
|
function tagToWireType(tag) { |
|
return /** @type {!WireType} */ (tag & 0x07); |
|
} |
|
|
|
/** |
|
* Returns the field number stored in a tag. |
|
* Protos store the field number in the upper 29 bits of a 32 bit number. |
|
* @param {number} tag |
|
* @return {number} |
|
*/ |
|
function tagToFieldNumber(tag) { |
|
return tag >>> 3; |
|
} |
|
|
|
/** |
|
* An Indexer that indexes a given binary protobuf by fieldnumber. |
|
*/ |
|
class Indexer { |
|
/** |
|
* @param {!BufferDecoder} bufferDecoder |
|
* @private |
|
*/ |
|
constructor(bufferDecoder) { |
|
/** @private @const {!BufferDecoder} */ |
|
this.bufferDecoder_ = bufferDecoder; |
|
/** @private {number} */ |
|
this.cursor_ = bufferDecoder.startIndex(); |
|
} |
|
|
|
/** |
|
* @param {number|undefined} pivot |
|
* @return {!Storage<!Field>} |
|
*/ |
|
index(pivot) { |
|
const storage = new Storage(pivot); |
|
while (this.hasNextByte_()) { |
|
const tag = this.readVarInt32_(); |
|
const wireType = tagToWireType(tag); |
|
const fieldNumber = tagToFieldNumber(tag); |
|
checkCriticalState( |
|
fieldNumber > 0, `Invalid field number ${fieldNumber}`); |
|
|
|
addIndexEntry(storage, fieldNumber, wireType, this.cursor_); |
|
|
|
checkCriticalState( |
|
!this.skipField_(wireType, fieldNumber), |
|
'Found unmatched stop group.'); |
|
} |
|
return storage; |
|
} |
|
|
|
/** |
|
* Skips over fields until the next field of the message. |
|
* @param {!WireType} wireType |
|
* @param {number} fieldNumber |
|
* @return {boolean} Whether the field we skipped over was a stop group. |
|
* @private |
|
*/ |
|
skipField_(wireType, fieldNumber) { |
|
switch (wireType) { |
|
case WireType.VARINT: |
|
this.cursor_ = this.bufferDecoder_.skipVarint(this.cursor_); |
|
return false; |
|
case WireType.FIXED64: |
|
this.skip_(8); |
|
return false; |
|
case WireType.DELIMITED: |
|
const length = this.readVarInt32_(); |
|
this.skip_(length); |
|
return false; |
|
case WireType.START_GROUP: |
|
checkCriticalState(this.skipGroup_(fieldNumber), 'No end group found.'); |
|
return false; |
|
case WireType.END_GROUP: |
|
// Signal that we found a stop group to the caller |
|
return true; |
|
case WireType.FIXED32: |
|
this.skip_(4); |
|
return false; |
|
default: |
|
throw new Error(`Invalid wire type: ${wireType}`); |
|
} |
|
} |
|
|
|
/** |
|
* Seeks forward by the given amount. |
|
* @param {number} skipAmount |
|
* @private |
|
*/ |
|
skip_(skipAmount) { |
|
this.cursor_ += skipAmount; |
|
checkCriticalPositionIndex(this.cursor_, this.bufferDecoder_.endIndex()); |
|
} |
|
|
|
/** |
|
* Skips over fields until it finds the end of a given group. |
|
* @param {number} groupFieldNumber |
|
* @return {boolean} Returns true if an end was found. |
|
* @private |
|
*/ |
|
skipGroup_(groupFieldNumber) { |
|
// On a start group we need to keep skipping fields until we find a |
|
// corresponding stop group |
|
// Note: Since we are calling skipField from here nested groups will be |
|
// handled by recursion of this method and thus we will not see a nested |
|
// STOP GROUP here unless there is something wrong with the input data. |
|
while (this.hasNextByte_()) { |
|
const tag = this.readVarInt32_(); |
|
const wireType = tagToWireType(tag); |
|
const fieldNumber = tagToFieldNumber(tag); |
|
|
|
if (this.skipField_(wireType, fieldNumber)) { |
|
checkCriticalState( |
|
groupFieldNumber === fieldNumber, |
|
`Expected stop group for fieldnumber ${ |
|
groupFieldNumber} not found.`); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Returns a JS number for a 32 bit var int. |
|
* @return {number} |
|
* @private |
|
*/ |
|
readVarInt32_() { |
|
const {lowBits, dataStart} = this.bufferDecoder_.getVarint(this.cursor_); |
|
this.cursor_ = dataStart; |
|
return lowBits; |
|
} |
|
|
|
/** |
|
* Returns true if there are more bytes to read in the array. |
|
* @return {boolean} |
|
* @private |
|
*/ |
|
hasNextByte_() { |
|
return this.cursor_ < this.bufferDecoder_.endIndex(); |
|
} |
|
} |
|
|
|
/** |
|
* Creates an index of field locations in a given binary protobuf. |
|
* @param {!BufferDecoder} bufferDecoder |
|
* @param {number|undefined} pivot |
|
* @return {!Storage<!Field>} |
|
* @package |
|
*/ |
|
function buildIndex(bufferDecoder, pivot) { |
|
return new Indexer(bufferDecoder).index(pivot); |
|
} |
|
|
|
|
|
exports = { |
|
buildIndex, |
|
};
|
|
|