Skip to content

Commit 78619fb

Browse files
Introduce the Engine abstraction.
This will allow users to plug different implementations in to the rest of the API. While I'm touching almost everything and breaking backwards compatibility, a bunch of renaming and cleanup was also done.
1 parent a21202e commit 78619fb

34 files changed

+2932
-3721
lines changed

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ sudo: required
55

66
matrix:
77
include:
8-
- rust: 1.36.0
9-
# cfg(doctest) is experimental in 1.39 but ignored with 1.34.0, and that snuck in when 1.39.0 wasn't tested
10-
- rust: 1.39.0
8+
- rust: 1.47.0
119
- rust: stable
1210
- rust: beta
1311
- rust: nightly
@@ -37,7 +35,8 @@ script:
3735
- cargo build --all-targets
3836
- cargo build --verbose $TARGET --no-default-features
3937
- cargo build --verbose $TARGET $FEATURES
40-
- 'if [[ -z "$TARGET" ]]; then cargo test --verbose; fi'
38+
# tests are cpu intensive enough that running them in release mode is much faster
39+
- 'if [[ -z "$TARGET" ]]; then cargo test --release --verbose; fi'
4140
- 'if [[ -z "$TARGET" ]]; then cargo doc --verbose; fi'
4241
# run for just a second to confirm that it can build and run ok
4342
- 'if [[ "$TRAVIS_RUST_VERSION" = nightly ]]; then cargo fuzz list | xargs -L 1 -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1; fi'

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ name = "benchmarks"
1616
harness = false
1717

1818
[dev-dependencies]
19-
# 0.3.3 requires rust 1.36.0 for stable copied()
2019
criterion = "0.3.4"
2120
rand = "0.6.1"
2221
structopt = "0.3.21"
22+
# test fixtures for engine tests
23+
rstest = "0.6.4"
24+
rstest_reuse = "0.1.1"
2325

2426
[features]
2527
default = ["std"]

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Made with CLion. Thanks to JetBrains for supporting open source!
99

1010
It's base64. What more could anyone want?
1111

12-
This library's goals are to be *correct* and *fast*. It's thoroughly tested and widely used. It exposes functionality at multiple levels of abstraction so you can choose the level of convenience vs performance that you want, e.g. `decode_config_slice` decodes into an existing `&mut [u8]` and is pretty fast (2.6GiB/s for a 3 KiB input), whereas `decode_config` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is slower (although still fast enough for almost any purpose) at 2.1 GiB/s.
12+
This library's goals are to be *correct* and *fast*. It's thoroughly tested and widely used. It exposes functionality at multiple levels of abstraction so you can choose the level of convenience vs performance that you want, e.g. `decode_engine_slice` decodes into an existing `&mut [u8]` and is pretty fast (2.6GiB/s for a 3 KiB input), whereas `decode_engine` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is slower (although still fast enough for almost any purpose) at 2.1 GiB/s.
1313

