@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,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCborDiagnostic - RFC 8949 Appendix B Diagnostic Notation
|
|
3
|
+
*
|
|
4
|
+
* Converts CBOR values to human-readable diagnostic notation as defined
|
|
5
|
+
* in RFC 8949 (Concise Binary Object Representation).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* const { toDiagnostic } = useCborDiagnostic()
|
|
10
|
+
*
|
|
11
|
+
* toDiagnostic(100) // "100"
|
|
12
|
+
* toDiagnostic(new Uint8Array([1,2])) // "h'0102'"
|
|
13
|
+
* toDiagnostic([1, 2, 3]) // "[1, 2, 3]"
|
|
14
|
+
* toDiagnostic({a: 1}) // '{"a": 1}'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Options for diagnostic notation output
|
|
20
|
+
*/
|
|
21
|
+
export interface DiagnosticOptions {
|
|
22
|
+
/** Pretty print with indentation (default: false) */
|
|
23
|
+
pretty?: boolean
|
|
24
|
+
/** Indentation string for pretty printing (default: ' ') */
|
|
25
|
+
indent?: string
|
|
26
|
+
/** Maximum depth for nested structures (default: 100) */
|
|
27
|
+
maxDepth?: number
|
|
28
|
+
/** Mark as indefinite-length (default: false) */
|
|
29
|
+
indefinite?: boolean
|
|
30
|
+
/** Show byte offsets as comments (default: false) */
|
|
31
|
+
showOffsets?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tagged value interface (CBOR Major Type 6)
|
|
36
|
+
*/
|
|
37
|
+
interface TaggedValue {
|
|
38
|
+
tag: number
|
|
39
|
+
value: unknown
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if value is a tagged value
|
|
44
|
+
*/
|
|
45
|
+
function isTaggedValue(value: unknown): value is TaggedValue {
|
|
46
|
+
return (
|
|
47
|
+
typeof value === 'object' &&
|
|
48
|
+
value !== null &&
|
|
49
|
+
'tag' in value &&
|
|
50
|
+
'value' in value &&
|
|
51
|
+
typeof (value as TaggedValue).tag === 'number'
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if value is a plain object (not array, not special type)
|
|
57
|
+
*/
|
|
58
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
59
|
+
return (
|
|
60
|
+
typeof value === 'object' &&
|
|
61
|
+
value !== null &&
|
|
62
|
+
!Array.isArray(value) &&
|
|
63
|
+
!(value instanceof Uint8Array) &&
|
|
64
|
+
!(value instanceof Map) &&
|
|
65
|
+
!(value instanceof Set) &&
|
|
66
|
+
!isTaggedValue(value) &&
|
|
67
|
+
value.constructor === Object
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Escape string for diagnostic notation (JSON-style escaping)
|
|
73
|
+
*/
|
|
74
|
+
function escapeString(str: string): string {
|
|
75
|
+
return str
|
|
76
|
+
.replace(/\\/g, '\\\\')
|
|
77
|
+
.replace(/"/g, '\\"')
|
|
78
|
+
.replace(/\n/g, '\\n')
|
|
79
|
+
.replace(/\r/g, '\\r')
|
|
80
|
+
.replace(/\t/g, '\\t')
|
|
81
|
+
.replace(/[\x00-\x1f\x7f-\x9f]/g, (char) => {
|
|
82
|
+
const code = char.charCodeAt(0)
|
|
83
|
+
return `\\u${code.toString(16).padStart(4, '0')}`
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert Uint8Array to hex string
|
|
89
|
+
*/
|
|
90
|
+
function bytesToHex(bytes: Uint8Array): string {
|
|
91
|
+
return Array.from(bytes)
|
|
92
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
93
|
+
.join('')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Composable for CBOR diagnostic notation
|
|
98
|
+
*/
|
|
99
|
+
export function useCborDiagnostic() {
|
|
100
|
+
/**
|
|
101
|
+
* Convert a CBOR value to RFC 8949 diagnostic notation
|
|
102
|
+
*
|
|
103
|
+
* @param value - The CBOR value to convert
|
|
104
|
+
* @param options - Formatting options
|
|
105
|
+
* @returns Diagnostic notation string
|
|
106
|
+
*/
|
|
107
|
+
const toDiagnostic = (
|
|
108
|
+
value: unknown,
|
|
109
|
+
options: DiagnosticOptions = {}
|
|
110
|
+
): string => {
|
|
111
|
+
const {
|
|
112
|
+
pretty = false,
|
|
113
|
+
indent = ' ',
|
|
114
|
+
maxDepth = 100,
|
|
115
|
+
indefinite = false
|
|
116
|
+
} = options
|
|
117
|
+
|
|
118
|
+
return formatValue(value, 0, pretty, indent, maxDepth, indefinite)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Internal recursive formatter
|
|
123
|
+
*/
|
|
124
|
+
const formatValue = (
|
|
125
|
+
value: unknown,
|
|
126
|
+
depth: number,
|
|
127
|
+
pretty: boolean,
|
|
128
|
+
indent: string,
|
|
129
|
+
maxDepth: number,
|
|
130
|
+
indefinite: boolean
|
|
131
|
+
): string => {
|
|
132
|
+
// Check depth limit
|
|
133
|
+
if (depth > maxDepth) {
|
|
134
|
+
return '...'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle null/undefined
|
|
138
|
+
if (value === null) {
|
|
139
|
+
return 'null'
|
|
140
|
+
}
|
|
141
|
+
if (value === undefined) {
|
|
142
|
+
return 'undefined'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Handle booleans
|
|
146
|
+
if (typeof value === 'boolean') {
|
|
147
|
+
return value ? 'true' : 'false'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Handle numbers
|
|
151
|
+
if (typeof value === 'number') {
|
|
152
|
+
// Special float values
|
|
153
|
+
if (Number.isNaN(value)) {
|
|
154
|
+
return 'NaN'
|
|
155
|
+
}
|
|
156
|
+
if (value === Infinity) {
|
|
157
|
+
return 'Infinity'
|
|
158
|
+
}
|
|
159
|
+
if (value === -Infinity) {
|
|
160
|
+
return '-Infinity'
|
|
161
|
+
}
|
|
162
|
+
// Negative zero
|
|
163
|
+
if (Object.is(value, -0)) {
|
|
164
|
+
return '-0.0'
|
|
165
|
+
}
|
|
166
|
+
// Regular numbers
|
|
167
|
+
if (Number.isInteger(value)) {
|
|
168
|
+
return value.toString()
|
|
169
|
+
}
|
|
170
|
+
// Floats - preserve precision
|
|
171
|
+
return value.toString()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle BigInt
|
|
175
|
+
if (typeof value === 'bigint') {
|
|
176
|
+
return value.toString()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle strings
|
|
180
|
+
if (typeof value === 'string') {
|
|
181
|
+
return `"${escapeString(value)}"`
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Handle byte strings (Uint8Array)
|
|
185
|
+
if (value instanceof Uint8Array) {
|
|
186
|
+
return `h'${bytesToHex(value)}'`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Handle tagged values
|
|
190
|
+
if (isTaggedValue(value)) {
|
|
191
|
+
const taggedContent = formatValue(
|
|
192
|
+
value.value,
|
|
193
|
+
depth + 1,
|
|
194
|
+
pretty,
|
|
195
|
+
indent,
|
|
196
|
+
maxDepth,
|
|
197
|
+
false
|
|
198
|
+
)
|
|
199
|
+
return `${value.tag}(${taggedContent})`
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Handle arrays
|
|
203
|
+
if (Array.isArray(value)) {
|
|
204
|
+
if (value.length === 0) {
|
|
205
|
+
return indefinite ? '[_ ]' : '[]'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const items = value.map(item =>
|
|
209
|
+
formatValue(item, depth + 1, pretty, indent, maxDepth, false)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if (pretty) {
|
|
213
|
+
const prefix = indefinite ? '[_ ' : '['
|
|
214
|
+
const lineIndent = indent.repeat(depth + 1)
|
|
215
|
+
const closeIndent = indent.repeat(depth)
|
|
216
|
+
return `${prefix}\n${lineIndent}${items.join(`,\n${lineIndent}`)}\n${closeIndent}]`
|
|
217
|
+
} else {
|
|
218
|
+
const prefix = indefinite ? '[_ ' : '['
|
|
219
|
+
return `${prefix}${items.join(', ')}]`
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Handle Maps
|
|
224
|
+
if (value instanceof Map) {
|
|
225
|
+
if (value.size === 0) {
|
|
226
|
+
return indefinite ? '{_ }' : '{}'
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const entries: string[] = []
|
|
230
|
+
for (const [k, v] of value) {
|
|
231
|
+
const keyStr = formatValue(k, depth + 1, pretty, indent, maxDepth, false)
|
|
232
|
+
const valueStr = formatValue(v, depth + 1, pretty, indent, maxDepth, false)
|
|
233
|
+
entries.push(`${keyStr}: ${valueStr}`)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (pretty) {
|
|
237
|
+
const prefix = indefinite ? '{_ ' : '{'
|
|
238
|
+
const lineIndent = indent.repeat(depth + 1)
|
|
239
|
+
const closeIndent = indent.repeat(depth)
|
|
240
|
+
return `${prefix}\n${lineIndent}${entries.join(`,\n${lineIndent}`)}\n${closeIndent}}`
|
|
241
|
+
} else {
|
|
242
|
+
const prefix = indefinite ? '{_ ' : '{'
|
|
243
|
+
return `${prefix}${entries.join(', ')}}`
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Handle Sets (convert to array)
|
|
248
|
+
if (value instanceof Set) {
|
|
249
|
+
const items = Array.from(value).map(item =>
|
|
250
|
+
formatValue(item, depth + 1, pretty, indent, maxDepth, false)
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if (pretty) {
|
|
254
|
+
const lineIndent = indent.repeat(depth + 1)
|
|
255
|
+
const closeIndent = indent.repeat(depth)
|
|
256
|
+
return `[\n${lineIndent}${items.join(`,\n${lineIndent}`)}\n${closeIndent}]`
|
|
257
|
+
} else {
|
|
258
|
+
return `[${items.join(', ')}]`
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Handle plain objects (as CBOR maps with string keys)
|
|
263
|
+
if (isPlainObject(value)) {
|
|
264
|
+
const keys = Object.keys(value)
|
|
265
|
+
if (keys.length === 0) {
|
|
266
|
+
return indefinite ? '{_ }' : '{}'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const entries = keys.map(key => {
|
|
270
|
+
const keyStr = `"${escapeString(key)}"`
|
|
271
|
+
const valueStr = formatValue(
|
|
272
|
+
value[key],
|
|
273
|
+
depth + 1,
|
|
274
|
+
pretty,
|
|
275
|
+
indent,
|
|
276
|
+
maxDepth,
|
|
277
|
+
false
|
|
278
|
+
)
|
|
279
|
+
return `${keyStr}: ${valueStr}`
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
if (pretty) {
|
|
283
|
+
const prefix = indefinite ? '{_ ' : '{'
|
|
284
|
+
const lineIndent = indent.repeat(depth + 1)
|
|
285
|
+
const closeIndent = indent.repeat(depth)
|
|
286
|
+
return `${prefix}\n${lineIndent}${entries.join(`,\n${lineIndent}`)}\n${closeIndent}}`
|
|
287
|
+
} else {
|
|
288
|
+
const prefix = indefinite ? '{_ ' : '{'
|
|
289
|
+
return `${prefix}${entries.join(', ')}}`
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Fallback for unknown types
|
|
294
|
+
return String(value)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Parse diagnostic notation back to CBOR value
|
|
299
|
+
* (Not fully implemented - for future use)
|
|
300
|
+
*/
|
|
301
|
+
const fromDiagnostic = (diag: string): unknown => {
|
|
302
|
+
// Basic parsing for common cases
|
|
303
|
+
const trimmed = diag.trim()
|
|
304
|
+
|
|
305
|
+
// Null/undefined
|
|
306
|
+
if (trimmed === 'null') return null
|
|
307
|
+
if (trimmed === 'undefined') return undefined
|
|
308
|
+
|
|
309
|
+
// Booleans
|
|
310
|
+
if (trimmed === 'true') return true
|
|
311
|
+
if (trimmed === 'false') return false
|
|
312
|
+
|
|
313
|
+
// Special floats
|
|
314
|
+
if (trimmed === 'NaN') return NaN
|
|
315
|
+
if (trimmed === 'Infinity') return Infinity
|
|
316
|
+
if (trimmed === '-Infinity') return -Infinity
|
|
317
|
+
|
|
318
|
+
// Numbers (including negative zero)
|
|
319
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
320
|
+
const num = Number(trimmed)
|
|
321
|
+
if (trimmed === '-0.0' || trimmed === '-0') {
|
|
322
|
+
return -0
|
|
323
|
+
}
|
|
324
|
+
return num
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Byte strings h'...'
|
|
328
|
+
const hexMatch = trimmed.match(/^h'([0-9a-fA-F]*)'$/)
|
|
329
|
+
if (hexMatch && hexMatch[1] !== undefined) {
|
|
330
|
+
const hex = hexMatch[1]
|
|
331
|
+
const bytes = new Uint8Array(hex.length / 2)
|
|
332
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
333
|
+
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
|
|
334
|
+
}
|
|
335
|
+
return bytes
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Strings "..."
|
|
339
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
340
|
+
// Basic unescape - full implementation would handle all escapes
|
|
341
|
+
return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\')
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// For arrays, maps, tags - would need full parser
|
|
345
|
+
// Return as string for now
|
|
346
|
+
return trimmed
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Format value with type annotation (extended notation)
|
|
351
|
+
*/
|
|
352
|
+
const toDiagnosticWithType = (
|
|
353
|
+
value: unknown,
|
|
354
|
+
majorType: number,
|
|
355
|
+
options: DiagnosticOptions = {}
|
|
356
|
+
): string => {
|
|
357
|
+
const typeNames: Record<number, string> = {
|
|
358
|
+
0: 'uint',
|
|
359
|
+
1: 'nint',
|
|
360
|
+
2: 'bstr',
|
|
361
|
+
3: 'tstr',
|
|
362
|
+
4: 'array',
|
|
363
|
+
5: 'map',
|
|
364
|
+
6: 'tag',
|
|
365
|
+
7: 'simple'
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const typeName = typeNames[majorType] || 'unknown'
|
|
369
|
+
const diag = toDiagnostic(value, options)
|
|
370
|
+
|
|
371
|
+
return `${typeName}(${diag})`
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
toDiagnostic,
|
|
376
|
+
fromDiagnostic,
|
|
377
|
+
toDiagnosticWithType
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export default useCborDiagnostic
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Float and Simple Values Parser Composable
|
|
3
|
+
* Handles Major Type 7 (Simple Values and Floats)
|
|
4
|
+
* Supports Float16, Float32, Float64, and simple values (true, false, null, undefined)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ParseResult, ParseOptions } from '../types'
|
|
8
|
+
import { hexToBytes, readByte, extractCborHeader } from '../utils'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Composable for parsing CBOR floats and simple values (Major Type 7)
|
|
12
|
+
*
|
|
13
|
+
* @returns Object with parse, parseFloat, and parseSimple functions
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const { parse } = useCborFloat()
|
|
18
|
+
* const result = parse('f5') // true
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function useCborFloat() {
|
|
22
|
+
/**
|
|
23
|
+
* Converts IEEE 754 binary16 (Float16) to JavaScript number
|
|
24
|
+
* Manual conversion required as JavaScript doesn't have native Float16 support
|
|
25
|
+
*
|
|
26
|
+
* @param buffer - Data buffer
|
|
27
|
+
* @param offset - Starting offset
|
|
28
|
+
* @returns Float64 number representation
|
|
29
|
+
*/
|
|
30
|
+
const float16ToFloat64 = (buffer: Uint8Array, offset: number): number => {
|
|
31
|
+
// Read 16-bit value in big-endian
|
|
32
|
+
const byte1 = readByte(buffer, offset)
|
|
33
|
+
const byte2 = readByte(buffer, offset + 1)
|
|
34
|
+
const value = (byte1 << 8) | byte2
|
|
35
|
+
|
|
36
|
+
// Extract components
|
|
37
|
+
const sign = (value & 0x8000) >> 15
|
|
38
|
+
const exponent = (value & 0x7c00) >> 10
|
|
39
|
+
const fraction = value & 0x03ff
|
|
40
|
+
|
|
41
|
+
// Handle special cases
|
|
42
|
+
if (exponent === 0) {
|
|
43
|
+
if (fraction === 0) {
|
|
44
|
+
// Zero (positive or negative)
|
|
45
|
+
return sign === 0 ? 0.0 : -0.0
|
|
46
|
+
}
|
|
47
|
+
// Subnormal number
|
|
48
|
+
return (sign === 0 ? 1 : -1) * Math.pow(2, -14) * (fraction / 1024)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (exponent === 0x1f) {
|
|
52
|
+
if (fraction === 0) {
|
|
53
|
+
// Infinity (positive or negative)
|
|
54
|
+
return sign === 0 ? Infinity : -Infinity
|
|
55
|
+
}
|
|
56
|
+
// NaN
|
|
57
|
+
return NaN
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Normal number
|
|
61
|
+
// Formula: (-1)^sign * 2^(exponent - 15) * (1 + fraction/1024)
|
|
62
|
+
return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - 15) * (1 + fraction / 1024)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parses simple values (booleans, null, undefined, unassigned)
|
|
67
|
+
*
|
|
68
|
+
* @param buffer - Data buffer
|
|
69
|
+
* @param offset - Current offset
|
|
70
|
+
* @returns Parsed simple value and bytes read
|
|
71
|
+
*/
|
|
72
|
+
const parseSimpleFromBuffer = (buffer: Uint8Array, offset: number): ParseResult => {
|
|
73
|
+
const initialByte = readByte(buffer, offset)
|
|
74
|
+
const { majorType, additionalInfo } = extractCborHeader(initialByte)
|
|
75
|
+
|
|
76
|
+
if (majorType !== 7) {
|
|
77
|
+
throw new Error(`Expected major type 7 (simple/float), got ${majorType}`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Simple values based on additional info
|
|
81
|
+
if (additionalInfo < 20) {
|
|
82
|
+
// Unassigned simple values (0-19)
|
|
83
|
+
return {
|
|
84
|
+
value: { simpleValue: additionalInfo },
|
|
85
|
+
bytesRead: 1
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
switch (additionalInfo) {
|
|
90
|
+
case 20: // false
|
|
91
|
+
return { value: false, bytesRead: 1 }
|
|
92
|
+
|
|
93
|
+
case 21: // true
|
|
94
|
+
return { value: true, bytesRead: 1 }
|
|
95
|
+
|
|
96
|
+
case 22: // null
|
|
97
|
+
return { value: null, bytesRead: 1 }
|
|
98
|
+
|
|
99
|
+
case 23: // undefined
|
|
100
|
+
return { value: undefined, bytesRead: 1 }
|
|
101
|
+
|
|
102
|
+
case 24: // 1-byte simple value
|
|
103
|
+
{
|
|
104
|
+
if (offset + 1 >= buffer.length) {
|
|
105
|
+
throw new Error('Unexpected end of buffer while reading simple value')
|
|
106
|
+
}
|
|
107
|
+
const simpleValue = readByte(buffer, offset + 1)
|
|
108
|
+
// Simple values 0-19 should not use 1-byte encoding
|
|
109
|
+
if (simpleValue < 32) {
|
|
110
|
+
throw new Error(`Invalid 1-byte encoding for simple value ${simpleValue}`)
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
value: { simpleValue },
|
|
114
|
+
bytesRead: 2
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case 25: // Float16
|
|
119
|
+
case 26: // Float32
|
|
120
|
+
case 27: // Float64
|
|
121
|
+
// These are floats, not simple values - should use parseFloatFromBuffer
|
|
122
|
+
throw new Error(`Additional info ${additionalInfo} is a float, use parseFloat instead`)
|
|
123
|
+
|
|
124
|
+
case 28:
|
|
125
|
+
case 29:
|
|
126
|
+
case 30:
|
|
127
|
+
// Reserved
|
|
128
|
+
throw new Error(`Reserved additional info value: ${additionalInfo}`)
|
|
129
|
+
|
|
130
|
+
case 31: // Break marker
|
|
131
|
+
throw new Error('Break marker (0xff) should only appear in indefinite-length items')
|
|
132
|
+
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`Invalid additional info: ${additionalInfo}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Parses floating point numbers (Float16, Float32, Float64)
|
|
140
|
+
*
|
|
141
|
+
* @param buffer - Data buffer
|
|
142
|
+
* @param offset - Current offset
|
|
143
|
+
* @returns Parsed float and bytes read
|
|
144
|
+
*/
|
|
145
|
+
const parseFloatFromBuffer = (buffer: Uint8Array, offset: number): ParseResult => {
|
|
146
|
+
const initialByte = readByte(buffer, offset)
|
|
147
|
+
const { majorType, additionalInfo } = extractCborHeader(initialByte)
|
|
148
|
+
|
|
149
|
+
if (majorType !== 7) {
|
|
150
|
+
throw new Error(`Expected major type 7 (simple/float), got ${majorType}`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
switch (additionalInfo) {
|
|
154
|
+
case 25: // Float16 (2 bytes)
|
|
155
|
+
{
|
|
156
|
+
if (offset + 2 >= buffer.length) {
|
|
157
|
+
throw new Error('Unexpected end of buffer while reading Float16')
|
|
158
|
+
}
|
|
159
|
+
const value = float16ToFloat64(buffer, offset + 1)
|
|
160
|
+
return { value, bytesRead: 3 }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
case 26: // Float32 (4 bytes)
|
|
164
|
+
{
|
|
165
|
+
if (offset + 4 >= buffer.length) {
|
|
166
|
+
throw new Error('Unexpected end of buffer while reading Float32')
|
|
167
|
+
}
|
|
168
|
+
// Use DataView for proper IEEE 754 parsing
|
|
169
|
+
const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 4)
|
|
170
|
+
const value = dataView.getFloat32(0, false) // false = big-endian
|
|
171
|
+
return { value, bytesRead: 5 }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 27: // Float64 (8 bytes)
|
|
175
|
+
{
|
|
176
|
+
if (offset + 8 >= buffer.length) {
|
|
177
|
+
throw new Error('Unexpected end of buffer while reading Float64')
|
|
178
|
+
}
|
|
179
|
+
// Use DataView for proper IEEE 754 parsing
|
|
180
|
+
const dataView = new DataView(buffer.buffer, buffer.byteOffset + offset + 1, 8)
|
|
181
|
+
const value = dataView.getFloat64(0, false) // false = big-endian
|
|
182
|
+
return { value, bytesRead: 9 }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
default:
|
|
186
|
+
throw new Error(`Additional info ${additionalInfo} is not a float type`)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Auto-detects and parses any Major Type 7 value (simple or float)
|
|
192
|
+
*
|
|
193
|
+
* @param buffer - Data buffer
|
|
194
|
+
* @param offset - Current offset
|
|
195
|
+
* @returns Parsed value and bytes read
|
|
196
|
+
*/
|
|
197
|
+
const parseFromBuffer = (buffer: Uint8Array, offset: number): ParseResult => {
|
|
198
|
+
const initialByte = readByte(buffer, offset)
|
|
199
|
+
const { majorType, additionalInfo } = extractCborHeader(initialByte)
|
|
200
|
+
|
|
201
|
+
if (majorType !== 7) {
|
|
202
|
+
throw new Error(`Expected major type 7 (simple/float), got ${majorType}`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Determine if it's a float or simple value based on additional info
|
|
206
|
+
if (additionalInfo === 25 || additionalInfo === 26 || additionalInfo === 27) {
|
|
207
|
+
// Float16, Float32, or Float64
|
|
208
|
+
return parseFloatFromBuffer(buffer, offset)
|
|
209
|
+
} else {
|
|
210
|
+
// Simple value (including false, true, null, undefined)
|
|
211
|
+
return parseSimpleFromBuffer(buffer, offset)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Parses CBOR simple value from hex string
|
|
217
|
+
*
|
|
218
|
+
* @param hexString - CBOR hex string
|
|
219
|
+
* @param _options - Parser options (optional, for future use)
|
|
220
|
+
* @returns Parsed simple value and bytes read
|
|
221
|
+
*/
|
|
222
|
+
const parseSimple = (hexString: string, _options?: ParseOptions): ParseResult => {
|
|
223
|
+
const buffer = hexToBytes(hexString)
|
|
224
|
+
return parseSimpleFromBuffer(buffer, 0)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Parses CBOR float from hex string
|
|
229
|
+
*
|
|
230
|
+
* @param hexString - CBOR hex string
|
|
231
|
+
* @param _options - Parser options (optional, for future use)
|
|
232
|
+
* @returns Parsed float and bytes read
|
|
233
|
+
*/
|
|
234
|
+
const parseFloat = (hexString: string, _options?: ParseOptions): ParseResult => {
|
|
235
|
+
const buffer = hexToBytes(hexString)
|
|
236
|
+
return parseFloatFromBuffer(buffer, 0)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Auto-detects and parses any Major Type 7 value from hex string
|
|
241
|
+
*
|
|
242
|
+
* @param hexString - CBOR hex string
|
|
243
|
+
* @param _options - Parser options (optional, for future use)
|
|
244
|
+
* @returns Parsed value and bytes read
|
|
245
|
+
*/
|
|
246
|
+
const parse = (hexString: string, _options?: ParseOptions): ParseResult => {
|
|
247
|
+
const buffer = hexToBytes(hexString)
|
|
248
|
+
return parseFromBuffer(buffer, 0)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
parse,
|
|
253
|
+
parseFloat,
|
|
254
|
+
parseSimple
|
|
255
|
+
}
|
|
256
|
+
}
|