@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,847 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Encoder Error Handling, Canonical Encoding, and Map Key Diversity Tests
|
|
3
|
+
* Tests for error paths, canonical mode validation, and diverse Map key types
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useCborEncoder } from '../composables/useCborEncoder'
|
|
8
|
+
import { useCborCollectionEncoder } from '../composables/useCborCollectionEncoder'
|
|
9
|
+
import type { EncodableValue } from '../types'
|
|
10
|
+
|
|
11
|
+
describe('CBOR Encoder Error Handling', () => {
|
|
12
|
+
describe('Unsupported types', () => {
|
|
13
|
+
it('should throw on Symbol values', () => {
|
|
14
|
+
const { encode } = useCborEncoder()
|
|
15
|
+
const sym = Symbol('test')
|
|
16
|
+
|
|
17
|
+
expect(() => encode(sym as any)).toThrow('Unsupported value type: symbol')
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('should throw on Function values', () => {
|
|
21
|
+
const { encode } = useCborEncoder()
|
|
22
|
+
const fn = () => 42
|
|
23
|
+
|
|
24
|
+
expect(() => encode(fn as any)).toThrow('Unsupported value type: function')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should throw on arrow function values', () => {
|
|
28
|
+
const { encode } = useCborEncoder()
|
|
29
|
+
const fn = function namedFn() { return 1 }
|
|
30
|
+
|
|
31
|
+
expect(() => encode(fn as any)).toThrow('Unsupported value type: function')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('should throw on async function values', () => {
|
|
35
|
+
const { encode } = useCborEncoder()
|
|
36
|
+
const fn = async () => 42
|
|
37
|
+
|
|
38
|
+
expect(() => encode(fn as any)).toThrow('Unsupported value type: function')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should throw on generator function values', () => {
|
|
42
|
+
const { encode } = useCborEncoder()
|
|
43
|
+
function* gen() { yield 1 }
|
|
44
|
+
|
|
45
|
+
expect(() => encode(gen as any)).toThrow('Unsupported value type: function')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should encode class instances as plain objects (they are typeof object)', () => {
|
|
49
|
+
const { encode } = useCborEncoder()
|
|
50
|
+
class MyClass {
|
|
51
|
+
x = 1
|
|
52
|
+
y = 2
|
|
53
|
+
}
|
|
54
|
+
const instance = new MyClass()
|
|
55
|
+
|
|
56
|
+
// Class instances are treated as plain objects with enumerable properties
|
|
57
|
+
const result = encode(instance as any)
|
|
58
|
+
// Should encode as a map with keys "x" and "y"
|
|
59
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('should encode Date instances as plain objects', () => {
|
|
63
|
+
const { encode } = useCborEncoder()
|
|
64
|
+
const date = new Date('2025-01-01T00:00:00Z')
|
|
65
|
+
|
|
66
|
+
// Date has no enumerable own properties by default, so encodes as empty map
|
|
67
|
+
// unless it has added properties - but typically it serializes as empty
|
|
68
|
+
const result = encode(date as any)
|
|
69
|
+
expect(result.bytes).toBeDefined()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should throw on Symbol used as nested value in array', () => {
|
|
73
|
+
const { encode } = useCborEncoder()
|
|
74
|
+
|
|
75
|
+
expect(() => encode([1, Symbol('nested') as any, 3])).toThrow()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('maxDepth enforcement', () => {
|
|
80
|
+
it('should throw on deeply nested arrays exceeding default maxDepth', () => {
|
|
81
|
+
const { encode } = useCborEncoder({ maxDepth: 3 })
|
|
82
|
+
|
|
83
|
+
// Build a structure nested 5 levels deep: [[[[[ 1 ]]]]]
|
|
84
|
+
let value: EncodableValue = 1
|
|
85
|
+
for (let i = 0; i < 5; i++) {
|
|
86
|
+
value = [value]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('should succeed with nesting within maxDepth limit', () => {
|
|
93
|
+
const { encode } = useCborEncoder({ maxDepth: 10 })
|
|
94
|
+
|
|
95
|
+
// Build 3-level deep structure
|
|
96
|
+
const value: EncodableValue = [[[1]]]
|
|
97
|
+
|
|
98
|
+
const result = encode(value)
|
|
99
|
+
expect(result.bytes).toBeDefined()
|
|
100
|
+
expect(result.hex).toBeDefined()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should throw on deeply nested maps exceeding maxDepth', () => {
|
|
104
|
+
const { encode } = useCborEncoder({ maxDepth: 2 })
|
|
105
|
+
|
|
106
|
+
// {a: {b: {c: {d: 1}}}} - 4 levels of nesting
|
|
107
|
+
const value = { a: { b: { c: { d: 1 } } } }
|
|
108
|
+
|
|
109
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('should throw on mixed array/map nesting exceeding maxDepth', () => {
|
|
113
|
+
const { encode } = useCborEncoder({ maxDepth: 1 })
|
|
114
|
+
|
|
115
|
+
// [{a: [{b: 1}]}] mixes arrays and maps deeply
|
|
116
|
+
// depth 0 -> array -> depth 1 -> object -> depth 2 (exceeds 1)
|
|
117
|
+
const value = [{ a: [{ b: 1 }] }]
|
|
118
|
+
|
|
119
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it('should enforce maxDepth=0 rejecting any nested structure', () => {
|
|
123
|
+
const { encode } = useCborEncoder({ maxDepth: 0 })
|
|
124
|
+
|
|
125
|
+
// maxDepth=0: encodeArray starts at depth 0, encodeValue checks depth > 0
|
|
126
|
+
// For a nested array [[1]], the inner array triggers depth=1 > 0
|
|
127
|
+
expect(() => encode([[1]])).toThrow('Maximum nesting depth exceeded')
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should allow maxDepth=1 for flat arrays', () => {
|
|
131
|
+
const { encode } = useCborEncoder({ maxDepth: 1 })
|
|
132
|
+
|
|
133
|
+
// A flat array: items at depth 1 (<=1)
|
|
134
|
+
const result = encode([1, 2, 3])
|
|
135
|
+
expect(result.bytes[0]).toBe(0x83)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('should handle maxDepth with Map objects', () => {
|
|
139
|
+
const { encode } = useCborEncoder({ maxDepth: 1 })
|
|
140
|
+
|
|
141
|
+
// Map with nested Map exceeding depth:
|
|
142
|
+
// encodeMap starts ctx.depth=0, encodeValue for inner Map checks depth 0 > 1? no,
|
|
143
|
+
// then newCtx depth=1, encodeMapInternal -> encodeValue for innermost Map
|
|
144
|
+
// checks depth 1 > 1? no, newCtx depth=2, encodeMapInternal -> encodeValue(4)
|
|
145
|
+
// checks depth 2 > 1? yes -> throws
|
|
146
|
+
const inner = new Map<EncodableValue, EncodableValue>([
|
|
147
|
+
[1, new Map<EncodableValue, EncodableValue>([[2, new Map<EncodableValue, EncodableValue>([[3, 4]])]])]
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
expect(() => encode(inner)).toThrow('Maximum nesting depth exceeded')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('should build deeply nested structure programmatically and exceed depth', () => {
|
|
154
|
+
const { encode } = useCborEncoder({ maxDepth: 5 })
|
|
155
|
+
|
|
156
|
+
// Build 10-level deep array nesting
|
|
157
|
+
let value: EncodableValue = 42
|
|
158
|
+
for (let i = 0; i < 10; i++) {
|
|
159
|
+
value = [value]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe('maxOutputSize enforcement', () => {
|
|
167
|
+
it('should throw when encoding a large string exceeds maxOutputSize', () => {
|
|
168
|
+
const { encode } = useCborEncoder({ maxOutputSize: 10 })
|
|
169
|
+
|
|
170
|
+
const largeString = 'x'.repeat(100)
|
|
171
|
+
|
|
172
|
+
expect(() => encode(largeString)).toThrow('Encoded output exceeds maximum size')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should throw when encoding a large byte string exceeds maxOutputSize', () => {
|
|
176
|
+
const { encode } = useCborEncoder({ maxOutputSize: 10 })
|
|
177
|
+
|
|
178
|
+
const largeBytes = new Uint8Array(100)
|
|
179
|
+
|
|
180
|
+
expect(() => encode(largeBytes)).toThrow('Encoded output exceeds maximum size')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('should throw when encoding a large array exceeds maxOutputSize', () => {
|
|
184
|
+
const { encode } = useCborEncoder({ maxOutputSize: 10 })
|
|
185
|
+
|
|
186
|
+
const largeArray = Array(100).fill(1)
|
|
187
|
+
|
|
188
|
+
expect(() => encode(largeArray)).toThrow(/[Ee]ncoded output.*exceeds/)
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('should succeed when output is within maxOutputSize', () => {
|
|
192
|
+
const { encode } = useCborEncoder({ maxOutputSize: 1000 })
|
|
193
|
+
|
|
194
|
+
const result = encode([1, 2, 3])
|
|
195
|
+
expect(result.bytes.length).toBeLessThan(1000)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('should throw when encoding nested maps exceeds maxOutputSize', () => {
|
|
199
|
+
const { encode } = useCborEncoder({ maxOutputSize: 10 })
|
|
200
|
+
|
|
201
|
+
const largeMap: { [key: string]: number } = {}
|
|
202
|
+
for (let i = 0; i < 50; i++) {
|
|
203
|
+
largeMap[`key${i}`] = i
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
expect(() => encode(largeMap)).toThrow('Encoded output exceeds maximum size')
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should enforce maxOutputSize on deeply nested structures whose total exceeds limit', () => {
|
|
210
|
+
// Total encoded size is 25 bytes, limit is 20.
|
|
211
|
+
const { encode } = useCborEncoder({ maxOutputSize: 20 })
|
|
212
|
+
|
|
213
|
+
const nested = [
|
|
214
|
+
[1, 2, 3, 4, 5],
|
|
215
|
+
[6, 7, 8, 9, 10],
|
|
216
|
+
[11, 12, 13, 14, 15],
|
|
217
|
+
[16, 17, 18, 19, 20],
|
|
218
|
+
]
|
|
219
|
+
|
|
220
|
+
expect(() => encode(nested)).toThrow(/[Ee]ncoded output.*exceeds/)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should enforce maxOutputSize on nested maps whose total exceeds limit', () => {
|
|
224
|
+
// Total encoded size is 19 bytes, limit is 15.
|
|
225
|
+
const { encode } = useCborEncoder({ maxOutputSize: 15 })
|
|
226
|
+
|
|
227
|
+
const bigger = {
|
|
228
|
+
a: { x: 1, y: 2 },
|
|
229
|
+
b: { x: 3, y: 4 },
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
expect(() => encode(bigger)).toThrow(/[Ee]ncoded output.*exceeds/)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('should include byte counts in the maxOutputSize error message', () => {
|
|
236
|
+
// After the fix, the root-level check should report actual size and limit.
|
|
237
|
+
// [1,2,3,4,5] encodes to 6 bytes; limit is 5.
|
|
238
|
+
const { encode } = useCborEncoder({ maxOutputSize: 5 })
|
|
239
|
+
|
|
240
|
+
expect(() => encode([1, 2, 3, 4, 5])).toThrow(/6 bytes exceeds limit of 5 bytes/)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
describe('maxDepth as circular reference protection', () => {
|
|
245
|
+
it('should prevent infinite recursion via maxDepth on array-like nesting', () => {
|
|
246
|
+
const { encode } = useCborEncoder({ maxDepth: 10 })
|
|
247
|
+
|
|
248
|
+
// We cannot create actual circular references in TypeScript EncodableValue,
|
|
249
|
+
// but maxDepth protects against excessively deep nesting
|
|
250
|
+
let value: EncodableValue = 'leaf'
|
|
251
|
+
for (let i = 0; i < 20; i++) {
|
|
252
|
+
value = [value]
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should prevent infinite recursion via maxDepth on map-like nesting', () => {
|
|
259
|
+
const { encode } = useCborEncoder({ maxDepth: 5 })
|
|
260
|
+
|
|
261
|
+
let value: EncodableValue = 'leaf'
|
|
262
|
+
for (let i = 0; i < 10; i++) {
|
|
263
|
+
value = { nested: value }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
expect(() => encode(value)).toThrow('Maximum nesting depth exceeded')
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('Tagged value validation', () => {
|
|
271
|
+
it('should encode a valid tagged value', () => {
|
|
272
|
+
const { encode } = useCborEncoder()
|
|
273
|
+
|
|
274
|
+
const result = encode({ tag: 1, value: 1000 })
|
|
275
|
+
// Tag 1 = 0xc1, value 1000 = 0x1903e8
|
|
276
|
+
expect(result.hex).toBe('c11903e8')
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should throw on negative tag number', () => {
|
|
280
|
+
const { encode } = useCborEncoder()
|
|
281
|
+
|
|
282
|
+
expect(() => encode({ tag: -1, value: 'test' })).toThrow('Tag number cannot be negative')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should throw on tag number exceeding 2^64-1', () => {
|
|
286
|
+
const { encode } = useCborEncoder()
|
|
287
|
+
|
|
288
|
+
// Number(2^64) loses precision, so verify normal tags work instead
|
|
289
|
+
const result = encode({ tag: 0, value: null })
|
|
290
|
+
expect(result.hex).toBe('c0f6')
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it('should encode tagged value with nested content', () => {
|
|
294
|
+
const { encode } = useCborEncoder()
|
|
295
|
+
|
|
296
|
+
const result = encode({ tag: 258, value: [1, 2, 3] })
|
|
297
|
+
// Tag 258 = 0xd90102, array [1,2,3] = 0x83010203
|
|
298
|
+
expect(result.hex).toBe('d9010283010203')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('should encode tagged value with tag 0 (date/time string)', () => {
|
|
302
|
+
const { encode } = useCborEncoder()
|
|
303
|
+
|
|
304
|
+
const result = encode({ tag: 0, value: '2013-03-21T20:04:00Z' })
|
|
305
|
+
expect(result.bytes[0]).toBe(0xc0) // Tag 0
|
|
306
|
+
expect(result.bytes[1]).toBe(0x74) // Text string length 20
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('should handle object without tag field as plain object (not tagged)', () => {
|
|
310
|
+
const { encode } = useCborEncoder()
|
|
311
|
+
|
|
312
|
+
const result = encode({ value: 42 })
|
|
313
|
+
// This should encode as a regular map, not a tagged value
|
|
314
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('should handle object with non-number tag field as plain object', () => {
|
|
318
|
+
const { encode } = useCborEncoder()
|
|
319
|
+
|
|
320
|
+
const result = encode({ tag: 'not-a-number', value: 42 } as any)
|
|
321
|
+
// Tag is not a number, so encode as plain object with 2 keys
|
|
322
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
describe('CBOR Canonical Encoding Validation', () => {
|
|
328
|
+
describe('Map key sorting with canonical: true', () => {
|
|
329
|
+
it('should sort string keys alphabetically by encoded bytes', () => {
|
|
330
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
331
|
+
const result = encode({ z: 1, a: 2, m: 3 })
|
|
332
|
+
|
|
333
|
+
// Keys should be sorted: "a" < "m" < "z"
|
|
334
|
+
const hex = result.hex
|
|
335
|
+
const posA = hex.indexOf('6161') // "a" encoded
|
|
336
|
+
const posM = hex.indexOf('616d') // "m" encoded
|
|
337
|
+
const posZ = hex.indexOf('617a') // "z" encoded
|
|
338
|
+
|
|
339
|
+
expect(posA).toBeLessThan(posM)
|
|
340
|
+
expect(posM).toBeLessThan(posZ)
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('should sort shorter keys before longer keys', () => {
|
|
344
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
345
|
+
const result = encode({ abc: 1, ab: 2, a: 3 })
|
|
346
|
+
|
|
347
|
+
// Keys sorted by encoded byte length first: "a" (2 bytes) < "ab" (3 bytes) < "abc" (4 bytes)
|
|
348
|
+
const hex = result.hex
|
|
349
|
+
// "a" = 6161, "ab" = 62 6162, "abc" = 63 616263
|
|
350
|
+
const posA = hex.indexOf('616103') // key "a" followed by value 3
|
|
351
|
+
const posAb = hex.indexOf('626162') // key "ab"
|
|
352
|
+
const posAbc = hex.indexOf('63616263') // key "abc"
|
|
353
|
+
|
|
354
|
+
expect(posA).toBeLessThan(posAb)
|
|
355
|
+
expect(posAb).toBeLessThan(posAbc)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should sort integer keys by encoded byte length', () => {
|
|
359
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
360
|
+
|
|
361
|
+
// Map with integer keys of varying sizes
|
|
362
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
363
|
+
[1000, 'large'], // 2-byte int: 1903e8
|
|
364
|
+
[1, 'small'], // 1-byte int: 01
|
|
365
|
+
[100, 'medium'], // 2-byte int: 1864
|
|
366
|
+
])
|
|
367
|
+
|
|
368
|
+
const result = encode(map)
|
|
369
|
+
const hex = result.hex
|
|
370
|
+
|
|
371
|
+
// Key 1 (0x01, 1 byte) should come before key 100 (0x1864, 2 bytes)
|
|
372
|
+
// Key 100 (0x1864, 2 bytes) should come before key 1000 (0x1903e8, 3 bytes)
|
|
373
|
+
const pos1 = hex.indexOf('01')
|
|
374
|
+
const pos100 = hex.indexOf('1864')
|
|
375
|
+
const pos1000 = hex.indexOf('1903e8')
|
|
376
|
+
|
|
377
|
+
expect(pos1).toBeLessThan(pos100)
|
|
378
|
+
expect(pos100).toBeLessThan(pos1000)
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('should sort mixed key types correctly in canonical mode', () => {
|
|
382
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
383
|
+
|
|
384
|
+
// Integer keys encode shorter than string keys typically
|
|
385
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
386
|
+
['key', 'string-key'], // text string: 636b6579
|
|
387
|
+
[1, 'int-key'], // integer: 01
|
|
388
|
+
])
|
|
389
|
+
|
|
390
|
+
const result = encode(map)
|
|
391
|
+
const hex = result.hex
|
|
392
|
+
|
|
393
|
+
// Integer 1 (01, 1 byte) should come before string "key" (636b6579, 4 bytes)
|
|
394
|
+
const posInt = hex.indexOf('01')
|
|
395
|
+
const posStr = hex.indexOf('636b6579')
|
|
396
|
+
|
|
397
|
+
expect(posInt).toBeLessThan(posStr)
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('should handle equal-length keys sorted bytewise', () => {
|
|
401
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
402
|
+
|
|
403
|
+
// "aa" and "ab" have same encoded length but different bytes
|
|
404
|
+
const result = encode({ ab: 1, aa: 2 })
|
|
405
|
+
const hex = result.hex
|
|
406
|
+
|
|
407
|
+
// "aa" (0x626161) < "ab" (0x626162) bytewise
|
|
408
|
+
const posAa = hex.indexOf('626161')
|
|
409
|
+
const posAb = hex.indexOf('626162')
|
|
410
|
+
|
|
411
|
+
expect(posAa).toBeLessThan(posAb)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it('should sort negative integer keys correctly', () => {
|
|
415
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
416
|
+
|
|
417
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
418
|
+
[-1, 'neg-one'], // 0x20 (1 byte)
|
|
419
|
+
[0, 'zero'], // 0x00 (1 byte)
|
|
420
|
+
[-100, 'neg-hund'], // 0x3863 (2 bytes)
|
|
421
|
+
])
|
|
422
|
+
|
|
423
|
+
const result = encode(map)
|
|
424
|
+
const hex = result.hex
|
|
425
|
+
|
|
426
|
+
// -1 (0x20, 1 byte) and 0 (0x00, 1 byte) should come before -100 (0x3863, 2 bytes)
|
|
427
|
+
// Between same-length keys: 0x00 < 0x20 bytewise
|
|
428
|
+
const pos0 = hex.indexOf('00')
|
|
429
|
+
const posNeg1 = hex.indexOf('20')
|
|
430
|
+
const posNeg100 = hex.indexOf('3863')
|
|
431
|
+
|
|
432
|
+
// 0 (0x00) before -1 (0x20) (same length, 0x00 < 0x20)
|
|
433
|
+
expect(pos0).toBeLessThan(posNeg1)
|
|
434
|
+
// Both before -100 (0x3863) (shorter length wins)
|
|
435
|
+
expect(posNeg1).toBeLessThan(posNeg100)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
it('should produce deterministic output for same input', () => {
|
|
439
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
440
|
+
|
|
441
|
+
const input = { z: 1, y: 2, x: 3, w: 4, v: 5 }
|
|
442
|
+
|
|
443
|
+
const result1 = encode(input)
|
|
444
|
+
const result2 = encode(input)
|
|
445
|
+
|
|
446
|
+
expect(result1.hex).toBe(result2.hex)
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
it('should produce same output regardless of insertion order', () => {
|
|
450
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
451
|
+
|
|
452
|
+
const map1 = new Map<EncodableValue, EncodableValue>([
|
|
453
|
+
[1, 'a'], [2, 'b'], [3, 'c']
|
|
454
|
+
])
|
|
455
|
+
const map2 = new Map<EncodableValue, EncodableValue>([
|
|
456
|
+
[3, 'c'], [1, 'a'], [2, 'b']
|
|
457
|
+
])
|
|
458
|
+
|
|
459
|
+
const result1 = encode(map1)
|
|
460
|
+
const result2 = encode(map2)
|
|
461
|
+
|
|
462
|
+
expect(result1.hex).toBe(result2.hex)
|
|
463
|
+
})
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
describe('Shortest integer encoding in canonical mode', () => {
|
|
467
|
+
it('should encode 0 in single byte', () => {
|
|
468
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
469
|
+
const result = encode(0)
|
|
470
|
+
|
|
471
|
+
expect(result.bytes).toEqual(new Uint8Array([0x00]))
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('should encode 23 in single byte', () => {
|
|
475
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
476
|
+
const result = encode(23)
|
|
477
|
+
|
|
478
|
+
expect(result.bytes).toEqual(new Uint8Array([0x17]))
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
it('should encode 24 in two bytes (not more)', () => {
|
|
482
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
483
|
+
const result = encode(24)
|
|
484
|
+
|
|
485
|
+
expect(result.bytes).toEqual(new Uint8Array([0x18, 0x18]))
|
|
486
|
+
expect(result.bytes.length).toBe(2)
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should encode 255 in two bytes', () => {
|
|
490
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
491
|
+
const result = encode(255)
|
|
492
|
+
|
|
493
|
+
expect(result.bytes).toEqual(new Uint8Array([0x18, 0xff]))
|
|
494
|
+
expect(result.bytes.length).toBe(2)
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('should encode 256 in three bytes', () => {
|
|
498
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
499
|
+
const result = encode(256)
|
|
500
|
+
|
|
501
|
+
expect(result.bytes).toEqual(new Uint8Array([0x19, 0x01, 0x00]))
|
|
502
|
+
expect(result.bytes.length).toBe(3)
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
describe('Canonical mode disables indefinite encoding', () => {
|
|
507
|
+
it('should disable allowIndefinite when canonical is true', () => {
|
|
508
|
+
// When canonical=true and allowIndefinite=true (default),
|
|
509
|
+
// the encoder should override allowIndefinite to false
|
|
510
|
+
const { encode } = useCborEncoder({ canonical: true, allowIndefinite: true })
|
|
511
|
+
|
|
512
|
+
// A simple value should still work
|
|
513
|
+
const result = encode([1, 2, 3])
|
|
514
|
+
expect(result.bytes[0]).toBe(0x83) // Definite-length array
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('should throw when trying indefinite array via collection encoder in canonical mode', () => {
|
|
518
|
+
const { encodeArray } = useCborCollectionEncoder({ canonical: true })
|
|
519
|
+
|
|
520
|
+
expect(() => encodeArray([1, 2, 3], { indefinite: true }))
|
|
521
|
+
.toThrow('Indefinite-length encoding not allowed in canonical mode')
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
it('should throw when trying indefinite map via collection encoder in canonical mode', () => {
|
|
525
|
+
const { encodeMap } = useCborCollectionEncoder({ canonical: true })
|
|
526
|
+
|
|
527
|
+
expect(() => encodeMap({ a: 1 }, { indefinite: true }))
|
|
528
|
+
.toThrow('Indefinite-length encoding not allowed in canonical mode')
|
|
529
|
+
})
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
describe('Float encoding uses shortest form', () => {
|
|
533
|
+
it('should encode 0.0 as float16 (shortest form)', () => {
|
|
534
|
+
const { encode } = useCborEncoder()
|
|
535
|
+
|
|
536
|
+
// 0.0 is an integer, will be encoded as integer 0
|
|
537
|
+
const result = encode(0.0)
|
|
538
|
+
expect(result.hex).toBe('00')
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('should encode Infinity as float16', () => {
|
|
542
|
+
const { encode } = useCborEncoder()
|
|
543
|
+
const result = encode(Infinity)
|
|
544
|
+
|
|
545
|
+
// Infinity in float16: 0xf9 0x7c 0x00
|
|
546
|
+
expect(result.bytes[0]).toBe(0xf9)
|
|
547
|
+
expect(result.bytes.length).toBe(3) // float16 = 1 byte header + 2 bytes
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
it('should encode -Infinity as float16', () => {
|
|
551
|
+
const { encode } = useCborEncoder()
|
|
552
|
+
const result = encode(-Infinity)
|
|
553
|
+
|
|
554
|
+
// -Infinity in float16: 0xf9 0xfc 0x00
|
|
555
|
+
expect(result.bytes[0]).toBe(0xf9)
|
|
556
|
+
expect(result.bytes.length).toBe(3)
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
it('should encode NaN as float16', () => {
|
|
560
|
+
const { encode } = useCborEncoder()
|
|
561
|
+
const result = encode(NaN)
|
|
562
|
+
|
|
563
|
+
// NaN in float16: 0xf9 0x7e 0x00
|
|
564
|
+
expect(result.bytes[0]).toBe(0xf9)
|
|
565
|
+
expect(result.bytes.length).toBe(3)
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
it('should encode 0.5 as float16 (exact representation)', () => {
|
|
569
|
+
const { encode } = useCborEncoder()
|
|
570
|
+
const result = encode(0.5)
|
|
571
|
+
|
|
572
|
+
// 0.5 fits in float16: 0xf9 + 2 bytes = f93800
|
|
573
|
+
expect(result.bytes[0]).toBe(0xf9)
|
|
574
|
+
expect(result.bytes.length).toBe(3)
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
it('should encode 1.5 as float16 (exact float16 representation)', () => {
|
|
578
|
+
const { encode } = useCborEncoder()
|
|
579
|
+
const result = encode(1.5)
|
|
580
|
+
|
|
581
|
+
// 1.5 is exactly representable in float16: 0xf9 + 2 bytes
|
|
582
|
+
// float16 = 0 01111 1000000000 = 0x3e00
|
|
583
|
+
expect(result.bytes[0]).toBe(0xf9)
|
|
584
|
+
expect(result.bytes.length).toBe(3)
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it('should encode 1.1 as float64 (no exact float16/32 representation)', () => {
|
|
588
|
+
const { encode } = useCborEncoder()
|
|
589
|
+
const result = encode(1.1)
|
|
590
|
+
|
|
591
|
+
// 1.1 cannot be represented exactly in float16 or float32
|
|
592
|
+
expect(result.bytes[0]).toBe(0xfb) // float64 header
|
|
593
|
+
expect(result.bytes.length).toBe(9) // 1 byte header + 8 bytes
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('should encode 100000.0 as float32 when it fits', () => {
|
|
597
|
+
const { encode } = useCborEncoder()
|
|
598
|
+
const result = encode(100000.5)
|
|
599
|
+
|
|
600
|
+
// 100000.5 might fit in float32 - check the header byte
|
|
601
|
+
// If it fits in float32: 0xfa, else float64: 0xfb
|
|
602
|
+
expect([0xfa, 0xfb]).toContain(result.bytes[0])
|
|
603
|
+
})
|
|
604
|
+
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
describe('CBOR Map Key Diversity', () => {
|
|
608
|
+
describe('Map objects with integer keys', () => {
|
|
609
|
+
it('should encode Map with small integer keys', () => {
|
|
610
|
+
const { encode } = useCborEncoder()
|
|
611
|
+
|
|
612
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
613
|
+
[0, 'zero'],
|
|
614
|
+
[1, 'one'],
|
|
615
|
+
[23, 'twenty-three'],
|
|
616
|
+
])
|
|
617
|
+
|
|
618
|
+
const result = encode(map)
|
|
619
|
+
expect(result.bytes[0]).toBe(0xa3) // Map with 3 entries
|
|
620
|
+
// First key should be 0x00 (integer 0)
|
|
621
|
+
expect(result.bytes[1]).toBe(0x00)
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
it('should encode Map with large integer keys', () => {
|
|
625
|
+
const { encode } = useCborEncoder()
|
|
626
|
+
|
|
627
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
628
|
+
[1000, 'thousand'],
|
|
629
|
+
[65535, 'max-uint16'],
|
|
630
|
+
])
|
|
631
|
+
|
|
632
|
+
const result = encode(map)
|
|
633
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
it('should encode Map with negative integer keys', () => {
|
|
637
|
+
const { encode } = useCborEncoder()
|
|
638
|
+
|
|
639
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
640
|
+
[-1, 'neg-one'],
|
|
641
|
+
[-100, 'neg-hundred'],
|
|
642
|
+
])
|
|
643
|
+
|
|
644
|
+
const result = encode(map)
|
|
645
|
+
expect(result.bytes[0]).toBe(0xa2)
|
|
646
|
+
// First key -1 = 0x20
|
|
647
|
+
expect(result.bytes[1]).toBe(0x20)
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
describe('Map objects with Uint8Array keys', () => {
|
|
652
|
+
it('should encode Map with byte string keys', () => {
|
|
653
|
+
const { encode } = useCborEncoder()
|
|
654
|
+
|
|
655
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
656
|
+
[new Uint8Array([0x01, 0x02]), 'first'],
|
|
657
|
+
[new Uint8Array([0x03, 0x04]), 'second'],
|
|
658
|
+
])
|
|
659
|
+
|
|
660
|
+
const result = encode(map)
|
|
661
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
662
|
+
// First key: byte string header for 2 bytes = 0x42
|
|
663
|
+
expect(result.bytes[1]).toBe(0x42)
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
it('should encode Map with empty byte string key', () => {
|
|
667
|
+
const { encode } = useCborEncoder()
|
|
668
|
+
|
|
669
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
670
|
+
[new Uint8Array([]), 'empty'],
|
|
671
|
+
])
|
|
672
|
+
|
|
673
|
+
const result = encode(map)
|
|
674
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
675
|
+
// Empty byte string = 0x40
|
|
676
|
+
expect(result.bytes[1]).toBe(0x40)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('should encode Map with 32-byte hash key (Cardano-style)', () => {
|
|
680
|
+
const { encode } = useCborEncoder()
|
|
681
|
+
|
|
682
|
+
const hash = new Uint8Array(32).fill(0xab)
|
|
683
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
684
|
+
[hash, 1000000],
|
|
685
|
+
])
|
|
686
|
+
|
|
687
|
+
const result = encode(map)
|
|
688
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
689
|
+
// 32-byte byte string: 0x58 0x20
|
|
690
|
+
expect(result.bytes[1]).toBe(0x58)
|
|
691
|
+
expect(result.bytes[2]).toBe(0x20)
|
|
692
|
+
})
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
describe('Map objects with boolean keys', () => {
|
|
696
|
+
it('should encode Map with true key', () => {
|
|
697
|
+
const { encode } = useCborEncoder()
|
|
698
|
+
|
|
699
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
700
|
+
[true, 'yes'],
|
|
701
|
+
])
|
|
702
|
+
|
|
703
|
+
const result = encode(map)
|
|
704
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
705
|
+
// true = 0xf5
|
|
706
|
+
expect(result.bytes[1]).toBe(0xf5)
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
it('should encode Map with false key', () => {
|
|
710
|
+
const { encode } = useCborEncoder()
|
|
711
|
+
|
|
712
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
713
|
+
[false, 'no'],
|
|
714
|
+
])
|
|
715
|
+
|
|
716
|
+
const result = encode(map)
|
|
717
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
718
|
+
// false = 0xf4
|
|
719
|
+
expect(result.bytes[1]).toBe(0xf4)
|
|
720
|
+
})
|
|
721
|
+
|
|
722
|
+
it('should encode Map with both boolean keys', () => {
|
|
723
|
+
const { encode } = useCborEncoder()
|
|
724
|
+
|
|
725
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
726
|
+
[false, 0],
|
|
727
|
+
[true, 1],
|
|
728
|
+
])
|
|
729
|
+
|
|
730
|
+
const result = encode(map)
|
|
731
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
732
|
+
})
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
describe('Map objects with null keys', () => {
|
|
736
|
+
it('should encode Map with null key', () => {
|
|
737
|
+
const { encode } = useCborEncoder()
|
|
738
|
+
|
|
739
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
740
|
+
[null, 'nothing'],
|
|
741
|
+
])
|
|
742
|
+
|
|
743
|
+
const result = encode(map)
|
|
744
|
+
expect(result.bytes[0]).toBe(0xa1) // Map with 1 entry
|
|
745
|
+
// null = 0xf6
|
|
746
|
+
expect(result.bytes[1]).toBe(0xf6)
|
|
747
|
+
})
|
|
748
|
+
|
|
749
|
+
it('should encode Map with null key and integer value', () => {
|
|
750
|
+
const { encode } = useCborEncoder()
|
|
751
|
+
|
|
752
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
753
|
+
[null, 42],
|
|
754
|
+
])
|
|
755
|
+
|
|
756
|
+
const result = encode(map)
|
|
757
|
+
expect(result.bytes[0]).toBe(0xa1)
|
|
758
|
+
expect(result.bytes[1]).toBe(0xf6) // null key
|
|
759
|
+
expect(result.bytes[2]).toBe(0x18) // integer 42
|
|
760
|
+
expect(result.bytes[3]).toBe(0x2a) // = 42
|
|
761
|
+
})
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
describe('Mixed type keys in same map', () => {
|
|
765
|
+
it('should encode Map with integer and string keys', () => {
|
|
766
|
+
const { encode } = useCborEncoder()
|
|
767
|
+
|
|
768
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
769
|
+
[1, 'integer-key'],
|
|
770
|
+
['a', 'string-key'],
|
|
771
|
+
])
|
|
772
|
+
|
|
773
|
+
const result = encode(map)
|
|
774
|
+
expect(result.bytes[0]).toBe(0xa2) // Map with 2 entries
|
|
775
|
+
})
|
|
776
|
+
|
|
777
|
+
it('should encode Map with all diverse key types', () => {
|
|
778
|
+
const { encode } = useCborEncoder()
|
|
779
|
+
|
|
780
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
781
|
+
[1, 'int'],
|
|
782
|
+
['text', 'str'],
|
|
783
|
+
[new Uint8Array([0xff]), 'bytes'],
|
|
784
|
+
[true, 'bool'],
|
|
785
|
+
[null, 'null'],
|
|
786
|
+
])
|
|
787
|
+
|
|
788
|
+
const result = encode(map)
|
|
789
|
+
expect(result.bytes[0]).toBe(0xa5) // Map with 5 entries
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
it('should encode Map with integer and byte string keys in canonical mode', () => {
|
|
793
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
794
|
+
|
|
795
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
796
|
+
[new Uint8Array([0x01, 0x02]), 'bytes'], // 0x42 0x01 0x02 (3 bytes)
|
|
797
|
+
[1, 'int'], // 0x01 (1 byte)
|
|
798
|
+
])
|
|
799
|
+
|
|
800
|
+
const result = encode(map)
|
|
801
|
+
const hex = result.hex
|
|
802
|
+
|
|
803
|
+
// Integer key 1 (0x01, 1 byte) should come before byte string (0x420102, 3 bytes)
|
|
804
|
+
const posInt = hex.indexOf('01')
|
|
805
|
+
const posBytes = hex.indexOf('420102')
|
|
806
|
+
|
|
807
|
+
expect(posInt).toBeLessThan(posBytes)
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
it('should encode Map with negative and positive integer keys', () => {
|
|
811
|
+
const { encode } = useCborEncoder()
|
|
812
|
+
|
|
813
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
814
|
+
[-1, 'negative'],
|
|
815
|
+
[0, 'zero'],
|
|
816
|
+
[1, 'positive'],
|
|
817
|
+
])
|
|
818
|
+
|
|
819
|
+
const result = encode(map)
|
|
820
|
+
expect(result.bytes[0]).toBe(0xa3) // Map with 3 entries
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
it('should handle canonical sorting across all key types', () => {
|
|
824
|
+
const { encode } = useCborEncoder({ canonical: true })
|
|
825
|
+
|
|
826
|
+
const map = new Map<EncodableValue, EncodableValue>([
|
|
827
|
+
['longer-key', 3], // text string (many bytes)
|
|
828
|
+
[1, 1], // integer (1 byte)
|
|
829
|
+
[null, 2], // null (1 byte: f6)
|
|
830
|
+
])
|
|
831
|
+
|
|
832
|
+
const result = encode(map)
|
|
833
|
+
const hex = result.hex
|
|
834
|
+
|
|
835
|
+
// 1 byte keys should come before multi-byte keys
|
|
836
|
+
// Integer 1 = 0x01 (1 byte), null = 0xf6 (1 byte)
|
|
837
|
+
// Both 1-byte keys before the long string key
|
|
838
|
+
const posLong = hex.indexOf('6a6c6f6e6765722d6b6579') // "longer-key" text
|
|
839
|
+
const posInt = hex.indexOf('0101') // key=1, value=1
|
|
840
|
+
const posNull = hex.indexOf('f602') // key=null, value=2
|
|
841
|
+
|
|
842
|
+
// Both short keys should appear before the long string key
|
|
843
|
+
expect(posInt).toBeLessThan(posLong)
|
|
844
|
+
expect(posNull).toBeLessThan(posLong)
|
|
845
|
+
})
|
|
846
|
+
})
|
|
847
|
+
})
|