Skip to content

Commit e3525bb

Browse files
Merge pull request marshallpierce#157 from marshallpierce/mp/engine
Introduce the `Engine` abstraction.
2 parents 5df2332 + 3155ca7 commit e3525bb

35 files changed

+3074
-3713
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ workflows:
1212
# be easier on the CI hosts since presumably those fat lower layers will already be cached, and
1313
# therefore faster than a minimal, customized alpine.
1414
# MSRV
15-
'rust:1.42.0',
15+
'rust:1.47.0',
1616
# stable
1717
'rust:latest',
1818
'rustlang/rust:nightly'
@@ -67,8 +67,8 @@ jobs:
6767
command: |
6868
if [[ '<< parameters.rust_img >>' = 'rustlang/rust:nightly' ]]
6969
then
70-
cargo +nightly install cargo-fuzz
71-
cargo fuzz list | xargs -L 1 -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1
70+
cargo install cargo-fuzz
71+
cargo fuzz list | xargs -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1
7272
fi
7373
7474
- save_cache:

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ main.rs
1010
*.iml
1111

1212
# `perf record` files
13-
perf.data*
13+
/*perf.data*
1414
/tmp

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ harness = false
1919
criterion = "0.3.4"
2020
rand = "0.6.1"
2121
structopt = "0.3.21"
22+
# test fixtures for engine tests
23+
rstest = "0.11.0"
24+
rstest_reuse = "0.1.3"
2225

2326
[features]
2427
default = ["std"]
@@ -28,3 +31,7 @@ std = []
2831
[profile.bench]
2932
# Useful for better disassembly when using `perf record` and `perf annotate`
3033
debug = true
34+
35+
[profile.test]
36+
# Faster tests save much more than the increase in compilation time
37+
opt-level = 3

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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
# 0.14.0
2-
3-
- MSRV is now 1.42.0
4-
- DecoderReader now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer.
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+
- MSRV is now 1.47.0 to allow various things to use `const fn`.
10+
- `DecoderReader` now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer.
511

612
# 0.13.0
713

benches/benchmarks.rs

Lines changed: 12 additions & 13 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 base64::engine::DEFAULT_ENGINE;
1213
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();

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)