@marcuspuchalla/nachos 0.1.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 (100) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +674 -0
  3. package/README.md +345 -0
  4. package/dist/chunk-2FUTHZQQ.cjs +755 -0
  5. package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
  6. package/dist/chunk-2HBCILJS.cjs +2034 -0
  7. package/dist/chunk-2HBCILJS.cjs.map +1 -0
  8. package/dist/chunk-7CFYWHS6.js +742 -0
  9. package/dist/chunk-7CFYWHS6.js.map +1 -0
  10. package/dist/chunk-PD72MVTX.cjs +160 -0
  11. package/dist/chunk-PD72MVTX.cjs.map +1 -0
  12. package/dist/chunk-ZDZ2B5PE.js +149 -0
  13. package/dist/chunk-ZDZ2B5PE.js.map +1 -0
  14. package/dist/chunk-ZRPJUEIZ.js +2020 -0
  15. package/dist/chunk-ZRPJUEIZ.js.map +1 -0
  16. package/dist/encoder/index.cjs +57 -0
  17. package/dist/encoder/index.cjs.map +1 -0
  18. package/dist/encoder/index.d.cts +72 -0
  19. package/dist/encoder/index.d.ts +72 -0
  20. package/dist/encoder/index.js +4 -0
  21. package/dist/encoder/index.js.map +1 -0
  22. package/dist/index.cjs +606 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d.cts +494 -0
  25. package/dist/index.d.ts +494 -0
  26. package/dist/index.js +523 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/metafile-cjs.json +1 -0
  29. package/dist/metafile-esm.json +1 -0
  30. package/dist/parser/index.cjs +85 -0
  31. package/dist/parser/index.cjs.map +1 -0
  32. package/dist/parser/index.d.cts +72 -0
  33. package/dist/parser/index.d.ts +72 -0
  34. package/dist/parser/index.js +4 -0
  35. package/dist/parser/index.js.map +1 -0
  36. package/dist/types-DvNlfbKB.d.cts +301 -0
  37. package/dist/types-DvNlfbKB.d.ts +301 -0
  38. package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
  39. package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
  40. package/dist/useCborTag-B_iaShG6.d.ts +142 -0
  41. package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
  42. package/package.json +102 -0
  43. package/src/__tests__/public-api.test.ts +326 -0
  44. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
  45. package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
  46. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
  47. package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
  48. package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
  49. package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
  50. package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
  51. package/src/encoder/composables/useCborEncoder.ts +203 -0
  52. package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
  53. package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
  54. package/src/encoder/composables/useCborStringEncoder.ts +266 -0
  55. package/src/encoder/composables/useCborTagEncoder.ts +158 -0
  56. package/src/encoder/index.ts +35 -0
  57. package/src/encoder/types.ts +88 -0
  58. package/src/encoder/utils.ts +80 -0
  59. package/src/index.ts +434 -0
  60. package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
  61. package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
  62. package/src/parser/__tests__/cbor-collection.test.ts +369 -0
  63. package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
  64. package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
  65. package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
  66. package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
  67. package/src/parser/__tests__/cbor-float.test.ts +502 -0
  68. package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
  69. package/src/parser/__tests__/cbor-integer.test.ts +200 -0
  70. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
  71. package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
  72. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
  73. package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
  74. package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
  75. package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
  76. package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
  77. package/src/parser/__tests__/cbor-string.test.ts +224 -0
  78. package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
  79. package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
  80. package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
  81. package/src/parser/__tests__/cbor-tag.test.ts +684 -0
  82. package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
  83. package/src/parser/__tests__/pathBuilder.test.ts +256 -0
  84. package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
  85. package/src/parser/__tests__/security-limits.test.ts +248 -0
  86. package/src/parser/__tests__/utils-errors.test.ts +127 -0
  87. package/src/parser/composables/useCborCollection.ts +509 -0
  88. package/src/parser/composables/useCborDiagnostic.ts +381 -0
  89. package/src/parser/composables/useCborFloat.ts +256 -0
  90. package/src/parser/composables/useCborInteger.ts +114 -0
  91. package/src/parser/composables/useCborParser.ts +951 -0
  92. package/src/parser/composables/useCborString.ts +330 -0
  93. package/src/parser/composables/useCborStringTypes.ts +129 -0
  94. package/src/parser/composables/useCborTag.ts +739 -0
  95. package/src/parser/index.ts +56 -0
  96. package/src/parser/types.ts +371 -0
  97. package/src/parser/utils/pathBuilder.ts +259 -0
  98. package/src/parser/utils.ts +398 -0
  99. package/src/utils/__tests__/logger.test.ts +186 -0
  100. package/src/utils/logger.ts +96 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * CBOR String Parser Composable
