@marcuspuchalla/nachos 0.1.3 → 0.1.4

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 (55) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/{chunk-5A5T56JB.js → chunk-5IWW5H47.js} +378 -207
  3. package/dist/chunk-5IWW5H47.js.map +1 -0
  4. package/dist/{chunk-PTWN7K3Y.cjs → chunk-RVG2BY32.cjs} +378 -207
  5. package/dist/chunk-RVG2BY32.cjs.map +1 -0
  6. package/dist/{chunk-R62CQQNI.cjs → chunk-S4RXO6IB.cjs} +195 -165
  7. package/dist/chunk-S4RXO6IB.cjs.map +1 -0
  8. package/dist/{chunk-2MTLSQ7E.js → chunk-UMAX5MX5.js} +195 -165
  9. package/dist/chunk-UMAX5MX5.js.map +1 -0
  10. package/dist/encoder/index.cjs +13 -13
  11. package/dist/encoder/index.d.cts +2 -2
  12. package/dist/encoder/index.d.ts +2 -2
  13. package/dist/encoder/index.js +1 -1
  14. package/dist/index.cjs +32 -32
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +28 -19
  17. package/dist/index.d.ts +28 -19
  18. package/dist/index.js +16 -16
  19. package/dist/index.js.map +1 -1
  20. package/dist/metafile-cjs.json +1 -1
  21. package/dist/metafile-esm.json +1 -1
  22. package/dist/parser/index.cjs +14 -14
  23. package/dist/parser/index.d.cts +3 -1
  24. package/dist/parser/index.d.ts +3 -1
  25. package/dist/parser/index.js +1 -1
  26. package/dist/{useCborSimpleEncoder-TVxzNJ_9.d.ts → useCborSimpleEncoder-BoKEmjP9.d.ts} +0 -2
  27. package/dist/{useCborSimpleEncoder-ButVU988.d.cts → useCborSimpleEncoder-C_OHxoB8.d.cts} +0 -2
  28. package/dist/{useCborTag-Cs1CZuXZ.d.cts → useCborTag-BD6Sqp7p.d.ts} +9 -4
  29. package/dist/{useCborTag-xV2Pz2VY.d.ts → useCborTag-QpZR-Er2.d.cts} +9 -4
  30. package/package.json +1 -1
  31. package/src/__tests__/public-api.test.ts +153 -0
  32. package/src/__tests__/roundtrip.test.ts +5 -6
  33. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +103 -5
  34. package/src/encoder/__tests__/cbor-encoder-errors.test.ts +40 -5
  35. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
  36. package/src/encoder/composables/useCborCollectionEncoder.ts +28 -25
  37. package/src/encoder/composables/useCborEncoder.ts +21 -0
  38. package/src/encoder/composables/useCborSimpleEncoder.ts +34 -7
  39. package/src/encoder/types.ts +0 -2
  40. package/src/index.ts +29 -20
  41. package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
  42. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
  43. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +164 -31
  44. package/src/parser/__tests__/cbor-standard-tags.test.ts +75 -7
  45. package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
  46. package/src/parser/composables/useCborCollection.ts +45 -42
  47. package/src/parser/composables/useCborFloat.ts +2 -1
  48. package/src/parser/composables/useCborInteger.ts +24 -10
  49. package/src/parser/composables/useCborParser.ts +387 -197
  50. package/src/parser/composables/useCborTag.ts +45 -37
  51. package/src/parser/utils.ts +11 -0
  52. package/dist/chunk-2MTLSQ7E.js.map +0 -1
  53. package/dist/chunk-5A5T56JB.js.map +0 -1
  54. package/dist/chunk-PTWN7K3Y.cjs.map +0 -1
  55. package/dist/chunk-R62CQQNI.cjs.map +0 -1
@@ -6,7 +6,7 @@
6
6
 
7
7
  import type { ParseResult, CborValue, CborMap, ParseOptions } from '../types'
8
8
  import { INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL } from '../types'
9
- import { hexToBytes, readByte, readUint, readBigUint, extractCborHeader, compareBytes, bytesToHex } from '../utils'
9
+ import { hexToBytes, readByte, readUint, readBigUint, extractCborHeader, compareBytes, serializeValueForComparison } from '../utils'
10
10
  import { useCborInteger } from './useCborInteger'
11
11
  import { useCborString } from './useCborString'
12
12
  import { useCborFloat } from './useCborFloat'
