cbor-x 1.6.7 → 1.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,7 +16,6 @@
16
16
  const RECORD_INLINE_ID = 0xdfff; // temporary first-come first-serve tag // proposed tag: 0x7265 // 're'
17
17
  const BUNDLED_STRINGS_ID = 0xdff9;
18
18
  const PACKED_REFERENCE_TAG_ID = 6;
19
- const STOP_CODE = {};
20
19
  let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
21
20
  // for JSC, but this is pretty large and should be sufficient for most use cases
22
21
  let maxMapSize = 16810000; // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
@@ -125,16 +124,11 @@
125
124
  // re-entrant execution, save the state and restore it after we do this decode
126
125
  return saveState(() => {
127
126
  clearSource();
128
- try {
129
- return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
130
- } catch (error) {
131
- if (error && typeof error == 'object')
132
- nestedDecodeErrors.add(error);
133
- throw error
134
- }
127
+ return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
135
128
  })
136
129
  }
137
- srcEnd = end > -1 ? end : source.length;
130
+ // past the view, the JS string readers would decode the missing bytes as zeros instead of failing as truncated
131
+ srcEnd = end > -1 && end < source.length ? end : source.length;
138
132
  position$1 = 0;
139
133
  srcStringEnd = 0;
140
134
  srcString = null;
@@ -172,18 +166,28 @@
172
166
  return checkedRead()
173
167
  }
