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