indexer.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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 {checkCriticalPositionIndex, 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. /** @private {number} */
  56. this.cursor_ = bufferDecoder.startIndex();
  57. }
  58. /**
  59. * @param {number|undefined} pivot
  60. * @return {!Storage<!Field>}
  61. */
  62. index(pivot) {
  63. const storage = new Storage(pivot);
  64. while (this.hasNextByte_()) {
  65. const tag = this.readVarInt32_();
  66. const wireType = tagToWireType(tag);
  67. const fieldNumber = tagToFieldNumber(tag);
  68. checkCriticalState(
  69. fieldNumber > 0, `Invalid field number ${fieldNumber}`);
  70. addIndexEntry(storage, fieldNumber, wireType, this.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. this.cursor_ = this.bufferDecoder_.skipVarint(this.cursor_);
  88. return false;
  89. case WireType.FIXED64:
  90. this.skip_(8);
  91. return false;
  92. case WireType.DELIMITED:
  93. const length = this.readVarInt32_();
  94. this.skip_(length);
  95. return false;
  96. case WireType.START_GROUP:
  97. checkCriticalState(this.skipGroup_(fieldNumber), 'No end group found.');
  98. return false;
  99. case WireType.END_GROUP:
  100. // Signal that we found a stop group to the caller
  101. return true;
  102. case WireType.FIXED32:
  103. this.skip_(4);
  104. return false;
  105. default:
  106. throw new Error(`Invalid wire type: ${wireType}`);
  107. }
  108. }
  109. /**
  110. * Seeks forward by the given amount.
  111. * @param {number} skipAmount
  112. * @private
  113. */
  114. skip_(skipAmount) {
  115. this.cursor_ += skipAmount;
  116. checkCriticalPositionIndex(this.cursor_, this.bufferDecoder_.endIndex());
  117. }
  118. /**
  119. * Skips over fields until it finds the end of a given group.
  120. * @param {number} groupFieldNumber
  121. * @return {boolean} Returns true if an end was found.
  122. * @private
  123. */
  124. skipGroup_(groupFieldNumber) {
  125. // On a start group we need to keep skipping fields until we find a
  126. // corresponding stop group
  127. // Note: Since we are calling skipField from here nested groups will be
  128. // handled by recursion of this method and thus we will not see a nested
  129. // STOP GROUP here unless there is something wrong with the input data.
  130. while (this.hasNextByte_()) {
  131. const tag = this.readVarInt32_();
  132. const wireType = tagToWireType(tag);
  133. const fieldNumber = tagToFieldNumber(tag);
  134. if (this.skipField_(wireType, fieldNumber)) {
  135. checkCriticalState(
  136. groupFieldNumber === fieldNumber,
  137. `Expected stop group for fieldnumber ${
  138. groupFieldNumber} not found.`);
  139. return true;
  140. }
  141. }
  142. return false;
  143. }
  144. /**
  145. * Returns a JS number for a 32 bit var int.
  146. * @return {number}
  147. * @private
  148. */
  149. readVarInt32_() {
  150. const {lowBits, dataStart} = this.bufferDecoder_.getVarint(this.cursor_);
  151. this.cursor_ = dataStart;
  152. return lowBits;
  153. }
  154. /**
  155. * Returns true if there are more bytes to read in the array.
  156. * @return {boolean}
  157. * @private
  158. */
  159. hasNextByte_() {
  160. return this.cursor_ < this.bufferDecoder_.endIndex();
  161. }
  162. }
  163. /**
  164. * Creates an index of field locations in a given binary protobuf.
  165. * @param {!BufferDecoder} bufferDecoder
  166. * @param {number|undefined} pivot
  167. * @return {!Storage<!Field>}
  168. * @package
  169. */
  170. function buildIndex(bufferDecoder, pivot) {
  171. return new Indexer(bufferDecoder).index(pivot);
  172. }
  173. exports = {
  174. buildIndex,
  175. };