-
Notifications
You must be signed in to change notification settings - Fork 2
/
base32.js
157 lines (128 loc) · 3.77 KB
/
base32.js
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
/* base32.js
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
// strings will be translated by gettext in the frontend
const _ = x => x;
const digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
// See RFC 4648
export
function decode(input, strict = true)
{
console.assert(digits.length == 32);
let output = [];
// process 40 bits at a time (8 base32 digits)
for (let i = 0; i < input.length; i += 8) {
let chunk = input.substring(i, i + 8);
if (chunk.length != 8) {
if (strict)
throw new Error(_('Invalid base32 input; missing padding.'));
else
chunk = chunk.padEnd(8, '=');
}
let value = 0;
let pad = 0;
for (let j = 0; j < 8; ++j) {
let d = chunk[j].toUpperCase();
let idx = digits.indexOf(d);
if (d == '=') {
idx = 0;
++pad;
}
if (idx == -1)
throw new Error(_('Invalid base32 character at position:')
+ ` ${i + j}`);
value = value * 32 + idx;
}
if (pad == 2 || pad == 5 || pad == 7)
throw new Error(_('Invalid padding.'));
// store value as little endian
let value_bytes = [];
for (let j = 0; j < 5; ++j) {
value_bytes.push(value % 256);
value = Math.trunc(value / 256);
}
// pad tells how many bytes to discard
value_bytes = value_bytes.slice(Math.ceil(pad * 5 / 8));
// turn to big endian
value_bytes.reverse();
output = output.concat(value_bytes);
}
return new Uint8Array(output);
}
export
function encode(input, trim = false)
{
let output = '';
for (let i = 0; i < input.length; i += 5) {
// quantum is always padded with zeros
let quantum = Array.from(input.slice(i, i + 5));
let pad = 5 - quantum.length;
for (let j = 0; j < pad; ++j)
quantum.push(0);
let value = 0;
quantum.forEach(x => value = (value * 256) + parseInt(x));
// note: qo is little endian
let qo = [];
for (let j = 0; j < 8; ++j) {
let idx = value & 0x1f;
qo.push(digits[idx]);
value = Math.trunc(value / 32);
}
if (!trim) {
let num_fill = Math.trunc(pad * 8 / 5);
qo.fill('=', 0, num_fill);
}
// reverse qo to big endian before joining the string
output += qo.reverse().join('');
}
return output;
}
// Unit test
/*
function _test()
{
function utf8(s)
{
let te = new TextEncoder();
return te.encode(s);
}
function str(a)
{
let td = new TextDecoder();
return td.decode(a);
}
function are_equal(a, b)
{
if (a.length != b.length)
return false;
for (let i = 0; i < a.length; ++i)
if (a[i] != b[i])
return false;
return true;
}
function check(input, expected)
{
let tinput = utf8(input);
let output = encode(tinput);
console.assert(output === expected,
`BASE32("${input}") should be "${expected}" but got "${output}"`);
let routput = decode(output);
console.assert(are_equal(routput, tinput),
`deBASE32("${output}") should be "${input}"`);
if (!are_equal(routput, tinput)) {
console.error(routput);
console.error(tinput);
}
}
// Examples taken from RFC 4648
check('', '');
check('f', 'MY======');
check('fo', 'MZXQ====');
check('foo', 'MZXW6===');
check('foob', 'MZXW6YQ=');
check('fooba', 'MZXW6YTB');
check('foobar', 'MZXW6YTBOI======');
}
*/
// _test();