indexer.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. /**
  2. * @fileoverview Utilities to index a binary proto by fieldnumbers without
  3. * relying on strutural proto information.
  4. */
  5. goog.module('protobuf.binary.indexer');
  6. const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
  7. const Storage = goog.require('protobuf.binary.Storage');
  8. const WireType = goog.require('protobuf.binary.WireType');
  9. const {Field} = goog.require('protobuf.binary.field');
  10. const {checkCriticalElementIndex, checkCriticalState} = goog.require('protobuf.internal.checks');
  11. /**
  12. * Appends a new entry in the index array for the given field number.
  13. * @param {!Storage<!Field>} storage
  14. * @param {number} fieldNumber
  15. * @param {!WireType} wireType
  16. * @param {number} startIndex
  17. */
  18. function addIndexEntry(storage, fieldNumber, wireType, startIndex) {
  19. const field = storage.get(fieldNumber);
  20. if (field !== undefined) {
  21. field.addIndexEntry(wireType, startIndex);
  22. } else {
  23. storage.set(fieldNumber, Field.fromFirstIndexEntry(wireType, startIndex));
  24. }
  25. }
  26. /**
  27. * Returns wire type stored in a tag.
  28. * Protos store the wire type as the first 3 bit of a tag.
  29. * @param {number} tag
  30. * @return {!WireType}
  31. */
  32. function tagToWireType(tag) {
  33. return /** @type {!WireType} */ (tag & 0x07);
  34. }
  35. /**
  36. * Returns the field number stored in a tag.
  37. * Protos store the field number in the upper 29 bits of a 32 bit number.
  38. * @param {number} tag
  39. * @return {number}
  40. */
  41. function tagToFieldNumber(tag) {
  42. return tag >>> 3;
  43. }
  44. /**
  45. * An Indexer that indexes a given binary protobuf by fieldnumber.
  46. */
  47. class Indexer {
  48. /**
  49. * @param {!BufferDecoder} bufferDecoder
  50. * @private
  51. */
  52. constructor(bufferDecoder) {
  53. /** @private @const {!BufferDecoder} */
  54. this.bufferDecoder_ = bufferDecoder;
  55. }
  56. /**
  57. * @param {number|undefined} pivot
  58. * @return {!Storage<!Field>}
  59. */
  60. index(pivot) {
  61. this.bufferDecoder_.setCursor(this.bufferDecoder_.startIndex());
  62. const storage = new Storage(pivot);
  63. while (this.bufferDecoder_.hasNext()) {
  64. const tag = this.bufferDecoder_.getUnsignedVarint32();
  65. const wireType = tagToWireType(tag);
  66. const fieldNumber = tagToFieldNumber(tag);
  67. checkCriticalState(
  68. fieldNumber > 0, `Invalid field number ${fieldNumber}`);
  69. addIndexEntry(
  70. storage, fieldNumber, wireType, this.bufferDecoder_.cursor());
  71. checkCriticalState(
  72. !this.skipField_(wireType, fieldNumber),
  73. 'Found unmatched stop group.');
  74. }
  75. return storage;
  76. }
  77. /**
  78. * Skips over fields until the next field of the message.
  79. * @param {!WireType} wireType
  80. * @param {number} fieldNumber
  81. * @return {boolean} Whether the field we skipped over was a stop group.
  82. * @private
  83. */
  84. skipField_(wireType, fieldNumber) {
  85. switch (wireType) {
  86. case WireType.VARINT:
  87. checkCriticalElementIndex(
  88. this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
  89. this.bufferDecoder_.skipVarint(this.bufferDecoder_.cursor());
  90. return false;
  91. case WireType.FIXED64:
  92. this.bufferDecoder_.skip(8);
  93. return false;
  94. case WireType.DELIMITED:
  95. checkCriticalElementIndex(
  96. this.bufferDecoder_.cursor(), this.bufferDecoder_.endIndex());
  97. const length = this.bufferDecoder_.getUnsignedVarint32();
  98. this.bufferDecoder_.skip(length);
  99. return false;
  100. case WireType.START_GROUP:
  101. checkCriticalState(this.skipGroup_(fieldNumber), 'No end group found.');
  102. return false;
  103. case WireType.END_GROUP:
  104. // Signal that we found a stop group to the caller
  105. return true;
  106. case WireType.FIXED32:
  107. this.bufferDecoder_.skip(4);
  108. return false;
  109. default:
  110. throw new Error(`Invalid wire type: ${wireType}`);
  111. }
  112. }
  113. /**
  114. * Skips over fields until it finds the end of a given group.
  115. * @param {number} groupFieldNumber
  116. * @return {boolean} Returns true if an end was found.
  117. * @private
  118. */
  119. skipGroup_(groupFieldNumber) {
  120. // On a start group we need to keep skipping fields until we find a
  121. // corresponding stop group
  122. // Note: Since we are calling skipField from here nested groups will be
  123. // handled by recursion of this method and thus we will not see a nested
  124. // STOP GROUP here unless there is something wrong with the input data.
  125. while (this.bufferDecoder_.hasNext()) {
  126. const tag = this.bufferDecoder_.getUnsignedVarint32();
  127. const wireType = tagToWireType(tag);
  128. const fieldNumber = tagToFieldNumber(tag);
  129. if (this.skipField_(wireType, fieldNumber)) {
  130. checkCriticalState(
  131. groupFieldNumber === fieldNumber,
  132. `Expected stop group for fieldnumber ${
  133. groupFieldNumber} not found.`);
  134. return true;
  135. }
  136. }
  137. return false;
  138. }
  139. }
  140. /**
  141. * Creates an index of field locations in a given binary protobuf.
  142. * @param {!BufferDecoder} bufferDecoder
  143. * @param {number|undefined} pivot
  144. * @return {!Storage<!Field>}
  145. * @package
  146. */
  147. function buildIndex(bufferDecoder, pivot) {
  148. return new Indexer(bufferDecoder).index(pivot);
  149. }
  150. exports = {
  151. buildIndex,
  152. };