Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the Engine abstraction. #157

Merged
merged 6 commits into from Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .circleci/config.yml
Expand Up @@ -12,7 +12,7 @@ workflows:
# be easier on the CI hosts since presumably those fat lower layers will already be cached, and
# therefore faster than a minimal, customized alpine.
# MSRV
'rust:1.42.0',
'rust:1.47.0',
# stable
'rust:latest',
'rustlang/rust:nightly'
Expand Down Expand Up @@ -67,8 +67,8 @@ jobs:
command: |
if [[ '<< parameters.rust_img >>' = 'rustlang/rust:nightly' ]]
then
cargo +nightly install cargo-fuzz
cargo fuzz list | xargs -L 1 -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1
cargo install cargo-fuzz
cargo fuzz list | xargs -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1
fi

- save_cache:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -10,5 +10,5 @@ main.rs
*.iml

# `perf record` files
perf.data*
/*perf.data*
/tmp
7 changes: 7 additions & 0 deletions Cargo.toml
Expand Up @@ -19,6 +19,9 @@ harness = false
criterion = "0.3.4"
rand = "0.6.1"
structopt = "0.3.21"
# test fixtures for engine tests
rstest = "0.11.0"
rstest_reuse = "0.1.3"

[features]
default = ["std"]
Expand All @@ -28,3 +31,7 @@ std = []
[profile.bench]
# Useful for better disassembly when using `perf record` and `perf annotate`
debug = true

[profile.test]
# Faster tests save much more than the increase in compilation time
opt-level = 3
10 changes: 2 additions & 8 deletions README.md
Expand Up @@ -9,7 +9,7 @@ Made with CLion. Thanks to JetBrains for supporting open source!

It's base64. What more could anyone want?

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.
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.

Example
---
Expand All @@ -33,7 +33,7 @@ See the [docs](https://docs.rs/base64) for all the details.
Rust version compatibility
---

The minimum required Rust version is 1.36.0.
The minimum required Rust version is 1.47.0.

# Contributing

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

Decoding is aided by some pre-calculated tables, which are generated by:

```bash
cargo run --example make_tables > src/tables.rs.tmp && mv src/tables.rs.tmp src/tables.rs
```

no_std
---

Expand Down
14 changes: 10 additions & 4 deletions RELEASE-NOTES.md
@@ -1,7 +1,13 @@
# 0.14.0

- MSRV is now 1.42.0
- DecoderReader now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer.
# 0.20.0

- Extended the `Config` concept into the `Engine` abstraction, allowing the user to pick different encoding / decoding implementations.
- What was formerly the only algorithm is now the `FastPortable` engine, so named because it's portable (works on any CPU) and relatively fast.
- 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.
- Standard base64 per the RFC is available via `DEFAULT_ENGINE`. To use different alphabets or other settings (padding, etc), create your own engine instance.
- `CharacterSet` is now `Alphabet` (per the RFC), and allows creating custom alphabets. The corresponding tables that were previously code-generated are now built dynamically.
- Since there are already multiple breaking changes, various functions are renamed to be more consistent and discoverable.
- MSRV is now 1.47.0 to allow various things to use `const fn`.
- `DecoderReader` now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer.

# 0.13.0

Expand Down
25 changes: 12 additions & 13 deletions benches/benchmarks.rs
Expand Up @@ -5,16 +5,15 @@ extern crate rand;

use base64::display;
use base64::{
decode, decode_config_buf, decode_config_slice, encode, encode_config_buf, encode_config_slice,
write, Config,
decode, decode_engine_slice, decode_engine_vec, encode, encode_engine_slice,
encode_engine_string, write,
};

use base64::engine::DEFAULT_ENGINE;
use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
use rand::{FromEntropy, Rng};
use std::io::{self, Read, Write};

const TEST_CONFIG: Config = base64::STANDARD;

fn do_decode_bench(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
fill(&mut v);
Expand All @@ -33,7 +32,7 @@ fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {

let mut buf = Vec::new();
b.iter(|| {
decode_config_buf(&encoded, TEST_CONFIG, &mut buf).unwrap();
decode_engine_vec(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
black_box(&buf);
buf.clear();
});
Expand All @@ -47,7 +46,7 @@ fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
let mut buf = Vec::new();
buf.resize(size, 0);
b.iter(|| {
decode_config_slice(&encoded, TEST_CONFIG, &mut buf).unwrap();
decode_engine_slice(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
black_box(&buf);
});
}
Expand All @@ -63,7 +62,7 @@ fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {

b.iter(|| {
let mut cursor = io::Cursor::new(&encoded[..]);
let mut decoder = base64::read::DecoderReader::new(&mut cursor, TEST_CONFIG);
let mut decoder = base64::read::DecoderReader::from(&mut cursor, &DEFAULT_ENGINE);
decoder.read_to_end(&mut buf).unwrap();
buf.clear();
black_box(&buf);
Expand All @@ -83,7 +82,7 @@ fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
let mut v: Vec<u8> = Vec::with_capacity(size);
fill(&mut v);
b.iter(|| {
let e = format!("{}", display::Base64Display::with_config(&v, TEST_CONFIG));
let e = format!("{}", display::Base64Display::from(&v, &DEFAULT_ENGINE));
black_box(&e);
});
}
Expand All @@ -93,7 +92,7 @@ fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
fill(&mut v);
let mut buf = String::new();
b.iter(|| {
encode_config_buf(&v, TEST_CONFIG, &mut buf);
encode_engine_string(&v, &mut buf, &DEFAULT_ENGINE);
buf.clear();
});
}
Expand All @@ -105,7 +104,7 @@ fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
// conservative estimate of encoded size
buf.resize(v.len() * 2, 0);
b.iter(|| {
encode_config_slice(&v, TEST_CONFIG, &mut buf);
encode_engine_slice(&v, &mut buf, &DEFAULT_ENGINE);
});
}

Expand All @@ -117,7 +116,7 @@ fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
buf.reserve(size * 2);
b.iter(|| {
buf.clear();
let mut stream_enc = write::EncoderWriter::new(&mut buf, TEST_CONFIG);
let mut stream_enc = write::EncoderWriter::from(&mut buf, &DEFAULT_ENGINE);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
});
Expand All @@ -128,7 +127,7 @@ fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
fill(&mut v);

b.iter(|| {
let mut stream_enc = write::EncoderStringWriter::new(TEST_CONFIG);
let mut stream_enc = write::EncoderStringWriter::from(&DEFAULT_ENGINE);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
let _ = stream_enc.into_inner();
Expand All @@ -142,7 +141,7 @@ fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
let mut buf = String::new();
b.iter(|| {
buf.clear();
let mut stream_enc = write::EncoderStringWriter::from(&mut buf, TEST_CONFIG);
let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &DEFAULT_ENGINE);
stream_enc.write_all(&v).unwrap();
stream_enc.flush().unwrap();
let _ = stream_enc.into_inner();
Expand Down
50 changes: 25 additions & 25 deletions examples/base64.rs
Expand Up @@ -4,37 +4,28 @@ use std::path::PathBuf;
use std::process;
use std::str::FromStr;

use base64::{read, write};
use base64::{alphabet, engine, read, write};
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
enum CharacterSet {
enum Alphabet {
Standard,
UrlSafe,
}

impl Default for CharacterSet {
impl Default for Alphabet {
fn default() -> Self {
CharacterSet::Standard
Alphabet::Standard
}
}

impl Into<base64::Config> for CharacterSet {
fn into(self) -> base64::Config {
match self {
CharacterSet::Standard => base64::STANDARD,
CharacterSet::UrlSafe => base64::URL_SAFE,
}
}
}

impl FromStr for CharacterSet {
impl FromStr for Alphabet {
type Err = String;
fn from_str(s: &str) -> Result<CharacterSet, String> {
fn from_str(s: &str) -> Result<Alphabet, String> {
match s {
"standard" => Ok(CharacterSet::Standard),
"urlsafe" => Ok(CharacterSet::UrlSafe),
_ => Err(format!("charset '{}' unrecognized", s)),
"standard" => Ok(Alphabet::Standard),
"urlsafe" => Ok(Alphabet::UrlSafe),
_ => Err(format!("alphabet '{}' unrecognized", s)),
}
}
}
Expand All @@ -45,10 +36,10 @@ struct Opt {
/// decode data
#[structopt(short = "d", long = "decode")]
decode: bool,
/// The character set to choose. Defaults to the standard base64 character set.
/// Supported character sets include "standard" and "urlsafe".
#[structopt(long = "charset")]
charset: Option<CharacterSet>,
/// The alphabet to choose. Defaults to the standard base64 alphabet.
/// Supported alphabets include "standard" and "urlsafe".
#[structopt(long = "alphabet")]
alphabet: Option<Alphabet>,
/// The file to encode/decode.
#[structopt(parse(from_os_str))]
file: Option<PathBuf>,
Expand All @@ -68,14 +59,23 @@ fn main() {
}
Some(f) => Box::new(File::open(f).unwrap()),
};
let config = opt.charset.unwrap_or_default().into();

let alphabet = opt.alphabet.unwrap_or_default();
let engine = engine::fast_portable::FastPortable::from(
&match alphabet {
Alphabet::Standard => alphabet::STANDARD,
Alphabet::UrlSafe => alphabet::URL_SAFE,
},
engine::fast_portable::PAD,
);

let stdout = io::stdout();
let mut stdout = stdout.lock();
let r = if opt.decode {
let mut decoder = read::DecoderReader::new(&mut input, config);
let mut decoder = read::DecoderReader::from(&mut input, &engine);
io::copy(&mut decoder, &mut stdout)
} else {
let mut encoder = write::EncoderWriter::new(&mut stdout, config);
let mut encoder = write::EncoderWriter::from(&mut stdout, &engine);
io::copy(&mut input, &mut encoder)
};
if let Err(e) = r {
Expand Down