@helios-lang/effect 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/LICENSE +28 -0
- package/README.md +3 -0
- package/dist/Address.js +13 -0
- package/dist/Address.js.map +1 -0
- package/dist/Bech32.js +153 -0
- package/dist/Bech32.js.map +1 -0
- package/dist/Cbor.js +1171 -0
- package/dist/Cbor.js.map +1 -0
- package/dist/Uplc/Cek.js +3 -0
- package/dist/Uplc/Cek.js.map +1 -0
- package/dist/Uplc/Data.js +171 -0
- package/dist/Uplc/Data.js.map +1 -0
- package/dist/Uplc/DataSchema.js +118 -0
- package/dist/Uplc/DataSchema.js.map +1 -0
- package/dist/Uplc/Primitive.js +23 -0
- package/dist/Uplc/Primitive.js.map +1 -0
- package/dist/Uplc/index.js +4 -0
- package/dist/Uplc/index.js.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/Base32.js +201 -0
- package/dist/internal/Base32.js.map +1 -0
- package/dist/internal/BigEndian.js +56 -0
- package/dist/internal/BigEndian.js.map +1 -0
- package/dist/internal/Bits.js +300 -0
- package/dist/internal/Bits.js.map +1 -0
- package/dist/internal/Bytes.js +293 -0
- package/dist/internal/Bytes.js.map +1 -0
- package/dist/internal/Flat.js +298 -0
- package/dist/internal/Flat.js.map +1 -0
- package/dist/internal/Float.js +154 -0
- package/dist/internal/Float.js.map +1 -0
- package/dist/internal/Utf8.js +44 -0
- package/dist/internal/Utf8.js.map +1 -0
- package/eslint.config.mjs +30 -0
- package/package.json +36 -0
- package/src/Address.ts +20 -0
- package/src/Bech32.test.ts +117 -0
- package/src/Bech32.ts +198 -0
- package/src/Cbor.test.ts +1610 -0
- package/src/Cbor.ts +1704 -0
- package/src/Uplc/Cek.ts +92 -0
- package/src/Uplc/Data.ts +259 -0
- package/src/Uplc/DataSchema.test.ts +207 -0
- package/src/Uplc/DataSchema.ts +181 -0
- package/src/Uplc/Primitive.ts +56 -0
- package/src/Uplc/index.ts +3 -0
- package/src/index.ts +4 -0
- package/src/internal/Base32.test.ts +219 -0
- package/src/internal/Base32.ts +341 -0
- package/src/internal/BigEndian.test.ts +79 -0
- package/src/internal/BigEndian.ts +67 -0
- package/src/internal/Bits.test.ts +300 -0
- package/src/internal/Bits.ts +398 -0
- package/src/internal/Bytes.test.ts +369 -0
- package/src/internal/Bytes.ts +343 -0
- package/src/internal/Flat.test.ts +29 -0
- package/src/internal/Flat.ts +387 -0
- package/src/internal/Float.test.ts +51 -0
- package/src/internal/Float.ts +190 -0
- package/src/internal/Utf8.test.ts +69 -0
- package/src/internal/Utf8.ts +58 -0
- package/tsconfig.build.json +14 -0
- package/tsconfig.json +38 -0
package/src/Cbor.ts
ADDED
|
@@ -0,0 +1,1704 @@
|
|
|
1
|
+
import { Data, Effect, Either } from "effect"
|
|
2
|
+
import * as BigEndian from "./internal/BigEndian.js"
|
|
3
|
+
import * as Bytes from "./internal/Bytes.js"
|
|
4
|
+
import * as Float from "./internal/Float.js"
|
|
5
|
+
import * as Utf8 from "./internal/Utf8.js"
|
|
6
|
+
|
|
7
|
+
export type Decoder<T> = (
|
|
8
|
+
stream: Bytes.Stream
|
|
9
|
+
) => Effect.Effect<T, Bytes.EndOfStreamError | DecodeError>
|
|
10
|
+
|
|
11
|
+
export type IndexedDecoder<T> = (
|
|
12
|
+
stream: Bytes.Stream,
|
|
13
|
+
index: number
|
|
14
|
+
) => Effect.Effect<T, Bytes.EndOfStreamError | DecodeError>
|
|
15
|
+
|
|
16
|
+
export type DecodeEffect<T> = Effect.Effect<
|
|
17
|
+
T,
|
|
18
|
+
Bytes.EndOfStreamError | DecodeError
|
|
19
|
+
>
|
|
20
|
+
export type PeekEffect<T> = Effect.Effect<T, Bytes.EndOfStreamError>
|
|
21
|
+
|
|
22
|
+
const FALSE_BYTE = 244 // m = 7, n = 20
|
|
23
|
+
const TRUE_BYTE = 245 // m = 7, n = 21
|
|
24
|
+
|
|
25
|
+
export class DecodeError extends Data.TaggedError("Cbor.DecodeError")<{
|
|
26
|
+
message: string
|
|
27
|
+
}> {
|
|
28
|
+
constructor(_stream: Bytes.Stream, message: string) {
|
|
29
|
+
super({ message: message })
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decodes a CBOR encoded `boolean`.
|
|
35
|
+
* Throws an error if the next element in bytes isn't a `boolean`.
|
|
36
|
+
* @param bytes
|
|
37
|
+
* @returns
|
|
38
|
+
*/
|
|
39
|
+
export const decodeBool = (bytes: Bytes.BytesLike): DecodeEffect<boolean> =>
|
|
40
|
+
Effect.gen(function* () {
|
|
41
|
+
const stream = Bytes.makeStream(bytes)
|
|
42
|
+
|
|
43
|
+
const b = yield* stream.shiftOne()
|
|
44
|
+
|
|
45
|
+
if (b == TRUE_BYTE) {
|
|
46
|
+
return true
|
|
47
|
+
} else if (b == FALSE_BYTE) {
|
|
48
|
+
return false
|
|
49
|
+
} else {
|
|
50
|
+
return yield* new DecodeError(
|
|
51
|
+
stream,
|
|
52
|
+
"unexpected non-boolean cbor object"
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Encodes a `boolean` into its CBOR representation.
|
|
59
|
+
* @param b
|
|
60
|
+
* @returns
|
|
61
|
+
*/
|
|
62
|
+
export function encodeBool(b: boolean): number[] {
|
|
63
|
+
if (b) {
|
|
64
|
+
return [TRUE_BYTE]
|
|
65
|
+
} else {
|
|
66
|
+
return [FALSE_BYTE]
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param bytes
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
export const isBool = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
75
|
+
Bytes.makeStream(bytes)
|
|
76
|
+
.peekOne()
|
|
77
|
+
.pipe(Effect.map((head) => head == FALSE_BYTE || head == TRUE_BYTE))
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Unwraps a CBOR encoded list of bytes
|
|
81
|
+
* @param bytes
|
|
82
|
+
* cborbytes, mutated to form remaining
|
|
83
|
+
* @returns byteArray
|
|
84
|
+
*/
|
|
85
|
+
export const decodeBytes = (bytes: Bytes.BytesLike): DecodeEffect<number[]> =>
|
|
86
|
+
Effect.gen(function* () {
|
|
87
|
+
const stream = Bytes.makeStream(bytes)
|
|
88
|
+
|
|
89
|
+
if (yield* isIndefBytes(bytes)) {
|
|
90
|
+
yield* stream.shiftOne()
|
|
91
|
+
|
|
92
|
+
// multiple chunks
|
|
93
|
+
|
|
94
|
+
let res: number[] = []
|
|
95
|
+
|
|
96
|
+
while ((yield* stream.peekOne()) != 255) {
|
|
97
|
+
const [, n] = yield* decodeDefHead(stream)
|
|
98
|
+
if (n > 64n) {
|
|
99
|
+
return yield* new DecodeError(stream, "Bytearray chunk too large")
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
res = res.concat(yield* stream.shiftMany(Number(n)))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if ((yield* stream.shiftOne()) != 255) {
|
|
106
|
+
throw new Error("invalid indef bytes termination byte")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return res
|
|
110
|
+
} else {
|
|
111
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
112
|
+
|
|
113
|
+
if (m != 2) {
|
|
114
|
+
return yield* new DecodeError(stream, "Invalid def bytes")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return yield* stream.shiftMany(Number(n))
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Wraps a list of bytes using CBOR. Optionally splits the bytes into chunks.
|
|
123
|
+
* @example
|
|
124
|
+
* bytesToHex(Cbor.encodeBytes("4d01000033222220051200120011")) == "4e4d01000033222220051200120011"
|
|
125
|
+
* @param bytes
|
|
126
|
+
* @param splitIntoChunks
|
|
127
|
+
* @returns
|
|
128
|
+
* cbor bytes
|
|
129
|
+
*/
|
|
130
|
+
export function encodeBytes(
|
|
131
|
+
bytes: string | number[] | Uint8Array,
|
|
132
|
+
splitIntoChunks: boolean = false
|
|
133
|
+
): number[] {
|
|
134
|
+
bytes = Bytes.toArray(bytes).slice()
|
|
135
|
+
|
|
136
|
+
if (bytes.length <= 64 || !splitIntoChunks) {
|
|
137
|
+
const head = encodeDefHead(2, BigInt(bytes.length))
|
|
138
|
+
return head.concat(bytes)
|
|
139
|
+
} else {
|
|
140
|
+
let res = encodeIndefHead(2)
|
|
141
|
+
|
|
142
|
+
while (bytes.length > 0) {
|
|
143
|
+
const chunk = bytes.splice(0, 64)
|
|
144
|
+
|
|
145
|
+
res = res.concat(encodeDefHead(2, BigInt(chunk.length))).concat(chunk)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
res.push(255)
|
|
149
|
+
|
|
150
|
+
return res
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param bytes
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
export const isBytes = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
159
|
+
peekMajorType(bytes).pipe(Effect.map((m) => m == 2))
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param bytes
|
|
163
|
+
* @returns
|
|
164
|
+
*/
|
|
165
|
+
export const isDefBytes = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
166
|
+
Effect.gen(function* () {
|
|
167
|
+
const stream = Bytes.makeStream(bytes)
|
|
168
|
+
|
|
169
|
+
const m = yield* peekMajorType(stream)
|
|
170
|
+
|
|
171
|
+
return m == 2 && (yield* stream.peekOne()) != 2 * 32 + 31
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @param bytes
|
|
176
|
+
* @returns
|
|
177
|
+
*/
|
|
178
|
+
export const isIndefBytes = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
179
|
+
Bytes.makeStream(bytes)
|
|
180
|
+
.peekOne()
|
|
181
|
+
.pipe(Effect.map((head) => head == 2 * 32 + 31))
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The homogenous field type case is used by the uplc ConstrData (undetermined number of UplcData items)
|
|
185
|
+
* @template Decoders
|
|
186
|
+
* Note: the conditional tuple check loses the tupleness if we just check against array, hence first we check against a tuple, and then an array (needed for the empty case)
|
|
187
|
+
* @param fieldDecoder
|
|
188
|
+
* Array for heterogenous item types, single function for homogenous item types
|
|
189
|
+
* @returns
|
|
190
|
+
*/
|
|
191
|
+
export const decodeConstr =
|
|
192
|
+
<
|
|
193
|
+
Decoders extends
|
|
194
|
+
| [Decoder<any>, ...Decoder<any>[]]
|
|
195
|
+
| Array<Decoder<any>>
|
|
196
|
+
| Decoder<any>
|
|
197
|
+
>(
|
|
198
|
+
fieldDecoder: Decoders extends [Decoder<any>, ...Decoder<any>[]]
|
|
199
|
+
? [...Decoders]
|
|
200
|
+
: Decoders extends Array<any>
|
|
201
|
+
? [...Decoders]
|
|
202
|
+
: Decoders
|
|
203
|
+
) =>
|
|
204
|
+
(
|
|
205
|
+
bytes: Bytes.BytesLike
|
|
206
|
+
): DecodeEffect<
|
|
207
|
+
[
|
|
208
|
+
number,
|
|
209
|
+
Decoders extends Array<any>
|
|
210
|
+
? {
|
|
211
|
+
[D in keyof Decoders]: Decoders[D] extends Decoder<infer T>
|
|
212
|
+
? T
|
|
213
|
+
: never
|
|
214
|
+
}
|
|
215
|
+
: Decoders extends Decoder<infer T>
|
|
216
|
+
? T[]
|
|
217
|
+
: never
|
|
218
|
+
]
|
|
219
|
+
> =>
|
|
220
|
+
Effect.gen(function* () {
|
|
221
|
+
const stream = Bytes.makeStream(bytes)
|
|
222
|
+
|
|
223
|
+
const tag = yield* decodeConstrTag(stream)
|
|
224
|
+
|
|
225
|
+
const res: any[] = yield* decodeList(
|
|
226
|
+
(itemStream: Bytes.Stream, i: number) =>
|
|
227
|
+
Effect.gen(function* () {
|
|
228
|
+
if (Array.isArray(fieldDecoder)) {
|
|
229
|
+
const decoder: Decoder<any> | undefined = fieldDecoder[i]
|
|
230
|
+
|
|
231
|
+
if (decoder === undefined) {
|
|
232
|
+
return yield* new DecodeError(
|
|
233
|
+
stream,
|
|
234
|
+
`expected ${fieldDecoder.length} fields, got more than ${i}`
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
239
|
+
return yield* decoder(itemStream)
|
|
240
|
+
} else {
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
242
|
+
return yield* fieldDecoder(itemStream)
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
)(stream)
|
|
246
|
+
|
|
247
|
+
if (Array.isArray(fieldDecoder)) {
|
|
248
|
+
if (res.length < fieldDecoder.length) {
|
|
249
|
+
return yield* new DecodeError(
|
|
250
|
+
stream,
|
|
251
|
+
`expected ${fieldDecoder.length} fields, only got ${res.length}`
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return [tag, res] as [
|
|
257
|
+
number,
|
|
258
|
+
Decoders extends Array<any>
|
|
259
|
+
? {
|
|
260
|
+
[D in keyof Decoders]: Decoders[D] extends Decoder<infer T>
|
|
261
|
+
? T
|
|
262
|
+
: never
|
|
263
|
+
}
|
|
264
|
+
: Decoders extends Decoder<infer T>
|
|
265
|
+
? T[]
|
|
266
|
+
: never
|
|
267
|
+
]
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* @param bytes
|
|
272
|
+
* @returns
|
|
273
|
+
*/
|
|
274
|
+
export const decodeConstrLazy = (
|
|
275
|
+
bytes: Bytes.BytesLike
|
|
276
|
+
): DecodeEffect<[number, <T>(itemDecoder: Decoder<T>) => DecodeEffect<T>]> =>
|
|
277
|
+
Effect.gen(function* () {
|
|
278
|
+
const stream = Bytes.makeStream(bytes)
|
|
279
|
+
const tag = yield* decodeConstrTag(stream)
|
|
280
|
+
const decodeField = yield* decodeListLazy(bytes)
|
|
281
|
+
|
|
282
|
+
return [tag, decodeField] as [number, typeof decodeField]
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param bytes
|
|
287
|
+
* @returns
|
|
288
|
+
*/
|
|
289
|
+
const decodeConstrTag = (bytes: Bytes.BytesLike): DecodeEffect<number> =>
|
|
290
|
+
Effect.gen(function* () {
|
|
291
|
+
const stream = Bytes.makeStream(bytes)
|
|
292
|
+
|
|
293
|
+
// constr
|
|
294
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
295
|
+
|
|
296
|
+
if (m != 6) {
|
|
297
|
+
return yield* new DecodeError(stream, "Unexpected constr tag head")
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (n < 102n) {
|
|
301
|
+
return yield* new DecodeError(
|
|
302
|
+
stream,
|
|
303
|
+
`unexpected encoded constr tag ${n}`
|
|
304
|
+
)
|
|
305
|
+
} else if (n == 102n) {
|
|
306
|
+
const [mCheck, nCheck] = yield* decodeDefHead(stream)
|
|
307
|
+
if (mCheck != 4 || nCheck != 2n) {
|
|
308
|
+
return yield* new DecodeError(
|
|
309
|
+
stream,
|
|
310
|
+
"Unexpected constr tag nested head"
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return Number(yield* decodeInt(stream))
|
|
315
|
+
} else if (n < 121n) {
|
|
316
|
+
return yield* new DecodeError(
|
|
317
|
+
stream,
|
|
318
|
+
`unexpected encoded constr tag ${n}`
|
|
319
|
+
)
|
|
320
|
+
} else if (n <= 127n) {
|
|
321
|
+
return Number(n - 121n)
|
|
322
|
+
} else if (n < 1280n) {
|
|
323
|
+
return yield* new DecodeError(
|
|
324
|
+
stream,
|
|
325
|
+
`unexpected encoded constr tag ${n}`
|
|
326
|
+
)
|
|
327
|
+
} else if (n <= 1400n) {
|
|
328
|
+
return Number(n - 1280n + 7n)
|
|
329
|
+
} else {
|
|
330
|
+
return yield* new DecodeError(
|
|
331
|
+
stream,
|
|
332
|
+
`unexpected encoded constr tag ${n}`
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Note: internally the indef list format is used if the number of fields is > 0, if the number of fields is 0 the def list format is used
|
|
339
|
+
* see [well-typed/cborg/serialise/src/Codec/Serialise/Class.hs](https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181).
|
|
340
|
+
* @param tag
|
|
341
|
+
* @param fields
|
|
342
|
+
* @returns
|
|
343
|
+
*/
|
|
344
|
+
export function encodeConstr(
|
|
345
|
+
tag: number,
|
|
346
|
+
fields: readonly number[][]
|
|
347
|
+
): number[] {
|
|
348
|
+
return encodeConstrTag(tag).concat(encodeList(fields))
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Encode a constructor tag of a ConstrData type
|
|
353
|
+
* @param tag
|
|
354
|
+
* @returns
|
|
355
|
+
* @throws
|
|
356
|
+
* If the tag is negative or not a whole number
|
|
357
|
+
*/
|
|
358
|
+
function encodeConstrTag(tag: number): number[] {
|
|
359
|
+
if (tag < 0 || tag % 1.0 != 0.0) {
|
|
360
|
+
throw new Error("invalid tag")
|
|
361
|
+
} else if (tag >= 0 && tag <= 6) {
|
|
362
|
+
return encodeDefHead(6, 121n + BigInt(tag))
|
|
363
|
+
} else if (tag >= 7 && tag <= 127) {
|
|
364
|
+
return encodeDefHead(6, 1280n + BigInt(tag - 7))
|
|
365
|
+
} else {
|
|
366
|
+
return encodeDefHead(6, 102n)
|
|
367
|
+
.concat(encodeDefHead(4, 2n))
|
|
368
|
+
.concat(encodeInt(BigInt(tag)))
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* @param bytes
|
|
374
|
+
* @returns
|
|
375
|
+
*/
|
|
376
|
+
export const isConstr = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
377
|
+
decodeDefHead(Bytes.makeStream(bytes).copy()).pipe(
|
|
378
|
+
Effect.map(([m, n]) => {
|
|
379
|
+
if (m == 6) {
|
|
380
|
+
return (
|
|
381
|
+
n == 102n || (n >= 121n && n <= 127n) || (n >= 1280n && n <= 1400n)
|
|
382
|
+
)
|
|
383
|
+
} else {
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
}),
|
|
387
|
+
Effect.catchTag("Cbor.DecodeError", () => {
|
|
388
|
+
return Effect.succeed(false)
|
|
389
|
+
})
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
const FLOAT16_HEAD = 249
|
|
393
|
+
const FLOAT32_HEAD = 250
|
|
394
|
+
const FLOAT64_HEAD = 251
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* @param bytes
|
|
398
|
+
* @returns
|
|
399
|
+
*/
|
|
400
|
+
export const decodeFloat = (bytes: Bytes.BytesLike): DecodeEffect<number> =>
|
|
401
|
+
Effect.gen(function* () {
|
|
402
|
+
const stream = Bytes.makeStream(bytes)
|
|
403
|
+
|
|
404
|
+
const head = yield* stream.shiftOne()
|
|
405
|
+
|
|
406
|
+
switch (head) {
|
|
407
|
+
case FLOAT16_HEAD:
|
|
408
|
+
return yield* Either.mapLeft(
|
|
409
|
+
Float.decodeFloat16(yield* stream.shiftMany(2)),
|
|
410
|
+
(e) =>
|
|
411
|
+
new DecodeError(stream, `failed to decode float16 (${e.message})`)
|
|
412
|
+
)
|
|
413
|
+
case FLOAT32_HEAD:
|
|
414
|
+
return yield* Either.mapLeft(
|
|
415
|
+
Float.decodeFloat32(yield* stream.shiftMany(4)),
|
|
416
|
+
(e) =>
|
|
417
|
+
new DecodeError(stream, `failed to decode float32 (${e.message})`)
|
|
418
|
+
)
|
|
419
|
+
case FLOAT64_HEAD:
|
|
420
|
+
return yield* Either.mapLeft(
|
|
421
|
+
Float.decodeFloat64(yield* stream.shiftMany(8)),
|
|
422
|
+
(e) =>
|
|
423
|
+
new DecodeError(stream, `faild to decode float64 (${e.message})`)
|
|
424
|
+
)
|
|
425
|
+
default:
|
|
426
|
+
return yield* new DecodeError(stream, "invalid float header")
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* @param bytes
|
|
432
|
+
* @returns
|
|
433
|
+
*/
|
|
434
|
+
export const decodeFloat16 = (bytes: Bytes.BytesLike): DecodeEffect<number> =>
|
|
435
|
+
Effect.gen(function* () {
|
|
436
|
+
const stream = Bytes.makeStream(bytes)
|
|
437
|
+
|
|
438
|
+
const head = yield* stream.shiftOne()
|
|
439
|
+
|
|
440
|
+
if (head != FLOAT16_HEAD) {
|
|
441
|
+
return yield* new DecodeError(stream, "invalid Float16 header")
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return yield* Either.mapLeft(
|
|
445
|
+
Float.decodeFloat16(yield* stream.shiftMany(2)),
|
|
446
|
+
(e) => new DecodeError(stream, `failed to decode float16 (${e.message})`)
|
|
447
|
+
)
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* @param bytes
|
|
452
|
+
* @returns
|
|
453
|
+
*/
|
|
454
|
+
export const decodeFloat32 = (bytes: Bytes.BytesLike): DecodeEffect<number> =>
|
|
455
|
+
Effect.gen(function* () {
|
|
456
|
+
const stream = Bytes.makeStream(bytes)
|
|
457
|
+
|
|
458
|
+
const head = yield* stream.shiftOne()
|
|
459
|
+
|
|
460
|
+
if (head != FLOAT32_HEAD) {
|
|
461
|
+
return yield* new DecodeError(stream, "invalid Float32 header")
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return yield* Either.mapLeft(
|
|
465
|
+
Float.decodeFloat32(yield* stream.shiftMany(4)),
|
|
466
|
+
(e) => new DecodeError(stream, `failed to decode float32 (${e.message})`)
|
|
467
|
+
)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @param bytes
|
|
472
|
+
* @returns
|
|
473
|
+
*/
|
|
474
|
+
export const decodeFloat64 = (bytes: Bytes.BytesLike): DecodeEffect<number> =>
|
|
475
|
+
Effect.gen(function* () {
|
|
476
|
+
const stream = Bytes.makeStream(bytes)
|
|
477
|
+
|
|
478
|
+
const head = yield* stream.shiftOne()
|
|
479
|
+
|
|
480
|
+
if (head != FLOAT64_HEAD) {
|
|
481
|
+
return yield* new DecodeError(stream, "invalid Float64 header")
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return yield* Either.mapLeft(
|
|
485
|
+
Float.decodeFloat64(yield* stream.shiftMany(8)),
|
|
486
|
+
(e) => new DecodeError(stream, `failed to decode float32 (${e.message})`)
|
|
487
|
+
)
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @param f
|
|
492
|
+
* @returns
|
|
493
|
+
*/
|
|
494
|
+
export function encodeFloat16(f: number): number[] {
|
|
495
|
+
return [FLOAT16_HEAD].concat(Float.encodeFloat16(f))
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* @param f
|
|
500
|
+
* @returns
|
|
501
|
+
*/
|
|
502
|
+
export function encodeFloat32(f: number): number[] {
|
|
503
|
+
return [FLOAT32_HEAD].concat(Float.encodeFloat32(f))
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* @param f
|
|
508
|
+
* @returns
|
|
509
|
+
*/
|
|
510
|
+
export function encodeFloat64(f: number): number[] {
|
|
511
|
+
return [FLOAT64_HEAD].concat(Float.encodeFloat64(f))
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* @param bytes
|
|
516
|
+
* @returns
|
|
517
|
+
*/
|
|
518
|
+
export const isFloat = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
519
|
+
Bytes.makeStream(bytes)
|
|
520
|
+
.peekOne()
|
|
521
|
+
.pipe(
|
|
522
|
+
Effect.map(
|
|
523
|
+
(head) =>
|
|
524
|
+
head == FLOAT16_HEAD || head == FLOAT32_HEAD || head == FLOAT64_HEAD
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* @param bytes
|
|
530
|
+
* @returns
|
|
531
|
+
*/
|
|
532
|
+
export const isFloat16 = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
533
|
+
Bytes.makeStream(bytes)
|
|
534
|
+
.peekOne()
|
|
535
|
+
.pipe(Effect.map((head) => head == FLOAT16_HEAD))
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* @param bytes
|
|
539
|
+
* @returns
|
|
540
|
+
*/
|
|
541
|
+
export const isFloat32 = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
542
|
+
Bytes.makeStream(bytes)
|
|
543
|
+
.peekOne()
|
|
544
|
+
.pipe(Effect.map((head) => head == FLOAT32_HEAD))
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* @param bytes
|
|
548
|
+
* @returns
|
|
549
|
+
*/
|
|
550
|
+
export const isFloat64 = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
551
|
+
Bytes.makeStream(bytes)
|
|
552
|
+
.peekOne()
|
|
553
|
+
.pipe(Effect.map((head) => head == FLOAT64_HEAD))
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* @param b0
|
|
557
|
+
* @returns
|
|
558
|
+
*/
|
|
559
|
+
function decodeFirstHeadByte(b0: number): [number, number] {
|
|
560
|
+
const m = Math.trunc(b0 / 32)
|
|
561
|
+
const n0 = b0 % 32
|
|
562
|
+
|
|
563
|
+
return [m, n0]
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* @param bytes
|
|
568
|
+
* @returns
|
|
569
|
+
* [majorType, n]
|
|
570
|
+
*/
|
|
571
|
+
export const decodeDefHead = (
|
|
572
|
+
bytes: Bytes.BytesLike
|
|
573
|
+
): DecodeEffect<[number, bigint]> =>
|
|
574
|
+
Effect.gen(function* () {
|
|
575
|
+
const stream = Bytes.makeStream(bytes)
|
|
576
|
+
|
|
577
|
+
if (stream.isAtEnd()) {
|
|
578
|
+
return yield* new DecodeError(stream, "Empty CBOR head")
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const first = yield* stream.shiftOne()
|
|
582
|
+
|
|
583
|
+
const [m, n0] = decodeFirstHeadByte(first)
|
|
584
|
+
|
|
585
|
+
if (n0 <= 23) {
|
|
586
|
+
return [m, BigInt(n0)]
|
|
587
|
+
} else if (n0 == 24) {
|
|
588
|
+
const l = yield* decodeIntInternal(stream, 1)
|
|
589
|
+
|
|
590
|
+
return [m, l]
|
|
591
|
+
} else if (n0 == 25) {
|
|
592
|
+
if (m == 7) {
|
|
593
|
+
return yield* new DecodeError(
|
|
594
|
+
stream,
|
|
595
|
+
"Unexpected float16 (hint: decode float16 by calling decodeFloat16 directly)"
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const n = yield* decodeIntInternal(stream, 2)
|
|
600
|
+
return [m, n]
|
|
601
|
+
} else if (n0 == 26) {
|
|
602
|
+
if (m == 7) {
|
|
603
|
+
return yield* new DecodeError(
|
|
604
|
+
stream,
|
|
605
|
+
"Unexpected float32 (hint: decode float32 by calling decodeFloat32 directly)"
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return [m, yield* decodeIntInternal(stream, 4)]
|
|
610
|
+
} else if (n0 == 27) {
|
|
611
|
+
if (m == 7) {
|
|
612
|
+
return yield* new DecodeError(
|
|
613
|
+
stream,
|
|
614
|
+
"Unexpected float64 (hint: decode float64 by calling decodeFloat64 directly)"
|
|
615
|
+
)
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return [m, yield* decodeIntInternal(stream, 8)]
|
|
619
|
+
} else if ((m == 2 || m == 3 || m == 4 || m == 5 || m == 7) && n0 == 31) {
|
|
620
|
+
// head value 31 is used an indefinite length marker for 2,3,4,5,7 (never for 0,1,6)
|
|
621
|
+
return yield* new DecodeError(
|
|
622
|
+
stream,
|
|
623
|
+
`Unexpected header m=${m} n0=${n0} (expected def instead of indef)`
|
|
624
|
+
)
|
|
625
|
+
} else {
|
|
626
|
+
return yield* new DecodeError(stream, "Bad CBOR header")
|
|
627
|
+
}
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* @param m major type
|
|
632
|
+
* @param n size parameter
|
|
633
|
+
* @returns uint8 bytes
|
|
634
|
+
* @throws
|
|
635
|
+
* If n is out of range (i.e. very very large)
|
|
636
|
+
*/
|
|
637
|
+
export function encodeDefHead(m: number, n: number | bigint): number[] {
|
|
638
|
+
if (n <= 23n) {
|
|
639
|
+
return [32 * m + Number(n)]
|
|
640
|
+
} else if (n >= 24n && n <= 255n) {
|
|
641
|
+
return [32 * m + 24, Number(n)]
|
|
642
|
+
} else if (n >= 256n && n <= 256n * 256n - 1n) {
|
|
643
|
+
return [
|
|
644
|
+
32 * m + 25,
|
|
645
|
+
Number((BigInt(n) / 256n) % 256n),
|
|
646
|
+
Number(BigInt(n) % 256n)
|
|
647
|
+
]
|
|
648
|
+
} else if (n >= 256n * 256n && n <= 256n * 256n * 256n * 256n - 1n) {
|
|
649
|
+
const e4 = BigEndian.encode(n)
|
|
650
|
+
|
|
651
|
+
while (e4.length < 4) {
|
|
652
|
+
e4.unshift(0)
|
|
653
|
+
}
|
|
654
|
+
return [32 * m + 26].concat(e4)
|
|
655
|
+
} else if (
|
|
656
|
+
n >= 256n * 256n * 256n * 256n &&
|
|
657
|
+
n <= 256n * 256n * 256n * 256n * 256n * 256n * 256n * 256n - 1n
|
|
658
|
+
) {
|
|
659
|
+
const e8 = BigEndian.encode(n)
|
|
660
|
+
|
|
661
|
+
while (e8.length < 8) {
|
|
662
|
+
e8.unshift(0)
|
|
663
|
+
}
|
|
664
|
+
return [32 * m + 27].concat(e8)
|
|
665
|
+
} else {
|
|
666
|
+
throw new Error("n out of range")
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* @param m
|
|
672
|
+
* @returns
|
|
673
|
+
*/
|
|
674
|
+
export function encodeIndefHead(m: number): number[] {
|
|
675
|
+
return [32 * m + 31]
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @param bytes
|
|
680
|
+
* @returns
|
|
681
|
+
*/
|
|
682
|
+
export const peekMajorType = (bytes: Bytes.BytesLike): PeekEffect<number> =>
|
|
683
|
+
Bytes.makeStream(bytes)
|
|
684
|
+
.peekOne()
|
|
685
|
+
.pipe(Effect.map((head) => Math.trunc(head / 32)))
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* @param bytes
|
|
689
|
+
* @returns
|
|
690
|
+
*/
|
|
691
|
+
export const peekMajorAndSimpleMinorType = (
|
|
692
|
+
bytes: Bytes.BytesLike
|
|
693
|
+
): PeekEffect<[number, number]> =>
|
|
694
|
+
Bytes.makeStream(bytes).peekOne().pipe(Effect.map(decodeFirstHeadByte))
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Decodes a CBOR encoded bigint integer.
|
|
698
|
+
* @param bytes
|
|
699
|
+
* @returns
|
|
700
|
+
*/
|
|
701
|
+
export const decodeInt = (bytes: Bytes.BytesLike): DecodeEffect<bigint> =>
|
|
702
|
+
Effect.gen(function* () {
|
|
703
|
+
const stream = Bytes.makeStream(bytes)
|
|
704
|
+
|
|
705
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
706
|
+
|
|
707
|
+
if (m == 0) {
|
|
708
|
+
return n
|
|
709
|
+
} else if (m == 1) {
|
|
710
|
+
return -n - 1n
|
|
711
|
+
} else if (m == 6) {
|
|
712
|
+
if (n == 2n) {
|
|
713
|
+
return yield* decodeIntInternal(stream)
|
|
714
|
+
} else if (n == 3n) {
|
|
715
|
+
return -(yield* decodeIntInternal(stream)) - 1n
|
|
716
|
+
} else {
|
|
717
|
+
return yield* new DecodeError(stream, `Unexpected tag m:${m}`)
|
|
718
|
+
}
|
|
719
|
+
} else {
|
|
720
|
+
return yield* new DecodeError(stream, `Unexpected tag m:${m}`)
|
|
721
|
+
}
|
|
722
|
+
})
|
|
723
|
+
|
|
724
|
+
const decodeIntInternal = (
|
|
725
|
+
stream: Bytes.Stream,
|
|
726
|
+
nBytes: number | undefined = undefined
|
|
727
|
+
): DecodeEffect<bigint> => {
|
|
728
|
+
return (
|
|
729
|
+
nBytes === undefined ? decodeBytes(stream) : stream.shiftMany(nBytes)
|
|
730
|
+
).pipe(
|
|
731
|
+
Effect.map(BigEndian.decode),
|
|
732
|
+
Effect.flatMap((result) => {
|
|
733
|
+
if (result._tag == "Left") {
|
|
734
|
+
return Effect.fail(
|
|
735
|
+
new DecodeError(
|
|
736
|
+
stream,
|
|
737
|
+
`failed to decode BigEndian int (${result.left.message})`
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
} else {
|
|
741
|
+
return Effect.succeed(result.right)
|
|
742
|
+
}
|
|
743
|
+
})
|
|
744
|
+
)
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Encodes a bigint integer using CBOR.
|
|
749
|
+
* @param n
|
|
750
|
+
* @returns
|
|
751
|
+
*/
|
|
752
|
+
export function encodeInt(n: number | bigint): number[] {
|
|
753
|
+
if (typeof n == "number") {
|
|
754
|
+
return encodeInt(BigInt(n))
|
|
755
|
+
} else if (n >= 0n && n <= (2n << 63n) - 1n) {
|
|
756
|
+
return encodeDefHead(0, n)
|
|
757
|
+
} else if (n >= 2n << 63n) {
|
|
758
|
+
return encodeDefHead(6, 2).concat(encodeBytes(BigEndian.encode(n)))
|
|
759
|
+
} else if (n <= -1n && n >= -(2n << 63n)) {
|
|
760
|
+
return encodeDefHead(1, -n - 1n)
|
|
761
|
+
} else {
|
|
762
|
+
return encodeDefHead(6, 3).concat(encodeBytes(BigEndian.encode(-n - 1n)))
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* @param bytes
|
|
768
|
+
* @returns
|
|
769
|
+
*/
|
|
770
|
+
export const isInt = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
771
|
+
peekMajorAndSimpleMinorType(bytes).pipe(
|
|
772
|
+
Effect.map(([m, n0]) => {
|
|
773
|
+
if (m == 0 || m == 1) {
|
|
774
|
+
return true
|
|
775
|
+
} else if (m == 6) {
|
|
776
|
+
return n0 == 2 || n0 == 3
|
|
777
|
+
} else {
|
|
778
|
+
return false
|
|
779
|
+
}
|
|
780
|
+
})
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Decodes a CBOR encoded list.
|
|
785
|
+
* A decoder function is called with the bytes of every contained item (nothing is returning directly).
|
|
786
|
+
* @template T
|
|
787
|
+
* @param itemDecoder
|
|
788
|
+
* @returns
|
|
789
|
+
*/
|
|
790
|
+
export const decodeList =
|
|
791
|
+
<T>(
|
|
792
|
+
itemDecoder: IndexedDecoder<T>
|
|
793
|
+
): ((bytes: Bytes.BytesLike) => DecodeEffect<T[]>) =>
|
|
794
|
+
(bytes: Bytes.BytesLike) =>
|
|
795
|
+
Effect.gen(function* () {
|
|
796
|
+
const stream = Bytes.makeStream(bytes)
|
|
797
|
+
|
|
798
|
+
const res: T[] = []
|
|
799
|
+
|
|
800
|
+
if (yield* isIndefList(stream)) {
|
|
801
|
+
yield* stream.shiftOne()
|
|
802
|
+
|
|
803
|
+
let i = 0
|
|
804
|
+
while ((yield* stream.peekOne()) != 255) {
|
|
805
|
+
res.push(yield* itemDecoder(stream, i))
|
|
806
|
+
i++
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const last = yield* stream.shiftOne()
|
|
810
|
+
if (last != 255) {
|
|
811
|
+
return yield* new DecodeError(stream, "Invalid def list head byte")
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
815
|
+
|
|
816
|
+
if (m != 4) {
|
|
817
|
+
return yield* new DecodeError(stream, "invalid def list head byte")
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
for (let i = 0; i < Number(n); i++) {
|
|
821
|
+
res.push(yield* itemDecoder(stream, i))
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return res
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* @param bytes
|
|
830
|
+
* @returns
|
|
831
|
+
*
|
|
832
|
+
*
|
|
833
|
+
*/
|
|
834
|
+
export const decodeListLazy = (
|
|
835
|
+
bytes: Bytes.BytesLike
|
|
836
|
+
): DecodeEffect<<T>(itemDecoder: IndexedDecoder<T>) => DecodeEffect<T>> =>
|
|
837
|
+
Effect.gen(function* () {
|
|
838
|
+
const stream = Bytes.makeStream(bytes)
|
|
839
|
+
|
|
840
|
+
let i = 0
|
|
841
|
+
let done = false
|
|
842
|
+
let checkDone: () => Effect.Effect<void, Bytes.EndOfStreamError>
|
|
843
|
+
|
|
844
|
+
if (yield* isIndefList(stream)) {
|
|
845
|
+
yield* stream.shiftOne()
|
|
846
|
+
|
|
847
|
+
checkDone = () =>
|
|
848
|
+
Effect.gen(function* () {
|
|
849
|
+
if ((yield* stream.peekOne()) == 255) {
|
|
850
|
+
yield* stream.shiftOne()
|
|
851
|
+
done = true
|
|
852
|
+
}
|
|
853
|
+
})
|
|
854
|
+
} else {
|
|
855
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
856
|
+
|
|
857
|
+
if (m != 4) {
|
|
858
|
+
return yield* new DecodeError(stream, "Unexpected header major type")
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
checkDone = () => {
|
|
862
|
+
if (i >= n) {
|
|
863
|
+
done = true
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return Effect.void
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
yield* checkDone()
|
|
871
|
+
|
|
872
|
+
const decodeItem = <T>(itemDecoder: IndexedDecoder<T>): DecodeEffect<T> =>
|
|
873
|
+
Effect.gen(function* () {
|
|
874
|
+
if (done) {
|
|
875
|
+
return yield* new DecodeError(stream, "end-of-list")
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const res = yield* itemDecoder(stream, i)
|
|
879
|
+
|
|
880
|
+
i++
|
|
881
|
+
|
|
882
|
+
yield* checkDone()
|
|
883
|
+
|
|
884
|
+
return res
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
return decodeItem
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* @param bytes
|
|
892
|
+
* @returns
|
|
893
|
+
*/
|
|
894
|
+
export const decodeListLazyOption = (
|
|
895
|
+
bytes: Bytes.BytesLike
|
|
896
|
+
): DecodeEffect<
|
|
897
|
+
<T>(itemDecoder: IndexedDecoder<T>) => DecodeEffect<T | undefined>
|
|
898
|
+
> =>
|
|
899
|
+
Effect.gen(function* () {
|
|
900
|
+
const stream = Bytes.makeStream(bytes)
|
|
901
|
+
|
|
902
|
+
let i = 0
|
|
903
|
+
let done = false
|
|
904
|
+
let checkDone: () => Effect.Effect<void, Bytes.EndOfStreamError>
|
|
905
|
+
|
|
906
|
+
if (yield* isIndefList(stream)) {
|
|
907
|
+
yield* stream.shiftOne()
|
|
908
|
+
|
|
909
|
+
checkDone = () =>
|
|
910
|
+
Effect.gen(function* () {
|
|
911
|
+
if ((yield* stream.peekOne()) == 255) {
|
|
912
|
+
yield* stream.shiftOne()
|
|
913
|
+
done = true
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
} else {
|
|
917
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
918
|
+
|
|
919
|
+
if (m != 4) {
|
|
920
|
+
return yield* new DecodeError(stream, "Unexpected major type for list")
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
checkDone = () => {
|
|
924
|
+
if (i >= n) {
|
|
925
|
+
done = true
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return Effect.void
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
yield* checkDone()
|
|
933
|
+
|
|
934
|
+
const decodeItem = <T>(
|
|
935
|
+
itemDecoder: IndexedDecoder<T>
|
|
936
|
+
): DecodeEffect<T | undefined> =>
|
|
937
|
+
Effect.gen(function* () {
|
|
938
|
+
if (done) {
|
|
939
|
+
return undefined
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
const res = yield* itemDecoder(stream, i)
|
|
943
|
+
|
|
944
|
+
i++
|
|
945
|
+
|
|
946
|
+
yield* checkDone()
|
|
947
|
+
|
|
948
|
+
return res
|
|
949
|
+
})
|
|
950
|
+
|
|
951
|
+
return decodeItem
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* This follows the serialization format that the Haskell input-output-hk/plutus UPLC evaluator (i.e. empty lists use `encodeDefList`, non-empty lists use `encodeIndefList`).
|
|
956
|
+
* See [well-typed/cborg/serialise/src/Codec/Serialise/Class.hs](https://github.com/well-typed/cborg/blob/4bdc818a1f0b35f38bc118a87944630043b58384/serialise/src/Codec/Serialise/Class.hs#L181).
|
|
957
|
+
* @param items already encoded
|
|
958
|
+
* @returns
|
|
959
|
+
*/
|
|
960
|
+
export function encodeList(items: readonly number[][]): number[] {
|
|
961
|
+
return items.length > 0 ? encodeIndefList(items) : encodeDefList(items)
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* @returns
|
|
966
|
+
*/
|
|
967
|
+
function encodeIndefListStart(): number[] {
|
|
968
|
+
return encodeIndefHead(4)
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* @param list
|
|
973
|
+
* @returns
|
|
974
|
+
*/
|
|
975
|
+
function encodeListInternal(list: readonly number[][]): number[] {
|
|
976
|
+
/**
|
|
977
|
+
* @type {number[]}
|
|
978
|
+
*/
|
|
979
|
+
let res: number[] = []
|
|
980
|
+
for (const item of list) {
|
|
981
|
+
res = res.concat(item)
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return res
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const INDEF_LIST_END = [255]
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* Encodes a list of CBOR encodeable items using CBOR indefinite length encoding.
|
|
991
|
+
* @param list Each item is either already serialized.
|
|
992
|
+
* @returns
|
|
993
|
+
*/
|
|
994
|
+
export function encodeIndefList(list: readonly number[][]): number[] {
|
|
995
|
+
return encodeIndefListStart()
|
|
996
|
+
.concat(encodeListInternal(list))
|
|
997
|
+
.concat(INDEF_LIST_END)
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* @param n
|
|
1002
|
+
* @returns
|
|
1003
|
+
*/
|
|
1004
|
+
function encodeDefListStart(n: bigint): number[] {
|
|
1005
|
+
return encodeDefHead(4, n)
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Encodes a list of CBOR encodeable items using CBOR definite length encoding
|
|
1010
|
+
* (i.e. header bytes of the element represent the length of the list).
|
|
1011
|
+
* @param items Each item is already serialized
|
|
1012
|
+
* @returns
|
|
1013
|
+
*/
|
|
1014
|
+
export function encodeDefList(items: readonly number[][]): number[] {
|
|
1015
|
+
return encodeDefListStart(BigInt(items.length)).concat(
|
|
1016
|
+
encodeListInternal(items)
|
|
1017
|
+
)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* @param bytes
|
|
1022
|
+
* @returns
|
|
1023
|
+
*/
|
|
1024
|
+
export const isList = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1025
|
+
peekMajorType(bytes).pipe(Effect.map((m) => m == 4))
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* @param bytes
|
|
1029
|
+
* @returns
|
|
1030
|
+
*/
|
|
1031
|
+
export const isDefList = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1032
|
+
Effect.gen(function* () {
|
|
1033
|
+
const stream = Bytes.makeStream(bytes)
|
|
1034
|
+
|
|
1035
|
+
return (
|
|
1036
|
+
(yield* peekMajorType(stream)) == 4 &&
|
|
1037
|
+
(yield* stream.peekOne()) != 4 * 32 + 31
|
|
1038
|
+
)
|
|
1039
|
+
})
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* @param bytes
|
|
1043
|
+
* @returns
|
|
1044
|
+
*/
|
|
1045
|
+
export const isIndefList = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1046
|
+
Bytes.makeStream(bytes)
|
|
1047
|
+
.peekOne()
|
|
1048
|
+
.pipe(Effect.map((head) => head == 4 * 32 + 31))
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Decodes a CBOR encoded map.
|
|
1052
|
+
* Calls a decoder function for each key-value pair (nothing is returned directly).
|
|
1053
|
+
*
|
|
1054
|
+
* The decoder function is responsible for separating the key from the value,
|
|
1055
|
+
* which are simply stored as consecutive CBOR elements.
|
|
1056
|
+
* @param keyDecoder
|
|
1057
|
+
* @param valueDecoder
|
|
1058
|
+
* @returns
|
|
1059
|
+
*/
|
|
1060
|
+
export const decodeMap =
|
|
1061
|
+
<TKey, TValue>(keyDecoder: Decoder<TKey>, valueDecoder: Decoder<TValue>) =>
|
|
1062
|
+
(bytes: Bytes.BytesLike): DecodeEffect<[TKey, TValue][]> =>
|
|
1063
|
+
Effect.gen(function* () {
|
|
1064
|
+
const stream = Bytes.makeStream(bytes)
|
|
1065
|
+
|
|
1066
|
+
if (yield* isIndefMap(stream)) {
|
|
1067
|
+
yield* stream.shiftOne()
|
|
1068
|
+
|
|
1069
|
+
return yield* decodeIndefMap<TKey, TValue>(
|
|
1070
|
+
stream,
|
|
1071
|
+
keyDecoder,
|
|
1072
|
+
valueDecoder
|
|
1073
|
+
)
|
|
1074
|
+
} else {
|
|
1075
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
1076
|
+
|
|
1077
|
+
if (m != 5) {
|
|
1078
|
+
return yield* new DecodeError(stream, "invalid def map")
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
return yield* decodeDefMap<TKey, TValue>(
|
|
1082
|
+
stream,
|
|
1083
|
+
Number(n),
|
|
1084
|
+
keyDecoder,
|
|
1085
|
+
valueDecoder
|
|
1086
|
+
)
|
|
1087
|
+
}
|
|
1088
|
+
})
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* Internal use only, header already decoded
|
|
1092
|
+
* @param stream
|
|
1093
|
+
* @param n
|
|
1094
|
+
* @param keyDecoder
|
|
1095
|
+
* @param valueDecoder
|
|
1096
|
+
* @returns
|
|
1097
|
+
*/
|
|
1098
|
+
const decodeDefMap = <TKey, TValue>(
|
|
1099
|
+
stream: Bytes.Stream,
|
|
1100
|
+
n: number,
|
|
1101
|
+
keyDecoder: Decoder<TKey>,
|
|
1102
|
+
valueDecoder: Decoder<TValue>
|
|
1103
|
+
): DecodeEffect<[TKey, TValue][]> =>
|
|
1104
|
+
Effect.gen(function* () {
|
|
1105
|
+
const res: [TKey, TValue][] = []
|
|
1106
|
+
|
|
1107
|
+
for (let i = 0; i < n; i++) {
|
|
1108
|
+
res.push([yield* keyDecoder(stream), yield* valueDecoder(stream)])
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
return res
|
|
1112
|
+
})
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Used internally, head already decoded
|
|
1116
|
+
* @template TKey
|
|
1117
|
+
* @template TValue
|
|
1118
|
+
* @param stream
|
|
1119
|
+
* @param keyDecoder
|
|
1120
|
+
* @param valueDecoder
|
|
1121
|
+
* @returns
|
|
1122
|
+
*/
|
|
1123
|
+
const decodeIndefMap = <TKey, TValue>(
|
|
1124
|
+
stream: Bytes.Stream,
|
|
1125
|
+
keyDecoder: Decoder<TKey>,
|
|
1126
|
+
valueDecoder: Decoder<TValue>
|
|
1127
|
+
): DecodeEffect<[TKey, TValue][]> =>
|
|
1128
|
+
Effect.gen(function* () {
|
|
1129
|
+
const res: [TKey, TValue][] = []
|
|
1130
|
+
|
|
1131
|
+
while ((yield* stream.peekOne()) != 255) {
|
|
1132
|
+
res.push([yield* keyDecoder(stream), yield* valueDecoder(stream)])
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
yield* stream.shiftOne()
|
|
1136
|
+
|
|
1137
|
+
return res
|
|
1138
|
+
})
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* Unlike lists, the default serialization format for maps seems to always be the defined format
|
|
1142
|
+
* @param pairs already encoded
|
|
1143
|
+
* @returns
|
|
1144
|
+
*/
|
|
1145
|
+
export function encodeMap(pairs: [number[], number[]][]): number[] {
|
|
1146
|
+
return encodeDefMap(pairs)
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Encodes a list of key-value pairs.
|
|
1151
|
+
* @param pairs
|
|
1152
|
+
* Each key and each value is an already encoded list of CBOR bytes.
|
|
1153
|
+
* @returns
|
|
1154
|
+
*/
|
|
1155
|
+
export function encodeDefMap(pairs: [number[], number[]][]): number[] {
|
|
1156
|
+
return encodeDefHead(5, BigInt(pairs.length)).concat(encodeMapInternal(pairs))
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Encodes a list of key-value pairs using the length undefined format.
|
|
1161
|
+
* @param pairs
|
|
1162
|
+
* Each key and each value is an already encoded list of CBOR bytes.
|
|
1163
|
+
* @returns
|
|
1164
|
+
*/
|
|
1165
|
+
export function encodeIndefMap(pairs: [number[], number[]][]): number[] {
|
|
1166
|
+
return encodeIndefHead(5).concat(encodeMapInternal(pairs)).concat([255])
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* @param pairs already encoded
|
|
1171
|
+
* @returns
|
|
1172
|
+
*/
|
|
1173
|
+
function encodeMapInternal(pairs: [number[], number[]][]): number[] {
|
|
1174
|
+
let res: number[] = []
|
|
1175
|
+
|
|
1176
|
+
for (const pair of pairs) {
|
|
1177
|
+
const key = pair[0]
|
|
1178
|
+
const value = pair[1]
|
|
1179
|
+
|
|
1180
|
+
res = res.concat(key)
|
|
1181
|
+
res = res.concat(value)
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
return res
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* @param bytes
|
|
1189
|
+
* @returns
|
|
1190
|
+
*/
|
|
1191
|
+
export const isMap = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1192
|
+
peekMajorType(bytes).pipe(Effect.map((m) => m == 5))
|
|
1193
|
+
|
|
1194
|
+
/**
|
|
1195
|
+
* @param bytes
|
|
1196
|
+
* @returns
|
|
1197
|
+
*/
|
|
1198
|
+
const isIndefMap = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1199
|
+
Bytes.makeStream(bytes)
|
|
1200
|
+
.peekOne()
|
|
1201
|
+
.pipe(Effect.map((head) => head == 5 * 32 + 31))
|
|
1202
|
+
|
|
1203
|
+
const NULL_BYTE = 246 // m = 7, n = 22
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Checks if next element in `bytes` is a `null`.
|
|
1207
|
+
* Throws an error if it isn't.
|
|
1208
|
+
* @param bytes
|
|
1209
|
+
* @returns
|
|
1210
|
+
*/
|
|
1211
|
+
export const decodeNull = (bytes: Bytes.BytesLike): DecodeEffect<null> =>
|
|
1212
|
+
Effect.gen(function* () {
|
|
1213
|
+
const stream = Bytes.makeStream(bytes)
|
|
1214
|
+
|
|
1215
|
+
const b = yield* stream.shiftOne()
|
|
1216
|
+
|
|
1217
|
+
if (b != NULL_BYTE) {
|
|
1218
|
+
return yield* new DecodeError(stream, "not null")
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
return null
|
|
1222
|
+
})
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Encode `null` into its CBOR representation.
|
|
1226
|
+
* @param _null ignored
|
|
1227
|
+
* @returns
|
|
1228
|
+
*/
|
|
1229
|
+
export function encodeNull(_null: null = null): number[] {
|
|
1230
|
+
return [NULL_BYTE]
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
/**
|
|
1234
|
+
* @param bytes
|
|
1235
|
+
* @returns
|
|
1236
|
+
*/
|
|
1237
|
+
export const isNull = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1238
|
+
Bytes.makeStream(bytes)
|
|
1239
|
+
.peekOne()
|
|
1240
|
+
.pipe(Effect.map((head) => head == NULL_BYTE))
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Decodes a CBOR encoded object with integer keys.
|
|
1244
|
+
* For each field a decoder is called which takes the field index and the field bytes as arguments.
|
|
1245
|
+
* @template Decoders
|
|
1246
|
+
* @param fieldDecoders
|
|
1247
|
+
* @returns
|
|
1248
|
+
*/
|
|
1249
|
+
export const decodeObjectIKey =
|
|
1250
|
+
<Decoders extends { [key: number]: Decoder<any> }>(fieldDecoders: Decoders) =>
|
|
1251
|
+
(
|
|
1252
|
+
bytes: Bytes.BytesLike
|
|
1253
|
+
): DecodeEffect<{
|
|
1254
|
+
[D in keyof Decoders]+?: Decoders[D] extends Decoder<infer T> ? T : never
|
|
1255
|
+
}> => {
|
|
1256
|
+
const stream = Bytes.makeStream(bytes)
|
|
1257
|
+
|
|
1258
|
+
const res: Record<number, any> = {}
|
|
1259
|
+
|
|
1260
|
+
return decodeMap(
|
|
1261
|
+
() => Effect.succeed(null),
|
|
1262
|
+
(pairStream) =>
|
|
1263
|
+
Effect.gen(function* () {
|
|
1264
|
+
const key = Number(yield* decodeInt(pairStream))
|
|
1265
|
+
|
|
1266
|
+
const decoder: Decoder<any> | undefined = fieldDecoders[key]
|
|
1267
|
+
|
|
1268
|
+
if (decoder === undefined) {
|
|
1269
|
+
return yield* new DecodeError(
|
|
1270
|
+
pairStream,
|
|
1271
|
+
`unhandled object field ${key}`
|
|
1272
|
+
)
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
1276
|
+
res[key] = yield* decoder(pairStream)
|
|
1277
|
+
|
|
1278
|
+
return Effect.void
|
|
1279
|
+
})
|
|
1280
|
+
)(stream).pipe(
|
|
1281
|
+
Effect.map(() => {
|
|
1282
|
+
return res as {
|
|
1283
|
+
[D in keyof Decoders]+?: Decoders[D] extends Decoder<infer T>
|
|
1284
|
+
? T
|
|
1285
|
+
: never
|
|
1286
|
+
}
|
|
1287
|
+
})
|
|
1288
|
+
)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Decodes a CBOR encoded object with string keys.
|
|
1293
|
+
* For each field a decoder is called which takes the field index and the field bytes as arguments.
|
|
1294
|
+
* @template Decoders
|
|
1295
|
+
* @param fieldDecoders
|
|
1296
|
+
* @returns
|
|
1297
|
+
*/
|
|
1298
|
+
export const decodeObjectSKey =
|
|
1299
|
+
<Decoders extends { [key: string]: Decoder<any> }>(fieldDecoders: Decoders) =>
|
|
1300
|
+
(
|
|
1301
|
+
bytes: Bytes.BytesLike
|
|
1302
|
+
): DecodeEffect<{
|
|
1303
|
+
[D in keyof Decoders]+?: Decoders[D] extends Decoder<infer T> ? T : never
|
|
1304
|
+
}> => {
|
|
1305
|
+
const stream = Bytes.makeStream(bytes)
|
|
1306
|
+
|
|
1307
|
+
const res: Record<string, any> = {}
|
|
1308
|
+
|
|
1309
|
+
return decodeMap(
|
|
1310
|
+
() => Effect.succeed(null),
|
|
1311
|
+
(pairStream) =>
|
|
1312
|
+
Effect.gen(function* () {
|
|
1313
|
+
const key = yield* decodeString(pairStream)
|
|
1314
|
+
|
|
1315
|
+
const decoder: Decoder<any> | undefined = fieldDecoders[key]
|
|
1316
|
+
|
|
1317
|
+
if (decoder === undefined) {
|
|
1318
|
+
return yield* new DecodeError(
|
|
1319
|
+
pairStream,
|
|
1320
|
+
`unhandled object field ${key}`
|
|
1321
|
+
)
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */
|
|
1325
|
+
res[key] = yield* decoder(pairStream)
|
|
1326
|
+
|
|
1327
|
+
return Effect.void
|
|
1328
|
+
})
|
|
1329
|
+
)(stream).pipe(
|
|
1330
|
+
Effect.map(() => {
|
|
1331
|
+
return res as {
|
|
1332
|
+
[D in keyof Decoders]+?: Decoders[D] extends Decoder<infer T>
|
|
1333
|
+
? T
|
|
1334
|
+
: never
|
|
1335
|
+
}
|
|
1336
|
+
})
|
|
1337
|
+
)
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Encodes an object with optional fields using integer keys.
|
|
1342
|
+
* @param object
|
|
1343
|
+
* A `Map` with integer keys representing the field indices.
|
|
1344
|
+
* @returns
|
|
1345
|
+
*/
|
|
1346
|
+
export function encodeObjectIKey(
|
|
1347
|
+
object: Map<number, number[]> | Record<number, number[]>
|
|
1348
|
+
): number[] {
|
|
1349
|
+
const entries: [number[], number[]][] =
|
|
1350
|
+
object instanceof Map
|
|
1351
|
+
? Array.from(object.entries()).map((pair) => [
|
|
1352
|
+
encodeInt(pair[0]),
|
|
1353
|
+
pair[1]
|
|
1354
|
+
])
|
|
1355
|
+
: Object.entries(object).map((pair) => [
|
|
1356
|
+
encodeInt(parseInt(pair[0])),
|
|
1357
|
+
pair[1]
|
|
1358
|
+
])
|
|
1359
|
+
|
|
1360
|
+
return encodeDefMap(entries)
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
/**
|
|
1364
|
+
* Encodes an object with optional fields using string keys.
|
|
1365
|
+
* @param object
|
|
1366
|
+
* A `Map` with string keys representing the field indices.
|
|
1367
|
+
* @returns
|
|
1368
|
+
*/
|
|
1369
|
+
export function encodeObjectSKey(
|
|
1370
|
+
object: Map<string, number[]> | Record<string, number[]>
|
|
1371
|
+
): number[] {
|
|
1372
|
+
const entries: [number[], number[]][] =
|
|
1373
|
+
object instanceof Map
|
|
1374
|
+
? Array.from(object.entries()).map((pair) => [
|
|
1375
|
+
encodeString(pair[0]),
|
|
1376
|
+
pair[1]
|
|
1377
|
+
])
|
|
1378
|
+
: Object.entries(object).map((pair) => [encodeString(pair[0]), pair[1]])
|
|
1379
|
+
|
|
1380
|
+
return encodeDefMap(entries)
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* @param bytes
|
|
1385
|
+
* @returns
|
|
1386
|
+
*/
|
|
1387
|
+
export const isObject = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1388
|
+
isMap(bytes)
|
|
1389
|
+
|
|
1390
|
+
const SET_TAG = 258n
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Like a list, but with an optional 258 tag
|
|
1394
|
+
* See: https://github.com/Emurgo/cardano-serialization-lib/releases/tag/13.0.0
|
|
1395
|
+
* @template T
|
|
1396
|
+
* @param itemDecoder
|
|
1397
|
+
* @returns
|
|
1398
|
+
*/
|
|
1399
|
+
export const decodeSet =
|
|
1400
|
+
<T>(itemDecoder: Decoder<T>) =>
|
|
1401
|
+
(bytes: Bytes.BytesLike): DecodeEffect<T[]> =>
|
|
1402
|
+
Effect.gen(function* () {
|
|
1403
|
+
const stream = Bytes.makeStream(bytes)
|
|
1404
|
+
|
|
1405
|
+
if (yield* isTag(stream)) {
|
|
1406
|
+
const tag = yield* decodeTag(stream)
|
|
1407
|
+
if (tag != SET_TAG) {
|
|
1408
|
+
return yield* new DecodeError(
|
|
1409
|
+
stream,
|
|
1410
|
+
`expected tag ${SET_TAG} for set, got tag ${tag}`
|
|
1411
|
+
)
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
return yield* decodeList(itemDecoder)(stream)
|
|
1416
|
+
})
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* A tagged def list (tag 258n)
|
|
1420
|
+
* @param items
|
|
1421
|
+
* @returns
|
|
1422
|
+
*/
|
|
1423
|
+
export function encodeSet(items: number[][]): number[] {
|
|
1424
|
+
return encodeTag(SET_TAG).concat(encodeDefList(items))
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* @param bytes
|
|
1429
|
+
* @returns
|
|
1430
|
+
*/
|
|
1431
|
+
export const isSet = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1432
|
+
peekTag(bytes).pipe(Effect.map((t) => t == SET_TAG))
|
|
1433
|
+
|
|
1434
|
+
/**
|
|
1435
|
+
* @param bytes
|
|
1436
|
+
* @returns
|
|
1437
|
+
*/
|
|
1438
|
+
export const decodeString = (bytes: Bytes.BytesLike): DecodeEffect<string> =>
|
|
1439
|
+
Effect.gen(function* () {
|
|
1440
|
+
const stream = Bytes.makeStream(bytes)
|
|
1441
|
+
|
|
1442
|
+
if (yield* isDefList(stream)) {
|
|
1443
|
+
let result = ""
|
|
1444
|
+
|
|
1445
|
+
yield* decodeList((itemBytes) =>
|
|
1446
|
+
decodeStringInternal(itemBytes).pipe(
|
|
1447
|
+
Effect.tap((s) => {
|
|
1448
|
+
result += s
|
|
1449
|
+
})
|
|
1450
|
+
)
|
|
1451
|
+
)(stream)
|
|
1452
|
+
|
|
1453
|
+
return result
|
|
1454
|
+
} else {
|
|
1455
|
+
return yield* decodeStringInternal(stream)
|
|
1456
|
+
}
|
|
1457
|
+
})
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* @param bytes
|
|
1461
|
+
* @returns
|
|
1462
|
+
*/
|
|
1463
|
+
const decodeStringInternal = (bytes: Bytes.BytesLike): DecodeEffect<string> =>
|
|
1464
|
+
Effect.gen(function* () {
|
|
1465
|
+
const stream = Bytes.makeStream(bytes)
|
|
1466
|
+
|
|
1467
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
1468
|
+
|
|
1469
|
+
if (m !== 3) {
|
|
1470
|
+
return yield* new DecodeError(stream, "unexpected")
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
return yield* Utf8.decode(yield* stream.shiftMany(Number(n))).pipe(
|
|
1474
|
+
Effect.mapError(
|
|
1475
|
+
(e) => new DecodeError(stream, `invalid utf8 (${e.message})`)
|
|
1476
|
+
)
|
|
1477
|
+
)
|
|
1478
|
+
})
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* Encodes a Utf8 string into Cbor bytes.
|
|
1482
|
+
* Strings can be split into lists with chunks of up to 64 bytes
|
|
1483
|
+
* to play nice with Cardano tx metadata constraints.
|
|
1484
|
+
* @param str
|
|
1485
|
+
* @param split
|
|
1486
|
+
* @returns
|
|
1487
|
+
*/
|
|
1488
|
+
export function encodeString(str: string, split: boolean = false): number[] {
|
|
1489
|
+
const bytes = Bytes.toArray(Utf8.encode(str))
|
|
1490
|
+
|
|
1491
|
+
if (split && bytes.length > 64) {
|
|
1492
|
+
const chunks: number[][] = []
|
|
1493
|
+
|
|
1494
|
+
let i = 0
|
|
1495
|
+
while (i < bytes.length) {
|
|
1496
|
+
// We encode the largest chunk up to 64 bytes
|
|
1497
|
+
// that is valid UTF-8
|
|
1498
|
+
let maxChunkLength = 64
|
|
1499
|
+
let chunk: number[]
|
|
1500
|
+
while (true) {
|
|
1501
|
+
chunk = bytes.slice(i, i + maxChunkLength)
|
|
1502
|
+
if (Utf8.isValid(chunk)) {
|
|
1503
|
+
break
|
|
1504
|
+
}
|
|
1505
|
+
maxChunkLength--
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
chunks.push(encodeDefHead(3, BigInt(chunk.length)).concat(chunk))
|
|
1509
|
+
i += chunk.length
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
return encodeDefList(chunks)
|
|
1513
|
+
} else {
|
|
1514
|
+
return encodeDefHead(3, BigInt(bytes.length)).concat(bytes)
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* @param bytes
|
|
1520
|
+
* @returns
|
|
1521
|
+
*/
|
|
1522
|
+
export const isString = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1523
|
+
peekMajorType(bytes).pipe(Effect.map((m) => m == 3))
|
|
1524
|
+
|
|
1525
|
+
/**
|
|
1526
|
+
* @param bytes
|
|
1527
|
+
* @returns
|
|
1528
|
+
*/
|
|
1529
|
+
export const decodeTag = (bytes: Bytes.BytesLike): DecodeEffect<bigint> =>
|
|
1530
|
+
Effect.gen(function* () {
|
|
1531
|
+
const stream = Bytes.makeStream(bytes)
|
|
1532
|
+
|
|
1533
|
+
const [m, n] = yield* decodeDefHead(stream)
|
|
1534
|
+
|
|
1535
|
+
if (m != 6) {
|
|
1536
|
+
return yield* new DecodeError(stream, "unexpected major type for tag")
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
return n
|
|
1540
|
+
})
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Unrelated to constructor
|
|
1544
|
+
* @param tag
|
|
1545
|
+
* @returns
|
|
1546
|
+
*/
|
|
1547
|
+
export function encodeTag(tag: number | bigint): number[] {
|
|
1548
|
+
if (typeof tag == "number") {
|
|
1549
|
+
return encodeTag(BigInt(tag))
|
|
1550
|
+
} else if (tag < 0) {
|
|
1551
|
+
throw new Error("can't encode negative tag")
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
return encodeDefHead(6, tag)
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* @param bytes
|
|
1559
|
+
* @returns
|
|
1560
|
+
*/
|
|
1561
|
+
export const isTag = (bytes: Bytes.BytesLike): PeekEffect<boolean> =>
|
|
1562
|
+
peekMajorType(bytes).pipe(Effect.map((m) => m == 6))
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* @param bytes
|
|
1566
|
+
* @returns
|
|
1567
|
+
*/
|
|
1568
|
+
export const peekTag = (
|
|
1569
|
+
bytes: Bytes.BytesLike
|
|
1570
|
+
): PeekEffect<bigint | undefined> =>
|
|
1571
|
+
decodeTag(Bytes.makeStream(bytes).copy()).pipe(
|
|
1572
|
+
Effect.catchTag("Cbor.DecodeError", () => Effect.succeed(undefined))
|
|
1573
|
+
)
|
|
1574
|
+
|
|
1575
|
+
/**
|
|
1576
|
+
* @param bytes
|
|
1577
|
+
* @returns
|
|
1578
|
+
*/
|
|
1579
|
+
export const decodeTagged = (
|
|
1580
|
+
bytes: Bytes.BytesLike
|
|
1581
|
+
): DecodeEffect<[number, <T>(itemDecoder: Decoder<T>) => DecodeEffect<T>]> =>
|
|
1582
|
+
Effect.gen(function* () {
|
|
1583
|
+
const stream = Bytes.makeStream(bytes)
|
|
1584
|
+
|
|
1585
|
+
if (yield* isList(stream)) {
|
|
1586
|
+
const decodeItem = yield* decodeListLazy(stream)
|
|
1587
|
+
|
|
1588
|
+
const tag = Number(yield* decodeItem(decodeInt))
|
|
1589
|
+
|
|
1590
|
+
return [tag, decodeItem]
|
|
1591
|
+
} else {
|
|
1592
|
+
return yield* decodeConstrLazy(stream)
|
|
1593
|
+
}
|
|
1594
|
+
})
|
|
1595
|
+
|
|
1596
|
+
/**
|
|
1597
|
+
* @template Decoders
|
|
1598
|
+
* @template OptionalDecoders
|
|
1599
|
+
* @param itemDecoders
|
|
1600
|
+
* @param optionalDecoders
|
|
1601
|
+
* Defaults to empty tuple
|
|
1602
|
+
* @returns
|
|
1603
|
+
*/
|
|
1604
|
+
export const decodeTuple =
|
|
1605
|
+
<
|
|
1606
|
+
Decoders extends Array<Decoder<any>>,
|
|
1607
|
+
OptionalDecoders extends Array<Decoder<any>>
|
|
1608
|
+
>(
|
|
1609
|
+
itemDecoders: [...Decoders],
|
|
1610
|
+
optionalDecoders: [...OptionalDecoders] | [] = []
|
|
1611
|
+
) =>
|
|
1612
|
+
(
|
|
1613
|
+
bytes: Bytes.BytesLike
|
|
1614
|
+
): DecodeEffect<
|
|
1615
|
+
[
|
|
1616
|
+
...{
|
|
1617
|
+
[D in keyof Decoders]: Decoders[D] extends Decoder<infer T> ? T : never
|
|
1618
|
+
},
|
|
1619
|
+
...{
|
|
1620
|
+
[D in keyof OptionalDecoders]: OptionalDecoders[D] extends Decoder<
|
|
1621
|
+
infer T
|
|
1622
|
+
>
|
|
1623
|
+
? T | undefined
|
|
1624
|
+
: never
|
|
1625
|
+
}
|
|
1626
|
+
]
|
|
1627
|
+
> =>
|
|
1628
|
+
Effect.gen(function* () {
|
|
1629
|
+
const stream = Bytes.makeStream(bytes)
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* decodeList is the right decoder, but has the wrong type interface
|
|
1633
|
+
* Cast the result to `any` to avoid type errors
|
|
1634
|
+
*/
|
|
1635
|
+
const res: any[] = yield* decodeList((itemStream, i) =>
|
|
1636
|
+
Effect.gen(function* () {
|
|
1637
|
+
let decoder: Decoder<any> | undefined = itemDecoders[i]
|
|
1638
|
+
|
|
1639
|
+
if (decoder === undefined) {
|
|
1640
|
+
decoder = optionalDecoders[i - itemDecoders.length]
|
|
1641
|
+
|
|
1642
|
+
if (decoder === undefined) {
|
|
1643
|
+
return yield* new DecodeError(
|
|
1644
|
+
itemStream,
|
|
1645
|
+
`expected at most ${
|
|
1646
|
+
itemDecoders.length + optionalDecoders.length
|
|
1647
|
+
} items, got more than ${i}`
|
|
1648
|
+
)
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
1653
|
+
return yield* decoder(itemStream)
|
|
1654
|
+
})
|
|
1655
|
+
)(stream)
|
|
1656
|
+
|
|
1657
|
+
if (res.length < itemDecoders.length) {
|
|
1658
|
+
return yield* new DecodeError(
|
|
1659
|
+
stream,
|
|
1660
|
+
`expected at least ${itemDecoders.length} items, only got ${res.length}`
|
|
1661
|
+
)
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
return res as [
|
|
1665
|
+
...{
|
|
1666
|
+
[D in keyof Decoders]: Decoders[D] extends Decoder<infer T>
|
|
1667
|
+
? T
|
|
1668
|
+
: never
|
|
1669
|
+
},
|
|
1670
|
+
...{
|
|
1671
|
+
[D in keyof OptionalDecoders]: OptionalDecoders[D] extends Decoder<
|
|
1672
|
+
infer T
|
|
1673
|
+
>
|
|
1674
|
+
? T | undefined
|
|
1675
|
+
: never
|
|
1676
|
+
}
|
|
1677
|
+
]
|
|
1678
|
+
})
|
|
1679
|
+
|
|
1680
|
+
/**
|
|
1681
|
+
* @param bytes
|
|
1682
|
+
* @returns
|
|
1683
|
+
*/
|
|
1684
|
+
export function decodeTupleLazy(
|
|
1685
|
+
bytes: Bytes.BytesLike
|
|
1686
|
+
): DecodeEffect<<T>(itemDecoder: Decoder<T>) => DecodeEffect<T>> {
|
|
1687
|
+
return decodeListLazy(bytes)
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/**
|
|
1691
|
+
* @param tuple
|
|
1692
|
+
* @returns
|
|
1693
|
+
*/
|
|
1694
|
+
export function encodeTuple(tuple: number[][]): number[] {
|
|
1695
|
+
return encodeDefList(tuple)
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
/**
|
|
1699
|
+
* @param bytes
|
|
1700
|
+
* @returns
|
|
1701
|
+
*/
|
|
1702
|
+
export function isTuple(bytes: Bytes.BytesLike): PeekEffect<boolean> {
|
|
1703
|
+
return isList(bytes)
|
|
1704
|
+
}
|