@marcuspuchalla/nachos 0.1.1 → 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 (62) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/{chunk-ZRPJUEIZ.js → chunk-5IWW5H47.js} +546 -227
  3. package/dist/chunk-5IWW5H47.js.map +1 -0
  4. package/dist/{chunk-2HBCILJS.cjs → chunk-RVG2BY32.cjs} +545 -226
  5. package/dist/chunk-RVG2BY32.cjs.map +1 -0
  6. package/dist/{chunk-2FUTHZQQ.cjs → chunk-S4RXO6IB.cjs} +244 -166
  7. package/dist/chunk-S4RXO6IB.cjs.map +1 -0
  8. package/dist/{chunk-7CFYWHS6.js → chunk-UMAX5MX5.js} +244 -166
  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-B_iaShG6.d.ts → useCborTag-BD6Sqp7p.d.ts} +11 -6
  29. package/dist/{useCborTag-BfTIV8HM.d.cts → useCborTag-QpZR-Er2.d.cts} +11 -6
  30. package/package.json +1 -1
  31. package/src/__tests__/public-api.test.ts +153 -0
  32. package/src/__tests__/roundtrip.test.ts +701 -0
  33. package/src/encoder/__tests__/cbor-collection-encoder.test.ts +129 -5
  34. package/src/encoder/__tests__/cbor-encoder-errors.test.ts +847 -0
  35. package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
  36. package/src/encoder/__tests__/cbor-string-encoder.test.ts +14 -0
  37. package/src/encoder/composables/useCborCollectionEncoder.ts +56 -23
  38. package/src/encoder/composables/useCborEncoder.ts +27 -1
  39. package/src/encoder/composables/useCborSimpleEncoder.ts +40 -8
  40. package/src/encoder/composables/useCborStringEncoder.ts +23 -10
  41. package/src/encoder/types.ts +0 -2
  42. package/src/index.ts +29 -20
  43. package/src/parser/__tests__/buffer-native-parsing.test.ts +338 -0
  44. package/src/parser/__tests__/cbor-float-errors.test.ts +41 -0
  45. package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
  46. package/src/parser/__tests__/cbor-security-dos-protection.test.ts +166 -33
  47. package/src/parser/__tests__/cbor-standard-tags.test.ts +104 -7
  48. package/src/parser/__tests__/cbor-string-errors.test.ts +4 -4
  49. package/src/parser/__tests__/cbor-tag-errors.test.ts +1 -1
  50. package/src/parser/__tests__/cbor-tag-reparse-fix.test.ts +268 -0
  51. package/src/parser/composables/useCborCollection.ts +45 -42
  52. package/src/parser/composables/useCborFloat.ts +95 -9
  53. package/src/parser/composables/useCborInteger.ts +24 -10
  54. package/src/parser/composables/useCborParser.ts +387 -216
  55. package/src/parser/composables/useCborString.ts +22 -4
  56. package/src/parser/composables/useCborTag.ts +149 -53
  57. package/src/parser/utils.ts +11 -0
  58. package/dist/chunk-2FUTHZQQ.cjs.map +0 -1
  59. package/dist/chunk-2HBCILJS.cjs.map +0 -1
  60. package/dist/chunk-7CFYWHS6.js.map +0 -1
  61. package/dist/chunk-ZRPJUEIZ.js.map +0 -1
  62. package/src/encoder/composables/#useCborTagEncoder.ts# +0 -158
@@ -10,9 +10,10 @@
10
10
  * These tests verify protections against known CBOR implementation vulnerabilities.
11
11
  */
12
12
 
13
- import { describe, it, expect } from 'vitest'
13
+ import { describe, it, expect, vi, afterEach } from 'vitest'
14
14
  import { useCborTag } from '../composables/useCborTag'
15
15
  import { useCborParser } from '../composables/useCborParser'
16
+ import type { TaggedValue } from '../types'
16
17
  import { useCborCollection } from '../composables/useCborCollection'
17
18
 
18
19
  describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
@@ -26,10 +27,10 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
26
27
 
27
28
  const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
28
29
 
29
- expect(result.value.tag).toBe(2)
30
- expect(typeof result.value.value).toBe('bigint')
30
+ expect((result.value as TaggedValue).tag).toBe(2)
31
+ expect(typeof (result.value as TaggedValue).value).toBe('bigint')
31
32
  // 512 bytes of 0x00 = BigInt 0
32
- expect(result.value.value).toBe(0n)
33
+ expect((result.value as TaggedValue).value).toBe(0n)
33
34
  })