174
168
  decodeMultiple(source, forEach) {
169
+ if (src) {
170
+ // Keep the complete nested sequence inside its own decoder state.
171
+ return saveState(() => {
172
+ clearSource();
173
+ return this ? this.decodeMultiple(source, forEach) : defaultDecoder.decodeMultiple(source, forEach)
174
+ })
175
+ }
175
176
  let values, lastPosition = 0;
176
177
  try {
177
178
  let size = source.length;
178
179
  sequentialMode = true;
179
180
  let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size);
180
181
  if (forEach) {
182
+ let end = position$1;
181
183
  if (forEach(value) === false) {
182
184
  return
183
185
  }
184
- while(position$1 < size) {
185
- lastPosition = position$1;
186
- if (forEach(checkedRead()) === false) {
186
+ while(end < size) {
187
+ lastPosition = end;
188
+ value = checkedRead();
189
+ end = position$1;
190
+ if (forEach(value) === false) {
187
191
  return
188
192
  }
189
193
  }
@@ -197,8 +201,10 @@
197
201
  return values
198
202
  }
199
203
  } catch(error) {
200
- error.lastPosition = lastPosition;
201
- error.values = values;
204
+ if (error && (typeof error == 'object' || typeof error == 'function')) {
205
+ error.lastPosition = lastPosition;
206
+ error.values = values;
207
+ }
202
208
  throw error
203
209
  } finally {
204
210
  sequentialMode = false;
@@ -207,8 +213,10 @@
207
213
  }
208
214
  }
209
215
  const nestedDecodeErrors = new WeakSet();
216
+ let arrayAllocationBudget;
210
217
  function checkedRead() {
211
218
  try {
219
+ arrayAllocationBudget = srcEnd - position$1;
212
220
  let result = read();
213
221
  if (bundledStrings$1) {
214
222
  if (position$1 >= bundledStrings$1.postBundlePosition)
@@ -250,9 +258,21 @@
250
258
  }
251
259
  }
252
260
 
253
- function endOfCBORError() {
261
+ function readBreak() {
262
+ if (position$1 < srcEnd && src[position$1] === 0xff) {
263
+ position$1++;
264
+ return true
265
+ }
266
+ return false
267
+ }
268
+ // neededEnd, from a length check, is a position that the source must extend to before the item can decode. It is a
269
+ // lower bound for any completion of the source: every byte read before the check was within the source, so a longer
270
+ // source reaches the same check with the same length. Streams wait for it instead of decoding on every chunk.
271
+ function endOfCBORError(neededEnd) {
254
272
  let error = new Error('Unexpected end of CBOR data');
255
273
  error.incomplete = true;
274
+ if (neededEnd)
275
+ error.neededEnd = neededEnd;
256
276
  return error
257
277
  }
258
278
 
@@ -304,6 +324,9 @@
304
324
  } else if (currentDecoder.int64AsNumber) {
305
325
  token = dataView.getUint32(position$1) * 0x100000000;
306
326
  token += dataView.getUint32(position$1 + 4);
327
+ position$1 += 8;
328
+ if (majorType === 1) return -1 - token
329
+ break
307
330
  } else token = dataView.getBigUint64(position$1);
308
331
  position$1 += 8;
309
332
  break
@@ -315,10 +338,10 @@
315
338
  throw new Error('Indefinite length not supported for byte or text strings')
316
339
  case 4: // array
317
340
  let array = [];
318
- let value, i = 0;
319
- while ((value = read()) != STOP_CODE) {
341
+ let i = 0;
342
+ while (!readBreak()) {
320
343
  if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
321
- array[i++] = value;
344
+ array[i++] = read();
322
345
  }
323
346
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
324
347
  case 5: // map
@@ -327,13 +350,15 @@
327
350
  let object = {};
328
351
  let i = 0;
329
352
  if (currentDecoder.keyMap) {
330
- while((key = read()) != STOP_CODE) {
353
+ while (!readBreak()) {
354
+ key = read();
331
355
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
332
356
  object[safeKey(currentDecoder.decodeKey(key))] = read();
333
357
  }
334
358
  }
335
359
  else {
336
- while ((key = read()) != STOP_CODE) {
360
+ while (!readBreak()) {
361
+ key = read();
337
362
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
338
363
  object[safeKey(key)] = read();
339
364
  }
@@ -347,7 +372,8 @@
347
372
  let map = new Map();
348
373
  if (currentDecoder.keyMap) {
349
374
  let i = 0;
350
- while((key = read()) != STOP_CODE) {
375
+ while (!readBreak()) {
376
+ key = read();
351
377
  if (i++ >= maxMapSize) {
352
378
  throw new Error(`Map size exceeds ${maxMapSize}`);
353
379
  }
@@ -356,7 +382,8 @@
356
382
  }
357
383
  else {
358
384
  let i = 0;
359
- while ((key = read()) != STOP_CODE) {
385
+ while (!readBreak()) {
386
+ key = read();
360
387
  if (i++ >= maxMapSize) {
361
388
  throw new Error(`Map size exceeds ${maxMapSize}`);
362
389
  }
@@ -366,7 +393,8 @@
366
393
  return map
367
394
  }
368
395
  case 7:
369
- return STOP_CODE
396
+ // readBreak consumes every break that ends an indefinite-length item
397
+ throw new Error('Unexpected break stop code outside of an indefinite-length item')
370
398
  default:
371
399
  throw new Error('Invalid major type for indefinite length ' + majorType)
372
400
  }
@@ -380,13 +408,13 @@
380
408
  case 1: // negative int
381
409
  return ~token
382
410
  case 2: // buffer
383
- if (token > srcEnd - position$1) throw endOfCBORError()
411
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
384
412
  return readBin(token)
385
413
  case 3: // string
386
414
  if (srcStringEnd >= position$1) { // already extracted, which only covers complete strings
387
415
  return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart)
388
416
  }
389
- if (token > srcEnd - position$1) throw endOfCBORError()
417
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
390
418
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
391
419
  // for small blocks, avoiding the overhead of the extract call is helpful
392
420
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
@@ -398,7 +426,9 @@
398
426
  if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
399
427
  // every element occupies at least one byte, so a length beyond what remains in the source can
400
428
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
401
- if (token > srcEnd - position$1) throw endOfCBORError()
429
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
430
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
431
+ if (token > srcEnd - position$1 || (arrayAllocationBudget -= token) < 0) throw endOfCBORError(position$1 + token)
402
432
  let array = new Array(token);
403
433
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
404
434
  //else
@@ -407,7 +437,7 @@
407
437
  case 5: // map
408
438
  if (currentDecoder.mapsAsObjects) {
409
439
  if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
410
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError() // each entry needs at least two bytes
440
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token) // each entry needs at least two bytes
411
441
  let object = {};
412
442
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
413
443
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
@@ -418,7 +448,7 @@
418
448
  restoreMapsAsObject = false;
419
449
  }
420
450
  if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
421
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError()
451
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token)
422
452
  let map = new Map();
423
453
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
424
454
  else for (let i = 0; i < token; i++) map.set(read(), read());
@@ -899,6 +929,7 @@
899
929
  }
900
930
  return object
901
931
  };
932
+ const legacyRecordDefinition = currentExtensions[LEGACY_RECORD_INLINE_ID];
902
933
  currentExtensions[14] = (value) => {
903
934
  if (bundledStrings$1)
904
935
  return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value)
@@ -1171,6 +1202,9 @@
1171
1202
  }
1172
1203
 
1173
1204
  function saveState(callback) {
1205
+ let savedFragmentEnd = fragmentEnd;
1206
+ let savedFragmentReader = fragmentReadString;
1207
+ let savedReadFixedString = readFixedString;
1174
1208
  let savedSrcEnd = srcEnd;
1175
1209
  let savedPosition = position$1;
1176
1210
  let savedSrcStringStart = srcStringStart;
@@ -1178,6 +1212,7 @@
1178
1212
  let savedSrcString = srcString;
1179
1213
  let savedReferenceMap = referenceMap;
1180
1214
  let savedBundledStrings = bundledStrings$1;
1215
+ let savedPackedValues = packedValues;
1181
1216
 
1182
1217
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
1183
1218
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -1185,10 +1220,23 @@
1185
1220
  let savedDecoder = currentDecoder;
1186
1221
  let savedSequentialMode = sequentialMode;
1187
1222
  let savedRestoreMapsAsObject = restoreMapsAsObject;
1223
+ let savedArrayAllocationBudget = arrayAllocationBudget;
1224
+ fragmentEnd = 0;
1225
+ if (fragmentReadString) readFixedString = fragmentReadString;
1226
+ fragmentReadString = null;
1188
1227
  restoreMapsAsObject = false;
1189
1228
  try {
1190
1229
  return callback()
1230
+ } catch (error) {
1231
+ // the callback decodes its own source (a shared structures blob, or an extension's embedded data), so a
1232
+ // truncation there cannot be completed by more of the outer input; checkedRead() clears the flag
1233
+ if (error && (typeof error == 'object' || typeof error == 'function'))
1234
+ nestedDecodeErrors.add(error);
1235
+ throw error
1191
1236
  } finally {
1237
+ fragmentEnd = savedFragmentEnd;
1238
+ fragmentReadString = savedFragmentReader;
1239
+ readFixedString = savedReadFixedString;
1192
1240
  srcEnd = savedSrcEnd;
1193
1241
  position$1 = savedPosition;
1194
1242
  srcStringStart = savedSrcStringStart;
@@ -1196,11 +1244,13 @@
1196
1244
  srcString = savedSrcString;
1197
1245
  referenceMap = savedReferenceMap;
1198
1246
  bundledStrings$1 = savedBundledStrings;
1247
+ packedValues = savedPackedValues;
1199
1248
  src = savedSrc;
1200
1249
  sequentialMode = savedSequentialMode;
1201
1250
  currentStructures = savedStructures;
1202
1251
  currentDecoder = savedDecoder;
1203
1252
  restoreMapsAsObject = savedRestoreMapsAsObject;
1253
+ arrayAllocationBudget = savedArrayAllocationBudget;
1204
1254
  dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1205
1255
  }
1206
1256
  }
@@ -1210,8 +1260,82 @@
1210
1260
  currentStructures = null;
1211
1261
  }
1212
1262
 
1263
+ const registeredExtensions = new Map();
1264
+ let fragmentEnd = 0;
1265
+ let fragmentReadString;
1266
+
1267
+ const builtinScanTags = new Map([[LEGACY_RECORD_INLINE_ID, legacyRecordDefinition], [259, currentExtensions[259]]]);
1268
+ function isBuiltinScanTag(tag) {
1269
+ return builtinScanTags.has(tag) && currentExtensions[tag] === builtinScanTags.get(tag)
1270
+ }
1271
+
1272
+ function getScanTag(tag, decoder) {
1273
+ return tag >= BUNDLED_STRINGS_ID && decoder.structures && decoder.structures[tag & 0x1fff]
1274
+ }
1275
+
1276
+ function checkScanContainer(major, length, mapsAsObjects) {
1277
+ if (major === 4) {
1278
+ if (length > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
1279
+ } else if (mapsAsObjects) {
1280
+ if (length > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
1281
+ } else if (length > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
1282
+ }
1283
+
1284
+ function decodeScanned(decoder, source, end) {
1285
+ const boundStrings = isNativeAccelerationEnabled ;
1286
+ if (!registeredExtensions.size && !boundStrings) return decoder.decodeMultiple(source)
1287
+ const savedEnd = fragmentEnd;
1288
+ const savedReader = readFixedString;
1289
+ const savedFragmentReader = fragmentReadString;
1290
+ const originals = [];
1291
+ fragmentEnd = end;
1292
+ fragmentReadString = savedReader;
1293
+ try {
1294
+ if (boundStrings) ;
1295
+ for (const [tag, extension] of registeredExtensions) {
1296
+ if (!extension.handlesRead) continue
1297
+ const original = currentExtensions[tag];
1298
+ const guarded = (read) => {
1299
+ if (!fragmentEnd || position$1 >= fragmentEnd) return extension(read)
1300
+ let calls = 0, framingError;
1301
+ const value = extension(() => {
1302
+ if (calls++) {
1303
+ framingError = new Error('Tag ' + tag + ': fragmented decoding cannot frame multi-item handlesRead extensions');
1304
+ throw framingError
1305
+ }
1306
+ return read()
1307
+ });
1308
+ if (framingError) throw framingError
1309
+ return value
1310
+ };
1311
+ guarded.handlesRead = true;
1312
+ originals.push([tag, original, guarded]);
1313
+ currentExtensions[tag] = guarded;
1314
+ }
1315
+ return decoder.decodeMultiple(source)
1316
+ } finally {
1317
+ fragmentEnd = savedEnd;
1318
+ fragmentReadString = savedFragmentReader;
1319
+ readFixedString = savedReader;
1320
+ for (const [tag, original, guarded] of originals) {
1321
+ if (currentExtensions[tag] === guarded) currentExtensions[tag] = original;
1322
+ }
1323
+ }
1324
+ }
1325
+
1326
+ // Internal streaming hooks; not part of the public decoder API.
1327
+ const _stream = {
1328
+ getTag: getScanTag,
1329
+ isBuiltinTag: isBuiltinScanTag,
1330
+ checkContainer: checkScanContainer,
1331
+ decode: decodeScanned,
1332
+ get readThreshold() { return inlineObjectReadThreshold }
1333
+ };
1334
+
1213
1335
  function addExtension$1(extension) {
1214
1336
  currentExtensions[extension.tag] = extension.decode;
1337
+ if (typeof extension.decode === 'function') registeredExtensions.set(extension.tag, extension.decode);
1338
+ else registeredExtensions.delete(extension.tag);
1215
1339
  }
1216
1340
 
1217
1341
  function setSizeLimits(limits) {
@@ -1811,6 +1935,11 @@
1811
1935
  return encode(json)
1812
1936
  }
1813
1937
 
1938
+ // not in extensions, so class instances that reach writeObject do not pay another instanceof
1939
+ if (constructor === DataView) {
1940
+ writeBuffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength), makeRoom);
1941
+ return
1942
+ }
1814
1943
  // no extension found, write as a plain object
1815
1944
  writeObject(value);
1816
1945
  }
@@ -1904,8 +2033,22 @@
1904
2033
  size++;
1905
2034
  }
1906
2035
  }
