@mxjp/binary 1.0.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 +21 -0
- package/dist/base32.d.ts +10 -0
- package/dist/base32.js +59 -0
- package/dist/base32.js.map +1 -0
- package/dist/base64.d.ts +39 -0
- package/dist/base64.js +143 -0
- package/dist/base64.js.map +1 -0
- package/dist/bytes.d.ts +10 -0
- package/dist/bytes.js +24 -0
- package/dist/bytes.js.map +1 -0
- package/dist/deserializer.d.ts +48 -0
- package/dist/deserializer.js +125 -0
- package/dist/deserializer.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/serializer.d.ts +61 -0
- package/dist/serializer.js +177 -0
- package/dist/serializer.js.map +1 -0
- package/package.json +32 -0
- package/src/base32.ts +57 -0
- package/src/base64.ts +146 -0
- package/src/bytes.ts +26 -0
- package/src/deserializer.ts +138 -0
- package/src/index.ts +5 -0
- package/src/serializer.ts +216 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { asUint8Array } from "./bytes.js";
|
|
2
|
+
export class Serializer {
|
|
3
|
+
#littleEndian;
|
|
4
|
+
#byteLength;
|
|
5
|
+
#parts;
|
|
6
|
+
constructor(littleEndian) {
|
|
7
|
+
this.#littleEndian = littleEndian ?? true;
|
|
8
|
+
if (typeof this.#littleEndian !== "boolean") {
|
|
9
|
+
throw new TypeError();
|
|
10
|
+
}
|
|
11
|
+
this.#byteLength = 0;
|
|
12
|
+
this.#parts = [];
|
|
13
|
+
}
|
|
14
|
+
get byteLength() {
|
|
15
|
+
return this.#byteLength;
|
|
16
|
+
}
|
|
17
|
+
#push(byteLength, serializeFn) {
|
|
18
|
+
this.#parts.push({ l: byteLength, s: serializeFn });
|
|
19
|
+
this.#byteLength += byteLength;
|
|
20
|
+
}
|
|
21
|
+
/** Serialize an unsigned 8 bit int. */
|
|
22
|
+
u8(value) {
|
|
23
|
+
if (typeof value !== "number") {
|
|
24
|
+
throw new TypeError();
|
|
25
|
+
}
|
|
26
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF) {
|
|
27
|
+
throw new RangeError();
|
|
28
|
+
}
|
|
29
|
+
this.#push(1, (ctx, byteOffset) => {
|
|
30
|
+
ctx.array[byteOffset] = value;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/** Serialize an unsigned 16 bit int in the serializer's endianess. */
|
|
34
|
+
u16(value) {
|
|
35
|
+
if (typeof value !== "number") {
|
|
36
|
+
throw new TypeError();
|
|
37
|
+
}
|
|
38
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF_FF) {
|
|
39
|
+
throw new RangeError();
|
|
40
|
+
}
|
|
41
|
+
this.#push(2, (ctx, byteOffset) => {
|
|
42
|
+
ctx.view.setUint16(byteOffset, value, this.#littleEndian);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Serialize an unsigned 32 bit int in the serializer's endianess. */
|
|
46
|
+
u32(value) {
|
|
47
|
+
if (typeof value !== "number") {
|
|
48
|
+
throw new TypeError();
|
|
49
|
+
}
|
|
50
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 0xFF_FF_FF_FF) {
|
|
51
|
+
throw new RangeError();
|
|
52
|
+
}
|
|
53
|
+
this.#push(4, (ctx, byteOffset) => {
|
|
54
|
+
ctx.view.setUint32(byteOffset, value, this.#littleEndian);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Serialize an unsigned 64 bit int in the serializer's endianess. */
|
|
58
|
+
u64(value) {
|
|
59
|
+
if (typeof value !== "number") {
|
|
60
|
+
throw new TypeError();
|
|
61
|
+
}
|
|
62
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
63
|
+
throw new RangeError();
|
|
64
|
+
}
|
|
65
|
+
this.#push(8, (ctx, byteOffset) => {
|
|
66
|
+
ctx.view.setBigUint64(byteOffset, BigInt(value), this.#littleEndian);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Serialize an unsigned 64 bit int in the serializer's endianess. */
|
|
70
|
+
bigU64(value) {
|
|
71
|
+
if (typeof value !== "bigint") {
|
|
72
|
+
throw new TypeError();
|
|
73
|
+
}
|
|
74
|
+
if (value < 0n || value > 0xffffffffffffffffn) {
|
|
75
|
+
throw new RangeError();
|
|
76
|
+
}
|
|
77
|
+
this.#push(8, (ctx, byteOffset) => {
|
|
78
|
+
ctx.view.setBigUint64(byteOffset, value, this.#littleEndian);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** Serialize a 32-bit IEEE 754 floating point number in the serializer's endianess. */
|
|
82
|
+
f32(value) {
|
|
83
|
+
if (typeof value !== "number") {
|
|
84
|
+
throw new TypeError();
|
|
85
|
+
}
|
|
86
|
+
this.#push(4, (ctx, byteOffset) => {
|
|
87
|
+
ctx.view.setFloat32(byteOffset, value, this.#littleEndian);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** Serialize a 64-bit IEEE 754 floating point number in the serializer's endianess. */
|
|
91
|
+
f64(value) {
|
|
92
|
+
if (typeof value !== "number") {
|
|
93
|
+
throw new TypeError();
|
|
94
|
+
}
|
|
95
|
+
this.#push(8, (ctx, byteOffset) => {
|
|
96
|
+
ctx.view.setFloat64(byteOffset, value, this.#littleEndian);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Serialize a boolean.
|
|
101
|
+
*
|
|
102
|
+
* + `false` is represented as `0x00`.
|
|
103
|
+
* + `true` is represented as `0x01`.
|
|
104
|
+
*/
|
|
105
|
+
bool(value) {
|
|
106
|
+
if (typeof value !== "boolean") {
|
|
107
|
+
throw new TypeError();
|
|
108
|
+
}
|
|
109
|
+
this.u8(value ? 0x01 : 0x00);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Serialize an optional value.
|
|
113
|
+
*
|
|
114
|
+
* + `null` and `undefined` are serialized using {@link boolean `boolean(false)`}.
|
|
115
|
+
* + All other values are serialized using {@link boolean `boolean(true)`} followed by immediately calling the specified function.
|
|
116
|
+
*/
|
|
117
|
+
option(value, fn) {
|
|
118
|
+
if (value === null || value === undefined) {
|
|
119
|
+
this.bool(false);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.bool(true);
|
|
123
|
+
fn(value, this);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Serialize bytes by copying them when {@link serialize serializing}.
|
|
128
|
+
*/
|
|
129
|
+
unsafeBytes(value) {
|
|
130
|
+
const bytes = asUint8Array(value);
|
|
131
|
+
this.#push(value.byteLength, (ctx, byteOffset) => {
|
|
132
|
+
ctx.array.set(bytes, byteOffset);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Serialize bytes by copying them when {@link serialize serializing} prefixed by their byte length.
|
|
137
|
+
*
|
|
138
|
+
* See {@link unsafeBytes}.
|
|
139
|
+
*/
|
|
140
|
+
prefixedUnsafeBytes(prefix, value) {
|
|
141
|
+
const bytes = asUint8Array(value);
|
|
142
|
+
prefix.call(this, value.byteLength);
|
|
143
|
+
this.unsafeBytes(bytes);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Serialize a utf-8 encoded string.
|
|
147
|
+
*/
|
|
148
|
+
utf8(value) {
|
|
149
|
+
this.unsafeBytes(new TextEncoder().encode(value));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Serialize a utf-8 encoded string prefixed by it's byte length.
|
|
153
|
+
*/
|
|
154
|
+
prefixedUtf8(prefix, value) {
|
|
155
|
+
this.prefixedUnsafeBytes(prefix, new TextEncoder().encode(value));
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Write all parts in this serializer to an array buffer.
|
|
159
|
+
*/
|
|
160
|
+
serialize() {
|
|
161
|
+
const buffer = new ArrayBuffer(this.#byteLength);
|
|
162
|
+
const context = {
|
|
163
|
+
buffer,
|
|
164
|
+
array: new Uint8Array(buffer),
|
|
165
|
+
view: new DataView(buffer),
|
|
166
|
+
};
|
|
167
|
+
const parts = this.#parts;
|
|
168
|
+
let byteOffset = 0;
|
|
169
|
+
for (let i = 0; i < parts.length; i++) {
|
|
170
|
+
const part = parts[i];
|
|
171
|
+
part.s(context, byteOffset);
|
|
172
|
+
byteOffset += part.l;
|
|
173
|
+
}
|
|
174
|
+
return buffer;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=serializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializer.js","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mxjp/binary",
|
|
3
|
+
"homepage": "https://mxjp.github.io/binary",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/mxjp/binary"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"version": "1.0.0",
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "npm run build:es && npm run build:tests",
|
|
18
|
+
"build:es": "tsc -p tsconfig-es.json",
|
|
19
|
+
"build:tests": "tsc -p tsconfig-tests.json",
|
|
20
|
+
"start": "mx-parallel npm:start:*",
|
|
21
|
+
"start:es": "tsc -p tsconfig-es.json -w --preserveWatchOutput",
|
|
22
|
+
"start:tests": "tsc -p tsconfig-tests.json -w --preserveWatchOutput",
|
|
23
|
+
"test": "cd tests_out && node --enable-source-maps --test \"**/*.test.js\""
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@mxjp/binary": "file:.",
|
|
27
|
+
"@mxjp/parallel": "^1.0.2",
|
|
28
|
+
"@types/node": "^24.6.2",
|
|
29
|
+
"tinybench": "^5.0.1",
|
|
30
|
+
"typescript": "^5.9.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/base32.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { asUint8Array, Bytes } from "./bytes.js";
|
|
2
|
+
|
|
3
|
+
const BASE32_TO_ASCII = new TextEncoder().encode("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567");
|
|
4
|
+
const MASK = 0b11111;
|
|
5
|
+
const PAD_ASCII = "=".charCodeAt(0);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base32 encode bytes without padding.
|
|
9
|
+
*
|
|
10
|
+
* See RFC 3548 for more info.
|
|
11
|
+
*
|
|
12
|
+
* @param bytes The bytes to encode.
|
|
13
|
+
* @param pad True, to include padding. Default is false.
|
|
14
|
+
*/
|
|
15
|
+
export function base32Encode(bytes: Bytes, pad = false): string {
|
|
16
|
+
const array = asUint8Array(bytes);
|
|
17
|
+
const rest = array.byteLength % 5;
|
|
18
|
+
const comp = array.byteLength - rest;
|
|
19
|
+
const base32Length = pad ? Math.ceil(array.byteLength / 5) * 8 : Math.ceil(array.byteLength / 5 * 8);
|
|
20
|
+
const base32 = new Uint8Array(base32Length);
|
|
21
|
+
let b = 0;
|
|
22
|
+
for (let i = 0; i < comp; i += 5) {
|
|
23
|
+
base32[b++] = BASE32_TO_ASCII[array[i] >>> 3];
|
|
24
|
+
base32[b++] = BASE32_TO_ASCII[((array[i] << 2) & MASK) | (array[i + 1] >>> 6)];
|
|
25
|
+
base32[b++] = BASE32_TO_ASCII[((array[i + 1] >>> 1) & MASK)];
|
|
26
|
+
base32[b++] = BASE32_TO_ASCII[((array[i + 1] << 4) & MASK) | (array[i + 2] >>> 4)];
|
|
27
|
+
base32[b++] = BASE32_TO_ASCII[((array[i + 2] << 1) & MASK) | (array[i + 3] >>> 7)];
|
|
28
|
+
base32[b++] = BASE32_TO_ASCII[((array[i + 3] >>> 2) & MASK)];
|
|
29
|
+
base32[b++] = BASE32_TO_ASCII[((array[i + 3] << 3) & MASK) | (array[i + 4] >>> 5)];
|
|
30
|
+
base32[b++] = BASE32_TO_ASCII[array[i + 4] & MASK];
|
|
31
|
+
}
|
|
32
|
+
if (rest > 0) {
|
|
33
|
+
base32[b++] = BASE32_TO_ASCII[array[comp] >>> 3];
|
|
34
|
+
if (rest > 1) {
|
|
35
|
+
base32[b++] = BASE32_TO_ASCII[((array[comp] << 2) & MASK) | (array[comp + 1] >>> 6)];
|
|
36
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp + 1] >>> 1) & MASK];
|
|
37
|
+
if (rest > 2) {
|
|
38
|
+
base32[b++] = BASE32_TO_ASCII[((array[comp + 1] << 4) & MASK) | (array[comp + 2] >>> 4)];
|
|
39
|
+
if (rest > 3) {
|
|
40
|
+
base32[b++] = BASE32_TO_ASCII[((array[comp + 2] << 1) & MASK) | (array[comp + 3] >>> 7)];
|
|
41
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp + 3] >>> 2) & MASK];
|
|
42
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp + 3] << 3) & MASK];
|
|
43
|
+
} else {
|
|
44
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp + 2] << 1) & MASK];
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp + 1] << 4) & MASK];
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
base32[b++] = BASE32_TO_ASCII[(array[comp] << 2) & MASK];
|
|
51
|
+
}
|
|
52
|
+
base32.fill(PAD_ASCII, b);
|
|
53
|
+
}
|
|
54
|
+
const value = new TextDecoder().decode(base32);
|
|
55
|
+
base32.fill(0);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
package/src/base64.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { asUint8Array, Bytes } from "./bytes.js";
|
|
2
|
+
|
|
3
|
+
const BASE64_TO_ASCII = new TextEncoder().encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
|
|
4
|
+
const BASE64URL_TO_ASCII = new TextEncoder().encode("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_");
|
|
5
|
+
|
|
6
|
+
const ASCII_TO_BASE64 = new Uint16Array(123);
|
|
7
|
+
const ASCII_TO_BASE64URL = new Uint16Array(123);
|
|
8
|
+
for (let i = 0; i < 64; i++) {
|
|
9
|
+
ASCII_TO_BASE64[BASE64_TO_ASCII[i]] = i | 0x8000;
|
|
10
|
+
ASCII_TO_BASE64URL[BASE64URL_TO_ASCII[i]] = i | 0x8000;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PAD_ASCII = "=".charCodeAt(0);
|
|
14
|
+
|
|
15
|
+
function encode(bytes: Uint8Array<ArrayBuffer>, map: Uint8Array<ArrayBuffer>, padding: boolean): string {
|
|
16
|
+
const base64Length = padding ? Math.ceil(bytes.byteLength / 3) * 4 : Math.ceil(bytes.byteLength * 4 / 3);
|
|
17
|
+
const base64 = new Uint8Array(base64Length);
|
|
18
|
+
for (let i = 0, x = 0; i < bytes.byteLength;) {
|
|
19
|
+
const a = bytes[i];
|
|
20
|
+
base64[x++] = map[a >>> 2];
|
|
21
|
+
if (++i < bytes.byteLength) {
|
|
22
|
+
const b = bytes[i];
|
|
23
|
+
base64[x++] = map[((a & 3) << 4) | (b >>> 4)];
|
|
24
|
+
if (++i < bytes.byteLength) {
|
|
25
|
+
const c = bytes[i];
|
|
26
|
+
base64[x++] = map[((b & 0xf) << 2) | (c >>> 6)];
|
|
27
|
+
base64[x++] = map[c & 0x3f];
|
|
28
|
+
i++;
|
|
29
|
+
} else {
|
|
30
|
+
base64[x++] = map[(b & 0xf) << 2];
|
|
31
|
+
if (padding) {
|
|
32
|
+
base64[x++] = PAD_ASCII;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
base64[x++] = map[(a & 3) << 4];
|
|
37
|
+
if (padding) {
|
|
38
|
+
base64[x++] = PAD_ASCII;
|
|
39
|
+
base64[x++] = PAD_ASCII;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const value = new TextDecoder().decode(base64);
|
|
44
|
+
base64.fill(0);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function decode(value: string, map: Uint16Array): Uint8Array {
|
|
49
|
+
if (typeof value !== "string") {
|
|
50
|
+
throw new TypeError();
|
|
51
|
+
}
|
|
52
|
+
const base64 = new TextEncoder().encode(value);
|
|
53
|
+
let padding = 0;
|
|
54
|
+
for (let i = value.length - 1; i >= 0; i--) {
|
|
55
|
+
if (base64[i] === PAD_ASCII) {
|
|
56
|
+
padding++;
|
|
57
|
+
} else {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const base64Length = value.length - padding;
|
|
62
|
+
if (padding > 0 && ((value.length & 3) !== 0 || padding > 2)) {
|
|
63
|
+
throw new DecodeBase64Error();
|
|
64
|
+
}
|
|
65
|
+
const partial = base64Length & 3;
|
|
66
|
+
const byteLength = ((base64Length >>> 2) * 3) + (partial >>> 1) + (partial & 1);
|
|
67
|
+
const bytes = new Uint8Array(byteLength);
|
|
68
|
+
for (let i = 0, x = 0; i < byteLength;) {
|
|
69
|
+
const a = map[base64[x++]];
|
|
70
|
+
const b = map[base64[x++]];
|
|
71
|
+
if (a === 0 || a === undefined || b === 0 || b === undefined) {
|
|
72
|
+
throw new DecodeBase64Error();
|
|
73
|
+
}
|
|
74
|
+
bytes[i++] = (a << 2) | (b >>> 4);
|
|
75
|
+
if (i < byteLength) {
|
|
76
|
+
const c = map[base64[x++]];
|
|
77
|
+
if (c === 0 || c === undefined) {
|
|
78
|
+
throw new DecodeBase64Error();
|
|
79
|
+
}
|
|
80
|
+
bytes[i++] = (b << 4) | (c >>> 2);
|
|
81
|
+
if (i < byteLength) {
|
|
82
|
+
const d = map[base64[x++]];
|
|
83
|
+
if (d === 0 || d === undefined) {
|
|
84
|
+
throw new DecodeBase64Error();
|
|
85
|
+
}
|
|
86
|
+
bytes[i++] = (c << 6) | d;
|
|
87
|
+
} else if ((c & 3) > 0) {
|
|
88
|
+
throw new DecodeBase64Error();
|
|
89
|
+
}
|
|
90
|
+
} else if ((b & 7) > 0) {
|
|
91
|
+
throw new DecodeBase64Error();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
base64.fill(0);
|
|
95
|
+
return bytes;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class DecodeBase64Error extends SyntaxError {}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Base64 encode bytes.
|
|
102
|
+
*
|
|
103
|
+
* See RFC 3548 for more info.
|
|
104
|
+
*
|
|
105
|
+
* @param bytes The bytes to encode.
|
|
106
|
+
* @param padding True, to include padding. Default is true.
|
|
107
|
+
*/
|
|
108
|
+
export function base64Encode(bytes: Bytes, padding = true): string {
|
|
109
|
+
return encode(asUint8Array(bytes), BASE64_TO_ASCII, padding);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Base64url encode bytes.
|
|
114
|
+
*
|
|
115
|
+
* See RFC 3548 for more info.
|
|
116
|
+
*
|
|
117
|
+
* @param bytes The bytes to encode.
|
|
118
|
+
* @param padding True, to include padding. Default is false.
|
|
119
|
+
*/
|
|
120
|
+
export function base64UrlEncode(bytes: Bytes, padding = false): string {
|
|
121
|
+
return encode(asUint8Array(bytes), BASE64URL_TO_ASCII, padding);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Decode a base64 encoded text with optional trailing padding.
|
|
126
|
+
*
|
|
127
|
+
* See RFC 3548 for more info.
|
|
128
|
+
*
|
|
129
|
+
* @param value The text to decode.
|
|
130
|
+
*/
|
|
131
|
+
export function base64Decode(value: string): Uint8Array {
|
|
132
|
+
return decode(value, ASCII_TO_BASE64);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Decode a base64 url encoded text with optional trailing padding.
|
|
137
|
+
*
|
|
138
|
+
* This does ignore padding characters, but not concatenated base64url strings.
|
|
139
|
+
*
|
|
140
|
+
* See RFC 3548 for more info.
|
|
141
|
+
*
|
|
142
|
+
* @param value The text to decode.
|
|
143
|
+
*/
|
|
144
|
+
export function base64UrlDecode(value: string): Uint8Array {
|
|
145
|
+
return decode(value, ASCII_TO_BASE64URL);
|
|
146
|
+
}
|
package/src/bytes.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
export type Bytes = ArrayBuffer | Uint8Array<ArrayBuffer>;
|
|
3
|
+
|
|
4
|
+
export function asUint8Array(value: Bytes): Uint8Array<ArrayBuffer> {
|
|
5
|
+
if (isArrayBuffer(value)) {
|
|
6
|
+
return new Uint8Array(value);
|
|
7
|
+
} else if (isUint8Array(value)) {
|
|
8
|
+
return value;
|
|
9
|
+
} else {
|
|
10
|
+
throw new TypeError();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if the specified value is exactly an {@link ArrayBuffer `ArrayBuffer`}.
|
|
16
|
+
*/
|
|
17
|
+
export function isArrayBuffer(value: unknown): value is ArrayBuffer {
|
|
18
|
+
return value instanceof ArrayBuffer && value.constructor === ArrayBuffer;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check if the specified value is exactly a {@link Uint8Array `Uint8Array<ArrayBuffer>`}.
|
|
23
|
+
*/
|
|
24
|
+
export function isUint8Array(value: unknown): value is Uint8Array<ArrayBuffer> {
|
|
25
|
+
return value instanceof Uint8Array && value.constructor === Uint8Array && isArrayBuffer(value.buffer);
|
|
26
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { isArrayBuffer, isUint8Array } from "./bytes.js";
|
|
2
|
+
|
|
3
|
+
export class Deserializer {
|
|
4
|
+
#littleEndian: boolean;
|
|
5
|
+
#buffer: ArrayBuffer;
|
|
6
|
+
#byteOffset: number;
|
|
7
|
+
#byteEnd: number;
|
|
8
|
+
#view: DataView;
|
|
9
|
+
|
|
10
|
+
constructor(buffer: ArrayBuffer, byteOffset: number, byteLength: number, littleEndian?: boolean) {
|
|
11
|
+
this.#littleEndian = littleEndian ?? true;
|
|
12
|
+
if (typeof this.#littleEndian !== "boolean" || !isArrayBuffer(buffer) || !Number.isSafeInteger(byteOffset) || !Number.isSafeInteger(byteLength)) {
|
|
13
|
+
throw new TypeError();
|
|
14
|
+
}
|
|
15
|
+
if (byteOffset < 0 || byteLength < 0 || (byteOffset + byteLength) > buffer.byteLength) {
|
|
16
|
+
throw new RangeError();
|
|
17
|
+
}
|
|
18
|
+
this.#buffer = buffer;
|
|
19
|
+
this.#byteOffset = byteOffset;
|
|
20
|
+
this.#byteEnd = byteOffset + byteLength;
|
|
21
|
+
this.#view = new DataView(buffer);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static from(source: ArrayBuffer | Uint8Array<ArrayBuffer>, littleEndian?: boolean) {
|
|
25
|
+
if (isArrayBuffer(source)) {
|
|
26
|
+
return new Deserializer(source, 0, source.byteLength, littleEndian);
|
|
27
|
+
} else if (isUint8Array(source)) {
|
|
28
|
+
return new Deserializer(source.buffer, source.byteOffset, source.byteLength, littleEndian);
|
|
29
|
+
} else {
|
|
30
|
+
throw new TypeError();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get bytesAvailable(): number {
|
|
35
|
+
return this.#byteEnd - this.#byteOffset;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#advanceBy(byteLength: number): number {
|
|
39
|
+
const byteOffset = this.#byteOffset;
|
|
40
|
+
const next = byteOffset + byteLength;
|
|
41
|
+
if (next > this.#byteEnd) {
|
|
42
|
+
throw new DeserializerEndError();
|
|
43
|
+
}
|
|
44
|
+
this.#byteOffset = next;
|
|
45
|
+
return byteOffset;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a copy of this deserializer in it's current state that views the same underlying data.
|
|
50
|
+
*
|
|
51
|
+
* @param littleEndian True to use little endian byte order. Default is this deserializer's byte order.
|
|
52
|
+
*/
|
|
53
|
+
fork(littleEndian?: boolean): Deserializer {
|
|
54
|
+
return new Deserializer(this.#buffer, this.#byteOffset, this.bytesAvailable, littleEndian ?? this.#littleEndian);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Deserialize an unsigned 8 bit int. */
|
|
58
|
+
u8(): number {
|
|
59
|
+
return this.#view.getUint8(this.#advanceBy(1));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Deserialize an unsigned 16 bit int in the deserializer's endianess. */
|
|
63
|
+
u16(): number {
|
|
64
|
+
return this.#view.getUint16(this.#advanceBy(2), this.#littleEndian);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Deserialize an unsigned 32 bit int in the deserializer's endianess. */
|
|
68
|
+
u32(): number {
|
|
69
|
+
return this.#view.getUint32(this.#advanceBy(4), this.#littleEndian);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Deserialize an unsigned 64 bit int in the deserializer's endianess. */
|
|
73
|
+
u64(): number {
|
|
74
|
+
const value = this.bigU64();
|
|
75
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
76
|
+
throw new RangeError();
|
|
77
|
+
}
|
|
78
|
+
return Number(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Deserialize an unsigned 64 bit int in the deserializer's endianess. */
|
|
82
|
+
bigU64(): bigint {
|
|
83
|
+
return this.#view.getBigUint64(this.#advanceBy(8), this.#littleEndian);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Deserialize a 32-bit IEEE 754 floating point number in the deserializer's endianess. */
|
|
87
|
+
f32(): number {
|
|
88
|
+
return this.#view.getFloat32(this.#advanceBy(4), this.#littleEndian);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Deserialize a 64-bit IEEE 754 floating point number in the deserializer's endianess. */
|
|
92
|
+
f64(): number {
|
|
93
|
+
return this.#view.getFloat64(this.#advanceBy(8), this.#littleEndian);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Deserialize a boolean.
|
|
98
|
+
*
|
|
99
|
+
* + `false` is represented as `0x00`.
|
|
100
|
+
* + `true` is represented as `0x01`.
|
|
101
|
+
*/
|
|
102
|
+
bool(): boolean {
|
|
103
|
+
switch (this.u8()) {
|
|
104
|
+
case 0x00: return false;
|
|
105
|
+
case 0x01: return true;
|
|
106
|
+
default: throw new RangeError();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Deserialize bytes by creating a view into the underlying data.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```js
|
|
115
|
+
* let array = d.unsafeViewBytes(7, Uint8Array);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
unsafeViewBytes<T extends typeof Uint8Array<ArrayBuffer> | typeof DataView<ArrayBuffer>>(byteLength: number, ctor: T): InstanceType<T> {
|
|
119
|
+
if (!Number.isSafeInteger(byteLength) || ctor as unknown !== Uint8Array && ctor !== DataView) {
|
|
120
|
+
throw new TypeError();
|
|
121
|
+
}
|
|
122
|
+
const byteOffset = this.#advanceBy(byteLength);
|
|
123
|
+
return new (ctor as typeof Uint8Array)(this.#buffer, byteOffset, byteLength) as InstanceType<T>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Deserialize bytes by copying.
|
|
128
|
+
*/
|
|
129
|
+
copyBytes(byteLength: number): ArrayBuffer {
|
|
130
|
+
if (!Number.isSafeInteger(byteLength)) {
|
|
131
|
+
throw new TypeError();
|
|
132
|
+
}
|
|
133
|
+
const byteOffset = this.#advanceBy(byteLength);
|
|
134
|
+
return this.#buffer.slice(byteOffset, byteOffset + byteLength);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export class DeserializerEndError extends Error {}
|