34
35
 
35
36
  it('should reject tag 2 bignum exceeding size limit', () => {
@@ -64,11 +65,11 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
64
65
 
65
66
  const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
66
67
 
67
- expect(result.value.tag).toBe(2)
68
- expect(typeof result.value.value).toBe('bigint')
68
+ expect((result.value as TaggedValue).tag).toBe(2)
69
+ expect(typeof (result.value as TaggedValue).value).toBe('bigint')
69
70
  // 1024 bytes of 0xff should be a large positive bigint
70
71
  // 2^(1024*8) - 1
71
- expect(result.value.value > 0n).toBe(true)
72
+ expect(((result.value as TaggedValue).value as bigint) > 0n).toBe(true)
72
73
  })
73
74
 
74
75
  it('should reject tag 2 with 1 byte over limit', () => {
@@ -102,10 +103,10 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
102
103
 
103
104
  const result = parseTag(bignum, { limits: { maxBignumBytes: 4096 } })
104
105
 
105
- expect(result.value.tag).toBe(2)
106
- expect(typeof result.value.value).toBe('bigint')
106
+ expect((result.value as TaggedValue).tag).toBe(2)
107
+ expect(typeof (result.value as TaggedValue).value).toBe('bigint')
107
108
  // 2048 bytes of 0xbb should be a large positive bigint
108
- expect(result.value.value > 0n).toBe(true)
109
+ expect(((result.value as TaggedValue).value as bigint) > 0n).toBe(true)
109
110
  })
110
111
 
111
112
  it('should accept empty bignum (edge case)', () => {
@@ -116,10 +117,10 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
116
117
 
117
118
  const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
118
119
 
119
- expect(result.value.tag).toBe(2)
120
- expect(typeof result.value.value).toBe('bigint')
120
+ expect((result.value as TaggedValue).tag).toBe(2)
121
+ expect(typeof (result.value as TaggedValue).value).toBe('bigint')
121
122
  // Empty byte string = BigInt 0
122
- expect(result.value.value).toBe(0n)
123
+ expect((result.value as TaggedValue).value).toBe(0n)
123
124
  })
124
125
  })
125
126
 
@@ -133,11 +134,11 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
133
134
 
134
135
  const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
135
136
 
136
- expect(result.value.tag).toBe(3)
137
- expect(typeof result.value.value).toBe('bigint')
137
+ expect((result.value as TaggedValue).tag).toBe(3)
138
+ expect(typeof (result.value as TaggedValue).value).toBe('bigint')
138
139
  // Tag 3 = -1 - n, where n is the bignum value
139
140
  // 256 bytes of 0xff should be a large negative bigint
140
- expect(result.value.value < 0n).toBe(true)
141
+ expect(((result.value as TaggedValue).value as bigint) < 0n).toBe(true)
141
142
  })
142
143
 
143
144
  it('should reject tag 3 bignum exceeding size limit', () => {
@@ -183,8 +184,8 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
183
184
 
184
185
  const result = parseTag(epochTag, { limits: { maxBignumBytes: 100 } })
185
186
 
186
- expect(result.value.tag).toBe(1)
187
- expect(result.value.value).toBe(1363896240)
187
+ expect((result.value as TaggedValue).tag).toBe(1)
188
+ expect((result.value as TaggedValue).value).toBe(1363896240)
188
189
  })
189
190
 
190
191
  it('should NOT apply bignum limit to tag 24 (embedded CBOR)', () => {
@@ -195,8 +196,8 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
195
196
 
196
197
  const result = parseTag(embeddedCBOR, { limits: { maxBignumBytes: 2 } })
197
198
 
198
- expect(result.value.tag).toBe(24)
199
- expect(result.value.value).toBeInstanceOf(Uint8Array)
199
+ expect((result.value as TaggedValue).tag).toBe(24)
200
+ expect((result.value as TaggedValue).value).toBeInstanceOf(Uint8Array)
200
201
  })
201
202
 
202
203
  it('should NOT apply bignum limit to tag 258 (set)', () => {
@@ -207,8 +208,8 @@ describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
207
208
 
208
209
  const result = parseTag(setTag, { limits: { maxBignumBytes: 2 } })
209
210
 
210
- expect(result.value.tag).toBe(258)
211
- expect(result.value.value).toBeInstanceOf(Array)
211
+ expect((result.value as TaggedValue).tag).toBe(258)
212
+ expect((result.value as TaggedValue).value).toBeInstanceOf(Array)
212
213
  })
