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