Skip to content

Commit 8350376

Browse files
Merge pull request marshallpierce#207 from marshallpierce/mp/api-rework
API rework
2 parents 10b73f6 + 726f784 commit 8350376

32 files changed

+1294
-938
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "base64"
3-
version = "0.20.0"
3+
version = "0.21.0-rc.1"
44
authors = ["Alice Maz <alice@alicemaz.com>", "Marshall Pierce <marshall@mpierce.org>"]
55
description = "encodes and decodes base64 as bytes or utf8"
66
repository = "https://github.com/marshallpierce/rust-base64"

README.md

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,6 @@ e.g. `decode_engine_slice` decodes into an existing `&mut [u8]` and is pretty fa
1414
whereas `decode_engine` allocates a new `Vec<u8>` and returns it, which might be more convenient in some cases, but is
1515
slower (although still fast enough for almost any purpose) at 2.1 GiB/s.
1616

17-
## Example
18-
19-
```rust
20-
use base64::{encode, decode};
21-
22-
fn main() {
23-
let a = b"hello world";
24-
let b = "aGVsbG8gd29ybGQ=";
25-
26-
assert_eq!(encode(a), b);
27-
assert_eq!(a, &decode(b).unwrap()[..]);
28-
}
29-
```
30-
3117
See the [docs](https://docs.rs/base64) for all the details.
3218

3319
## FAQ

RELEASE-NOTES.md

Lines changed: 112 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,106 @@
1+
# 0.21.0
2+
3+
(not yet released)
4+
5+
6+
## Migration
7+
8+
### Functions
9+
10+
| < 0.20 function | 0.21 equivalent |
11+
|-------------------------|-------------------------------------------------------------------------------------|
12+
| `encode()` | `engine::general_purpose::STANDARD.encode()` or `prelude::BASE64_STANDARD.encode()` |
13+
| `encode_config()` | `engine.encode()` |
14+
| `encode_config_buf()` | `engine.encode_string()` |
15+
| `encode_config_slice()` | `engine.encode_slice()` |
16+
| `decode()` | `engine::general_purpose::STANDARD.decode()` or `prelude::BASE64_STANDARD.decode()` |
17+
| `decode_config()` | `engine.decode()` |
18+
| `decode_config_buf()` | `engine.decode_vec()` |
19+
| `decode_config_slice()` | `engine.decode_slice()` |
20+
21+
The short-lived 0.20 functions were the 0.13 functions with `config` replaced with `engine`.
22+
23+
### Padding
24+
25+
If applicable, use the preset engines `engine::STANDARD`, `engine::STANDARD_NO_PAD`, `engine::URL_SAFE`,
26+
or `engine::URL_SAFE_NO_PAD`.
27+
The `NO_PAD` ones require that padding is absent when decoding, and the others require that
28+
canonical padding is present .
29+
30+
If you need the < 0.20 behavior that did not care about padding, or want to recreate < 0.20.0's predefined `Config`s
31+
precisely, see the following table.
32+
33+
| 0.13.1 Config | 0.20.0+ alphabet | `encode_padding` | `decode_padding_mode` |
34+
|-----------------|------------------|------------------|-----------------------|
35+
| STANDARD | STANDARD | true | Indifferent |
36+
| STANDARD_NO_PAD | STANDARD | false | Indifferent |
37+
| URL_SAFE | URL_SAFE | true | Indifferent |
38+
| URL_SAFE_NO_PAD | URL_SAFE | false | Indifferent |
39+
40+
# 0.21.0-rc.1
41+
42+
- Restore the ability to decode into a slice of precisely the correct length with `Engine.decode_slice_unchecked`.
43+
- Add `Engine` as a `pub use` in `prelude`.
44+
45+
# 0.21.0-beta.2
46+
47+
## Breaking changes
48+
49+
- Re-exports of preconfigured engines in `engine` are removed in favor of `base64::prelude::...` that are better suited to those who wish to `use` the entire path to a name.
50+
51+
# 0.21.0-beta.1
52+
53+
## Breaking changes
54+
55+
- `FastPortable` was only meant to be an interim name, and shouldn't have shipped in 0.20. It is now `GeneralPurpose` to
56+
make its intended usage more clear.
57+
- `GeneralPurpose` and its config are now `pub use`'d in the `engine` module for convenience.
58+
- Change a few `from()` functions to be `new()`. `from()` causes confusing compiler errors because of confusion
59+
with `From::from`, and is a little misleading because some of those invocations are not very cheap as one would
60+
usually expect from a `from` call.
61+
- `encode*` and `decode*` top level functions are now methods on `Engine`.
62+
- `DEFAULT_ENGINE` was replaced by `engine::general_purpose::STANDARD`
63+
- Predefined engine consts `engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD}`
64+
- These are `pub use`d into `engine` as well
65+
- The `*_slice` decode/encode functions now return an error instead of panicking when the output slice is too small
66+
- As part of this, there isn't now a public way to decode into a slice _exactly_ the size needed for inputs that
67+
aren't multiples of 4 tokens. If adding up to 2 bytes to always be a multiple of 3 bytes for the decode buffer is
68+
a problem, file an issue.
69+
70+
## Other changes
71+
72+
- `decoded_len_estimate()` is provided to make it easy to size decode buffers correctly.
73+
174
# 0.20.0
275

3-
### Breaking changes
76+
## Breaking changes
477

578
- Update MSRV to 1.57.0
6-
- Decoding can now either ignore padding, require correct padding, or require no padding. The default is to require correct padding.
7-
- The `NO_PAD` config now requires that padding be absent when decoding.
79+
- Decoding can now either ignore padding, require correct padding, or require no padding. The default is to require
80+
correct padding.
81+
- The `NO_PAD` config now requires that padding be absent when decoding.
882

983
## 0.20.0-alpha.1
1084

1185
### Breaking changes
12-
- Extended the `Config` concept into the `Engine` abstraction, allowing the user to pick different encoding / decoding implementations.
13-
- What was formerly the only algorithm is now the `FastPortable` engine, so named because it's portable (works on any CPU) and relatively fast.
14-
- 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.
15-
- Standard base64 per the RFC is available via `DEFAULT_ENGINE`. To use different alphabets or other settings (padding, etc), create your own engine instance.
16-
- `CharacterSet` is now `Alphabet` (per the RFC), and allows creating custom alphabets. The corresponding tables that were previously code-generated are now built dynamically.
17-
- Since there are already multiple breaking changes, various functions are renamed to be more consistent and discoverable.
86+
87+
- Extended the `Config` concept into the `Engine` abstraction, allowing the user to pick different encoding / decoding
88+
implementations.
89+
- What was formerly the only algorithm is now the `FastPortable` engine, so named because it's portable (works on
90+
any CPU) and relatively fast.
91+
- This opens the door to a portable constant-time
92+
implementation ([#153](https://github.com/marshallpierce/rust-base64/pull/153),
93+
presumably `ConstantTimePortable`?) for security-sensitive applications that need side-channel resistance, and
94+
CPU-specific SIMD implementations for more speed.
95+
- Standard base64 per the RFC is available via `DEFAULT_ENGINE`. To use different alphabets or other settings (
96+
padding, etc), create your own engine instance.
97+
- `CharacterSet` is now `Alphabet` (per the RFC), and allows creating custom alphabets. The corresponding tables that
98+
were previously code-generated are now built dynamically.
99+
- Since there are already multiple breaking changes, various functions are renamed to be more consistent and
100+
discoverable.
18101
- MSRV is now 1.47.0 to allow various things to use `const fn`.
19-
- `DecoderReader` now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer.
102+
- `DecoderReader` now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do
103+
the same with its writer.
20104
- `encoded_len` is now public so you can size encode buffers precisely.
21105

22106
# 0.13.1
@@ -28,8 +112,11 @@
28112
- Config methods are const
29113
- Added `EncoderStringWriter` to allow encoding directly to a String
30114
- `EncoderWriter` now owns its delegate writer rather than keeping a reference to it (though refs still work)
31-
- As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`, which returns `Result<W>` instead of `Result<()>`. If you were calling `finish()` explicitly, you will now need to use `let _ = foo.finish()` instead of just `foo.finish()` to avoid a warning about the unused value.
32-
- When decoding input that has both an invalid length and an invalid symbol as the last byte, `InvalidByte` will be emitted instead of `InvalidLength` to make the problem more obvious.
115+
- As a consequence, it is now possible to extract the delegate writer from an `EncoderWriter` via `finish()`, which
116+
returns `Result<W>` instead of `Result<()>`. If you were calling `finish()` explicitly, you will now need to
117+
use `let _ = foo.finish()` instead of just `foo.finish()` to avoid a warning about the unused value.
118+
- When decoding input that has both an invalid length and an invalid symbol as the last byte, `InvalidByte` will be
119+
emitted instead of `InvalidLength` to make the problem more obvious.
33120

34121
# 0.12.2
35122

@@ -47,23 +134,31 @@
47134
- A minor performance improvement in encoding
48135

49136
# 0.11.0
137+
50138
- Minimum rust version 1.34.0
51139
- `no_std` is now supported via the two new features `alloc` and `std`.
52140

53141
# 0.10.1
54142

55143
- Minimum rust version 1.27.2
56-
- Fix bug in streaming encoding ([#90](https://github.com/marshallpierce/rust-base64/pull/90)): if the underlying writer didn't write all the bytes given to it, the remaining bytes would not be retried later. See the docs on `EncoderWriter::write`.
144+
- Fix bug in streaming encoding ([#90](https://github.com/marshallpierce/rust-base64/pull/90)): if the underlying writer
145+
didn't write all the bytes given to it, the remaining bytes would not be retried later. See the docs
146+
on `EncoderWriter::write`.
57147
- Make it configurable whether or not to return an error when decoding detects excess trailing bits.
58148

59149
# 0.10.0
60150

61-
- Remove line wrapping. Line wrapping was never a great conceptual fit in this library, and other features (streaming encoding, etc) either couldn't support it or could support only special cases of it with a great increase in complexity. Line wrapping has been pulled out into a [line-wrap](https://crates.io/crates/line-wrap) crate, so it's still available if you need it.
62-
- `Base64Display` creation no longer uses a `Result` because it can't fail, which means its helper methods for common
63-
configs that `unwrap()` for you are no longer needed
151+
- Remove line wrapping. Line wrapping was never a great conceptual fit in this library, and other features (streaming
152+
encoding, etc) either couldn't support it or could support only special cases of it with a great increase in
153+
complexity. Line wrapping has been pulled out into a [line-wrap](https://crates.io/crates/line-wrap) crate, so it's
154+
still available if you need it.
155+
- `Base64Display` creation no longer uses a `Result` because it can't fail, which means its helper methods for
156+
common
157+
configs that `unwrap()` for you are no longer needed
64158
- Add a streaming encoder `Write` impl to transparently base64 as you write.
65159
- Remove the remaining `unsafe` code.
66-
- Remove whitespace stripping to simplify `no_std` support. No out of the box configs use it, and it's trivial to do yourself if needed: `filter(|b| !b" \n\t\r\x0b\x0c".contains(b)`.
160+
- Remove whitespace stripping to simplify `no_std` support. No out of the box configs use it, and it's trivial to do
161+
yourself if needed: `filter(|b| !b" \n\t\r\x0b\x0c".contains(b)`.
67162
- Detect invalid trailing symbols when decoding and return an error rather than silently ignoring them.
68163

69164
# 0.9.3

benches/benchmarks.rs

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
11
#[macro_use]
22
extern crate criterion;
33

4-
use base64::display;
54
use base64::{
6-
decode, decode_engine_slice, decode_engine_vec, encode, encode_engine_slice,
7-
encode_engine_string, write,
5+
display,
6+
engine::{general_purpose::STANDARD, Engine},
7+
write,
88
};
9-
10-
use base64::engine::DEFAULT_ENGINE;
119
use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
1210
use rand::{Rng, SeedableRng};
1311
use std::io::{self, Read, Write};
1412

1513
fn do_decode_bench(b: &mut Bencher, &size: &usize) {
1614
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
1715
fill(&mut v);
18-
let encoded = encode(&v);
16+
let encoded = STANDARD.encode(&v);
1917

2018
b.iter(|| {
21-
let orig = decode(&encoded);
19+
let orig = STANDARD.decode(&encoded);
2220
black_box(&orig);
2321
});
2422
}
2523

2624
fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
2725
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
2826
fill(&mut v);
29-
let encoded = encode(&v);
27+
let encoded = STANDARD.encode(&v);
3028

3129
let mut buf = Vec::new();
3230
b.iter(|| {
33-
decode_engine_vec(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
31+
STANDARD.decode_vec(&encoded, &mut buf).unwrap();
3432
black_box(&buf);
3533
buf.clear();
3634
});
@@ -39,28 +37,28 @@ fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
3937
fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
4038
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
4139
fill(&mut v);
42-
let encoded = encode(&v);
40+
let encoded = STANDARD.encode(&v);
4341

4442
let mut buf = Vec::new();
4543
buf.resize(size, 0);
4644
b.iter(|| {
47-
decode_engine_slice(&encoded, &mut buf, &DEFAULT_ENGINE).unwrap();
45+
STANDARD.decode_slice(&encoded, &mut buf).unwrap();
4846
black_box(&buf);
4947
});
5048
}
5149

5250
fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
5351
let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
5452
fill(&mut v);
55-
let encoded = encode(&v);
53+
let encoded = STANDARD.encode(&v);
5654

5755
let mut buf = Vec::new();
5856
buf.resize(size, 0);
5957
buf.truncate(0);
6058

6159
b.iter(|| {
6260
let mut cursor = io::Cursor::new(&encoded[..]);
63-
let mut decoder = base64::read::DecoderReader::from(&mut cursor, &DEFAULT_ENGINE);
61+
let mut decoder = base64::read::DecoderReader::new(&mut cursor, &STANDARD);
6462
decoder.read_to_end(&mut buf).unwrap();
6563
buf.clear();
6664
black_box(&buf);
@@ -71,7 +69,7 @@ fn do_encode_bench(b: &mut Bencher, &size: &usize) {
7169
let mut v: Vec<u8> = Vec::with_capacity(size);
7270
fill(&mut v);
7371
b.iter(|| {
74-
let e = encode(&v);
72+
let e = STANDARD.encode(&v);
7573
black_box(&e);
7674
});
7775
}
@@ -80,7 +78,7 @@ fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
8078
let mut v: Vec<u8> = Vec::with_capacity(size);
8179
fill(&mut v);
8280
b.iter(|| {
83-
let e = format!("{}", display::Base64Display::from(&v, &DEFAULT_ENGINE));
81+
let e = format!("{}", display::Base64Display::new(&v, &STANDARD));
8482
black_box(&e);
8583
});
8684
}
@@ -90,7 +88,7 @@ fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
9088
fill(&mut v);
9189
let mut buf = String::new();
9290
b.iter(|| {
93-
encode_engine_string(&v, &mut buf, &DEFAULT_ENGINE);
91+
STANDARD.encode_string(&v, &mut buf);
9492
buf.clear();
9593
});
9694
}
@@ -101,9 +99,7 @@ fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
10199
let mut buf = Vec::new();
102100
// conservative estimate of encoded size
103101
buf.resize(v.len() * 2, 0);
104-
b.iter(|| {
105-
encode_engine_slice(&v, &mut buf, &DEFAULT_ENGINE);
106-
});
102+
b.iter(|| STANDARD.encode_slice(&v, &mut buf).unwrap());
107103
}
108104

109105
fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
@@ -114,7 +110,7 @@ fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
114110
buf.reserve(size * 2);
115111
b.iter(|| {
116112
buf.clear();
117-
let mut stream_enc = write::EncoderWriter::from(&mut buf, &DEFAULT_ENGINE);
113+
let mut stream_enc = write::EncoderWriter::new(&mut buf, &STANDARD);
118114
stream_enc.write_all(&v).unwrap();
119115
stream_enc.flush().unwrap();
120116
});
@@ -125,7 +121,7 @@ fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
125121
fill(&mut v);
126122

127123
b.iter(|| {
128-
let mut stream_enc = write::EncoderStringWriter::from(&DEFAULT_ENGINE);
124+
let mut stream_enc = write::EncoderStringWriter::new(&STANDARD);
129125
stream_enc.write_all(&v).unwrap();
130126
stream_enc.flush().unwrap();
131127
let _ = stream_enc.into_inner();
@@ -139,7 +135,7 @@ fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
139135
let mut buf = String::new();
140136
b.iter(|| {
141137
buf.clear();
142-
let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &DEFAULT_ENGINE);
138+
let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &STANDARD);
143139
stream_enc.write_all(&v).unwrap();
144140
stream_enc.flush().unwrap();
145141
let _ = stream_enc.into_inner();

examples/base64.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,21 @@ fn main() {
6161
};
6262

6363
let alphabet = opt.alphabet.unwrap_or_default();
64-
let engine = engine::fast_portable::FastPortable::from(
64+
let engine = engine::GeneralPurpose::new(
6565
&match alphabet {
6666
Alphabet::Standard => alphabet::STANDARD,
6767
Alphabet::UrlSafe => alphabet::URL_SAFE,
6868
},
69-
engine::fast_portable::PAD,
69+
engine::general_purpose::PAD,
7070
);
7171

7272
let stdout = io::stdout();
7373
let mut stdout = stdout.lock();
7474
let r = if opt.decode {
75-
let mut decoder = read::DecoderReader::from(&mut input, &engine);
75+
let mut decoder = read::DecoderReader::new(&mut input, &engine);
7676
io::copy(&mut decoder, &mut stdout)
7777
} else {
78-
let mut encoder = write::EncoderWriter::from(&mut stdout, &engine);
78+
let mut encoder = write::EncoderWriter::new(&mut stdout, &engine);
7979
io::copy(&mut input, &mut encoder)
8080
};
8181
if let Err(e) = r {

fuzz/fuzzers/decode_random.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ fuzz_target!(|data: &[u8]| {
1111

1212
// The data probably isn't valid base64 input, but as long as it returns an error instead
1313
// of crashing, that's correct behavior.
14-
let _ = decode_engine(data, &engine);
14+
let _ = engine.decode(data);
1515
});

fuzz/fuzzers/roundtrip.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
#[macro_use] extern crate libfuzzer_sys;
33
extern crate base64;
44

5-
use base64::engine::DEFAULT_ENGINE;
5+
use base64::{Engine as _, engine::general_purpose::STANDARD};
66

77
fuzz_target!(|data: &[u8]| {
8-
let encoded = base64::encode_engine(data, &DEFAULT_ENGINE);
9-
let decoded = base64::decode_engine(&encoded, &DEFAULT_ENGINE).unwrap();
8+
let encoded = STANDARD.encode(data);
9+
let decoded = STANDARD.decode(&encoded).unwrap();
1010
assert_eq!(data, decoded.as_slice());
1111
});

0 commit comments

Comments
 (0)