213
214
  })
214
215
  })
@@ -223,7 +224,7 @@ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
223
224
 
224
225
  const result = parseTag(nested, { limits: { maxTagDepth: 64 } })
225
226
 
226
- expect(result.value.tag).toBe(0)
227
+ expect((result.value as TaggedValue).tag).toBe(0)
227
228
  // Nested structure: tag 0 -> tag 0 -> ... -> 0
228
229
  })
229
230
 
@@ -255,14 +256,14 @@ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
255
256
 
256
257
  const result = parseTag(nested, { limits: { maxTagDepth: 10 } })
257
258
 
258
- expect(result.value.tag).toBe(0)
259
+ expect((result.value as TaggedValue).tag).toBe(0)
259
260
  })
260
261
 
261
262
  it('should use default tag depth limit when not specified', () => {
262
263
  const { parseTag } = useCborTag()
263
264
 
264
- // 100 nested tags (exceeds default 64)
265
- const nested = 'c0'.repeat(100) + '00'
265
+ // 150 nested tags (exceeds default 100)
266
+ const nested = 'c0'.repeat(150) + '00'
266
267
 
267
268
  expect(() => parseTag(nested)) // No options = use defaults
268
269
  .toThrow(/tag nesting depth.*exceeds/i)
@@ -276,7 +277,7 @@ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
276
277
 
277
278
  const result = parseTag(nested, { limits: { maxTagDepth: 150 } })
278
279
 
279
- expect(result.value.tag).toBe(0)
280
+ expect((result.value as TaggedValue).tag).toBe(0)
280
281
  })
281
282
 
282
283
  it('should handle nested tags with different tag numbers', () => {
@@ -287,9 +288,9 @@ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
287
288
 
288
289
  const result = parseTag(nested, { limits: { maxTagDepth: 5 } })
289
290
 
290
- expect(result.value.tag).toBe(1)
291
- expect(result.value.value.tag).toBe(2)
292
- expect((result.value.value as any).value).toHaveProperty('tag', 3)
291
+ expect((result.value as TaggedValue).tag).toBe(1)
292
+ expect(((result.value as TaggedValue).value as TaggedValue).tag).toBe(2)
293
+ expect(((result.value as TaggedValue).value as any).value).toHaveProperty('tag', 3)
293
294
  })
294
295
 
295
296
  it('should reject deeply nested mixed tags', () => {
@@ -312,8 +313,8 @@ describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
312
313
  limits: { maxTagDepth: 5, maxDepth: 5 }
313
314
  })
314
315
 
315
- expect(result.value.tag).toBe(1)
316
- expect(result.value.value).toEqual([1, 2, 3])
316
+ expect((result.value as TaggedValue).tag).toBe(1)
317
+ expect((result.value as TaggedValue).value).toEqual([1, 2, 3])
317
318
  })
318
319
 
319
320
  it('should prevent stack overflow with minimal input (< 1KB)', () => {
@@ -467,7 +468,7 @@ describe('Combined Security Protections', () => {
467
468
  }
468
469
  })
469
470
 
470
- expect(result.value.tag).toBe(1)
471
+ expect((result.value as TaggedValue).tag).toBe(1)
471
472
  })
472
473
 
473
474
  it('should reject when any single limit is exceeded', () => {
@@ -501,3 +502,135 @@ describe('Combined Security Protections', () => {
501
502
  .toThrow(/duplicate/i)
502
503
  })
503
504
  })
