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/node.cjs CHANGED
@@ -17,7 +17,6 @@ const RECORD_DEFINITIONS_ID = 0xdffe;
17
17
  const RECORD_INLINE_ID = 0xdfff; // temporary first-come first-serve tag // proposed tag: 0x7265 // 're'
18
18
  const BUNDLED_STRINGS_ID = 0xdff9;
19
19
  const PACKED_REFERENCE_TAG_ID = 6;
20
- const STOP_CODE = {};
21
20
  let maxArraySize = 112810000; // This is the maximum array size in V8. We would potentially detect and set it higher
22
21
  // for JSC, but this is pretty large and should be sufficient for most use cases
23
22
  let maxMapSize = 16810000; // JavaScript has a fixed maximum map size of about 16710000, but JS itself enforces this,
@@ -128,16 +127,11 @@ class Decoder {
128
127
  // re-entrant execution, save the state and restore it after we do this decode
129
128
  return saveState(() => {
130
129
  clearSource();
131
- try {
132
- return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
133
- } catch (error) {
134
- if (error && typeof error == 'object')
135
- nestedDecodeErrors.add(error);
136
- throw error
137
- }
130
+ return this ? this.decode(source, end) : Decoder.prototype.decode.call(defaultOptions, source, end)
138
131
  })
139
132
  }
140
- srcEnd = end > -1 ? end : source.length;
133
+ // past the view, the JS string readers would decode the missing bytes as zeros instead of failing as truncated
134
+ srcEnd = end > -1 && end < source.length ? end : source.length;
141
135
  position$1 = 0;
142
136
  stringPosition = 0;
143
137
  srcStringEnd = 0;
@@ -177,18 +171,28 @@ class Decoder {
177
171
  return checkedRead()
178
172
  }
