|  | @@ -47,118 +47,96 @@ function tagToFieldNumber(tag) {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * An Indexer that indexes a given binary protobuf by fieldnumber.
 | 
	
		
			
				|  |  | + * Creates an index of field locations in a given binary protobuf.
 | 
	
		
			
				|  |  | + * @param {!BufferDecoder} bufferDecoder
 | 
	
		
			
				|  |  | + * @param {number|undefined} pivot
 | 
	
		
			
				|  |  | + * @return {!Storage<!Field>}
 | 
	
		
			
				|  |  | + * @package
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -class Indexer {
 | 
	
		
			
				|  |  | -  /**
 | 
	
		
			
				|  |  | -   * @param {!BufferDecoder} bufferDecoder
 | 
	
		
			
				|  |  | -   * @private
 | 
	
		
			
				|  |  | -   */
 | 
	
		
			
				|  |  | -  constructor(bufferDecoder) {
 | 
	
		
			
				|  |  | -    /** @private @const {!BufferDecoder} */
 | 
	
		
			
				|  |  | -    this.bufferDecoder_ = bufferDecoder;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  /**
 | 
	
		
			
				|  |  | -   * @param {number|undefined} pivot
 | 
	
		
			
				|  |  | -   * @return {!Storage<!Field>}
 | 
	
		
			
				|  |  | -   */
 | 
	
		
			
				|  |  | -  index(pivot) {
 | 
	
		
			
				|  |  | -    this.bufferDecoder_.setCursor(this.bufferDecoder_.startIndex());
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    const storage = new Storage(pivot);
 | 
	
		
			
				|  |  | -    while (this.bufferDecoder_.hasNext()) {
 | 
	
		
			
				|  |  | -      const tag = this.bufferDecoder_.getUnsignedVarint32();
 | 
	
		
			
				|  |  | -      const wireType = tagToWireType(tag);
 | 
	
		
			
				|  |  | -      const fieldNumber = tagToFieldNumber(tag);
 | 
	
		
			
				|  |  | -      checkCriticalState(
 | 
	
		
			
				|  |  | -          fieldNumber > 0, `Invalid field number ${fieldNumber}`);
 | 
	
		
			
				|  |  | +function buildIndex(bufferDecoder, pivot) {
 | 
	
		
			
				|  |  | +  bufferDecoder.setCursor(bufferDecoder.startIndex());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -      addIndexEntry(
 | 
	
		
			
				|  |  | -          storage, fieldNumber, wireType, this.bufferDecoder_.cursor());
 | 
	
		
			
				|  |  | +  const storage = new Storage(pivot);
 | 
	
		
			
				|  |  | +  while (bufferDecoder.hasNext()) {
 | 
	
		
			
				|  |  | +    const tag = bufferDecoder.getUnsignedVarint32();
 | 
	
		
			
				|  |  | +    const wireType = tagToWireType(tag);
 | 
	
		
			
				|  |  | +    const fieldNumber = tagToFieldNumber(tag);
 | 
	
		
			
				|  |  | +    checkCriticalState(fieldNumber > 0, `Invalid field number ${fieldNumber}`);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -      checkCriticalState(
 | 
	
		
			
				|  |  | -          !this.skipField_(wireType, fieldNumber),
 | 
	
		
			
				|  |  | -          'Found unmatched stop group.');
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -    return storage;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | +    addIndexEntry(storage, fieldNumber, wireType, bufferDecoder.cursor());
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  /**
 | 
	
		
			
				|  |  | -   * 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:
 | 
	
		
			
				|  |  | -        checkCriticalElementIndex(
 | 
	
		
			
				|  |  | -            this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
 | 
	
		
			
				|  |  | -        this.bufferDecoder_.skipVarint(this.bufferDecoder_.cursor());
 | 
	
		
			
				|  |  | -        return false;
 | 
	
		
			
				|  |  | -      case WireType.FIXED64:
 | 
	
		
			
				|  |  | -        this.bufferDecoder_.skip(8);
 | 
	
		
			
				|  |  | -        return false;
 | 
	
		
			
				|  |  | -      case WireType.DELIMITED:
 | 
	
		
			
				|  |  | -        checkCriticalElementIndex(
 | 
	
		
			
				|  |  | -            this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
 | 
	
		
			
				|  |  | -        const length = this.bufferDecoder_.getUnsignedVarint32();
 | 
	
		
			
				|  |  | -        this.bufferDecoder_.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.bufferDecoder_.skip(4);
 | 
	
		
			
				|  |  | -        return false;
 | 
	
		
			
				|  |  | -      default:
 | 
	
		
			
				|  |  | -        throw new Error(`Invalid wire type: ${wireType}`);
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | +    checkCriticalState(
 | 
	
		
			
				|  |  | +        !skipField_(bufferDecoder, wireType, fieldNumber),
 | 
	
		
			
				|  |  | +        'Found unmatched stop group.');
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +  return storage;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  /**
 | 
	
		
			
				|  |  | -   * 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.bufferDecoder_.hasNext()) {
 | 
	
		
			
				|  |  | -      const tag = this.bufferDecoder_.getUnsignedVarint32();
 | 
	
		
			
				|  |  | -      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;
 | 
	
		
			
				|  |  | +/**
 | 
	
		
			
				|  |  | + * Skips over fields until the next field of the message.
 | 
	
		
			
				|  |  | + * @param {!BufferDecoder} bufferDecoder
 | 
	
		
			
				|  |  | + * @param {!WireType} wireType
 | 
	
		
			
				|  |  | + * @param {number} fieldNumber
 | 
	
		
			
				|  |  | + * @return {boolean} Whether the field we skipped over was a stop group.
 | 
	
		
			
				|  |  | + * @private
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +function skipField_(bufferDecoder, wireType, fieldNumber) {
 | 
	
		
			
				|  |  | +  switch (wireType) {
 | 
	
		
			
				|  |  | +    case WireType.VARINT:
 | 
	
		
			
				|  |  | +      checkCriticalElementIndex(
 | 
	
		
			
				|  |  | +          bufferDecoder.cursor(), bufferDecoder.endIndex());
 | 
	
		
			
				|  |  | +      bufferDecoder.skipVarint(bufferDecoder.cursor());
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  | +    case WireType.FIXED64:
 | 
	
		
			
				|  |  | +      bufferDecoder.skip(8);
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  | +    case WireType.DELIMITED:
 | 
	
		
			
				|  |  | +      checkCriticalElementIndex(
 | 
	
		
			
				|  |  | +          bufferDecoder.cursor(), bufferDecoder.endIndex());
 | 
	
		
			
				|  |  | +      const length = bufferDecoder.getUnsignedVarint32();
 | 
	
		
			
				|  |  | +      bufferDecoder.skip(length);
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  | +    case WireType.START_GROUP:
 | 
	
		
			
				|  |  | +      checkCriticalState(
 | 
	
		
			
				|  |  | +          skipGroup_(bufferDecoder, 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:
 | 
	
		
			
				|  |  | +      bufferDecoder.skip(4);
 | 
	
		
			
				|  |  | +      return false;
 | 
	
		
			
				|  |  | +    default:
 | 
	
		
			
				|  |  | +      throw new Error(`Invalid wire type: ${wireType}`);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * Creates an index of field locations in a given binary protobuf.
 | 
	
		
			
				|  |  | + * Skips over fields until it finds the end of a given group.
 | 
	
		
			
				|  |  |   * @param {!BufferDecoder} bufferDecoder
 | 
	
		
			
				|  |  | - * @param {number|undefined} pivot
 | 
	
		
			
				|  |  | - * @return {!Storage<!Field>}
 | 
	
		
			
				|  |  | - * @package
 | 
	
		
			
				|  |  | + * @param {number} groupFieldNumber
 | 
	
		
			
				|  |  | + * @return {boolean} Returns true if an end was found.
 | 
	
		
			
				|  |  | + * @private
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -function buildIndex(bufferDecoder, pivot) {
 | 
	
		
			
				|  |  | -  return new Indexer(bufferDecoder).index(pivot);
 | 
	
		
			
				|  |  | +function skipGroup_(bufferDecoder, 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 (bufferDecoder.hasNext()) {
 | 
	
		
			
				|  |  | +    const tag = bufferDecoder.getUnsignedVarint32();
 | 
	
		
			
				|  |  | +    const wireType = tagToWireType(tag);
 | 
	
		
			
				|  |  | +    const fieldNumber = tagToFieldNumber(tag);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (skipField_(bufferDecoder, wireType, fieldNumber)) {
 | 
	
		
			
				|  |  | +      checkCriticalState(
 | 
	
		
			
				|  |  | +          groupFieldNumber === fieldNumber,
 | 
	
		
			
				|  |  | +          `Expected stop group for fieldnumber ${groupFieldNumber} not found.`);
 | 
	
		
			
				|  |  | +      return true;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +  return false;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  exports = {
 |