• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Tests for indexer.js.
3 */
4goog.module('protobuf.binary.IndexerTest');
5
6goog.setTestOnly();
7
8// Note to the reader:
9// Since the index behavior changes with the checking level some of the tests
10// in this file have to know which checking level is enabled to make correct
11// assertions.
12// Test are run in all checking levels.
13const BinaryStorage = goog.require('protobuf.runtime.BinaryStorage');
14const BufferDecoder = goog.require('protobuf.binary.BufferDecoder');
15const WireType = goog.require('protobuf.binary.WireType');
16const {CHECK_CRITICAL_STATE} = goog.require('protobuf.internal.checks');
17const {Field, IndexEntry} = goog.require('protobuf.binary.field');
18const {buildIndex} = goog.require('protobuf.binary.indexer');
19const {createBufferDecoder} = goog.require('protobuf.binary.bufferDecoderHelper');
20
21/**
22 * Returns the number of fields stored.
23 *
24 * @param {!BinaryStorage} storage
25 * @return {number}
26 */
27function getStorageSize(storage) {
28  let size = 0;
29  storage.forEach(() => void size++);
30  return size;
31}
32
33/**
34 * @type {number}
35 */
36const PIVOT = 1;
37
38/**
39 * Asserts a single IndexEntry at a given field number.
40 * @param {!BinaryStorage} storage
41 * @param {number} fieldNumber
42 * @param {...!IndexEntry} expectedEntries
43 */
44function assertStorageEntries(storage, fieldNumber, ...expectedEntries) {
45  expect(getStorageSize(storage)).toBe(1);
46
47  const entryArray = storage.get(fieldNumber).getIndexArray();
48  expect(entryArray).not.toBeUndefined();
49  expect(entryArray.length).toBe(expectedEntries.length);
50
51  for (let i = 0; i < entryArray.length; i++) {
52    const storageEntry = entryArray[i];
53    const expectedEntry = expectedEntries[i];
54
55    expect(storageEntry).toBe(expectedEntry);
56  }
57}
58
59describe('Indexer does', () => {
60  it('return empty storage for empty array', () => {
61    const storage = buildIndex(createBufferDecoder(), PIVOT);
62    expect(storage).not.toBeNull();
63    expect(getStorageSize(storage)).toBe(0);
64  });
65
66  it('throw for null array', () => {
67    expect(
68        () => buildIndex(
69            /** @type {!BufferDecoder} */ (/** @type {*} */ (null)), PIVOT))
70        .toThrow();
71  });
72
73  it('fail for invalid wire type (6)', () => {
74    expect(() => buildIndex(createBufferDecoder(0x0E, 0x01), PIVOT))
75        .toThrowError('Unexpected wire type: 6');
76  });
77
78  it('fail for invalid wire type (7)', () => {
79    expect(() => buildIndex(createBufferDecoder(0x0F, 0x01), PIVOT))
80        .toThrowError('Unexpected wire type: 7');
81  });
82
83  it('index varint', () => {
84    const data = createBufferDecoder(0x08, 0x01, 0x08, 0x01);
85    const storage = buildIndex(data, PIVOT);
86    assertStorageEntries(
87        storage, /* fieldNumber= */ 1,
88        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1),
89        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 3));
90  });
91
92  it('index varint with two bytes field number', () => {
93    const data = createBufferDecoder(0xF8, 0x01, 0x01);
94    const storage = buildIndex(data, PIVOT);
95    assertStorageEntries(
96        storage, /* fieldNumber= */ 31,
97        Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 2));
98  });
99
100  it('fail for varints that are longer than 10 bytes', () => {
101    const data = createBufferDecoder(
102        0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00);
103    if (CHECK_CRITICAL_STATE) {
104      expect(() => buildIndex(data, PIVOT))
105          .toThrowError('Index out of bounds: index: 12 size: 11');
106    } else {
107      // Note in unchecked mode we produce invalid output for invalid inputs.
108      // This test just documents our behavior in those cases.
109      // These values might change at any point and are not considered
110      // what the implementation should be doing here.
111      const storage = buildIndex(data, PIVOT);
112      assertStorageEntries(
113          storage, /* fieldNumber= */ 1,
114          Field.encodeIndexEntry(WireType.VARINT, /* startIndex= */ 1));
115    }
116  });
117
118  it('fail for varints with no data', () => {
119    const data = createBufferDecoder(0x08);
120    expect(() => buildIndex(data, PIVOT)).toThrow();
121  });
122
123  it('index fixed64', () => {
124    const data = createBufferDecoder(
125        /* first= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
126        /* second= */ 0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08);
127    const storage = buildIndex(data, PIVOT);
128    assertStorageEntries(
129        storage, /* fieldNumber= */ 1,
130        Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1),
131        Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 10));
132  });
133
134  it('fail for fixed64 data missing in input', () => {
135    const data =
136        createBufferDecoder(0x09, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07);
137    if (CHECK_CRITICAL_STATE) {
138      expect(() => buildIndex(data, PIVOT))
139          .toThrowError('Index out of bounds: index: 9 size: 8');
140    } else {
141      // Note in unchecked mode we produce invalid output for invalid inputs.
142      // This test just documents our behavior in those cases.
143      // These values might change at any point and are not considered
144      // what the implementation should be doing here.
145      const storage = buildIndex(data, PIVOT);
146      assertStorageEntries(
147          storage, /* fieldNumber= */ 1,
148          Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
149    }
150  });
151
152  it('fail for fixed64 tag that has no data after it', () => {
153    if (CHECK_CRITICAL_STATE) {
154      const data = createBufferDecoder(0x09);
155      expect(() => buildIndex(data, PIVOT))
156          .toThrowError('Index out of bounds: index: 9 size: 1');
157    } else {
158      // Note in unchecked mode we produce invalid output for invalid inputs.
159      // This test just documents our behavior in those cases.
160      // These values might change at any point and are not considered
161      // what the implementation should be doing here.
162      const data = createBufferDecoder(0x09);
163      const storage = buildIndex(data, PIVOT);
164      assertStorageEntries(
165          storage, /* fieldNumber= */ 1,
166          Field.encodeIndexEntry(WireType.FIXED64, /* startIndex= */ 1));
167    }
168  });
169
170  it('index delimited', () => {
171    const data = createBufferDecoder(
172        /* first= */ 0x0A, 0x02, 0x00, 0x01, /* second= */ 0x0A, 0x02, 0x00,
173        0x01);
174    const storage = buildIndex(data, PIVOT);
175    assertStorageEntries(
176        storage, /* fieldNumber= */ 1,
177        Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1),
178        Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 5));
179  });
180
181  it('fail for length deliimted field data missing in input', () => {
182    const data = createBufferDecoder(0x0A, 0x04, 0x00, 0x01);
183    if (CHECK_CRITICAL_STATE) {
184      expect(() => buildIndex(data, PIVOT))
185          .toThrowError('Index out of bounds: index: 6 size: 4');
186    } else {
187      // Note in unchecked mode we produce invalid output for invalid inputs.
188      // This test just documents our behavior in those cases.
189      // These values might change at any point and are not considered
190      // what the implementation should be doing here.
191      const storage = buildIndex(data, PIVOT);
192      assertStorageEntries(
193          storage, /* fieldNumber= */ 1,
194          Field.encodeIndexEntry(WireType.DELIMITED, /* startIndex= */ 1));
195    }
196  });
197
198  it('fail for delimited tag that has no data after it', () => {
199    const data = createBufferDecoder(0x0A);
200    expect(() => buildIndex(data, PIVOT)).toThrow();
201  });
202
203  it('index fixed32', () => {
204    const data = createBufferDecoder(
205        /* first= */ 0x0D, 0x01, 0x02, 0x03, 0x04, /* second= */ 0x0D, 0x01,
206        0x02, 0x03, 0x04);
207    const storage = buildIndex(data, PIVOT);
208    assertStorageEntries(
209        storage, /* fieldNumber= */ 1,
210        Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1),
211        Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 6));
212  });
213
214  it('fail for fixed32 data missing in input', () => {
215    const data = createBufferDecoder(0x0D, 0x01, 0x02, 0x03);
216
217    if (CHECK_CRITICAL_STATE) {
218      expect(() => buildIndex(data, PIVOT))
219          .toThrowError('Index out of bounds: index: 5 size: 4');
220    } else {
221      // Note in unchecked mode we produce invalid output for invalid inputs.
222      // This test just documents our behavior in those cases.
223      // These values might change at any point and are not considered
224      // what the implementation should be doing here.
225      const storage = buildIndex(data, PIVOT);
226      assertStorageEntries(
227          storage, /* fieldNumber= */ 1,
228          Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
229    }
230  });
231
232  it('fail for fixed32 tag that has no data after it', () => {
233    if (CHECK_CRITICAL_STATE) {
234      const data = createBufferDecoder(0x0D);
235      expect(() => buildIndex(data, PIVOT))
236          .toThrowError('Index out of bounds: index: 5 size: 1');
237    } else {
238      // Note in unchecked mode we produce invalid output for invalid inputs.
239      // This test just documents our behavior in those cases.
240      // These values might change at any point and are not considered
241      // what the implementation should be doing here.
242      const data = createBufferDecoder(0x0D);
243      const storage = buildIndex(data, PIVOT);
244      assertStorageEntries(
245          storage, /* fieldNumber= */ 1,
246          Field.encodeIndexEntry(WireType.FIXED32, /* startIndex= */ 1));
247    }
248  });
249
250  it('index group', () => {
251    const data = createBufferDecoder(
252        /* first= */ 0x0B, 0x08, 0x01, 0x0C, /* second= */ 0x0B, 0x08, 0x01,
253        0x0C);
254    const storage = buildIndex(data, PIVOT);
255    assertStorageEntries(
256        storage, /* fieldNumber= */ 1,
257        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1),
258        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 5));
259  });
260
261  it('index group and skips inner group', () => {
262    const data =
263        createBufferDecoder(0x0B, 0x0B, 0x08, 0x01, 0x0C, 0x08, 0x01, 0x0C);
264    const storage = buildIndex(data, PIVOT);
265    assertStorageEntries(
266        storage, /* fieldNumber= */ 1,
267        Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
268  });
269
270  it('fail on unmatched stop group', () => {
271    const data = createBufferDecoder(0x0C, 0x01);
272    expect(() => buildIndex(data, PIVOT))
273        .toThrowError('Unexpected wire type: 4');
274  });
275
276  it('fail for groups without matching stop group', () => {
277    const data = createBufferDecoder(0x0B, 0x08, 0x01, 0x1C);
278    if (CHECK_CRITICAL_STATE) {
279      expect(() => buildIndex(data, PIVOT))
280          .toThrowError('Expected stop group for fieldnumber 1 not found.');
281    } else {
282      // Note in unchecked mode we produce invalid output for invalid inputs.
283      // This test just documents our behavior in those cases.
284      // These values might change at any point and are not considered
285      // what the implementation should be doing here.
286      const storage = buildIndex(data, PIVOT);
287      assertStorageEntries(
288          storage, /* fieldNumber= */ 1,
289          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
290    }
291  });
292
293  it('fail for groups without stop group', () => {
294    const data = createBufferDecoder(0x0B, 0x08, 0x01);
295    if (CHECK_CRITICAL_STATE) {
296      expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
297    } else {
298      // Note in unchecked mode we produce invalid output for invalid inputs.
299      // This test just documents our behavior in those cases.
300      // These values might change at any point and are not considered
301      // what the implementation should be doing here.
302      const storage = buildIndex(data, PIVOT);
303      assertStorageEntries(
304          storage, /* fieldNumber= */ 1,
305          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
306    }
307  });
308
309  it('fail for group tag that has no data after it', () => {
310    const data = createBufferDecoder(0x0B);
311    if (CHECK_CRITICAL_STATE) {
312      expect(() => buildIndex(data, PIVOT)).toThrowError('No end group found.');
313    } else {
314      // Note in unchecked mode we produce invalid output for invalid inputs.
315      // This test just documents our behavior in those cases.
316      // These values might change at any point and are not considered
317      // what the implementation should be doing here.
318      const storage = buildIndex(data, PIVOT);
319      assertStorageEntries(
320          storage, /* fieldNumber= */ 1,
321          Field.encodeIndexEntry(WireType.START_GROUP, /* startIndex= */ 1));
322    }
323  });
324
325  it('index too large tag', () => {
326    const data = createBufferDecoder(0xF8, 0xFF, 0xFF, 0xFF, 0xFF);
327    expect(() => buildIndex(data, PIVOT)).toThrow();
328  });
329
330  it('fail for varint tag that has no data after it', () => {
331    const data = createBufferDecoder(0x08);
332    expect(() => buildIndex(data, PIVOT)).toThrow();
333  });
334});
335