forked from AlttiRi/base85
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase85.js
126 lines (111 loc) · 3.88 KB
/
base85.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
const ascii85 = charsetToMap(`!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstu`);
const z85 = charsetToMap(`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#`);
const pow2 = 85 ** 2;
const pow3 = 85 ** 3;
const pow4 = 85 ** 4;
/** @param {"ascii85" | "z85" | string} [charset="z85"]
* @return {Uint8Array} */
function getMap(charset = "z85") {
if (charset === "ascii85") {
return ascii85;
}
if (charset?.length === 85) {
return charsetToMap(charset);
}
return z85;
}
/** @param {string} charset - 85 characters ASCII string */
function charsetToMap(charset) {
const ui8a = new Uint8Array(85);
for (let i = 0; i < 85; i++) {
ui8a[i] = charset.charAt(i).charCodeAt(0);
}
return ui8a;
}
/** @param {Uint8Array} mapOrig
* @return {Uint8Array} */
function getReverseMap(mapOrig) {
const revMap = new Uint8Array(128);
for (const [num, charCode] of Object.entries(mapOrig)) {
revMap[charCode] = parseInt(num);
}
return revMap;
}
/**
* Returns Base85 string.
* @param {Uint8Array} ui8a
* @param {"ascii85" | "z85" | string} [charset="z85"]
* @return {string}
* */
export function encode(ui8a, charset) {
const charMap = getMap(charset);
const remain = ui8a.length % 4;
const last5Length = remain ? remain + 1 : 0;
const length = Math.ceil(ui8a.length * 5 / 4);
const target = new Uint8Array(length);
const dw = new DataView(ui8a.buffer);
const to = Math.trunc(ui8a.length / 4);
for (let i = 0; i < to; i++) {
let num = dw.getUint32(4 * i);
for (let k = 4; k >= 0; k--) {
target[k + i * 5] = charMap[num % 85];
num = Math.trunc(num / 85);
}
}
if (remain) {
const lastPartIndex = Math.trunc(ui8a.length / 4) * 4;
const lastPart = Uint8Array.from([...ui8a.slice(lastPartIndex), 0, 0, 0]);
const offset = target.length - last5Length - 1;
const dw = new DataView(lastPart.buffer);
let num = dw.getUint32(0);
for (let i = 4; i >= 0; i--) {
const value = charMap[num % 85];
num = Math.trunc(num / 85);
if (i < last5Length) {
const index = offset + i + 1;
target[index] = value;
}
}
}
return new TextDecoder().decode(target);
}
/**
* Decodes Base85 string.
* @param {string} base85
* @param {"ascii85" | "z85" | string} [charset="z85"]
* @return {Uint8Array}
* */
export function decode(base85, charset) {
const map = getMap(charset);
const revMap = getReverseMap(map);
const base85ab = new TextEncoder().encode(base85);
const pad = (5 - (base85ab.length % 5)) % 5;
const ints = new Uint8Array((Math.ceil(base85ab.length / 5) * 4) - pad);
let dw = new DataView(ints.buffer);
let i = 0;
for (; i < base85ab.length / 5 - 1; i++) {
const c1 = revMap[base85ab[i * 5 + 4]];
const c2 = revMap[base85ab[i * 5 + 3]] * 85;
const c3 = revMap[base85ab[i * 5 + 2]] * pow2;
const c4 = revMap[base85ab[i * 5 + 1]] * pow3;
const c5 = revMap[base85ab[i * 5 ]] * pow4;
dw.setUint32(i * 4, c1 + c2 + c3 + c4 + c5);
}
const lCh = map[map.length - 1];
const lastPart = new Uint8Array([...base85ab.slice(i * 5), lCh, lCh, lCh, lCh]);
dw = new DataView(lastPart.buffer);
const c1 = revMap[lastPart[4]];
const c2 = revMap[lastPart[3]] * 85;
const c3 = revMap[lastPart[2]] * pow2;
const c4 = revMap[lastPart[1]] * pow3;
const c5 = revMap[lastPart[0]] * pow4;
dw.setUint32(0, c1 + c2 + c3 + c4 + c5);
for (let j = 0; j < 4 - pad; j++) {
ints[i * 4 + j] = lastPart[j];
}
return ints;
}
export const encodeBase85 = encode;
export const decodeBase85 = decode;
const base85 = {encode, decode, encodeBase85, decodeBase85};
export default base85;