-
Notifications
You must be signed in to change notification settings - Fork 2
/
base85.ts
131 lines (115 loc) · 4.36 KB
/
base85.ts
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
/** 85 unique characters string */
type CharSet = string;
const ascii85: Uint8Array = charsetToMap(`!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\`abcdefghijklmnopqrstu`);
const z85: Uint8Array = charsetToMap(`0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#`);
const pow2 = 7225; // 85 ** 2
const pow3 = 614125; // 85 ** 3
const pow4 = 52200625; // 85 ** 4
/** @param {"ascii85" | "z85" | string} [charset="z85"]
* @return {Uint8Array} */
function getMap(charset: "ascii85" | "z85" | string = "z85"): Uint8Array {
if (charset === "ascii85") {
return ascii85;
}
if (charset.length === 85) {
return charsetToMap(charset);
}
return z85;
}
/** @param {string} charset - 85 characters ASCII string */
function charsetToMap(charset: string):Uint8Array {
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: Uint8Array): Uint8Array {
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 - input data to encode
* @param {"ascii85" | "z85" | string} [charset="z85"] - 85 unique characters string
* @return {string}
* */
export function encode(ui8a: Uint8Array, charset?: "ascii85" | "z85" | CharSet): string {
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, ui8a.byteOffset, ui8a.byteLength);
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 - base85 encoded string
* @param {"ascii85" | "z85" | string} [charset="z85"] - 85 unique characters string
* @return {Uint8Array}
* */
export function decode(base85: string, charset?: "ascii85" | "z85" | CharSet): Uint8Array {
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;
}
/** Alias to `encode`. */
export const encodeBase85 = encode;
/** Alias to `decode`. */
export const decodeBase85 = decode;
const base85 = {encode, decode, encodeBase85, decodeBase85};
export default base85;