@marcuspuchalla/nachos 0.1.3 → 0.2.0

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/dist/{chunk-PTWN7K3Y.cjs → chunk-3Z45RBZP.cjs} +469 -244
  3. package/dist/chunk-3Z45RBZP.cjs.map +1 -0
  4. package/dist/{chunk-2MTLSQ7E.js → chunk-EDXZTSIA.js} +224 -166
  5. package/dist/chunk-EDXZTSIA.js.map +1 -0
  6. package/dist/{chunk-R62CQQNI.cjs → chunk-HMUA5KLG.cjs} +239 -181
  7. package/dist/chunk-HMUA5KLG.cjs.map +1 -0
  8. package/dist/{chunk-ZDZ2B5PE.js → chunk-JESIF5IF.js} +7 -3
  9. package/dist/chunk-JESIF5IF.js.map +1 -0
  10. package/dist/{chunk-5A5T56JB.js → chunk-LWNWC2O7.js} +442 -217
  11. package/dist/chunk-LWNWC2O7.js.map +1 -0
  12. package/dist/{chunk-PD72MVTX.cjs → chunk-P6A2OOIY.cjs} +7 -3
  13. package/dist/chunk-P6A2OOIY.cjs.map +1 -0
  14. package/dist/encoder/index.cjs +14 -14
  15. package/dist/encoder/index.d.cts +5 -4
  16. package/dist/encoder/index.d.ts +5 -4
  17. package/dist/encoder/index.js +2 -2
  18. package/dist/index.cjs +58 -39
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +40 -21
  21. package/dist/index.d.ts +40 -21
  22. package/dist/index.js +37 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/metafile-cjs.json +1 -1
  25. package/dist/metafile-esm.json +1 -1
  26. package/dist/parser/index.cjs +21 -21
  27. package/dist/parser/index.d.cts +4 -2
  28. package/dist/parser/index.d.ts +4 -2
  29. package/dist/parser/index.js +2 -2
  30. package/dist/{types-DvNlfbKB.d.cts → types-eG2qalpr.d.cts} +27 -1
  31. package/dist/{types-DvNlfbKB.d.ts → types-eG2qalpr.d.ts} +27 -1
  32. package/dist/{useCborSimpleEncoder-TVxzNJ_9.d.ts → useCborSimpleEncoder-CamvS-_N.d.ts} +7 -3
  33. package/dist/{useCborSimpleEncoder-ButVU988.d.cts → useCborSimpleEncoder-DXgPx62d.d.cts} +7 -3
  34. package/dist/{useCborTag-xV2Pz2VY.d.ts → useCborTag-D4d7xG3-.d.cts} +9 -4
  35. package/dist/{useCborTag-Cs1CZuXZ.d.cts → useCborTag-TYst1KR6.d.ts} +9 -4
  36. package/package.json +1 -1
  37. package/src/__tests__/audit-fixes.test.ts +141 -0
  38. package/src/__tests__/public-api.test.ts +153 -0
  39. package/src/__tests__/roundtrip.test.ts +5 -6
  40. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +103 -5
  41. package/src/encoder/__tests__/cbor-encoder-errors.test.ts +40 -5
  42. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
  43. package/src/encoder/composables/useCborCollectionEncoder.ts +30 -26
  44. package/src/encoder/composables/useCborEncoder.ts +40 -0
  45. package/src/encoder/composables/useCborSimpleEncoder.ts +40 -9
  46. package/src/encoder/types.ts +9 -4
  47. package/src/encoder/utils.ts +33 -1
  48. package/src/index.ts +39 -20
  49. package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
  50. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
  51. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +164 -31
  52. package/src/parser/__tests__/cbor-standard-tags.test.ts +75 -7
  53. package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
  54. package/src/parser/__tests__/utils-errors.test.ts +11 -3
  55. package/src/parser/composables/useCborCollection.ts +51 -45
  56. package/src/parser/composables/useCborDiagnostic.ts +28 -0
  57. package/src/parser/composables/useCborFloat.ts +2 -1
  58. package/src/parser/composables/useCborInteger.ts +24 -10
  59. package/src/parser/composables/useCborParser.ts +448 -208
  60. package/src/parser/composables/useCborTag.ts +53 -38
  61. package/src/parser/types.ts +32 -1
  62. package/src/parser/utils.ts +52 -0
  63. package/dist/chunk-2MTLSQ7E.js.map +0 -1
  64. package/dist/chunk-5A5T56JB.js.map +0 -1
  65. package/dist/chunk-PD72MVTX.cjs.map +0 -1
  66. package/dist/chunk-PTWN7K3Y.cjs.map +0 -1
  67. package/dist/chunk-R62CQQNI.cjs.map +0 -1
  68. package/dist/chunk-ZDZ2B5PE.js.map +0 -1
@@ -1,7 +1,16 @@
1
- import { useCborByteString, useCborTextString, DEFAULT_LIMITS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS } from './chunk-ZDZ2B5PE.js';
1
+ import { useCborByteString, useCborTextString, DEFAULT_LIMITS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS } from './chunk-JESIF5IF.js';
2
2
 
3
3
  // src/parser/utils.ts
