@marcuspuchalla/nachos 0.1.0 → 0.1.3

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 (47) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/{chunk-7CFYWHS6.js → chunk-2MTLSQ7E.js} +58 -10
  4. package/dist/chunk-2MTLSQ7E.js.map +1 -0
  5. package/dist/{chunk-ZRPJUEIZ.js → chunk-5A5T56JB.js} +170 -22
  6. package/dist/chunk-5A5T56JB.js.map +1 -0
  7. package/dist/{chunk-2HBCILJS.cjs → chunk-PTWN7K3Y.cjs} +169 -21
  8. package/dist/chunk-PTWN7K3Y.cjs.map +1 -0
  9. package/dist/{chunk-2FUTHZQQ.cjs → chunk-R62CQQNI.cjs} +58 -10
  10. package/dist/chunk-R62CQQNI.cjs.map +1 -0
  11. package/dist/encoder/index.cjs +13 -13
  12. package/dist/encoder/index.js +1 -1
  13. package/dist/index.cjs +20 -20
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +4 -4
  17. package/dist/metafile-cjs.json +1 -1
  18. package/dist/metafile-esm.json +1 -1
  19. package/dist/parser/index.cjs +14 -14
  20. package/dist/parser/index.d.cts +1 -1
  21. package/dist/parser/index.d.ts +1 -1
  22. package/dist/parser/index.js +1 -1
  23. package/dist/{useCborTag-BfTIV8HM.d.cts → useCborTag-Cs1CZuXZ.d.cts} +2 -2
  24. package/dist/{useCborTag-B_iaShG6.d.ts → useCborTag-xV2Pz2VY.d.ts} +2 -2
  25. package/package.json +1 -1
  26. package/src/__tests__/roundtrip.test.ts +702 -0
  27. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +26 -0
  28. package/src/encoder/__tests__/cbor-encoder-errors.test.ts +812 -0
  29. package/src/encoder/__tests__/cbor-string-encoder.test.ts +14 -0
  30. package/src/encoder/composables/useCborCollectionEncoder.ts +30 -0
  31. package/src/encoder/composables/useCborEncoder.ts +6 -1
  32. package/src/encoder/composables/useCborSimpleEncoder.ts +7 -2
  33. package/src/encoder/composables/useCborStringEncoder.ts +23 -10
  34. package/src/parser/__tests__/cbor-float-errors.test.ts +41 -0
  35. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +2 -2
  36. package/src/parser/__tests__/cbor-standard-tags.test.ts +29 -0
  37. package/src/parser/__tests__/cbor-string-errors.test.ts +4 -4
  38. package/src/parser/__tests__/cbor-tag-errors.test.ts +1 -1
  39. package/src/parser/composables/useCborFloat.ts +93 -8
  40. package/src/parser/composables/useCborParser.ts +0 -19
  41. package/src/parser/composables/useCborString.ts +22 -4
  42. package/src/parser/composables/useCborTag.ts +104 -16
  43. package/dist/chunk-2FUTHZQQ.cjs.map +0 -1
  44. package/dist/chunk-2HBCILJS.cjs.map +0 -1
  45. package/dist/chunk-7CFYWHS6.js.map +0 -1
  46. package/dist/chunk-ZRPJUEIZ.js.map +0 -1
  47. package/src/encoder/composables/#useCborTagEncoder.ts# +0 -158
@@ -363,6 +363,14 @@ function useCborString() {
363
363
  currentOffset++;
364
364
  break;
365
365
  }
366
+ const chunkInitialByte = readByte(buffer, currentOffset);
367
+ const chunkHeader = extractCborHeader(chunkInitialByte);
368
+ if (chunkHeader.majorType !== 2) {
369
+ throw new Error(`Indefinite byte string chunks must be byte strings (major type 2), got ${chunkHeader.majorType}`);
370
+ }
371
+ if (chunkHeader.additionalInfo === 31) {
372
+ throw new Error("Indefinite byte string chunks must be definite-length (RFC 8949 Section 3.2.3)");
373
+ }
366
374
  let chunkResult;
