@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,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Collection Encoder Composable
|
|
3
|
+
* Handles Major Type 4 (Arrays) and Major Type 5 (Maps)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, EncodeOptions, EncodeContext, EncodableValue } from '../types'
|
|
8
|
+
import { DEFAULT_ENCODE_OPTIONS, INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL } from '../types'
|
|
9
|
+
import { bytesToHex, concatenateUint8Arrays, compareBytes } from '../utils'
|
|
10
|
+
import { useCborIntegerEncoder } from './useCborIntegerEncoder'
|
|
11
|
+
import { useCborStringEncoder } from './useCborStringEncoder'
|
|
12
|
+
import { useCborByteString, useCborTextString } from '../../parser/composables/useCborStringTypes'
|
|
13
|
+
|
|
14
|
+
interface CollectionEncodeOptions {
|
|
15
|
+
indefinite?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CBOR Collection Encoder Composable
|
|
20
|
+
*
|
|
21
|
+
* Provides functions to encode arrays and maps:
|
|
22
|
+
* - Major Type 4: Arrays
|
|
23
|
+
* - Major Type 5: Maps (objects or Map instances)
|
|
24
|
+
*
|
|
25
|
+
* Supports both definite-length and indefinite-length encoding.
|
|
26
|
+
* Handles canonical encoding with sorted map keys.
|
|
27
|
+
* Enforces depth and size limits.
|
|
28
|
+
*
|
|
29
|
+
* @param options - Global encoder options
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const { encodeArray, encodeMap } = useCborCollectionEncoder()
|
|
34
|
+
*
|
|
35
|
+
* // Encode array
|
|
36
|
+
* const arr = [1, 2, 3]
|
|
37
|
+
* const result1 = encodeArray(arr)
|
|
38
|
+
* // result1: { bytes: Uint8Array([0x83, 0x01, 0x02, 0x03]), hex: '83010203' }
|
|
39
|
+
*
|
|
40
|
+
* // Encode map
|
|
41
|
+
* const map = { amount: 1000000 }
|
|
42
|
+
* const result2 = encodeMap(map)
|
|
43
|
+
* // result2: { bytes: ..., hex: 'a166616d6f756e741a000f4240' }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function useCborCollectionEncoder(globalOptions?: Partial<EncodeOptions>) {
|
|
47
|
+
const options = { ...DEFAULT_ENCODE_OPTIONS, ...globalOptions }
|
|
48
|
+
|
|
49
|
+
// Get other encoders
|
|
50
|
+
const { encodeInteger } = useCborIntegerEncoder()
|
|
51
|
+
const { encodeTextString, encodeByteString } = useCborStringEncoder(globalOptions)
|
|
52
|
+
const { isCborByteString } = useCborByteString()
|
|
53
|
+
const { isCborTextString } = useCborTextString()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Encode a single value (recursive)
|
|
57
|
+
*
|
|
58
|
+
* NOTE: This is set by the main encoder to enable recursive encoding of all types
|
|
59
|
+
* including tagged values. This avoids circular dependencies.
|
|
60
|
+
*/
|
|
61
|
+
let mainEncode: ((value: EncodableValue) => EncodeResult) | null = null
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Set the main encode function for recursive encoding
|
|
65
|
+
* This must be called by useCborEncoder before encoding collections
|
|
66
|
+
*/
|
|
67
|
+
const setMainEncode = (encodeFn: (value: EncodableValue) => EncodeResult) => {
|
|
68
|
+
mainEncode = encodeFn
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Encode a single value (recursive)
|
|
73
|
+
*
|
|
74
|
+
* @param value - Value to encode
|
|
75
|
+
* @param ctx - Encoding context for depth tracking
|
|
76
|
+
* @returns Encoded CBOR bytes
|
|
77
|
+
*/
|
|
78
|
+
const encodeValue = (value: EncodableValue, ctx: EncodeContext): Uint8Array => {
|
|
79
|
+
// Check depth limit
|
|
80
|
+
if (ctx.depth > ctx.options.maxDepth) {
|
|
81
|
+
throw new Error('Maximum nesting depth exceeded')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Encode based on type
|
|
85
|
+
if (value === null || value === undefined) {
|
|
86
|
+
// null/undefined -> CBOR null (0xf6)
|
|
87
|
+
return new Uint8Array([0xf6])
|
|
88
|
+
}
|
|
89
|
+
else if (typeof value === 'boolean') {
|
|
90
|
+
// true: 0xf5, false: 0xf4
|
|
91
|
+
return new Uint8Array([value ? 0xf5 : 0xf4])
|
|
92
|
+
}
|
|
93
|
+
else if (typeof value === 'number' || typeof value === 'bigint') {
|
|
94
|
+
return encodeInteger(value).bytes
|
|
95
|
+
}
|
|
96
|
+
else if (typeof value === 'string' || isCborTextString(value)) {
|
|
97
|
+
return encodeTextString(value).bytes
|
|
98
|
+
}
|
|
99
|
+
else if (value instanceof Uint8Array || isCborByteString(value)) {
|
|
100
|
+
return encodeByteString(value).bytes
|
|
101
|
+
}
|
|
102
|
+
else if (Array.isArray(value)) {
|
|
103
|
+
// Recursive array encoding - check for indefinite marker
|
|
104
|
+
const isIndefinite = (value as any)[INDEFINITE_SYMBOL] === true
|
|
105
|
+
const newCtx = { ...ctx, depth: ctx.depth + 1 }
|
|
106
|
+
|
|
107
|
+
if (isIndefinite) {
|
|
108
|
+
// Use indefinite encoding - but need to recursively encode items
|
|
109
|
+
const parts: Uint8Array[] = [new Uint8Array([0x9f])] // Start marker
|
|
110
|
+
for (const item of value) {
|
|
111
|
+
const encoded = encodeValue(item, newCtx)
|
|
112
|
+
parts.push(encoded)
|
|
113
|
+
}
|
|
114
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
115
|
+
return concatenateUint8Arrays(parts)
|
|
116
|
+
} else {
|
|
117
|
+
return encodeArrayInternal(value, newCtx).bytes
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (typeof value === 'object' && value !== null && 'tag' in value && 'value' in value) {
|
|
121
|
+
// Tagged value - delegate to main encoder if available
|
|
122
|
+
if (mainEncode) {
|
|
123
|
+
return mainEncode(value).bytes
|
|
124
|
+
}
|
|
125
|
+
throw new Error('Tagged value encoding requires main encoder to be set')
|
|
126
|
+
}
|
|
127
|
+
else if (value instanceof Map || (typeof value === 'object' && value !== null)) {
|
|
128
|
+
// Recursive map encoding - check for indefinite marker
|
|
129
|
+
const isIndefinite = (value as any)[INDEFINITE_SYMBOL] === true
|
|
130
|
+
const newCtx = { ...ctx, depth: ctx.depth + 1 }
|
|
131
|
+
|
|
132
|
+
if (isIndefinite) {
|
|
133
|
+
// Use indefinite encoding
|
|
134
|
+
const entries: Array<[EncodableValue, EncodableValue]> =
|
|
135
|
+
value instanceof Map
|
|
136
|
+
? Array.from(value.entries())
|
|
137
|
+
: Object.entries(value)
|
|
138
|
+
|
|
139
|
+
const parts: Uint8Array[] = [new Uint8Array([0xbf])] // Start marker
|
|
140
|
+
for (const [key, val] of entries) {
|
|
141
|
+
const encodedKey = encodeValue(key, newCtx)
|
|
142
|
+
const encodedValue = encodeValue(val, newCtx)
|
|
143
|
+
parts.push(encodedKey)
|
|
144
|
+
parts.push(encodedValue)
|
|
145
|
+
}
|
|
146
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
147
|
+
return concatenateUint8Arrays(parts)
|
|
148
|
+
} else {
|
|
149
|
+
return encodeMapInternal(value as Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue }, newCtx).bytes
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
throw new Error(`Unsupported value type: ${typeof value}`)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Encode length header for arrays and maps
|
|
159
|
+
*
|
|
160
|
+
* @param majorType - Major type (4 for arrays, 5 for maps)
|
|
161
|
+
* @param length - Number of elements
|
|
162
|
+
* @returns Encoded length header
|
|
163
|
+
*/
|
|
164
|
+
const encodeLengthHeader = (majorType: number, length: number): Uint8Array => {
|
|
165
|
+
const baseValue = majorType << 5
|
|
166
|
+
|
|
167
|
+
if (length <= 23) {
|
|
168
|
+
return new Uint8Array([baseValue | length])
|
|
169
|
+
}
|
|
170
|
+
else if (length <= 255) {
|
|
171
|
+
return new Uint8Array([baseValue | 24, length])
|
|
172
|
+
}
|
|
173
|
+
else if (length <= 65535) {
|
|
174
|
+
return new Uint8Array([baseValue | 25, length >> 8, length & 0xff])
|
|
175
|
+
}
|
|
176
|
+
else if (length <= 4294967295) {
|
|
177
|
+
return new Uint8Array([
|
|
178
|
+
baseValue | 26,
|
|
179
|
+
(length >> 24) & 0xff,
|
|
180
|
+
(length >> 16) & 0xff,
|
|
181
|
+
(length >> 8) & 0xff,
|
|
182
|
+
length & 0xff
|
|
183
|
+
])
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
throw new Error('Collection too large to encode')
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Internal array encoding (used by recursive calls)
|
|
192
|
+
*/
|
|
193
|
+
const encodeArrayInternal = (
|
|
194
|
+
array: EncodableValue[],
|
|
195
|
+
ctx: EncodeContext
|
|
196
|
+
): EncodeResult => {
|
|
197
|
+
const header = encodeLengthHeader(4, array.length)
|
|
198
|
+
const parts: Uint8Array[] = [header]
|
|
199
|
+
|
|
200
|
+
// Encode each element
|
|
201
|
+
for (const item of array) {
|
|
202
|
+
const encoded = encodeValue(item, ctx)
|
|
203
|
+
parts.push(encoded)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = concatenateUint8Arrays(parts)
|
|
207
|
+
|
|
208
|
+
// Check output size
|
|
209
|
+
ctx.bytesWritten += result.length
|
|
210
|
+
if (ctx.bytesWritten > ctx.options.maxOutputSize) {
|
|
211
|
+
throw new Error('Encoded output exceeds maximum size')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
bytes: result,
|
|
216
|
+
hex: bytesToHex(result)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Encode array (Major Type 4)
|
|
222
|
+
*
|
|
223
|
+
* @param array - Array of encodable values
|
|
224
|
+
* @param encodeOptions - Encoding options
|
|
225
|
+
* @returns Encoded CBOR bytes and hex string
|
|
226
|
+
*/
|
|
227
|
+
const encodeArray = (
|
|
228
|
+
array: EncodableValue[],
|
|
229
|
+
encodeOptions?: CollectionEncodeOptions
|
|
230
|
+
): EncodeResult => {
|
|
231
|
+
// Check if array was originally encoded with indefinite length
|
|
232
|
+
const isIndefinite = (array as any)[INDEFINITE_SYMBOL] === true
|
|
233
|
+
|
|
234
|
+
// Handle indefinite-length encoding
|
|
235
|
+
if (encodeOptions?.indefinite || isIndefinite) {
|
|
236
|
+
if (options.canonical) {
|
|
237
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
238
|
+
}
|
|
239
|
+
return encodeArrayIndefinite(array)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Definite-length encoding
|
|
243
|
+
const ctx: EncodeContext = {
|
|
244
|
+
depth: 0,
|
|
245
|
+
bytesWritten: 0,
|
|
246
|
+
options
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return encodeArrayInternal(array, ctx)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Encode array with indefinite length
|
|
254
|
+
*
|
|
255
|
+
* @param array - Array of encodable values
|
|
256
|
+
* @returns Encoded CBOR bytes and hex string
|
|
257
|
+
*/
|
|
258
|
+
const encodeArrayIndefinite = (array: EncodableValue[]): EncodeResult => {
|
|
259
|
+
if (options.canonical) {
|
|
260
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const parts: Uint8Array[] = [new Uint8Array([0x9f])] // Start marker
|
|
264
|
+
|
|
265
|
+
const ctx: EncodeContext = {
|
|
266
|
+
depth: 0,
|
|
267
|
+
bytesWritten: 0,
|
|
268
|
+
options
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Encode each element
|
|
272
|
+
for (const item of array) {
|
|
273
|
+
const encoded = encodeValue(item, ctx)
|
|
274
|
+
parts.push(encoded)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
278
|
+
|
|
279
|
+
const result = concatenateUint8Arrays(parts)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
bytes: result,
|
|
283
|
+
hex: bytesToHex(result)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Internal map encoding (used by recursive calls)
|
|
289
|
+
*/
|
|
290
|
+
const encodeMapInternal = (
|
|
291
|
+
map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue },
|
|
292
|
+
ctx: EncodeContext
|
|
293
|
+
): EncodeResult => {
|
|
294
|
+
// Check for ALL_ENTRIES_SYMBOL for byte-perfect preservation with duplicates
|
|
295
|
+
let entries: Array<[EncodableValue, EncodableValue]>
|
|
296
|
+
|
|
297
|
+
if ((map as any)[ALL_ENTRIES_SYMBOL]) {
|
|
298
|
+
// Use the preserved entries (includes duplicates and original order)
|
|
299
|
+
entries = (map as any)[ALL_ENTRIES_SYMBOL]
|
|
300
|
+
} else {
|
|
301
|
+
// Convert object to Map if needed
|
|
302
|
+
entries = map instanceof Map
|
|
303
|
+
? Array.from(map.entries())
|
|
304
|
+
: Object.entries(map)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// In canonical mode, sort entries by encoded key (unless using allEntries for byte-perfect)
|
|
308
|
+
if (ctx.options.canonical && !(map as any)[ALL_ENTRIES_SYMBOL]) {
|
|
309
|
+
entries.sort((a, b) => {
|
|
310
|
+
const keyA = encodeValue(a[0], { ...ctx, depth: ctx.depth + 1 })
|
|
311
|
+
const keyB = encodeValue(b[0], { ...ctx, depth: ctx.depth + 1 })
|
|
312
|
+
return compareBytes(keyA, keyB)
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const header = encodeLengthHeader(5, entries.length)
|
|
317
|
+
const parts: Uint8Array[] = [header]
|
|
318
|
+
|
|
319
|
+
// Encode each key-value pair
|
|
320
|
+
for (const [key, value] of entries) {
|
|
321
|
+
const encodedKey = encodeValue(key, ctx)
|
|
322
|
+
const encodedValue = encodeValue(value, ctx)
|
|
323
|
+
parts.push(encodedKey)
|
|
324
|
+
parts.push(encodedValue)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const result = concatenateUint8Arrays(parts)
|
|
328
|
+
|
|
329
|
+
// Check output size
|
|
330
|
+
ctx.bytesWritten += result.length
|
|
331
|
+
if (ctx.bytesWritten > ctx.options.maxOutputSize) {
|
|
332
|
+
throw new Error('Encoded output exceeds maximum size')
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
bytes: result,
|
|
337
|
+
hex: bytesToHex(result)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Encode map (Major Type 5)
|
|
343
|
+
*
|
|
344
|
+
* @param map - Map or plain object
|
|
345
|
+
* @param encodeOptions - Encoding options
|
|
346
|
+
* @returns Encoded CBOR bytes and hex string
|
|
347
|
+
*/
|
|
348
|
+
const encodeMap = (
|
|
349
|
+
map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue },
|
|
350
|
+
encodeOptions?: CollectionEncodeOptions
|
|
351
|
+
): EncodeResult => {
|
|
352
|
+
// Check if map was originally encoded with indefinite length
|
|
353
|
+
const isIndefinite = (map as any)[INDEFINITE_SYMBOL] === true
|
|
354
|
+
|
|
355
|
+
// Handle indefinite-length encoding
|
|
356
|
+
if (encodeOptions?.indefinite || isIndefinite) {
|
|
357
|
+
if (options.canonical) {
|
|
358
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
359
|
+
}
|
|
360
|
+
return encodeMapIndefinite(map)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Definite-length encoding
|
|
364
|
+
const ctx: EncodeContext = {
|
|
365
|
+
depth: 0,
|
|
366
|
+
bytesWritten: 0,
|
|
367
|
+
options
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return encodeMapInternal(map, ctx)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Encode map with indefinite length
|
|
375
|
+
*
|
|
376
|
+
* @param map - Map or plain object
|
|
377
|
+
* @returns Encoded CBOR bytes and hex string
|
|
378
|
+
*/
|
|
379
|
+
const encodeMapIndefinite = (
|
|
380
|
+
map: Map<EncodableValue, EncodableValue> | { [key: string]: EncodableValue }
|
|
381
|
+
): EncodeResult => {
|
|
382
|
+
if (options.canonical) {
|
|
383
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const entries: Array<[EncodableValue, EncodableValue]> =
|
|
387
|
+
map instanceof Map
|
|
388
|
+
? Array.from(map.entries())
|
|
389
|
+
: Object.entries(map)
|
|
390
|
+
|
|
391
|
+
const parts: Uint8Array[] = [new Uint8Array([0xbf])] // Start marker
|
|
392
|
+
|
|
393
|
+
const ctx: EncodeContext = {
|
|
394
|
+
depth: 0,
|
|
395
|
+
bytesWritten: 0,
|
|
396
|
+
options
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Encode each key-value pair
|
|
400
|
+
for (const [key, value] of entries) {
|
|
401
|
+
const encodedKey = encodeValue(key, ctx)
|
|
402
|
+
const encodedValue = encodeValue(value, ctx)
|
|
403
|
+
parts.push(encodedKey)
|
|
404
|
+
parts.push(encodedValue)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
408
|
+
|
|
409
|
+
const result = concatenateUint8Arrays(parts)
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
bytes: result,
|
|
413
|
+
hex: bytesToHex(result)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
encodeArray,
|
|
419
|
+
encodeArrayIndefinite,
|
|
420
|
+
encodeMap,
|
|
421
|
+
encodeMapIndefinite,
|
|
422
|
+
setMainEncode
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main CBOR Encoder Composable
|
|
3
|
+
* High-level API for encoding JavaScript values to CBOR
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, EncodeOptions, EncodableValue, TaggedValue } from '../types'
|
|
8
|
+
import { DEFAULT_ENCODE_OPTIONS } from '../types'
|
|
9
|
+
import { useCborIntegerEncoder } from './useCborIntegerEncoder'
|
|
10
|
+
import { useCborStringEncoder } from './useCborStringEncoder'
|
|
11
|
+
import { useCborCollectionEncoder } from './useCborCollectionEncoder'
|
|
12
|
+
import { useCborSimpleEncoder } from './useCborSimpleEncoder'
|
|
13
|
+
import { useCborTagEncoder } from './useCborTagEncoder'
|
|
14
|
+
import { useCborByteString, useCborTextString } from '../../parser/composables/useCborStringTypes'
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Main CBOR Encoder Composable
|
|
18
|
+
*
|
|
19
|
+
* Provides a unified interface for encoding any JavaScript value to CBOR.
|
|
20
|
+
* Automatically selects the appropriate encoder based on value type.
|
|
21
|
+
*
|
|
22
|
+
* @param options - Global encoder options
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const { encode } = useCborEncoder()
|
|
27
|
+
*
|
|
28
|
+
* // Encode various types
|
|
29
|
+
* encode(42) // Integer
|
|
30
|
+
* encode("hello") // Text string
|
|
31
|
+
* encode([1, 2, 3]) // Array
|
|
32
|
+
* encode({ a: 1 }) // Map
|
|
33
|
+
* encode(true) // Boolean
|
|
34
|
+
* encode(3.14) // Float
|
|
35
|
+
* encode(new Uint8Array([0xff])) // Byte string
|
|
36
|
+
*
|
|
37
|
+
* // With options
|
|
38
|
+
* const { encode: encodeCanonical } = useCborEncoder({ canonical: true })
|
|
39
|
+
* encodeCanonical({ z: 1, a: 2 }) // Keys will be sorted
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useCborEncoder(globalOptions?: Partial<EncodeOptions>) {
|
|
43
|
+
const options = { ...DEFAULT_ENCODE_OPTIONS, ...globalOptions }
|
|
44
|
+
|
|
45
|
+
// Get all specialized encoders
|
|
46
|
+
const { encodeInteger } = useCborIntegerEncoder()
|
|
47
|
+
const { encodeTextString, encodeByteString } = useCborStringEncoder(options)
|
|
48
|
+
const { encodeArray, encodeMap, setMainEncode } = useCborCollectionEncoder(options)
|
|
49
|
+
const { encodeSimple, encodeFloat } = useCborSimpleEncoder(options)
|
|
50
|
+
const { encodeTaggedValue } = useCborTagEncoder()
|
|
51
|
+
|
|
52
|
+
// Get type guards for composable string types
|
|
53
|
+
const { isCborByteString } = useCborByteString()
|
|
54
|
+
const { isCborTextString } = useCborTextString()
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Encode any JavaScript value to CBOR
|
|
58
|
+
*
|
|
59
|
+
* Automatically detects the type and uses the appropriate encoder:
|
|
60
|
+
* - number/bigint → Integer or Float
|
|
61
|
+
* - string → Text string
|
|
62
|
+
* - boolean/null/undefined → Simple values
|
|
63
|
+
* - Uint8Array → Byte string
|
|
64
|
+
* - Array → CBOR array
|
|
65
|
+
* - {tag: number, value: any} → Tagged value
|
|
66
|
+
* - Object/Map → CBOR map
|
|
67
|
+
*
|
|
68
|
+
* @param value - Value to encode
|
|
69
|
+
* @returns Encoded CBOR bytes and hex string
|
|
70
|
+
* @throws Error if value type is unsupported
|
|
71
|
+
*/
|
|
72
|
+
const encode = (value: EncodableValue): EncodeResult => {
|
|
73
|
+
// Handle null/undefined/boolean
|
|
74
|
+
if (value === null || value === undefined || typeof value === 'boolean') {
|
|
75
|
+
return encodeSimple(value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle numbers
|
|
79
|
+
if (typeof value === 'number') {
|
|
80
|
+
// Check if it's an integer
|
|
81
|
+
if (Number.isInteger(value) && Number.isSafeInteger(value)) {
|
|
82
|
+
return encodeInteger(value)
|
|
83
|
+
}
|
|
84
|
+
// It's a float
|
|
85
|
+
return encodeFloat(value)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle bigint
|
|
89
|
+
if (typeof value === 'bigint') {
|
|
90
|
+
return encodeInteger(value)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle composable text strings (check before primitive strings)
|
|
94
|
+
if (isCborTextString(value)) {
|
|
95
|
+
return encodeTextString(value)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Handle strings
|
|
99
|
+
if (typeof value === 'string') {
|
|
100
|
+
return encodeTextString(value)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Handle composable byte strings (check before Uint8Array)
|
|
104
|
+
if (isCborByteString(value)) {
|
|
105
|
+
return encodeByteString(value)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle Uint8Array (byte strings)
|
|
109
|
+
if (value instanceof Uint8Array) {
|
|
110
|
+
return encodeByteString(value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle arrays
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
return encodeArray(value)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle Map
|
|
119
|
+
if (value instanceof Map) {
|
|
120
|
+
return encodeMap(value)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle tagged values (MUST come before plain objects)
|
|
124
|
+
// Check for {tag: number, value: any} structure
|
|
125
|
+
if (typeof value === 'object' && value !== null && 'tag' in value && 'value' in value && typeof (value as { tag: unknown }).tag === 'number') {
|
|
126
|
+
return encodeTaggedValue(value as TaggedValue, encode)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Handle plain objects
|
|
130
|
+
if (typeof value === 'object' && value !== null) {
|
|
131
|
+
return encodeMap(value as { [key: string]: EncodableValue })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
throw new Error(`Unsupported value type: ${typeof value}`)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Set the main encode function for recursive collection encoding
|
|
138
|
+
// This allows the collection encoder to handle nested tagged values
|
|
139
|
+
setMainEncode(encode)
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Encode value and return only the hex string
|
|
143
|
+
*
|
|
144
|
+
* @param value - Value to encode
|
|
145
|
+
* @returns Hex string representation
|
|
146
|
+
*/
|
|
147
|
+
const encodeToHex = (value: EncodableValue): string => {
|
|
148
|
+
return encode(value).hex
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Encode value and return only the bytes
|
|
153
|
+
*
|
|
154
|
+
* @param value - Value to encode
|
|
155
|
+
* @returns Uint8Array bytes
|
|
156
|
+
*/
|
|
157
|
+
const encodeToBytes = (value: EncodableValue): Uint8Array => {
|
|
158
|
+
return encode(value).bytes
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Encode multiple values in sequence
|
|
163
|
+
*
|
|
164
|
+
* Useful for CBOR sequences (RFC 8742)
|
|
165
|
+
*
|
|
166
|
+
* @param values - Values to encode
|
|
167
|
+
* @returns Concatenated CBOR encoding
|
|
168
|
+
*/
|
|
169
|
+
const encodeSequence = (values: EncodableValue[]): EncodeResult => {
|
|
170
|
+
const allBytes: Uint8Array[] = []
|
|
171
|
+
|
|
172
|
+
for (const value of values) {
|
|
173
|
+
const result = encode(value)
|
|
174
|
+
allBytes.push(result.bytes)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Concatenate all encoded values
|
|
178
|
+
const totalLength = allBytes.reduce((sum, arr) => sum + arr.length, 0)
|
|
179
|
+
const concatenated = new Uint8Array(totalLength)
|
|
180
|
+
|
|
181
|
+
let offset = 0
|
|
182
|
+
for (const bytes of allBytes) {
|
|
183
|
+
concatenated.set(bytes, offset)
|
|
184
|
+
offset += bytes.length
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const hex = Array.from(concatenated)
|
|
188
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
189
|
+
.join('')
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
bytes: concatenated,
|
|
193
|
+
hex
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
encode,
|
|
199
|
+
encodeToHex,
|
|
200
|
+
encodeToBytes,
|
|
201
|
+
encodeSequence
|
|
202
|
+
}
|
|
203
|
+
}
|