179
173
  decodeMultiple(source, forEach) {
174
+ if (src) {
175
+ // Keep the complete nested sequence inside its own decoder state.
176
+ return saveState(() => {
177
+ clearSource();
178
+ return this ? this.decodeMultiple(source, forEach) : defaultDecoder.decodeMultiple(source, forEach)
179
+ })
180
+ }
180
181
  let values, lastPosition = 0;
181
182
  try {
182
183
  let size = source.length;
183
184
  sequentialMode = true;
184
185
  let value = this ? this.decode(source, size) : defaultDecoder.decode(source, size);
185
186
  if (forEach) {
187
+ let end = position$1;
186
188
  if (forEach(value) === false) {
187
189
  return
188
190
  }
189
- while(position$1 < size) {
190
- lastPosition = position$1;
191
- if (forEach(checkedRead()) === false) {
191
+ while(end < size) {
192
+ lastPosition = end;
193
+ value = checkedRead();
194
+ end = position$1;
195
+ if (forEach(value) === false) {
192
196
  return
193
197
  }
194
198
  }
@@ -202,8 +206,10 @@ class Decoder {
202
206
  return values
203
207
  }
204
208
  } catch(error) {
205
- error.lastPosition = lastPosition;
206
- error.values = values;
209
+ if (error && (typeof error == 'object' || typeof error == 'function')) {
210
+ error.lastPosition = lastPosition;
211
+ error.values = values;
212
+ }
207
213
  throw error
208
214
  } finally {
209
215
  sequentialMode = false;
@@ -212,8 +218,10 @@ class Decoder {
212
218
  }
213
219
  }
214
220
  const nestedDecodeErrors = new WeakSet();
221
+ let arrayAllocationBudget;
215
222
  function checkedRead() {
216
223
  try {
224
+ arrayAllocationBudget = srcEnd - position$1;
217
225
  let result = read();
218
226
  if (bundledStrings$1) {
219
227
  if (position$1 >= bundledStrings$1.postBundlePosition)
@@ -255,9 +263,21 @@ function checkedRead() {
255
263
  }
256
264
  }
257
265
 
258
- function endOfCBORError() {
266
+ function readBreak() {
267
+ if (position$1 < srcEnd && src[position$1] === 0xff) {
268
+ position$1++;
269
+ return true
270
+ }
271
+ return false
272
+ }
273
+ // neededEnd, from a length check, is a position that the source must extend to before the item can decode. It is a
274
+ // lower bound for any completion of the source: every byte read before the check was within the source, so a longer
275
+ // source reaches the same check with the same length. Streams wait for it instead of decoding on every chunk.
276
+ function endOfCBORError(neededEnd) {
259
277
  let error = new Error('Unexpected end of CBOR data');
260
278
  error.incomplete = true;
279
+ if (neededEnd)
280
+ error.neededEnd = neededEnd;
261
281
  return error
262
282
  }
263
283
 
@@ -309,6 +329,9 @@ function read() {
309
329
  } else if (currentDecoder.int64AsNumber) {
310
330
  token = dataView.getUint32(position$1) * 0x100000000;
311
331
  token += dataView.getUint32(position$1 + 4);
332
+ position$1 += 8;
333
+ if (majorType === 1) return -1 - token
334
+ break
312
335
  } else token = dataView.getBigUint64(position$1);
313
336
  position$1 += 8;
314
337
  break
@@ -320,10 +343,10 @@ function read() {
320
343
  throw new Error('Indefinite length not supported for byte or text strings')
321
344
  case 4: // array
322
345
  let array = [];
323
- let value, i = 0;
324
- while ((value = read()) != STOP_CODE) {
346
+ let i = 0;
347
+ while (!readBreak()) {
325
348
  if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
326
- array[i++] = value;
349
+ array[i++] = read();
327
350
  }
328
351
  return majorType == 4 ? array : majorType == 3 ? array.join('') : Buffer.concat(array)
329
352
  case 5: // map
@@ -332,13 +355,15 @@ function read() {
332
355
  let object = {};
333
356
  let i = 0;
334
357
  if (currentDecoder.keyMap) {
335
- while((key = read()) != STOP_CODE) {
358
+ while (!readBreak()) {
359
+ key = read();
336
360
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
337
361
  object[safeKey(currentDecoder.decodeKey(key))] = read();
338
362
  }
339
363
  }
340
364
  else {
341
- while ((key = read()) != STOP_CODE) {
365
+ while (!readBreak()) {
366
+ key = read();
342
367
  if (i++ >= objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
343
368
  object[safeKey(key)] = read();
344
369
  }
@@ -352,7 +377,8 @@ function read() {
352
377
  let map = new Map();
353
378
  if (currentDecoder.keyMap) {
354
379
  let i = 0;
355
- while((key = read()) != STOP_CODE) {
380
+ while (!readBreak()) {
381
+ key = read();
356
382
  if (i++ >= maxMapSize) {
357
383
  throw new Error(`Map size exceeds ${maxMapSize}`);
358
384
  }
@@ -361,7 +387,8 @@ function read() {
361
387
  }
362
388
  else {
363
389
  let i = 0;
364
- while ((key = read()) != STOP_CODE) {
390
+ while (!readBreak()) {
391
+ key = read();
365
392
  if (i++ >= maxMapSize) {
366
393
  throw new Error(`Map size exceeds ${maxMapSize}`);
367
394
  }
@@ -371,7 +398,8 @@ function read() {
371
398
  return map
372
399
  }
373
400
  case 7:
374
- return STOP_CODE
401
+ // readBreak consumes every break that ends an indefinite-length item
402
+ throw new Error('Unexpected break stop code outside of an indefinite-length item')
375
403
  default:
376
404
  throw new Error('Invalid major type for indefinite length ' + majorType)
377
405
  }
@@ -385,13 +413,13 @@ function read() {
385
413
  case 1: // negative int
386
414
  return ~token
387
415
  case 2: // buffer
388
- if (token > srcEnd - position$1) throw endOfCBORError()
416
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
389
417
  return readBin(token)
390
418
  case 3: // string
391
419
  if (srcStringEnd >= position$1) { // already extracted, which only covers complete strings
392
420
  return srcString.slice(position$1 - srcStringStart, (position$1 += token) - srcStringStart)
393
421
  }
394
- if (token > srcEnd - position$1) throw endOfCBORError()
422
+ if (token > srcEnd - position$1) throw endOfCBORError(position$1 + token)
395
423
  if (srcStringEnd == 0 && srcEnd < 140 && token < 32) {
396
424
  // for small blocks, avoiding the overhead of the extract call is helpful
397
425
  let string = token < 16 ? shortStringInJS(token) : longStringInJS(token);
@@ -403,7 +431,9 @@ function read() {
403
431
  if (token > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
404
432
  // every element occupies at least one byte, so a length beyond what remains in the source can
405
433
  // never be satisfied; check before allocating so a tiny header can not force a huge allocation
406
- if (token > srcEnd - position$1) throw endOfCBORError()
434
+ // nested arrays each pass the check above yet still preallocate, so also spend from a
435
+ // per-decode budget of the remaining bytes; the summed lengths of any valid tree stay under it
436
+ if (token > srcEnd - position$1 || (arrayAllocationBudget -= token) < 0) throw endOfCBORError(position$1 + token)
407
437
  let array = new Array(token);
408
438
  //if (currentDecoder.keyMap) for (let i = 0; i < token; i++) array[i] = currentDecoder.decodeKey(read())
409
439
  //else
@@ -412,7 +442,7 @@ function read() {
412
442
  case 5: // map
413
443
  if (currentDecoder.mapsAsObjects) {
414
444
  if (token > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
415
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError() // each entry needs at least two bytes
445
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token) // each entry needs at least two bytes
416
446
  let object = {};
417
447
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) object[safeKey(currentDecoder.decodeKey(read()))] = read();
418
448
  else for (let i = 0; i < token; i++) object[safeKey(read())] = read();
@@ -423,7 +453,7 @@ function read() {
423
453
  restoreMapsAsObject = false;
424
454
  }
425
455
  if (token > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
426
- if (token > (srcEnd - position$1) / 2) throw endOfCBORError()
456
+ if (token > (srcEnd - position$1) / 2) throw endOfCBORError(position$1 + 2 * token)
427
457
  let map = new Map();
428
458
  if (currentDecoder.keyMap) for (let i = 0; i < token; i++) map.set(currentDecoder.decodeKey(read()),read());
429
459
  else for (let i = 0; i < token; i++) map.set(read(), read());
@@ -939,6 +969,7 @@ currentExtensions[LEGACY_RECORD_INLINE_ID] = (data) => {
939
969
  }
940
970
  return object
941
971
  };
972
+ const legacyRecordDefinition = currentExtensions[LEGACY_RECORD_INLINE_ID];
942
973
  currentExtensions[14] = (value) => {
943
974
  if (bundledStrings$1)
944
975
  return bundledStrings$1[0].slice(bundledStrings$1.position0, bundledStrings$1.position0 += value)
@@ -1211,6 +1242,9 @@ function loadShared() {
1211
1242
  }
1212
1243
 
1213
1244
  function saveState(callback) {
1245
+ let savedFragmentEnd = fragmentEnd;
1246
+ let savedFragmentReader = fragmentReadString;
1247
+ let savedReadFixedString = readFixedString;
1214
1248
  let savedSrcEnd = srcEnd;
1215
1249
  let savedPosition = position$1;
1216
1250
  let savedStringPosition = stringPosition;
@@ -1220,6 +1254,7 @@ function saveState(callback) {
1220
1254
  let savedStrings = strings;
1221
1255
  let savedReferenceMap = referenceMap;
1222
1256
  let savedBundledStrings = bundledStrings$1;
1257
+ let savedPackedValues = packedValues;
1223
1258
 
1224
1259
  // TODO: We may need to revisit this if we do more external calls to user code (since it could be slow)
1225
1260
  let savedSrc = new Uint8Array(src.slice(0, srcEnd)); // we copy the data in case it changes while external data is processed
@@ -1227,10 +1262,23 @@ function saveState(callback) {
1227
1262
  let savedDecoder = currentDecoder;
1228
1263
  let savedSequentialMode = sequentialMode;
1229
1264
  let savedRestoreMapsAsObject = restoreMapsAsObject;
1265
+ let savedArrayAllocationBudget = arrayAllocationBudget;
1266
+ fragmentEnd = 0;
1267
+ if (fragmentReadString) readFixedString = fragmentReadString;
1268
+ fragmentReadString = null;
1230
1269
  restoreMapsAsObject = false;
1231
1270
  try {
1232
1271
  return callback()
1272
+ } catch (error) {
1273
+ // the callback decodes its own source (a shared structures blob, or an extension's embedded data), so a
1274
+ // truncation there cannot be completed by more of the outer input; checkedRead() clears the flag
1275
+ if (error && (typeof error == 'object' || typeof error == 'function'))
1276
+ nestedDecodeErrors.add(error);
1277
+ throw error
1233
1278
  } finally {
1279
+ fragmentEnd = savedFragmentEnd;
1280
+ fragmentReadString = savedFragmentReader;
1281
+ readFixedString = savedReadFixedString;
1234
1282
  srcEnd = savedSrcEnd;
1235
1283
  position$1 = savedPosition;
1236
1284
  stringPosition = savedStringPosition;
@@ -1240,11 +1288,13 @@ function saveState(callback) {
1240
1288
  strings = savedStrings;
1241
1289
  referenceMap = savedReferenceMap;
1242
1290
  bundledStrings$1 = savedBundledStrings;
1291
+ packedValues = savedPackedValues;
1243
1292
  src = savedSrc;
1244
1293
  sequentialMode = savedSequentialMode;
1245
1294
  currentStructures = savedStructures;
1246
1295
  currentDecoder = savedDecoder;
1247
1296
  restoreMapsAsObject = savedRestoreMapsAsObject;
1297
+ arrayAllocationBudget = savedArrayAllocationBudget;
1248
1298
  dataView = new DataView(src.buffer, src.byteOffset, src.byteLength);
1249
1299
  }
1250
1300
  }
@@ -1254,8 +1304,87 @@ function clearSource() {
1254
1304
  currentStructures = null;
1255
1305
  }
1256
1306
 
1307
+ const registeredExtensions = new Map();
1308
+ let fragmentEnd = 0;
1309
+ let fragmentReadString;
1310
+
1311
+ const builtinScanTags = new Map([[LEGACY_RECORD_INLINE_ID, legacyRecordDefinition], [259, currentExtensions[259]]]);
1312
+ function isBuiltinScanTag(tag) {
1313
+ return builtinScanTags.has(tag) && currentExtensions[tag] === builtinScanTags.get(tag)
1314
+ }
1315
+
1316
+ function getScanTag(tag, decoder) {
1317
+ return tag >= BUNDLED_STRINGS_ID && decoder.structures && decoder.structures[tag & 0x1fff]
1318
+ }
1319
+
1320
+ function checkScanContainer(major, length, mapsAsObjects) {
1321
+ if (major === 4) {
1322
+ if (length > maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
1323
+ } else if (mapsAsObjects) {
1324
+ if (length > objectSizeLimit) throw new Error(`Property count exceeds ${objectSizeLimit}`)
1325
+ } else if (length > maxMapSize) throw new Error(`Map size exceeds ${maxMapSize}`)
1326
+ }
1327
+
1328
+ function decodeScanned(decoder, source, end) {
1329
+ const boundStrings = exports.isNativeAccelerationEnabled && end < source.length;
1330
+ if (!registeredExtensions.size && !boundStrings) return decoder.decodeMultiple(source)
1331
+ const savedEnd = fragmentEnd;
1332
+ const savedReader = readFixedString;
1333
+ const savedFragmentReader = fragmentReadString;
1334
+ const originals = [];
1335
+ fragmentEnd = end;
1336
+ fragmentReadString = savedReader;
1337
+ try {
1338
+ if (boundStrings) readFixedString = (length) => {
1339
+ if (!fragmentEnd || position$1 >= fragmentEnd) return savedReader(length)
1340
+ const savedSrcEnd = srcEnd;
1341
+ srcEnd = fragmentEnd;
1342
+ try { return savedReader(length) } finally { srcEnd = savedSrcEnd; }
1343
+ };
1344
+ for (const [tag, extension] of registeredExtensions) {
1345
+ if (!extension.handlesRead) continue
1346
+ const original = currentExtensions[tag];
1347
+ const guarded = (read) => {
1348
+ if (!fragmentEnd || position$1 >= fragmentEnd) return extension(read)
1349
+ let calls = 0, framingError;
1350
+ const value = extension(() => {
1351
+ if (calls++) {
1352
+ framingError = new Error('Tag ' + tag + ': fragmented decoding cannot frame multi-item handlesRead extensions');
1353
+ throw framingError
1354
+ }
1355
+ return read()
1356
+ });
1357
+ if (framingError) throw framingError
1358
+ return value
1359
+ };
1360
+ guarded.handlesRead = true;
1361
+ originals.push([tag, original, guarded]);
1362
+ currentExtensions[tag] = guarded;
1363
+ }
1364
+ return decoder.decodeMultiple(source)
1365
+ } finally {
1366
+ fragmentEnd = savedEnd;
1367
+ fragmentReadString = savedFragmentReader;
1368
+ readFixedString = savedReader;
1369
+ for (const [tag, original, guarded] of originals) {
1370
+ if (currentExtensions[tag] === guarded) currentExtensions[tag] = original;
1371
+ }
1372
+ }
1373
+ }
1374
+
1375
+ // Internal streaming hooks; not part of the public decoder API.
1376
+ const _stream = {
1377
+ getTag: getScanTag,
1378
+ isBuiltinTag: isBuiltinScanTag,
1379
+ checkContainer: checkScanContainer,
1380
+ decode: decodeScanned,
1381
+ get readThreshold() { return inlineObjectReadThreshold }
1382
+ };
1383
+
1257
1384
  function addExtension$1(extension) {
1258
1385
  currentExtensions[extension.tag] = extension.decode;
1386
+ if (typeof extension.decode === 'function') registeredExtensions.set(extension.tag, extension.decode);
1387
+ else registeredExtensions.delete(extension.tag);
1259
1388
  }
1260
1389
 
1261
1390
  function setSizeLimits(limits) {
@@ -1855,6 +1984,11 @@ class Encoder extends Decoder {
1855
1984
  return encode(json)
1856
1985
  }
1857
1986
 
1987
+ // not in extensions, so class instances that reach writeObject do not pay another instanceof
1988
+ if (constructor === DataView) {
1989
+ writeBuffer(new Uint8Array(value.buffer, value.byteOffset, value.byteLength), makeRoom);
1990
+ return
1991
+ }
1858
1992
  // no extension found, write as a plain object
1859
1993
  writeObject(value);
1860
1994
  }
@@ -1948,8 +2082,22 @@ class Encoder extends Decoder {
1948
2082
  size++;
1949
2083
  }
1950
2084
  }
1951
- target[objectOffset++ + start] = size >> 8;
1952
- target[objectOffset + start] = size & 0xff;
2085
+ if (size < 0x10000) {
2086
+ // fits in a map-16 (uint16) count: back-patch the reserved 2 bytes
2087
+ target[objectOffset++ + start] = size >> 8;
2088
+ target[objectOffset + start] = size & 0xff;
2089
+ } else {
2090
+ // too many entries for a map-16 count: widen the already-written header
2091
+ // to map-32, mirroring writeArrayHeader's 0x9a/setUint32 path. Shift the
2092
+ // encoded entries 2 bytes forward to make room for the wider (uint32) count.
2093
+ if (position + 2 > safeEnd)
2094
+ makeRoom(position + 2);
2095
+ let headerPosition = objectOffset + start;
2096
+ target.copyWithin(headerPosition + 4, headerPosition + 2, position);
2097
+ position += 2;
2098
+ target[headerPosition - 1] = 0xba; // map 32
2099
+ targetView.setUint32(headerPosition, size);
2100
+ }
1953
2101
  } :
1954
2102
  (object, skipValues) => {
1955
2103
  let nextTransition, transition = structures.transitions || (structures.transitions = Object.create(null));
@@ -2516,59 +2664,256 @@ const REUSE_BUFFER_MODE = 512;
2516
2664
  const RESET_BUFFER_MODE = 1024;
2517
2665
  const THROW_ON_ITERABLE = 2048;
2518
2666
 
2519
- class EncoderStream extends stream.Transform {
2520
- constructor(options) {
2521
- if (!options)
2522
- options = {};
2523
- options.writableObjectMode = true;
2524
- super(options);
2525
- options.sequential = true;
2526
- this.encoder = options.encoder || new Encoder(options);
2527
- }
2528
- async _transform(value, encoding, callback) {
2529
- try {
2530
- for await (let chunk of this.encoder.encodeAsAsyncIterable(value)) {
2531
- this.push(chunk);
2532
- }
2533
- callback();
2534
- } catch(error) { callback (error); }
2535
- }
2536
- }
2667
+ // Positions are relative to the retained value, including bundle pointers. Frames and their storage survive appends.
2668
+ class StructureScanner {
2669
+ constructor (decoder) {
2670
+ this.decoder = decoder;
2671
+ this.frames = [];
2672
+ this.records = new Map();
2673
+ this.readers = new Map();
2674
+ this.reset();
2675
+ }
2537
2676
 
2538
- class DecoderStream extends stream.Transform {
2539
- constructor(options) {
2540
- if (!options)
2541
- options = {};
2542
- options.objectMode = true;
2543
- super(options);
2544
- options.structures = [];
2545
- this.decoder = options.decoder || new Decoder(options);
2546
- }
2547
- _transform(chunk, encoding, callback) {
2548
- if (this.incompleteBuffer) {
2549
- chunk = Buffer.concat([this.incompleteBuffer, chunk]);
2550
- this.incompleteBuffer = null;
2551
- }
2552
- let values, fatalError;
2553
- try {
2554
- values = this.decoder.decodeMultiple(chunk);
2555
- } catch(error) {
2556
- values = error.values;
2557
- if (error.incomplete)
2558
- this.incompleteBuffer = chunk.slice(error.lastPosition);
2559
- else
2560
- fatalError = error;
2561
- }
2562
- for (let value of values || []) {
2563
- if (value === null)
2564
- value = this.getNullValue();
2565
- this.push(value);
2566
- }
2567
- callback(fatalError);
2568
- }
2569
- getNullValue() {
2570
- return Symbol.for(null)
2571
- }
2677
+ reset () {
2678
+ this.position = 0;
2679
+ this.depth = 0;
2680
+ this.neededLength = 0;
2681
+ this.postBundlePosition = 0;
2682
+ this.headers = 0;
2683
+ this.mapsAsObjects = !!this.decoder.mapsAsObjects;
2684
+ this.restoreMap = false;
2685
+ this.records.clear();
2686
+ this.readers.clear();
2687
+ this.push('items', 1);
2688
+ }
2689
+
2690
+ push (kind, remaining) {
2691
+ if (this.depth === 16384) throw new RangeError('Maximum CBOR nesting depth exceeded')
2692
+ const frame = this.frames[this.depth] || (this.frames[this.depth] = {});
2693
+ frame.kind = kind;
2694
+ frame.remaining = remaining;
2695
+ frame.stage = 0;
2696
+ frame.id = undefined;
2697
+ frame.restoreMap = false;
2698
+ frame.legacy = false;
2699
+ frame.definition = undefined;
2700
+ frame.limitMajor = 0;
2701
+ frame.count = 0;
2702
+ this.depth++;
2703
+ return frame
2704
+ }
2705
+
2706
+ header (source, end, lengthOnly = false) {
2707
+ const start = this.position;
2708
+ if (start >= end) {
2709
+ this.neededLength = start + 1;
2710
+ return false
2711
+ }
2712
+ const byte = source[start];
2713
+ const major = byte >> 5;
2714
+ const info = byte & 31;
2715
+ const width = info < 24 || (lengthOnly && info > 26) ? 0 : info === 24 ? 1 : info === 25 ? 2 : info === 26 ? 4 : info === 27 ? 8 : 0;
2716
+ if (!lengthOnly && info >= 28 && info <= 30) throw new Error('Unknown token ' + info)
2717
+ if (start + 1 + width > end) {
2718
+ this.neededLength = start + 1 + width;
2719
+ return false
2720
+ }
2721
+ let value = info;
2722
+ if (width) {
2723
+ value = 0;
2724
+ for (let i = start + 1; i <= start + width; i++) value = value * 256 + source[i];
2725
+ if (width === 8 && major > 1 && major !== 7 && value > 0xffffffff) {
2726
+ throw new Error('JavaScript does not support arrays, maps, or strings with length over 4294967295')
2727
+ }
2728
+ }
2729
+ this.major = major;
2730
+ this.info = info;
2731
+ this.argument = value;
2732
+ this.position = start + 1 + width;
2733
+ this.headers++;
2734
+ return true
2735
+ }
2736
+
2737
+ scan (source, end) {
2738
+ while (this.depth) {
2739
+ if (this.position > end) {
2740
+ this.neededLength = this.position;
2741
+ return false
2742
+ }
2743
+ const frame = this.frames[this.depth - 1];
2744
+ if (frame.kind === 'bundle') {
2745
+ if (frame.stage === 0) {
2746
+ if (!this.header(source, end, true)) return false
2747
+ frame.remaining = Math.max(0, this.argument - 2);
2748
+ frame.stage = 1;
2749
+ }
2750
+ if (frame.stage === 1) {
2751
+ const offsetStart = this.position;
2752
+ if (!this.header(source, end)) return false
2753
+ if (this.major > 1 || this.info === 31) throw new Error('Invalid bundled string offset')
2754
+ frame.target = offsetStart + (this.major === 1 ? -1 - this.argument : this.argument);
2755
+ if (!Number.isSafeInteger(frame.target) || frame.target < 0) throw new Error('Invalid bundled string offset')
2756
+ frame.stage = 2;
2757
+ }
2758
+ if (frame.stage === 2) {
2759
+ if (frame.remaining) {
2760
+ if (!this.header(source, end, true)) return false
2761
+ this.position += this.argument;
2762
+ frame.remaining--;
2763
+ continue
2764
+ }
2765
+ frame.resume = this.position;
2766
+ this.position = frame.target;
2767
+ frame.remaining = 2;
2768
+ frame.stage = 3;
2769
+ continue
2770
+ }
2771
+ if (frame.stage === 3) {
2772
+ if (frame.remaining) {
2773
+ if (!this.header(source, end, true)) return false
2774
+ this.position += this.argument;
2775
+ frame.remaining--;
2776
+ continue
2777
+ }
2778
+ this.postBundlePosition = this.position;
2779
+ this.position = frame.resume;
2780
+ frame.kind = 'items';
2781
+ frame.remaining = 1;
2782
+ continue
2783
+ }
2784
+ }
2785
+ if (frame.kind === 'record') {
2786
+ if (!this.header(source, end, true)) return false
2787
+ if (this.info > 26) throw new Error('Expected array header, but got ' + source[this.position - 1])
2788
+ frame.kind = 'items';
2789
+ frame.remaining = this.argument;
2790
+ const reader = frame.reader;
2791
+ if (reader.compiled.has(this.argument)) frame.remaining = Math.min(this.argument, reader.length);
2792
+ else if (reader.slowReads++ >= _stream.readThreshold) {
2793
+ reader.compiled.add(this.argument);
2794
+ frame.remaining = Math.min(this.argument, reader.length);
2795
+ }
2796
+ continue
2797
+ }
2798
+ if (frame.kind === 'definition' && frame.stage === 0) {
2799
+ const start = this.position;
2800
+ if (!this.header(source, end, !frame.legacy)) return false
2801
+ if (frame.legacy && this.major !== 4) {
2802
+ this.position = start;
2803
+ this.headers--;
2804
+ frame.kind = 'items';
2805
+ frame.remaining = 1;
2806
+ continue
2807
+ }
2808
+ frame.remaining = frame.legacy ? (this.info === 31 ? -1 : this.argument) : Math.max(2, this.argument);
2809
+ if (frame.legacy) {
2810
+ if (frame.remaining >= 0) _stream.checkContainer(4, frame.remaining);
2811
+ else frame.limitMajor = 4;
2812
+ frame.index = 0;
2813
+ }
2814
+ frame.total = frame.remaining;
2815
+ frame.stage = 1;
2816
+ continue
2817
+ }
2818
+ if (frame.remaining === 0) {
2819
+ if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition);
2820
+ if (frame.restoreMap && this.restoreMap) {
2821
+ this.mapsAsObjects = true;
2822
+ this.restoreMap = false;
2823
+ }
2824
+ this.depth--;
2825
+ continue
2826
+ }
2827
+ if (!this.header(source, end)) return false
2828
+ let { major, argument, info } = this;
2829
+ if (frame.kind === 'definition') {
2830
+ const index = frame.legacy ? frame.index++ : frame.total - frame.remaining;
2831
+ if (index === 0 && major === 0) frame.id = argument;
2832
+ if ((frame.multiple ? index >= 1 && index < frame.total - 1 : index === 1) && major === 4 && info !== 31 && frame.id !== undefined) {
2833
+ const definition = { length: argument, slowReads: 0, compiled: new Set() };
2834
+ if (frame.legacy) frame.definition = definition;
2835
+ else this.records.set(frame.id - 0xe000 + (frame.multiple ? index - 1 : 0), definition);
2836
+ }
2837
+ }
2838
+ if (major === 7 && info === 31 && (frame.remaining === -1 || frame.remaining === -2)) {
2839
+ if (frame.definition) this.records.set(frame.id - 0xe000, frame.definition);
2840
+ this.depth--;
2841
+ continue
2842
+ }
2843
+ if (frame.limitMajor && (frame.remaining === -1 || frame.remaining === -2)) {
2844
+ _stream.checkContainer(frame.limitMajor, ++frame.count, frame.mapsAsObjects);
2845
+ }
2846
+ if (frame.remaining > 0) frame.remaining--;
2847
+ else if (frame.remaining === -2) frame.remaining = -3;
2848
+ else if (frame.remaining === -3) frame.remaining = -2;
2849
+ if (info === 31) {
2850
+ if (major === 2 || major === 3) throw new Error('Indefinite length not supported for byte or text strings')
2851
+ if (major === 4 || major === 5) {
2852
+ const container = this.push('items', major === 4 ? -1 : -2);
2853
+ container.limitMajor = major;
2854
+ container.mapsAsObjects = this.mapsAsObjects;
2855
+ if (major === 5 && this.restoreMap) {
2856
+ this.mapsAsObjects = true;
2857
+ this.restoreMap = false;
2858
+ }
2859
+ }
2860
+ else if (major === 7) throw new Error('Unexpected break stop code outside of an indefinite-length item')
2861
+ else throw new Error('Invalid major type for indefinite length ' + major)
2862
+ continue
2863
+ }
2864
+ switch (major) {
2865
+ case 2:
2866
+ case 3:
2867
+ this.position += argument;
2868
+ break
2869
+ case 4:
2870
+ case 5:
2871
+ _stream.checkContainer(major, argument, this.mapsAsObjects);
2872
+ if (major === 5 && this.restoreMap) {
2873
+ this.mapsAsObjects = true;
2874
+ this.restoreMap = false;
2875
+ }
2876
+ if (argument) this.push('items', major === 5 ? argument * 2 : argument);
2877
+ break
2878
+ case 6: {
2879
+ const structure = _stream.getTag(argument, this.decoder);
2880
+ let reader = this.records.get(argument & 0x1fff);
2881
+ if (argument >= 0xdff9 && (structure || reader)) {
2882
+ if (!reader) {
2883
+ reader = this.readers.get(structure);
2884
+ if (!reader) {
2885
+ reader = { length: structure.length, slowReads: structure.slowReads || 0, compiled: new Set() };
2886
+ for (let compiled = structure.compiledReader; compiled; compiled = compiled.next) reader.compiled.add(compiled.propertyCount);
2887
+ this.readers.set(structure, reader);
2888
+ }
2889
+ }
2890
+ this.push('record').reader = reader;
2891
+ } else if (argument === 0xdff9) {
2892
+ this.push('bundle');
2893
+ } else if (argument === 0xdfff || argument === 0xdffe) {
2894
+ this.push('definition').multiple = argument === 0xdffe;
2895
+ } else if (argument === 259 && _stream.isBuiltinTag(argument) && this.mapsAsObjects) {
2896
+ this.push('items', 1).restoreMap = true;
2897
+ this.mapsAsObjects = false;
2898
+ this.restoreMap = true;
2899
+ } else if (argument === 105 && _stream.isBuiltinTag(argument)) {
2900
+ const definition = this.push('definition');
2901
+ definition.multiple = false;
2902
+ definition.legacy = true;
2903
+ } else {
2904
+ this.push('items', 1);
2905
+ }
2906
+ break
2907
+ }
2908
+ }
2909
+ }
2910
+ if (this.postBundlePosition) {
2911
+ if (this.position >= this.postBundlePosition) throw new Error('Unexpected bundle position')
2912
+ this.position = this.postBundlePosition;
2913
+ }
2914
+ if (this.frames.length > 256) this.frames.length = 256;
2915
+ return true
2916
+ }
2572
2917
  }
2573
2918
 
2574
2919
  /**
@@ -2608,7 +2953,7 @@ async function * encodeIterAsync (objectIterator, options) {
2608
2953
  * Given an Iterable/Iterator input which yields buffers, returns an IterableIterator which yields sync decoded objects
2609
2954
  * Or, given an Async Iterable/Iterator which yields promises resolving in buffers, returns an AsyncIterableIterator.
2610
2955
  * @param {Iterable|Iterator|AsyncIterable|AsyncIterableIterator} bufferIterator
2611
- * @param {object} [options] - Decoder options
2956
+ * @param {object} [options] - Decoder options, and maxIncompleteBufferSize
2612
2957
  * @returns {IterableIterator|Promise.<AsyncIterableIterator}
2613
2958
  */
2614
2959
  function decodeIter (bufferIterator, options = {}) {
@@ -2617,26 +2962,15 @@ function decodeIter (bufferIterator, options = {}) {
2617
2962
  }
2618
2963
 
2619
2964
  const decoder = new Decoder(options);
2620
- let incomplete, fatalError;
2965
+ const chunkDecoder = new ChunkDecoder(decoder, options.maxIncompleteBufferSize);
2966
+ let fatalError;
2621
2967
  const parser = (chunk) => {
2622
- let yields;
2623
- // if there's incomplete data from previous chunk, concatinate and try again
2624
- if (incomplete) {
2625
- chunk = Buffer.concat([incomplete, chunk]);
2626
- incomplete = undefined;
2627
- }
2628
-
2629
2968
  try {
2630
- yields = decoder.decodeMultiple(chunk);
2631
- } catch (err) {
2632
- yields = err.values;
2633
- if (err.incomplete) {
2634
- incomplete = chunk.slice(err.lastPosition);
2635
- } else {
2636
- fatalError = err;
2637
- }
2969
+ return chunkDecoder.buffer ? chunkDecoder.decodeAfterIncomplete(chunk) : decoder.decodeMultiple(chunk)
2970
+ } catch (error) {
2971
+ fatalError = chunkDecoder.keepIncomplete(error, chunk);
2972
+ return (error && error.values) || []
2638
2973
  }
2639
- return yields || []
2640
2974
  };
2641
2975
 
2642
2976
  if (typeof bufferIterator[Symbol.iterator] === 'function') {
@@ -2645,6 +2979,8 @@ function decodeIter (bufferIterator, options = {}) {
2645
2979
  yield * parser(value);
2646
2980
  if (fatalError) throw fatalError
2647
2981
  }
2982
+ const endError = chunkDecoder.end();
2983
+ if (endError) throw endError
2648
2984
  })()
2649
2985
  } else if (typeof bufferIterator[Symbol.asyncIterator] === 'function') {
2650
2986
  return (async function * iter () {
@@ -2652,10 +2988,175 @@ function decodeIter (bufferIterator, options = {}) {
2652
2988
  yield * parser(value);
2653
2989
  if (fatalError) throw fatalError
2654
2990
  }
2991
+ const endError = chunkDecoder.end();
2992
+ if (endError) throw endError
2655
2993
  })()
2656
2994
  }
2657
2995
  }
2658
2996
 
2997
+ /**
2998
+ * Keeps the bytes of a value that a chunk of a stream ends inside of, for DecoderStream and decodeIter, and decodes
2999
+ * them again once enough have arrived to possibly complete it. Callers decode a chunk with decodeAfterIncomplete if
3000
+ * buffer is set, or else directly with decodeMultiple, and pass any error to keepIncomplete, which returns the error
3001
+ * to report, if any. end returns an error if the input ended inside a value.
3002
+ */
3003
+ class ChunkDecoder {
3004
+ constructor (decoder, maxIncompleteBufferSize) {
3005
+ this.decoder = decoder;
3006
+ if (maxIncompleteBufferSize === undefined) maxIncompleteBufferSize = 0x4000000;
3007
+ if (typeof maxIncompleteBufferSize !== 'number' || !(maxIncompleteBufferSize >= 0)) {
3008
+ throw new TypeError('maxIncompleteBufferSize must be a non-negative number')
3009
+ }
3010
+ this.maxIncompleteBufferSize = Math.floor(maxIncompleteBufferSize);
3011
+ // buffer[0, bufferEnd) is the incomplete value; no decoded value is a view of buffer while it is kept here
3012
+ this.buffer = null;
3013
+ this.bufferEnd = 0;
3014
+ this.appends = 0;
3015
+ this.scanner = null;
3016
+ this.scanned = false;
3017
+ this.resetScanner = false;
3018
+ this.neededLength = 0; // a lower bound on the incomplete value's length
3019
+ }
3020
+
3021
+ decodeAfterIncomplete (chunk) {
3022
+ if (!(ArrayBuffer.isView(chunk) && chunk.BYTES_PER_ELEMENT === 1)) {
3023
+ throw new TypeError('Source must be a Uint8Array or Buffer')
3024
+ }
3025
+ const length = this.bufferEnd + chunk.length;
3026
+ if (length < this.neededLength && length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
3027
+ if (length > this.buffer.length) {
3028
+ // Values usually complete within two appends, which are sized exactly. After that the buffer grows by at least
3029
+ // the bytes already kept, so that they are copied a bounded number of times however small the chunks are.
3030
+ const size = this.appends < 2 ? length
3031
+ : Math.max(length, Math.min(length + this.bufferEnd, this.maxIncompleteBufferSize));
3032
+ const buffer = Buffer.allocUnsafe(size);
3033
+ if (size > length) buffer.fill(0, length); // decoded values are views of this buffer, so no uninitialized memory
3034
+ buffer.set(this.buffer.subarray(0, this.bufferEnd));
3035
+ this.buffer = buffer;
3036
+ }
3037
+ this.buffer.set(chunk, this.bufferEnd);
3038
+ this.bufferEnd = length;
3039
+ this.appends++;
3040
+ if (length < this.neededLength) return []
3041
+ if (!this.scanner) this.scanner = new StructureScanner(this.decoder);
3042
+ if (this.resetScanner) {
3043
+ this.scanner.reset();
3044
+ this.resetScanner = false;
3045
+ }
3046
+ if (!this.scanner.scan(this.buffer, length)) {
3047
+ this.neededLength = this.scanner.neededLength;
3048
+ if (length > this.maxIncompleteBufferSize) throw maxIncompleteBufferSizeError()
3049
+ return []
3050
+ }
3051
+ this.scanned = true;
3052
+ const values = _stream.decode(this.decoder, this.buffer.subarray(0, length), this.scanner.position);
3053
+ this.scanned = false;
3054
+ this.buffer = null;
3055
+ return values
3056
+ }
3057
+
3058
+ keepIncomplete (error, chunk) {
3059
+ // an extension may throw any value, and a falsy one would read as success
3060
+ if (!error) return this.fail(new Error('Decoding failed: ' + error))
3061
+ if (!error.incomplete) return this.fail(error)
3062
+ if (this.scanned && error.lastPosition === 0) {
3063
+ error.incomplete = false;
3064
+ return this.fail(error)
3065
+ }
3066
+ const source = this.buffer ? this.buffer.subarray(0, this.bufferEnd) : chunk;
3067
+ const length = source.length - error.lastPosition;
3068
+ if (length > this.maxIncompleteBufferSize) return this.fail(maxIncompleteBufferSizeError())
3069
+ if (!this.buffer || error.lastPosition > 0) {
3070
+ if (length === 0) return
3071
+ try {
3072
+ // copied, so that the remainder does not keep in memory the chunk, or the buffer that the values before it
3073
+ // are views of
3074
+ this.buffer = Buffer.from(source.subarray(error.lastPosition));
3075
+ } catch (copyError) {
3076
+ return this.fail(copyError)
3077
+ }
3078
+ this.bufferEnd = length;
3079
+ this.appends = 0;
3080
+ }
3081
+ this.scanned = false;
3082
+ this.resetScanner = true;
3083
+ this.neededLength = Math.max(length + 1, error.neededEnd ? error.neededEnd - error.lastPosition : 0);
3084
+ }
3085
+
3086
+ end () {
3087
+ if (this.buffer) {
3088
+ const error = this.fail(new Error('Unexpected end of CBOR data'));
3089
+ error.incomplete = true;
3090
+ return error
3091
+ }
3092
+ }
3093
+
3094
+ fail (error) {
3095
+ this.buffer = null;
3096
+ this.scanner = null;
3097
+ this.scanned = false;
3098
+ this.resetScanner = false;
3099
+ return error
3100
+ }
3101
+ }
3102
+
3103
+ function maxIncompleteBufferSizeError () {
3104
+ return new Error('Maximum incomplete buffer size exceeded')
3105
+ }
3106
+
3107
+ class EncoderStream extends stream.Transform {
3108
+ constructor(options) {
3109
+ if (!options)
3110
+ options = {};
3111
+ options.writableObjectMode = true;
3112
+ super(options);
3113
+ options.sequential = true;
3114
+ this.encoder = options.encoder || new Encoder(options);
3115
+ }
3116
+ async _transform(value, encoding, callback) {
3117
+ try {
3118
+ for await (let chunk of this.encoder.encodeAsAsyncIterable(value)) {
3119
+ this.push(chunk);
3120
+ }
3121
+ callback();
3122
+ } catch(error) { callback (error); }
3123
+ }
3124
+ }
3125
+
3126
+ class DecoderStream extends stream.Transform {
3127
+ constructor(options) {
3128
+ if (!options)
3129
+ options = {};
3130
+ options.objectMode = true;
3131
+ super(options);
3132
+ options.structures = [];
3133
+ this.decoder = options.decoder || new Decoder(options);
3134
+ this.chunkDecoder = new ChunkDecoder(this.decoder, options.maxIncompleteBufferSize);
3135
+ }
3136
+ _transform(chunk, encoding, callback) {
3137
+ let values, fatalError;
3138
+ try {
3139
+ // decodeMultiple is called directly, since another call level here measurably slows small writes
3140
+ values = this.chunkDecoder.buffer ? this.chunkDecoder.decodeAfterIncomplete(chunk) : this.decoder.decodeMultiple(chunk);
3141
+ } catch(error) {
3142
+ values = error && error.values;
3143
+ fatalError = this.chunkDecoder.keepIncomplete(error, chunk);
3144
+ }
3145
+ for (let value of values || []) {
3146
+ if (value === null)
3147
+ value = this.getNullValue();
3148
+ this.push(value);
3149
+ }
3150
+ callback(fatalError);
3151
+ }
3152
+ _flush(callback) {
3153
+ callback(this.chunkDecoder.end());
3154
+ }
3155
+ getNullValue() {
3156
+ return Symbol.for(null)
3157
+ }
3158
+ }
3159
+
2659
3160
  const useRecords = false;
2660
3161
  const mapsAsObjects = true;
2661
3162