1907
- target[objectOffset++ + start] = size >> 8;
1908
- target[objectOffset + start] = size & 0xff;
2036
+ if (size < 0x10000) {
2037
+ // fits in a map-16 (uint16) count: back-patch the reserved 2 bytes
2038
+ target[objectOffset++ + start] = size >> 8;
2039
+ target[objectOffset + start] = size & 0xff;
2040
+ } else {
2041
+ // too many entries for a map-16 count: widen the already-written header
2042
+ // to map-32, mirroring writeArrayHeader's 0x9a/setUint32 path. Shift the
2043
+ // encoded entries 2 bytes forward to make room for the wider (uint32) count.
2044
+ if (position + 2 > safeEnd)
2045
+ makeRoom(position + 2);
2046
+ let headerPosition = objectOffset + start;
2047
+ target.copyWithin(headerPosition + 4, headerPosition + 2, position);
2048
+ position += 2;
2049
+ target[headerPosition - 1] = 0xba; // map 32
2050
+ targetView.setUint32(headerPosition, size);
2051
+ }
1909
2052
  } :
1910
2053
  (object, skipValues) => {
1911
2054
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
@@ -2472,6 +2615,258 @@
2472
2615
  const RESET_BUFFER_MODE = 1024;
2473
2616
  const THROW_ON_ITERABLE = 2048;
2474
2617
 
2618
+ // Positions are relative to the retained value, including bundle pointers. Frames and their storage survive appends.
2619
+ class StructureScanner {
2620
+ constructor (decoder) {
2621
+ this.decoder = decoder;
2622
+ this.frames = [];
2623
+ this.records = new Map();
2624
+ this.readers = new Map();
2625
+ this.reset();
2626
+ }
2627
+
2628
+ reset () {
2629
+ this.position = 0;
2630
+ this.depth = 0;
2631
+ this.neededLength = 0;
2632
+ this.postBundlePosition = 0;
2633
+ this.headers = 0;
2634
+ this.mapsAsObjects = !!this.decoder.mapsAsObjects;
2635
+ this.restoreMap = false;
2636
+ this.records.clear();
2637
+ this.readers.clear();
2638
+ this.push('items', 1);
2639
+ }
2640
+
2641
+ push (kind, remaining) {
2642
+ if (this.depth === 16384) throw new RangeError('Maximum CBOR nesting depth exceeded')
2643
+ const frame = this.frames[this.depth] || (this.frames[this.depth] = {});
2644
+ frame.kind = kind;
2645
+ frame.remaining = remaining;
2646
+ frame.stage = 0;
2647
+ frame.id = undefined;
2648
+ frame.restoreMap = false;
2649
+ frame.legacy = false;
2650
+ frame.definition = undefined;
2651
+ frame.limitMajor = 0;
2652
+ frame.count = 0;
2653
+ this.depth++;
2654
+ return frame
2655
+ }
2656
+
2657
+ header (source, end, lengthOnly = false) {
2658
+ const start = this.position;
2659
+ if (start >= end) {
2660
+ this.neededLength = start + 1;
2661
+ return false
2662
+ }
2663
+ const byte = source[start];
2664
+ const major = byte >> 5;
2665
+ const info = byte & 31;
2666
+ const width = info < 24 || (lengthOnly && info > 26) ? 0 : info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : info === 27 ? 8 : 0;
2667
+ if (!lengthOnly && info >= 28 && info <= 30) throw new Error('Unknown token ' + info)
2668
+ if (start + 1 + width > end) {
2669
+ this.neededLength = start + 1 + width;
2670
+ return false
2671
+ }
2672
+ let value = info;
2673
+ if (width) {
2674
+ value = 0;
2675
+ for (let i = start + 1; i <= start + width; i++) value = value * 256 + source[i];
2676
+ if (width === 8 && major > 1 && major !== 7 && value > 0xffffffff) {
2677
+ throw new Error('JavaScript does not support arrays, maps, or strings with length over 4294967295')
2678
+ }
2679
+ }
2680
+ this.major = major;
2681
+ this.info = info;
2682
+ this.argument = value;
2683
+ this.position = start + 1 + width;
2684
+ this.headers++;
2685
+ return true
2686
+ }
2687
+
2688
+ scan (source, end) {
2689
+ while (this.depth) {
2690
+ if (this.position > end) {
2691
+ this.neededLength = this.position;
2692
+ return false
2693
+ }
2694
+ const frame = this.frames[this.depth - 1];
2695
+ if (frame.kind === 'bundle') {
2696
+ if (frame.stage === 0) {
2697
+ if (!this.header(source, end, true)) return false
2698
+ frame.remaining = Math.max(0, this.argument - 2);
2699
+ frame.stage = 1;
2700
+ }
2701
+ if (frame.stage === 1) {
2702
+ const offsetStart = this.position;
2703
+ if (!this.header(source, end)) return false
2704
+ if (this.major > 1 || this.info === 31) throw new Error('Invalid bundled string offset')
2705
+ frame.target = offsetStart + (this.major === 1 ? -1 - this.argument : this.argument);
2706
+ if (!Number.isSafeInteger(frame.target) || frame.target < 0) throw new Error('Invalid bundled string offset')
2707
+ frame.stage = 2;
2708
+ }
2709
+ if (frame.stage === 2) {
2710
+ if (frame.remaining) {
2711
+ if (!this.header(source, end, true)) return false
2712
+ this.position += this.argument;
2713
+ frame.remaining--;
2714
+ continue
2715
+ }
2716
+ frame.resume = this.position;
2717
+ this.position = frame.target;
2718
+ frame.remaining = 2;
2719
+ frame.stage = 3;
2720
+ continue
2721
+ }
2722
+ if (frame.stage === 3) {
2723
+ if (frame.remaining) {
2724
+ if (!this.header(source, end, true)) return false
2725
+ this.position += this.argument;
2726
+ frame.remaining--;
2727
+ continue
2728
+ }
2729
+ this.postBundlePosition = this.position;
2730
+ this.position = frame.resume;
2731
+ frame.kind = 'items';
2732
+ frame.remaining = 1;
2733
+ continue
2734
+ }
2735
+ }
2736
+ if (frame.kind === 'record') {
2737
+ if (!this.header(source, end, true)) return false
2738
+ if (this.info > 26) throw new Error('Expected array header, but got ' + source[this.position - 1])
2739
+ frame.kind = 'items';
2740
+ frame.remaining = this.argument;
2741
+ const reader = frame.reader;
2742
+ if (reader.compiled.has(this.argument)) frame.remaining = Math.min(this.argument, reader.length);
2743
+ else if (reader.slowReads++ >= _stream.readThreshold) {
2744
+ reader.compiled.add(this.argument);
2745
+ frame.remaining = Math.min(this.argument, reader.length);
2746
+ }
2747
+ continue
2748
+ }
2749
+ if (frame.kind === 'definition' && frame.stage === 0) {
2750
+ const start = this.position;
2751
+ if (!this.header(source, end, !frame.legacy)) return false
2752
+ if (frame.legacy && this.major !== 4) {
2753
+ this.position = start;
2754
+ this.headers--;
2755
+ frame.kind = 'items';
2756
+ frame.remaining = 1;
2757
+ continue
2758
+ }
2759
+ frame.remaining = frame.legacy ? (this.info === 31 ? -1 : this.argument) : Math.max(2, this.argument);
2760
+ if (frame.legacy) {
2761
+ if (frame.remaining >= 0) _stream.checkContainer(4, frame.remaining);
2762
+ else frame.limitMajor = 4;
2763
+ frame.index = 0;
2764
+ }
2765
+ frame.total = frame.remaining;
2766
+ frame.stage = 1;
2767
+ continue
2768
+ }
2769
+ if (frame.remaining === 0) {
2770
+ if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition);
2771
+ if (frame.restoreMap && this.restoreMap) {
2772
+ this.mapsAsObjects = true;
2773
+ this.restoreMap = false;
2774
+ }
2775
+ this.depth--;
2776
+ continue
2777
+ }
2778
+ if (!this.header(source, end)) return false
2779
+ let { major, argument, info } = this;
2780
+ if (frame.kind === 'definition') {
2781
+ const index = frame.legacy ? frame.index++ : frame.total - frame.remaining;
2782
+ if (index === 0 && major === 0) frame.id = argument;
2783
+ if ((frame.multiple ? index >= 1 && index < frame.total - 1 : index === 1) && major === 4 && info !== 31 && frame.id !== undefined) {
2784
+ const definition = { length: argument, slowReads: 0, compiled: new Set() };
2785
+ if (frame.legacy) frame.definition = definition;
2786
+ else this.records.set(frame.id - 0xe000 + (frame.multiple ? index - 1 : 0), definition);
2787
+ }
2788
+ }
2789
+ if (major === 7 && info === 31 && (frame.remaining === -1 || frame.remaining === -2)) {
2790
+ if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition);
2791
+ this.depth--;
2792
+ continue
2793
+ }
2794
+ if (frame.limitMajor && (frame.remaining === -1 || frame.remaining === -2)) {
2795
+ _stream.checkContainer(frame.limitMajor, ++frame.count, frame.mapsAsObjects);
2796
+ }
2797
+ if (frame.remaining > 0) frame.remaining--;
2798
+ else if (frame.remaining === -2) frame.remaining = -3;
2799
+ else if (frame.remaining === -3) frame.remaining = -2;
2800
+ if (info === 31) {
2801
+ if (major === 2 || major === 3) throw new Error('Indefinite length not supported for byte or text strings')
2802
+ if (major === 4 || major === 5) {
2803
+ const container = this.push('items', major === 4 ? -1 : -2);
2804
+ container.limitMajor = major;
2805
+ container.mapsAsObjects = this.mapsAsObjects;
2806
+ if (major === 5 && this.restoreMap) {
2807
+ this.mapsAsObjects = true;
2808
+ this.restoreMap = false;
2809
+ }
2810
+ }
2811
+ else if (major === 7) throw new Error('Unexpected break stop code outside of an indefinite-length item')
2812
+ else throw new Error('Invalid major type for indefinite length ' + major)
2813
+ continue
2814
+ }
2815
+ switch (major) {
2816
+ case 2:
2817
+ case 3:
2818
+ this.position += argument;
2819
+ break
2820
+ case 4:
2821
+ case 5:
2822
+ _stream.checkContainer(major, argument, this.mapsAsObjects);
2823
+ if (major === 5 && this.restoreMap) {
2824
+ this.mapsAsObjects = true;
2825
+ this.restoreMap = false;
2826
+ }
2827
+ if (argument) this.push('items', major === 5 ? argument * 2 : argument);
2828
+ break
2829
+ case 6: {
2830
+ const structure = _stream.getTag(argument, this.decoder);
2831
+ let reader = this.records.get(argument & 0x1fff);
2832
+ if (argument >= 0xdff9 && (structure || reader)) {
2833
+ if (!reader) {
2834
+ reader = this.readers.get(structure);
2835
+ if (!reader) {
2836
+ reader = { length: structure.length, slowReads: structure.slowReads || 0, compiled: new Set() };
2837
+ for (let compiled = structure.compiledReader; compiled; compiled = compiled.next) reader.compiled.add(compiled.propertyCount);
2838
+ this.readers.set(structure, reader);
2839
+ }
2840
+ }
2841
+ this.push('record').reader = reader;
2842
+ } else if (argument === 0xdff9) {
2843
+ this.push('bundle');
2844
+ } else if (argument === 0xdfff || argument === 0xdffe) {
2845
+ this.push('definition').multiple = argument === 0xdffe;
2846
+ } else if (argument === 259 && _stream.isBuiltinTag(argument) && this.mapsAsObjects) {
2847
+ this.push('items', 1).restoreMap = true;
2848
+ this.mapsAsObjects = false;
2849
+ this.restoreMap = true;
2850
+ } else if (argument === 105 && _stream.isBuiltinTag(argument)) {
2851
+ const definition = this.push('definition');
2852
+ definition.multiple = false;
2853
+ definition.legacy = true;
2854
+ } else {
2855
+ this.push('items', 1);
2856
+ }
2857
+ break
2858
+ }
2859
+ }
2860
+ }
2861
+ if (this.postBundlePosition) {
2862
+ if (this.position >= this.postBundlePosition) throw new Error('Unexpected bundle position')
2863
+ this.position = this.postBundlePosition;
2864
+ }
2865
+ if (this.frames.length > 256) this.frames.length = 256;
2866
+ return true
2867
+ }
2868
+ }
2869
+
2475
2870
  /**
2476
2871
  * Given an Iterable first argument, returns an Iterable where each value is encoded as a Buffer
2477
2872
  * If the argument is only Async Iterable, the return value will be an Async Iterable.
@@ -2509,7 +2904,7 @@
2509
2904
  * Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
2510
2905
  * Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
2511
2906
  * @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
2512
- * @param {object} [options] - Decoder options
2907
+ * @param {object} [options] - Decoder options, and maxIncompleteBufferSize
2513
2908
  * @returns {IterableIterator|Promise.<AsyncIterableIterator}
2514
2909
  */
