@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
@@ -1,4 +1,4 @@
1
- import { useCborByteString, useCborTextString, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS, DEFAULT_LIMITS } from './chunk-ZDZ2B5PE.js';
1
+ import { useCborByteString, useCborTextString, DEFAULT_LIMITS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL, DEFAULT_OPTIONS } from './chunk-ZDZ2B5PE.js';
2
2
 
3
3
  // src/parser/utils.ts
4
4
  var hexToBytes = (hex) => {
@@ -361,6 +361,14 @@ function useCborString() {
361
361
  currentOffset++;
362
362
  break;
363
363
  }
364
+ const chunkInitialByte = readByte(buffer, currentOffset);
365
+ const chunkHeader = extractCborHeader(chunkInitialByte);
366
+ if (chunkHeader.majorType !== 2) {
367
+ throw new Error(`Indefinite byte string chunks must be byte strings (major type 2), got ${chunkHeader.majorType}`);
368
+ }
369
+ if (chunkHeader.additionalInfo === 31) {
370
+ throw new Error("Indefinite byte string chunks must be definite-length (RFC 8949 Section 3.2.3)");
371
+ }
364
372
  let chunkResult;
365
373
  try {
366
374
  chunkResult = parseByteString(buffer, currentOffset, options);
@@ -424,6 +432,14 @@ function useCborString() {
424
432
  currentOffset++;
425
433
  break;
426
434
  }
435
+ const chunkInitialByte = readByte(buffer, currentOffset);
436
+ const chunkHeader = extractCborHeader(chunkInitialByte);
437
+ if (chunkHeader.majorType !== 3) {
438
+ throw new Error(`Indefinite text string chunks must be text strings (major type 3), got ${chunkHeader.majorType}`);
439
+ }
440
+ if (chunkHeader.additionalInfo === 31) {
441
+ throw new Error("Indefinite text string chunks must be definite-length (RFC 8949 Section 3.2.3)");
442
+ }
427
443
  let chunkResult;
428
444
  try {
429
445
  chunkResult = parseTextString(buffer, currentOffset, options);
@@ -506,6 +522,45 @@ function useCborFloat() {
506
522
  }
507
523
  return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + fraction / 1024);
508
524
  };
525
+ const fitsInFloat16 = (value) => {
526
+ if (Number.isNaN(value) || value === Infinity || value === -Infinity) return true;
527
+ if (Object.is(value, 0) || Object.is(value, -0)) return true;
528
+ const abs = Math.abs(value);
529
+ if (abs > 65504) return false;
530
+ if (abs < 5960464477539063e-23) return false;
531
+ const sign = value < 0 ? 1 : 0;
532
+ const buf = new ArrayBuffer(8);
533
+ const view = new DataView(buf);
534
+ view.setFloat64(0, abs, false);
535
+ const bits64 = view.getBigUint64(0, false);
536
+ const exp64 = Number(bits64 >> 52n & 0x7ffn) - 1023;
537
+ const mant64 = Number(bits64 & 0xfffffffffffffn);
538
+ let exp16;
539
+ let mant16;
540
+ if (exp64 < -14) {
541
+ exp16 = 0;
542
+ const shift = -14 - exp64;
543
+ mant16 = (1 << 10 | mant64 >> 42) >> shift;
544
+ } else if (exp64 > 15) {
545
+ return false;
546
+ } else {
547
+ exp16 = exp64 + 15;
548
+ mant16 = mant64 >> 42;
549
+ }
550
+ const float16Bits = sign << 15 | exp16 << 10 | mant16;
551
+ const s = (float16Bits & 32768) >> 15;
552
+ const e = (float16Bits & 31744) >> 10;
553
+ const f = float16Bits & 1023;
554
+ let reconstructed;
555
+ if (e === 0) {
556
+ reconstructed = (s === 0 ? 1 : -1) * Math.pow(2, -14) * (f / 1024);
557
+ } else if (e === 31) {
558
+ reconstructed = f === 0 ? s === 0 ? Infinity : -Infinity : NaN;
559
+ } else {
560
+ reconstructed = (s === 0 ? 1 : -1) * Math.pow(2, e - 15) * (1 + f / 1024);
561
+ }
562
+ return reconstructed === value;
563
+ };
509
564
  const parseSimpleFromBuffer = (buffer, offset) => {
510
565
  const initialByte = readByte(buffer, offset);
511
566
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
@@ -556,7 +611,7 @@ function useCborFloat() {
556
611
  throw new Error(`Invalid additional info: ${additionalInfo}`);
557
612
  }
558
613
  };
559
- const parseFloatFromBuffer = (buffer, offset) => {
614
+ const parseFloatFromBuffer = (buffer, offset, options) => {
560
615
  const initialByte = readByte(buffer, offset);
561
616
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
562
617
  if (majorType !== 7) {
@@ -567,6 +622,16 @@ function useCborFloat() {
567
622
  if (offset + 2 >= buffer.length) {
568
623
  throw new Error("Unexpected end of buffer while reading Float16");
569
624
  }
625
+ if (options?.validateCanonical) {
626
+ const byte1 = readByte(buffer, offset + 1);
627
+ const byte2 = readByte(buffer, offset + 2);
628
+ const bits = byte1 << 8 | byte2;
629
+ const exp = bits >> 10 & 31;
630
+ const mant = bits & 1023;
631
+ if (exp === 31 && mant !== 0 && bits !== 32256) {
632
+ throw new Error("Non-canonical NaN encoding: use 0xf97e00");
633
+ }
634
+ }
570
635
  const value = float16ToFloat64(buffer, offset + 1);
571
636
  return { value, bytesRead: 3 };
572
637
  }
@@ -576,6 +641,14 @@ function useCborFloat() {
576
641
  }
577
642
  const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 4);
578
643
  const value = dataView.getFloat32(0, false);
644
+ if (options?.validateCanonical) {
645
+ if (Number.isNaN(value)) {
646
+ throw new Error("Non-canonical NaN encoding: NaN must use float16 0xf97e00");
647
+ }
648
+ if (fitsInFloat16(value)) {
649
+ throw new Error("Non-canonical float32: value fits in float16, use shortest encoding");
650
+ }
651
+ }
579
652
  return { value, bytesRead: 5 };
580
653
  }
581
654
  case 27: {
@@ -584,20 +657,32 @@ function useCborFloat() {
584
657
  }
585
658
  const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 8);
586
659
  const value = dataView.getFloat64(0, false);
660
+ if (options?.validateCanonical) {
661
+ if (Number.isNaN(value)) {
662
+ throw new Error("Non-canonical NaN encoding: NaN must use float16 0xf97e00");
663
+ }
664
+ if (fitsInFloat16(value)) {
665
+ throw new Error("Non-canonical float64: value fits in float16/float32, use shortest encoding");
666
+ }
667
+ const f32 = Math.fround(value);
668
+ if (f32 === value || Object.is(value, -0) && Object.is(f32, -0)) {
669
+ throw new Error("Non-canonical float64: value fits in float16/float32, use shortest encoding");
670
+ }
671
+ }
587
672
  return { value, bytesRead: 9 };
588
673
  }
589
674
  default:
590
675
  throw new Error(`Additional info ${additionalInfo} is not a float type`);
591
676
  }
