@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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for buffer-native parsing optimization (Task 2-A)
|
|
3
|
+
* Validates that buffer-based parsers produce identical results to hex-string parsers
|
|
4
|
+
* and that O(N^2) hex conversion is eliminated.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect } from 'vitest'
|
|
8
|
+
import { useCborInteger } from '../composables/useCborInteger'
|
|
9
|
+
import { useCborFloat } from '../composables/useCborFloat'
|
|
10
|
+
import { useCborTag } from '../composables/useCborTag'
|
|
11
|
+
import { useCborCollection } from '../composables/useCborCollection'
|
|
12
|
+
import { hexToBytes } from '../utils'
|
|
13
|
+
|
|
14
|
+
describe('Buffer-native parsing - parseIntegerFromBuffer', () => {
|
|
15
|
+
const { parseInteger, parseIntegerFromBuffer } = useCborInteger()
|
|
16
|
+
|
|
17
|
+
it('should export parseIntegerFromBuffer from useCborInteger', () => {
|
|
18
|
+
expect(typeof parseIntegerFromBuffer).toBe('function')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should parse unsigned integer 0 from buffer', () => {
|
|
22
|
+
const buffer = hexToBytes('00')
|
|
23
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
24
|
+
expect(result.value).toBe(0)
|
|
25
|
+
expect(result.bytesRead).toBe(1)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should parse unsigned integer 23 from buffer', () => {
|
|
29
|
+
const buffer = hexToBytes('17')
|
|
30
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
31
|
+
expect(result.value).toBe(23)
|
|
32
|
+
expect(result.bytesRead).toBe(1)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should parse unsigned integer 24 from buffer (1-byte follows)', () => {
|
|
36
|
+
const buffer = hexToBytes('1818')
|
|
37
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
38
|
+
expect(result.value).toBe(24)
|
|
39
|
+
expect(result.bytesRead).toBe(2)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should parse unsigned integer 100 from buffer (1-byte follows)', () => {
|
|
43
|
+
const buffer = hexToBytes('1864')
|
|
44
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
45
|
+
expect(result.value).toBe(100)
|
|
46
|
+
expect(result.bytesRead).toBe(2)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('should parse unsigned integer 1000 from buffer (2-byte follows)', () => {
|
|
50
|
+
const buffer = hexToBytes('1903e8')
|
|
51
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
52
|
+
expect(result.value).toBe(1000)
|
|
53
|
+
expect(result.bytesRead).toBe(3)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('should parse unsigned integer 1000000 from buffer (4-byte follows)', () => {
|
|
57
|
+
const buffer = hexToBytes('1a000f4240')
|
|
58
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
59
|
+
expect(result.value).toBe(1000000)
|
|
60
|
+
expect(result.bytesRead).toBe(5)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should parse large unsigned integer from buffer (8-byte follows)', () => {
|
|
64
|
+
const buffer = hexToBytes('1b000000e8d4a51000')
|
|
65
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
66
|
+
expect(result.value).toBe(1000000000000)
|
|
67
|
+
expect(result.bytesRead).toBe(9)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should parse negative integer -1 from buffer', () => {
|
|
71
|
+
const buffer = hexToBytes('20')
|
|
72
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
73
|
+
expect(result.value).toBe(-1)
|
|
74
|
+
expect(result.bytesRead).toBe(1)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should parse negative integer -100 from buffer', () => {
|
|
78
|
+
const buffer = hexToBytes('3863')
|
|
79
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
80
|
+
expect(result.value).toBe(-100)
|
|
81
|
+
expect(result.bytesRead).toBe(2)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should parse BigInt unsigned from buffer', () => {
|
|
85
|
+
// 18446744073709551615 (max uint64)
|
|
86
|
+
const buffer = hexToBytes('1bffffffffffffffff')
|
|
87
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
88
|
+
expect(result.value).toBe(18446744073709551615n)
|
|
89
|
+
expect(result.bytesRead).toBe(9)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should parse BigInt negative from buffer', () => {
|
|
93
|
+
// -18446744073709551616
|
|
94
|
+
const buffer = hexToBytes('3bffffffffffffffff')
|
|
95
|
+
const result = parseIntegerFromBuffer(buffer, 0)
|
|
96
|
+
expect(result.value).toBe(-18446744073709551616n)
|
|
97
|
+
expect(result.bytesRead).toBe(9)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should parse integer at non-zero offset', () => {
|
|
101
|
+
// Buffer: [0x83, 0x01, 0x02, 0x03] - array(3) then integers 1, 2, 3
|
|
102
|
+
// Parsing integer at offset 1 should get value 1
|
|
103
|
+
const buffer = hexToBytes('83010203')
|
|
104
|
+
const result = parseIntegerFromBuffer(buffer, 1)
|
|
105
|
+
expect(result.value).toBe(1)
|
|
106
|
+
expect(result.bytesRead).toBe(1)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should parse integer at offset with trailing data', () => {
|
|
110
|
+
// Buffer with integer 100 at offset 2, followed by other data
|
|
111
|
+
const buffer = hexToBytes('0000186400')
|
|
112
|
+
const result = parseIntegerFromBuffer(buffer, 2)
|
|
113
|
+
expect(result.value).toBe(100)
|
|
114
|
+
expect(result.bytesRead).toBe(2)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should produce identical results to parseInteger for all test vectors', () => {
|
|
118
|
+
const testVectors = [
|
|
119
|
+
'00', '01', '0a', '17', '1818', '1864', '18ff',
|
|
120
|
+
'190100', '1903e8', '19ffff',
|
|
121
|
+
'1a00010000', '1a000f4240', '1affffffff',
|
|
122
|
+
'1b0000000100000000', '1b000000e8d4a51000', '1bffffffffffffffff',
|
|
123
|
+
'20', '37', '3863', '38ff',
|
|
124
|
+
'390100', '3903e7', '39ffff',
|
|
125
|
+
'3a00010000', '3affffffff',
|
|
126
|
+
'3b0000000100000000', '3bffffffffffffffff',
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for (const hex of testVectors) {
|
|
130
|
+
const hexResult = parseInteger(hex)
|
|
131
|
+
const buffer = hexToBytes(hex)
|
|
132
|
+
const bufferResult = parseIntegerFromBuffer(buffer, 0)
|
|
133
|
+
|
|
134
|
+
expect(bufferResult.value).toEqual(hexResult.value)
|
|
135
|
+
expect(bufferResult.bytesRead).toEqual(hexResult.bytesRead)
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('should validate canonical encoding when options are passed', () => {
|
|
140
|
+
// Non-canonical: value 0 encoded with 1-byte AI
|
|
141
|
+
const buffer = hexToBytes('1800')
|
|
142
|
+
expect(() => parseIntegerFromBuffer(buffer, 0, { validateCanonical: true }))
|
|
143
|
+
.toThrow(/[Nn]on-canonical/)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
describe('Buffer-native parsing - parseFromBuffer (float/simple)', () => {
|
|
148
|
+
const { parseFromBuffer } = useCborFloat()
|
|
149
|
+
|
|
150
|
+
it('should export parseFromBuffer from useCborFloat', () => {
|
|
151
|
+
expect(typeof parseFromBuffer).toBe('function')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should parse false from buffer', () => {
|
|
155
|
+
const buffer = hexToBytes('f4')
|
|
156
|
+
const result = parseFromBuffer(buffer, 0)
|
|
157
|
+
expect(result.value).toBe(false)
|
|
158
|
+
expect(result.bytesRead).toBe(1)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should parse true from buffer', () => {
|
|
162
|
+
const buffer = hexToBytes('f5')
|
|
163
|
+
const result = parseFromBuffer(buffer, 0)
|
|
164
|
+
expect(result.value).toBe(true)
|
|
165
|
+
expect(result.bytesRead).toBe(1)
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should parse null from buffer', () => {
|
|
169
|
+
const buffer = hexToBytes('f6')
|
|
170
|
+
const result = parseFromBuffer(buffer, 0)
|
|
171
|
+
expect(result.value).toBe(null)
|
|
172
|
+
expect(result.bytesRead).toBe(1)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should parse undefined from buffer', () => {
|
|
176
|
+
const buffer = hexToBytes('f7')
|
|
177
|
+
const result = parseFromBuffer(buffer, 0)
|
|
178
|
+
expect(result.value).toBe(undefined)
|
|
179
|
+
expect(result.bytesRead).toBe(1)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should parse float16 0.0 from buffer', () => {
|
|
183
|
+
const buffer = hexToBytes('f90000')
|
|
184
|
+
const result = parseFromBuffer(buffer, 0)
|
|
185
|
+
expect(result.value).toBe(0.0)
|
|
186
|
+
expect(result.bytesRead).toBe(3)
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should parse float16 1.0 from buffer', () => {
|
|
190
|
+
const buffer = hexToBytes('f93c00')
|
|
191
|
+
const result = parseFromBuffer(buffer, 0)
|
|
192
|
+
expect(result.value).toBe(1.0)
|
|
193
|
+
expect(result.bytesRead).toBe(3)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should parse float32 from buffer', () => {
|
|
197
|
+
const buffer = hexToBytes('fa47c35000')
|
|
198
|
+
const result = parseFromBuffer(buffer, 0)
|
|
199
|
+
expect(result.value).toBe(100000.0)
|
|
200
|
+
expect(result.bytesRead).toBe(5)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should parse float64 from buffer', () => {
|
|
204
|
+
const buffer = hexToBytes('fb3ff199999999999a')
|
|
205
|
+
const result = parseFromBuffer(buffer, 0)
|
|
206
|
+
expect(result.value).toBeCloseTo(1.1, 10)
|
|
207
|
+
expect(result.bytesRead).toBe(9)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('should parse Infinity from buffer', () => {
|
|
211
|
+
const buffer = hexToBytes('f97c00')
|
|
212
|
+
const result = parseFromBuffer(buffer, 0)
|
|
213
|
+
expect(result.value).toBe(Infinity)
|
|
214
|
+
expect(result.bytesRead).toBe(3)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should parse NaN from buffer', () => {
|
|
218
|
+
const buffer = hexToBytes('f97e00')
|
|
219
|
+
const result = parseFromBuffer(buffer, 0)
|
|
220
|
+
expect(result.value).toBeNaN()
|
|
221
|
+
expect(result.bytesRead).toBe(3)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('should parse at non-zero offset', () => {
|
|
225
|
+
const buffer = hexToBytes('00f5')
|
|
226
|
+
const result = parseFromBuffer(buffer, 1)
|
|
227
|
+
expect(result.value).toBe(true)
|
|
228
|
+
expect(result.bytesRead).toBe(1)
|
|
229
|
+
})
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
describe('Buffer-native parsing - parseTagFromBuffer', () => {
|
|
233
|
+
const { parseTagFromBuffer } = useCborTag()
|
|
234
|
+
|
|
235
|
+
it('should export parseTagFromBuffer from useCborTag', () => {
|
|
236
|
+
expect(typeof parseTagFromBuffer).toBe('function')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('should parse tag 1 (epoch time) from buffer', () => {
|
|
240
|
+
// c11a514b67b0 = tag(1, 1363896240)
|
|
241
|
+
const buffer = hexToBytes('c11a514b67b0')
|
|
242
|
+
const result = parseTagFromBuffer(buffer, 0)
|
|
243
|
+
expect(result.value).toEqual({
|
|
244
|
+
tag: 1,
|
|
245
|
+
value: 1363896240
|
|
246
|
+
})
|
|
247
|
+
expect(result.bytesRead).toBe(6)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should parse self-describe tag 55799 from buffer', () => {
|
|
251
|
+
// d9d9f7 01 = tag(55799, 1)
|
|
252
|
+
const buffer = hexToBytes('d9d9f701')
|
|
253
|
+
const result = parseTagFromBuffer(buffer, 0)
|
|
254
|
+
expect(result.value).toEqual({
|
|
255
|
+
tag: 55799,
|
|
256
|
+
value: 1
|
|
257
|
+
})
|
|
258
|
+
expect(result.bytesRead).toBe(4)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should parse tag at non-zero offset', () => {
|
|
262
|
+
// Prefix with 0x01, then tag 1 with value 0
|
|
263
|
+
const buffer = hexToBytes('01c100')
|
|
264
|
+
const result = parseTagFromBuffer(buffer, 1)
|
|
265
|
+
expect(result.value).toEqual({
|
|
266
|
+
tag: 1,
|
|
267
|
+
value: 0
|
|
268
|
+
})
|
|
269
|
+
expect(result.bytesRead).toBe(2)
|
|
270
|
+
})
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
describe('Buffer-native parsing - collection integration', () => {
|
|
274
|
+
const { parseArray, parseMap } = useCborCollection()
|
|
275
|
+
|
|
276
|
+
it('should parse array of integers without O(N^2) hex conversion', () => {
|
|
277
|
+
// 83 01 02 03 = [1, 2, 3]
|
|
278
|
+
const result = parseArray('83010203')
|
|
279
|
+
expect(result.value).toEqual([1, 2, 3])
|
|
280
|
+
expect(result.bytesRead).toBe(4)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('should parse array with mixed types including floats', () => {
|
|
284
|
+
// 83 01 f5 f6 = [1, true, null]
|
|
285
|
+
const result = parseArray('8301f5f6')
|
|
286
|
+
expect(result.value).toEqual([1, true, null])
|
|
287
|
+
expect(result.bytesRead).toBe(4)
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should parse nested array with tags', () => {
|
|
291
|
+
// 82 c1 1a514b67b0 01 = [tag(1, 1363896240), 1]
|
|
292
|
+
const result = parseArray('82c11a514b67b001')
|
|
293
|
+
expect(result.value).toHaveLength(2)
|
|
294
|
+
expect((result.value as any[])[0]).toEqual({
|
|
295
|
+
tag: 1,
|
|
296
|
+
value: 1363896240
|
|
297
|
+
})
|
|
298
|
+
expect((result.value as any[])[1]).toBe(1)
|
|
299
|
+
expect(result.bytesRead).toBe(8)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('should parse map with integer keys', () => {
|
|
303
|
+
// a2 01 02 03 04 = {1: 2, 3: 4}
|
|
304
|
+
const result = parseMap('a201020304')
|
|
305
|
+
const map = result.value as Map<any, any>
|
|
306
|
+
expect(map.get(1)).toBe(2)
|
|
307
|
+
expect(map.get(3)).toBe(4)
|
|
308
|
+
expect(result.bytesRead).toBe(5)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('should parse map with float value', () => {
|
|
312
|
+
// a1 01 f93c00 = {1: 1.0}
|
|
313
|
+
const result = parseMap('a101f93c00')
|
|
314
|
+
const map = result.value as Map<any, any>
|
|
315
|
+
expect(map.get(1)).toBe(1.0)
|
|
316
|
+
expect(result.bytesRead).toBe(5)
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should parse large array correctly', () => {
|
|
320
|
+
// Build array of 100 integers 0-99
|
|
321
|
+
let hex = '9864' // array(100)... but that needs proper length encoding
|
|
322
|
+
// Actually: 0x98 = array with 1-byte length, 0x64 = 100
|
|
323
|
+
hex = '9864'
|
|
324
|
+
for (let i = 0; i < 100; i++) {
|
|
325
|
+
if (i < 24) {
|
|
326
|
+
hex += i.toString(16).padStart(2, '0')
|
|
327
|
+
} else {
|
|
328
|
+
hex += '18' + i.toString(16).padStart(2, '0')
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const result = parseArray(hex)
|
|
332
|
+
const arr = result.value as number[]
|
|
333
|
+
expect(arr).toHaveLength(100)
|
|
334
|
+
for (let i = 0; i < 100; i++) {
|
|
335
|
+
expect(arr[i]).toBe(i)
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
})
|
|
@@ -211,6 +211,47 @@ describe('useCborFloat - Error Handling', () => {
|
|
|
211
211
|
})
|
|
212
212
|
})
|
|
213
213
|
|
|
214
|
+
describe('Canonical float validation', () => {
|
|
215
|
+
it('should reject float32 when value fits in float16', () => {
|
|
216
|
+
const { parseFloat } = useCborFloat()
|
|
217
|
+
|
|
218
|
+
// 1.0 fits in float16, but encoded as float32
|
|
219
|
+
expect(() => parseFloat('fa3f800000', { validateCanonical: true }))
|
|
220
|
+
.toThrow(/canonical.*float16/i)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should reject float64 when value fits in float32', () => {
|
|
224
|
+
const { parseFloat } = useCborFloat()
|
|
225
|
+
|
|
226
|
+
// 1.0 fits in float32, but encoded as float64
|
|
227
|
+
expect(() => parseFloat('fb3ff0000000000000', { validateCanonical: true }))
|
|
228
|
+
.toThrow(/canonical.*float16\/float32/i)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should reject non-canonical NaN encodings for float32/float64', () => {
|
|
232
|
+
const { parseFloat } = useCborFloat()
|
|
233
|
+
|
|
234
|
+
// Float16 NaN with non-canonical payload
|
|
235
|
+
expect(() => parseFloat('f97e01', { validateCanonical: true }))
|
|
236
|
+
.toThrow(/nan/i)
|
|
237
|
+
|
|
238
|
+
// Float32 NaN
|
|
239
|
+
expect(() => parseFloat('fa7fc00000', { validateCanonical: true }))
|
|
240
|
+
.toThrow(/nan/i)
|
|
241
|
+
|
|
242
|
+
// Float64 NaN
|
|
243
|
+
expect(() => parseFloat('fb7ff8000000000001', { validateCanonical: true }))
|
|
244
|
+
.toThrow(/nan/i)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should accept float16 when canonical validation is enabled', () => {
|
|
248
|
+
const { parseFloat } = useCborFloat()
|
|
249
|
+
|
|
250
|
+
const result = parseFloat('f93e00', { validateCanonical: true })
|
|
251
|
+
expect(result.value).toBe(1.5)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
214
255
|
describe('Wrong Major Type in parse()', () => {
|
|
215
256
|
it('should throw error when auto-detecting with wrong major type', () => {
|
|
216
257
|
const { parse } = useCborFloat()
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { describe, it, expect } from 'vitest'
|
|
15
15
|
import { useCborParser } from '../composables/useCborParser'
|
|
16
16
|
import { useCborCollection } from '../composables/useCborCollection'
|
|
17
|
+
import type { CborMap } from '../types'
|
|
17
18
|
|
|
18
19
|
describe('CBOR Map Duplicate Key Detection', () => {
|
|
19
20
|
describe('String Keys - Duplicate Detection', () => {
|
|
@@ -154,7 +155,7 @@ describe('CBOR Map Duplicate Key Detection', () => {
|
|
|
154
155
|
|
|
155
156
|
const result = parseMap(uniqueBytes, { dupMapKeyMode: 'reject' })
|
|
156
157
|
// Byte strings as keys become comma-separated strings in JS
|
|
157
|
-
expect(result.value.size).toBe(3)
|
|
158
|
+
expect((result.value as CborMap).size).toBe(3)
|
|
158
159
|
})
|
|
159
160
|
|
|
160
161
|
it('should reject duplicate empty byte strings', () => {
|
|
@@ -169,17 +170,16 @@ describe('CBOR Map Duplicate Key Detection', () => {
|
|
|
169
170
|
})
|
|
170
171
|
|
|
171
172
|
describe('Mixed Type Keys - Duplicate Detection', () => {
|
|
172
|
-
it('should
|
|
173
|
+
it('should allow distinct CBOR keys even if they stringify similarly', () => {
|
|
173
174
|
const { parseMap } = useCborCollection()
|
|
174
175
|
|
|
175
176
|
// Map: {1: 10, "1": 20} - different types in CBOR (int vs string)
|
|
176
177
|
// a2 (map of 2) + 01 (key:1 integer) + 0a (val:10) + 6131 (key:"1" string) + 14 (val:20)
|
|
177
|
-
//
|
|
178
|
-
// This is correctly rejected to prevent unexpected behavior in Cardano applications
|
|
178
|
+
// Keys are distinct at the CBOR level and should not be treated as duplicates
|
|
179
179
|
const mixedTypes = 'a2010a613114'
|
|
180
180
|
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
const result = parseMap(mixedTypes, { dupMapKeyMode: 'reject' })
|
|
182
|
+
expect((result.value as CborMap).size).toBe(2)
|
|
183
183
|
})
|
|
184
184
|
|
|
185
185
|
it('should detect duplicates when both are strings', () => {
|
|
@@ -333,7 +333,7 @@ describe('CBOR Map Duplicate Key Detection', () => {
|
|
|
333
333
|
limits: { maxMapSize: 100 }
|
|
334
334
|
})
|
|
335
335
|
|
|
336
|
-
expect(result.value.size).toBe(30)
|
|
336
|
+
expect((result.value as CborMap).size).toBe(30)
|
|
337
337
|
})
|
|
338
338
|
})
|
|
339
339
|
|
|
@@ -377,6 +377,96 @@ describe('CBOR Map Duplicate Key Detection', () => {
|
|
|
377
377
|
})
|
|
378
378
|
})
|
|
379
379
|
|
|
380
|
+
describe('Semantic Duplicate Detection (RFC 8949 Section 5.6)', () => {
|
|
381
|
+
it('should reject integer 1 encoded as 0x01 and 0x1801 as duplicate keys', () => {
|
|
382
|
+
const { parseMap } = useCborCollection()
|
|
383
|
+
|
|
384
|
+
// Map with 2 entries, key 1 (0x01) -> value 10 (0x0a), key 1 (0x1801) -> value 20 (0x14)
|
|
385
|
+
// 0x01 encodes integer 1 directly (AI=1)
|
|
386
|
+
// 0x1801 encodes integer 1 with 1-byte payload (AI=24, payload=0x01)
|
|
387
|
+
// Both represent semantic value 1 -- must be detected as duplicate
|
|
388
|
+
// a2 = map(2), 01 = int(1), 0a = int(10), 1801 = int(1), 14 = int(20)
|
|
389
|
+
const duplicateHex = 'a2010a180114'
|
|
390
|
+
|
|
391
|
+
expect(() => parseMap(duplicateHex, { dupMapKeyMode: 'reject' }))
|
|
392
|
+
.toThrow(/duplicate/i)
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
it('should reject integer 1 encoded as 0x01 and 0x190001 as duplicate keys', () => {
|
|
396
|
+
const { parseMap } = useCborCollection()
|
|
397
|
+
|
|
398
|
+
// 0x01 encodes integer 1 directly (AI=1)
|
|
399
|
+
// 0x190001 encodes integer 1 with 2-byte payload (AI=25, payload=0x0001)
|
|
400
|
+
// Both represent semantic value 1
|
|
401
|
+
// a2 = map(2), 01 = int(1), 0a = int(10), 190001 = int(1), 14 = int(20)
|
|
402
|
+
const duplicateHex = 'a2010a19000114'
|
|
403
|
+
|
|
404
|
+
expect(() => parseMap(duplicateHex, { dupMapKeyMode: 'reject' }))
|
|
405
|
+
.toThrow(/duplicate/i)
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('should reject integer 1 encoded as 0x1801 and 0x190001 as duplicate keys', () => {
|
|
409
|
+
const { parseMap } = useCborCollection()
|
|
410
|
+
|
|
411
|
+
// 0x1801 encodes integer 1 with 1-byte payload (AI=24, payload=0x01)
|
|
412
|
+
// 0x190001 encodes integer 1 with 2-byte payload (AI=25, payload=0x0001)
|
|
413
|
+
// Both represent semantic value 1
|
|
414
|
+
// a2 = map(2), 1801 = int(1), 0a = int(10), 190001 = int(1), 14 = int(20)
|
|
415
|
+
const duplicateHex = 'a218010a19000114'
|
|
416
|
+
|
|
417
|
+
expect(() => parseMap(duplicateHex, { dupMapKeyMode: 'reject' }))
|
|
418
|
+
.toThrow(/duplicate/i)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('should reject integer 0 encoded with different byte widths as duplicate keys', () => {
|
|
422
|
+
const { parseMap } = useCborCollection()
|
|
423
|
+
|
|
424
|
+
// 0x00 encodes integer 0 directly (AI=0)
|
|
425
|
+
// 0x1800 encodes integer 0 with 1-byte payload (AI=24, payload=0x00)
|
|
426
|
+
// Both represent semantic value 0
|
|
427
|
+
// a2 = map(2), 00 = int(0), 01 = int(1), 1800 = int(0), 02 = int(2)
|
|
428
|
+
const duplicateHex = 'a200011800 02'
|
|
429
|
+
|
|
430
|
+
expect(() => parseMap(duplicateHex, { dupMapKeyMode: 'reject' }))
|
|
431
|
+
.toThrow(/duplicate/i)
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
it('should still allow semantically different integer keys', () => {
|
|
435
|
+
const { parseMap } = useCborCollection()
|
|
436
|
+
|
|
437
|
+
// Map: {1: 10, 2: 20} - different semantic values, even if both use non-canonical encoding
|
|
438
|
+
// a2 = map(2), 1801 = int(1), 0a = int(10), 1802 = int(2), 14 = int(20)
|
|
439
|
+
const uniqueHex = 'a218010a180214'
|
|
440
|
+
|
|
441
|
+
const result = parseMap(uniqueHex, { dupMapKeyMode: 'reject' })
|
|
442
|
+
expect((result.value as CborMap).size).toBe(2)
|
|
443
|
+
})
|
|
444
|
+
|
|
445
|
+
it('should detect semantic duplicates via parseWithSourceMap path', () => {
|
|
446
|
+
const { parseWithSourceMap } = useCborParser()
|
|
447
|
+
|
|
448
|
+
// Map with key 1 (0x01) and key 1 (0x1801) -- semantic duplicate
|
|
449
|
+
// a2 = map(2), 01 = int(1), 0a = int(10), 1801 = int(1), 14 = int(20)
|
|
450
|
+
const duplicateHex = 'a2010a180114'
|
|
451
|
+
|
|
452
|
+
expect(() => parseWithSourceMap(duplicateHex, { dupMapKeyMode: 'reject' }))
|
|
453
|
+
.toThrow(/duplicate/i)
|
|
454
|
+
})
|
|
455
|
+
|
|
456
|
+
it('should detect semantic duplicates in indefinite-length maps', () => {
|
|
457
|
+
const { parseMap } = useCborCollection()
|
|
458
|
+
|
|
459
|
+
// Indefinite map: {_ 1: 10, 1(non-canonical): 20 }
|
|
460
|
+
// bf = map(indefinite), 01 = int(1), 0a = int(10), 1801 = int(1), 14 = int(20), ff = break
|
|
461
|
+
const duplicateHex = 'bf010a180114ff'
|
|
462
|
+
|
|
463
|
+
expect(() => parseMap(duplicateHex, {
|
|
464
|
+
dupMapKeyMode: 'reject',
|
|
465
|
+
allowIndefinite: true
|
|
466
|
+
})).toThrow(/duplicate/i)
|
|
467
|
+
})
|
|
468
|
+
})
|
|
469
|
+
|
|
380
470
|
describe('Default Behavior', () => {
|
|
381
471
|
it('should NOT reject duplicates by default (lenient mode)', () => {
|
|
382
472
|
const { parseMap } = useCborCollection()
|