Skip to content

Commit 8416468

Browse files
authored
Add decode_in_place and benchmarks to b64ct (#206)
1 parent 811883a commit 8416468

File tree

3 files changed

+108
-3
lines changed

3 files changed

+108
-3
lines changed

b64ct/benches/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#![feature(test)]
2+
extern crate test;
3+
use test::Bencher;
4+
5+
const B64_LEN: usize = 100_002;
6+
const RAW_LEN: usize = (3 * B64_LEN) / 4;
7+
8+
#[inline(never)]
9+
fn get_raw_data() -> Vec<u8> {
10+
(0..RAW_LEN).map(|i| i as u8).collect()
11+
}
12+
13+
#[inline(never)]
14+
fn get_b64_data() -> String {
15+
(0..B64_LEN)
16+
.map(|i| match (i % 64) as u8 {
17+
v @ 0..=25 => (v + 'A' as u8) as char,
18+
v @ 26..=51 => (v - 26 + 'a' as u8) as char,
19+
v @ 52..=61 => (v - 52 + '0' as u8) as char,
20+
62 => '+',
21+
_ => '/',
22+
})
23+
.collect()
24+
}
25+
26+
#[bench]
27+
fn decode_bench(b: &mut Bencher) {
28+
let b64_data = get_b64_data();
29+
let mut buf = get_raw_data();
30+
b.iter(|| {
31+
let out = b64ct::decode(&b64_data, &mut buf).unwrap();
32+
test::black_box(out);
33+
});
34+
b.bytes = RAW_LEN as u64;
35+
}
36+
37+
#[bench]
38+
fn decode_in_place_bench(b: &mut Bencher) {
39+
let mut b64_data = get_b64_data().into_bytes();
40+
b.iter(|| {
41+
// since it works on the same buffer over and over,
42+
// almost always `out` will be an error
43+
let out = b64ct::decode_in_place(&mut b64_data);
44+
let _ = test::black_box(out);
45+
});
46+
b.bytes = RAW_LEN as u64;
47+
}
48+
49+
#[bench]
50+
fn encode_bench(b: &mut Bencher) {
51+
let mut buf = get_b64_data().into_bytes();
52+
let raw_data = get_raw_data();
53+
b.iter(|| {
54+
let out = b64ct::encode(&raw_data, &mut buf).unwrap();
55+
test::black_box(out);
56+
});
57+
b.bytes = RAW_LEN as u64;
58+
}

b64ct/src/lib.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,40 @@ pub fn decode<'a>(src: &str, dst: &'a mut [u8]) -> Result<&'a [u8], Error> {
123123
}
124124
}
125125

126+
/// Decode B64-encoded string in-place.
127+
pub fn decode_in_place(buf: &mut [u8]) -> Result<&[u8], InvalidEncodingError> {
128+
// TODO: make panic-free
129+
let mut err: isize = 0;
130+
let mut tmp_in = [0u8; 4];
131+
let mut tmp_out = [0u8; 3];
132+
133+
let full_chunks = buf.len() / 4;
134+
135+
for chunk in 0..full_chunks {
136+
tmp_in.copy_from_slice(&buf[4 * chunk..][..4]);
137+
err |= decode_3bytes(&tmp_in, &mut tmp_out);
138+
buf[3 * chunk..][..3].copy_from_slice(&tmp_out);
139+
}
140+
141+
let dlen = decoded_len_inner(buf.len());
142+
let src_rem_pos = 4 * full_chunks;
143+
let src_rem_len = buf.len() - src_rem_pos;
144+
let dst_rem_pos = 3 * full_chunks;
145+
let dst_rem_len = dlen - dst_rem_pos;
146+
147+
err |= !(src_rem_len == 0 || src_rem_len >= 2) as isize;
148+
tmp_in = [b'A'; 4];
149+
tmp_in[..src_rem_len].copy_from_slice(&buf[src_rem_pos..]);
150+
err |= decode_3bytes(&tmp_in, &mut tmp_out);
151+
buf[dst_rem_pos..][..dst_rem_len].copy_from_slice(&tmp_out[..dst_rem_len]);
152+
153+
if err == 0 {
154+
Ok(&buf[..dlen])
155+
} else {
156+
Err(InvalidEncodingError)
157+
}
158+
}
159+
126160
/// Decode a "B64"-encoded string into a byte vector.
127161
#[cfg(feature = "alloc")]
128162
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
@@ -139,7 +173,11 @@ pub fn decode_vec(input: &str) -> Result<Vec<u8>, InvalidEncodingError> {
139173

140174
/// Get the length of the output from decoding the provided "B64"-encoded input.
141175
pub const fn decoded_len(bytes: &str) -> usize {
142-
(bytes.len() * 3) / 4
176+
decoded_len_inner(bytes.len())
177+
}
178+
179+
const fn decoded_len_inner(n: usize) -> usize {
180+
(n * 3) / 4
143181
}
144182

145183
// B64 character set:

b64ct/tests/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use b64ct::{decode, decoded_len, encode, encoded_len, Error};
1+
use b64ct::{decode, decode_in_place, decoded_len, encode, encoded_len, Error};
22

33
/// "B64" test vector
44
struct TestVector {
@@ -53,10 +53,14 @@ fn decode_test_vectors() {
5353
let mut buf = [0u8; 1024];
5454

5555
for vector in TEST_VECTORS {
56-
println!("{:?}", vector.raw);
5756
let out = decode(vector.b64, &mut buf).unwrap();
5857
assert_eq!(decoded_len(vector.b64), out.len());
5958
assert_eq!(vector.raw, &out[..]);
59+
60+
let n = vector.b64.len();
61+
buf[..n].copy_from_slice(vector.b64.as_bytes());
62+
let out = decode_in_place(&mut buf[..n]).unwrap();
63+
assert_eq!(vector.raw, out);
6064
}
6165
}
6266

@@ -72,6 +76,11 @@ fn encode_and_decode_various_lengths() {
7276
// Make sure it round trips
7377
let decoded = decode(encoded, &mut outbuf).unwrap();
7478
assert_eq!(decoded, &data[..i]);
79+
80+
let elen = encode(&data[..i], &mut inbuf).unwrap().len();
81+
let buf = &mut inbuf[..elen];
82+
let decoded = decode_in_place(buf).unwrap();
83+
assert_eq!(decoded, &data[..i]);
7584
}
7685
}
7786

0 commit comments

Comments
 (0)