indexer.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  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. * Creates an index of field locations in a given binary protobuf.
  46. * @param {!BufferDecoder} bufferDecoder
  47. * @param {number|undefined} pivot
  48. * @return {!Storage<!Field>}
  49. * @package
  50. */
  51. function buildIndex(bufferDecoder, pivot) {
  52. bufferDecoder.setCursor(bufferDecoder.startIndex());
  53. const storage = new Storage(pivot);
  54. while (bufferDecoder.hasNext()) {
  55. const tag = bufferDecoder.getUnsignedVarint32();
  56. const wireType = tagToWireType(tag);
  57. const fieldNumber = tagToFieldNumber(tag);
  58. checkCriticalState(fieldNumber > 0, `Invalid field number ${fieldNumber}`);
  59. addIndexEntry(storage, fieldNumber, wireType, bufferDecoder.cursor());
  60. checkCriticalState(
  61. !skipField_(bufferDecoder, wireType, fieldNumber),
  62. 'Found unmatched stop group.');
  63. }
  64. return storage;
  65. }
  66. /**
  67. * Skips over fields until the next field of the message.
  68. * @param {!BufferDecoder} bufferDecoder
  69. * @param {!WireType} wireType
  70. * @param {number} fieldNumber
  71. * @return {boolean} Whether the field we skipped over was a stop group.
  72. * @private
  73. */
  74. function skipField_(bufferDecoder, wireType, fieldNumber) {
  75. switch (wireType) {
  76. case WireType.VARINT:
  77. checkCriticalElementIndex(
  78. bufferDecoder.cursor(), bufferDecoder.endIndex());
  79. bufferDecoder.skipVarint(bufferDecoder.cursor());
  80. return false;
  81. case WireType.FIXED64:
  82. bufferDecoder.skip(8);
  83. return false;
  84. case WireType.DELIMITED:
  85. checkCriticalElementIndex(
  86. bufferDecoder.cursor(), bufferDecoder.endIndex());
  87. const length = bufferDecoder.getUnsignedVarint32();
  88. bufferDecoder.skip(length);
  89. return false;
  90. case WireType.START_GROUP:
  91. checkCriticalState(
  92. skipGroup_(bufferDecoder, fieldNumber), 'No end group found.');
  93. return false;
  94. case WireType.END_GROUP:
  95. // Signal that we found a stop group to the caller
  96. return true;
  97. case WireType.FIXED32:
  98. bufferDecoder.skip(4);
  99. return false;
  100. default:
  101. throw new Error(`Invalid wire type: ${wireType}`);
  102. }
  103. }
  104. /**
  105. * Skips over fields until it finds the end of a given group.
  106. * @param {!BufferDecoder} bufferDecoder
  107. * @param {number} groupFieldNumber
  108. * @return {boolean} Returns true if an end was found.
  109. * @private
  110. */
  111. function skipGroup_(bufferDecoder, groupFieldNumber) {
  112. // On a start group we need to keep skipping fields until we find a
  113. // corresponding stop group
  114. // Note: Since we are calling skipField from here nested groups will be
  115. // handled by recursion of this method and thus we will not see a nested
  116. // STOP GROUP here unless there is something wrong with the input data.
  117. while (bufferDecoder.hasNext()) {
  118. const tag = bufferDecoder.getUnsignedVarint32();
  119. const wireType = tagToWireType(tag);
  120. const fieldNumber = tagToFieldNumber(tag);
  121. if (skipField_(bufferDecoder, wireType, fieldNumber)) {
  122. checkCriticalState(
  123. groupFieldNumber === fieldNumber,
  124. `Expected stop group for fieldnumber ${groupFieldNumber} not found.`);
  125. return true;
  126. }
  127. }
  128. return false;
  129. }
  130. exports = {
  131. buildIndex,
  132. };