-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprimitive.ts
More file actions
213 lines (176 loc) · 8.39 KB
/
primitive.ts
File metadata and controls
213 lines (176 loc) · 8.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/**
* This module contains all the primitive types, ready to be composed into more complex codecs.
*
* @module
*/
import type { Codec } from "./codec.ts";
type DataViewFromFunc<T> = (byteOffset: number, littleEndian?: boolean) => T;
type DataViewFromFn<T> = {
[K in keyof DataView]: DataView[K] extends DataViewFromFunc<T> ? K : never;
}[keyof DataView];
type DataViewToFunc<T> = (byteOffset: number, value: T, littleEndian?: boolean) => void;
type DataViewToFn<T> = {
[K in keyof DataView]: DataView[K] extends DataViewToFunc<T> ? K : never;
}[keyof DataView];
/**
* Create a new {@link Codec} that uses {@link DataView} for encoding and decoding data.
* @param bytes The number of bytes to read.
* @param fromFn The function in `DataView` to be used for reading value.
* @param toFn The function in `DataView` to be used for writing value.
* @param littleEndian Whether the value should be in little endian encoding.
* @returns A new codec, using underlying `DataView` for encoding and decoding data.
*/
export function dataView<T>(
bytes: number,
fromFn: DataViewFromFn<T>,
toFn: DataViewToFn<T>,
littleEndian: boolean,
): Codec<T> {
return {
encode(v, output) {
const buf = new ArrayBuffer(bytes);
(new DataView(buf)[toFn] as DataViewToFunc<T>)(0, v, littleEndian);
output.write(new Uint8Array(buf));
},
decode(input) {
const raw = input.read(bytes);
return (new DataView(raw.buffer)[fromFn] as DataViewFromFunc<T>)(0, littleEndian);
},
};
}
/**
* A {@link Codec} for `number` values that are 8-bit unsigned integers.
*/
export const u8: Codec<number> = {
encode: (v, output) => output.write([v]),
decode: (input) => input.read(1)[0],
};
/**
* A {@link Codec} for `number` values that are 8-bit signed integers.
*/
export const i8: Codec<number> = {
encode: (v, output) => output.write([v]),
decode: (input) => input.read(1, Int8Array)[0],
};
/** A {@link Codec} for `number` values that are 16-bit unsigned integers in big endian format. */
export const u16BE: Codec<number> = dataView(2, "getUint16", "setUint16", false);
/**A {@link Codec} for `number` values that are 32-bit unsigned integers in big endian format. */
export const u32BE: Codec<number> = dataView(4, "getUint32", "setUint32", false);
/**A {@link Codec} for `bigint` values that are 64-bit unsigned integers in big endian format. */
export const u64BE: Codec<bigint> = dataView(8, "getBigUint64", "setBigUint64", false);
/**A {@link Codec} for `number` values that are 16-bit signed integers in big endian format. */
export const i16BE: Codec<number> = dataView(2, "getInt16", "setInt16", false);
/**A {@link Codec} for `number` values that are 32-bit signed integers in big endian format. */
export const i32BE: Codec<number> = dataView(4, "getInt32", "setInt32", false);
/**A {@link Codec} for `bigint` values that are 64-bit signed integers in big endian format. */
export const i64BE: Codec<bigint> = dataView(8, "getBigInt64", "setBigInt64", false);
/**A {@link Codec} for `number` values that are 16-bit floating-point numbers in big endian format. */
export const f16BE: Codec<number> = dataView(2, "getFloat16", "setFloat16", false);
/**A {@link Codec} for `number` values that are 32-bit floating-point numbers in big endian format. */
export const f32BE: Codec<number> = dataView(4, "getFloat32", "setFloat32", false);
/** A {@link Codec} for `number` values that are 64-bit floating-point numbers in big endian format. */
export const f64BE: Codec<number> = dataView(8, "getFloat64", "setFloat64", false);
/** A {@link Codec} for `number` values that are 16-bit unsigned integers in little endian format. */
export const u16LE: Codec<number> = dataView(2, "getUint16", "setUint16", true);
/** A {@link Codec} for `number` values that are 32-bit unsigned integers in little endian format. */
export const u32LE: Codec<number> = dataView(4, "getUint32", "setUint32", true);
/** A {@link Codec} for `bigint` values that are 64-bit unsigned integers in little endian format. */
export const u64LE: Codec<bigint> = dataView(8, "getBigUint64", "setBigUint64", true);
/** A {@link Codec} for `number` values that are 16-bit signed integers in little endian format. */
export const i16LE: Codec<number> = dataView(2, "getInt16", "setInt16", true);
/** A {@link Codec} for `number` values that are 32-bit signed integers in little endian format. */
export const i32LE: Codec<number> = dataView(4, "getInt32", "setInt32", true);
/** A {@link Codec} for `bigint` values that are 64-bit signed integers in little endian format. */
export const i64LE: Codec<bigint> = dataView(8, "getBigInt64", "setBigInt64", true);
/** A {@link Codec} for `number` values that are 16-bit floating-point numbers in little endian format. */
export const f16LE: Codec<number> = dataView(2, "getFloat16", "setFloat16", true);
/** A {@link Codec} for `number` values that are 32-bit floating-point numbers in little endian format. */
export const f32LE: Codec<number> = dataView(4, "getFloat32", "setFloat32", true);
/** A {@link Codec} for `number` values that are 64-bit floating-point numbers in little endian format. */
export const f64LE: Codec<number> = dataView(8, "getFloat64", "setFloat64", true);
/**
* A {@link Codec} that encodes `0x01` for `true` and `0x00` for `false`.
*/
export const bool: Codec<boolean> = {
encode: (obj, output) => output.write([obj ? 1 : 0]),
decode: (input) => !!input.read(1)[0],
};
/**
* A {@link Codec} that encodes variable-length integer in little endian format. Since the binary operation on JS number
* are always limited to 32-bit, this codec is not suitable for any value outside `2**31 - 1` limit.
*/
export const uvarLE: Codec<number> = {
encode(v, output) {
if (v < 0) throw new Error(`Must be a non-negative integer, found ${v}`);
if (v >= 0x80000000) throw new Error(
`Value ${v} is greater than 0x80000000, which when applied binary op, will become negative value`
);
const out = new Uint8Array(5); // At most 5 bytes, since binary op on JS is limited to 32-bit
let i = 0;
do {
const b = v & 0x7F;
v >>= 7;
out[i++] = b | (v != 0 ? 0x80 : 0);
} while (v != 0);
output.write(out.subarray(0, i));
},
decode(input) {
let v = 0;
let i = 0;
let b: number;
do {
if (i >= 5) throw new Error(`Varint overflow (expecting at most 4 consecutive bytes with next marker)`);
[b] = input.read(1);
v |= (b & 0x7F) << (7 * i);
i++;
} while (b & 0x80);
return v;
},
};
/**
* Create a new {@link Codec} for encoding and decoding between `string` and UTF-8 with fixed number of bytes. If the
* length of encoded UTF-8 is less than maximum bytes, the data will suffixed with zeros.
* @param bytes The maximum number of bytes.
* @returns A new codec that can encode and decode UTF-8 strings.
*/
export function fixedUtf8(bytes: number): Codec<string> {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
return {
encode(obj, output) {
const str = encoder.encode(obj);
if (str.length > bytes) {
throw new Error(
`Exceeding bytes limit (encoded UTF-8 length must <= ${bytes} bytes, found ${str.length})`,
);
}
output.write(str);
output.write(new Uint8Array(bytes - str.length));
},
decode(input) {
const buf = input.read(bytes);
const idx = buf.indexOf(0);
return decoder.decode(idx != -1 ? buf.subarray(0, idx) : buf);
},
};
}
/**
* Create a new {@link Codec} for encoding and decoding between `string` and length-prefixed UTF-8.
* @param lengthCodec The codec for encoding and decoding length of UTF-8 data.
* @returns A new codec that can encode and decode UTF-8 strings.
*/
export function dynamicUtf8(lengthCodec: Codec<number>): Codec<string> {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
return {
encode(obj, output) {
const str = encoder.encode(obj);
lengthCodec.encode(str.length, output);
output.write(str);
},
decode(input) {
const bytes = lengthCodec.decode(input);
return decoder.decode(input.read(bytes));
},
};
}