505
+
506
+ describe('maxParseTime Timeout Protection for Standard decode() Path', () => {
507
+ afterEach(() => {
508
+ vi.restoreAllMocks()
509
+ })
510
+
511
+ describe('useCborCollection: parseItem timeout', () => {
512
+ it('should enforce maxParseTime when parsing deeply nested arrays via standard path', () => {
513
+ // Mock Date.now: first call returns start time, subsequent calls simulate time passing
514
+ let callCount = 0
515
+ vi.spyOn(Date, 'now').mockImplementation(() => {
516
+ callCount++
517
+ // First call sets parseStartTime, subsequent calls check elapsed
518
+ return callCount <= 1 ? 1000 : 1100
519
+ })
520
+
521
+ const { parseArray } = useCborCollection()
522
+
523
+ // Build a deeply nested array: [[[[...]]]]
524
+ // 10 levels of nesting, innermost value is 0x00 (integer 0)
525
+ const nested = '81'.repeat(10) + '00'
526
+
527
+ // maxParseTime=50ms, but mocked time shows 100ms elapsed
528
+ expect(() => parseArray(nested, { limits: { maxParseTime: 50, maxDepth: 200 } }))
529
+ .toThrow(/parse timeout/i)
530
+ })
531
+
532
+ it('should enforce maxParseTime when parsing deeply nested maps via standard path', () => {
533
+ let callCount = 0
534
+ vi.spyOn(Date, 'now').mockImplementation(() => {
535
+ callCount++
536
+ return callCount <= 1 ? 1000 : 1100
537
+ })
538
+
539
+ const { parseMap } = useCborCollection()
540
+
541
+ // Build nested map: {"a": {"a": ... 0 }}
542
+ const nested = 'a16161'.repeat(10) + '00'
543
+
544
+ expect(() => parseMap(nested, { limits: { maxParseTime: 50, maxDepth: 200 } }))
545
+ .toThrow(/parse timeout/i)
546
+ })
547
+
548
+ it('should enforce maxParseTime via the top-level decode() function', () => {
549
+ let callCount = 0
550
+ vi.spyOn(Date, 'now').mockImplementation(() => {
551
+ callCount++
552
+ return callCount <= 1 ? 1000 : 1100
553
+ })
554
+
555
+ const { parse } = useCborParser()
556
+
557
+ // Nested arrays through the standard parse() path
558
+ const nested = '81'.repeat(10) + '00'
559
+
560
+ expect(() => parse(nested, { limits: { maxParseTime: 50, maxDepth: 200 } }))
561
+ .toThrow(/parse timeout/i)
562
+ })
563
+
564
+ it('should NOT timeout when parsing completes quickly with generous maxParseTime', () => {
565
+ // Mock Date.now to always return same value (no time passes)
566
+ vi.spyOn(Date, 'now').mockReturnValue(1000)
567
+
568
+ const { parse } = useCborParser()
569
+
570
+ // Simple array [1, 2, 3]
571
+ const result = parse('83010203', { limits: { maxParseTime: 5000 } })
572
+ expect(result.value).toEqual([1, 2, 3])
573
+ })
574
+
575
+ it('should NOT timeout for simple values when maxParseTime is set', () => {
576
+ vi.spyOn(Date, 'now').mockReturnValue(1000)
577
+
578
+ const { parse } = useCborParser()
579
+
580
+ const result = parse('1864', { limits: { maxParseTime: 5000 } })
581
+ expect(result.value).toBe(100)
582
+ })
583
+ })
584
+
585
+ describe('useCborTag: parseItem timeout', () => {
586
+ it('should enforce maxParseTime when parsing tags containing deeply nested structures', () => {
587
+ let callCount = 0
588
+ vi.spyOn(Date, 'now').mockImplementation(() => {
589
+ callCount++
590
+ return callCount <= 1 ? 1000 : 1100
591
+ })
592
+
593
+ const { parseTag } = useCborTag()
594
+
595
+ // Tag 1 wrapping deeply nested arrays
596
+ const nested = 'c1' + '81'.repeat(10) + '00'
597
+
598
+ expect(() => parseTag(nested, { limits: { maxParseTime: 50, maxDepth: 200, maxTagDepth: 200 } }))
599
+ .toThrow(/parse timeout/i)
600
+ })
601
+
602
+ it('should enforce maxParseTime for deeply nested tags', () => {
603
+ let callCount = 0
604
+ vi.spyOn(Date, 'now').mockImplementation(() => {
605
+ callCount++
606
+ return callCount <= 1 ? 1000 : 1100
607
+ })
608
+
609
+ const { parseTag } = useCborTag()
610
+
611
+ // 10 nested tags wrapping an integer
612
+ const nested = 'c0'.repeat(10) + '00'
613
+
614
+ expect(() => parseTag(nested, { limits: { maxParseTime: 50, maxTagDepth: 200 } }))
615
+ .toThrow(/parse timeout/i)
616
+ })
617
+ })
618
+
619
+ describe('parseSequence timeout', () => {
620
+ it('should enforce maxParseTime when parsing a CBOR sequence', () => {
621
+ let callCount = 0
622
+ vi.spyOn(Date, 'now').mockImplementation(() => {
623
+ callCount++
624
+ return callCount <= 1 ? 1000 : 1100
625
+ })
626
+
627
+ const { parseSequence } = useCborParser()
628
+
629
+ // Sequence of nested arrays
630
+ const nested = '81'.repeat(10) + '00'
631
+
632
+ expect(() => parseSequence(nested, { limits: { maxParseTime: 50, maxDepth: 200 } }))
633
+ .toThrow(/parse timeout/i)
634
+ })
635
+ })
636
+ })
@@ -20,11 +20,9 @@
20
20
 
