@solana/codecs-strings 6.3.1 → 6.3.2-canary-20260313143218
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/dist/index.browser.cjs +2 -2
- package/dist/index.browser.cjs.map +1 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.native.mjs +2 -2
- package/dist/index.native.mjs.map +1 -1
- package/dist/index.node.cjs +2 -2
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +2 -2
- package/dist/index.node.mjs.map +1 -1
- package/package.json +6 -5
- package/src/assertions.ts +31 -0
- package/src/base10.ts +87 -0
- package/src/base16.ts +156 -0
- package/src/base58.ts +87 -0
- package/src/base64.ts +166 -0
- package/src/baseX-reslice.ts +147 -0
- package/src/baseX.ts +189 -0
- package/src/index.ts +210 -0
- package/src/null-characters.ts +36 -0
- package/src/utf8.ts +114 -0
package/src/base64.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combineCodec,
|
|
3
|
+
createDecoder,
|
|
4
|
+
createEncoder,
|
|
5
|
+
toArrayBuffer,
|
|
6
|
+
transformDecoder,
|
|
7
|
+
transformEncoder,
|
|
8
|
+
VariableSizeCodec,
|
|
9
|
+
VariableSizeDecoder,
|
|
10
|
+
VariableSizeEncoder,
|
|
11
|
+
} from '@solana/codecs-core';
|
|
12
|
+
import { SOLANA_ERROR__CODECS__INVALID_STRING_FOR_BASE, SolanaError } from '@solana/errors';
|
|
13
|
+
|
|
14
|
+
import { assertValidBaseString } from './assertions';
|
|
15
|
+
import { getBaseXResliceDecoder, getBaseXResliceEncoder } from './baseX-reslice';
|
|
16
|
+
|
|
17
|
+
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns an encoder for base-64 strings.
|
|
21
|
+
*
|
|
22
|
+
* This encoder serializes strings using a base-64 encoding scheme,
|
|
23
|
+
* commonly used for data encoding in URLs, cryptographic keys, and binary-to-text encoding.
|
|
24
|
+
*
|
|
25
|
+
* For more details, see {@link getBase64Codec}.
|
|
26
|
+
*
|
|
27
|
+
* @returns A `VariableSizeEncoder<string>` for encoding base-64 strings.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* Encoding a base-64 string.
|
|
31
|
+
* ```ts
|
|
32
|
+
* const encoder = getBase64Encoder();
|
|
33
|
+
* const bytes = encoder.encode('hello+world'); // 0x85e965a3ec28ae57
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @see {@link getBase64Codec}
|
|
37
|
+
*/
|
|
38
|
+
export const getBase64Encoder = (): VariableSizeEncoder<string> => {
|
|
39
|
+
if (__BROWSER__) {
|
|
40
|
+
return createEncoder({
|
|
41
|
+
getSizeFromValue: (value: string) => {
|
|
42
|
+
try {
|
|
43
|
+
return (atob as Window['atob'])(value).length;
|
|
44
|
+
} catch {
|
|
45
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_STRING_FOR_BASE, {
|
|
46
|
+
alphabet,
|
|
47
|
+
base: 64,
|
|
48
|
+
value,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
write(value: string, bytes, offset) {
|
|
53
|
+
try {
|
|
54
|
+
const bytesToAdd = (atob as Window['atob'])(value)
|
|
55
|
+
.split('')
|
|
56
|
+
.map(c => c.charCodeAt(0));
|
|
57
|
+
bytes.set(bytesToAdd, offset);
|
|
58
|
+
return bytesToAdd.length + offset;
|
|
59
|
+
} catch {
|
|
60
|
+
throw new SolanaError(SOLANA_ERROR__CODECS__INVALID_STRING_FOR_BASE, {
|
|
61
|
+
alphabet,
|
|
62
|
+
base: 64,
|
|
63
|
+
value,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (__NODEJS__) {
|
|
71
|
+
return createEncoder({
|
|
72
|
+
getSizeFromValue: (value: string) => Buffer.from(value, 'base64').length,
|
|
73
|
+
write(value: string, bytes, offset) {
|
|
74
|
+
assertValidBaseString(alphabet, value.replace(/=/g, ''));
|
|
75
|
+
const buffer = Buffer.from(value, 'base64');
|
|
76
|
+
bytes.set(buffer, offset);
|
|
77
|
+
return buffer.length + offset;
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return transformEncoder(getBaseXResliceEncoder(alphabet, 6), (value: string): string => value.replace(/=/g, ''));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns a decoder for base-64 strings.
|
|
87
|
+
*
|
|
88
|
+
* This decoder deserializes base-64 encoded strings from a byte array.
|
|
89
|
+
*
|
|
90
|
+
* For more details, see {@link getBase64Codec}.
|
|
91
|
+
*
|
|
92
|
+
* @returns A `VariableSizeDecoder<string>` for decoding base-64 strings.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* Decoding a base-64 string.
|
|
96
|
+
* ```ts
|
|
97
|
+
* const decoder = getBase64Decoder();
|
|
98
|
+
* const value = decoder.decode(new Uint8Array([0x85, 0xe9, 0x65, 0xa3, 0xec, 0x28, 0xae, 0x57])); // "hello+world"
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @see {@link getBase64Codec}
|
|
102
|
+
*/
|
|
103
|
+
export const getBase64Decoder = (): VariableSizeDecoder<string> => {
|
|
104
|
+
if (__BROWSER__) {
|
|
105
|
+
return createDecoder({
|
|
106
|
+
read(bytes, offset = 0) {
|
|
107
|
+
const slice = bytes.slice(offset);
|
|
108
|
+
const value = (btoa as Window['btoa'])(String.fromCharCode(...slice));
|
|
109
|
+
return [value, bytes.length];
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (__NODEJS__) {
|
|
115
|
+
return createDecoder({
|
|
116
|
+
read: (bytes, offset = 0) => [Buffer.from(toArrayBuffer(bytes), offset).toString('base64'), bytes.length],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return transformDecoder(getBaseXResliceDecoder(alphabet, 6), (value: string): string =>
|
|
121
|
+
value.padEnd(Math.ceil(value.length / 4) * 4, '='),
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns a codec for encoding and decoding base-64 strings.
|
|
127
|
+
*
|
|
128
|
+
* This codec serializes strings using a base-64 encoding scheme,
|
|
129
|
+
* commonly used for data encoding in URLs, cryptographic keys, and binary-to-text encoding.
|
|
130
|
+
*
|
|
131
|
+
* @returns A `VariableSizeCodec<string>` for encoding and decoding base-64 strings.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* Encoding and decoding a base-64 string.
|
|
135
|
+
* ```ts
|
|
136
|
+
* const codec = getBase64Codec();
|
|
137
|
+
* const bytes = codec.encode('hello+world'); // 0x85e965a3ec28ae57
|
|
138
|
+
* const value = codec.decode(bytes); // "hello+world"
|
|
139
|
+
* ```
|
|
140
|
+
*
|
|
141
|
+
* @remarks
|
|
142
|
+
* This codec does not enforce a size boundary. It will encode and decode all bytes necessary to represent the string.
|
|
143
|
+
*
|
|
144
|
+
* If you need a fixed-size base-64 codec, consider using {@link fixCodecSize}.
|
|
145
|
+
*
|
|
146
|
+
* ```ts
|
|
147
|
+
* const codec = fixCodecSize(getBase64Codec(), 8);
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* If you need a size-prefixed base-64 codec, consider using {@link addCodecSizePrefix}.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* const codec = addCodecSizePrefix(getBase64Codec(), getU32Codec());
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* Separate {@link getBase64Encoder} and {@link getBase64Decoder} functions are available.
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* const bytes = getBase64Encoder().encode('hello+world');
|
|
160
|
+
* const value = getBase64Decoder().decode(bytes);
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @see {@link getBase64Encoder}
|
|
164
|
+
* @see {@link getBase64Decoder}
|
|
165
|
+
*/
|
|
166
|
+
export const getBase64Codec = (): VariableSizeCodec<string> => combineCodec(getBase64Encoder(), getBase64Decoder());
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combineCodec,
|
|
3
|
+
createDecoder,
|
|
4
|
+
createEncoder,
|
|
5
|
+
VariableSizeCodec,
|
|
6
|
+
VariableSizeDecoder,
|
|
7
|
+
VariableSizeEncoder,
|
|
8
|
+
} from '@solana/codecs-core';
|
|
9
|
+
|
|
10
|
+
import { assertValidBaseString } from './assertions';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns an encoder for base-X encoded strings using bit re-slicing.
|
|
14
|
+
*
|
|
15
|
+
* This encoder serializes strings by dividing the input into custom-sized bit chunks,
|
|
16
|
+
* mapping them to an alphabet, and encoding the result into a byte array.
|
|
17
|
+
* This approach is commonly used for encoding schemes where the alphabet's length is a power of 2,
|
|
18
|
+
* such as base-16 or base-64.
|
|
19
|
+
*
|
|
20
|
+
* For more details, see {@link getBaseXResliceCodec}.
|
|
21
|
+
*
|
|
22
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
23
|
+
* @param bits - The number of bits per encoded chunk, typically `log2(alphabet.length)`.
|
|
24
|
+
* @returns A `VariableSizeEncoder<string>` for encoding base-X strings using bit re-slicing.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* Encoding a base-X string using bit re-slicing.
|
|
28
|
+
* ```ts
|
|
29
|
+
* const encoder = getBaseXResliceEncoder('elho', 2);
|
|
30
|
+
* const bytes = encoder.encode('hellolol'); // 0x4aee
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @see {@link getBaseXResliceCodec}
|
|
34
|
+
*/
|
|
35
|
+
export const getBaseXResliceEncoder = (alphabet: string, bits: number): VariableSizeEncoder<string> =>
|
|
36
|
+
createEncoder({
|
|
37
|
+
getSizeFromValue: (value: string) => Math.floor((value.length * bits) / 8),
|
|
38
|
+
write(value: string, bytes, offset) {
|
|
39
|
+
assertValidBaseString(alphabet, value);
|
|
40
|
+
if (value === '') return offset;
|
|
41
|
+
const charIndices = [...value].map(c => alphabet.indexOf(c));
|
|
42
|
+
const reslicedBytes = reslice(charIndices, bits, 8, false);
|
|
43
|
+
bytes.set(reslicedBytes, offset);
|
|
44
|
+
return reslicedBytes.length + offset;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns a decoder for base-X encoded strings using bit re-slicing.
|
|
50
|
+
*
|
|
51
|
+
* This decoder deserializes base-X encoded strings by re-slicing the bits of a byte array into
|
|
52
|
+
* custom-sized chunks and mapping them to a specified alphabet.
|
|
53
|
+
* This is typically used for encoding schemes where the alphabet's length is a power of 2,
|
|
54
|
+
* such as base-16 or base-64.
|
|
55
|
+
*
|
|
56
|
+
* For more details, see {@link getBaseXResliceCodec}.
|
|
57
|
+
*
|
|
58
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
59
|
+
* @param bits - The number of bits per encoded chunk, typically `log2(alphabet.length)`.
|
|
60
|
+
* @returns A `VariableSizeDecoder<string>` for decoding base-X strings using bit re-slicing.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* Decoding a base-X string using bit re-slicing.
|
|
64
|
+
* ```ts
|
|
65
|
+
* const decoder = getBaseXResliceDecoder('elho', 2);
|
|
66
|
+
* const value = decoder.decode(new Uint8Array([0x4a, 0xee])); // "hellolol"
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @see {@link getBaseXResliceCodec}
|
|
70
|
+
*/
|
|
71
|
+
export const getBaseXResliceDecoder = (alphabet: string, bits: number): VariableSizeDecoder<string> =>
|
|
72
|
+
createDecoder({
|
|
73
|
+
read(rawBytes, offset = 0): [string, number] {
|
|
74
|
+
const bytes = offset === 0 || offset <= -rawBytes.byteLength ? rawBytes : rawBytes.slice(offset);
|
|
75
|
+
if (bytes.length === 0) return ['', rawBytes.length];
|
|
76
|
+
const charIndices = reslice([...bytes], 8, bits, true);
|
|
77
|
+
return [charIndices.map(i => alphabet[i]).join(''), rawBytes.length];
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns a codec for encoding and decoding base-X strings using bit re-slicing.
|
|
83
|
+
*
|
|
84
|
+
* This codec serializes strings by dividing the input into custom-sized bit chunks,
|
|
85
|
+
* mapping them to a given alphabet, and encoding the result into bytes.
|
|
86
|
+
* It is particularly suited for encoding schemes where the alphabet's length is a power of 2,
|
|
87
|
+
* such as base-16 or base-64.
|
|
88
|
+
*
|
|
89
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
90
|
+
* @param bits - The number of bits per encoded chunk, typically `log2(alphabet.length)`.
|
|
91
|
+
* @returns A `VariableSizeCodec<string>` for encoding and decoding base-X strings using bit re-slicing.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* Encoding and decoding a base-X string using bit re-slicing.
|
|
95
|
+
* ```ts
|
|
96
|
+
* const codec = getBaseXResliceCodec('elho', 2);
|
|
97
|
+
* const bytes = codec.encode('hellolol'); // 0x4aee
|
|
98
|
+
* const value = codec.decode(bytes); // "hellolol"
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* This codec does not enforce a size boundary. It will encode and decode all bytes necessary to represent the string.
|
|
103
|
+
*
|
|
104
|
+
* If you need a fixed-size base-X codec, consider using {@link fixCodecSize}.
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* const codec = fixCodecSize(getBaseXResliceCodec('elho', 2), 8);
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* If you need a size-prefixed base-X codec, consider using {@link addCodecSizePrefix}.
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* const codec = addCodecSizePrefix(getBaseXResliceCodec('elho', 2), getU32Codec());
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* Separate {@link getBaseXResliceEncoder} and {@link getBaseXResliceDecoder} functions are available.
|
|
117
|
+
*
|
|
118
|
+
* ```ts
|
|
119
|
+
* const bytes = getBaseXResliceEncoder('elho', 2).encode('hellolol');
|
|
120
|
+
* const value = getBaseXResliceDecoder('elho', 2).decode(bytes);
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @see {@link getBaseXResliceEncoder}
|
|
124
|
+
* @see {@link getBaseXResliceDecoder}
|
|
125
|
+
*/
|
|
126
|
+
export const getBaseXResliceCodec = (alphabet: string, bits: number): VariableSizeCodec<string> =>
|
|
127
|
+
combineCodec(getBaseXResliceEncoder(alphabet, bits), getBaseXResliceDecoder(alphabet, bits));
|
|
128
|
+
|
|
129
|
+
/** Helper function to reslice the bits inside bytes. */
|
|
130
|
+
function reslice(input: number[], inputBits: number, outputBits: number, useRemainder: boolean): number[] {
|
|
131
|
+
const output = [];
|
|
132
|
+
let accumulator = 0;
|
|
133
|
+
let bitsInAccumulator = 0;
|
|
134
|
+
const mask = (1 << outputBits) - 1;
|
|
135
|
+
for (const value of input) {
|
|
136
|
+
accumulator = (accumulator << inputBits) | value;
|
|
137
|
+
bitsInAccumulator += inputBits;
|
|
138
|
+
while (bitsInAccumulator >= outputBits) {
|
|
139
|
+
bitsInAccumulator -= outputBits;
|
|
140
|
+
output.push((accumulator >> bitsInAccumulator) & mask);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (useRemainder && bitsInAccumulator > 0) {
|
|
144
|
+
output.push((accumulator << (outputBits - bitsInAccumulator)) & mask);
|
|
145
|
+
}
|
|
146
|
+
return output;
|
|
147
|
+
}
|
package/src/baseX.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
combineCodec,
|
|
3
|
+
createDecoder,
|
|
4
|
+
createEncoder,
|
|
5
|
+
VariableSizeCodec,
|
|
6
|
+
VariableSizeDecoder,
|
|
7
|
+
VariableSizeEncoder,
|
|
8
|
+
} from '@solana/codecs-core';
|
|
9
|
+
|
|
10
|
+
import { assertValidBaseString } from './assertions';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Returns an encoder for base-X encoded strings.
|
|
14
|
+
*
|
|
15
|
+
* This encoder serializes strings using a custom alphabet, treating the length of the alphabet as the base.
|
|
16
|
+
* The encoding process involves converting the input string to a numeric value in base-X, then
|
|
17
|
+
* encoding that value into bytes while preserving leading zeroes.
|
|
18
|
+
*
|
|
19
|
+
* For more details, see {@link getBaseXCodec}.
|
|
20
|
+
*
|
|
21
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
22
|
+
* @returns A `VariableSizeEncoder<string>` for encoding base-X strings.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* Encoding a base-X string using a custom alphabet.
|
|
26
|
+
* ```ts
|
|
27
|
+
* const encoder = getBaseXEncoder('0123456789abcdef');
|
|
28
|
+
* const bytes = encoder.encode('deadface'); // 0xdeadface
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @see {@link getBaseXCodec}
|
|
32
|
+
*/
|
|
33
|
+
export const getBaseXEncoder = (alphabet: string): VariableSizeEncoder<string> => {
|
|
34
|
+
return createEncoder({
|
|
35
|
+
getSizeFromValue: (value: string): number => {
|
|
36
|
+
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet[0]);
|
|
37
|
+
if (!tailChars) return value.length;
|
|
38
|
+
|
|
39
|
+
const base10Number = getBigIntFromBaseX(tailChars, alphabet);
|
|
40
|
+
return leadingZeroes.length + Math.ceil(base10Number.toString(16).length / 2);
|
|
41
|
+
},
|
|
42
|
+
write(value: string, bytes, offset) {
|
|
43
|
+
// Check if the value is valid.
|
|
44
|
+
assertValidBaseString(alphabet, value);
|
|
45
|
+
if (value === '') return offset;
|
|
46
|
+
|
|
47
|
+
// Handle leading zeroes.
|
|
48
|
+
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet[0]);
|
|
49
|
+
if (!tailChars) {
|
|
50
|
+
bytes.set(new Uint8Array(leadingZeroes.length).fill(0), offset);
|
|
51
|
+
return offset + leadingZeroes.length;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// From baseX to base10.
|
|
55
|
+
let base10Number = getBigIntFromBaseX(tailChars, alphabet);
|
|
56
|
+
|
|
57
|
+
// From base10 to bytes.
|
|
58
|
+
const tailBytes: number[] = [];
|
|
59
|
+
while (base10Number > 0n) {
|
|
60
|
+
tailBytes.unshift(Number(base10Number % 256n));
|
|
61
|
+
base10Number /= 256n;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const bytesToAdd = [...Array(leadingZeroes.length).fill(0), ...tailBytes];
|
|
65
|
+
bytes.set(bytesToAdd, offset);
|
|
66
|
+
return offset + bytesToAdd.length;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns a decoder for base-X encoded strings.
|
|
73
|
+
*
|
|
74
|
+
* This decoder deserializes base-X encoded strings from a byte array using a custom alphabet.
|
|
75
|
+
* The decoding process converts the byte array into a numeric value in base-10, then
|
|
76
|
+
* maps that value back to characters in the specified base-X alphabet.
|
|
77
|
+
*
|
|
78
|
+
* For more details, see {@link getBaseXCodec}.
|
|
79
|
+
*
|
|
80
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
81
|
+
* @returns A `VariableSizeDecoder<string>` for decoding base-X strings.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* Decoding a base-X string using a custom alphabet.
|
|
85
|
+
* ```ts
|
|
86
|
+
* const decoder = getBaseXDecoder('0123456789abcdef');
|
|
87
|
+
* const value = decoder.decode(new Uint8Array([0xde, 0xad, 0xfa, 0xce])); // "deadface"
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @see {@link getBaseXCodec}
|
|
91
|
+
*/
|
|
92
|
+
export const getBaseXDecoder = (alphabet: string): VariableSizeDecoder<string> => {
|
|
93
|
+
return createDecoder({
|
|
94
|
+
read(rawBytes, offset): [string, number] {
|
|
95
|
+
const bytes = offset === 0 || offset <= -rawBytes.byteLength ? rawBytes : rawBytes.slice(offset);
|
|
96
|
+
if (bytes.length === 0) return ['', 0];
|
|
97
|
+
|
|
98
|
+
// Handle leading zeroes.
|
|
99
|
+
let trailIndex = bytes.findIndex(n => n !== 0);
|
|
100
|
+
trailIndex = trailIndex === -1 ? bytes.length : trailIndex;
|
|
101
|
+
const leadingZeroes = alphabet[0].repeat(trailIndex);
|
|
102
|
+
if (trailIndex === bytes.length) return [leadingZeroes, rawBytes.length];
|
|
103
|
+
|
|
104
|
+
// From bytes to base10.
|
|
105
|
+
const base10Number = bytes.slice(trailIndex).reduce((sum, byte) => sum * 256n + BigInt(byte), 0n);
|
|
106
|
+
|
|
107
|
+
// From base10 to baseX.
|
|
108
|
+
const tailChars = getBaseXFromBigInt(base10Number, alphabet);
|
|
109
|
+
|
|
110
|
+
return [leadingZeroes + tailChars, rawBytes.length];
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Returns a codec for encoding and decoding base-X strings.
|
|
117
|
+
*
|
|
118
|
+
* This codec serializes strings using a custom alphabet, treating the length of the alphabet as the base.
|
|
119
|
+
* The encoding process converts the input string into a numeric value in base-X, which is then encoded as bytes.
|
|
120
|
+
* The decoding process reverses this transformation to reconstruct the original string.
|
|
121
|
+
*
|
|
122
|
+
* This codec supports leading zeroes by treating the first character of the alphabet as the zero character.
|
|
123
|
+
*
|
|
124
|
+
* @param alphabet - The set of characters defining the base-X encoding.
|
|
125
|
+
* @returns A `VariableSizeCodec<string>` for encoding and decoding base-X strings.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* Encoding and decoding a base-X string using a custom alphabet.
|
|
129
|
+
* ```ts
|
|
130
|
+
* const codec = getBaseXCodec('0123456789abcdef');
|
|
131
|
+
* const bytes = codec.encode('deadface'); // 0xdeadface
|
|
132
|
+
* const value = codec.decode(bytes); // "deadface"
|
|
133
|
+
* ```
|
|
134
|
+
*
|
|
135
|
+
* @remarks
|
|
136
|
+
* This codec does not enforce a size boundary. It will encode and decode all bytes necessary to represent the string.
|
|
137
|
+
*
|
|
138
|
+
* If you need a fixed-size base-X codec, consider using {@link fixCodecSize}.
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* const codec = fixCodecSize(getBaseXCodec('0123456789abcdef'), 8);
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* If you need a size-prefixed base-X codec, consider using {@link addCodecSizePrefix}.
|
|
145
|
+
*
|
|
146
|
+
* ```ts
|
|
147
|
+
* const codec = addCodecSizePrefix(getBaseXCodec('0123456789abcdef'), getU32Codec());
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* Separate {@link getBaseXEncoder} and {@link getBaseXDecoder} functions are available.
|
|
151
|
+
*
|
|
152
|
+
* ```ts
|
|
153
|
+
* const bytes = getBaseXEncoder('0123456789abcdef').encode('deadface');
|
|
154
|
+
* const value = getBaseXDecoder('0123456789abcdef').decode(bytes);
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @see {@link getBaseXEncoder}
|
|
158
|
+
* @see {@link getBaseXDecoder}
|
|
159
|
+
*/
|
|
160
|
+
export const getBaseXCodec = (alphabet: string): VariableSizeCodec<string> =>
|
|
161
|
+
combineCodec(getBaseXEncoder(alphabet), getBaseXDecoder(alphabet));
|
|
162
|
+
|
|
163
|
+
function partitionLeadingZeroes(
|
|
164
|
+
value: string,
|
|
165
|
+
zeroCharacter: string,
|
|
166
|
+
): [leadingZeros: string, tailChars: string | undefined] {
|
|
167
|
+
const [leadingZeros, tailChars] = value.split(new RegExp(`((?!${zeroCharacter}).*)`));
|
|
168
|
+
return [leadingZeros, tailChars];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getBigIntFromBaseX(value: string, alphabet: string): bigint {
|
|
172
|
+
const base = BigInt(alphabet.length);
|
|
173
|
+
let sum = 0n;
|
|
174
|
+
for (const char of value) {
|
|
175
|
+
sum *= base;
|
|
176
|
+
sum += BigInt(alphabet.indexOf(char));
|
|
177
|
+
}
|
|
178
|
+
return sum;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function getBaseXFromBigInt(value: bigint, alphabet: string): string {
|
|
182
|
+
const base = BigInt(alphabet.length);
|
|
183
|
+
const tailChars = [];
|
|
184
|
+
while (value > 0n) {
|
|
185
|
+
tailChars.unshift(alphabet[Number(value % base)]);
|
|
186
|
+
value /= base;
|
|
187
|
+
}
|
|
188
|
+
return tailChars.join('');
|
|
189
|
+
}
|