123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- /**
- * @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,
- };
|