367
375
  try {
368
376
  chunkResult = parseByteString(buffer, currentOffset, options);
@@ -426,6 +434,14 @@ function useCborString() {
426
434
  currentOffset++;
427
435
  break;
428
436
  }
437
+ const chunkInitialByte = readByte(buffer, currentOffset);
438
+ const chunkHeader = extractCborHeader(chunkInitialByte);
439
+ if (chunkHeader.majorType !== 3) {
440
+ throw new Error(`Indefinite text string chunks must be text strings (major type 3), got ${chunkHeader.majorType}`);
441
+ }
442
+ if (chunkHeader.additionalInfo === 31) {
443
+ throw new Error("Indefinite text string chunks must be definite-length (RFC 8949 Section 3.2.3)");
444
+ }
429
445
  let chunkResult;
430
446
  try {
431
447
  chunkResult = parseTextString(buffer, currentOffset, options);
@@ -508,6 +524,45 @@ function useCborFloat() {
508
524
  }
509
525
  return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + fraction / 1024);
510
526
  };
527
+ const fitsInFloat16 = (value) => {
528
+ if (Number.isNaN(value) || value === Infinity || value === -Infinity) return true;
529
+ if (Object.is(value, 0) || Object.is(value, -0)) return true;
530
+ const abs = Math.abs(value);
531
+ if (abs > 65504) return false;
532
+ if (abs < 5960464477539063e-23) return false;
533
+ const sign = value < 0 ? 1 : 0;
534
+ const buf = new ArrayBuffer(8);
535
+ const view = new DataView(buf);
536
+ view.setFloat64(0, abs, false);
537
+ const bits64 = view.getBigUint64(0, false);
538
+ const exp64 = Number(bits64 >> 52n & 0x7ffn) - 1023;
539
+ const mant64 = Number(bits64 & 0xfffffffffffffn);
540
+ let exp16;
541
+ let mant16;
542
+ if (exp64 < -14) {
543
+ exp16 = 0;
544
+ const shift = -14 - exp64;
545
+ mant16 = (1 << 10 | mant64 >> 42) >> shift;
546
+ } else if (exp64 > 15) {
547
+ return false;
548
+ } else {
549
+ exp16 = exp64 + 15;
550
+ mant16 = mant64 >> 42;
551
+ }
552
+ const float16Bits = sign << 15 | exp16 << 10 | mant16;
553
+ const s = (float16Bits & 32768) >> 15;
554
+ const e = (float16Bits & 31744) >> 10;
555
+ const f = float16Bits & 1023;
556
+ let reconstructed;
557
+ if (e === 0) {
558
+ reconstructed = (s === 0 ? 1 : -1) * Math.pow(2, -14) * (f / 1024);
559
+ } else if (e === 31) {
560
+ reconstructed = f === 0 ? s === 0 ? Infinity : -Infinity : NaN;
561
+ } else {
562
+ reconstructed = (s === 0 ? 1 : -1) * Math.pow(2, e - 15) * (1 + f / 1024);
563
+ }
564
+ return reconstructed === value;
565
+ };
511
566
  const parseSimpleFromBuffer = (buffer, offset) => {
512
567
  const initialByte = readByte(buffer, offset);
513
568
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
@@ -558,7 +613,7 @@ function useCborFloat() {
558
613
  throw new Error(`Invalid additional info: ${additionalInfo}`);
559
614
  }
560
615
  };
561
- const parseFloatFromBuffer = (buffer, offset) => {
616
+ const parseFloatFromBuffer = (buffer, offset, options) => {
562
617
  const initialByte = readByte(buffer, offset);
563
618
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
564
619
  if (majorType !== 7) {
@@ -569,6 +624,16 @@ function useCborFloat() {
569
624
  if (offset + 2 >= buffer.length) {
570
625
  throw new Error("Unexpected end of buffer while reading Float16");
571
626
  }
627
+ if (options?.validateCanonical) {
628
+ const byte1 = readByte(buffer, offset + 1);
629
+ const byte2 = readByte(buffer, offset + 2);
630
+ const bits = byte1 << 8 | byte2;
631
+ const exp = bits >> 10 & 31;
632
+ const mant = bits & 1023;
633
+ if (exp === 31 && mant !== 0 && bits !== 32256) {
634
+ throw new Error("Non-canonical NaN encoding: use 0xf97e00");
635
+ }
636
+ }
572
637
  const value = float16ToFloat64(buffer, offset + 1);
573
638
  return { value, bytesRead: 3 };
574
639
  }
@@ -578,6 +643,14 @@ function useCborFloat() {
578
643
  }
579
644
  const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 4);
580
645
  const value = dataView.getFloat32(0, false);
646
+ if (options?.validateCanonical) {
647
+ if (Number.isNaN(value)) {
648
+ throw new Error("Non-canonical NaN encoding: NaN must use float16 0xf97e00");
649
+ }
650
+ if (fitsInFloat16(value)) {
651
+ throw new Error("Non-canonical float32: value fits in float16, use shortest encoding");
652
+ }
653
+ }
581
654
  return { value, bytesRead: 5 };
582
655
  }
583
656
  case 27: {
@@ -586,20 +659,32 @@ function useCborFloat() {
586
659
  }
587
660
  const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 8);
588
661
  const value = dataView.getFloat64(0, false);
662
+ if (options?.validateCanonical) {
663
+ if (Number.isNaN(value)) {
664
+ throw new Error("Non-canonical NaN encoding: NaN must use float16 0xf97e00");
665
+ }
666
+ if (fitsInFloat16(value)) {
667
+ throw new Error("Non-canonical float64: value fits in float16/float32, use shortest encoding");
668
+ }
669
+ const f32 = Math.fround(value);
670
+ if (f32 === value || Object.is(value, -0) && Object.is(f32, -0)) {
671
+ throw new Error("Non-canonical float64: value fits in float16/float32, use shortest encoding");
672
+ }
673
+ }
589
674
  return { value, bytesRead: 9 };
590
675
  }
