@litert/base32 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.
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Copyright 2025 Angus.Fenying <fenying@litert.org>
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ const BASE32_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
18
+
19
+ const BITS_TO_CHARS = Buffer.from(BASE32_CHARS);
20
+
21
+ const CHARS_TO_BITS: Record<string, number> = {};
22
+
23
+ for (let i = 0; i < BASE32_CHARS.length; i++) {
24
+
25
+ CHARS_TO_BITS[BASE32_CHARS[i]] = i;
26
+ }
27
+
28
+ export const PADDING = '='.charCodeAt(0);
29
+
30
+ /**
31
+ * Encode a `Buffer` to a BASE32-encoded string.
32
+ *
33
+ * @param data The `Buffer` to be encoded.
34
+ *
35
+ * @returns The BASE32 encoded string.
36
+ *
37
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
38
+ */
39
+ export function bufferToBase32(data: Buffer): string {
40
+
41
+ const ret: Buffer = Buffer.alloc(Math.ceil(data.length / 5) * 8);
42
+
43
+ for (let i = 0, j = 0; i < data.length; i += 5) {
44
+
45
+ switch (data.length - i) {
46
+ case 1:
47
+
48
+ /**
49
+ * 11111 111|00 00000 0|0000 0000|0 00000 00|000 00000
50
+ */
51
+ ret[j++] = BITS_TO_CHARS[data[i] >> 3];
52
+ ret[j++] = BITS_TO_CHARS[(data[i] & 0x07) << 2];
53
+ ret[j++] = PADDING;
54
+ ret[j++] = PADDING;
55
+ ret[j++] = PADDING;
56
+ ret[j++] = PADDING;
57
+ ret[j++] = PADDING;
58
+ ret[j++] = PADDING;
59
+ break;
60
+ case 2:
61
+
62
+ /**
63
+ * 11111 111|11 11111 1|0000 0000|0 00000 00|000 00000
64
+ */
65
+ ret[j++] = BITS_TO_CHARS[data[i] >> 3];
66
+ ret[j++] = BITS_TO_CHARS[((data[i] & 0x07) << 2) | (data[i + 1] >> 6)];
67
+ ret[j++] = BITS_TO_CHARS[(data[i + 1] >> 1) & 0x1F];
68
+ ret[j++] = BITS_TO_CHARS[(data[i + 1] & 0x01) << 4];
69
+ ret[j++] = PADDING;
70
+ ret[j++] = PADDING;
71
+ ret[j++] = PADDING;
72
+ ret[j++] = PADDING;
73
+ break;
74
+ case 3:
75
+
76
+ /**
77
+ * 11111 111|11 11111 1|1111 1111|0 00000 00|000 00000
78
+ */
79
+ ret[j++] = BITS_TO_CHARS[data[i] >> 3];
80
+ ret[j++] = BITS_TO_CHARS[((data[i] & 0x07) << 2) | (data[i + 1] >> 6)];
81
+ ret[j++] = BITS_TO_CHARS[(data[i + 1] >> 1) & 0x1F];
82
+ ret[j++] = BITS_TO_CHARS[((data[i + 1] & 0x01) << 4) | (data[i + 2] >> 4)];
83
+ ret[j++] = BITS_TO_CHARS[(data[i + 2] & 0x0F) << 1];
84
+ ret[j++] = PADDING;
85
+ ret[j++] = PADDING;
86
+ ret[j++] = PADDING;
87
+ break;
88
+ case 4:
89
+
90
+ /**
91
+ * 11111 111|11 11111 1|1111 1111|1 11111 11|000 00000
92
+ */
93
+ ret[j++] = BITS_TO_CHARS[data[i] >> 3];
94
+ ret[j++] = BITS_TO_CHARS[((data[i] & 0x07) << 2) | (data[i + 1] >> 6)];
95
+ ret[j++] = BITS_TO_CHARS[(data[i + 1] >> 1) & 0x1F];
96
+ ret[j++] = BITS_TO_CHARS[((data[i + 1] & 0x01) << 4) | (data[i + 2] >> 4)];
97
+ ret[j++] = BITS_TO_CHARS[((data[i + 2] & 0x0F) << 1) | (data[i + 3] >> 7)];
98
+ ret[j++] = BITS_TO_CHARS[(data[i + 3] >> 2) & 0x1F];
99
+ ret[j++] = BITS_TO_CHARS[(data[i + 3] & 0x03) << 3];
100
+ ret[j++] = PADDING;
101
+ break;
102
+ default: // >= 5
103
+ /**
104
+ * 11111 111|11 11111 1|1111 1111|1 11111 11|111 11111
105
+ */
106
+ ret[j++] = BITS_TO_CHARS[data[i] >> 3];
107
+ ret[j++] = BITS_TO_CHARS[((data[i] & 0x07) << 2) | (data[i + 1] >> 6)];
108
+ ret[j++] = BITS_TO_CHARS[(data[i + 1] >> 1) & 0x1F];
109
+ ret[j++] = BITS_TO_CHARS[((data[i + 1] & 0x01) << 4) | (data[i + 2] >> 4)];
110
+ ret[j++] = BITS_TO_CHARS[((data[i + 2] & 0x0F) << 1) | (data[i + 3] >> 7)];
111
+ ret[j++] = BITS_TO_CHARS[(data[i + 3] >> 2) & 0x1F];
112
+ ret[j++] = BITS_TO_CHARS[((data[i + 3] & 0x03) << 3) | (data[i + 4] >> 5)];
113
+ ret[j++] = BITS_TO_CHARS[data[i + 4] & 0x1F];
114
+ break;
115
+ }
116
+ }
117
+
118
+ return ret.toString();
119
+ }
120
+
121
+ /**
122
+ * Decode a BASE32-encoded string into a `Buffer`.
123
+ *
124
+ * @param input The BASE32-encoded string to be decoded.
125
+ *
126
+ * @returns The decoded `Buffer`.
127
+ *
128
+ * @throws `RangeError` if the input is not a valid BASE32-encoded string.
129
+ *
130
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
131
+ */
132
+ export function bufferFromBase32(input: string): Buffer {
133
+
134
+ if (input.length & 0x7) {
135
+
136
+ throw new RangeError('Unrecognizable base32 input.');
137
+ }
138
+
139
+ const ret: Buffer = Buffer.allocUnsafe(input.length / 8 * 5);
140
+
141
+ for (let i = 0, j = 0; i < input.length; i += 8) {
142
+
143
+ let padFrom = 0;
144
+
145
+ if (input[i + 7] === '=') {
146
+
147
+ if (input[i + 8] !== undefined) {
148
+
149
+ throw new RangeError('Unrecognizable base32 input.');
150
+ }
151
+
152
+ padFrom = input.indexOf('=', i) - i;
153
+ }
154
+
155
+ switch (padFrom) {
156
+ case 0: // no padding
157
+
158
+ /**
159
+ * 11111 111|11 11111 1|1111 1111|1 11111 11|111 11111
160
+ */
161
+ ret[j++] = (CHARS_TO_BITS[input[i]] << 3) | (CHARS_TO_BITS[input[i + 1]] >> 2);
162
+ ret[j++] = ((CHARS_TO_BITS[input[i + 1]] & 0x03) << 6)
163
+ | (CHARS_TO_BITS[input[i + 2]] << 1)
164
+ | (CHARS_TO_BITS[input[i + 3]] >> 4);
165
+ ret[j++] = ((CHARS_TO_BITS[input[i + 3]] & 0x0F) << 4)
166
+ | (CHARS_TO_BITS[input[i + 4]] >> 1);
167
+ ret[j++] = ((CHARS_TO_BITS[input[i + 4]] & 0x01) << 7)
168
+ | (CHARS_TO_BITS[input[i + 5]] << 2)
169
+ | (CHARS_TO_BITS[input[i + 6]] >> 3);
170
+ ret[j++] = ((CHARS_TO_BITS[input[i + 6]] & 0x07) << 5)
171
+ | CHARS_TO_BITS[input[i + 7]];
172
+ break;
173
+
174
+ case 7: // 1 padding
175
+
176
+ /**
177
+ * 11111 111|11 11111 1|1111 1111|1 11111 11|000 00000
178
+ */
179
+ ret[j++] = (CHARS_TO_BITS[input[i]] << 3) | (CHARS_TO_BITS[input[i + 1]] >> 2);
180
+ ret[j++] = ((CHARS_TO_BITS[input[i + 1]] & 0x03) << 6)
181
+ | (CHARS_TO_BITS[input[i + 2]] << 1)
182
+ | (CHARS_TO_BITS[input[i + 3]] >> 4);
183
+ ret[j++] = ((CHARS_TO_BITS[input[i + 3]] & 0x0F) << 4)
184
+ | (CHARS_TO_BITS[input[i + 4]] >> 1);
185
+ ret[j++] = ((CHARS_TO_BITS[input[i + 4]] & 0x01) << 7)
186
+ | (CHARS_TO_BITS[input[i + 5]] << 2)
187
+ | (CHARS_TO_BITS[input[i + 6]] >> 3);
188
+
189
+ return ret.subarray(0, -1);
190
+
191
+ case 5: // 3 padding
192
+
193
+ /**
194
+ * 11111 111|11 11111 1|1111 1111|0 00000 00|000 00000
195
+ */
196
+ ret[j++] = (CHARS_TO_BITS[input[i]] << 3) | (CHARS_TO_BITS[input[i + 1]] >> 2);
197
+ ret[j++] = ((CHARS_TO_BITS[input[i + 1]] & 0x03) << 6)
198
+ | (CHARS_TO_BITS[input[i + 2]] << 1)
199
+ | (CHARS_TO_BITS[input[i + 3]] >> 4);
200
+ ret[j++] = ((CHARS_TO_BITS[input[i + 3]] & 0x0F) << 4)
201
+ | (CHARS_TO_BITS[input[i + 4]] >> 1);
202
+
203
+ return ret.subarray(0, -2);
204
+
205
+ case 4: // 4 padding
206
+
207
+ /**
208
+ * 11111 111|11 11111 1|0000 0000|0 00000 00|000 00000
209
+ */
210
+ ret[j++] = (CHARS_TO_BITS[input[i]] << 3) | (CHARS_TO_BITS[input[i + 1]] >> 2);
211
+ ret[j++] = ((CHARS_TO_BITS[input[i + 1]] & 0x03) << 6)
212
+ | (CHARS_TO_BITS[input[i + 2]] << 1)
213
+ | (CHARS_TO_BITS[input[i + 3]] >> 4);
214
+
215
+ return ret.subarray(0, -3);
216
+
217
+ case 2: // 6 padding
218
+
219
+ /**
220
+ * 11111 111|00 00000 0|0000 0000|0 00000 00|000 00000
221
+ */
222
+ ret[j++] = (CHARS_TO_BITS[input[i]] << 3) | (CHARS_TO_BITS[input[i + 1]] >> 2);
223
+
224
+ return ret.subarray(0, -4);
225
+
226
+ default:
227
+
228
+ throw new RangeError('Unrecognizable base32 input.');
229
+ }
230
+ }
231
+
232
+ return ret;
233
+ }
234
+
235
+ /**
236
+ * Encode a UTF-8 string into a BASE32-encoded string.
237
+ *
238
+ * > This method transform the input string into a `Buffer`, and then calls `bufferToBase32`.
239
+ *
240
+ * @param data The string to be encoded.
241
+ *
242
+ * @returns The BASE32-encoded string.
243
+ *
244
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
245
+ */
246
+ export function stringToBase32(data: string): string {
247
+
248
+ return bufferToBase32(Buffer.from(data));
249
+ }
250
+
251
+ /**
252
+ * Decode a BASE32-encoded string into a UTF-8 string.
253
+ *
254
+ * > This method calls `bufferFromBase32` to decode the input string into a `Buffer`, and then
255
+ * > converts it to a UTF-8 string.
256
+ *
257
+ * @param data The BASE32-encoded string to be decoded.
258
+ *
259
+ * @returns The decoded UTF-8 string.
260
+ *
261
+ * @throws `RangeError` if the input is not a valid BASE32-encoded string.
262
+ *
263
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
264
+ */
265
+ export function stringFromBase32(data: string): string {
266
+
267
+ return bufferFromBase32(data).toString();
268
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Copyright 2025 Angus.Fenying <fenying@litert.org>
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import * as NodeFS from 'node:fs';
17
+
18
+ let wasmModule: WebAssembly.Module;
19
+
20
+ interface IWasmApis {
21
+
22
+ readonly memory: WebAssembly.Memory;
23
+
24
+ initTable(): void;
25
+
26
+ getReserverMemorySize(): number;
27
+
28
+ encode(len: number): number;
29
+
30
+ decode(len: number): number;
31
+ }
32
+
33
+ /**
34
+ * The encoder (and also the decoder) for base32 encoding,
35
+ * written in WebAssembly.
36
+ */
37
+ class WasmBase32Encoder {
38
+
39
+ /**
40
+ * Allocated pages for the WebAssembly memory.
41
+ * The initial value is 1, which means 64K memory is allocated.
42
+ *
43
+ * > A page in WebAssembly is 64 KiB (65536 bytes).
44
+ */
45
+ private _allocPages: number = 1;
46
+
47
+ private readonly _wasmInst: WebAssembly.Instance;
48
+
49
+ private readonly _apis: IWasmApis;
50
+
51
+ public constructor() {
52
+
53
+ wasmModule ??= new WebAssembly.Module(NodeFS.readFileSync(`${__dirname}/../wasm/base32.wasm`));
54
+
55
+ this._wasmInst = new WebAssembly.Instance(wasmModule, {});
56
+
57
+ this._apis = this._wasmInst.exports as unknown as IWasmApis;
58
+
59
+ this._apis.memory.grow(1); // Allocate 64K memory for the first time.
60
+
61
+ this._apis.initTable();
62
+ }
63
+
64
+ public bufferToBase32(data: Buffer): string {
65
+
66
+ const RMS = this._apis.getReserverMemorySize();
67
+
68
+ // Prepare memory for decoding: RMS + input length + expected output length.
69
+ // The expected output length is 8/5 of the input length.
70
+ // Then the most safe way is just using the 2x (10/5, 200%) as the expected output length,
71
+ // So, the final memory size will be: RMS + input length * 2 + input length.
72
+ const minMemPages = Math.ceil((RMS + data.byteLength * 3) / 65536);
73
+
74
+ if (minMemPages > this._allocPages) {
75
+
76
+ this._apis.memory.grow(minMemPages - this._allocPages);
77
+ this._allocPages = minMemPages;
78
+ }
79
+
80
+ data.copy(Buffer.from(this._apis.memory.buffer, RMS, data.byteLength));
81
+ const outLength = this._apis.encode(data.byteLength);
82
+ return Buffer.from(this._apis.memory.buffer, RMS + data.byteLength, outLength).toString('utf8');
83
+ }
84
+
85
+ public bufferFromBase32(input: string): Buffer {
86
+
87
+ const RMS = this._apis.getReserverMemorySize();
88
+ const inLength = Buffer.byteLength(input);
89
+
90
+ // Prepare memory for decoding: RMS + input length + expected output length.
91
+ // The expected output length is 5/8 of the input length.
92
+ // So, to simplify the calculation, just use the same as the expected output length,
93
+ // So, the final memory size will be: RMS + input length * 2.
94
+ const minMemPages = Math.ceil((inLength * 2 + RMS) / 65536);
95
+
96
+ if (minMemPages > this._allocPages) {
97
+
98
+ this._apis.memory.grow(minMemPages - this._allocPages);
99
+ this._allocPages = minMemPages;
100
+ }
101
+
102
+ // copy the input string to the wasm memory (starting from RMS, ending at RMS + inLength).
103
+ Buffer.from(this._apis.memory.buffer, RMS, inLength).write(input);
104
+
105
+ const outLength = this._apis.decode(inLength);
106
+ if (outLength < 0) {
107
+
108
+ throw new TypeError('Invalid input data for base32 encoding.');
109
+ }
110
+
111
+ // read the result from the wasm memory (starting from RMS + inLength, ending at RMS + inLength + outLength).
112
+ return Buffer.from(this._apis.memory.buffer, RMS + inLength, outLength);
113
+ }
114
+
115
+ public stringToBase32(data: string): string {
116
+
117
+ return this.bufferToBase32(Buffer.from(data));
118
+ }
119
+
120
+ public stringFromBase32(data: string): string {
121
+
122
+ return this.bufferFromBase32(data).toString();
123
+ }
124
+ }
125
+
126
+ const enc = new WasmBase32Encoder();
127
+
128
+ /**
129
+ * Encode a UTF-8 string into a BASE32-encoded string.
130
+ *
131
+ * > This method transform the input string into a `Buffer`, and then calls `bufferToBase32`.
132
+ *
133
+ * @param data The string to be encoded.
134
+ *
135
+ * @returns The BASE32-encoded string.
136
+ *
137
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
138
+ */
139
+ export const stringToBase32 = enc.stringToBase32.bind(enc);
140
+
141
+ /**
142
+ * Decode a BASE32-encoded string into a UTF-8 string.
143
+ *
144
+ * > This method calls `bufferFromBase32` to decode the input string into a `Buffer`, and then
145
+ * > converts it to a UTF-8 string.
146
+ *
147
+ * @param data The BASE32-encoded string to be decoded.
148
+ *
149
+ * @returns The decoded UTF-8 string.
150
+ *
151
+ * @throws `RangeError` if the input is not a valid BASE32-encoded string.
152
+ *
153
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
154
+ */
155
+ export const stringFromBase32 = enc.stringFromBase32.bind(enc);
156
+
157
+ /**
158
+ * Encode a `Buffer` to a BASE32-encoded string.
159
+ *
160
+ * @param data The `Buffer` to be encoded.
161
+ *
162
+ * @returns The BASE32 encoded string.
163
+ *
164
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
165
+ */
166
+ export const bufferToBase32 = enc.bufferToBase32.bind(enc);
167
+
168
+ /**
169
+ * Decode a BASE32-encoded string into a `Buffer`.
170
+ *
171
+ * @param input The BASE32-encoded string to be decoded.
172
+ *
173
+ * @returns The decoded `Buffer`.
174
+ *
175
+ * @throws `RangeError` if the input is not a valid BASE32-encoded string.
176
+ *
177
+ * @see https://datatracker.ietf.org/doc/html/rfc4648#section-6
178
+ */
179
+ export const bufferFromBase32 = enc.bufferFromBase32.bind(enc);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Copyright 2025 Angus.Fenying <fenying@litert.org>
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ export * from './base32-wasm';
Binary file