@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.
- package/CHANGELOG.md +64 -0
- package/LICENSE +674 -0
- package/README.md +345 -0
- package/dist/chunk-2FUTHZQQ.cjs +755 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
- package/dist/chunk-2HBCILJS.cjs +2034 -0
- package/dist/chunk-2HBCILJS.cjs.map +1 -0
- package/dist/chunk-7CFYWHS6.js +742 -0
- package/dist/chunk-7CFYWHS6.js.map +1 -0
- package/dist/chunk-PD72MVTX.cjs +160 -0
- package/dist/chunk-PD72MVTX.cjs.map +1 -0
- package/dist/chunk-ZDZ2B5PE.js +149 -0
- package/dist/chunk-ZDZ2B5PE.js.map +1 -0
- package/dist/chunk-ZRPJUEIZ.js +2020 -0
- package/dist/chunk-ZRPJUEIZ.js.map +1 -0
- package/dist/encoder/index.cjs +57 -0
- package/dist/encoder/index.cjs.map +1 -0
- package/dist/encoder/index.d.cts +72 -0
- package/dist/encoder/index.d.ts +72 -0
- package/dist/encoder/index.js +4 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/parser/index.cjs +85 -0
- package/dist/parser/index.cjs.map +1 -0
- package/dist/parser/index.d.cts +72 -0
- package/dist/parser/index.d.ts +72 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/types-DvNlfbKB.d.cts +301 -0
- package/dist/types-DvNlfbKB.d.ts +301 -0
- package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
- package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
- package/dist/useCborTag-B_iaShG6.d.ts +142 -0
- package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
- package/package.json +102 -0
- package/src/__tests__/public-api.test.ts +326 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
- package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
- package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
- package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
- package/src/encoder/composables/useCborEncoder.ts +203 -0
- package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
- package/src/encoder/composables/useCborStringEncoder.ts +266 -0
- package/src/encoder/composables/useCborTagEncoder.ts +158 -0
- package/src/encoder/index.ts +35 -0
- package/src/encoder/types.ts +88 -0
- package/src/encoder/utils.ts +80 -0
- package/src/index.ts +434 -0
- package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
- package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
- package/src/parser/__tests__/cbor-collection.test.ts +369 -0
- package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
- package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
- package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
- package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
- package/src/parser/__tests__/cbor-float.test.ts +502 -0
- package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
- package/src/parser/__tests__/cbor-integer.test.ts +200 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
- package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
- package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
- package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
- package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
- package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
- package/src/parser/__tests__/cbor-string.test.ts +224 -0
- package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
- package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
- package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
- package/src/parser/__tests__/cbor-tag.test.ts +684 -0
- package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
- package/src/parser/__tests__/pathBuilder.test.ts +256 -0
- package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
- package/src/parser/__tests__/security-limits.test.ts +248 -0
- package/src/parser/__tests__/utils-errors.test.ts +127 -0
- package/src/parser/composables/useCborCollection.ts +509 -0
- package/src/parser/composables/useCborDiagnostic.ts +381 -0
- package/src/parser/composables/useCborFloat.ts +256 -0
- package/src/parser/composables/useCborInteger.ts +114 -0
- package/src/parser/composables/useCborParser.ts +951 -0
- package/src/parser/composables/useCborString.ts +330 -0
- package/src/parser/composables/useCborStringTypes.ts +129 -0
- package/src/parser/composables/useCborTag.ts +739 -0
- package/src/parser/index.ts +56 -0
- package/src/parser/types.ts +371 -0
- package/src/parser/utils/pathBuilder.ts +259 -0
- package/src/parser/utils.ts +398 -0
- package/src/utils/__tests__/logger.test.ts +186 -0
- package/src/utils/logger.ts +96 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { useCborParser } from '../composables/useCborParser'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AST Tree Structure Tests
|
|
6
|
+
*
|
|
7
|
+
* These tests verify that the source map produces proper AST structure where:
|
|
8
|
+
* - Each node shows only its header bytes (type + length encoding)
|
|
9
|
+
* - Content is separated from headers
|
|
10
|
+
* - Nested structures are properly represented
|
|
11
|
+
*/
|
|
12
|
+
describe('AST Tree Structure', () => {
|
|
13
|
+
const { parseWithSourceMap } = useCborParser()
|
|
14
|
+
|
|
15
|
+
describe('Byte String AST Structure', () => {
|
|
16
|
+
it('should split byte string into header and content', () => {
|
|
17
|
+
// 54 = bytes(20)
|
|
18
|
+
// 736c6f6fc9942073e1b4892073e1b489c9a5ca87 = 20 bytes of data
|
|
19
|
+
const hex = '54736c6f6fc9942073e1b4892073e1b489c9a5ca87'
|
|
20
|
+
const result = parseWithSourceMap(hex)
|
|
21
|
+
|
|
22
|
+
expect(result.sourceMap).toHaveLength(2)
|
|
23
|
+
|
|
24
|
+
// Header entry (container)
|
|
25
|
+
const headerEntry = result.sourceMap[0]
|
|
26
|
+
expect(headerEntry.type).toBe('bytes(20)')
|
|
27
|
+
expect(headerEntry.start).toBe(0)
|
|
28
|
+
expect(headerEntry.end).toBe(1) // Only the "54" byte
|
|
29
|
+
expect(headerEntry.isHeader).toBe(true)
|
|
30
|
+
expect(headerEntry.headerEnd).toBe(1)
|
|
31
|
+
expect(headerEntry.contentPath).toBe('#content')
|
|
32
|
+
expect(headerEntry.children).toEqual(['#content']) // Header is a container
|
|
33
|
+
|
|
34
|
+
// Content entry (child of header)
|
|
35
|
+
const contentEntry = result.sourceMap[1]
|
|
36
|
+
expect(contentEntry.type).toBe('→ 20 bytes')
|
|
37
|
+
expect(contentEntry.start).toBe(1)
|
|
38
|
+
expect(contentEntry.end).toBe(21) // The 20 data bytes
|
|
39
|
+
expect(contentEntry.isContent).toBe(true)
|
|
40
|
+
expect(contentEntry.parent).toBe('') // Parent is the header
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should handle empty byte string', () => {
|
|
44
|
+
// 40 = bytes(0)
|
|
45
|
+
const hex = '40'
|
|
46
|
+
const result = parseWithSourceMap(hex)
|
|
47
|
+
|
|
48
|
+
expect(result.sourceMap).toHaveLength(1)
|
|
49
|
+
|
|
50
|
+
// Only header, no content
|
|
51
|
+
const headerEntry = result.sourceMap[0]
|
|
52
|
+
expect(headerEntry.type).toBe('bytes(0)')
|
|
53
|
+
expect(headerEntry.start).toBe(0)
|
|
54
|
+
expect(headerEntry.end).toBe(1)
|
|
55
|
+
expect(headerEntry.isHeader).toBe(true)
|
|
56
|
+
expect(headerEntry.contentPath).toBeUndefined()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('Text String AST Structure', () => {
|
|
61
|
+
it('should split text string into header and content', () => {
|
|
62
|
+
// 64 = text(4)
|
|
63
|
+
// 49455446 = "IETF"
|
|
64
|
+
const hex = '6449455446'
|
|
65
|
+
const result = parseWithSourceMap(hex)
|
|
66
|
+
|
|
67
|
+
expect(result.sourceMap).toHaveLength(2)
|
|
68
|
+
|
|
69
|
+
// Header entry (container)
|
|
70
|
+
const headerEntry = result.sourceMap[0]
|
|
71
|
+
expect(headerEntry.type).toBe('text(4)')
|
|
72
|
+
expect(headerEntry.start).toBe(0)
|
|
73
|
+
expect(headerEntry.end).toBe(1) // Only the "64" byte
|
|
74
|
+
expect(headerEntry.isHeader).toBe(true)
|
|
75
|
+
expect(headerEntry.headerEnd).toBe(1)
|
|
76
|
+
expect(headerEntry.children).toEqual(['#content']) // Header is a container
|
|
77
|
+
|
|
78
|
+
// Content entry (child of header)
|
|
79
|
+
const contentEntry = result.sourceMap[1]
|
|
80
|
+
expect(contentEntry.type).toBe('→ "IETF"')
|
|
81
|
+
expect(contentEntry.start).toBe(1)
|
|
82
|
+
expect(contentEntry.end).toBe(5) // The 4 text bytes
|
|
83
|
+
expect(contentEntry.isContent).toBe(true)
|
|
84
|
+
expect(contentEntry.parent).toBe('') // Parent is the header
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('Array AST Structure', () => {
|
|
89
|
+
it('should show array header separate from elements', () => {
|
|
90
|
+
// 82 = array(2)
|
|
91
|
+
// 01 = int 1
|
|
92
|
+
// 02 = int 2
|
|
93
|
+
const hex = '820102'
|
|
94
|
+
const result = parseWithSourceMap(hex)
|
|
95
|
+
|
|
96
|
+
// Should have: array header, element[0], element[1]
|
|
97
|
+
expect(result.sourceMap).toHaveLength(3)
|
|
98
|
+
|
|
99
|
+
// Array header
|
|
100
|
+
const arrayEntry = result.sourceMap[0]
|
|
101
|
+
expect(arrayEntry.type).toBe('array(2)')
|
|
102
|
+
expect(arrayEntry.start).toBe(0)
|
|
103
|
+
expect(arrayEntry.end).toBe(1) // Only the "82" byte
|
|
104
|
+
expect(arrayEntry.isHeader).toBe(true)
|
|
105
|
+
expect(arrayEntry.headerEnd).toBe(1)
|
|
106
|
+
expect(arrayEntry.children).toEqual(['[0]', '[1]'])
|
|
107
|
+
|
|
108
|
+
// First element
|
|
109
|
+
const elem0 = result.sourceMap[1]
|
|
110
|
+
expect(elem0.path).toBe('[0]')
|
|
111
|
+
expect(elem0.start).toBe(1)
|
|
112
|
+
expect(elem0.end).toBe(2) // The "01" byte
|
|
113
|
+
expect(elem0.parent).toBe('')
|
|
114
|
+
|
|
115
|
+
// Second element
|
|
116
|
+
const elem1 = result.sourceMap[2]
|
|
117
|
+
expect(elem1.path).toBe('[1]')
|
|
118
|
+
expect(elem1.start).toBe(2)
|
|
119
|
+
expect(elem1.end).toBe(3) // The "02" byte
|
|
120
|
+
expect(elem1.parent).toBe('')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('should handle nested arrays with proper AST structure', () => {
|
|
124
|
+
// 82 = array(2)
|
|
125
|
+
// 82 = array(2)
|
|
126
|
+
// 58 20 = bytes(32)
|
|
127
|
+
// 00 = int 0
|
|
128
|
+
// 01 = int 1
|
|
129
|
+
const hex = '8282582048bd01d51e580cde15afa6d28f63d89c9137b93a910e5941192e26b129061067000001'
|
|
130
|
+
const result = parseWithSourceMap(hex)
|
|
131
|
+
|
|
132
|
+
// Outer array header
|
|
133
|
+
const outerArray = result.sourceMap[0]
|
|
134
|
+
expect(outerArray.type).toBe('array(2)')
|
|
135
|
+
expect(outerArray.start).toBe(0)
|
|
136
|
+
expect(outerArray.end).toBe(1) // Only first "82"
|
|
137
|
+
expect(outerArray.isHeader).toBe(true)
|
|
138
|
+
|
|
139
|
+
// Inner array header (first element of outer array)
|
|
140
|
+
const innerArray = result.sourceMap.find(e => e.path === '[0]' && e.type.startsWith('array'))
|
|
141
|
+
expect(innerArray).toBeDefined()
|
|
142
|
+
expect(innerArray!.type).toBe('array(2)')
|
|
143
|
+
expect(innerArray!.start).toBe(1)
|
|
144
|
+
expect(innerArray!.end).toBe(2) // Only second "82"
|
|
145
|
+
expect(innerArray!.isHeader).toBe(true)
|
|
146
|
+
expect(innerArray!.children).toEqual(['[0][0]', '[0][1]'])
|
|
147
|
+
|
|
148
|
+
// Byte string header inside inner array
|
|
149
|
+
const bytesHeader = result.sourceMap.find(e => e.path === '[0][0]' && e.isHeader === true)
|
|
150
|
+
expect(bytesHeader).toBeDefined()
|
|
151
|
+
expect(bytesHeader!.type).toBe('bytes(32)')
|
|
152
|
+
expect(bytesHeader!.start).toBe(2)
|
|
153
|
+
expect(bytesHeader!.end).toBe(4) // "58 20"
|
|
154
|
+
expect(bytesHeader!.isHeader).toBe(true)
|
|
155
|
+
|
|
156
|
+
// Byte string content
|
|
157
|
+
const bytesContent = result.sourceMap.find(e => e.path === '[0][0]#content')
|
|
158
|
+
expect(bytesContent).toBeDefined()
|
|
159
|
+
expect(bytesContent!.start).toBe(4)
|
|
160
|
+
expect(bytesContent!.end).toBe(36) // 32 bytes of data
|
|
161
|
+
expect(bytesContent!.isContent).toBe(true)
|
|
162
|
+
|
|
163
|
+
// Integer inside inner array
|
|
164
|
+
const intEntry = result.sourceMap.find(e => e.path === '[0][1]')
|
|
165
|
+
expect(intEntry).toBeDefined()
|
|
166
|
+
expect(intEntry!.start).toBe(36)
|
|
167
|
+
expect(intEntry!.end).toBe(37) // The "00" byte
|
|
168
|
+
|
|
169
|
+
// Second element of outer array
|
|
170
|
+
const secondElem = result.sourceMap.find(e => e.path === '[1]' && !e.type.startsWith('array'))
|
|
171
|
+
expect(secondElem).toBeDefined()
|
|
172
|
+
expect(secondElem!.start).toBe(37)
|
|
173
|
+
expect(secondElem!.end).toBe(38) // The "01" byte
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('Map AST Structure', () => {
|
|
178
|
+
it('should show map header separate from key-value pairs', () => {
|
|
179
|
+
// a1 = map(1)
|
|
180
|
+
// 61 61 = text(1) "a"
|
|
181
|
+
// 01 = int 1
|
|
182
|
+
const hex = 'a16161 01'
|
|
183
|
+
const result = parseWithSourceMap(hex.replace(/\s/g, ''))
|
|
184
|
+
|
|
185
|
+
// Map header
|
|
186
|
+
const mapEntry = result.sourceMap[0]
|
|
187
|
+
expect(mapEntry.type).toBe('map(1)')
|
|
188
|
+
expect(mapEntry.start).toBe(0)
|
|
189
|
+
expect(mapEntry.end).toBe(1) // Only the "a1" byte
|
|
190
|
+
expect(mapEntry.isHeader).toBe(true)
|
|
191
|
+
expect(mapEntry.headerEnd).toBe(1)
|
|
192
|
+
|
|
193
|
+
// Should have children pointing to values (not keys)
|
|
194
|
+
expect(mapEntry.children).toBeDefined()
|
|
195
|
+
expect(mapEntry.children!.length).toBe(1)
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
describe('Tag AST Structure', () => {
|
|
200
|
+
it('should show tag header separate from tagged value', () => {
|
|
201
|
+
// c2 = tag(2) - bignum
|
|
202
|
+
// 41 ff = bytes(1) with value 0xff
|
|
203
|
+
const hex = 'c241ff'
|
|
204
|
+
const result = parseWithSourceMap(hex)
|
|
205
|
+
|
|
206
|
+
// Tag header
|
|
207
|
+
const tagEntry = result.sourceMap[0]
|
|
208
|
+
expect(tagEntry.type).toBe('tag(2)')
|
|
209
|
+
expect(tagEntry.start).toBe(0)
|
|
210
|
+
expect(tagEntry.end).toBe(1) // Only the "c2" byte
|
|
211
|
+
expect(tagEntry.isHeader).toBe(true)
|
|
212
|
+
expect(tagEntry.headerEnd).toBe(1)
|
|
213
|
+
expect(tagEntry.children).toEqual(['.value'])
|
|
214
|
+
|
|
215
|
+
// Tagged value - bytes header
|
|
216
|
+
const bytesHeader = result.sourceMap.find(e => e.path === '.value' && e.isHeader === true)
|
|
217
|
+
expect(bytesHeader).toBeDefined()
|
|
218
|
+
expect(bytesHeader!.type).toBe('bytes(1)')
|
|
219
|
+
expect(bytesHeader!.start).toBe(1)
|
|
220
|
+
expect(bytesHeader!.end).toBe(2) // The "41" byte
|
|
221
|
+
expect(bytesHeader!.parent).toBe('')
|
|
222
|
+
|
|
223
|
+
// Bytes content
|
|
224
|
+
const bytesContent = result.sourceMap.find(e => e.path === '.value#content')
|
|
225
|
+
expect(bytesContent).toBeDefined()
|
|
226
|
+
expect(bytesContent!.start).toBe(2)
|
|
227
|
+
expect(bytesContent!.end).toBe(3) // The "ff" byte
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should handle tag 258 (Cardano set) with proper AST', () => {
|
|
231
|
+
// d9 0102 = tag(258)
|
|
232
|
+
// 81 = array(1)
|
|
233
|
+
// 01 = int 1
|
|
234
|
+
const hex = 'd901028101'
|
|
235
|
+
const result = parseWithSourceMap(hex)
|
|
236
|
+
|
|
237
|
+
// Tag header
|
|
238
|
+
const tagEntry = result.sourceMap[0]
|
|
239
|
+
expect(tagEntry.type).toBe('tag(258)')
|
|
240
|
+
expect(tagEntry.start).toBe(0)
|
|
241
|
+
expect(tagEntry.end).toBe(3) // "d9 01 02"
|
|
242
|
+
expect(tagEntry.isHeader).toBe(true)
|
|
243
|
+
expect(tagEntry.headerEnd).toBe(3)
|
|
244
|
+
|
|
245
|
+
// Array header (tagged value)
|
|
246
|
+
const arrayEntry = result.sourceMap.find(e => e.path === '.value' && e.type === 'array(1)')
|
|
247
|
+
expect(arrayEntry).toBeDefined()
|
|
248
|
+
expect(arrayEntry!.start).toBe(3)
|
|
249
|
+
expect(arrayEntry!.end).toBe(4) // Only the "81" byte
|
|
250
|
+
expect(arrayEntry!.isHeader).toBe(true)
|
|
251
|
+
expect(arrayEntry!.parent).toBe('')
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
describe('Complex Cardano Transaction Example', () => {
|
|
256
|
+
it('should produce proper AST for real Cardano UTXO', () => {
|
|
257
|
+
// This is the user's example from the conversation:
|
|
258
|
+
// 82 82 58 20 48bd... 00 82 58 39 00dae... 1a 000f4240
|
|
259
|
+
const hex = '8282582048bd01d51e580cde15afa6d28f63d89c9137b93a910e5941192e26b12906106700825839000dae074cac48222800da644971a35b68832abb40b619643efde77dc9db8ec58f9fa297093e286f81d37bea7154209064956254d5d4e2108d1a000f4240'
|
|
260
|
+
const result = parseWithSourceMap(hex)
|
|
261
|
+
|
|
262
|
+
// Outer array: 82 at offset 0
|
|
263
|
+
const outerArray = result.sourceMap[0]
|
|
264
|
+
expect(outerArray.type).toBe('array(2)')
|
|
265
|
+
expect(outerArray.start).toBe(0)
|
|
266
|
+
expect(outerArray.end).toBe(1) // Only first "82"
|
|
267
|
+
expect(outerArray.isHeader).toBe(true)
|
|
268
|
+
|
|
269
|
+
// First inner array: 82 at offset 1
|
|
270
|
+
const firstInnerArray = result.sourceMap.find(e => e.path === '[0]' && e.type === 'array(2)')
|
|
271
|
+
expect(firstInnerArray).toBeDefined()
|
|
272
|
+
expect(firstInnerArray!.start).toBe(1)
|
|
273
|
+
expect(firstInnerArray!.end).toBe(2) // Only second "82"
|
|
274
|
+
expect(firstInnerArray!.isHeader).toBe(true)
|
|
275
|
+
|
|
276
|
+
// Byte string header: 58 20 at offset 2-3
|
|
277
|
+
const bytesHeader = result.sourceMap.find(e => e.path === '[0][0]' && e.isHeader === true)
|
|
278
|
+
expect(bytesHeader).toBeDefined()
|
|
279
|
+
expect(bytesHeader!.type).toBe('bytes(32)')
|
|
280
|
+
expect(bytesHeader!.start).toBe(2)
|
|
281
|
+
expect(bytesHeader!.end).toBe(4) // "58 20"
|
|
282
|
+
expect(bytesHeader!.isHeader).toBe(true)
|
|
283
|
+
|
|
284
|
+
// Byte string content: 32 bytes starting at offset 4
|
|
285
|
+
const bytesContent = result.sourceMap.find(e => e.path === '[0][0]#content')
|
|
286
|
+
expect(bytesContent).toBeDefined()
|
|
287
|
+
expect(bytesContent!.start).toBe(4)
|
|
288
|
+
expect(bytesContent!.end).toBe(36) // 32 bytes
|
|
289
|
+
expect(bytesContent!.isContent).toBe(true)
|
|
290
|
+
|
|
291
|
+
// Integer: 00 at offset 36
|
|
292
|
+
const intEntry = result.sourceMap.find(e => e.path === '[0][1]')
|
|
293
|
+
expect(intEntry).toBeDefined()
|
|
294
|
+
expect(intEntry!.start).toBe(36)
|
|
295
|
+
expect(intEntry!.end).toBe(37)
|
|
296
|
+
|
|
297
|
+
// Second inner array: 82 at offset 37
|
|
298
|
+
const secondInnerArray = result.sourceMap.find(e => e.path === '[1]' && e.type === 'array(2)')
|
|
299
|
+
expect(secondInnerArray).toBeDefined()
|
|
300
|
+
expect(secondInnerArray!.start).toBe(37)
|
|
301
|
+
expect(secondInnerArray!.end).toBe(38) // Only the "82" byte
|
|
302
|
+
expect(secondInnerArray!.isHeader).toBe(true)
|
|
303
|
+
|
|
304
|
+
// Verify proper parent-child relationships
|
|
305
|
+
expect(firstInnerArray!.parent).toBe('')
|
|
306
|
+
expect(secondInnerArray!.parent).toBe('')
|
|
307
|
+
expect(bytesHeader!.parent).toBe('[0]')
|
|
308
|
+
expect(bytesContent!.parent).toBe('[0][0]')
|
|
309
|
+
})
|
|
310
|
+
})
|
|
311
|
+
})
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Collection Parser Error Handling Tests
|
|
3
|
+
* Tests all error cases and edge cases for 100% code coverage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useCborCollection } from '../composables/useCborCollection'
|
|
8
|
+
|
|
9
|
+
describe('useCborCollection - Error Handling', () => {
|
|
10
|
+
describe('parseItem - Unexpected End of Buffer', () => {
|
|
11
|
+
it('should throw error when offset is at end of buffer', () => {
|
|
12
|
+
const { parseArray } = useCborCollection()
|
|
13
|
+
|
|
14
|
+
// Array with 2 elements, but only 1 element provided
|
|
15
|
+
// 82 (array of 2) + 01 (element 1) - missing element 2
|
|
16
|
+
expect(() => parseArray('8201')).toThrow('Unexpected end of buffer')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should throw error when buffer ends mid-element', () => {
|
|
20
|
+
const { parseArray } = useCborCollection()
|
|
21
|
+
|
|
22
|
+
// Array with nested array that's incomplete
|
|
23
|
+
// 82 (array of 2) + 01 (element 1) + 82 (nested array of 2, incomplete)
|
|
24
|
+
expect(() => parseArray('820182')).toThrow('Unexpected end of buffer')
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
describe('parseItem - Tag Parsing Support', () => {
|
|
29
|
+
it('should successfully parse array containing a tag', () => {
|
|
30
|
+
const { parseArray } = useCborCollection()
|
|
31
|
+
|
|
32
|
+
// Array containing a tag (now properly supported)
|
|
33
|
+
// 81 (array of 1) + c0 (tag 0) + 00 (value)
|
|
34
|
+
const result = parseArray('81c000')
|
|
35
|
+
expect(result.value).toBeInstanceOf(Array)
|
|
36
|
+
expect(result.value).toHaveLength(1)
|
|
37
|
+
expect(result.value[0].tag).toBe(0)
|
|
38
|
+
expect(result.value[0].value).toBe(0)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should successfully parse map with tag as value', () => {
|
|
42
|
+
const { parseMap } = useCborCollection()
|
|
43
|
+
|
|
44
|
+
// Map with tag as value (now properly supported)
|
|
45
|
+
// a1 (map of 1) + 01 (key) + c0 (tag 0) + 00 (value)
|
|
46
|
+
const result = parseMap('a101c000')
|
|
47
|
+
expect(result.value.get(1)).toBeDefined()
|
|
48
|
+
expect(result.value.get(1).tag).toBe(0)
|
|
49
|
+
expect(result.value.get(1).value).toBe(0)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('parseLength - All Encoding Sizes for Collections', () => {
|
|
54
|
+
it('should parse array with 4-byte length (AI 26)', () => {
|
|
55
|
+
const { parseArray } = useCborCollection()
|
|
56
|
+
|
|
57
|
+
// Array with 1000 elements (using 4-byte length)
|
|
58
|
+
// 9a (MT 4, AI 26) + 000003e8 (1000 in 4 bytes) + 1000x 00 (zeros)
|
|
59
|
+
const elements = '00'.repeat(1000)
|
|
60
|
+
const result = parseArray('9a000003e8' + elements)
|
|
61
|
+
expect(result.value.length).toBe(1000)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('should parse array with 8-byte length (AI 27)', () => {
|
|
65
|
+
const { parseArray } = useCborCollection()
|
|
66
|
+
|
|
67
|
+
// Array with 100 elements (using 8-byte length)
|
|
68
|
+
// 9b (MT 4, AI 27) + 0000000000000064 (100 in 8 bytes) + 100x 00
|
|
69
|
+
const elements = '00'.repeat(100)
|
|
70
|
+
const result = parseArray('9b0000000000000064' + elements)
|
|
71
|
+
expect(result.value.length).toBe(100)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should parse map with 4-byte length (AI 26)', () => {
|
|
75
|
+
const { parseMap } = useCborCollection()
|
|
76
|
+
|
|
77
|
+
// Map with 100 entries (using 4-byte length)
|
|
78
|
+
// ba (MT 5, AI 26) + 00000064 (100 in 4 bytes) + 100x unique key-value pairs
|
|
79
|
+
// Use 1-byte encoding for keys to avoid reserved AI values (28-31)
|
|
80
|
+
const entries = Array.from({ length: 100 }, (_, i) => {
|
|
81
|
+
const keyHex = '18' + (24 + i).toString(16).padStart(2, '0')
|
|
82
|
+
const valueHex = '18' + (124 + i).toString(16).padStart(2, '0')
|
|
83
|
+
return keyHex + valueHex
|
|
84
|
+
}).join('')
|
|
85
|
+
const result = parseMap('ba00000064' + entries)
|
|
86
|
+
expect(result.value.size).toBe(100)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should parse map with 8-byte length (AI 27)', () => {
|
|
90
|
+
const { parseMap } = useCborCollection()
|
|
91
|
+
|
|
92
|
+
// Map with 50 entries (using 8-byte length)
|
|
93
|
+
// bb (MT 5, AI 27) + 0000000000000032 (50 in 8 bytes) + 50x unique key-value pairs
|
|
94
|
+
const entries = Array.from({ length: 50 }, (_, i) => {
|
|
95
|
+
const keyHex = '18' + (30 + i).toString(16).padStart(2, '0')
|
|
96
|
+
const valueHex = '18' + (80 + i).toString(16).padStart(2, '0')
|
|
97
|
+
return keyHex + valueHex
|
|
98
|
+
}).join('')
|
|
99
|
+
const result = parseMap('bb0000000000000032' + entries)
|
|
100
|
+
expect(result.value.size).toBe(50)
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('parseLength - Invalid Additional Info', () => {
|
|
105
|
+
it('should throw error for array with reserved AI 28', () => {
|
|
106
|
+
const { parseArray } = useCborCollection()
|
|
107
|
+
|
|
108
|
+
// MT 4, AI 28 - reserved
|
|
109
|
+
expect(() => parseArray('9c')).toThrow('Invalid additional info: 28')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should throw error for array with reserved AI 29', () => {
|
|
113
|
+
const { parseArray } = useCborCollection()
|
|
114
|
+
|
|
115
|
+
// MT 4, AI 29 - reserved
|
|
116
|
+
expect(() => parseArray('9d')).toThrow('Invalid additional info: 29')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should throw error for array with reserved AI 30', () => {
|
|
120
|
+
const { parseArray } = useCborCollection()
|
|
121
|
+
|
|
122
|
+
// MT 4, AI 30 - reserved
|
|
123
|
+
expect(() => parseArray('9e')).toThrow('Invalid additional info: 30')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should throw error for map with reserved AI 28', () => {
|
|
127
|
+
const { parseMap } = useCborCollection()
|
|
128
|
+
|
|
129
|
+
// MT 5, AI 28 - reserved
|
|
130
|
+
expect(() => parseMap('bc')).toThrow('Invalid additional info: 28')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('should throw error for map with reserved AI 29', () => {
|
|
134
|
+
const { parseMap } = useCborCollection()
|
|
135
|
+
|
|
136
|
+
// MT 5, AI 29 - reserved
|
|
137
|
+
expect(() => parseMap('bd')).toThrow('Invalid additional info: 29')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should throw error for map with reserved AI 30', () => {
|
|
141
|
+
const { parseMap } = useCborCollection()
|
|
142
|
+
|
|
143
|
+
// MT 5, AI 30 - reserved
|
|
144
|
+
expect(() => parseMap('be')).toThrow('Invalid additional info: 30')
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('parseArray - Wrong Major Type', () => {
|
|
149
|
+
it('should throw error when major type is not 4', () => {
|
|
150
|
+
const { parseArray } = useCborCollection()
|
|
151
|
+
|
|
152
|
+
// MT 0 (integer) is not an array
|
|
153
|
+
expect(() => parseArray('00')).toThrow('Expected major type 4 (array), got 0')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should throw error for MT 5 (map)', () => {
|
|
157
|
+
const { parseArray } = useCborCollection()
|
|
158
|
+
|
|
159
|
+
// MT 5 (map) is not an array
|
|
160
|
+
expect(() => parseArray('a0')).toThrow('Expected major type 4 (array), got 5')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('parseMap - Wrong Major Type', () => {
|
|
165
|
+
it('should throw error when major type is not 5', () => {
|
|
166
|
+
const { parseMap } = useCborCollection()
|
|
167
|
+
|
|
168
|
+
// MT 0 (integer) is not a map
|
|
169
|
+
expect(() => parseMap('00')).toThrow('Expected major type 5 (map), got 0')
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should throw error for MT 4 (array)', () => {
|
|
173
|
+
const { parseMap } = useCborCollection()
|
|
174
|
+
|
|
175
|
+
// MT 4 (array) is not a map
|
|
176
|
+
expect(() => parseMap('80')).toThrow('Expected major type 5 (map), got 4')
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
describe('parseArray - Insufficient Data', () => {
|
|
181
|
+
it('should throw error when array element is missing', () => {
|
|
182
|
+
const { parseArray } = useCborCollection()
|
|
183
|
+
|
|
184
|
+
// Array of 3, but only 2 elements
|
|
185
|
+
expect(() => parseArray('830102')).toThrow('Unexpected end of buffer while parsing array element 2/3')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should throw error when nested array is incomplete', () => {
|
|
189
|
+
const { parseArray } = useCborCollection()
|
|
190
|
+
|
|
191
|
+
// Array with incomplete nested structure
|
|
192
|
+
// 82 (array of 2) + 01 + 8301 (nested array of 3 with only 1 element)
|
|
193
|
+
expect(() => parseArray('8201830102')).toThrow('Unexpected end of buffer while parsing array element 2/3')
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('parseMap - Insufficient Data', () => {
|
|
198
|
+
it('should throw error when map entry is missing', () => {
|
|
199
|
+
const { parseMap } = useCborCollection()
|
|
200
|
+
|
|
201
|
+
// Map of 2, but only 1 entry
|
|
202
|
+
expect(() => parseMap('a20102')).toThrow('Unexpected end of buffer while parsing map entry 1/2')
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('should throw error when map value is missing', () => {
|
|
206
|
+
const { parseMap } = useCborCollection()
|
|
207
|
+
|
|
208
|
+
// Map of 1, with key but no value
|
|
209
|
+
expect(() => parseMap('a101')).toThrow('Unexpected end of buffer while parsing map value for entry 0/1')
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('should throw error in indefinite map when value is missing after key', () => {
|
|
213
|
+
const { parseMap } = useCborCollection()
|
|
214
|
+
|
|
215
|
+
// Indefinite map with key but no value before break
|
|
216
|
+
// bf (indefinite map) + 01 (key) - missing value
|
|
217
|
+
expect(() => parseMap('bf01')).toThrow('Unexpected end of buffer while parsing map value')
|
|
218
|
+
})
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
describe('Indefinite-Length Collections', () => {
|
|
222
|
+
it('should parse indefinite array with proper break', () => {
|
|
223
|
+
const { parseArray } = useCborCollection()
|
|
224
|
+
|
|
225
|
+
// 9f (indefinite array) + 01 02 03 + ff (break)
|
|
226
|
+
const result = parseArray('9f010203ff')
|
|
227
|
+
// Use spread to get clean array without Symbol metadata
|
|
228
|
+
expect([...result.value]).toEqual([1, 2, 3])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('should parse indefinite map with proper break', () => {
|
|
232
|
+
const { parseMap } = useCborCollection()
|
|
233
|
+
|
|
234
|
+
// bf (indefinite map) + 01 02 03 04 + ff (break)
|
|
235
|
+
const result = parseMap('bf01020304ff')
|
|
236
|
+
// Use spread to get clean map without Symbol metadata
|
|
237
|
+
expect(new Map([...result.value])).toEqual(new Map([[1, 2], [3, 4]]))
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should parse nested indefinite structures', () => {
|
|
241
|
+
const { parseArray } = useCborCollection()
|
|
242
|
+
|
|
243
|
+
// 9f (indefinite array) + 9f 01 ff (nested indefinite array [1]) + ff (break)
|
|
244
|
+
const result = parseArray('9f9f01ffff')
|
|
245
|
+
// Use spread to get clean arrays without Symbol metadata
|
|
246
|
+
expect([...result.value].map(inner => [...inner])).toEqual([[1]])
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('Complex Nested Structures', () => {
|
|
251
|
+
it('should parse deeply nested arrays', () => {
|
|
252
|
+
const { parseArray } = useCborCollection()
|
|
253
|
+
|
|
254
|
+
// [[[[1]]]]
|
|
255
|
+
const result = parseArray('81818181 01'.replace(/\s/g, ''))
|
|
256
|
+
expect(result.value).toEqual([[[[1]]]])
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('should parse map with array values', () => {
|
|
260
|
+
const { parseMap } = useCborCollection()
|
|
261
|
+
|
|
262
|
+
// {"a": [1, 2], "b": [3, 4]}
|
|
263
|
+
const result = parseMap('a261618201026162820304')
|
|
264
|
+
expect(result.value).toEqual(new Map([['a', [1, 2]], ['b', [3, 4]]]))
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should parse array with map values', () => {
|
|
268
|
+
const { parseArray } = useCborCollection()
|
|
269
|
+
|
|
270
|
+
// [{"x": 1}, {"y": 2}]
|
|
271
|
+
const result = parseArray('82a16178 01a16179 02'.replace(/\s/g, ''))
|
|
272
|
+
expect(result.value).toEqual([new Map([['x', 1]]), new Map([['y', 2]])])
|
|
273
|
+
})
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
describe('All Major Types in Collections', () => {
|
|
277
|
+
it('should parse array containing all major types', () => {
|
|
278
|
+
const { parseArray } = useCborCollection()
|
|
279
|
+
|
|
280
|
+
// Array with: integer, negative, text string, byte string, array, map, simple
|
|
281
|
+
// [0, -1, "", h'', [], {}, false]
|
|
282
|
+
// 87 (array of 7) + 00 (0) + 20 (-1) + 60 ("") + 40 (h'') + 80 ([]) + a0 ({}) + f4 (false)
|
|
283
|
+
const result = parseArray('8700206040 80a0f4'.replace(/\s/g, ''))
|
|
284
|
+
expect(result.value).toEqual([0, -1, '', new Uint8Array([]), [], new Map(), false])
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('should handle map with various key types', () => {
|
|
288
|
+
const { parseMap } = useCborCollection()
|
|
289
|
+
|
|
290
|
+
// Map with integer and string keys
|
|
291
|
+
// {1: "a", "b": 2}
|
|
292
|
+
const result = parseMap('a2016161616202')
|
|
293
|
+
expect(result.value).toEqual(new Map([[1, 'a'], ['b', 2]]))
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
})
|