Skip to content

Commit f309da0

Browse files
committed
feat: add native C++ encoding/decoding for hex, base64, and more
Move buffer↔string conversion from the JS Buffer polyfill to native C++ via Nitro Modules, using OpenSSL EVP for base64 and hand-rolled hex. Supports hex, base64, base64url, utf8, latin1, binary, and ascii. The JS-side ab2str/binaryLikeToArrayBuffer now route through the native path for supported encodings, falling back to CraftzdogBuffer for others. Includes benchmark suite comparing native vs polyfill performance.
1 parent 5ea5518 commit f309da0

8 files changed

Lines changed: 448 additions & 2 deletions

File tree

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import {
2+
bufferToString,
3+
stringToBuffer,
4+
ab2str_old,
5+
stringToBuffer_old,
6+
} from 'react-native-quick-crypto';
7+
import type { BenchFn } from '../../types/benchmarks';
8+
import { Bench } from 'tinybench';
9+
10+
// Generate test data
11+
const generate1MB = (): ArrayBuffer => {
12+
const bytes = new Uint8Array(1024 * 1024);
13+
for (let i = 0; i < bytes.length; i++) {
14+
bytes[i] = i & 0xff;
15+
}
16+
return bytes.buffer as ArrayBuffer;
17+
};
18+
19+
const ab1MB = generate1MB();
20+
const ab32B = new Uint8Array(32).buffer as ArrayBuffer; // typical hash digest size
21+
// Fill 32B with non-zero data
22+
new Uint8Array(ab32B).set([
23+
0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0x01, 0x23, 0x45, 0x67, 0x89,
24+
0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0x11, 0x22,
25+
0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
26+
]);
27+
28+
// Pre-encode strings for decode benchmarks
29+
const hex1MB = bufferToString(ab1MB, 'hex');
30+
const base64_1MB = bufferToString(ab1MB, 'base64');
31+
const hex32B = bufferToString(ab32B, 'hex');
32+
const base64_32B = bufferToString(ab32B, 'base64');
33+
34+
// --- Encode benchmarks (ArrayBuffer → string) ---
35+
36+
const encode_hex_32b: BenchFn = () => {
37+
const bench = new Bench({
38+
name: 'hex encode 32B (digest size)',
39+
iterations: 100,
40+
warmupIterations: 10,
41+
time: 0,
42+
});
43+
44+
bench
45+
.add('rnqc', () => {
46+
bufferToString(ab32B, 'hex');
47+
})
48+
.add('Buffer polyfill', () => {
49+
ab2str_old(ab32B, 'hex');
50+
});
51+
52+
return bench;
53+
};
54+
55+
const encode_hex_1mb: BenchFn = () => {
56+
const bench = new Bench({
57+
name: 'hex encode 1MB',
58+
iterations: 10,
59+
warmupIterations: 2,
60+
time: 0,
61+
});
62+
63+
bench
64+
.add('rnqc', () => {
65+
bufferToString(ab1MB, 'hex');
66+
})
67+
.add('Buffer polyfill', () => {
68+
ab2str_old(ab1MB, 'hex');
69+
});
70+
71+
return bench;
72+
};
73+
74+
const encode_base64_32b: BenchFn = () => {
75+
const bench = new Bench({
76+
name: 'base64 encode 32B (digest size)',
77+
iterations: 100,
78+
warmupIterations: 10,
79+
time: 0,
80+
});
81+
82+
bench
83+
.add('rnqc', () => {
84+
bufferToString(ab32B, 'base64');
85+
})
86+
.add('Buffer polyfill', () => {
87+
ab2str_old(ab32B, 'base64');
88+
});
89+
90+
return bench;
91+
};
92+
93+
const encode_base64_1mb: BenchFn = () => {
94+
const bench = new Bench({
95+
name: 'base64 encode 1MB',
96+
iterations: 10,
97+
warmupIterations: 2,
98+
time: 0,
99+
});
100+
101+
bench
102+
.add('rnqc', () => {
103+
bufferToString(ab1MB, 'base64');
104+
})
105+
.add('Buffer polyfill', () => {
106+
ab2str_old(ab1MB, 'base64');
107+
});
108+
109+
return bench;
110+
};
111+
112+
// --- Decode benchmarks (string → ArrayBuffer) ---
113+
114+
const decode_hex_32b: BenchFn = () => {
115+
const bench = new Bench({
116+
name: 'hex decode 32B',
117+
iterations: 100,
118+
warmupIterations: 10,
119+
time: 0,
120+
});
121+
122+
bench
123+
.add('rnqc', () => {
124+
stringToBuffer(hex32B, 'hex');
125+
})
126+
.add('Buffer polyfill', () => {
127+
stringToBuffer_old(hex32B, 'hex');
128+
});
129+
130+
return bench;
131+
};
132+
133+
const decode_hex_1mb: BenchFn = () => {
134+
const bench = new Bench({
135+
name: 'hex decode 1MB',
136+
iterations: 10,
137+
warmupIterations: 2,
138+
time: 0,
139+
});
140+
141+
bench
142+
.add('rnqc', () => {
143+
stringToBuffer(hex1MB, 'hex');
144+
})
145+
.add('Buffer polyfill', () => {
146+
stringToBuffer_old(hex1MB, 'hex');
147+
});
148+
149+
return bench;
150+
};
151+
152+
const decode_base64_32b: BenchFn = () => {
153+
const bench = new Bench({
154+
name: 'base64 decode 32B',
155+
iterations: 100,
156+
warmupIterations: 10,
157+
time: 0,
158+
});
159+
160+
bench
161+
.add('rnqc', () => {
162+
stringToBuffer(base64_32B, 'base64');
163+
})
164+
.add('Buffer polyfill', () => {
165+
stringToBuffer_old(base64_32B, 'base64');
166+
});
167+
168+
return bench;
169+
};
170+
171+
const decode_base64_1mb: BenchFn = () => {
172+
const bench = new Bench({
173+
name: 'base64 decode 1MB',
174+
iterations: 10,
175+
warmupIterations: 2,
176+
time: 0,
177+
});
178+
179+
bench
180+
.add('rnqc', () => {
181+
stringToBuffer(base64_1MB, 'base64');
182+
})
183+
.add('Buffer polyfill', () => {
184+
stringToBuffer_old(base64_1MB, 'base64');
185+
});
186+
187+
return bench;
188+
};
189+
190+
export default [
191+
encode_hex_32b,
192+
encode_hex_1mb,
193+
encode_base64_32b,
194+
encode_base64_1mb,
195+
decode_hex_32b,
196+
decode_hex_1mb,
197+
decode_base64_32b,
198+
decode_base64_1mb,
199+
];

example/src/hooks/useBenchmarks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import ecdh from '../benchmarks/ecdh/ecdh';
1111
import dh from '../benchmarks/dh/dh';
1212
import random from '../benchmarks/random/randomBytes';
1313
import scrypt from '../benchmarks/scrypt/scrypt';
14+
import encoding from '../benchmarks/encoding/encoding';
1415
import xsalsa20 from '../benchmarks/cipher/xsalsa20';
1516

1617
export const useBenchmarks = (): [
@@ -43,6 +44,11 @@ export const useBenchmarks = (): [
4344
}),
4445
);
4546
newSuites.push(new BenchmarkSuite('scrypt', scrypt));
47+
newSuites.push(
48+
new BenchmarkSuite('encoding', encoding, {
49+
'Buffer polyfill': 'old CraftzdogBuffer.from/toString path',
50+
}),
51+
);
4652
setSuites(newSuites);
4753
}, []);
4854

0 commit comments

Comments
 (0)