4
4
  var hexToBytes = (hex) => {
5
+ if (hex.length === 0) {
6
+ return new Uint8Array(0);
7
+ }
8
+ if (hex.length % 2 !== 0) {
9
+ throw new Error("Hex string must have even length");
10
+ }
11
+ if (!/^[0-9a-fA-F]+$/.test(hex)) {
12
+ throw new Error(`Invalid hex character in: ${hex}`);
13
+ }
5
14
  const bytes = hex.match(/.{1,2}/g);
6
15
  if (!bytes) return new Uint8Array(0);
7
16
  return new Uint8Array(bytes.map((byte) => parseInt(byte, 16)));
@@ -30,6 +39,9 @@ var readUint = (buffer, offset, length) => {
30
39
  for (let i = 0; i < length; i++) {
31
40
  result = result * 256 + readByte(buffer, offset + i);
32
41
  }
42
+ if (result > Number.MAX_SAFE_INTEGER) {
43
+ throw new Error(`Value at offset ${offset} (${length} bytes) exceeds MAX_SAFE_INTEGER; use readBigUint`);
44
+ }
33
45
  return result;
34
46
  };
35
47
  var readBigUint = (buffer, offset, length) => {
@@ -196,6 +208,23 @@ function compareBytes(a, b) {
196
208
  }
197
209
  return 0;
198
210
  }
211
+ function compareBytesLexicographic(a, b) {
212
+ if (!a || !b) {
213
+ throw new Error("compareBytesLexicographic: arguments cannot be null or undefined");
214
+ }
215
+ const min = Math.min(a.length, b.length);
216
+ for (let i = 0; i < min; i++) {
217
+ const byteA = a[i];
218
+ const byteB = b[i];
219
+ if (byteA !== byteB) {
220
+ return byteA - byteB;
221
+ }
222
+ }
223
+ return a.length - b.length;
224
+ }
225
+ function compareMapKeys(a, b, order = "length-first") {
226
+ return order === "bytewise" ? compareBytesLexicographic(a, b) : compareBytes(a, b);
227
+ }
199
228
  function serializeValueForComparison(value) {
200
229
  if (value === null) return "null";
201
230
  if (value === void 0) return "undefined";
@@ -233,9 +262,8 @@ function hasDuplicates(items) {
233
262
 
234
263
  // src/parser/composables/useCborInteger.ts
235
264
  function useCborInteger() {
236
- const parseInteger = (hexString, options) => {
237
- const buffer = hexToBytes(hexString);
238
- const initialByte = readByte(buffer, 0);
265
+ const parseIntegerFromBuffer = (buffer, offset, options) => {
266
+ const initialByte = readByte(buffer, offset);
239
267
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
240
268
  let rawValue;
241
269
  let bytesRead;
@@ -243,16 +271,16 @@ function useCborInteger() {
243
271
  rawValue = additionalInfo;
244
272
  bytesRead = 1;
245
273
  } else if (additionalInfo === 24) {
246
- rawValue = readByte(buffer, 1);
274
+ rawValue = readByte(buffer, offset + 1);
247
275
  bytesRead = 2;
248
276
  } else if (additionalInfo === 25) {
249
- rawValue = readUint(buffer, 1, 2);
277
+ rawValue = readUint(buffer, offset + 1, 2);
250
278
  bytesRead = 3;
251
279
  } else if (additionalInfo === 26) {
252
- rawValue = readUint(buffer, 1, 4);
280
+ rawValue = readUint(buffer, offset + 1, 4);
253
281
  bytesRead = 5;
254
282
  } else if (additionalInfo === 27) {
255
- const bigValue = readBigUint(buffer, 1, 8);
283
+ const bigValue = readBigUint(buffer, offset + 1, 8);
256
284
  if (bigValue <= BigInt(Number.MAX_SAFE_INTEGER)) {
257
285
  rawValue = Number(bigValue);
258
286
  } else {
@@ -292,8 +320,13 @@ function useCborInteger() {
292
320
  bytesRead
293
321
  };
294
322
  };
323
+ const parseInteger = (hexString, options) => {
324
+ const buffer = hexToBytes(hexString);
325
+ return parseIntegerFromBuffer(buffer, 0, options);
326
+ };
295
327
  return {
296
- parseInteger
328
+ parseInteger,
329
+ parseIntegerFromBuffer
297
330
  };
298
331
  }
299
332
 
@@ -702,29 +735,34 @@ function useCborFloat() {
702
735
  return {
703
736
  parse,
704
737
  parseFloat,
705
- parseSimple
738
+ parseSimple,
739
+ parseFromBuffer
706
740
  };
707
741
  }
708
742
 
709
743
  // src/parser/composables/useCborTag.ts
710
744
  function useCborTag() {
711
- const { parseInteger } = useCborInteger();
745
+ const { parseIntegerFromBuffer } = useCborInteger();
712
746
  const { parseByteString, parseTextString } = useCborString();
713
- const { parseFloat, parseSimple } = useCborFloat();
747
+ const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat();
748
+ let parseStartTime = 0;
714
749
  const parseItem = (buffer, offset, options, tagDepth = 0, collectionDepth = 0) => {
750
+ if (parseStartTime > 0 && options?.limits?.maxParseTime) {
751
+ const elapsed = Date.now() - parseStartTime;
752
+ if (elapsed > options.limits.maxParseTime) {
753
+ throw new Error(`Parse timeout: exceeded ${options.limits.maxParseTime}ms limit`);
754
+ }
755
+ }
715
756
  if (offset >= buffer.length) {
716
757
  throw new Error(`Unexpected end of buffer at offset ${offset}`);
717
758
  }
718
759
  const initialByte = readByte(buffer, offset);
719
- const { majorType, additionalInfo } = extractCborHeader(initialByte);
760
+ const { majorType } = extractCborHeader(initialByte);
720
761
  switch (majorType) {
721
762
  case 0:
722
763
  // Unsigned integer
723
- case 1: {
724
- const intHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
725
- const result = parseInteger(intHex, options);
726
- return { value: result.value, bytesRead: result.bytesRead };
727
- }
764
+ case 1:
765
+ return parseIntegerFromBuffer(buffer, offset, options);
728
766
  case 2:
729
767
  return parseByteString(buffer, offset, options);
730
768
  case 3:
@@ -735,16 +773,8 @@ function useCborTag() {
735
773
  return parseMapInternal(buffer, offset, options, collectionDepth);
736
774
  case 6:
737
775
  return parseTagFromBuffer(buffer, offset, options, tagDepth);
738
- case 7: {
739
- const simpleHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
740
- if (additionalInfo >= 25 && additionalInfo <= 27) {
741
- const result = parseFloat(simpleHex, options);
742
- return { value: result.value, bytesRead: result.bytesRead };
743
- } else {
744
- const result = parseSimple(simpleHex, options);
745
- return { value: result.value, bytesRead: result.bytesRead };
746
- }
747
- }
776
+ case 7:
777
+ return parseFloatOrSimpleFromBuffer(buffer, offset, options);
748
778
  default:
749
779
  throw new Error(`Unknown major type: ${majorType}`);
750
780
  }
@@ -1008,11 +1038,11 @@ function useCborTag() {
1008
1038
  if (value.length !== 2) {
1009
1039
  throw new Error(`Tag 4 (decimal fraction) array must have exactly 2 elements [exponent, mantissa], got ${value.length}`);
1010
1040
  }
1011
- if (typeof value[0] !== "number" && typeof value[0] !== "bigint") {
1012
- throw new Error(`Tag 4 (decimal fraction) exponent must be an integer, got ${typeof value[0]}`);
1041
+ if (typeof value[0] !== "number" && typeof value[0] !== "bigint" || typeof value[0] === "number" && !Number.isInteger(value[0])) {
1042
+ throw new Error(`Tag 4 (decimal fraction) exponent must be an integer, got ${value[0]}`);
1013
1043
  }
1014
- if (typeof value[1] !== "number" && typeof value[1] !== "bigint") {
1015
- throw new Error(`Tag 4 (decimal fraction) mantissa must be an integer, got ${typeof value[1]}`);
1044
+ if (typeof value[1] !== "number" && typeof value[1] !== "bigint" || typeof value[1] === "number" && !Number.isInteger(value[1])) {
1045
+ throw new Error(`Tag 4 (decimal fraction) mantissa must be an integer, got ${value[1]}`);
1016
1046
  }
1017
1047
  break;
1018
1048
  case 5:
@@ -1023,11 +1053,11 @@ function useCborTag() {
1023
1053
  if (value.length !== 2) {
1024
1054
  throw new Error(`Tag 5 (bigfloat) array must have exactly 2 elements [exponent, mantissa], got ${value.length}`);
1025
1055
  }
1026
- if (typeof value[0] !== "number" && typeof value[0] !== "bigint") {
1027
- throw new Error(`Tag 5 (bigfloat) exponent must be an integer, got ${typeof value[0]}`);
1056
+ if (typeof value[0] !== "number" && typeof value[0] !== "bigint" || typeof value[0] === "number" && !Number.isInteger(value[0])) {
1057
+ throw new Error(`Tag 5 (bigfloat) exponent must be an integer, got ${value[0]}`);
1028
1058
  }
1029
- if (typeof value[1] !== "number" && typeof value[1] !== "bigint") {
1030
- throw new Error(`Tag 5 (bigfloat) mantissa must be an integer, got ${typeof value[1]}`);
1059
+ if (typeof value[1] !== "number" && typeof value[1] !== "bigint" || typeof value[1] === "number" && !Number.isInteger(value[1])) {
1060
+ throw new Error(`Tag 5 (bigfloat) mantissa must be an integer, got ${value[1]}`);
1031
1061
  }
1032
1062
  break;
1033
1063
  case 32:
@@ -1192,6 +1222,9 @@ function useCborTag() {
1192
1222
  throw new Error(`Tag nesting depth ${tagDepth} exceeds limit of ${maxTagDepth}`);
1193
1223
  }
1194
1224
  const { tagNumber, bytesConsumed } = parseTagNumber(buffer, offset + 1, additionalInfo);
1225
+ if (options?.validateCanonical) {
1226
+ validateCanonicalInteger(tagNumber, additionalInfo);
1227
+ }
1195
1228
  let currentOffset = offset + 1 + bytesConsumed;
1196
1229
  if (currentOffset >= buffer.length) {
1197
1230
  throw new Error(`Unexpected end of buffer after tag ${tagNumber}`);
@@ -1231,12 +1264,25 @@ function useCborTag() {
1231
1264
  const parseTag = (hexString, options) => {
1232
1265
  const cleanHex = hexString.replace(/\s+/g, "");
1233
1266
  const buffer = hexToBytes(cleanHex);
1234
- return parseTagFromBuffer(buffer, 0, options);
1267
+ const isTopLevel = parseStartTime === 0;
1268
+ if (isTopLevel && options?.limits?.maxParseTime) {
1269
+ parseStartTime = Date.now();
1270
+ }
1271
+ try {
1272
+ return parseTagFromBuffer(buffer, 0, options);
1273
+ } finally {
1274
+ if (isTopLevel) {
1275
+ parseStartTime = 0;
1276
+ }
1277
+ }
1235
1278
  };
1236
1279
  const parse = parseTag;
1237
1280
  return {
1238
1281
  parseTag,
1239
- parse
1282
+ parse,
1283
+ parseTagFromBuffer,
1284
+ validateTagSemantics,
1285
+ decodePlutusConstructor
1240
1286
  };
1241
1287
  }
1242
1288
 
@@ -1295,17 +1341,18 @@ var logger = {
1295
1341
 
1296
1342
  // src/parser/composables/useCborCollection.ts
1297
1343
  function useCborCollection() {
1298
- const { parseInteger } = useCborInteger();
1344
+ const { parseIntegerFromBuffer } = useCborInteger();
1299
1345
  const { parseByteString, parseTextString } = useCborString();
1300
- const { parse: parseFloatOrSimple } = useCborFloat();
1301
- const { parseTag } = useCborTag();
1302
- const convertKeyToString = (key) => {
1303
- if (key instanceof Uint8Array) {
1304
- return bytesToHex(key);
1305
- }
1306
- return String(key);
1307
- };
1346
+ const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat();
1347
+ const { parseTagFromBuffer } = useCborTag();
1348
+ let parseStartTime = 0;
1308
1349
  const parseItem = (buffer, offset, options, depth = 0) => {
1350
+ if (parseStartTime > 0 && options?.limits?.maxParseTime) {
1351
+ const elapsed = Date.now() - parseStartTime;
1352
+ if (elapsed > options.limits.maxParseTime) {
1353
+ throw new Error(`Parse timeout: exceeded ${options.limits.maxParseTime}ms limit`);
1354
+ }
1355
+ }
1309
1356
  if (offset >= buffer.length) {
1310
1357
  throw new Error(`Unexpected end of buffer at offset ${offset}`);
1311
1358
  }
@@ -1314,11 +1361,8 @@ function useCborCollection() {
1314
1361
  switch (majorType) {
1315
1362
  case 0:
1316
1363
  // Unsigned integer
1317
- case 1: {
1318
- const intHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1319
- const result = parseInteger(intHex, options);
1320
- return { value: result.value, bytesRead: result.bytesRead };
1321
- }
1364
+ case 1:
1365
+ return parseIntegerFromBuffer(buffer, offset, options);
1322
1366
  case 2:
1323
1367
  return parseByteString(buffer, offset, options);
1324
1368
  case 3:
@@ -1327,16 +1371,10 @@ function useCborCollection() {
1327
1371
  return parseArrayFromBuffer(buffer, offset, options, depth);
1328
1372
  case 5:
1329
1373
  return parseMapFromBuffer(buffer, offset, options, depth);
1330
- case 6: {
1331
- const tagHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1332
- const result = parseTag(tagHex, options);
1333
- return { value: result.value, bytesRead: result.bytesRead };
1334
- }
1335
- case 7: {
1336
- const floatHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1337
- const result = parseFloatOrSimple(floatHex, options);
1338
- return { value: result.value, bytesRead: result.bytesRead };
1339
- }
1374
+ case 6:
1375
+ return parseTagFromBuffer(buffer, offset, options);
1376
+ case 7:
1377
+ return parseFloatOrSimpleFromBuffer(buffer, offset, options);
1340
1378
  default:
1341
1379
  throw new Error(`Unknown major type: ${majorType}`);
1342
1380
  }
@@ -1478,7 +1516,7 @@ function useCborCollection() {
1478
1516
  }
1479
1517
  const valueResult = parseItem(buffer, currentOffset, options, depth + 1);
1480
1518
  currentOffset += valueResult.bytesRead;
1481
- const keyString = convertKeyToString(keyResult.value);
1519
+ const keyString = serializeValueForComparison(keyResult.value);
1482
1520
  if (seenKeys.has(keyString)) {
1483
1521
  const mode = options?.dupMapKeyMode || "allow";
1484
1522
  if (mode === "reject") {
@@ -1516,7 +1554,7 @@ function useCborCollection() {
1516
1554
  }
1517
1555
  const valueResult = parseItem(buffer, currentOffset, options, depth + 1);
1518
1556
  currentOffset += valueResult.bytesRead;
1519
- const keyString = convertKeyToString(keyResult.value);
1557
+ const keyString = serializeValueForComparison(keyResult.value);
1520
1558
  if (seenKeys.has(keyString)) {
1521
1559
  const mode = options?.dupMapKeyMode || "allow";
1522
1560
  if (mode === "reject") {
@@ -1532,14 +1570,15 @@ function useCborCollection() {
1532
1570
  }
1533
1571
  map[ALL_ENTRIES_SYMBOL] = allEntries;
1534
1572
  if (options?.validateCanonical && keyBytes.length > 1) {
1573
+ const keyOrder = options?.mapKeyOrder ?? "length-first";
1535
1574
  for (let i = 1; i < keyBytes.length; i++) {
1536
1575
  const prevKey = keyBytes[i - 1];
1537
1576
  const currKey = keyBytes[i];
1538
1577
  if (prevKey && currKey) {
1539
- const cmp = compareBytes(prevKey, currKey);
1578
+ const cmp = compareMapKeys(prevKey, currKey, keyOrder);
1540
1579
  if (cmp > 0) {
1541
1580
  throw new Error(
1542
- `Map keys are not in canonical order: key at index ${i} should come before key at index ${i - 1}`
1581
+ `Map keys are not in canonical order (${keyOrder}): key at index ${i} should come before key at index ${i - 1}`
1543
1582
  );
1544
1583
  }
1545
1584
  if (cmp === 0) {
@@ -1556,12 +1595,26 @@ function useCborCollection() {
1556
1595
  const parseArray = (hexString, options) => {
1557
1596
  const cleanHex = hexString.replace(/\s+/g, "");
1558
1597
  const buffer = hexToBytes(cleanHex);
1559
- return parseArrayFromBuffer(buffer, 0, options, 0);
1598
+ if (options?.limits?.maxParseTime) {
1599
+ parseStartTime = Date.now();
1600
+ }
1601
+ try {
1602
+ return parseArrayFromBuffer(buffer, 0, options, 0);
1603
+ } finally {
1604
+ parseStartTime = 0;
1605
+ }
1560
1606
  };
1561
1607
  const parseMap = (hexString, options) => {
1562
1608
  const cleanHex = hexString.replace(/\s+/g, "");
1563
1609
  const buffer = hexToBytes(cleanHex);
1564
- return parseMapFromBuffer(buffer, 0, options, 0);
1610
+ if (options?.limits?.maxParseTime) {
1611
+ parseStartTime = Date.now();
1612
+ }
1613
+ try {
1614
+ return parseMapFromBuffer(buffer, 0, options, 0);
1615
+ } finally {
1616
+ parseStartTime = 0;
1617
+ }
1565
1618
  };
1566
1619
  return {
1567
1620
  parseArray,
@@ -1585,6 +1638,9 @@ function useCborParser() {
1585
1638
  validateSetUniqueness: options.validateSetUniqueness ?? (options.strict ? true : DEFAULT_OPTIONS.validateSetUniqueness),
1586
1639
  validateTagSemantics: options.validateTagSemantics ?? (options.strict ? true : DEFAULT_OPTIONS.validateTagSemantics),
1587
1640
  validatePlutusSemantics: options.validatePlutusSemantics ?? (options.strict ? true : DEFAULT_OPTIONS.validatePlutusSemantics),
1641
+ mapKeyOrder: options.mapKeyOrder ?? DEFAULT_OPTIONS.mapKeyOrder,
1642
+ // Strict mode rejects trailing data after the top-level item (well-formedness).
1643
+ allowTrailingData: options.allowTrailingData ?? (options.strict ? false : DEFAULT_OPTIONS.allowTrailingData),
1588
1644
  limits: {
1589
1645
  maxInputSize: options.limits?.maxInputSize ?? DEFAULT_LIMITS.maxInputSize,
1590
1646
  maxOutputSize: options.limits?.maxOutputSize ?? DEFAULT_LIMITS.maxOutputSize,
@@ -1605,13 +1661,25 @@ function useCborParser() {
1605
1661
  throw new Error(`Parse timeout: exceeded ${ctx.options.limits.maxParseTime}ms limit (elapsed: ${elapsed}ms)`);
1606
1662
  }
1607
1663
  };
1608
- const { parseInteger } = useCborInteger();
1609
- const { parseString } = useCborString();
1664
+ const { parseInteger, parseIntegerFromBuffer: integerFromBuffer } = useCborInteger();
1665
+ const { parseString, parseByteString: byteStringFromBuffer, parseTextString: textStringFromBuffer } = useCborString();
1610
1666
  const { parseArray, parseMap } = useCborCollection();
1611
- const { parseTag } = useCborTag();
1612
- const { parse: parseFloatOrSimple } = useCborFloat();
1613
- const parse = (hexString, options) => {
1614
- const cleanHex = hexString.replace(/\s+/g, "");
1667
+ const { parseTag, validateTagSemantics, decodePlutusConstructor } = useCborTag();
1668
+ const { parse: parseFloatOrSimple, parseFromBuffer: floatOrSimpleFromBuffer } = useCborFloat();
1669
+ const parse = (input, options) => {
1670
+ const mergedOptions = mergeOptions(options);
1671
+ if (input instanceof Uint8Array) {
1672
+ if (input.length === 0) {
1673
+ throw new Error("Empty input");
1674
+ }
1675
+ if (mergedOptions.limits?.maxInputSize && input.length > mergedOptions.limits.maxInputSize) {
1676
+ throw new Error(`Input size ${input.length} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
1677
+ }
1678
+ const bufResult = dispatchFromBuffer(input, 0, mergedOptions);
1679
+ checkTrailingData(bufResult.bytesRead, input.length, mergedOptions);
1680
+ return bufResult;
1681
+ }
1682
+ const cleanHex = input.replace(/\s+/g, "");
1615
1683
  if (!cleanHex || cleanHex.length === 0) {
1616
1684
  throw new Error("Empty hex string");
1617
1685
  }
@@ -1621,7 +1689,6 @@ function useCborParser() {
1621
1689
  if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
1622
1690
  throw new Error(`Invalid hex character in: ${cleanHex}`);
1623
1691
  }
1624
- const mergedOptions = mergeOptions(options);
1625
1692
  const inputSize = cleanHex.length / 2;
1626
1693
  if (mergedOptions.limits?.maxInputSize && inputSize > mergedOptions.limits.maxInputSize) {
1627
1694
  throw new Error(`Input size ${inputSize} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
@@ -1629,47 +1696,77 @@ function useCborParser() {
1629
1696
  const buffer = hexToBytes(cleanHex);
1630
1697
  const initialByte = readByte(buffer, 0);
1631
1698
  const { majorType } = extractCborHeader(initialByte);
1699
+ let result;
1632
1700
  switch (majorType) {
1633
1701
  case 0:
1634
1702
  // Unsigned integer
1635
1703
  case 1:
1636
- return parseInteger(cleanHex, mergedOptions);
1704
+ result = parseInteger(cleanHex, mergedOptions);
1705
+ break;
1637
1706
  case 2:
1638
1707
  // Byte string
1639
1708
  case 3:
1640
- return parseString(cleanHex, mergedOptions);
1709
+ result = parseString(cleanHex, mergedOptions);
1710
+ break;
1641
1711
  case 4:
1642
- return parseArray(cleanHex, mergedOptions);
1712
+ result = parseArray(cleanHex, mergedOptions);
1713
+ break;
1643
1714
  case 5:
1644
- return parseMap(cleanHex, mergedOptions);
1715
+ result = parseMap(cleanHex, mergedOptions);
1716
+ break;
1645
1717
  case 6:
1646
- return parseTag(cleanHex, mergedOptions);
1718
+ result = parseTag(cleanHex, mergedOptions);
1719
+ break;
1647
1720
  case 7:
1648
- return parseFloatOrSimple(cleanHex, mergedOptions);
1721
+ result = parseFloatOrSimple(cleanHex, mergedOptions);
1722
+ break;
1649
1723
  default:
1650
1724
  throw new Error(`Unknown major type: ${majorType}`);
1651
1725
  }
1726
+ checkTrailingData(result.bytesRead, buffer.length, mergedOptions);
1727
+ return result;
1652
1728
  };
1653
- const parseWithSourceMap = (hexString, options) => {
1654
- const cleanHex = hexString.replace(/\s+/g, "");
1655
- if (!cleanHex || cleanHex.length === 0) {
1656
- throw new Error("Empty hex string");
1657
- }
1658
- if (cleanHex.length % 2 !== 0) {
1659
- throw new Error("Hex string must have even length");
1660
- }
1661
- if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
1662
- throw new Error(`Invalid hex character in: ${cleanHex}`);
1729
+ const checkTrailingData = (bytesRead, totalLength, opts) => {
1730
+ if (!opts.allowTrailingData && bytesRead < totalLength) {
1731
+ throw new Error(
1732
+ `Trailing data: ${totalLength - bytesRead} byte(s) remain after the top-level CBOR item (bytesRead=${bytesRead}, length=${totalLength}). Use parseSequence to decode multiple items.`
1733
+ );
1663
1734
  }
1735
+ };
1736
+ const parseWithSourceMap = (input, options) => {
1664
1737
  const mergedOptions = mergeOptions(options);
1665
- const inputSize = cleanHex.length / 2;
1666
- if (mergedOptions.limits?.maxInputSize && inputSize > mergedOptions.limits.maxInputSize) {
1667
- throw new Error(`Input size ${inputSize} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
1738
+ let buffer;
1739
+ if (input instanceof Uint8Array) {
1740
+ if (input.length === 0) {
1741
+ throw new Error("Empty input");
1742
+ }
1743
+ if (mergedOptions.limits?.maxInputSize && input.length > mergedOptions.limits.maxInputSize) {
1744
+ throw new Error(`Input size ${input.length} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
1745
+ }
1746
+ buffer = input;
1747
+ } else {
1748
+ const cleanHex = input.replace(/\s+/g, "");
1749
+ if (!cleanHex || cleanHex.length === 0) {
1750
+ throw new Error("Empty hex string");
1751
+ }
1752
+ if (cleanHex.length % 2 !== 0) {
1753
+ throw new Error("Hex string must have even length");
1754
+ }
1755
+ if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
1756
+ throw new Error(`Invalid hex character in: ${cleanHex}`);
1757
+ }
1758
+ const inputSize = cleanHex.length / 2;
1759
+ if (mergedOptions.limits?.maxInputSize && inputSize > mergedOptions.limits.maxInputSize) {
1760
+ throw new Error(`Input size ${inputSize} bytes exceeds limit of ${mergedOptions.limits.maxInputSize} bytes`);
1761
+ }
1762
+ buffer = hexToBytes(cleanHex);
1668
1763
  }
1669
- const buffer = hexToBytes(cleanHex);
1670
1764
  const sourceMap = [];
1671
1765
  const ctx = {
1672
1766
  buffer,
1767
+ offset: 0,
1768
+ sourceMap,
1769
+ currentDepth: 0,
1673
1770
  startTime: Date.now(),
1674
1771
  bytesAllocated: 0,
1675
1772
  options: mergedOptions
@@ -1812,19 +1909,57 @@ function useCborParser() {
1812
1909
  }
1813
1910
  return result;
1814
1911
  };
1912
+ const dispatchFromBuffer = (buffer, offset, options) => {
1913
+ const initialByte = readByte(buffer, offset);
1914
+ const { majorType } = extractCborHeader(initialByte);
1915
+ switch (majorType) {
1916
+ case 0:
1917
+ // Unsigned integer
1918
+ case 1:
1919
+ return integerFromBuffer(buffer, offset, options);
1920
+ case 2:
1921
+ return byteStringFromBuffer(buffer, offset, options);
1922
+ case 3:
1923
+ return textStringFromBuffer(buffer, offset, options);
1924
+ case 4: {
1925
+ const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1926
+ return parseArray(hexString, options);
1927
+ }
1928
+ case 5: {
1929
+ const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1930
+ return parseMap(hexString, options);
1931
+ }
1932
+ case 6: {
1933
+ const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1934
+ return parseTag(hexString, options);
1935
+ }
1936
+ case 7:
1937
+ return floatOrSimpleFromBuffer(buffer, offset, options);
1938
+ default:
1939
+ throw new Error(`Unknown major type: ${majorType}`);
1940
+ }
1941
+ };
1815
1942
  const parseIntegerFromBuffer = (buffer, offset, options) => {
1816
- const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1817
- return parseInteger(hexString, options);
1943
+ return integerFromBuffer(buffer, offset, options);
1818
1944
  };
1819
1945
  const parseStringFromBuffer = (buffer, offset, options) => {
1820
- const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1821
- return parseString(hexString, options);
1946
+ const initialByte = readByte(buffer, offset);
1947
+ const { majorType } = extractCborHeader(initialByte);
1948
+ if (majorType === 2) {
1949
+ return byteStringFromBuffer(buffer, offset, options);
1950
+ }
1951
+ return textStringFromBuffer(buffer, offset, options);
1822
1952
  };
1823
1953
  const parseFloatFromBuffer = (buffer, offset, options) => {
1824
- const hexString = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
1825
- return parseFloatOrSimple(hexString, options);
1954
+ return floatOrSimpleFromBuffer(buffer, offset, options);
1826
1955
  };
1827
1956
  const parseArrayWithMap = (ctx, offset, path, sourceMap) => {
1957
+ const previousDepth = ctx.currentDepth ?? 0;
1958
+ const maxDepth = ctx.options?.limits?.maxDepth;
1959
+ if (maxDepth !== void 0 && previousDepth >= maxDepth) {
1960
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
1961
+ }
1962
+ ctx.currentDepth = previousDepth + 1;
1828
1963
  const startOffset = offset;
1829
1964
  const initialByte = readByte(ctx.buffer, offset);
1830
1965
  const { additionalInfo } = extractCborHeader(initialByte);
@@ -1851,6 +1986,10 @@ function useCborParser() {
1851
1986
  length = Number(bigLength);
1852
1987
  currentOffset += 8;
1853
1988
  } else if (additionalInfo === 31) {
1989
+ const isIndefiniteAllowed = ctx.options?.allowIndefinite ?? !(ctx.options?.validateCanonical || ctx.options?.strict);
1990
+ if (!isIndefiniteAllowed) {
1991
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
1992
+ }
1854
1993
  isIndefinite = true;
1855
1994
  length = 0;
1856
1995
  } else {
@@ -1867,49 +2006,70 @@ function useCborParser() {
1867
2006
  isHeader: true,
1868
2007
  headerEnd
1869
2008
  });
1870
- const childPaths = [];
1871
- if (isIndefinite) {
1872
- let index = 0;
1873
- while (currentOffset < ctx.buffer.length) {
1874
- const nextByte = readByte(ctx.buffer, currentOffset);
1875
- if (nextByte === 255) {
1876
- currentOffset++;
1877
- break;
2009
+ try {
2010
+ const childPaths = [];
2011
+ if (isIndefinite) {
2012
+ let index = 0;
2013
+ let foundBreak = false;
2014
+ while (currentOffset < ctx.buffer.length) {
2015
+ const nextByte = readByte(ctx.buffer, currentOffset);
2016
+ if (nextByte === 255) {
2017
+ currentOffset++;
2018
+ foundBreak = true;
2019
+ break;
2020
+ }
2021
+ if (ctx.options?.limits?.maxArrayLength && index >= ctx.options.limits.maxArrayLength) {
2022
+ throw new Error(`Array length exceeds limit of ${ctx.options.limits.maxArrayLength}`);
2023
+ }
2024
+ const elementPath = `${path}[${index}]`;
2025
+ childPaths.push(elementPath);
2026
+ const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
2027
+ items.push(elementResult.value);
2028
+ currentOffset += elementResult.bytesRead;
2029
+ const elementEntry = sourceMap.find((e) => e.path === elementPath);
2030
+ if (elementEntry) {
2031
+ elementEntry.parent = path;
2032
+ }
2033
+ index++;
1878
2034
  }
1879
- const elementPath = `${path}[${index}]`;
1880
- childPaths.push(elementPath);
1881
- const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
1882
- items.push(elementResult.value);
1883
- currentOffset += elementResult.bytesRead;
1884
- const elementEntry = sourceMap.find((e) => e.path === elementPath);
1885
- if (elementEntry) {
1886
- elementEntry.parent = path;
2035
+ if (!foundBreak) {
2036
+ throw new Error("Indefinite-length array missing break code (0xFF)");
1887
2037
  }
1888
- index++;
1889
- }
1890
- } else {
1891
- for (let i = 0; i < length; i++) {
1892
- const elementPath = `${path}[${i}]`;
1893
- childPaths.push(elementPath);
1894
- const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
1895
- items.push(elementResult.value);
1896
- currentOffset += elementResult.bytesRead;
1897
- const elementEntry = sourceMap.find((e) => e.path === elementPath);
1898
- if (elementEntry) {
1899
- elementEntry.parent = path;
2038
+ } else {
2039
+ if (ctx.options?.limits?.maxArrayLength && length > ctx.options.limits.maxArrayLength) {
2040
+ throw new Error(`Array length ${length} exceeds limit of ${ctx.options.limits.maxArrayLength}`);
2041
+ }
2042
+ for (let i = 0; i < length; i++) {
2043
+ const elementPath = `${path}[${i}]`;
2044
+ childPaths.push(elementPath);
2045
+ const elementResult = parseValueWithMap(ctx, currentOffset, elementPath, sourceMap);
2046
+ items.push(elementResult.value);
2047
+ currentOffset += elementResult.bytesRead;
2048
+ const elementEntry = sourceMap.find((e) => e.path === elementPath);
2049
+ if (elementEntry) {
2050
+ elementEntry.parent = path;
2051
+ }
1900
2052
  }
1901
2053
  }
2054
+ const bytesRead = currentOffset - offset;
2055
+ if (childPaths.length > 0 && sourceMap[arrayEntryIndex]) {
2056
+ sourceMap[arrayEntryIndex].children = childPaths;
2057
+ }
2058
+ return {
2059
+ value: items,
2060
+ bytesRead
2061
+ };
2062
+ } finally {
2063
+ ctx.currentDepth = previousDepth;
1902
2064
  }
1903
- const bytesRead = currentOffset - offset;
1904
- if (childPaths.length > 0 && sourceMap[arrayEntryIndex]) {
1905
- sourceMap[arrayEntryIndex].children = childPaths;
1906
- }
1907
- return {
1908
- value: items,
1909
- bytesRead
1910
- };
1911
2065
  };
1912
2066
  const parseMapWithMap = (ctx, offset, path, sourceMap) => {
2067
+ const previousDepth = ctx.currentDepth ?? 0;
2068
+ const maxDepth = ctx.options?.limits?.maxDepth;
2069
+ if (maxDepth !== void 0 && previousDepth >= maxDepth) {
2070
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
2071
+ }
2072
+ ctx.currentDepth = previousDepth + 1;
1913
2073
  const startOffset = offset;
1914
2074
  const initialByte = readByte(ctx.buffer, offset);
1915
2075
  const { additionalInfo } = extractCborHeader(initialByte);
@@ -1936,6 +2096,10 @@ function useCborParser() {
1936
2096
  length = Number(bigLength);
1937
2097
  currentOffset += 8;
1938
2098
  } else if (additionalInfo === 31) {
2099
+ const isIndefiniteAllowed = ctx.options?.allowIndefinite ?? !(ctx.options?.validateCanonical || ctx.options?.strict);
2100
+ if (!isIndefiniteAllowed) {
2101
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
2102
+ }
1939
2103
  isIndefinite = true;
1940
2104
  length = 0;
1941
2105
  } else {
@@ -1952,72 +2116,91 @@ function useCborParser() {
1952
2116
  isHeader: true,
1953
2117
  headerEnd
1954
2118
  });
1955
- const childPaths = [];
1956
- const seenKeys = /* @__PURE__ */ new Set();
1957
- if (isIndefinite) {
1958
- while (currentOffset < ctx.buffer.length) {
1959
- const nextByte = readByte(ctx.buffer, currentOffset);
1960
- if (nextByte === 255) {
1961
- currentOffset++;
1962
- break;
1963
- }
1964
- const keyPath = `${path}${path ? "." : ""}#key`;
1965
- const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
1966
- currentOffset += keyResult.bytesRead;
1967
- const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
1968
- if (seenKeys.has(keyString)) {
1969
- const mode = ctx.options?.dupMapKeyMode || "allow";
1970
- if (mode === "reject") {
1971
- throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
1972
- } else if (mode === "warn") {
1973
- logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2119
+ try {
2120
+ const childPaths = [];
2121
+ const seenKeys = /* @__PURE__ */ new Set();
2122
+ if (isIndefinite) {
2123
+ let count = 0;
2124
+ let foundBreak = false;
2125
+ while (currentOffset < ctx.buffer.length) {
2126
+ const nextByte = readByte(ctx.buffer, currentOffset);
2127
+ if (nextByte === 255) {
2128
+ currentOffset++;
2129
+ foundBreak = true;
2130
+ break;
2131
+ }
2132
+ if (ctx.options?.limits?.maxMapSize && count >= ctx.options.limits.maxMapSize) {
2133
+ throw new Error(`Map size exceeds limit of ${ctx.options.limits.maxMapSize}`);
2134
+ }
2135
+ const keyPath = `${path}${path ? "." : ""}#key`;
2136
+ const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
2137
+ currentOffset += keyResult.bytesRead;
2138
+ const keyForDupCheck = serializeValueForComparison(keyResult.value);
2139
+ const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
2140
+ if (seenKeys.has(keyForDupCheck)) {
2141
+ const mode = ctx.options?.dupMapKeyMode || "allow";
2142
+ if (mode === "reject") {
2143
+ throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2144
+ } else if (mode === "warn") {
2145
+ logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2146
+ }
1974
2147
  }
2148
+ seenKeys.add(keyForDupCheck);
2149
+ const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
2150
+ childPaths.push(valuePath);
2151
+ const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
2152
+ map.set(keyResult.value, valueResult.value);
2153
+ currentOffset += valueResult.bytesRead;
2154
+ const valueEntry = sourceMap.find((e) => e.path === valuePath);
2155
+ if (valueEntry) {
2156
+ valueEntry.parent = path;
2157
+ }
2158
+ count++;
1975
2159
  }
1976
- seenKeys.add(keyString);
1977
- const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
1978
- childPaths.push(valuePath);
1979
- const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
1980
- map.set(keyResult.value, valueResult.value);
1981
- currentOffset += valueResult.bytesRead;
1982
- const valueEntry = sourceMap.find((e) => e.path === valuePath);
1983
- if (valueEntry) {
1984
- valueEntry.parent = path;
2160
+ if (!foundBreak) {
2161
+ throw new Error("Indefinite-length map missing break code (0xFF)");
1985
2162
  }
1986
- }
1987
- } else {
1988
- for (let i = 0; i < length; i++) {
1989
- const keyPath = `${path}${path ? "." : ""}#key${i}`;
1990
- const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
1991
- currentOffset += keyResult.bytesRead;
1992
- const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
1993
- if (seenKeys.has(keyString)) {
1994
- const mode = ctx.options?.dupMapKeyMode || "allow";
1995
- if (mode === "reject") {
1996
- throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
1997
- } else if (mode === "warn") {
1998
- logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2163
+ } else {
2164
+ if (ctx.options?.limits?.maxMapSize && length > ctx.options.limits.maxMapSize) {
2165
+ throw new Error(`Map size ${length} exceeds limit of ${ctx.options.limits.maxMapSize}`);
2166
+ }
2167
+ for (let i = 0; i < length; i++) {
2168
+ const keyPath = `${path}${path ? "." : ""}#key${i}`;
2169
+ const keyResult = parseValueWithMap(ctx, currentOffset, keyPath, sourceMap);
2170
+ currentOffset += keyResult.bytesRead;
2171
+ const keyForDupCheck = serializeValueForComparison(keyResult.value);
2172
+ const keyString = keyResult.value instanceof Uint8Array ? Array.from(keyResult.value).map((b) => b.toString(16).padStart(2, "0")).join("") : String(keyResult.value);
2173
+ if (seenKeys.has(keyForDupCheck)) {
2174
+ const mode = ctx.options?.dupMapKeyMode || "allow";
2175
+ if (mode === "reject") {
2176
+ throw new Error(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2177
+ } else if (mode === "warn") {
2178
+ logger.warn(`Duplicate map key detected: ${keyString} at offset ${currentOffset}`);
2179
+ }
2180
+ }
2181
+ seenKeys.add(keyForDupCheck);
2182
+ const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
2183
+ childPaths.push(valuePath);
2184
+ const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
2185
+ map.set(keyResult.value, valueResult.value);
2186
+ currentOffset += valueResult.bytesRead;
2187
+ const valueEntry = sourceMap.find((e) => e.path === valuePath);
2188
+ if (valueEntry) {
2189
+ valueEntry.parent = path;
1999
2190
  }
2000
- }
2001
- seenKeys.add(keyString);
2002
- const valuePath = path ? `${path}.${keyString}` : `.${keyString}`;
2003
- childPaths.push(valuePath);
2004
- const valueResult = parseValueWithMap(ctx, currentOffset, valuePath, sourceMap);
2005
- map.set(keyResult.value, valueResult.value);
2006
- currentOffset += valueResult.bytesRead;
2007
- const valueEntry = sourceMap.find((e) => e.path === valuePath);
2008
- if (valueEntry) {
2009
- valueEntry.parent = path;
2010
2191
  }
2011
2192
  }
2193
+ const bytesRead = currentOffset - offset;
2194
+ if (sourceMap[mapEntryIndex]) {
2195
+ sourceMap[mapEntryIndex].children = childPaths;
2196
+ }
2197
+ return {
2198
+ value: map,
2199
+ bytesRead
2200
+ };
2201
+ } finally {
2202
+ ctx.currentDepth = previousDepth;
2012
2203
  }
2013
- const bytesRead = currentOffset - offset;
2014
- if (sourceMap[mapEntryIndex]) {
2015
- sourceMap[mapEntryIndex].children = childPaths;
2016
- }
2017
- return {
2018
- value: map,
2019
- bytesRead
2020
- };
2021
2204
  };
2022
2205
  const parseTagNumberHelper = (buffer, offset, ai) => {
2023
2206
  if (ai < 24) {
@@ -2045,6 +2228,12 @@ function useCborParser() {
2045
2228
  }
2046
2229
  };
2047
2230
  const parseTagWithMap = (ctx, offset, path, sourceMap) => {
2231
+ const previousTagDepth = ctx.currentTagDepth ?? 0;
2232
+ const maxTagDepth = ctx.options?.limits?.maxTagDepth ?? DEFAULT_LIMITS.maxTagDepth;
2233
+ if (previousTagDepth >= maxTagDepth) {
2234
+ throw new Error(`Tag nesting depth ${previousTagDepth} exceeds limit of ${maxTagDepth}`);
2235
+ }
2236
+ ctx.currentTagDepth = previousTagDepth + 1;
2048
2237
  const startOffset = offset;
2049
2238
  const initialByte = readByte(ctx.buffer, offset);
2050
2239
  const { additionalInfo } = extractCborHeader(initialByte);
@@ -2053,6 +2242,9 @@ function useCborParser() {
2053
2242
  offset + 1,
2054
2243
  additionalInfo
2055
2244
  );
2245
+ if (ctx.options?.validateCanonical) {
2246
+ validateCanonicalInteger(tagNumber, additionalInfo);
2247
+ }
2056
2248
  let currentOffset = offset + 1 + bytesConsumed;
2057
2249
  const headerEnd = currentOffset;
2058
2250
  const tagEntryIndex = sourceMap.length;
@@ -2076,10 +2268,30 @@ function useCborParser() {
2076
2268
  if (valueEntry) {
2077
2269
  valueEntry.parent = path;
2078
2270
  }
2079
- const hexString = Array.from(ctx.buffer.slice(startOffset, currentOffset)).map((b) => b.toString(16).padStart(2, "0")).join("");
2080
- const tagResult = parseTag(hexString, ctx.options);
2271
+ let finalValue = valueResult.value;
2272
+ if ((tagNumber === 2 || tagNumber === 3) && finalValue instanceof Uint8Array) {
2273
+ const maxBignumBytes = ctx.options?.limits?.maxBignumBytes ?? DEFAULT_LIMITS.maxBignumBytes;
2274
+ if (finalValue.length > maxBignumBytes) {
2275
+ throw new Error(
2276
+ `Bignum (tag ${tagNumber}) size ${finalValue.length} bytes exceeds limit of ${maxBignumBytes} bytes`
2277
+ );
2278
+ }
2279
+ let bigintValue = 0n;
2280
+ for (let i = 0; i < finalValue.length; i++) {
2281
+ bigintValue = bigintValue << 8n | BigInt(finalValue[i]);
2282
+ }
2283
+ finalValue = tagNumber === 2 ? bigintValue : -1n - bigintValue;
2284
+ }
2285
+ validateTagSemantics(tagNumber, finalValue, ctx.options);
2286
+ const plutusConstr = decodePlutusConstructor(tagNumber, finalValue);
2287
+ const taggedValue = {
2288
+ tag: tagNumber,
2289
+ value: finalValue,
2290
+ ...plutusConstr && { plutus: plutusConstr }
2291
+ };
2292
+ ctx.currentTagDepth = previousTagDepth;
2081
2293
  return {
2082
- value: tagResult.value,
2294
+ value: taggedValue,
2083
2295
  bytesRead: currentOffset - startOffset
2084
2296
  };
2085
2297
  };
@@ -2094,28 +2306,42 @@ function useCborParser() {
2094
2306
  if (ai < 20) return `Simple Value ${ai}`;
2095
2307
  return "Simple Value";
2096
2308
  };
2097
- const parseSequence = (hexString, options) => {
2098
- const cleanHex = hexString.replace(/\s+/g, "");
2099
- if (!cleanHex || cleanHex.length === 0) {
2100
- return [];
2101
- }
2102
- if (cleanHex.length % 2 !== 0) {
2103
- throw new Error("Hex string must have even length");
2104
- }
2105
- if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
2106
- throw new Error(`Invalid hex character in: ${cleanHex}`);
2107
- }
2309
+ const parseSequence = (input, options) => {
2108
2310
  const mergedOptions = mergeOptions(options);
2109
- const buffer = hexToBytes(cleanHex);
2311
+ let buffer;
2312
+ if (input instanceof Uint8Array) {
2313
+ if (input.length === 0) {
2314
+ return [];
2315
+ }
2316
+ buffer = input;
2317
+ } else {
2318
+ const cleanHex = input.replace(/\s+/g, "");
2319
+ if (!cleanHex || cleanHex.length === 0) {
2320
+ return [];
2321
+ }
2322
+ if (cleanHex.length % 2 !== 0) {
2323
+ throw new Error("Hex string must have even length");
2324
+ }
2325
+ if (!/^[0-9a-fA-F]+$/.test(cleanHex)) {
2326
+ throw new Error(`Invalid hex character in: ${cleanHex}`);
2327
+ }
2328
+ buffer = hexToBytes(cleanHex);
2329
+ }
2110
2330
  const results = [];
2111
2331
  let offset = 0;
2332
+ const sequenceStartTime = mergedOptions.limits?.maxParseTime ? Date.now() : 0;
2112
2333
  while (offset < buffer.length) {
2334
+ if (sequenceStartTime > 0 && mergedOptions.limits?.maxParseTime) {
2335
+ const elapsed = Date.now() - sequenceStartTime;
2336
+ if (elapsed > mergedOptions.limits.maxParseTime) {
2337
+ throw new Error(`Parse timeout: exceeded ${mergedOptions.limits.maxParseTime}ms limit`);
2338
+ }
2339
+ }
2113
2340
  const byte = readByte(buffer, offset);
2114
2341
  if (byte === 255) {
2115
2342
  throw new Error(`Unexpected break code (0xff) at offset ${offset} - not inside indefinite-length item`);
2116
2343
  }
2117
- const remainingHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
2118
- const result = parse(remainingHex, mergedOptions);
2344
+ const result = dispatchFromBuffer(buffer, offset, mergedOptions);
2119
2345
  results.push(result.value);
2120
2346
  offset += result.bytesRead;
2121
2347
  }
@@ -2142,8 +2368,7 @@ function useCborParser() {
2142
2368
  if (byte === 255) {
2143
2369
  throw new Error(`Unexpected break code (0xff) at offset ${offset}`);
2144
2370
  }
2145
- const remainingHex = Array.from(buffer.slice(offset)).map((b) => b.toString(16).padStart(2, "0")).join("");
2146
- const result = parseWithSourceMap(remainingHex, mergedOptions);
2371
+ const result = parseWithSourceMap(buffer.subarray(offset), mergedOptions);
2147
2372
  const adjustedSourceMap = result.sourceMap.map((entry) => ({
2148
2373
  ...entry,
2149
2374
  start: entry.start + offset,
@@ -2164,5 +2389,5 @@ function useCborParser() {
2164
2389
  }
2165
2390
 
2166
2391
  export { bytesToHex, extractCborHeader, hexToBytes, readBigUint, readByte, readUint, useCborCollection, useCborFloat, useCborInteger, useCborParser, useCborString, useCborTag, validateUtf8Strict };
2167
- //# sourceMappingURL=chunk-5A5T56JB.js.map
2168
- //# sourceMappingURL=chunk-5A5T56JB.js.map
2392
+ //# sourceMappingURL=chunk-LWNWC2O7.js.map
2393
+ //# sourceMappingURL=chunk-LWNWC2O7.js.map