Skip to content

Commit 9a30242

Browse files
authored
feat: native C++ encoding/decoding for hex, base64, and more (#967)
1 parent 5ea5518 commit 9a30242

10 files changed

Lines changed: 702 additions & 2 deletions

File tree

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

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

example/src/hooks/useTestsList.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import '../tests/subtle/supports';
4444
import '../tests/subtle/getPublicKey';
4545
import '../tests/subtle/wrap_unwrap';
4646
import '../tests/utils/utils_tests';
47+
import '../tests/utils/encoding_tests';
4748
import '../tests/x509/x509_tests';
4849

4950
export const useTestsList = (): [

0 commit comments

Comments
 (0)