2515
2910
  function decodeIter (bufferIterator, options = {}) {
@@ -2518,26 +2913,15 @@
2518
2913
  }
2519
2914
 
2520
2915
  const decoder = new Decoder(options);
2521
- let incomplete, fatalError;
2916
+ const chunkDecoder = new ChunkDecoder(decoder, options.maxIncompleteBufferSize);
2917
+ let fatalError;
2522
2918
  const parser = (chunk) => {
2523
- let yields;
2524
- // if there's incomplete data from previous chunk, concatinate and try again
2525
- if (incomplete) {
2526
- chunk = Buffer.concat([incomplete, chunk]);
2527
- incomplete = undefined;
2528
- }
2529
-
2530
2919
  try {
2531
- yields = decoder.decodeMultiple(chunk);
2532
- } catch (err) {
2533
- yields = err.values;
2534
- if (err.incomplete) {
2535
- incomplete = chunk.slice(err.lastPosition);
2536
- } else {
2537
- fatalError = err;
2538
- }
2920
+ return chunkDecoder.buffer ? chunkDecoder.decodeAfterIncomplete(chunk) : decoder.decodeMultiple(chunk)
2921
+ } catch (error) {
2922
+ fatalError = chunkDecoder.keepIncomplete(error, chunk);
2923
+ return (error && error.values) || []
2539
2924
  }
2540
- return yields || []
2541
2925
  };
