@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.
- package/CHANGELOG.md +23 -0
- package/dist/{chunk-5A5T56JB.js → chunk-5IWW5H47.js} +378 -207
- package/dist/chunk-5IWW5H47.js.map +1 -0
- package/dist/{chunk-PTWN7K3Y.cjs → chunk-RVG2BY32.cjs} +378 -207
- package/dist/chunk-RVG2BY32.cjs.map +1 -0
- package/dist/{chunk-R62CQQNI.cjs → chunk-S4RXO6IB.cjs} +195 -165
- package/dist/chunk-S4RXO6IB.cjs.map +1 -0
- package/dist/{chunk-2MTLSQ7E.js → chunk-UMAX5MX5.js} +195 -165
- package/dist/chunk-UMAX5MX5.js.map +1 -0
- package/dist/encoder/index.cjs +13 -13
- package/dist/encoder/index.d.cts +2 -2
- package/dist/encoder/index.d.ts +2 -2
- package/dist/encoder/index.js +1 -1
- package/dist/index.cjs +32 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -19
- package/dist/index.d.ts +28 -19
- package/dist/index.js +16 -16
- package/dist/index.js.map +1 -1
- package/dist/metafile-cjs.json +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/parser/index.cjs +14 -14
- package/dist/parser/index.d.cts +3 -1
- package/dist/parser/index.d.ts +3 -1
- package/dist/parser/index.js +1 -1
- package/dist/{useCborSimpleEncoder-TVxzNJ_9.d.ts → useCborSimpleEncoder-BoKEmjP9.d.ts} +0 -2
- package/dist/{useCborSimpleEncoder-ButVU988.d.cts → useCborSimpleEncoder-C_OHxoB8.d.cts} +0 -2
- package/dist/{useCborTag-Cs1CZuXZ.d.cts → useCborTag-BD6Sqp7p.d.ts} +9 -4
- package/dist/{useCborTag-xV2Pz2VY.d.ts → useCborTag-QpZR-Er2.d.cts} +9 -4
- package/package.json +1 -1
- package/src/__tests__/public-api.test.ts +153 -0
- package/src/__tests__/roundtrip.test.ts +5 -6
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +103 -5
- package/src/encoder/__tests__/cbor-encoder-errors.test.ts +40 -5
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +28 -25
- package/src/encoder/composables/useCborEncoder.ts +21 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +34 -7
- package/src/encoder/types.ts +0 -2
- package/src/index.ts +29 -20
- package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +164 -31
- package/src/parser/__tests__/cbor-standard-tags.test.ts +75 -7
- package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
- package/src/parser/composables/useCborCollection.ts +45 -42
- package/src/parser/composables/useCborFloat.ts +2 -1
- package/src/parser/composables/useCborInteger.ts +24 -10
- package/src/parser/composables/useCborParser.ts +387 -197
- package/src/parser/composables/useCborTag.ts +45 -37
- package/src/parser/utils.ts +11 -0
- package/dist/chunk-2MTLSQ7E.js.map +0 -1
- package/dist/chunk-5A5T56JB.js.map +0 -1
- package/dist/chunk-PTWN7K3Y.cjs.map +0 -1
- 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,
|
|
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 {
|
|
28
|
+
const { parseIntegerFromBuffer } = useCborInteger()
|
|
29
29
|
const { parseByteString, parseTextString } = useCborString()
|
|
30
|
-
const {
|
|
31
|
-
const {
|
|
30
|
+
const { parseFromBuffer: parseFloatOrSimpleFromBuffer } = useCborFloat()
|
|
31
|
+
const { parseTagFromBuffer } = useCborTag()
|
|
32
32
|
|
|
33
|
-
/**
|
|
34
|
-
|
|
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
|
|
360
|
-
|
|
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
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -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
|
|
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
|
|
30
|
-
const
|
|
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
|
}
|