21
21
  import { describe, it, expect } from 'vitest'
22
22
  import { useCborParser } from '../composables/useCborParser'
23
- import { useCborTag } from '../composables/useCborTag'
24
23
 
25
24
  describe('CBOR Standard Tags (RFC 8949)', () => {
26
25
  const { parse } = useCborParser()
27
- const { parseTag } = useCborTag()
28
26
 
29
27
  describe('Tag 0: Date/Time String (RFC 3339)', () => {
30
28
  it('should parse valid ISO 8601 date/time string', () => {
@@ -149,17 +147,57 @@ describe('CBOR Standard Tags (RFC 8949)', () => {
149
147
  })
150
148
 
151
149
  it('should reject non-integer exponent in strict mode', () => {
152
- // Tag 4 + [3.14, 500] (invalid - exponent must be integer)
150
+ // Tag 4 + [3.14, 500] (invalid - exponent must be integer per RFC 8949)
153
151
  // fb 40091eb851eb851f = float64 3.14
154
152
  // 1901f4 = uint 500
155
153
  const hex = 'c482fb40091eb851eb851f1901f4' // [3.14, 500]
156
154
 
157
- // Note: The parser validates that exponent is number|bigint, and 3.14 is a number
158
- // So this passes parsing but semantically the exponent should be integer
159
- // The current implementation accepts floats as exponents, which is RFC-compliant
160
- // (RFC says "integer" but implementations often accept any number)
155
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
156
+ .toThrow(/exponent must be an integer/)
157
+ })
158
+
159
+ it('should reject non-integer mantissa in strict mode', () => {
160
+ // Tag 4 + [-2, 3.14] (invalid - mantissa must be integer per RFC 8949)
161
+ // 21 = -2
162
+ // fb 40091eb851eb851f = float64 3.14
163
+ const hex = 'c48221fb40091eb851eb851f' // [-2, 3.14]
164
+
165
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
166
+ .toThrow(/mantissa must be an integer/)
167
+ })
168
+
169
+ it('should accept integer exponent and mantissa values', () => {
170
+ // Tag 4 + [0, 42]
171
+ // 00 = 0, 1829 = 42 (wrong, 182a = 42)
172
+ // Actually: 00 = 0, 18 2a = 42
173
+ const hex = 'c48200182a' // [0, 42]
174
+ const result = parse(hex, { strict: true, validateTagSemantics: true })
175
+ expect(result.value).toMatchObject({ tag: 4 })
176
+ const arr = (result.value as any).value as number[]
177
+ expect(arr[0]).toBe(0)
178
+ expect(arr[1]).toBe(42)
179
+ })
180
+
181
+ it('should accept negative integer exponent', () => {
182
+ // Tag 4 + [-1, 500]
183
+ // 20 = -1, 1901f4 = 500
184
+ const hex = 'c482201901f4' // [-1, 500]
161
185
  const result = parse(hex, { strict: true, validateTagSemantics: true })
162
186
  expect(result.value).toMatchObject({ tag: 4 })
187
+ const arr = (result.value as any).value as number[]
188
+ expect(arr[0]).toBe(-1)
189
+ expect(arr[1]).toBe(500)
190
+ })
191
+
192
+ it('should accept zero exponent and large mantissa', () => {
193
+ // Tag 4 + [0, 12345]
194
+ // 00 = 0, 193039 = 12345
195
+ const hex = 'c48200193039'
196
+ const result = parse(hex, { strict: true, validateTagSemantics: true })
197
+ expect(result.value).toMatchObject({ tag: 4 })
198
+ const arr = (result.value as any).value as number[]
199
+ expect(arr[0]).toBe(0)
200
+ expect(arr[1]).toBe(12345)
163
201
  })
164
202
  })
165
203
 
@@ -187,6 +225,36 @@ describe('CBOR Standard Tags (RFC 8949)', () => {
187
225
  expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
188
226
  .toThrow(/exactly 2 elements/i)
189
227
  })
228
+
229
+ it('should reject non-integer exponent in strict mode', () => {
230
+ // Tag 5 + [3.14, 500] (invalid - exponent must be integer per RFC 8949)
231
+ // fb 40091eb851eb851f = float64 3.14
232
+ // 1901f4 = uint 500
233
+ const hex = 'c582fb40091eb851eb851f1901f4' // [3.14, 500]
234
+
235
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
236
+ .toThrow(/exponent must be an integer/)
237
+ })
238
+
239
+ it('should reject non-integer mantissa in strict mode', () => {
240
+ // Tag 5 + [-2, 3.14] (invalid - mantissa must be integer per RFC 8949)
241
+ // 21 = -2
242
+ // fb 40091eb851eb851f = float64 3.14
243
+ const hex = 'c58221fb40091eb851eb851f' // [-2, 3.14]
244
+
245
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
246
+ .toThrow(/mantissa must be an integer/)
247
+ })
248
+
249
+ it('should accept integer exponent and mantissa values', () => {
250
+ // Tag 5 + [-2, 500]
251
+ const hex = 'c5822119 01f4'.replace(/\s/g, '')
252
+ const result = parse(hex, { strict: true, validateTagSemantics: true })
253
+ expect(result.value).toMatchObject({ tag: 5 })
254
+ const arr = (result.value as any).value as number[]
255
+ expect(arr[0]).toBe(-2)
256
+ expect(arr[1]).toBe(500)
257
+ })
190
258
  })
191
259
 
192
260
  describe('Tags 21-23: Expected Encoding', () => {
@@ -221,6 +289,35 @@ describe('CBOR Standard Tags (RFC 8949)', () => {
221
289
  })
222
290
  })
223
291
 
292
+ describe('Tags 2-3: Bignums', () => {
293
+ it('should parse tag 2 with byte string', () => {
294
+ // c2 (tag 2) + 41 (1-byte byte string) + 01
295
+ const hex = 'c24101'
296
+ const result = parse(hex)
297
+
298
+ expect(result.value).toMatchObject({
299
+ tag: 2,
300
+ value: 1n
301
+ })
302
+ })
303
+
304
+ it('should reject non-byte-string tag 2 in strict mode', () => {
305
+ // c2 (tag 2) + 01 (integer)
306
+ const hex = 'c201'
307
+
308
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
309
+ .toThrow(/byte string/i)
310
+ })
311
+
312
+ it('should reject non-byte-string tag 3 in strict mode', () => {
313
+ // c3 (tag 3) + 01 (integer)
314
+ const hex = 'c301'
315
+
316
+ expect(() => parse(hex, { strict: true, validateTagSemantics: true }))
317
+ .toThrow(/byte string/i)
318
+ })
319
+ })
320
+
224
321
  describe('Tag 32: URI', () => {
225
322
  it('should parse valid URI', () => {
226
323
  // d8 20 (tag 32) + 76 (22-byte text) + "http://www.example.com"
@@ -112,8 +112,8 @@ describe('useCborString - Error Handling', () => {
112
112
  // 5f (indefinite byte string) + 6161 (text "a") + ff (break)
113
113
  const buffer = new Uint8Array([0x5f, 0x61, 0x61, 0xff])
114
114
 
115
- // The error is thrown when trying to parse the text string as a byte string
116
- expect(() => parseByteString(buffer, 0)).toThrow('Expected major type 2')
115
+ // The error is thrown when validating the chunk type
116
+ expect(() => parseByteString(buffer, 0)).toThrow('chunks must be byte strings')
117
117
  })
118
118
  })
119
119
 
@@ -125,8 +125,8 @@ describe('useCborString - Error Handling', () => {
125
125
  // 7f (indefinite text string) + 4161 (byte string containing 'a') + ff (break)
126
126
  const buffer = new Uint8Array([0x7f, 0x41, 0x61, 0xff])
127
127
 
128
- // The error is thrown when trying to parse the byte string as a text string
129
- expect(() => parseTextString(buffer, 0)).toThrow('Expected major type 3')
128
+ // The error is thrown when validating the chunk type
129
+ expect(() => parseTextString(buffer, 0)).toThrow('chunks must be text strings')
130
130
  })
131
131
  })
132
132
 
@@ -20,7 +20,7 @@ describe('useCborTag - Error Handling', () => {
20
20
 
21
21
  // Tag with array that's incomplete
22
22
  // c1 (tag 1) + 82 (array of 2) + 01 (element 1) - missing element 2
23
- expect(() => parseTag('c18201')).toThrow('Unexpected end of buffer at offset')
23
+ expect(() => parseTag('c18201')).toThrow('Unexpected end of buffer')
24
24
  })
25
25
  })
26
26