@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,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR String Encoder Composable
|
|
3
|
+
* Handles Major Type 2 (Byte Strings) and Major Type 3 (Text Strings)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, EncodeOptions, CborByteString, CborTextString } from '../types'
|
|
8
|
+
import { DEFAULT_ENCODE_OPTIONS, INDEFINITE_SYMBOL } from '../types'
|
|
9
|
+
import { bytesToHex, concatenateUint8Arrays, writeUint, writeBigUint } from '../utils'
|
|
10
|
+
import { useCborByteString, useCborTextString } from '../../parser/composables/useCborStringTypes'
|
|
11
|
+
|
|
12
|
+
interface StringEncodeOptions {
|
|
13
|
+
indefinite?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CBOR String Encoder Composable
|
|
18
|
+
*
|
|
19
|
+
* Provides functions to encode byte strings and text strings:
|
|
20
|
+
* - Major Type 2: Byte strings (Uint8Array)
|
|
21
|
+
* - Major Type 3: Text strings (UTF-8 encoded)
|
|
22
|
+
*
|
|
23
|
+
* Supports both definite-length and indefinite-length encoding.
|
|
24
|
+
*
|
|
25
|
+
* @param options - Global encoder options
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* const { encodeByteString, encodeTextString } = useCborStringEncoder()
|
|
30
|
+
*
|
|
31
|
+
* // Encode byte string
|
|
32
|
+
* const bytes = new Uint8Array([0x01, 0x02, 0x03])
|
|
33
|
+
* const result1 = encodeByteString(bytes)
|
|
34
|
+
* // result1: { bytes: Uint8Array([0x43, 0x01, 0x02, 0x03]), hex: '43010203' }
|
|
35
|
+
*
|
|
36
|
+
* // Encode text string
|
|
37
|
+
* const result2 = encodeTextString('Hello')
|
|
38
|
+
* // result2: { bytes: Uint8Array([0x65, 0x48, 0x65, 0x6c, 0x6c, 0x6f]), hex: '6548656c6c6f' }
|
|
39
|
+
*
|
|
40
|
+
* // Indefinite-length encoding
|
|
41
|
+
* const chunks = [new Uint8Array([0xaa]), new Uint8Array([0xbb])]
|
|
42
|
+
* const result3 = encodeByteStringIndefinite(chunks)
|
|
43
|
+
* // result3: { bytes: Uint8Array([0x5f, 0x41, 0xaa, 0x41, 0xbb, 0xff]), hex: '5f41aa41bbff' }
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function useCborStringEncoder(globalOptions?: Partial<EncodeOptions>) {
|
|
47
|
+
const options = { ...DEFAULT_ENCODE_OPTIONS, ...globalOptions }
|
|
48
|
+
const { isCborByteString } = useCborByteString()
|
|
49
|
+
const { isCborTextString } = useCborTextString()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Encode the length header for a string
|
|
53
|
+
*
|
|
54
|
+
* @param majorType - Major type (2 for bytes, 3 for text)
|
|
55
|
+
* @param length - Length of the string in bytes
|
|
56
|
+
* @returns Encoded length header
|
|
57
|
+
*/
|
|
58
|
+
const encodeLengthHeader = (majorType: number, length: number): Uint8Array => {
|
|
59
|
+
const baseValue = majorType << 5
|
|
60
|
+
|
|
61
|
+
// Direct encoding (0-23)
|
|
62
|
+
if (length <= 23) {
|
|
63
|
+
return new Uint8Array([baseValue | length])
|
|
64
|
+
}
|
|
65
|
+
// 1-byte length (24-255)
|
|
66
|
+
else if (length <= 255) {
|
|
67
|
+
return new Uint8Array([baseValue | 24, length])
|
|
68
|
+
}
|
|
69
|
+
// 2-byte length (256-65535)
|
|
70
|
+
else if (length <= 65535) {
|
|
71
|
+
const lengthBytes = writeUint(length, 2)
|
|
72
|
+
return new Uint8Array([baseValue | 25, ...lengthBytes])
|
|
73
|
+
}
|
|
74
|
+
// 4-byte length (65536-4294967295)
|
|
75
|
+
else if (length <= 4294967295) {
|
|
76
|
+
const lengthBytes = writeUint(length, 4)
|
|
77
|
+
return new Uint8Array([baseValue | 26, ...lengthBytes])
|
|
78
|
+
}
|
|
79
|
+
// 8-byte length (> 4294967295)
|
|
80
|
+
else {
|
|
81
|
+
const lengthBytes = length > Number.MAX_SAFE_INTEGER
|
|
82
|
+
? writeBigUint(BigInt(length), 8)
|
|
83
|
+
: writeUint(length, 8)
|
|
84
|
+
return new Uint8Array([baseValue | 27, ...lengthBytes])
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Encode byte string (Major Type 2)
|
|
90
|
+
*
|
|
91
|
+
* Can encode either:
|
|
92
|
+
* - Definite-length: Single Uint8Array
|
|
93
|
+
* - Indefinite-length: Array of Uint8Array chunks (if indefinite option is set)
|
|
94
|
+
*
|
|
95
|
+
* @param data - Byte data or array of chunks
|
|
96
|
+
* @param encodeOptions - Encoding options
|
|
97
|
+
* @returns Encoded CBOR bytes and hex string
|
|
98
|
+
* @throws Error if indefinite encoding is used in canonical mode
|
|
99
|
+
*/
|
|
100
|
+
const encodeByteString = (
|
|
101
|
+
data: Uint8Array | Uint8Array[] | CborByteString,
|
|
102
|
+
encodeOptions?: StringEncodeOptions
|
|
103
|
+
): EncodeResult => {
|
|
104
|
+
// Check if it's a CborByteString with indefinite marker
|
|
105
|
+
const isIndefinite = isCborByteString(data) && (data as any)[INDEFINITE_SYMBOL] === true
|
|
106
|
+
|
|
107
|
+
// Handle indefinite-length encoding
|
|
108
|
+
if (encodeOptions?.indefinite || Array.isArray(data) || isIndefinite) {
|
|
109
|
+
if (options.canonical) {
|
|
110
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If it's a CborByteString with chunks, use the original chunks
|
|
114
|
+
if (isCborByteString(data) && data.chunks) {
|
|
115
|
+
return encodeByteStringIndefinite(data.chunks)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Otherwise, get the actual bytes and encode as single chunk
|
|
119
|
+
const bytes = isCborByteString(data) ? data.bytes : (Array.isArray(data) ? data : [data])
|
|
120
|
+
return encodeByteStringIndefinite(Array.isArray(bytes) ? bytes : [bytes])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Definite-length encoding - extract bytes from CborByteString if needed
|
|
124
|
+
const bytes = isCborByteString(data) ? data.bytes : (data as Uint8Array)
|
|
125
|
+
const header = encodeLengthHeader(2, bytes.length)
|
|
126
|
+
const result = concatenateUint8Arrays([header, bytes])
|
|
127
|
+
|
|
128
|
+
// Check output size limit
|
|
129
|
+
if (result.length > options.maxOutputSize) {
|
|
130
|
+
throw new Error('Encoded output exceeds maximum size')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
bytes: result,
|
|
135
|
+
hex: bytesToHex(result)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Encode byte string with indefinite length (Major Type 2)
|
|
141
|
+
*
|
|
142
|
+
* Format: 0x5f + chunk1 + chunk2 + ... + 0xff
|
|
143
|
+
* Each chunk is a definite-length byte string.
|
|
144
|
+
*
|
|
145
|
+
* @param chunks - Array of byte string chunks
|
|
146
|
+
* @returns Encoded CBOR bytes and hex string
|
|
147
|
+
*/
|
|
148
|
+
const encodeByteStringIndefinite = (chunks: Uint8Array[]): EncodeResult => {
|
|
149
|
+
if (options.canonical) {
|
|
150
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const parts: Uint8Array[] = [new Uint8Array([0x5f])] // Start marker
|
|
154
|
+
|
|
155
|
+
// Encode each chunk as definite-length byte string
|
|
156
|
+
for (const chunk of chunks) {
|
|
157
|
+
const header = encodeLengthHeader(2, chunk.length)
|
|
158
|
+
parts.push(header)
|
|
159
|
+
parts.push(chunk)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
163
|
+
|
|
164
|
+
const result = concatenateUint8Arrays(parts)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
bytes: result,
|
|
168
|
+
hex: bytesToHex(result)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Encode text string (Major Type 3)
|
|
174
|
+
*
|
|
175
|
+
* Text strings are encoded as UTF-8 bytes.
|
|
176
|
+
*
|
|
177
|
+
* @param text - Text string to encode
|
|
178
|
+
* @param _encodeOptions - Encoding options (reserved for future use)
|
|
179
|
+
* @returns Encoded CBOR bytes and hex string
|
|
180
|
+
*/
|
|
181
|
+
const encodeTextString = (
|
|
182
|
+
text: string | CborTextString,
|
|
183
|
+
encodeOptions?: StringEncodeOptions
|
|
184
|
+
): EncodeResult => {
|
|
185
|
+
// Check if it's a CborTextString with indefinite marker
|
|
186
|
+
const isIndefinite = isCborTextString(text) && (text as any)[INDEFINITE_SYMBOL] === true
|
|
187
|
+
|
|
188
|
+
// Handle indefinite-length encoding
|
|
189
|
+
if (encodeOptions?.indefinite || isIndefinite) {
|
|
190
|
+
if (options.canonical) {
|
|
191
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// If it's a CborTextString with chunks, use the original chunks
|
|
195
|
+
if (isCborTextString(text) && text.chunks) {
|
|
196
|
+
return encodeTextStringIndefinite(text.chunks)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Otherwise, get the actual text and encode as single chunk
|
|
200
|
+
const textStr = isCborTextString(text) ? text.text : text
|
|
201
|
+
return encodeTextStringIndefinite([textStr])
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Definite-length encoding - extract text from CborTextString if needed
|
|
205
|
+
const textStr = isCborTextString(text) ? text.text : text
|
|
206
|
+
|
|
207
|
+
// Convert string to UTF-8 bytes
|
|
208
|
+
const encoder = new TextEncoder()
|
|
209
|
+
const utf8Bytes = encoder.encode(textStr)
|
|
210
|
+
|
|
211
|
+
const header = encodeLengthHeader(3, utf8Bytes.length)
|
|
212
|
+
const result = concatenateUint8Arrays([header, utf8Bytes])
|
|
213
|
+
|
|
214
|
+
// Check output size limit
|
|
215
|
+
if (result.length > options.maxOutputSize) {
|
|
216
|
+
throw new Error('Encoded output exceeds maximum size')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
bytes: result,
|
|
221
|
+
hex: bytesToHex(result)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Encode text string with indefinite length (Major Type 3)
|
|
227
|
+
*
|
|
228
|
+
* Format: 0x7f + chunk1 + chunk2 + ... + 0xff
|
|
229
|
+
* Each chunk is a definite-length text string.
|
|
230
|
+
*
|
|
231
|
+
* @param chunks - Array of text string chunks
|
|
232
|
+
* @returns Encoded CBOR bytes and hex string
|
|
233
|
+
*/
|
|
234
|
+
const encodeTextStringIndefinite = (chunks: string[]): EncodeResult => {
|
|
235
|
+
if (options.canonical) {
|
|
236
|
+
throw new Error('Indefinite-length encoding not allowed in canonical mode')
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const parts: Uint8Array[] = [new Uint8Array([0x7f])] // Start marker
|
|
240
|
+
|
|
241
|
+
// Encode each chunk as definite-length text string
|
|
242
|
+
const encoder = new TextEncoder()
|
|
243
|
+
for (const chunk of chunks) {
|
|
244
|
+
const utf8Bytes = encoder.encode(chunk)
|
|
245
|
+
const header = encodeLengthHeader(3, utf8Bytes.length)
|
|
246
|
+
parts.push(header)
|
|
247
|
+
parts.push(utf8Bytes)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
parts.push(new Uint8Array([0xff])) // Break marker
|
|
251
|
+
|
|
252
|
+
const result = concatenateUint8Arrays(parts)
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
bytes: result,
|
|
256
|
+
hex: bytesToHex(result)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
encodeByteString,
|
|
262
|
+
encodeByteStringIndefinite,
|
|
263
|
+
encodeTextString,
|
|
264
|
+
encodeTextStringIndefinite
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Tag Encoder Composable
|
|
3
|
+
* Handles Major Type 6 (Semantic Tags)
|
|
4
|
+
* Following RFC 8949 specification
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { EncodeResult, TaggedValue, EncodableValue } from '../types'
|
|
8
|
+
import { bytesToHex, writeUint, writeBigUint } from '../utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CBOR Tag Encoder Composable
|
|
12
|
+
*
|
|
13
|
+
* Provides functions to encode tagged values to CBOR format:
|
|
14
|
+
* - Major Type 6: Semantic tags (0 to 2^64-1)
|
|
15
|
+
*
|
|
16
|
+
* Tags provide semantic meaning to CBOR values:
|
|
17
|
+
* - Tag 0: Date/time string (RFC 3339)
|
|
18
|
+
* - Tag 1: Epoch timestamp
|
|
19
|
+
* - Tag 2: Positive bignum
|
|
20
|
+
* - Tag 3: Negative bignum
|
|
21
|
+
* - Tag 258: Cardano set (CIP-0005)
|
|
22
|
+
* - And many more...
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const { encodeTag } = useCborTagEncoder()
|
|
27
|
+
*
|
|
28
|
+
* // Encode date/time string (tag 0)
|
|
29
|
+
* const result1 = encodeTag(0, '2013-03-21T20:04:00Z', encode)
|
|
30
|
+
* // result1.hex: 'c074323031332d30332d32315432303a30343a30305a'
|
|
31
|
+
*
|
|
32
|
+
* // Encode positive bignum (tag 2)
|
|
33
|
+
* const result2 = encodeTag(2, new Uint8Array([0x01, 0xff]), encode)
|
|
34
|
+
* // result2.hex: 'c24201ff'
|
|
35
|
+
*
|
|
36
|
+
* // Encode Cardano set (tag 258)
|
|
37
|
+
* const result3 = encodeTag(258, [1, 2, 3], encode)
|
|
38
|
+
* // result3.hex: 'd9010283010203'
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function useCborTagEncoder() {
|
|
42
|
+
/**
|
|
43
|
+
* Encode tag number (Major Type 6 header)
|
|
44
|
+
*
|
|
45
|
+
* Tag numbers use the same encoding rules as unsigned integers:
|
|
46
|
+
* - 0-23: Direct encoding in initial byte (0xc0-0xd7)
|
|
47
|
+
* - 24-255: 0xd8 + 1 byte
|
|
48
|
+
* - 256-65535: 0xd9 + 2 bytes
|
|
49
|
+
* - 65536-4294967295: 0xda + 4 bytes
|
|
50
|
+
* - 4294967296-2^64-1: 0xdb + 8 bytes
|
|
51
|
+
*
|
|
52
|
+
* @param tagNumber - Tag number (0 to 2^64-1)
|
|
53
|
+
* @returns Encoded tag header bytes
|
|
54
|
+
* @throws Error if tag number is negative or >= 2^64
|
|
55
|
+
*/
|
|
56
|
+
const encodeTagNumber = (tagNumber: number | bigint): Uint8Array => {
|
|
57
|
+
// Convert to BigInt for consistent handling
|
|
58
|
+
const bigTag = typeof tagNumber === 'bigint' ? tagNumber : BigInt(tagNumber)
|
|
59
|
+
|
|
60
|
+
// Validate tag is non-negative
|
|
61
|
+
if (bigTag < 0n) {
|
|
62
|
+
throw new Error('Tag number cannot be negative')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Validate tag doesn't exceed 2^64-1
|
|
66
|
+
const MAX_UINT64 = 18446744073709551615n // 2^64 - 1
|
|
67
|
+
if (bigTag > MAX_UINT64) {
|
|
68
|
+
throw new Error('Tag number exceeds maximum (2^64-1)')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let bytes: Uint8Array
|
|
72
|
+
|
|
73
|
+
// Direct encoding (0-23) - Major type 6 (0xc0) + tag number
|
|
74
|
+
if (bigTag <= 23n) {
|
|
75
|
+
bytes = new Uint8Array([0xc0 + Number(bigTag)])
|
|
76
|
+
}
|
|
77
|
+
// 1-byte encoding (24-255) - 0xd8 + 1 byte
|
|
78
|
+
else if (bigTag <= 255n) {
|
|
79
|
+
bytes = new Uint8Array([0xd8, Number(bigTag)])
|
|
80
|
+
}
|
|
81
|
+
// 2-byte encoding (256-65535) - 0xd9 + 2 bytes
|
|
82
|
+
else if (bigTag <= 65535n) {
|
|
83
|
+
const valueBytes = writeUint(Number(bigTag), 2)
|
|
84
|
+
bytes = new Uint8Array([0xd9, ...valueBytes])
|
|
85
|
+
}
|
|
86
|
+
// 4-byte encoding (65536-4294967295) - 0xda + 4 bytes
|
|
87
|
+
else if (bigTag <= 4294967295n) {
|
|
88
|
+
const valueBytes = writeUint(Number(bigTag), 4)
|
|
89
|
+
bytes = new Uint8Array([0xda, ...valueBytes])
|
|
90
|
+
}
|
|
91
|
+
// 8-byte encoding (> 4294967295) - 0xdb + 8 bytes
|
|
92
|
+
else {
|
|
93
|
+
const valueBytes = writeBigUint(bigTag, 8)
|
|
94
|
+
bytes = new Uint8Array([0xdb, ...valueBytes])
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return bytes
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Encode tagged value (tag + content)
|
|
102
|
+
*
|
|
103
|
+
* A tagged value consists of:
|
|
104
|
+
* 1. Tag number (Major Type 6 header)
|
|
105
|
+
* 2. Tagged content (recursively encoded value)
|
|
106
|
+
*
|
|
107
|
+
* The encode function is passed as a parameter to avoid circular dependencies.
|
|
108
|
+
*
|
|
109
|
+
* @param tagNumber - Tag number
|
|
110
|
+
* @param value - Value to tag
|
|
111
|
+
* @param encode - Encoder function for the tagged value
|
|
112
|
+
* @returns Encoded CBOR bytes and hex string
|
|
113
|
+
*/
|
|
114
|
+
const encodeTag = (
|
|
115
|
+
tagNumber: number | bigint,
|
|
116
|
+
value: EncodableValue,
|
|
117
|
+
encode: (value: EncodableValue) => EncodeResult
|
|
118
|
+
): EncodeResult => {
|
|
119
|
+
// Encode tag number
|
|
120
|
+
const tagBytes = encodeTagNumber(tagNumber)
|
|
121
|
+
|
|
122
|
+
// Recursively encode the tagged value
|
|
123
|
+
const valueResult = encode(value)
|
|
124
|
+
|
|
125
|
+
// Concatenate tag header + value bytes
|
|
126
|
+
const totalLength = tagBytes.length + valueResult.bytes.length
|
|
127
|
+
const bytes = new Uint8Array(totalLength)
|
|
128
|
+
bytes.set(tagBytes, 0)
|
|
129
|
+
bytes.set(valueResult.bytes, tagBytes.length)
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
bytes,
|
|
133
|
+
hex: bytesToHex(bytes)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Encode TaggedValue object
|
|
139
|
+
*
|
|
140
|
+
* Convenience function for encoding { tag, value } objects.
|
|
141
|
+
*
|
|
142
|
+
* @param taggedValue - Object with tag and value properties
|
|
143
|
+
* @param encode - Encoder function for the tagged value
|
|
144
|
+
* @returns Encoded CBOR bytes and hex string
|
|
145
|
+
*/
|
|
146
|
+
const encodeTaggedValue = (
|
|
147
|
+
taggedValue: TaggedValue,
|
|
148
|
+
encode: (value: EncodableValue) => EncodeResult
|
|
149
|
+
): EncodeResult => {
|
|
150
|
+
return encodeTag(taggedValue.tag, taggedValue.value, encode)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
encodeTagNumber,
|
|
155
|
+
encodeTag,
|
|
156
|
+
encodeTaggedValue
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NACHOS Encoder Module
|
|
3
|
+
*
|
|
4
|
+
* @module @marcuspuchalla/nachos/encoder
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Export encoder composables
|
|
8
|
+
export { useCborEncoder } from './composables/useCborEncoder'
|
|
9
|
+
export { useCborIntegerEncoder } from './composables/useCborIntegerEncoder'
|
|
10
|
+
export { useCborStringEncoder } from './composables/useCborStringEncoder'
|
|
11
|
+
export { useCborCollectionEncoder } from './composables/useCborCollectionEncoder'
|
|
12
|
+
export { useCborSimpleEncoder } from './composables/useCborSimpleEncoder'
|
|
13
|
+
export { useCborTagEncoder } from './composables/useCborTagEncoder'
|
|
14
|
+
|
|
15
|
+
// Export types
|
|
16
|
+
export type {
|
|
17
|
+
EncodeResult,
|
|
18
|
+
EncodeOptions,
|
|
19
|
+
EncodableValue,
|
|
20
|
+
EncodeContext
|
|
21
|
+
} from './types'
|
|
22
|
+
|
|
23
|
+
// Export constants
|
|
24
|
+
export {
|
|
25
|
+
DEFAULT_ENCODE_OPTIONS
|
|
26
|
+
} from './types'
|
|
27
|
+
|
|
28
|
+
// Export utility functions
|
|
29
|
+
export {
|
|
30
|
+
bytesToHex,
|
|
31
|
+
concatenateUint8Arrays,
|
|
32
|
+
compareBytes,
|
|
33
|
+
writeUint,
|
|
34
|
+
writeBigUint
|
|
35
|
+
} from './utils'
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Encoder Type Definitions
|
|
3
|
+
* Following RFC 8949 specification
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PlutusConstr, CborByteString, CborTextString } from '../parser/types'
|
|
7
|
+
import { INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL } from '../parser/types'
|
|
8
|
+
|
|
9
|
+
// Re-export symbols and types for use in encoder
|
|
10
|
+
export { INDEFINITE_SYMBOL, ALL_ENTRIES_SYMBOL }
|
|
11
|
+
export type { CborByteString, CborTextString }
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Encoder options for controlling behavior
|
|
15
|
+
*/
|
|
16
|
+
export interface EncodeOptions {
|
|
17
|
+
/** Enable canonical encoding (shortest form, sorted maps) */
|
|
18
|
+
canonical?: boolean
|
|
19
|
+
/** Allow indefinite-length encoding (false in canonical mode) */
|
|
20
|
+
allowIndefinite?: boolean
|
|
21
|
+
/** Reject duplicate map keys */
|
|
22
|
+
rejectDuplicateKeys?: boolean
|
|
23
|
+
/** Maximum nesting depth */
|
|
24
|
+
maxDepth?: number
|
|
25
|
+
/** Maximum output size in bytes */
|
|
26
|
+
maxOutputSize?: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default encode options
|
|
31
|
+
*/
|
|
32
|
+
export const DEFAULT_ENCODE_OPTIONS: Required<EncodeOptions> = {
|
|
33
|
+
canonical: false,
|
|
34
|
+
allowIndefinite: true,
|
|
35
|
+
rejectDuplicateKeys: false,
|
|
36
|
+
maxDepth: 64,
|
|
37
|
+
maxOutputSize: 100 * 1024 * 1024 // 100 MB
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Result of encoding operation
|
|
42
|
+
*/
|
|
43
|
+
export interface EncodeResult {
|
|
44
|
+
/** Encoded CBOR bytes */
|
|
45
|
+
bytes: Uint8Array
|
|
46
|
+
/** Hex string representation */
|
|
47
|
+
hex: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Values that can be encoded to CBOR
|
|
52
|
+
*
|
|
53
|
+
* Supports both plain objects (for convenience) and Maps (for type preservation).
|
|
54
|
+
* Map<any, any> is preferred for maps with non-string keys (integers, Uint8Arrays, etc.)
|
|
55
|
+
*/
|
|
56
|
+
export type EncodableValue =
|
|
57
|
+
| number
|
|
58
|
+
| bigint
|
|
59
|
+
| string
|
|
60
|
+
| boolean
|
|
61
|
+
| null
|
|
62
|
+
| undefined
|
|
63
|
+
| Uint8Array
|
|
64
|
+
| EncodableValue[]
|
|
65
|
+
| { [key: string]: EncodableValue } // Plain object (legacy/convenience)
|
|
66
|
+
| Map<EncodableValue, EncodableValue> // Map (preserves key types)
|
|
67
|
+
| TaggedValue
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tagged CBOR value (Major Type 6)
|
|
71
|
+
*/
|
|
72
|
+
export interface TaggedValue {
|
|
73
|
+
tag: number
|
|
74
|
+
value: EncodableValue
|
|
75
|
+
plutus?: PlutusConstr
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Encoding context that tracks state during CBOR encoding
|
|
80
|
+
*/
|
|
81
|
+
export interface EncodeContext {
|
|
82
|
+
/** Current nesting depth */
|
|
83
|
+
depth: number
|
|
84
|
+
/** Bytes written so far */
|
|
85
|
+
bytesWritten: number
|
|
86
|
+
/** Encoder options */
|
|
87
|
+
options: Required<EncodeOptions>
|
|
88
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Encoder Utility Functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert Uint8Array to hex string
|
|
7
|
+
*/
|
|
8
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
|
9
|
+
return Array.from(bytes)
|
|
10
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
11
|
+
.join('')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Concatenate multiple Uint8Arrays
|
|
16
|
+
*/
|
|
17
|
+
export function concatenateUint8Arrays(arrays: Uint8Array[]): Uint8Array {
|
|
18
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0)
|
|
19
|
+
const result = new Uint8Array(totalLength)
|
|
20
|
+
|
|
21
|
+
let offset = 0
|
|
22
|
+
for (const arr of arrays) {
|
|
23
|
+
result.set(arr, offset)
|
|
24
|
+
offset += arr.length
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Compare two Uint8Arrays bytewise (for canonical map sorting)
|
|
32
|
+
*/
|
|
33
|
+
export function compareBytes(a: Uint8Array, b: Uint8Array): number {
|
|
34
|
+
// First, compare lengths
|
|
35
|
+
if (a.length !== b.length) {
|
|
36
|
+
return a.length - b.length
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Then, compare bytewise
|
|
40
|
+
for (let i = 0; i < a.length; i++) {
|
|
41
|
+
const byteA = a[i]
|
|
42
|
+
const byteB = b[i]
|
|
43
|
+
if (byteA === undefined || byteB === undefined) {
|
|
44
|
+
throw new Error(`Unexpected undefined byte at index ${i}`)
|
|
45
|
+
}
|
|
46
|
+
if (byteA !== byteB) {
|
|
47
|
+
return byteA - byteB
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Write unsigned integer to bytes (big-endian)
|
|
56
|
+
*/
|
|
57
|
+
export function writeUint(value: number, bytes: number): Uint8Array {
|
|
58
|
+
const result = new Uint8Array(bytes)
|
|
59
|
+
|
|
60
|
+
for (let i = bytes - 1; i >= 0; i--) {
|
|
61
|
+
result[i] = value & 0xff
|
|
62
|
+
value = value >>> 8
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Write BigInt to bytes (big-endian)
|
|
70
|
+
*/
|
|
71
|
+
export function writeBigUint(value: bigint, bytes: number): Uint8Array {
|
|
72
|
+
const result = new Uint8Array(bytes)
|
|
73
|
+
|
|
74
|
+
for (let i = bytes - 1; i >= 0; i--) {
|
|
75
|
+
result[i] = Number(value & 0xffn)
|
|
76
|
+
value = value >> 8n
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return result
|
|
80
|
+
}
|