@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,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API Tests
|
|
3
|
+
*
|
|
4
|
+
* These tests import from the main index.ts to ensure the public API
|
|
5
|
+
* is properly exported and functional. This also provides coverage
|
|
6
|
+
* for the main entry point.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from 'vitest'
|
|
10
|
+
|
|
11
|
+
// Import everything from the public API
|
|
12
|
+
import {
|
|
13
|
+
// Functional API
|
|
14
|
+
decode,
|
|
15
|
+
decodeWithSourceMap,
|
|
16
|
+
encode,
|
|
17
|
+
encodeToHex,
|
|
18
|
+
encodeToBytes,
|
|
19
|
+
encodeSequence,
|
|
20
|
+
toDiagnostic,
|
|
21
|
+
decodeToDiagnostic,
|
|
22
|
+
// Class API
|
|
23
|
+
CborDecoder,
|
|
24
|
+
CborEncoder,
|
|
25
|
+
// Composables
|
|
26
|
+
useCborParser,
|
|
27
|
+
useCborEncoder,
|
|
28
|
+
useCborInteger,
|
|
29
|
+
useCborString,
|
|
30
|
+
useCborCollection,
|
|
31
|
+
useCborFloat,
|
|
32
|
+
useCborTag,
|
|
33
|
+
useCborDiagnostic,
|
|
34
|
+
useCborIntegerEncoder,
|
|
35
|
+
useCborStringEncoder,
|
|
36
|
+
useCborCollectionEncoder,
|
|
37
|
+
useCborSimpleEncoder,
|
|
38
|
+
// Utilities
|
|
39
|
+
PathBuilder,
|
|
40
|
+
// Constants
|
|
41
|
+
DEFAULT_OPTIONS,
|
|
42
|
+
DEFAULT_LIMITS,
|
|
43
|
+
DEFAULT_ENCODE_OPTIONS,
|
|
44
|
+
CborMajorType,
|
|
45
|
+
CborAdditionalInfo,
|
|
46
|
+
CborSimpleValue,
|
|
47
|
+
CborTag,
|
|
48
|
+
} from '../index'
|
|
49
|
+
|
|
50
|
+
describe('Public API - Functional Decode', () => {
|
|
51
|
+
it('decode() should decode CBOR hex strings', () => {
|
|
52
|
+
// Integer
|
|
53
|
+
const intResult = decode('1864')
|
|
54
|
+
expect(intResult.value).toBe(100)
|
|
55
|
+
expect(intResult.bytesRead).toBe(2)
|
|
56
|
+
|
|
57
|
+
// String
|
|
58
|
+
const strResult = decode('6449455446')
|
|
59
|
+
expect(strResult.value).toBe('IETF')
|
|
60
|
+
|
|
61
|
+
// Array
|
|
62
|
+
const arrResult = decode('83010203')
|
|
63
|
+
expect(arrResult.value).toEqual([1, 2, 3])
|
|
64
|
+
|
|
65
|
+
// Tagged value (Cardano Plutus Constructor 0)
|
|
66
|
+
const tagResult = decode('d87980')
|
|
67
|
+
expect(tagResult.value).toMatchObject({ tag: 121, value: [] })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('decode() should accept options', () => {
|
|
71
|
+
const result = decode('1864', { strict: true })
|
|
72
|
+
expect(result.value).toBe(100)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('decodeWithSourceMap() should return source map', () => {
|
|
76
|
+
const result = decodeWithSourceMap('d87980')
|
|
77
|
+
expect(result.value).toMatchObject({ tag: 121, value: [] })
|
|
78
|
+
expect(result.sourceMap).toBeDefined()
|
|
79
|
+
expect(Array.isArray(result.sourceMap)).toBe(true)
|
|
80
|
+
expect(result.sourceMap.length).toBeGreaterThan(0)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('Public API - Functional Encode', () => {
|
|
85
|
+
it('encode() should encode values to CBOR', () => {
|
|
86
|
+
// Integer
|
|
87
|
+
const intResult = encode(100)
|
|
88
|
+
expect(intResult.hex).toBe('1864')
|
|
89
|
+
expect(intResult.bytes).toBeInstanceOf(Uint8Array)
|
|
90
|
+
|
|
91
|
+
// String
|
|
92
|
+
const strResult = encode('IETF')
|
|
93
|
+
expect(strResult.hex).toBe('6449455446')
|
|
94
|
+
|
|
95
|
+
// Array
|
|
96
|
+
const arrResult = encode([1, 2, 3])
|
|
97
|
+
expect(arrResult.hex).toBe('83010203')
|
|
98
|
+
|
|
99
|
+
// Tagged value
|
|
100
|
+
const tagResult = encode({ tag: 121, value: [] })
|
|
101
|
+
expect(tagResult.hex).toBe('d87980')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('encode() should accept options', () => {
|
|
105
|
+
const result = encode({ z: 1, a: 2 }, { canonical: true })
|
|
106
|
+
expect(result.hex).toBeDefined()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('encodeToHex() should return only hex string', () => {
|
|
110
|
+
const hex = encodeToHex(100)
|
|
111
|
+
expect(hex).toBe('1864')
|
|
112
|
+
expect(typeof hex).toBe('string')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('encodeToBytes() should return only bytes', () => {
|
|
116
|
+
const bytes = encodeToBytes(100)
|
|
117
|
+
expect(bytes).toBeInstanceOf(Uint8Array)
|
|
118
|
+
expect(bytes).toEqual(new Uint8Array([0x18, 0x64]))
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('encodeSequence() should encode multiple values', () => {
|
|
122
|
+
const result = encodeSequence([1, 2, 3])
|
|
123
|
+
expect(result.hex).toBe('010203')
|
|
124
|
+
expect(result.bytes).toEqual(new Uint8Array([0x01, 0x02, 0x03]))
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('Public API - Diagnostic Notation', () => {
|
|
129
|
+
it('toDiagnostic() should convert values to diagnostic notation', () => {
|
|
130
|
+
expect(toDiagnostic(100)).toBe('100')
|
|
131
|
+
expect(toDiagnostic('hello')).toBe('"hello"')
|
|
132
|
+
expect(toDiagnostic([1, 2, 3])).toBe('[1, 2, 3]')
|
|
133
|
+
expect(toDiagnostic(true)).toBe('true')
|
|
134
|
+
expect(toDiagnostic(null)).toBe('null')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('toDiagnostic() should handle tagged values', () => {
|
|
138
|
+
const result = toDiagnostic({ tag: 121, value: [] })
|
|
139
|
+
expect(result).toBe('121([])')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('decodeToDiagnostic() should decode and convert in one step', () => {
|
|
143
|
+
expect(decodeToDiagnostic('1864')).toBe('100')
|
|
144
|
+
expect(decodeToDiagnostic('83010203')).toBe('[1, 2, 3]')
|
|
145
|
+
expect(decodeToDiagnostic('d87980')).toBe('121([])')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('Public API - CborDecoder Class', () => {
|
|
150
|
+
it('should create decoder with default options', () => {
|
|
151
|
+
const decoder = new CborDecoder()
|
|
152
|
+
const result = decoder.decode('1864')
|
|
153
|
+
expect(result.value).toBe(100)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('should create decoder with custom options', () => {
|
|
157
|
+
const decoder = new CborDecoder({ strict: true })
|
|
158
|
+
const result = decoder.decode('1864')
|
|
159
|
+
expect(result.value).toBe(100)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should decode with source map', () => {
|
|
163
|
+
const decoder = new CborDecoder()
|
|
164
|
+
const result = decoder.decodeWithSourceMap('d87980')
|
|
165
|
+
expect(result.value).toMatchObject({ tag: 121, value: [] })
|
|
166
|
+
expect(result.sourceMap).toBeDefined()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe('Public API - CborEncoder Class', () => {
|
|
171
|
+
it('should create encoder with default options', () => {
|
|
172
|
+
const encoder = new CborEncoder()
|
|
173
|
+
const result = encoder.encode(100)
|
|
174
|
+
expect(result.hex).toBe('1864')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('should create encoder with custom options', () => {
|
|
178
|
+
const encoder = new CborEncoder({ canonical: true })
|
|
179
|
+
const result = encoder.encode({ z: 1, a: 2 })
|
|
180
|
+
expect(result.hex).toBeDefined()
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should encode to hex', () => {
|
|
184
|
+
const encoder = new CborEncoder()
|
|
185
|
+
const hex = encoder.encodeToHex(100)
|
|
186
|
+
expect(hex).toBe('1864')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('should encode to bytes', () => {
|
|
190
|
+
const encoder = new CborEncoder()
|
|
191
|
+
const bytes = encoder.encodeToBytes(100)
|
|
192
|
+
expect(bytes).toEqual(new Uint8Array([0x18, 0x64]))
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('should encode sequence', () => {
|
|
196
|
+
const encoder = new CborEncoder()
|
|
197
|
+
const result = encoder.encodeSequence([1, 2, 3])
|
|
198
|
+
expect(result.hex).toBe('010203')
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
describe('Public API - Composables', () => {
|
|
203
|
+
it('useCborParser should be exported', () => {
|
|
204
|
+
const parser = useCborParser()
|
|
205
|
+
expect(parser.parse).toBeDefined()
|
|
206
|
+
expect(parser.parseWithSourceMap).toBeDefined()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('useCborEncoder should be exported', () => {
|
|
210
|
+
const encoder = useCborEncoder()
|
|
211
|
+
expect(encoder.encode).toBeDefined()
|
|
212
|
+
expect(encoder.encodeToHex).toBeDefined()
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
it('useCborInteger should be exported', () => {
|
|
216
|
+
const integer = useCborInteger()
|
|
217
|
+
expect(integer.parseInteger).toBeDefined()
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('useCborString should be exported', () => {
|
|
221
|
+
const string = useCborString()
|
|
222
|
+
expect(string.parseByteString).toBeDefined()
|
|
223
|
+
expect(string.parseTextString).toBeDefined()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('useCborCollection should be exported', () => {
|
|
227
|
+
const collection = useCborCollection()
|
|
228
|
+
expect(collection.parseArray).toBeDefined()
|
|
229
|
+
expect(collection.parseMap).toBeDefined()
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('useCborFloat should be exported', () => {
|
|
233
|
+
const float = useCborFloat()
|
|
234
|
+
expect(float.parseFloat).toBeDefined()
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('useCborTag should be exported', () => {
|
|
238
|
+
const tag = useCborTag()
|
|
239
|
+
expect(tag.parseTag).toBeDefined()
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('useCborDiagnostic should be exported', () => {
|
|
243
|
+
const diagnostic = useCborDiagnostic()
|
|
244
|
+
expect(diagnostic.toDiagnostic).toBeDefined()
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('useCborIntegerEncoder should be exported', () => {
|
|
248
|
+
const encoder = useCborIntegerEncoder()
|
|
249
|
+
expect(encoder.encodeInteger).toBeDefined()
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('useCborStringEncoder should be exported', () => {
|
|
253
|
+
const encoder = useCborStringEncoder()
|
|
254
|
+
expect(encoder.encodeTextString).toBeDefined()
|
|
255
|
+
expect(encoder.encodeByteString).toBeDefined()
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('useCborCollectionEncoder should be exported', () => {
|
|
259
|
+
const encoder = useCborCollectionEncoder()
|
|
260
|
+
expect(encoder.encodeArray).toBeDefined()
|
|
261
|
+
expect(encoder.encodeMap).toBeDefined()
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
it('useCborSimpleEncoder should be exported', () => {
|
|
265
|
+
const encoder = useCborSimpleEncoder()
|
|
266
|
+
expect(encoder.encodeSimple).toBeDefined()
|
|
267
|
+
expect(encoder.encodeFloat).toBeDefined()
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
describe('Public API - Utilities', () => {
|
|
272
|
+
it('PathBuilder should be exported', () => {
|
|
273
|
+
expect(PathBuilder).toBeDefined()
|
|
274
|
+
expect(PathBuilder.root()).toBe('')
|
|
275
|
+
expect(PathBuilder.arrayIndex('', 0)).toBe('[0]')
|
|
276
|
+
expect(PathBuilder.mapKey('', 'a')).toBe('.a')
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('Public API - Constants and Enums', () => {
|
|
281
|
+
it('DEFAULT_OPTIONS should be exported', () => {
|
|
282
|
+
expect(DEFAULT_OPTIONS).toBeDefined()
|
|
283
|
+
expect(DEFAULT_OPTIONS.strict).toBe(false)
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
it('DEFAULT_LIMITS should be exported', () => {
|
|
287
|
+
expect(DEFAULT_LIMITS).toBeDefined()
|
|
288
|
+
expect(DEFAULT_LIMITS.maxDepth).toBeDefined()
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('DEFAULT_ENCODE_OPTIONS should be exported', () => {
|
|
292
|
+
expect(DEFAULT_ENCODE_OPTIONS).toBeDefined()
|
|
293
|
+
expect(DEFAULT_ENCODE_OPTIONS.canonical).toBe(false)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('CborMajorType enum should be exported', () => {
|
|
297
|
+
expect(CborMajorType.UNSIGNED_INT).toBe(0)
|
|
298
|
+
expect(CborMajorType.NEGATIVE_INT).toBe(1)
|
|
299
|
+
expect(CborMajorType.BYTE_STRING).toBe(2)
|
|
300
|
+
expect(CborMajorType.TEXT_STRING).toBe(3)
|
|
301
|
+
expect(CborMajorType.ARRAY).toBe(4)
|
|
302
|
+
expect(CborMajorType.MAP).toBe(5)
|
|
303
|
+
expect(CborMajorType.TAG).toBe(6)
|
|
304
|
+
expect(CborMajorType.SIMPLE).toBe(7)
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('CborAdditionalInfo enum should be exported', () => {
|
|
308
|
+
expect(CborAdditionalInfo.DIRECT).toBe(23)
|
|
309
|
+
expect(CborAdditionalInfo.ONE_BYTE).toBe(24)
|
|
310
|
+
expect(CborAdditionalInfo.INDEFINITE).toBe(31)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
it('CborSimpleValue enum should be exported', () => {
|
|
314
|
+
expect(CborSimpleValue.FALSE).toBe(20)
|
|
315
|
+
expect(CborSimpleValue.TRUE).toBe(21)
|
|
316
|
+
expect(CborSimpleValue.NULL).toBe(22)
|
|
317
|
+
expect(CborSimpleValue.UNDEFINED).toBe(23)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('CborTag enum should be exported', () => {
|
|
321
|
+
expect(CborTag.DATE_TIME_STRING).toBe(0)
|
|
322
|
+
expect(CborTag.EPOCH_DATE_TIME).toBe(1)
|
|
323
|
+
expect(CborTag.POSITIVE_BIGNUM).toBe(2)
|
|
324
|
+
expect(CborTag.NEGATIVE_BIGNUM).toBe(3)
|
|
325
|
+
})
|
|
326
|
+
})
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Collection Encoder Test Suite
|
|
3
|
+
* Tests for Major Type 4 (Arrays) and Major Type 5 (Maps)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useCborCollectionEncoder } from '../composables/useCborCollectionEncoder'
|
|
8
|
+
import type { EncodableValue } from '../types'
|
|
9
|
+
|
|
10
|
+
describe('CBOR Collection Encoder', () => {
|
|
11
|
+
describe('Arrays (Major Type 4)', () => {
|
|
12
|
+
describe('Definite-length arrays', () => {
|
|
13
|
+
it('should encode empty array', () => {
|
|
14
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
15
|
+
const result = encodeArray([])
|
|
16
|
+
|
|
17
|
+
expect(result.bytes).toEqual(new Uint8Array([0x80]))
|
|
18
|
+
expect(result.hex).toBe('80')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should encode array with single integer', () => {
|
|
22
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
23
|
+
const result = encodeArray([1])
|
|
24
|
+
|
|
25
|
+
// 0x81 (array length 1) + 0x01 (integer 1)
|
|
26
|
+
expect(result.bytes).toEqual(new Uint8Array([0x81, 0x01]))
|
|
27
|
+
expect(result.hex).toBe('8101')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('should encode array of integers [1, 2, 3]', () => {
|
|
31
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
32
|
+
const result = encodeArray([1, 2, 3])
|
|
33
|
+
|
|
34
|
+
// RFC 8949 Appendix A: [1, 2, 3] = 0x83 0x01 0x02 0x03
|
|
35
|
+
expect(result.bytes).toEqual(new Uint8Array([0x83, 0x01, 0x02, 0x03]))
|
|
36
|
+
expect(result.hex).toBe('83010203')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should encode array with mixed types', () => {
|
|
40
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
41
|
+
const result = encodeArray([1, 'test', true])
|
|
42
|
+
|
|
43
|
+
// 0x83 (length 3) + 0x01 (1) + 0x64746573740a ("test") + 0xf5 (true)
|
|
44
|
+
expect(result.bytes[0]).toBe(0x83)
|
|
45
|
+
expect(result.bytes[1]).toBe(0x01) // Integer 1
|
|
46
|
+
expect(result.bytes[2]).toBe(0x64) // Text string length 4
|
|
47
|
+
expect(result.bytes[7]).toBe(0xf5) // true
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('should encode nested arrays [[1, 2], [3, 4]]', () => {
|
|
51
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
52
|
+
const result = encodeArray([[1, 2], [3, 4]])
|
|
53
|
+
|
|
54
|
+
// 0x82 (outer array length 2)
|
|
55
|
+
// + 0x82 0x01 0x02 (inner array [1,2])
|
|
56
|
+
// + 0x82 0x03 0x04 (inner array [3,4])
|
|
57
|
+
expect(result.bytes).toEqual(new Uint8Array([
|
|
58
|
+
0x82, // Array length 2
|
|
59
|
+
0x82, 0x01, 0x02, // [1, 2]
|
|
60
|
+
0x82, 0x03, 0x04 // [3, 4]
|
|
61
|
+
]))
|
|
62
|
+
expect(result.hex).toBe('82820102820304')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should encode array with 24 elements (1-byte length)', () => {
|
|
66
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
67
|
+
const arr = Array(24).fill(1)
|
|
68
|
+
const result = encodeArray(arr)
|
|
69
|
+
|
|
70
|
+
// 0x98 0x18 (length 24) + 24 × 0x01
|
|
71
|
+
expect(result.bytes[0]).toBe(0x98)
|
|
72
|
+
expect(result.bytes[1]).toBe(24)
|
|
73
|
+
expect(result.bytes.length).toBe(26) // 2 byte header + 24 elements
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should encode Cardano collateral UTXO array', () => {
|
|
77
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
78
|
+
|
|
79
|
+
// Real Cardano CIP-30 example: array of [txHash, outputIndex] pairs
|
|
80
|
+
const txHash = new Uint8Array([
|
|
81
|
+
0x48, 0xbd, 0x01, 0xd5, 0x1e, 0x58, 0x0c, 0xde,
|
|
82
|
+
0x15, 0xaf, 0xa6, 0xd2, 0x8f, 0x63, 0xd8, 0x9c,
|
|
83
|
+
0x91, 0x37, 0xb9, 0x3a, 0x91, 0x0e, 0x59, 0x41,
|
|
84
|
+
0x19, 0x2e, 0x26, 0xb1, 0x29, 0x06, 0x10, 0x67
|
|
85
|
+
])
|
|
86
|
+
|
|
87
|
+
const utxo: EncodableValue[] = [txHash, 0]
|
|
88
|
+
const result = encodeArray([utxo])
|
|
89
|
+
|
|
90
|
+
// Should start with 0x81 (outer array length 1)
|
|
91
|
+
expect(result.bytes[0]).toBe(0x81)
|
|
92
|
+
// Then 0x82 (inner array length 2)
|
|
93
|
+
expect(result.bytes[1]).toBe(0x82)
|
|
94
|
+
// Then 0x58 0x20 (byte string length 32)
|
|
95
|
+
expect(result.bytes[2]).toBe(0x58)
|
|
96
|
+
expect(result.bytes[3]).toBe(0x20)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
describe('Indefinite-length arrays', () => {
|
|
101
|
+
it('should encode empty indefinite array', () => {
|
|
102
|
+
const { encodeArrayIndefinite } = useCborCollectionEncoder()
|
|
103
|
+
const result = encodeArrayIndefinite([])
|
|
104
|
+
|
|
105
|
+
// 0x9f (start) + 0xff (break)
|
|
106
|
+
expect(result.bytes).toEqual(new Uint8Array([0x9f, 0xff]))
|
|
107
|
+
expect(result.hex).toBe('9fff')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should encode indefinite array [1, 2, 3]', () => {
|
|
111
|
+
const { encodeArrayIndefinite } = useCborCollectionEncoder()
|
|
112
|
+
const result = encodeArrayIndefinite([1, 2, 3])
|
|
113
|
+
|
|
114
|
+
// RFC 8949: 0x9f 0x01 0x02 0x03 0xff
|
|
115
|
+
expect(result.bytes).toEqual(new Uint8Array([0x9f, 0x01, 0x02, 0x03, 0xff]))
|
|
116
|
+
expect(result.hex).toBe('9f010203ff')
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should reject indefinite arrays in canonical mode', () => {
|
|
120
|
+
const { encodeArray } = useCborCollectionEncoder({ canonical: true })
|
|
121
|
+
|
|
122
|
+
expect(() => encodeArray([], { indefinite: true }))
|
|
123
|
+
.toThrow('Indefinite-length encoding not allowed in canonical mode')
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe('Maps (Major Type 5)', () => {
|
|
129
|
+
describe('Definite-length maps', () => {
|
|
130
|
+
it('should encode empty map', () => {
|
|
131
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
132
|
+
const result = encodeMap({})
|
|
133
|
+
|
|
134
|
+
expect(result.bytes).toEqual(new Uint8Array([0xa0]))
|
|
135
|
+
expect(result.hex).toBe('a0')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should encode map with single string key', () => {
|
|
139
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
140
|
+
const result = encodeMap({ a: 1 })
|
|
141
|
+
|
|
142
|
+
// 0xa1 (map length 1) + 0x61 0x61 ("a") + 0x01 (1)
|
|
143
|
+
expect(result.bytes).toEqual(new Uint8Array([0xa1, 0x61, 0x61, 0x01]))
|
|
144
|
+
expect(result.hex).toBe('a1616101')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('should encode Cardano amount map', () => {
|
|
148
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
149
|
+
const result = encodeMap({ amount: 1000000 })
|
|
150
|
+
|
|
151
|
+
// 0xa1 (length 1)
|
|
152
|
+
// + 0x66 "amount" (text string)
|
|
153
|
+
// + 0x1a 0x000f4240 (integer 1000000)
|
|
154
|
+
expect(result.bytes[0]).toBe(0xa1)
|
|
155
|
+
expect(result.bytes[1]).toBe(0x66) // Text string length 6
|
|
156
|
+
expect(result.hex).toBe('a166616d6f756e741a000f4240')
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
it('should encode map with integer keys', () => {
|
|
160
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
161
|
+
const result = encodeMap(new Map([[1, 2], [3, 4]]))
|
|
162
|
+
|
|
163
|
+
// 0xa2 (length 2) + (0x01 0x02) + (0x03 0x04)
|
|
164
|
+
expect(result.bytes).toEqual(new Uint8Array([0xa2, 0x01, 0x02, 0x03, 0x04]))
|
|
165
|
+
expect(result.hex).toBe('a201020304')
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should encode nested map', () => {
|
|
169
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
170
|
+
const result = encodeMap({ a: { b: 1 } })
|
|
171
|
+
|
|
172
|
+
// 0xa1 (outer map length 1)
|
|
173
|
+
// + 0x61 0x61 ("a")
|
|
174
|
+
// + 0xa1 (inner map length 1)
|
|
175
|
+
// + 0x61 0x62 ("b")
|
|
176
|
+
// + 0x01 (1)
|
|
177
|
+
expect(result.bytes[0]).toBe(0xa1)
|
|
178
|
+
expect(result.bytes[3]).toBe(0xa1) // Nested map
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should encode map with mixed value types', () => {
|
|
182
|
+
const { encodeMap } = useCborCollectionEncoder()
|
|
183
|
+
const result = encodeMap({
|
|
184
|
+
num: 42,
|
|
185
|
+
str: 'hello',
|
|
186
|
+
bool: true,
|
|
187
|
+
arr: [1, 2]
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
expect(result.bytes[0]).toBe(0xa4) // Map length 4
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('Canonical map encoding', () => {
|
|
195
|
+
it('should sort map keys by encoded bytes (canonical mode)', () => {
|
|
196
|
+
const { encodeMap } = useCborCollectionEncoder({ canonical: true })
|
|
197
|
+
const result = encodeMap({ z: 1, a: 2 })
|
|
198
|
+
|
|
199
|
+
// In canonical mode, keys must be sorted by encoded bytes
|
|
200
|
+
// "a" (0x6161) < "z" (0x617a), so "a" should come first
|
|
201
|
+
const hex = result.hex
|
|
202
|
+
const aPos = hex.indexOf('6161') // "a"
|
|
203
|
+
const zPos = hex.indexOf('617a') // "z"
|
|
204
|
+
|
|
205
|
+
expect(aPos).toBeLessThan(zPos)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('should sort by length first, then bytewise', () => {
|
|
209
|
+
const { encodeMap } = useCborCollectionEncoder({ canonical: true })
|
|
210
|
+
const result = encodeMap({ aa: 1, b: 2 })
|
|
211
|
+
|
|
212
|
+
// "b" (0x6162) is shorter than "aa" (0x626161), so "b" comes first
|
|
213
|
+
const hex = result.hex
|
|
214
|
+
const bPos = hex.indexOf('6162') // "b"
|
|
215
|
+
const aaPos = hex.indexOf('626161') // "aa"
|
|
216
|
+
|
|
217
|
+
expect(bPos).toBeLessThan(aaPos)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
it('should reject duplicate keys when option is set', () => {
|
|
221
|
+
const { encodeMap } = useCborCollectionEncoder({ rejectDuplicateKeys: true })
|
|
222
|
+
|
|
223
|
+
// Create a map with duplicate keys using Map constructor
|
|
224
|
+
const map = new Map([
|
|
225
|
+
['key', 1],
|
|
226
|
+
['key', 2] // Duplicate (will overwrite in Map)
|
|
227
|
+
])
|
|
228
|
+
|
|
229
|
+
// This should not throw because Map automatically handles duplicates
|
|
230
|
+
// but we test with manual encoding later
|
|
231
|
+
const result = encodeMap(map)
|
|
232
|
+
expect(result.bytes[0]).toBe(0xa1) // Only one entry
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
describe('Indefinite-length maps', () => {
|
|
237
|
+
it('should encode empty indefinite map', () => {
|
|
238
|
+
const { encodeMapIndefinite } = useCborCollectionEncoder()
|
|
239
|
+
const result = encodeMapIndefinite({})
|
|
240
|
+
|
|
241
|
+
// 0xbf (start) + 0xff (break)
|
|
242
|
+
expect(result.bytes).toEqual(new Uint8Array([0xbf, 0xff]))
|
|
243
|
+
expect(result.hex).toBe('bfff')
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should encode indefinite map {"a": 1}', () => {
|
|
247
|
+
const { encodeMapIndefinite } = useCborCollectionEncoder()
|
|
248
|
+
const result = encodeMapIndefinite({ a: 1 })
|
|
249
|
+
|
|
250
|
+
// 0xbf + 0x61 0x61 ("a") + 0x01 (1) + 0xff
|
|
251
|
+
expect(result.bytes).toEqual(new Uint8Array([0xbf, 0x61, 0x61, 0x01, 0xff]))
|
|
252
|
+
expect(result.hex).toBe('bf616101ff')
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
it('should reject indefinite maps in canonical mode', () => {
|
|
256
|
+
const { encodeMap } = useCborCollectionEncoder({ canonical: true })
|
|
257
|
+
|
|
258
|
+
expect(() => encodeMap({}, { indefinite: true }))
|
|
259
|
+
.toThrow('Indefinite-length encoding not allowed in canonical mode')
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('Depth limits', () => {
|
|
265
|
+
it('should enforce maximum nesting depth', () => {
|
|
266
|
+
const { encodeArray } = useCborCollectionEncoder({ maxDepth: 1 })
|
|
267
|
+
|
|
268
|
+
// Create deeply nested array: [[[1]]]
|
|
269
|
+
// With maxDepth=1, recursion proceeds as:
|
|
270
|
+
// - encodeArray starts with depth=0
|
|
271
|
+
// - encodeValue([[1]]) checks depth=0 > 1? No, creates newCtx depth=1
|
|
272
|
+
// - encodeValue([1]) checks depth=1 > 1? No, creates newCtx depth=2
|
|
273
|
+
// - encodeValue(1) checks depth=2 > 1? Yes! Throws
|
|
274
|
+
const deep = [[[1]]]
|
|
275
|
+
|
|
276
|
+
expect(() => encodeArray(deep))
|
|
277
|
+
.toThrow('Maximum nesting depth exceeded')
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
it('should handle nested maps and arrays', () => {
|
|
281
|
+
const { encodeArray } = useCborCollectionEncoder({ maxDepth: 5 })
|
|
282
|
+
|
|
283
|
+
const nested = [
|
|
284
|
+
{
|
|
285
|
+
data: [
|
|
286
|
+
{ inner: [1, 2, 3] }
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
const result = encodeArray(nested)
|
|
292
|
+
expect(result.bytes[0]).toBe(0x81) // Should succeed
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('Output size limits', () => {
|
|
297
|
+
it('should respect maxOutputSize option', () => {
|
|
298
|
+
const { encodeArray } = useCborCollectionEncoder({ maxOutputSize: 10 })
|
|
299
|
+
const largeArray = Array(100).fill(1)
|
|
300
|
+
|
|
301
|
+
expect(() => encodeArray(largeArray))
|
|
302
|
+
.toThrow('Encoded output exceeds maximum size')
|
|
303
|
+
})
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
describe('Real-world Cardano examples', () => {
|
|
307
|
+
it('should encode Cardano transaction structure', () => {
|
|
308
|
+
const { encodeMap, encodeArray } = useCborCollectionEncoder()
|
|
309
|
+
|
|
310
|
+
// Simplified Cardano transaction
|
|
311
|
+
const tx = {
|
|
312
|
+
inputs: [[new Uint8Array(32).fill(0xaa), 0]],
|
|
313
|
+
outputs: [{ address: new Uint8Array(28).fill(0xbb), amount: 1000000 }],
|
|
314
|
+
fee: 170000
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const result = encodeMap(tx)
|
|
318
|
+
expect(result.bytes[0]).toBe(0xa3) // Map with 3 entries
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should encode Plutus data list', () => {
|
|
322
|
+
const { encodeArray } = useCborCollectionEncoder()
|
|
323
|
+
|
|
324
|
+
// Plutus list of integers
|
|
325
|
+
const plutusList = [121, [1, 2, 3]] // Tag 121 with list
|
|
326
|
+
const result = encodeArray(plutusList)
|
|
327
|
+
|
|
328
|
+
expect(result.bytes[0]).toBe(0x82) // Array length 2
|
|
329
|
+
})
|
|
330
|
+
})
|
|
331
|
+
})
|