@@ -25,21 +25,13 @@ import { logger } from '../../utils/logger'
25
25
  * ```
26
26
  */
27
27
  export function useCborCollection() {
28
- const { parseInteger } = useCborInteger()
28
+ const { parseIntegerFromBuffer } = useCborInteger()
29
29
  const { parseByteString, parseTextString } = useCborString()
30
- const { parse: parseFloatOrSimple } = useCborFloat()
31
- const { parseTag } = useCborTag()
30
+ const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat()
31
+ const { parseTagFromBuffer } = useCborTag()
32
32
 
33
- /**
34
- * Convert a CBOR value to a string key for use in JavaScript objects
35
- * Handles Uint8Array keys by converting them to hex strings
36
- */
37
- const convertKeyToString = (key: CborValue): string => {
38
- if (key instanceof Uint8Array) {
39
- return bytesToHex(key)
40
- }
41
- return String(key)
42
- }
33
+ /** Tracks when parsing started for timeout enforcement */
34
+ let parseStartTime = 0
43
35
 
44
36
  /**
45
37
  * Internal parser dispatcher for CBOR items
@@ -52,6 +44,14 @@ export function useCborCollection() {
52
44
  * @returns Parsed value and bytes consumed
53
45
  */
54
46
  const parseItem = (buffer: Uint8Array, offset: number, options?: ParseOptions, depth: number = 0): ParseResult => {
47
+ // Check timeout on every recursive call
48
+ if (parseStartTime > 0 && options?.limits?.maxParseTime) {
49
+ const elapsed = Date.now() - parseStartTime
50
+ if (elapsed > options.limits.maxParseTime) {
51
+ throw new Error(`Parse timeout: exceeded ${options.limits.maxParseTime}ms limit`)
52
+ }
53
+ }
54
+
55
55
  if (offset >= buffer.length) {
56
56
  throw new Error(`Unexpected end of buffer at offset ${offset}`)
57
57
  }
@@ -62,14 +62,7 @@ export function useCborCollection() {
62
62
  switch (majorType) {
63
63
  case 0: // Unsigned integer
64
64
  case 1: // Negative integer
65
- {
66
- // Create a hex string from the buffer starting at offset
67
- const intHex = Array.from(buffer.slice(offset))
68
- .map(b => b.toString(16).padStart(2, '0'))
69
- .join('')
70
- const result = parseInteger(intHex, options)
71
- return { value: result.value, bytesRead: result.bytesRead }
72
- }
65
+ return parseIntegerFromBuffer(buffer, offset, options)
73
66
 
74
67
  case 2: // Byte string
75
68
  return parseByteString(buffer, offset, options)
@@ -84,22 +77,10 @@ export function useCborCollection() {
84
77
  return parseMapFromBuffer(buffer, offset, options, depth)
85
78
 
86
79
  case 6: // Tag
87
- {
88
- const tagHex = Array.from(buffer.slice(offset))
89
- .map(b => b.toString(16).padStart(2, '0'))
90
- .join('')
91
- const result = parseTag(tagHex, options)
92
- return { value: result.value, bytesRead: result.bytesRead }
93
- }
80
+ return parseTagFromBuffer(buffer, offset, options)
94
81
 
95
82
  case 7: // Simple/Float
96
- {
97
- const floatHex = Array.from(buffer.slice(offset))
98
- .map(b => b.toString(16).padStart(2, '0'))
99
- .join('')
100
- const result = parseFloatOrSimple(floatHex, options)
101
- return { value: result.value, bytesRead: result.bytesRead }
102
- }
83
+ return parseFloatOrSimpleFromBuffer(buffer, offset, options)
103
84
 
104
85
  default:
105
86
  throw new Error(`Unknown major type: ${majorType}`)
@@ -356,8 +337,10 @@ export function useCborCollection() {
356
337
  const valueResult = parseItem(buffer, currentOffset, options, depth + 1)
357
338
  currentOffset += valueResult.bytesRead
358
339
 
359
- // For duplicate key detection, serialize the key
360
- const keyString = convertKeyToString(keyResult.value)
340
+ // For duplicate key detection, serialize the parsed value semantically
341
+ // RFC 8949 Section 5.6: comparison must be on semantic values, not raw bytes
342
+ // (raw bytes differ when same value uses different byte widths)
343
+ const keyString = serializeValueForComparison(keyResult.value)
361
344
 
362
345
  // Check for duplicate keys based on dupMapKeyMode
363
346
  // RFC 8949: Deterministic encoding SHOULD reject duplicate keys
@@ -420,8 +403,10 @@ export function useCborCollection() {
420
403
  const valueResult = parseItem(buffer, currentOffset, options, depth + 1)
421
404
  currentOffset += valueResult.bytesRead
422
405
 
423
- // For duplicate key detection, serialize the key
424
- const keyString = convertKeyToString(keyResult.value)
406
+ // For duplicate key detection, serialize the parsed value semantically
407
+ // RFC 8949 Section 5.6: comparison must be on semantic values, not raw bytes
408
+ // (raw bytes differ when same value uses different byte widths)
409
+ const keyString = serializeValueForComparison(keyResult.value)
425
410
 
426
411
  // Check for duplicate keys based on dupMapKeyMode
427
412
  // RFC 8949: Deterministic encoding SHOULD reject duplicate keys
@@ -485,7 +470,16 @@ export function useCborCollection() {
485
470
  // Remove spaces from hex string
486
471
  const cleanHex = hexString.replace(/\s+/g, '')
487
472
  const buffer = hexToBytes(cleanHex)
488
- return parseArrayFromBuffer(buffer, 0, options, 0)
473
+
474
+ // Set parse start time for timeout enforcement
475
+ if (options?.limits?.maxParseTime) {
476
+ parseStartTime = Date.now()
477
+ }
478
+ try {
479
+ return parseArrayFromBuffer(buffer, 0, options, 0)
480
+ } finally {
481
+ parseStartTime = 0
482
+ }
489
483
  }
490
484
 
491
485
  /**
@@ -499,7 +493,16 @@ export function useCborCollection() {
499
493
  // Remove spaces from hex string
500
494
  const cleanHex = hexString.replace(/\s+/g, '')
501
495
  const buffer = hexToBytes(cleanHex)
502
- return parseMapFromBuffer(buffer, 0, options, 0)
496
+
497
+ // Set parse start time for timeout enforcement
498
+ if (options?.limits?.maxParseTime) {
499
+ parseStartTime = Date.now()
500
+ }
501
+ try {
502
+ return parseMapFromBuffer(buffer, 0, options, 0)
503
+ } finally {
504
+ parseStartTime = 0
505
+ }
503
506
  }
504
507
 
505
508
  return {
@@ -336,6 +336,7 @@ export function useCborFloat() {
336
336
  return {
337
337
  parse,
338
338
  parseFloat,
339
- parseSimple
339
+ parseSimple,
340
+ parseFromBuffer
340
341
  }
341
342
  }
@@ -20,15 +20,15 @@ import { hexToBytes, readByte, readUint, readBigUint, extractCborHeader, validat
20
20
  */
21
21
  export function useCborInteger() {
22
22
  /**
23
- * Parses CBOR integer (Major Type 0 or 1)
23
+ * Parses CBOR integer (Major Type 0 or 1) from a buffer at a given offset
24
24
  *
25
- * @param hexString - CBOR hex string
25
+ * @param buffer - Data buffer
26
+ * @param offset - Current offset into the buffer
26
27
  * @param options - Parser options (optional)
27
28
  * @returns Parsed integer value and bytes read
28
29
  */
29
- const parseInteger = (hexString: string, options?: ParseOptions): ParseResult => {
30
- const buffer = hexToBytes(hexString)
31
- const initialByte = readByte(buffer, 0)
30
+ const parseIntegerFromBuffer = (buffer: Uint8Array, offset: number, options?: ParseOptions): ParseResult => {
31
+ const initialByte = readByte(buffer, offset)
32
32
 
33
33
  const { majorType, additionalInfo } = extractCborHeader(initialByte)
34
34
 
@@ -42,19 +42,19 @@ export function useCborInteger() {
42
42
  bytesRead = 1
43
43
  } else if (additionalInfo === 24) {
44
44
  // 1 byte follows
45
- rawValue = readByte(buffer, 1)
45
+ rawValue = readByte(buffer, offset + 1)
46
46
  bytesRead = 2
47
47
  } else if (additionalInfo === 25) {
48
48
  // 2 bytes follow
49
- rawValue = readUint(buffer, 1, 2)
49
+ rawValue = readUint(buffer, offset + 1, 2)
50
50
  bytesRead = 3
51
51
  } else if (additionalInfo === 26) {
52
52
  // 4 bytes follow
53
- rawValue = readUint(buffer, 1, 4)
53
+ rawValue = readUint(buffer, offset + 1, 4)
54
54
  bytesRead = 5
55
55
  } else if (additionalInfo === 27) {
56
56
  // 8 bytes follow - use BigInt for large values
57
- const bigValue = readBigUint(buffer, 1, 8)
57
+ const bigValue = readBigUint(buffer, offset + 1, 8)
58
58
 
59
59
  // Check if value fits in Number.MAX_SAFE_INTEGER
60
60
  if (bigValue <= BigInt(Number.MAX_SAFE_INTEGER)) {
@@ -108,7 +108,21 @@ export function useCborInteger() {
108
108
  }
109
109
  }
110
110
 
111
+ /**
112
+ * Parses CBOR integer (Major Type 0 or 1) from hex string
113
+ * Thin wrapper around parseIntegerFromBuffer
114
+ *
115
+ * @param hexString - CBOR hex string
116
+ * @param options - Parser options (optional)
117
+ * @returns Parsed integer value and bytes read
118
+ */
119
+ const parseInteger = (hexString: string, options?: ParseOptions): ParseResult => {
120
+ const buffer = hexToBytes(hexString)
121
+ return parseIntegerFromBuffer(buffer, 0, options)
122
+ }
123
+
111
124
  return {
112
- parseInteger
125
+ parseInteger,
126
+ parseIntegerFromBuffer
113
127
  }
114
128
  }