591
676
  default:
592
677
  throw new Error(`Additional info ${additionalInfo} is not a float type`);
593
678
  }
594
679
  };
595
- const parseFromBuffer = (buffer, offset) => {
680
+ const parseFromBuffer = (buffer, offset, options) => {
596
681
  const initialByte = readByte(buffer, offset);
597
682
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
598
683
  if (majorType !== 7) {
599
684
  throw new Error(`Expected major type 7 (simple/float), got ${majorType}`);
600
685
  }
601
686
  if (additionalInfo === 25 || additionalInfo === 26 || additionalInfo === 27) {
602
- return parseFloatFromBuffer(buffer, offset);
687
+ return parseFloatFromBuffer(buffer, offset, options);
603
688
  } else {
604
689
  return parseSimpleFromBuffer(buffer, offset);
605
690
  }
@@ -608,13 +693,13 @@ function useCborFloat() {
608
693
  const buffer = hexToBytes(hexString);
609
694
  return parseSimpleFromBuffer(buffer, 0);
610
695
  };
611
- const parseFloat = (hexString, _options) => {
696
+ const parseFloat = (hexString, options) => {
612
697
  const buffer = hexToBytes(hexString);
613
- return parseFloatFromBuffer(buffer, 0);
698
+ return parseFloatFromBuffer(buffer, 0, options);
614
699
  };
615
- const parse = (hexString, _options) => {
700
+ const parse = (hexString, options) => {
616
701
  const buffer = hexToBytes(hexString);
617
- return parseFromBuffer(buffer, 0);
702
+ return parseFromBuffer(buffer, 0, options);
618
703
  };
619
704
  return {
620
705
  parse,
@@ -628,7 +713,7 @@ function useCborTag() {
628
713
  const { parseInteger } = useCborInteger();
629
714
  const { parseByteString, parseTextString } = useCborString();
630
715
  const { parseFloat, parseSimple } = useCborFloat();
631
- const parseItem = (buffer, offset, options, tagDepth = 0) => {
716
+ const parseItem = (buffer, offset, options, tagDepth = 0, collectionDepth = 0) => {
632
717
  if (offset >= buffer.length) {
633
718
  throw new Error(`Unexpected end of buffer at offset ${offset}`);
634
719
  }
@@ -647,9 +732,9 @@ function useCborTag() {
647
732
  case 3:
648
733
  return parseTextString(buffer, offset, options);
649
734
  case 4:
650
- return parseArrayInternal(buffer, offset, options);
735
+ return parseArrayInternal(buffer, offset, options, collectionDepth);
651
736
  case 5:
652
- return parseMapInternal(buffer, offset, options);
737
+ return parseMapInternal(buffer, offset, options, collectionDepth);
653
738
  case 6:
654
739
  return parseTagFromBuffer(buffer, offset, options, tagDepth);
655
740
  case 7: {
@@ -666,12 +751,20 @@ function useCborTag() {
666
751
  throw new Error(`Unknown major type: ${majorType}`);
667
752
  }
668
753
  };
669
- const parseArrayInternal = (buffer, offset, options) => {
754
+ const parseArrayInternal = (buffer, offset, options, depth = 0) => {
670
755
  const initialByte = readByte(buffer, offset);
671
756
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
672
757
  if (majorType !== 4) {
673
758
  throw new Error(`Expected major type 4 (array), got ${majorType}`);
674
759
  }
760
+ const isIndefiniteAllowed = options?.allowIndefinite ?? !(options?.validateCanonical || options?.strict);
761
+ if (additionalInfo === 31 && !isIndefiniteAllowed) {
762
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
763
+ }
764
+ const maxDepth = options?.limits?.maxDepth ?? chunkPD72MVTX_cjs.DEFAULT_LIMITS.maxDepth;
765
+ if (depth >= maxDepth) {
766
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
767
+ }
675
768
  let length;
676
769
  let bytesConsumed;
677
770
  if (additionalInfo < 24) {
@@ -699,23 +792,39 @@ function useCborTag() {
699
792
  } else {
700
793
  throw new Error(`Invalid additional info for array: ${additionalInfo}`);
701
794
  }
795
+ if (length !== null && options?.limits?.maxArrayLength && length > options.limits.maxArrayLength) {
796
+ throw new Error(`Array length ${length} exceeds limit of ${options.limits.maxArrayLength}`);
797
+ }
702
798
  let currentOffset = offset + 1 + bytesConsumed;
703
799
  const items = [];
704
800
  if (length === null) {
801
+ let index = 0;
802
+ let foundBreak = false;
705
803
  while (currentOffset < buffer.length) {
706
804
  const nextByte = readByte(buffer, currentOffset);
707
805
  if (nextByte === 255) {
708
806
  currentOffset++;
807
+ foundBreak = true;
709
808
  break;
710
809
  }
711
- const itemResult = parseItem(buffer, currentOffset, options);
810
+ if (options?.limits?.maxArrayLength && index >= options.limits.maxArrayLength) {
811
+ throw new Error(`Array length exceeds limit of ${options.limits.maxArrayLength}`);
812
+ }
813
+ const itemResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
712
814
  items.push(itemResult.value);
713
815
  currentOffset += itemResult.bytesRead;
816
+ index++;
817
+ }
818
+ if (!foundBreak) {
819
+ throw new Error("Indefinite-length array missing break code (0xFF)");
714
820
  }
715
821
  items[chunkPD72MVTX_cjs.INDEFINITE_SYMBOL] = true;
716
822
  } else {
717
823
  for (let i = 0; i < length; i++) {
718
- const itemResult = parseItem(buffer, currentOffset, options);
824
+ if (currentOffset >= buffer.length) {
825
+ throw new Error(`Unexpected end of buffer while parsing array element ${i}/${length}`);
826
+ }
827
+ const itemResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
719
828
  items.push(itemResult.value);
720
829
  currentOffset += itemResult.bytesRead;
721
830
  }
@@ -725,12 +834,20 @@ function useCborTag() {
725
834
  bytesRead: currentOffset - offset
726
835
  };
727
836
  };
728
- const parseMapInternal = (buffer, offset, options) => {
837
+ const parseMapInternal = (buffer, offset, options, depth = 0) => {
729
838
  const initialByte = readByte(buffer, offset);
730
839
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
731
840
  if (majorType !== 5) {
732
841
  throw new Error(`Expected major type 5 (map), got ${majorType}`);
733
842
  }
843
+ const isIndefiniteAllowed = options?.allowIndefinite ?? !(options?.validateCanonical || options?.strict);
844
+ if (additionalInfo === 31 && !isIndefiniteAllowed) {
845
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
846
+ }
847
+ const maxDepth = options?.limits?.maxDepth ?? chunkPD72MVTX_cjs.DEFAULT_LIMITS.maxDepth;
848
+ if (depth >= maxDepth) {
849
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
850
+ }
734
851
  let length;
735
852
  let bytesConsumed;
736
853
  if (additionalInfo < 24) {
@@ -758,27 +875,43 @@ function useCborTag() {
758
875
  } else {
759
876
  throw new Error(`Invalid additional info for map: ${additionalInfo}`);
760
877
  }
878
+ if (length !== null && options?.limits?.maxMapSize && length > options.limits.maxMapSize) {
879
+ throw new Error(`Map size ${length} exceeds limit of ${options.limits.maxMapSize}`);
880
+ }
761
881
  let currentOffset = offset + 1 + bytesConsumed;
762
882
  const map = /* @__PURE__ */ new Map();
763
883
  if (length === null) {
884
+ let index = 0;
885
+ let foundBreak = false;
764
886
  while (currentOffset < buffer.length) {
765
887
  const nextByte = readByte(buffer, currentOffset);
766
888
  if (nextByte === 255) {
767
889
  currentOffset++;
890
+ foundBreak = true;
768
891
  break;
769
892
  }
770
- const keyResult = parseItem(buffer, currentOffset, options);
893
+ if (options?.limits?.maxMapSize && index >= options.limits.maxMapSize) {
894
+ throw new Error(`Map size exceeds limit of ${options.limits.maxMapSize}`);
895
+ }
896
+ const keyResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
771
897
  currentOffset += keyResult.bytesRead;
772
- const valueResult = parseItem(buffer, currentOffset, options);
898
+ const valueResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
773
899
  currentOffset += valueResult.bytesRead;
774
900
  map.set(keyResult.value, valueResult.value);
901
+ index++;
902
+ }
903
+ if (!foundBreak) {
904
+ throw new Error("Indefinite-length map missing break code (0xFF)");
775
905
  }
776
906
  map[chunkPD72MVTX_cjs.INDEFINITE_SYMBOL] = true;
777
907
  } else {
778
908
  for (let i = 0; i < length; i++) {
779
- const keyResult = parseItem(buffer, currentOffset, options);
909
+ if (currentOffset >= buffer.length) {
910
+ throw new Error(`Unexpected end of buffer while parsing map entry ${i}/${length}`);
911
+ }
912
+ const keyResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
780
913
  currentOffset += keyResult.bytesRead;
781
- const valueResult = parseItem(buffer, currentOffset, options);
914
+ const valueResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
782
915
  currentOffset += valueResult.bytesRead;
783
916
  map.set(keyResult.value, valueResult.value);
784
917
  }
@@ -828,6 +961,13 @@ function useCborTag() {
828
961
  }
829
962
  return false;
830
963
  };
964
+ const isByteString = (value) => {
965
+ if (value instanceof Uint8Array) return true;
966
+ if (value && typeof value === "object" && "type" in value && value.type === "cbor-byte-string") {
967
+ return true;
968
+ }
969
+ return false;
970
+ };
831
971
  const getTextStringValue = (value) => {
832
972
  if (typeof value === "string") return value;
833
973
  if (value && typeof value === "object" && "text" in value) {
@@ -854,6 +994,14 @@ function useCborTag() {
854
994
  throw new Error(`Tag 1 (epoch time) must contain a number (integer or float), got ${typeof value}`);
855
995
  }
856
996
  break;
997
+ case 2:
998
+ // Positive bignum
999
+ case 3:
1000
+ if (!shouldValidateStandard) break;
1001
+ if (!isByteString(value)) {
1002
+ throw new Error(`Tag ${tagNumber} (bignum) must contain a byte string, got ${typeof value}`);
1003
+ }
1004
+ break;
857
1005
  case 4:
858
1006
  if (!shouldValidateStandard) break;
859
1007
  if (!Array.isArray(value)) {
@@ -1041,7 +1189,7 @@ function useCborTag() {
1041
1189
  if (majorType !== 6) {
1042
1190
  throw new Error(`Expected major type 6 (tag), got ${majorType}`);
1043
1191
  }
1044
- const maxTagDepth = options?.limits?.maxTagDepth ?? 64;
1192
+ const maxTagDepth = options?.limits?.maxTagDepth ?? chunkPD72MVTX_cjs.DEFAULT_LIMITS.maxTagDepth;
1045
1193
  if (tagDepth >= maxTagDepth) {
1046
1194
  throw new Error(`Tag nesting depth ${tagDepth} exceeds limit of ${maxTagDepth}`);
1047
1195
  }
@@ -2030,5 +2178,5 @@ exports.useCborParser = useCborParser;
2030
2178
  exports.useCborString = useCborString;
2031
2179
  exports.useCborTag = useCborTag;
2032
2180
  exports.validateUtf8Strict = validateUtf8Strict;
2033
- //# sourceMappingURL=chunk-2HBCILJS.cjs.map
2034
- //# sourceMappingURL=chunk-2HBCILJS.cjs.map
2181
+ //# sourceMappingURL=chunk-PTWN7K3Y.cjs.map
2182
+ //# sourceMappingURL=chunk-PTWN7K3Y.cjs.map