592
677
  };
593
- const parseFromBuffer = (buffer, offset) => {
678
+ const parseFromBuffer = (buffer, offset, options) => {
594
679
  const initialByte = readByte(buffer, offset);
595
680
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
596
681
  if (majorType !== 7) {
597
682
  throw new Error(`Expected major type 7 (simple/float), got ${majorType}`);
598
683
  }
599
684
  if (additionalInfo === 25 || additionalInfo === 26 || additionalInfo === 27) {
600
- return parseFloatFromBuffer(buffer, offset);
685
+ return parseFloatFromBuffer(buffer, offset, options);
601
686
  } else {
602
687
  return parseSimpleFromBuffer(buffer, offset);
603
688
  }
@@ -606,13 +691,13 @@ function useCborFloat() {
606
691
  const buffer = hexToBytes(hexString);
607
692
  return parseSimpleFromBuffer(buffer, 0);
608
693
  };
609
- const parseFloat = (hexString, _options) => {
694
+ const parseFloat = (hexString, options) => {
610
695
  const buffer = hexToBytes(hexString);
611
- return parseFloatFromBuffer(buffer, 0);
696
+ return parseFloatFromBuffer(buffer, 0, options);
612
697
  };
613
- const parse = (hexString, _options) => {
698
+ const parse = (hexString, options) => {
614
699
  const buffer = hexToBytes(hexString);
615
- return parseFromBuffer(buffer, 0);
700
+ return parseFromBuffer(buffer, 0, options);
616
701
  };
617
702
  return {
618
703
  parse,
@@ -626,7 +711,7 @@ function useCborTag() {
626
711
  const { parseInteger } = useCborInteger();
627
712
  const { parseByteString, parseTextString } = useCborString();
628
713
  const { parseFloat, parseSimple } = useCborFloat();
629
- const parseItem = (buffer, offset, options, tagDepth = 0) => {
714
+ const parseItem = (buffer, offset, options, tagDepth = 0, collectionDepth = 0) => {
630
715
  if (offset >= buffer.length) {
631
716
  throw new Error(`Unexpected end of buffer at offset ${offset}`);
632
717
  }
@@ -645,9 +730,9 @@ function useCborTag() {
645
730
  case 3:
646
731
  return parseTextString(buffer, offset, options);
647
732
  case 4:
648
- return parseArrayInternal(buffer, offset, options);
733
+ return parseArrayInternal(buffer, offset, options, collectionDepth);
649
734
  case 5:
650
- return parseMapInternal(buffer, offset, options);
735
+ return parseMapInternal(buffer, offset, options, collectionDepth);
651
736
  case 6:
652
737
  return parseTagFromBuffer(buffer, offset, options, tagDepth);
653
738
  case 7: {
@@ -664,12 +749,20 @@ function useCborTag() {
664
749
  throw new Error(`Unknown major type: ${majorType}`);
665
750
  }
666
751
  };
667
- const parseArrayInternal = (buffer, offset, options) => {
752
+ const parseArrayInternal = (buffer, offset, options, depth = 0) => {
668
753
  const initialByte = readByte(buffer, offset);
669
754
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
670
755
  if (majorType !== 4) {
671
756
  throw new Error(`Expected major type 4 (array), got ${majorType}`);
672
757
  }
758
+ const isIndefiniteAllowed = options?.allowIndefinite ?? !(options?.validateCanonical || options?.strict);
759
+ if (additionalInfo === 31 && !isIndefiniteAllowed) {
760
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
761
+ }
762
+ const maxDepth = options?.limits?.maxDepth ?? DEFAULT_LIMITS.maxDepth;
763
+ if (depth >= maxDepth) {
764
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
765
+ }
673
766
  let length;
674
767
  let bytesConsumed;
675
768
  if (additionalInfo < 24) {
@@ -697,23 +790,39 @@ function useCborTag() {
697
790
  } else {
698
791
  throw new Error(`Invalid additional info for array: ${additionalInfo}`);
699
792
  }
793
+ if (length !== null && options?.limits?.maxArrayLength && length > options.limits.maxArrayLength) {
794
+ throw new Error(`Array length ${length} exceeds limit of ${options.limits.maxArrayLength}`);
795
+ }
700
796
  let currentOffset = offset + 1 + bytesConsumed;
701
797
  const items = [];
702
798
  if (length === null) {
799
+ let index = 0;
800
+ let foundBreak = false;
703
801
  while (currentOffset < buffer.length) {
704
802
  const nextByte = readByte(buffer, currentOffset);
705
803
  if (nextByte === 255) {
706
804
  currentOffset++;
805
+ foundBreak = true;
707
806
  break;
708
807
  }
709
- const itemResult = parseItem(buffer, currentOffset, options);
808
+ if (options?.limits?.maxArrayLength && index >= options.limits.maxArrayLength) {
809
+ throw new Error(`Array length exceeds limit of ${options.limits.maxArrayLength}`);
810
+ }
811
+ const itemResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
710
812
  items.push(itemResult.value);
711
813
  currentOffset += itemResult.bytesRead;
814
+ index++;
815
+ }
816
+ if (!foundBreak) {
817
+ throw new Error("Indefinite-length array missing break code (0xFF)");
712
818
  }
713
819
  items[INDEFINITE_SYMBOL] = true;
714
820
  } else {
715
821
  for (let i = 0; i < length; i++) {
716
- const itemResult = parseItem(buffer, currentOffset, options);
822
+ if (currentOffset >= buffer.length) {
823
+ throw new Error(`Unexpected end of buffer while parsing array element ${i}/${length}`);
824
+ }
825
+ const itemResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
717
826
  items.push(itemResult.value);
718
827
  currentOffset += itemResult.bytesRead;
719
828
  }
@@ -723,12 +832,20 @@ function useCborTag() {
723
832
  bytesRead: currentOffset - offset
724
833
  };
725
834
  };
726
- const parseMapInternal = (buffer, offset, options) => {
835
+ const parseMapInternal = (buffer, offset, options, depth = 0) => {
727
836
  const initialByte = readByte(buffer, offset);
728
837
  const { majorType, additionalInfo } = extractCborHeader(initialByte);
729
838
  if (majorType !== 5) {
730
839
  throw new Error(`Expected major type 5 (map), got ${majorType}`);
731
840
  }
841
+ const isIndefiniteAllowed = options?.allowIndefinite ?? !(options?.validateCanonical || options?.strict);
842
+ if (additionalInfo === 31 && !isIndefiniteAllowed) {
843
+ throw new Error("Indefinite-length encoding is not allowed (strict/canonical mode)");
844
+ }
845
+ const maxDepth = options?.limits?.maxDepth ?? DEFAULT_LIMITS.maxDepth;
846
+ if (depth >= maxDepth) {
847
+ throw new Error(`Maximum nesting depth ${maxDepth} exceeded`);
848
+ }
732
849
  let length;
733
850
  let bytesConsumed;
734
851
  if (additionalInfo < 24) {
@@ -756,27 +873,43 @@ function useCborTag() {
756
873
  } else {
757
874
  throw new Error(`Invalid additional info for map: ${additionalInfo}`);
758
875
  }
876
+ if (length !== null && options?.limits?.maxMapSize && length > options.limits.maxMapSize) {
877
+ throw new Error(`Map size ${length} exceeds limit of ${options.limits.maxMapSize}`);
878
+ }
759
879
  let currentOffset = offset + 1 + bytesConsumed;
760
880
  const map = /* @__PURE__ */ new Map();
761
881
  if (length === null) {
882
+ let index = 0;
883
+ let foundBreak = false;
762
884
  while (currentOffset < buffer.length) {
763
885
  const nextByte = readByte(buffer, currentOffset);
764
886
  if (nextByte === 255) {
765
887
  currentOffset++;
888
+ foundBreak = true;
766
889
  break;
767
890
  }
768
- const keyResult = parseItem(buffer, currentOffset, options);
891
+ if (options?.limits?.maxMapSize && index >= options.limits.maxMapSize) {
892
+ throw new Error(`Map size exceeds limit of ${options.limits.maxMapSize}`);
893
+ }
894
+ const keyResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
769
895
  currentOffset += keyResult.bytesRead;
770
- const valueResult = parseItem(buffer, currentOffset, options);
896
+ const valueResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
771
897
  currentOffset += valueResult.bytesRead;
772
898
  map.set(keyResult.value, valueResult.value);
899
+ index++;
900
+ }
901
+ if (!foundBreak) {
902
+ throw new Error("Indefinite-length map missing break code (0xFF)");
773
903
  }
774
904
  map[INDEFINITE_SYMBOL] = true;
775
905
  } else {
776
906
  for (let i = 0; i < length; i++) {
777
- const keyResult = parseItem(buffer, currentOffset, options);
907
+ if (currentOffset >= buffer.length) {
908
+ throw new Error(`Unexpected end of buffer while parsing map entry ${i}/${length}`);
909
+ }
910
+ const keyResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
778
911
  currentOffset += keyResult.bytesRead;
779
- const valueResult = parseItem(buffer, currentOffset, options);
912
+ const valueResult = parseItem(buffer, currentOffset, options, 0, depth + 1);
780
913
  currentOffset += valueResult.bytesRead;
781
914
  map.set(keyResult.value, valueResult.value);
782
915
  }
@@ -826,6 +959,13 @@ function useCborTag() {
826
959
  }
827
960
  return false;
828
961
  };
962
+ const isByteString = (value) => {
963
+ if (value instanceof Uint8Array) return true;
964
+ if (value && typeof value === "object" && "type" in value && value.type === "cbor-byte-string") {
965
+ return true;
966
+ }
967
+ return false;
968
+ };
829
969
  const getTextStringValue = (value) => {
830
970
  if (typeof value === "string") return value;
831
971
  if (value && typeof value === "object" && "text" in value) {
@@ -852,6 +992,14 @@ function useCborTag() {
852
992
  throw new Error(`Tag 1 (epoch time) must contain a number (integer or float), got ${typeof value}`);
853
993
  }
854
994
  break;
995
+ case 2:
996
+ // Positive bignum
997
+ case 3:
998
+ if (!shouldValidateStandard) break;
999
+ if (!isByteString(value)) {
1000
+ throw new Error(`Tag ${tagNumber} (bignum) must contain a byte string, got ${typeof value}`);
1001
+ }
1002
+ break;
855
1003
  case 4:
856
1004
  if (!shouldValidateStandard) break;
857
1005
  if (!Array.isArray(value)) {
@@ -1039,7 +1187,7 @@ function useCborTag() {
1039
1187
  if (majorType !== 6) {
1040
1188
  throw new Error(`Expected major type 6 (tag), got ${majorType}`);
1041
1189
  }
1042
- const maxTagDepth = options?.limits?.maxTagDepth ?? 64;
1190
+ const maxTagDepth = options?.limits?.maxTagDepth ?? DEFAULT_LIMITS.maxTagDepth;
1043
1191
  if (tagDepth >= maxTagDepth) {
1044
1192
  throw new Error(`Tag nesting depth ${tagDepth} exceeds limit of ${maxTagDepth}`);
1045
1193
  }
@@ -2016,5 +2164,5 @@ function useCborParser() {
2016
2164
  }
2017
2165
 
2018
2166
  export { bytesToHex, extractCborHeader, hexToBytes, readBigUint, readByte, readUint, useCborCollection, useCborFloat, useCborInteger, useCborParser, useCborString, useCborTag, validateUtf8Strict };
2019
- //# sourceMappingURL=chunk-ZRPJUEIZ.js.map
2020
- //# sourceMappingURL=chunk-ZRPJUEIZ.js.map
2167
+ //# sourceMappingURL=chunk-5A5T56JB.js.map
2168
+ //# sourceMappingURL=chunk-5A5T56JB.js.map