@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.
- package/CHANGELOG.md +55 -0
- package/dist/{chunk-ZRPJUEIZ.js → chunk-5IWW5H47.js} +546 -227
- package/dist/chunk-5IWW5H47.js.map +1 -0
- package/dist/{chunk-2HBCILJS.cjs → chunk-RVG2BY32.cjs} +545 -226
- package/dist/chunk-RVG2BY32.cjs.map +1 -0
- package/dist/{chunk-2FUTHZQQ.cjs → chunk-S4RXO6IB.cjs} +244 -166
- package/dist/chunk-S4RXO6IB.cjs.map +1 -0
- package/dist/{chunk-7CFYWHS6.js → chunk-UMAX5MX5.js} +244 -166
- 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-B_iaShG6.d.ts → useCborTag-BD6Sqp7p.d.ts} +11 -6
- package/dist/{useCborTag-BfTIV8HM.d.cts → useCborTag-QpZR-Er2.d.cts} +11 -6
- package/package.json +1 -1
- package/src/__tests__/public-api.test.ts +153 -0
- package/src/__tests__/roundtrip.test.ts +701 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +129 -5
- package/src/encoder/__tests__/cbor-encoder-errors.test.ts +847 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +126 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +14 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +56 -23
- package/src/encoder/composables/useCborEncoder.ts +27 -1
- package/src/encoder/composables/useCborSimpleEncoder.ts +40 -8
- package/src/encoder/composables/useCborStringEncoder.ts +23 -10
- 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-float-errors.test.ts +41 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +97 -7
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +166 -33
- package/src/parser/__tests__/cbor-standard-tags.test.ts +104 -7
- package/src/parser/__tests__/cbor-string-errors.test.ts +4 -4
- package/src/parser/__tests__/cbor-tag-errors.test.ts +1 -1
- 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 +95 -9
- package/src/parser/composables/useCborInteger.ts +24 -10
- package/src/parser/composables/useCborParser.ts +387 -216
- package/src/parser/composables/useCborString.ts +22 -4
- package/src/parser/composables/useCborTag.ts +149 -53
- package/src/parser/utils.ts +11 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +0 -1
- package/dist/chunk-2HBCILJS.cjs.map +0 -1
- package/dist/chunk-7CFYWHS6.js.map +0 -1
- package/dist/chunk-ZRPJUEIZ.js.map +0 -1
- 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
|
-
//
|
|
265
|
-
const nested = 'c0'.repeat(
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
116
|
-
expect(() => parseByteString(buffer, 0)).toThrow('
|
|
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
|
|
129
|
-
expect(() => parseTextString(buffer, 0)).toThrow('
|
|
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
|
|
23
|
+
expect(() => parseTag('c18201')).toThrow('Unexpected end of buffer')
|
|
24
24
|
})
|
|
25
25
|
})
|
|
26
26
|
|