1414
Example
1515
---
@@ -33,7 +33,7 @@ See the [docs](https://docs.rs/base64) for all the details.
3333
Rust version compatibility
3434
---
3535

36-
The minimum required Rust version is 1.36.0.
36+
The minimum required Rust version is 1.47.0.
3737

3838
# Contributing
3939

@@ -50,12 +50,6 @@ Benchmarks are in `benches/`. Running them requires nightly rust, but `rustup` m
5050
rustup run nightly cargo bench
5151
```
5252

53-
Decoding is aided by some pre-calculated tables, which are generated by:
54-
55-
```bash
56-
cargo run --example make_tables > src/tables.rs.tmp && mv src/tables.rs.tmp src/tables.rs
57-
```
58-
5953
no_std
6054
---
6155

RELEASE-NOTES.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1-
# 0.14.0
2-
3-
- MSRV is now 1.36.0
1+
# 0.20.0
2+
3+
- Extended the `Config` concept into the `Engine` abstraction, allowing the user to pick different encoding / decoding implementations.
4+
- What was formerly the only algorithm is now the `FastPortable` engine, so named because it's portable (works on any CPU) and relatively fast.
5+
- This opens the door to a portable constant-time implementation ([#153](https://github.com/marshallpierce/rust-base64/pull/153), presumably `ConstantTimePortable`?) for security-sensitive applications that need side-channel resistance, and CPU-specific SIMD implementations for more speed.
6+
- Standard base64 per the RFC is available via `DEFAULT_ENGINE`. To use different alphabets or other settings (padding, etc), create your own engine instance.
7+
- `CharacterSet` is now `Alphabet` (per the RFC), and allows creating custom alphabets. The corresponding tables that were previously code-generated are now built dynamically.
8+
- Since there are already multiple breaking changes, various functions are renamed to be more consistent and discoverable
9+
- DecoderReader now owns its delegate reader
10+
- MSRV is now 1.47.0
411

512
# 0.13.0
613

benches/benchmarks.rs

Lines changed: 53 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ extern crate rand;
55

66
use base64::display;
77
use base64::{
8-
decode, decode_config_buf, decode_config_slice, encode, encode_config_buf, encode_config_slice,
9-
write, Config,
8+
decode, decode_engine_slice, decode_engine_vec, encode, encode_engine_slice,
9+
encode_engine_string, write,
1010
};
1111

12-
use criterion::{black_box, Bencher, Criterion, Throughput, BenchmarkId};
12+
use base64::engine::DEFAULT_ENGINE;
13+
use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
1314
use rand::{FromEntropy, Rng};
1415
use std::io::{self, Read, Write};
1516

16-
const TEST_CONFIG: Config = base64::STANDARD;
17-
1817
fn do_decode_bench(b: &mut Bencher, &size: &usize) {
1918
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
2019
fill(&mut v);
@@ -33,7 +32,7 @@ fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
3332

3433
let mut buf = Vec::new();
3534
b.iter(|| {
36-
decode_config_buf(&encoded, TEST_CONFIG, &mut buf).unwrap();
35+
decode_engine_vec(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
3736
black_box(&buf);
3837
buf.clear();
3938
});
@@ -47,7 +46,7 @@ fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
4746
let mut buf = Vec::new();
4847
buf.resize(size, 0);
4948
b.iter(|| {
50-
decode_config_slice(&encoded, TEST_CONFIG, &mut buf).unwrap();
49+
decode_engine_slice(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
5150
black_box(&buf);
5251
});
5352
}
@@ -63,7 +62,7 @@ fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
6362

6463
b.iter(|| {
6564
let mut cursor = io::Cursor::new(&encoded[..]);
66-
let mut decoder = base64::read::DecoderReader::new(&mut cursor, TEST_CONFIG);
65+
let mut decoder = base64::read::DecoderReader::from(&mut cursor, &DEFAULT_ENGINE);
6766
decoder.read_to_end(&mut buf).unwrap();
6867
buf.clear();
6968
black_box(&buf);
@@ -83,7 +82,7 @@ fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
8382
let mut v: Vec<u8> = Vec::with_capacity(size);
8483
fill(&mut v);
8584
b.iter(|| {
86-
let e = format!("{}", display::Base64Display::with_config(&v, TEST_CONFIG));
85+
let e = format!("{}", display::Base64Display::from(&v, &DEFAULT_ENGINE));
8786
black_box(&e);
8887
});
8988
}
@@ -93,7 +92,7 @@ fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
9392
fill(&mut v);
9493
let mut buf = String::new();
9594
b.iter(|| {
96-
encode_config_buf(&v, TEST_CONFIG, &mut buf);
95+
encode_engine_string(&v, &mut buf, &DEFAULT_ENGINE);
9796
buf.clear();
9897
});
9998
}
@@ -105,7 +104,7 @@ fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
105104
// conservative estimate of encoded size
106105
buf.resize(v.len() * 2, 0);
107106
b.iter(|| {
108-
encode_config_slice(&v, TEST_CONFIG, &mut buf);
107+
encode_engine_slice(&v, &mut buf, &DEFAULT_ENGINE);
109108
});
110109
}
111110

@@ -117,7 +116,7 @@ fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
117116
buf.reserve(size * 2);
118117
b.iter(|| {
119118
buf.clear();
120-
let mut stream_enc = write::EncoderWriter::new(&mut buf, TEST_CONFIG);
119+
let mut stream_enc = write::EncoderWriter::from(&mut buf, &DEFAULT_ENGINE);
121120
stream_enc.write_all(&v).unwrap();
122121
stream_enc.flush().unwrap();
123122
});
@@ -128,7 +127,7 @@ fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
128127
fill(&mut v);
129128

130129
b.iter(|| {
131-
let mut stream_enc = write::EncoderStringWriter::new(TEST_CONFIG);
130+
let mut stream_enc = write::EncoderStringWriter::from(&DEFAULT_ENGINE);
132131
stream_enc.write_all(&v).unwrap();
133132
stream_enc.flush().unwrap();
134133
let _ = stream_enc.into_inner();
@@ -142,7 +141,7 @@ fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
142141
let mut buf = String::new();
143142
b.iter(|| {
144143
buf.clear();
145-
let mut stream_enc = write::EncoderStringWriter::from(&mut buf, TEST_CONFIG);
144+
let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &DEFAULT_ENGINE);
146145
stream_enc.write_all(&v).unwrap();
147146
stream_enc.flush().unwrap();
148147
let _ = stream_enc.into_inner();
@@ -174,11 +173,31 @@ fn encode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
174173
group
175174
.throughput(Throughput::Bytes(*size as u64))
176175
.bench_with_input(BenchmarkId::new("encode", size), size, do_encode_bench)
177-
.bench_with_input(BenchmarkId::new("encode_display", size), size, do_encode_bench_display)
178-
.bench_with_input(BenchmarkId::new("encode_reuse_buf", size), size, do_encode_bench_reuse_buf)
179-
.bench_with_input(BenchmarkId::new("encode_slice", size), size, do_encode_bench_slice)
180-
.bench_with_input(BenchmarkId::new("encode_reuse_buf_stream", size), size, do_encode_bench_stream)
181-
.bench_with_input(BenchmarkId::new("encode_string_stream", size), size, do_encode_bench_string_stream)
176+
.bench_with_input(
177+
BenchmarkId::new("encode_display", size),
178+
size,
179+
do_encode_bench_display,
180+
)
181+
.bench_with_input(
182+
BenchmarkId::new("encode_reuse_buf", size),
183+
size,
184+
do_encode_bench_reuse_buf,
185+
)
186+
.bench_with_input(
187+
BenchmarkId::new("encode_slice", size),
188+
size,
189+
do_encode_bench_slice,
190+
)
191+
.bench_with_input(
192+
BenchmarkId::new("encode_reuse_buf_stream", size),
193+
size,
194+
do_encode_bench_stream,
195+
)
196+
.bench_with_input(
197+
BenchmarkId::new("encode_string_stream", size),
198+
size,
199+
do_encode_bench_string_stream,
200+
)
182201
.bench_with_input(
183202
BenchmarkId::new("encode_string_reuse_buf_stream", size),
184203
size,
@@ -198,9 +217,21 @@ fn decode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
198217
.measurement_time(std::time::Duration::from_secs(3))
199218
.throughput(Throughput::Bytes(*size as u64))
200219
.bench_with_input(BenchmarkId::new("decode", size), size, do_decode_bench)
201-
.bench_with_input(BenchmarkId::new("decode_reuse_buf", size), size, do_decode_bench_reuse_buf)
202-
.bench_with_input(BenchmarkId::new("decode_slice", size), size, do_decode_bench_slice)
203-
.bench_with_input(BenchmarkId::new("decode_stream", size), size, do_decode_bench_stream);
220+
.bench_with_input(
221+
BenchmarkId::new("decode_reuse_buf", size),
222+
size,
223+
do_decode_bench_reuse_buf,
224+
)
225+
.bench_with_input(
226+
BenchmarkId::new("decode_slice", size),
227+
size,
228+
do_decode_bench_slice,
229+
)
230+
.bench_with_input(
231+
BenchmarkId::new("decode_stream", size),
232+
size,
233+
do_decode_bench_stream,
234+
);
204235
}
205236

206237
group.finish();

examples/base64.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,28 @@ use std::path::PathBuf;
44
use std::process;
55
use std::str::FromStr;
66

7-
use base64::{read, write};
7+
use base64::{alphabet, engine, read, write};
88
use structopt::StructOpt;
99

1010
#[derive(Debug, StructOpt)]
11-
enum CharacterSet {
11+
enum Alphabet {
1212
Standard,
1313
UrlSafe,
1414
}
1515

16-
impl Default for CharacterSet {
16+
impl Default for Alphabet {
1717
fn default() -> Self {
18-
CharacterSet::Standard
18+
Alphabet::Standard
1919
}
2020
}
2121

22-
impl Into<base64::Config> for CharacterSet {
23-
fn into(self) -> base64::Config {
24-
match self {
25-
CharacterSet::Standard => base64::STANDARD,
26-
CharacterSet::UrlSafe => base64::URL_SAFE,
27-
}
28-
}
29-
}
30-
31-
impl FromStr for CharacterSet {
22+
impl FromStr for Alphabet {
3223
type Err = String;
33-
fn from_str(s: &str) -> Result<CharacterSet, String> {
24+
fn from_str(s: &str) -> Result<Alphabet, String> {
3425
match s {
35-
"standard" => Ok(CharacterSet::Standard),
36-
"urlsafe" => Ok(CharacterSet::UrlSafe),
37-
_ => Err(format!("charset '{}' unrecognized", s)),
26+
"standard" => Ok(Alphabet::Standard),
27+
"urlsafe" => Ok(Alphabet::UrlSafe),
28+
_ => Err(format!("alphabet '{}' unrecognized", s)),
3829
}
3930
}
4031
}
@@ -45,10 +36,10 @@ struct Opt {
4536
/// decode data
4637
#[structopt(short = "d", long = "decode")]
4738
decode: bool,
48-
/// The character set to choose. Defaults to the standard base64 character set.
49-
/// Supported character sets include "standard" and "urlsafe".
50-
#[structopt(long = "charset")]
51-
charset: Option<CharacterSet>,
39+
/// The alphabet to choose. Defaults to the standard base64 alphabet.
40+
/// Supported alphabets include "standard" and "urlsafe".
41+
#[structopt(long = "alphabet")]
42+
alphabet: Option<Alphabet>,
5243
/// The file to encode/decode.
5344
#[structopt(parse(from_os_str))]
5445
file: Option<PathBuf>,
@@ -68,14 +59,23 @@ fn main() {
6859
}
6960
Some(f) => Box::new(File::open(f).unwrap()),
7061
};
71-
let config = opt.charset.unwrap_or_default().into();
62+
63+
let alphabet = opt.alphabet.unwrap_or_default();
64+
let engine = engine::fast_portable::FastPortable::from(
65+
&match alphabet {
66+
Alphabet::Standard => alphabet::STANDARD,
67+
Alphabet::UrlSafe => alphabet::URL_SAFE,
68+
},
69+
engine::fast_portable::PAD,
70+
);
71+
7272
let stdout = io::stdout();
7373
let mut stdout = stdout.lock();
7474
let r = if opt.decode {
75-
let mut decoder = read::DecoderReader::new(&mut input, config);
75+
let mut decoder = read::DecoderReader::from(&mut input, &engine);
7676
io::copy(&mut decoder, &mut stdout)
7777
} else {
78-
let mut encoder = write::EncoderWriter::new(&mut stdout, config);
78+
let mut encoder = write::EncoderWriter::from(&mut stdout, &engine);
7979
io::copy(&mut input, &mut encoder)
8080
};
8181
if let Err(e) = r {

0 commit comments

Comments
 (0)