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/README.md +4 -0
- package/decode.js +157 -28
- package/dist/decode-no-eval.cjs +158 -28
- package/dist/decode-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.cjs +546 -48
- package/dist/index-no-eval.cjs.map +1 -1
- package/dist/index-no-eval.min.js +1 -1
- package/dist/index-no-eval.min.js.map +1 -1
- package/dist/index.js +546 -48
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/node.cjs +601 -100
- package/dist/node.cjs.map +1 -1
- package/dist/test.js +153 -30
- package/dist/test.js.map +1 -1
- package/encode.js +21 -2
- package/index.d.ts +1 -1
- package/iterators.js +123 -19
- package/package.json +2 -2
- package/scan.js +253 -0
- package/stream.js +9 -10
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
|
-
|
|
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
|
-
|
|
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(
|
|
190
|
-
lastPosition =
|
|
191
|
-
|
|
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
|
|
206
|
-
|
|
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
|
|
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
|
|
324
|
-
while ((
|
|
346
|
+
let i = 0;
|
|
347
|
+
while (!readBreak()) {
|
|
325
348
|
if (i >= maxArraySize) throw new Error(`Array length exceeds ${maxArraySize}`)
|
|
326
|
-
array[i++] =
|
|
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((
|
|
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 ((
|
|
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((
|
|
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 ((
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1952
|
-
|
|
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
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
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
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2631
|
-
} catch (
|
|
2632
|
-
|
|
2633
|
-
|
|
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
|
|