Skip to content

Commit ce8bb84

Browse files
Clean up FastPortableConfig creation
also a few other Config tweaks
1 parent 1843fa5 commit ce8bb84

File tree

11 files changed

+94
-71
lines changed

11 files changed

+94
-71
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ criterion = "0.3.4"
2020
rand = "0.6.1"
2121
structopt = "0.3.21"
2222
# test fixtures for engine tests
23-
rstest = "0.6.4"
24-
rstest_reuse = "0.1.1"
23+
rstest = "0.11.0"
24+
rstest_reuse = "0.1.3"
2525

2626
[features]
2727
default = ["std"]

src/chunked_encoder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl<'e, E: Engine> ChunkedEncoder<'e, E> {
2727
pub fn from(engine: &'e E) -> ChunkedEncoder<'e, E> {
2828
ChunkedEncoder {
2929
engine,
30-
max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().padding()),
30+
max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().encode_padding()),
3131
}
3232
}
3333

@@ -46,7 +46,7 @@ impl<'e, E: Engine> ChunkedEncoder<'e, E> {
4646
input_index += input_chunk_len;
4747
let more_input_left = input_index < bytes.len();
4848

49-
if self.engine.config().padding() && !more_input_left {
49+
if self.engine.config().encode_padding() && !more_input_left {
5050
// no more input, add padding if needed. Buffer will have room because
5151
// max_input_length leaves room for it.
5252
b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]);

src/decode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ mod tests {
240240

241241
let engine = random_engine(&mut rng);
242242
encode_engine_string(&orig_data, &mut encoded_data, &engine);
243-
assert_encode_sanity(&encoded_data, engine.config().padding(), input_len);
243+
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
244244

245245
let prefix_len = prefix_len_range.sample(&mut rng);
246246

@@ -295,7 +295,7 @@ mod tests {
295295

296296
let engine = random_engine(&mut rng);
297297
encode_engine_string(&orig_data, &mut encoded_data, &engine);
298-
assert_encode_sanity(&encoded_data, engine.config().padding(), input_len);
298+
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
299299

300300
// fill the buffer with random garbage, long enough to have some room before and after
301301
for _ in 0..5000 {
@@ -347,7 +347,7 @@ mod tests {
347347

348348
let engine = random_engine(&mut rng);
349349
encode_engine_string(&orig_data, &mut encoded_data, &engine);
350-
assert_encode_sanity(&encoded_data, engine.config().padding(), input_len);
350+
assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len);
351351

352352
decode_buf.resize(input_len, 0);
353353

src/encode.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
5454
///```
5555
#[cfg(any(feature = "alloc", feature = "std", test))]
5656
pub fn encode_engine<E: Engine, T: AsRef<[u8]>>(input: T, engine: &E) -> String {
57-
let encoded_size = encoded_len(input.as_ref().len(), engine.config().padding())
57+
let encoded_size = encoded_len(input.as_ref().len(), engine.config().encode_padding())
5858
.expect("integer overflow when calculating buffer size");
5959
let mut buf = vec![0; encoded_size];
6060

@@ -148,7 +148,7 @@ pub fn encode_engine_slice<E: Engine, T: AsRef<[u8]>>(
148148
) -> usize {
149149
let input_bytes = input.as_ref();
150150

151-
let encoded_size = encoded_len(input_bytes.len(), engine.config().padding())
151+
let encoded_size = encoded_len(input_bytes.len(), engine.config().encode_padding())
152152
.expect("usize overflow when calculating buffer size");
153153

154154
let mut b64_output = &mut output_buf[0..encoded_size];
@@ -178,7 +178,7 @@ fn encode_with_padding<E: Engine>(
178178

179179
let b64_bytes_written = engine.encode(input, output);
180180

181-
let padding_bytes = if engine.config().padding() {
181+
let padding_bytes = if engine.config().encode_padding() {
182182
add_padding(input.len(), &mut output[b64_bytes_written..])
183183
} else {
184184
0
@@ -350,12 +350,12 @@ mod tests {
350350
);
351351
assert_encode_sanity(
352352
&encoded_data_no_prefix,
353-
engine.config().padding(),
353+
engine.config().encode_padding(),
354354
input_len,
355355
);
356356
assert_encode_sanity(
357357
&encoded_data_with_prefix[prefix_len..],
358-
engine.config().padding(),
358+
engine.config().encode_padding(),
359359
input_len,
360360
);
361361

@@ -401,7 +401,7 @@ mod tests {
401401

402402
let engine = random_engine(&mut rng);
403403

404-
let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap();
404+
let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
405405

406406
assert_eq!(
407407
encoded_size,
@@ -410,7 +410,7 @@ mod tests {
410410

411411
assert_encode_sanity(
412412
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
413-
engine.config().padding(),
413+
engine.config().encode_padding(),
414414
input_len,
415415
);
416416

@@ -447,7 +447,7 @@ mod tests {
447447

448448
let engine = random_engine(&mut rng);
449449

450-
let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap();
450+
let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
451451

452452
encoded_data.resize(encoded_size, 0);
453453

@@ -458,7 +458,7 @@ mod tests {
458458

459459
assert_encode_sanity(
460460
std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(),
461-
engine.config().padding(),
461+
engine.config().encode_padding(),
462462
input_len,
463463
);
464464

@@ -490,7 +490,7 @@ mod tests {
490490
let engine = random_engine(&mut rng);
491491

492492
// fill up the output buffer with garbage
493-
let encoded_size = encoded_len(input_len, config.padding()).unwrap();
493+
let encoded_size = encoded_len(input_len, config.encode_padding()).unwrap();
494494
for _ in 0..encoded_size {
495495
output.push(rng.gen());
496496
}
@@ -529,7 +529,7 @@ mod tests {
529529
let engine = random_engine(&mut rng);
530530

531531
// fill up the output buffer with garbage
532-
let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap();
532+
let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap();
533533
for _ in 0..encoded_size + 1000 {
534534
output.push(rng.gen());
535535
}

src/engine/fast_portable/decode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const INPUT_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * INPUT_CHUNK_LEN;
2020
const DECODED_BLOCK_LEN: usize =
2121
CHUNKS_PER_FAST_LOOP_BLOCK * DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX;
2222

23-
/// Estimate with metadata for FastPortable's decode logic
23+
#[doc(hidden)]
2424
pub struct FastPortableEstimate {
2525
/// Total number of decode chunks, including a possibly partial last chunk
2626
num_chunks: usize,

src/engine/fast_portable/mod.rs

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ impl super::Engine for FastPortable {
176176
)
177177
}
178178

179-
fn config(&self) -> Self::Config {
180-
self.config
179+
fn config(&self) -> &Self::Config {
180+
&self.config
181181
}
182182
}
183183

@@ -238,43 +238,61 @@ fn read_u64(s: &[u8]) -> u64 {
238238
u64::from_be_bytes(s[..8].try_into().unwrap())
239239
}
240240

241-
/// Contains miscellaneous configuration parameters for base64 encoding and decoding.
241+
/// Contains configuration parameters for base64 encoding and decoding.
242+
///
243+
/// ```
244+
/// # use base64::engine::fast_portable::FastPortableConfig;
245+
/// let config = FastPortableConfig::new()
246+
/// .with_encode_padding(false);
247+
/// // further customize using `.with_*` methods as needed
248+
/// ```
249+
///
250+
/// The constants [PAD] and [NO_PAD] cover most use cases.
242251
///
243252
/// To specify the characters used, see [crate::alphabet::Alphabet].
244253
#[derive(Clone, Copy, Debug)]
245254
pub struct FastPortableConfig {
246-
/// `true` to pad output with `=` characters
247-
padding: bool,
248-
/// `true` to ignore excess nonzero bits in the last few symbols, otherwise an error is returned
255+
encode_padding: bool,
249256
decode_allow_trailing_bits: bool,
250257
}
251258

252259
impl FastPortableConfig {
253-
/// Create a new config.
260+
/// Create a new config with `padding` = `true` and `decode_allow_trailing_bits` = `false`.
254261
///
255-
/// - `padding`: if `true`, encoding will append `=` padding characters to produce an
256-
/// output whose length is a multiple of 4. Padding is not needed for decoding and
257-
/// only serves to waste bytes but it's in the spec. For new applications, consider
258-
/// not using padding.
259-
/// - `decode_allow_trailing_bits`: If unsure, use `false`.
260-
/// Useful if you need to decode base64 produced by a buggy encoder that
261-
/// has bits set in the unused space on the last base64 character as per
262-
/// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
263-
/// If invalid trailing bits are present and this `true`, those bits will
264-
/// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted.
265-
pub const fn from(padding: bool, decode_allow_trailing_bits: bool) -> FastPortableConfig {
262+
/// This probably matches most people's expectations, but consider disabling padding to save
263+
/// a few bytes unless you specifically need it for compatibility with some legacy system.
264+
pub const fn new() -> FastPortableConfig {
266265
FastPortableConfig {
267-
padding,
268-
decode_allow_trailing_bits,
266+
// RFC states that padding must be applied by default
267+
encode_padding: true,
268+
decode_allow_trailing_bits: false,
269269
}
270270
}
271271

272-
/// Create a new `Config` based on `self` with an updated `padding` parameter.
273-
pub const fn with_padding(self, padding: bool) -> FastPortableConfig {
274-
FastPortableConfig { padding, ..self }
272+
/// Create a new config based on `self` with an updated `padding` parameter.
273+
///
274+
/// If `true`, encoding will append either 1 or 2 `=` padding characters to produce an
275+
/// output whose length is a multiple of 4.
276+
///
277+
/// Padding is not needed for correct decoding and only serves to waste bytes, but it's in the
278+
/// [spec](https://datatracker.ietf.org/doc/html/rfc4648#section-3.2).
279+
///
280+
/// For new applications, consider not using padding if the decoders you're using don't require
281+
/// padding to be present.
282+
pub const fn with_encode_padding(self, padding: bool) -> FastPortableConfig {
283+
FastPortableConfig {
284+
encode_padding: padding,
285+
..self
286+
}
275287
}
276288

277-
/// Create a new `Config` based on `self` with an updated `decode_allow_trailing_bits` parameter.
289+
/// Create a new config based on `self` with an updated `decode_allow_trailing_bits` parameter.
290+
///
291+
/// Most users will not need to configure this. It's useful if you need to decode base64
292+
/// produced by a buggy encoder that has bits set in the unused space on the last base64
293+
/// character as per [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode).
294+
/// If invalid trailing bits are present and this is `true`, those bits will
295+
/// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted.
278296
pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> FastPortableConfig {
279297
FastPortableConfig {
280298
decode_allow_trailing_bits: allow,
@@ -283,20 +301,21 @@ impl FastPortableConfig {
283301
}
284302
}
285303

304+
impl Default for FastPortableConfig {
305+
/// Delegates to [FastPortableConfig::new].
306+
fn default() -> Self {
307+
FastPortableConfig::new()
308+
}
309+
}
310+
286311
impl Config for FastPortableConfig {
287-
fn padding(&self) -> bool {
288-
self.padding
312+
fn encode_padding(&self) -> bool {
313+
self.encode_padding
289314
}
290315
}
291316

292317
/// Include padding bytes when encoding.
293-
pub const PAD: FastPortableConfig = FastPortableConfig {
294-
padding: true,
295-
decode_allow_trailing_bits: false,
296-
};
318+
pub const PAD: FastPortableConfig = FastPortableConfig::new();
297319

298320
/// Don't add padding when encoding.
299-
pub const NO_PAD: FastPortableConfig = FastPortableConfig {
300-
padding: false,
301-
decode_allow_trailing_bits: false,
302-
};
321+
pub const NO_PAD: FastPortableConfig = FastPortableConfig::new().with_encode_padding(false);

src/engine/mod.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ mod naive;
1010
#[cfg(test)]
1111
mod tests;
1212

13-
// TODO shared DecodeError or per-impl as an associated type?
14-
1513
/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use.
1614
///
1715
/// Different implementations offer different characteristics. The library currently ships with
@@ -25,7 +23,7 @@ mod tests;
2523
// - add an implementation of [engine::tests::EngineWrapper]
2624
// - add the implementation to the `all_engines` macro
2725
// All tests run on all engines listed in the macro.
28-
pub trait Engine {
26+
pub trait Engine: Send + Sync {
2927
/// The config type used by this engine
3028
type Config: Config;
3129
/// The decode estimate used by this engine
@@ -70,10 +68,10 @@ pub trait Engine {
7068
) -> Result<usize, DecodeError>;
7169

7270
/// Returns the config for this engine.
73-
fn config(&self) -> Self::Config;
71+
fn config(&self) -> &Self::Config;
7472
}
7573

76-
/// The minimal level of configuration engines must expose.
74+
/// The minimal level of configuration that engines must support.
7775
pub trait Config {
7876
/// Returns `true` if padding should be added after the encoded output.
7977
///
@@ -83,10 +81,11 @@ pub trait Config {
8381
// It could be provided as a separate parameter when encoding, but that feels like
8482
// leaking an implementation detail to the user, and it's hopefully more convenient
8583
// to have to only pass one thing (the engine) to any part of the API.
86-
fn padding(&self) -> bool;
84+
fn encode_padding(&self) -> bool;
8785
}
8886

89-
/// The decode estimate used by an engine implementation.
87+
/// The decode estimate used by an engine implementation. Users do not need to interact with this;
88+
/// it is only for engine implementors.
9089
///
9190
/// Implementors may want to store relevant calculations when constructing this to avoid having
9291
/// to calculate them again during actual decoding.
@@ -96,6 +95,6 @@ pub trait DecodeEstimate {
9695
fn decoded_length_estimate(&self) -> usize;
9796
}
9897

99-
/// An engine that will work on all CPUs using the standard base64 alphabet.
98+
/// An engine that will work on all CPUs using the standard base64 alphabet and config.
10099
pub const DEFAULT_ENGINE: FastPortable =
101100
FastPortable::from(&alphabet::STANDARD, fast_portable::PAD);

src/engine/naive.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ impl Engine for Naive {
264264
Ok(output_index)
265265
}
266266

267-
fn config(&self) -> Self::Config {
268-
self.config
267+
fn config(&self) -> &Self::Config {
268+
&self.config
269269
}
270270
}
271271

@@ -301,7 +301,7 @@ pub struct NaiveConfig {
301301
}
302302

303303
impl Config for NaiveConfig {
304-
fn padding(&self) -> bool {
304+
fn encode_padding(&self) -> bool {
305305
self.padding
306306
}
307307
}

src/engine/tests.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ use crate::{
1414
DecodeError, PAD_BYTE,
1515
};
1616

17+
// the case::foo syntax includes the "foo" in the generated test method names
1718
#[template]
1819
#[rstest(engine_wrapper,
19-
case(FastPortableWrapper {}),
20-
case(NaiveWrapper {}),
20+
case::fast_portable(FastPortableWrapper {}),
21+
case::naive(NaiveWrapper {}),
2122
)]
2223
fn all_engines<E: EngineWrapper>(engine_wrapper: E) {}
2324

@@ -830,7 +831,7 @@ impl EngineWrapper for FastPortableWrapper {
830831
fn standard_forgiving() -> Self::Engine {
831832
fast_portable::FastPortable::from(
832833
&STANDARD,
833-
fast_portable::FastPortableConfig::from(true, true),
834+
fast_portable::FastPortableConfig::new().with_decode_allow_trailing_bits(true),
834835
)
835836
}
836837

@@ -841,7 +842,9 @@ impl EngineWrapper for FastPortableWrapper {
841842
}
842843

843844
fn random_alphabet<R: Rng>(rng: &mut R, alphabet: &Alphabet) -> Self::Engine {
844-
let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen());
845+
let config = fast_portable::FastPortableConfig::new()
846+
.with_encode_padding(rng.gen())
847+
.with_decode_allow_trailing_bits(rng.gen());
845848

846849
fast_portable::FastPortable::from(alphabet, config)
847850
}

0 commit comments

Comments
 (0)