Skip to content

Commit abd4b37

Browse files
committed
switch from base64 to z85
pre-compute charset array fix z85 implementation
1 parent f3fd2aa commit abd4b37

File tree

5 files changed

+86
-146
lines changed

5 files changed

+86
-146
lines changed

src/base64.js

Lines changed: 0 additions & 110 deletions
This file was deleted.

src/parse.js

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { decode64 } from './base64.js';
1+
import { decode85 } from './z85.js';
22
import {
33
HOLE,
44
NAN,
@@ -102,31 +102,26 @@ export function unflatten(parsed, revivers) {
102102
}
103103
break;
104104

105-
case "Int8Array":
106-
case "Uint8Array":
107-
case "Uint8ClampedArray":
108-
case "Int16Array":
109-
case "Uint16Array":
110-
case "Int32Array":
111-
case "Uint32Array":
112-
case "Float32Array":
113-
case "Float64Array":
114-
case "BigInt64Array":
115-
case "BigUint64Array": {
116-
const TypedArrayConstructor = globalThis[type];
117-
const base64 = value[1];
118-
const arraybuffer = decode64(base64);
119-
const typedArray = new TypedArrayConstructor(arraybuffer);
120-
hydrated[index] = typedArray;
121-
break;
122-
}
123-
124-
case "ArrayBuffer": {
125-
const base64 = value[1];
126-
const arraybuffer = decode64(base64);
127-
hydrated[index] = arraybuffer;
128-
break;
129-
}
105+
case "Int8Array":
106+
case "Uint8Array":
107+
case "Uint8ClampedArray":
108+
case "Int16Array":
109+
case "Uint16Array":
110+
case "Int32Array":
111+
case "Uint32Array":
112+
case "Float32Array":
113+
case "Float64Array":
114+
case "BigInt64Array":
115+
case "BigUint64Array": {
116+
const TypedArrayConstructor = globalThis[type];
117+
hydrated[index] = new TypedArrayConstructor(decode85(value[1]));
118+
break;
119+
}
120+
121+
case "ArrayBuffer": {
122+
hydrated[index] = decode85(value[1]);
123+
break;
124+
}
130125

131126
default:
132127
throw new Error(`Unknown type ${type}`);

src/stringify.js

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
POSITIVE_INFINITY,
1616
UNDEFINED
1717
} from './constants.js';
18-
import { encode64 } from './base64.js';
18+
import { encode85 } from './z85.js';
1919

2020
/**
2121
* Turn a value into a JSON string that can be parsed with `devalue.parse`
@@ -153,20 +153,18 @@ export function stringify(value, reducers) {
153153
case "BigUint64Array": {
154154
/** @type {import("./types.js").TypedArray} */
155155
const typedArray = thing;
156-
const base64 = encode64(typedArray.buffer);
157-
str = '["' + type + '","' + base64 + '"]';
156+
str = '["' + type + '","' + encode85(typedArray.buffer) + '"]';
158157
break;
159158
}
160-
159+
161160
case "ArrayBuffer": {
162161
/** @type {ArrayBuffer} */
163162
const arraybuffer = thing;
164-
const base64 = encode64(arraybuffer);
165-
166-
str = `["ArrayBuffer","${base64}"]`;
163+
164+
str = `["ArrayBuffer","${encode85(arraybuffer)}"]`;
167165
break;
168166
}
169-
167+
170168
default:
171169
if (!is_plain_object(thing)) {
172170
throw new DevalueError(

src/z85.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const ENCODE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#";
2+
const DECODE = [-1, 68, -1, 84, 83, 82, 72, -1, 75, 76, 70, 65, -1, 63, 62, 69, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, -1, 73, 66, 74, 71, 81, 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, 77, -1, 78, 67, -1, -1, 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, 79, -1, 80, -1, -1];
3+
const POW_85 = [0, 1, 85, 7225, 614125, 52200625];
4+
const POW_256 = [1, 256, 65536, 16777216];
5+
6+
/**
7+
* Encodes binary data into a Z85 string.
8+
* @param {ArrayBuffer} data - The binary data to encode
9+
* @returns {string} The Z85 encoded string
10+
*/
11+
function encode85(data) {
12+
const dv = new DataView(data);
13+
const length = dv.byteLength;
14+
const padding = (4 - (length % 4)) % 4;
15+
16+
let result = '', value = 0;
17+
for (let i = 0; i < length + padding; ++i) {
18+
const isPadding = i >= length;
19+
value = value * 256 + (isPadding ? 0 : dv.getUint8(i));
20+
if ((i + 1) % 4 === 0) {
21+
for (let j = 5; j > 0; --j) {
22+
if (isPadding && j <= padding)
23+
continue;
24+
25+
result += ENCODE[Math.floor(value / POW_85[j]) % 85];
26+
}
27+
value = 0;
28+
}
29+
}
30+
31+
return result;
32+
};
33+
34+
/**
35+
* Decodes a Z85 string into binary data.
36+
* @param {string} string - The Z85 encoded string
37+
* @returns {ArrayBuffer} The decoded binary data
38+
*/
39+
function decode85(string) {
40+
const remainder = string.length % 5;
41+
const padding = 5 - (remainder === 0 ? 5 : remainder);
42+
string = string.padEnd(string.length + padding, ENCODE[ENCODE.length - 1]);
43+
const length = string.length;
44+
45+
let buffer = new Uint8Array((length * 4 / 5) - padding);
46+
let value = 0, char = 0, byte = 0;
47+
for (let i = 0; i < length; ++i) {
48+
value = value * 85 + DECODE[string.charCodeAt(char++) - 32];
49+
if (char % 5 !== 0) continue;
50+
51+
for (let j = 3; j >= 0; --j)
52+
buffer[byte++] = Math.floor(value / POW_256[j]) % 256;
53+
value = 0;
54+
}
55+
56+
return buffer.buffer;
57+
}

test/test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,13 @@ const fixtures = {
164164
name: 'Uint8Array',
165165
value: new Uint8Array([1, 2, 3]),
166166
js: 'new Uint8Array([1,2,3])',
167-
json: '[["Uint8Array","AQID"]]'
167+
json: '[["Uint8Array","0rJu"]]'
168168
},
169169
{
170170
name: 'ArrayBuffer',
171171
value: new Uint8Array([1, 2, 3]).buffer,
172172
js: 'new Uint8Array([1,2,3]).buffer',
173-
json: '[["ArrayBuffer","AQID"]]'
173+
json: '[["ArrayBuffer","0rJu"]]'
174174
}
175175
],
176176

0 commit comments

Comments
 (0)