| 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,};
 |