Skip to content

Commit a267efe

Browse files
committed
fix: address review feedback
1 parent 14fb384 commit a267efe

3 files changed

Lines changed: 47 additions & 20 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@
8383
},
8484
"./bases/base45": {
8585
"types": "./dist/src/bases/base45.d.ts",
86-
"import": "./dist/src/bases/base45.js"
86+
"import": "./dist/src/bases/base45.js",
87+
"module-sync": "./dist/src/bases/base45.js"
8788
},
8889
"./bases/base58": {
8990
"types": "./dist/src/bases/base58.d.ts",

src/bases/base45.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { from } from './base.ts'
22

33
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
4+
const INVALID = 0xff
5+
const decodeTable = (() => {
6+
const table = new Uint8Array(256).fill(INVALID)
7+
for (let i = 0; i < alphabet.length; i++) {
8+
const code = alphabet.charCodeAt(i)
9+
table[code] = i
10+
if (code >= 0x41 && code <= 0x5a) {
11+
table[code + 0x20] = i
12+
}
13+
}
14+
return table
15+
})()
416

5-
function indexOf (char: string): number {
6-
const index = alphabet.indexOf(char)
7-
if (index === -1) {
8-
throw new Error(`Non-base45 character: ${char}`)
17+
function decodeChar (input: string, i: number): number {
18+
const v = decodeTable[input.charCodeAt(i)]
19+
if (v === INVALID) {
20+
throw new Error(`Non-base45 character: ${input[i]}`)
921
}
10-
return index
22+
return v
1123
}
1224

1325
export const base45 = from({
@@ -18,33 +30,35 @@ export const base45 = from({
1830
for (let i = 0; i < input.length; i += 2) {
1931
if (i + 1 === input.length) {
2032
const v = input[i]
21-
const a = v / 45 | 0
22-
const b = v % 45 | 0
23-
ret += alphabet[b] + alphabet[a]
33+
ret += alphabet[v % 45] + alphabet[(v / 45) | 0]
2434
break
2535
}
26-
const v = input[i] << 8 | input[i + 1]
27-
const a = v / 45 ** 2 | 0
28-
const b = v / 45 % 45 | 0
29-
const c = v % 45
30-
ret += alphabet[c] + alphabet[b] + alphabet[a]
36+
const v = (input[i] << 8) | input[i + 1]
37+
ret += alphabet[v % 45] + alphabet[((v / 45) | 0) % 45] + alphabet[(v / 2025) | 0]
3138
}
3239
return ret
3340
},
3441
decode: (input: string): Uint8Array<ArrayBuffer> => {
3542
if ((input.length * 2) % 3 === 2) {
3643
throw new Error('Unexpected end of data')
3744
}
38-
const out = new Uint8Array(Math.floor(input.length * 2 / 3))
45+
const out = new Uint8Array(((input.length * 2) / 3) | 0)
46+
let o = 0
3947
for (let i = 0; i < input.length; i += 3) {
4048
if (i + 2 === input.length) {
41-
const v = indexOf(input[i]) + indexOf(input[i + 1]) * 45
42-
out[i / 3 * 2] = v
49+
const v = decodeChar(input, i) + decodeChar(input, i + 1) * 45
50+
if (v > 0xff) {
51+
throw new Error('Invalid base45 encoding: trailing chunk out of range')
52+
}
53+
out[o++] = v
4354
break
4455
}
45-
const v = indexOf(input[i]) + indexOf(input[i + 1]) * 45 + indexOf(input[i + 2]) * 45 ** 2
46-
out[i / 3 * 2] = v >> 8
47-
out[i / 3 * 2 + 1] = v & 0xff
56+
const v = decodeChar(input, i) + decodeChar(input, i + 1) * 45 + decodeChar(input, i + 2) * 2025
57+
if (v > 0xffff) {
58+
throw new Error('Invalid base45 encoding: chunk out of range')
59+
}
60+
out[o++] = v >> 8
61+
out[o++] = v & 0xff
4862
}
4963
return out
5064
}

test/test-multibase-spec.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,4 +194,16 @@ describe('spec test', () => {
194194
// not enough input chars, should be multiple of 3 or multiple of 3 + 2
195195
assert.throws(() => bases.base45.decode('R%69 VD92EX'), 'Unexpected end of data')
196196
})
197+
198+
// RFC9285 section 6: encoded chunks must lie within the byte ranges they
199+
// represent; 3-char chunks decode to a 16-bit value, 2-char trailers to 8-bit.
200+
for (const input of ['RGGW', 'R:::', 'R000V5', 'R000::']) {
201+
it(`base45 should fail with out-of-range encoding [${input}]`, () => {
202+
assert.throws(() => bases.base45.decode(input), 'Invalid base45 encoding')
203+
})
204+
}
205+
206+
it('base45 should decode case-insensitively', () => {
207+
assert.deepStrictEqual(bases.base45.decode('R+8d vd82ek4f.kea2'), fromString('hello world'))
208+
})
197209
})

0 commit comments

Comments
 (0)