2542
2926
 
2543
2927
  if (typeof bufferIterator[Symbol.iterator] === 'function') {
@@ -2546,6 +2930,8 @@
2546
2930
  yield * parser(value);
2547
2931
  if (fatalError) throw fatalError
2548
2932
  }
2933
+ const endError = chunkDecoder.end();
2934
+ if (endError) throw endError
2549
2935
  })()
2550
2936
  } else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
2551
2937
  return (async function * iter () {
@@ -2553,10 +2939,122 @@
2553
2939
  yield * parser(value);
2554
2940
  if (fatalError) throw fatalError
2555
2941
  }
2942
+ const endError = chunkDecoder.end();
2943
+ if (endError) throw endError
2556
2944
  })()
2557
2945
  }
2558
2946
  }
2559
2947
 
2948
+ /**
2949
+ * Keeps the bytes of a value that a chunk of a stream ends inside of, for DecoderStream and decodeIter, and decodes
2950
+ * them again once enough have arrived to possibly complete it. Callers decode a chunk with decodeAfterIncomplete if
2951
+ * buffer is set, or else directly with decodeMultiple, and pass any error to keepIncomplete, which returns the error
2952
+ * to report, if any. end returns an error if the input ended inside a value.
2953
+ */
2954
+ class ChunkDecoder {
2955
+ constructor (decoder, maxIncompleteBufferSize) {
2956
+ this.decoder = decoder;
2957
+ if (maxIncompleteBufferSize === undefined) maxIncompleteBufferSize = 0x4000000;
2958
+ if (typeof maxIncompleteBufferSize !== 'number' || !(maxIncompleteBufferSize >= 0)) {
2959
+ throw new TypeError('maxIncompleteBufferSize must be a non-negative number')
2960
+ }
2961
+ this.maxIncompleteBufferSize = Math.floor(maxIncompleteBufferSize);
2962
+ // buffer[0, bufferEnd) is the incomplete value; no decoded value is a view of buffer while it is kept here
2963
+ this.buffer = null;
2964
+ this.bufferEnd = 0;
2965
+ this.appends = 0;
2966
+ this.scanner = null;
2967
+ this.scanned = false;
2968
+ this.resetScanner = false;
2969
+ this.neededLength = 0; // a lower bound on the incomplete value's length
2970
+ }
2971
+
2972
+ decodeAfterIncomplete (chunk) {
2973
+ if (!(ArrayBuffer.isView(chunk) && chunk.BYTES_PER_ELEMENT === 1)) {
2974
+ throw new TypeError('Source must be a Uint8Array or Buffer')
2975
+ }
2976
+ const length = this.bufferEnd + chunk.length;
2977
+ if (length < this.neededLength && length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
2978
+ if (length > this.buffer.length) {
2979
+ // Values usually complete within two appends, which are sized exactly. After that the buffer grows by at least
2980
+ // the bytes already kept, so that they are copied a bounded number of times however small the chunks are.
2981
+ const size = this.appends < 2 ? length
2982
+ : Math.max(length, Math.min(length + this.bufferEnd, this.maxIncompleteBufferSize));
2983
+ const buffer = Buffer.allocUnsafe(size);
2984
+ if (size > length) buffer.fill(0, length); // decoded values are views of this buffer, so no uninitialized memory
2985
+ buffer.set(this.buffer.subarray(0, this.bufferEnd));
2986
+ this.buffer = buffer;
2987
+ }
2988
+ this.buffer.set(chunk, this.bufferEnd);
2989
+ this.bufferEnd = length;
2990
+ this.appends++;
2991
+ if (length < this.neededLength) return []
2992
+ if (!this.scanner) this.scanner = new StructureScanner(this.decoder);
2993
+ if (this.resetScanner) {
2994
+ this.scanner.reset();
2995
+ this.resetScanner = false;
2996
+ }
2997
+ if (!this.scanner.scan(this.buffer, length)) {
2998
+ this.neededLength = this.scanner.neededLength;
2999
+ if (length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
3000
+ return []
3001
+ }
3002
+ this.scanned = true;
3003
+ const values = _stream.decode(this.decoder, this.buffer.subarray(0, length), this.scanner.position);
3004
+ this.scanned = false;
3005
+ this.buffer = null;
3006
+ return values
3007
+ }
3008
+
3009
+ keepIncomplete (error, chunk) {
3010
+ // an extension may throw any value, and a falsy one would read as success
3011
+ if (!error) return this.fail(new Error('Decoding failed: ' + error))
3012
+ if (!error.incomplete) return this.fail(error)
3013
+ if (this.scanned && error.lastPosition === 0) {
3014
+ error.incomplete = false;
3015
+ return this.fail(error)
3016
+ }
3017
+ const source = this.buffer ? this.buffer.subarray(0, this.bufferEnd) : chunk;
3018
+ const length = source.length - error.lastPosition;
3019
+ if (length > this.maxIncompleteBufferSize) return this.fail(maxIncompleteBufferSizeError())
3020
+ if (!this.buffer || error.lastPosition > 0) {
3021
+ if (length === 0) return
3022
+ try {
3023
+ // copied, so that the remainder does not keep in memory the chunk, or the buffer that the values before it
3024
+ // are views of
3025
+ this.buffer = Buffer.from(source.subarray(error.lastPosition));
3026
+ } catch (copyError) {
3027
+ return this.fail(copyError)
3028
+ }
3029
+ this.bufferEnd = length;
3030
+ this.appends = 0;
3031
+ }
3032
+ this.scanned = false;
3033
+ this.resetScanner = true;
3034
+ this.neededLength = Math.max(length + 1, error.neededEnd ? error.neededEnd - error.lastPosition : 0);
3035
+ }
3036
+
3037
+ end () {
3038
+ if (this.buffer) {
3039
+ const error = this.fail(new Error('Unexpected end of CBOR data'));
3040
+ error.incomplete = true;
3041
+ return error
3042
+ }
3043
+ }
3044
+
3045
+ fail (error) {
3046
+ this.buffer = null;
3047
+ this.scanner = null;
3048
+ this.scanned = false;
3049
+ this.resetScanner = false;
3050
+ return error
3051
+ }
3052
+ }
3053
+
3054
+ function maxIncompleteBufferSizeError () {
3055
+ return new Error('Maximum incomplete buffer size exceeded')
3056
+ }
3057
+
2560
3058
  exports.ALWAYS = ALWAYS;
2561
3059
  exports.DECIMAL_FIT = DECIMAL_FIT;
2562
3060
  exports.DECIMAL_ROUND = DECIMAL_ROUND;