3
+ * Handles Major Types 2 (Byte Strings) and 3 (Text Strings)
4
+ * Supports definite and indefinite length encoding
5
+ */
6
+
7
+ import type { ParseResult, ParseOptions } from '../types'
8
+ import { hexToBytes, readByte, readUint, readBigUint, extractCborHeader, validateUtf8Strict } from '../utils'
9
+ import { useCborByteString, useCborTextString } from './useCborStringTypes'
10
+
11
+ /**
12
+ * Composable for parsing CBOR strings (Major Types 2 and 3)
13
+ *
14
+ * @returns Object with string parsing functions
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const { parseString } = useCborString()
19
+ * const result = parseString('6449455446') // "IETF"
20
+ * ```
21
+ */
22
+ export function useCborString() {
23
+ const { create: createByteString } = useCborByteString()
24
+ const { create: createTextString } = useCborTextString()
25
+
26
+ /**
27
+ * Parses the length from CBOR additional info field
28
+ *
29
+ * @param buffer - Data buffer
30
+ * @param offset - Current offset (after initial byte)
31
+ * @param ai - Additional info field (0-31)
32
+ * @returns Object with length and bytes consumed
33
+ */
34
+ const parseLength = (buffer: Uint8Array, offset: number, ai: number, options?: ParseOptions): { length: number; bytesConsumed: number } => {
35
+ if (ai < 24) {
36
+ // Direct encoding (0-23)
37
+ return { length: ai, bytesConsumed: 0 }
38
+ } else if (ai === 24) {
39
+ // 1 byte follows
40
+ const length = readByte(buffer, offset)
41
+
42
+ // RFC 8949 Section 4.2.1: Canonical encoding requires shortest form
43
+ if (options?.validateCanonical && length < 24) {
44
+ throw new Error(`Non-canonical length encoding: ${length} should use direct encoding (AI < 24), not AI=24`)
45
+ }
46
+
47
+ return { length, bytesConsumed: 1 }
48
+ } else if (ai === 25) {
49
+ // 2 bytes follow
50
+ const length = readUint(buffer, offset, 2)
51
+
52
+ // RFC 8949 Section 4.2.1: Value must be >= 256 to justify 2-byte encoding
53
+ if (options?.validateCanonical && length < 256) {
54
+ throw new Error(`Non-canonical length encoding: ${length} should use ${length < 24 ? 'direct encoding' : '1-byte encoding (AI=24)'}, not AI=25`)
55
+ }
56
+
57
+ return { length, bytesConsumed: 2 }
58
+ } else if (ai === 26) {
59
+ // 4 bytes follow
60
+ const length = readUint(buffer, offset, 4)
61
+
62
+ // RFC 8949 Section 4.2.1: Value must be >= 65536 to justify 4-byte encoding
63
+ if (options?.validateCanonical && length < 65536) {
64
+ throw new Error(`Non-canonical length encoding: ${length} should use shorter encoding, not AI=26`)
65
+ }
66
+
67
+ return { length, bytesConsumed: 4 }
68
+ } else if (ai === 27) {
69
+ // 8 bytes follow - rare for strings, but supported
70
+ const lengthBigInt = readBigUint(buffer, offset, 8)
71
+
72
+ // Check if value fits in safe integer range
73
+ if (lengthBigInt > BigInt(Number.MAX_SAFE_INTEGER)) {
74
+ throw new Error(`String length ${lengthBigInt} exceeds maximum safe integer`)
75
+ }
76
+ const length = Number(lengthBigInt)
77
+
78
+ // RFC 8949 Section 4.2.1: Value must be >= 2^32 to justify 8-byte encoding
79
+ if (options?.validateCanonical && length < 4294967296) {
80
+ throw new Error(`Non-canonical length encoding: ${length} should use shorter encoding, not AI=27`)
81
+ }
82
+
83
+ return { length, bytesConsumed: 8 }
84
+ } else if (ai === 31) {
85
+ // Indefinite length (break-terminated) - special marker
86
+ return { length: -1, bytesConsumed: 0 }
87
+ } else {
88
+ throw new Error(`Invalid additional info: ${ai}`)
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Parses CBOR byte string (Major Type 2)
94
+ *
95
+ * @param buffer - Data buffer
96
+ * @param offset - Current offset
97
+ * @param options - Parser options (optional)
98
+ * @returns Parsed byte array and bytes read
99
+ */
100
+ const parseByteString = (buffer: Uint8Array, offset: number = 0, options?: ParseOptions): ParseResult => {
101
+ const initialByte = readByte(buffer, offset)
102
+ const { majorType, additionalInfo } = extractCborHeader(initialByte)
103
+
104
+ if (majorType !== 2) {
105
+ throw new Error(`Expected major type 2 (byte string), got ${majorType}`)
106
+ }
107
+
108
+ // Check if indefinite length is allowed
109
+ if (additionalInfo === 31 && options?.allowIndefinite === false) {
110
+ throw new Error('Indefinite-length encoding is not allowed in strict mode')
111
+ }
112
+
113
+ // Parse the length
114
+ const { length, bytesConsumed } = parseLength(buffer, offset + 1, additionalInfo, options)
115
+
116
+ // Handle indefinite-length byte string
117
+ if (length === -1) {
118
+ const chunks: Uint8Array[] = []
119
+ let currentOffset = offset + 1 + bytesConsumed
120
+ let totalLength = 0
121
+
122
+ while (currentOffset < buffer.length) {
123
+ // Peek at next byte to check for break marker
124
+ if (currentOffset >= buffer.length) {
125
+ throw new Error(`Incomplete indefinite byte string: missing break marker`)
126
+ }
127
+
128
+ const nextByte = buffer[currentOffset]
129
+
130
+ // Check for break marker (0xff)
131
+ if (nextByte === 0xff) {
132
+ currentOffset++ // Consume break byte
133
+ break
134
+ }
135
+
136
+ // Parse chunk (must be definite-length byte string)
137
+ // Use try-catch to provide better error context for indefinite strings
138
+ let chunkResult
139
+ try {
140
+ chunkResult = parseByteString(buffer, currentOffset, options)
141
+ } catch (error) {
142
+ throw new Error(`Error parsing indefinite byte string chunk at offset ${currentOffset}: ${(error as Error).message}`)
143
+ }
144
+
145
+ // Validate chunk is a byte string
146
+ if (!(chunkResult.value instanceof Uint8Array)) {
147
+ throw new Error('Indefinite byte string must contain only byte string chunks')
148
+ }
149
+
150
+ chunks.push(chunkResult.value)
151
+ totalLength += chunkResult.value.length
152
+ currentOffset += chunkResult.bytesRead
153
+
154
+ // Check length limit during accumulation
155
+ if (options?.limits?.maxStringLength && totalLength > options.limits.maxStringLength) {
156
+ throw new Error(`Byte string length ${totalLength} exceeds limit of ${options.limits.maxStringLength} bytes`)
157
+ }
158
+ }
159
+
160
+ // Concatenate all chunks
161
+ const bytes = new Uint8Array(totalLength)
162
+ let destOffset = 0
163
+ for (const chunk of chunks) {
164
+ bytes.set(chunk, destOffset)
165
+ destOffset += chunk.length
166
+ }
167
+
168
+ // Return CborByteString with indefinite marker and original chunks
169
+ return {
170
+ value: createByteString(bytes, true, chunks),
171
+ bytesRead: currentOffset - offset
172
+ }
173
+ }
174
+
175
+ // Definite-length byte string
176
+ // Check length limit before allocating
177
+ if (options?.limits?.maxStringLength && length > options.limits.maxStringLength) {
178
+ throw new Error(`Byte string length ${length} exceeds limit of ${options.limits.maxStringLength} bytes`)
179
+ }
180
+
181
+ const payloadOffset = offset + 1 + bytesConsumed
182
+
183
+ // Validate buffer has enough bytes
184
+ if (payloadOffset + length > buffer.length) {
185
+ throw new Error(`Insufficient data: expected ${length} bytes at offset ${payloadOffset}, but only ${buffer.length - payloadOffset} bytes available`)
186
+ }
187
+
188
+ const payload = buffer.slice(payloadOffset, payloadOffset + length)
189
+
190
+ return {
191
+ value: payload,
192
+ bytesRead: 1 + bytesConsumed + length
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Parses CBOR text string (Major Type 3)
198
+ *
199
+ * @param buffer - Data buffer
200
+ * @param offset - Current offset
201
+ * @param options - Parser options (optional)
202
+ * @returns Parsed text string and bytes read
203
+ */
204
+ const parseTextString = (buffer: Uint8Array, offset: number = 0, options?: ParseOptions): ParseResult => {
205
+ const initialByte = readByte(buffer, offset)
206
+ const { majorType, additionalInfo } = extractCborHeader(initialByte)
207
+
208
+ if (majorType !== 3) {
209
+ throw new Error(`Expected major type 3 (text string), got ${majorType}`)
210
+ }
211
+
212
+ // Check if indefinite length is allowed
213
+ if (additionalInfo === 31 && options?.allowIndefinite === false) {
214
+ throw new Error('Indefinite-length encoding is not allowed in strict mode')
215
+ }
216
+
217
+ // Parse the length
218
+ const { length, bytesConsumed } = parseLength(buffer, offset + 1, additionalInfo, options)
219
+
220
+ // Handle indefinite-length text string
221
+ if (length === -1) {
222
+ const chunks: string[] = []
223
+ let currentOffset = offset + 1 + bytesConsumed
224
+ let totalLength = 0
225
+
226
+ while (currentOffset < buffer.length) {
227
+ // Peek at next byte to check for break marker
228
+ if (currentOffset >= buffer.length) {
229
+ throw new Error(`Incomplete indefinite text string: missing break marker`)
230
+ }
231
+
232
+ const nextByte = buffer[currentOffset]
233
+
234
+ // Check for break marker (0xff)
235
+ if (nextByte === 0xff) {
236
+ currentOffset++ // Consume break byte
237
+ break
238
+ }
239
+
240
+ // Parse chunk (must be definite-length text string)
241
+ // Use try-catch to provide better error context for indefinite strings
242
+ let chunkResult
243
+ try {
244
+ chunkResult = parseTextString(buffer, currentOffset, options)
245
+ } catch (error) {
246
+ throw new Error(`Error parsing indefinite text string chunk at offset ${currentOffset}: ${(error as Error).message}`)
247
+ }
248
+
249
+ // Validate chunk is a text string
250
+ if (typeof chunkResult.value !== 'string') {
251
+ throw new Error('Indefinite text string must contain only text string chunks')
252
+ }
253
+
254
+ chunks.push(chunkResult.value)
255
+ totalLength += chunkResult.value.length
256
+ currentOffset += chunkResult.bytesRead
257
+
258
+ // Check length limit during accumulation
259
+ if (options?.limits?.maxStringLength && totalLength > options.limits.maxStringLength) {
260
+ throw new Error(`Text string length ${totalLength} exceeds limit of ${options.limits.maxStringLength} characters`)
261
+ }
262
+ }
263
+
264
+ // Concatenate all chunks
265
+ const text = chunks.join('')
266
+
267
+ // Return CborTextString with indefinite marker and original chunks
268
+ return {
269
+ value: createTextString(text, true, chunks),
270
+ bytesRead: currentOffset - offset
271
+ }
272
+ }
273
+
274
+ // Definite-length text string
275
+ // Check length limit before allocating
276
+ if (options?.limits?.maxStringLength && length > options.limits.maxStringLength) {
277
+ throw new Error(`Text string length ${length} bytes exceeds limit of ${options.limits.maxStringLength} bytes`)
278
+ }
279
+
280
+ const payloadOffset = offset + 1 + bytesConsumed
281
+
282
+ // Validate buffer has enough bytes
283
+ if (payloadOffset + length > buffer.length) {
284
+ throw new Error(`Insufficient data: expected ${length} bytes at offset ${payloadOffset}, but only ${buffer.length - payloadOffset} bytes available`)
285
+ }
286
+
287
+ const payload = buffer.slice(payloadOffset, payloadOffset + length)
288
+
289
+ // Validate UTF-8 if strict validation is enabled
290
+ if (options?.validateUtf8Strict) {
291
+ validateUtf8Strict(payload)
292
+ }
293
+
294
+ // Decode UTF-8 bytes to string
295
+ const decoder = new TextDecoder('utf-8')
296
+ const text = decoder.decode(payload)
297
+
298
+ return {
299
+ value: text,
300
+ bytesRead: 1 + bytesConsumed + length
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Parses CBOR string (auto-detects byte or text string)
306
+ *
307
+ * @param hexString - CBOR hex string
308
+ * @param options - Parser options (optional)
309
+ * @returns Parsed string (Uint8Array for MT 2, string for MT 3) and bytes read
310
+ */
311
+ const parseString = (hexString: string, options?: ParseOptions): ParseResult => {
312
+ const buffer = hexToBytes(hexString)
313
+ const initialByte = readByte(buffer, 0)
314
+ const { majorType } = extractCborHeader(initialByte)
315
+
316
+ if (majorType === 2) {
317
+ return parseByteString(buffer, 0, options)
318
+ } else if (majorType === 3) {
319
+ return parseTextString(buffer, 0, options)
320
+ } else {
321
+ throw new Error(`Expected major type 2 or 3 (string), got ${majorType}`)
322
+ }
323
+ }
324
+
325
+ return {
326
+ parseString,
327
+ parseByteString,
328
+ parseTextString
329
+ }
330
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * CBOR String Type Helpers
3
+ * Composables for working with CborByteString and CborTextString
4
+ */
5
+
6
+ import type { CborByteString, CborTextString } from '../types'
7
+ import { INDEFINITE_SYMBOL } from '../types'
8
+
9
+ /**
10
+ * Composable for creating and working with CBOR byte strings
11
+ */
12
+ export function useCborByteString() {
13
+ /**
14
+ * Create a CBOR byte string
15
+ *
16
+ * @param bytes - The byte data
17
+ * @param indefinite - Whether this was encoded with indefinite length
18
+ * @param chunks - Original chunks for indefinite byte strings
19
+ * @returns CborByteString object
20
+ */
21
+ const create = (bytes: Uint8Array, indefinite = false, chunks?: Uint8Array[]): CborByteString => {
22
+ const result: CborByteString = {
23
+ type: 'cbor-byte-string',
24
+ bytes,
25
+ ...(chunks && { chunks })
26
+ }
27
+
28
+ if (indefinite) {
29
+ (result as any)[INDEFINITE_SYMBOL] = true
30
+ }
31
+
32
+ return result
33
+ }
34
+
35
+ /**
36
+ * Check if a byte string was encoded with indefinite length
37
+ */
38
+ const isIndefinite = (value: CborByteString | Uint8Array): boolean => {
39
+ return (value as any)[INDEFINITE_SYMBOL] === true
40
+ }
41
+
42
+ /**
43
+ * Extract raw bytes from a CborByteString or Uint8Array
44
+ */
45
+ const toBytes = (value: CborByteString | Uint8Array): Uint8Array => {
46
+ if (value instanceof Uint8Array) {
47
+ return value
48
+ }
49
+ return value.bytes
50
+ }
51
+
52
+ /**
53
+ * Check if a value is a CborByteString
54
+ */
55
+ const isCborByteString = (value: any): value is CborByteString => {
56
+ return typeof value === 'object' &&
57
+ value !== null &&
58
+ value.type === 'cbor-byte-string' &&
59
+ value.bytes instanceof Uint8Array
60
+ }
61
+
62
+ return {
63
+ create,
64
+ isIndefinite,
65
+ toBytes,
66
+ isCborByteString
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Composable for creating and working with CBOR text strings
72
+ */
73
+ export function useCborTextString() {
74
+ /**
75
+ * Create a CBOR text string
76
+ *
77
+ * @param text - The text data
78
+ * @param indefinite - Whether this was encoded with indefinite length
79
+ * @param chunks - Original chunks for indefinite text strings
80
+ * @returns CborTextString object
81
+ */
82
+ const create = (text: string, indefinite = false, chunks?: string[]): CborTextString => {
83
+ const result: CborTextString = {
84
+ type: 'cbor-text-string',
85
+ text,
86
+ ...(chunks && { chunks })
87
+ }
88
+
89
+ if (indefinite) {
90
+ (result as any)[INDEFINITE_SYMBOL] = true
91
+ }
92
+
93
+ return result
94
+ }
95
+
96
+ /**
97
+ * Check if a text string was encoded with indefinite length
98
+ */
99
+ const isIndefinite = (value: CborTextString | string): boolean => {
100
+ return (value as any)[INDEFINITE_SYMBOL] === true
101
+ }
102
+
103
+ /**
104
+ * Extract raw string from a CborTextString or string
105
+ */
106
+ const toString = (value: CborTextString | string): string => {
107
+ if (typeof value === 'string') {
108
+ return value
109
+ }
110
+ return value.text
111
+ }
112
+
113
+ /**
114
+ * Check if a value is a CborTextString
115
+ */
116
+ const isCborTextString = (value: any): value is CborTextString => {
117
+ return typeof value === 'object' &&
118
+ value !== null &&
119
+ value.type === 'cbor-text-string' &&
120
+ typeof value.text === 'string'
121
+ }
122
+
123
+ return {
124
+ create,
125
+ isIndefinite,
126
+ toString,
127
+ isCborTextString
128
+ }
129
+ }