From 78619fb4847a44acc35120f1e8b0d2bdfdac7c5c Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Sat, 13 Feb 2021 18:01:16 -0700 Subject: [PATCH 1/5] 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. --- .travis.yml | 7 +- Cargo.toml | 4 +- README.md | 10 +- RELEASE-NOTES.md | 13 +- benches/benchmarks.rs | 75 +- examples/base64.rs | 50 +- examples/make_tables.rs | 179 --- fuzz/fuzzers/decode_random.rs | 4 +- fuzz/fuzzers/roundtrip.rs | 6 +- fuzz/fuzzers/roundtrip_no_pad.rs | 8 +- fuzz/fuzzers/roundtrip_random_config.rs | 6 +- fuzz/fuzzers/utils.rs | 14 +- src/alphabet.rs | 164 ++ src/chunked_encoder.rs | 98 +- src/decode.rs | 673 +------- src/display.rs | 29 +- src/encode.rs | 383 ++--- src/engine/fast_portable/decode.rs | 457 ++++++ src/engine/fast_portable/mod.rs | 285 ++++ src/engine/mod.rs | 101 ++ src/engine/naive.rs | 307 ++++ src/engine/tests.rs | 901 +++++++++++ src/lib.rs | 231 +-- src/read/decoder.rs | 29 +- src/read/decoder_tests.rs | 53 +- src/tables.rs | 1957 ----------------------- src/tests.rs | 50 +- src/write/encoder.rs | 44 +- src/write/encoder_string_writer.rs | 48 +- src/write/encoder_tests.rs | 113 +- src/write/mod.rs | 1 + tests/decode.rs | 258 +-- tests/encode.rs | 12 +- tests/tests.rs | 83 +- 34 files changed, 2932 insertions(+), 3721 deletions(-) delete mode 100644 examples/make_tables.rs create mode 100644 src/alphabet.rs create mode 100644 src/engine/fast_portable/decode.rs create mode 100644 src/engine/fast_portable/mod.rs create mode 100644 src/engine/mod.rs create mode 100644 src/engine/naive.rs create mode 100644 src/engine/tests.rs delete mode 100644 src/tables.rs diff --git a/.travis.yml b/.travis.yml index 84d033c4..3c38beaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,7 @@ sudo: required matrix: include: - - rust: 1.36.0 - # cfg(doctest) is experimental in 1.39 but ignored with 1.34.0, and that snuck in when 1.39.0 wasn't tested - - rust: 1.39.0 + - rust: 1.47.0 - rust: stable - rust: beta - rust: nightly @@ -37,7 +35,8 @@ script: - cargo build --all-targets - cargo build --verbose $TARGET --no-default-features - cargo build --verbose $TARGET $FEATURES - - 'if [[ -z "$TARGET" ]]; then cargo test --verbose; fi' + # tests are cpu intensive enough that running them in release mode is much faster + - 'if [[ -z "$TARGET" ]]; then cargo test --release --verbose; fi' - 'if [[ -z "$TARGET" ]]; then cargo doc --verbose; fi' # run for just a second to confirm that it can build and run ok - 'if [[ "$TRAVIS_RUST_VERSION" = nightly ]]; then cargo fuzz list | xargs -L 1 -I FUZZER cargo fuzz run FUZZER -- -max_total_time=1; fi' diff --git a/Cargo.toml b/Cargo.toml index 9175d6b2..64c146c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,12 @@ name = "benchmarks" harness = false [dev-dependencies] -# 0.3.3 requires rust 1.36.0 for stable copied() criterion = "0.3.4" rand = "0.6.1" structopt = "0.3.21" +# test fixtures for engine tests +rstest = "0.6.4" +rstest_reuse = "0.1.1" [features] default = ["std"] diff --git a/README.md b/README.md index 2184d9b2..5d26ca7c 100644 --- a/README.md +++ b/README.md @@ -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` 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` 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 --- @@ -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 @@ -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 --- diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 2c8287ea..d11e2970 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,6 +1,13 @@ -# 0.14.0 - -- MSRV is now 1.36.0 +# 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 +- DecoderReader now owns its delegate reader +- MSRV is now 1.47.0 # 0.13.0 diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 053b9796..0d0c71ff 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -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 criterion::{black_box, Bencher, Criterion, Throughput, BenchmarkId}; +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 = Vec::with_capacity(size * 3 / 4); fill(&mut v); @@ -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(); }); @@ -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); }); } @@ -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); @@ -83,7 +82,7 @@ fn do_encode_bench_display(b: &mut Bencher, &size: &usize) { let mut v: Vec = 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); }); } @@ -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(); }); } @@ -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); }); } @@ -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(); }); @@ -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(); @@ -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(); @@ -174,11 +173,31 @@ fn encode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) { group .throughput(Throughput::Bytes(*size as u64)) .bench_with_input(BenchmarkId::new("encode", size), size, do_encode_bench) - .bench_with_input(BenchmarkId::new("encode_display", size), size, do_encode_bench_display) - .bench_with_input(BenchmarkId::new("encode_reuse_buf", size), size, do_encode_bench_reuse_buf) - .bench_with_input(BenchmarkId::new("encode_slice", size), size, do_encode_bench_slice) - .bench_with_input(BenchmarkId::new("encode_reuse_buf_stream", size), size, do_encode_bench_stream) - .bench_with_input(BenchmarkId::new("encode_string_stream", size), size, do_encode_bench_string_stream) + .bench_with_input( + BenchmarkId::new("encode_display", size), + size, + do_encode_bench_display, + ) + .bench_with_input( + BenchmarkId::new("encode_reuse_buf", size), + size, + do_encode_bench_reuse_buf, + ) + .bench_with_input( + BenchmarkId::new("encode_slice", size), + size, + do_encode_bench_slice, + ) + .bench_with_input( + BenchmarkId::new("encode_reuse_buf_stream", size), + size, + do_encode_bench_stream, + ) + .bench_with_input( + BenchmarkId::new("encode_string_stream", size), + size, + do_encode_bench_string_stream, + ) .bench_with_input( BenchmarkId::new("encode_string_reuse_buf_stream", size), size, @@ -198,9 +217,21 @@ fn decode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) { .measurement_time(std::time::Duration::from_secs(3)) .throughput(Throughput::Bytes(*size as u64)) .bench_with_input(BenchmarkId::new("decode", size), size, do_decode_bench) - .bench_with_input(BenchmarkId::new("decode_reuse_buf", size), size, do_decode_bench_reuse_buf) - .bench_with_input(BenchmarkId::new("decode_slice", size), size, do_decode_bench_slice) - .bench_with_input(BenchmarkId::new("decode_stream", size), size, do_decode_bench_stream); + .bench_with_input( + BenchmarkId::new("decode_reuse_buf", size), + size, + do_decode_bench_reuse_buf, + ) + .bench_with_input( + BenchmarkId::new("decode_slice", size), + size, + do_decode_bench_slice, + ) + .bench_with_input( + BenchmarkId::new("decode_stream", size), + size, + do_decode_bench_stream, + ); } group.finish(); diff --git a/examples/base64.rs b/examples/base64.rs index cba745b0..9c75435c 100644 --- a/examples/base64.rs +++ b/examples/base64.rs @@ -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 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 { + fn from_str(s: &str) -> Result { 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)), } } } @@ -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, + /// The alphabet to choose. Defaults to the standard base64 alphabet. + /// Supported alphabets include "standard" and "urlsafe". + #[structopt(long = "alphabet")] + alphabet: Option, /// The file to encode/decode. #[structopt(parse(from_os_str))] file: Option, @@ -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 { diff --git a/examples/make_tables.rs b/examples/make_tables.rs deleted file mode 100644 index 2f27c0eb..00000000 --- a/examples/make_tables.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::collections::{HashMap, HashSet}; -use std::iter::Iterator; - -fn main() { - println!("pub const INVALID_VALUE: u8 = 255;"); - - // A-Z - let standard_alphabet: Vec = (0x41..0x5B) - // a-z - .chain(0x61..0x7B) - // 0-9 - .chain(0x30..0x3A) - // + - .chain(0x2B..0x2C) - // / - .chain(0x2F..0x30) - .collect(); - print_encode_table(&standard_alphabet, "STANDARD_ENCODE", 0); - print_decode_table(&standard_alphabet, "STANDARD_DECODE", 0); - - // A-Z - let url_alphabet: Vec = (0x41..0x5B) - // a-z - .chain(0x61..0x7B) - // 0-9 - .chain(0x30..0x3A) - // - - .chain(0x2D..0x2E) - // _ - .chain(0x5F..0x60) - .collect(); - print_encode_table(&url_alphabet, "URL_SAFE_ENCODE", 0); - print_decode_table(&url_alphabet, "URL_SAFE_DECODE", 0); - - // ./0123456789 - let crypt_alphabet: Vec = (b'.'..(b'9' + 1)) - // A-Z - .chain(b'A'..(b'Z' + 1)) - // a-z - .chain(b'a'..(b'z' + 1)) - .collect(); - print_encode_table(&crypt_alphabet, "CRYPT_ENCODE", 0); - print_decode_table(&crypt_alphabet, "CRYPT_DECODE", 0); - - // ./ - let bcrypt_alphabet: Vec = (b'.'..(b'/' + 1)) - // A-Z - .chain(b'A'..(b'Z' + 1)) - // a-z - .chain(b'a'..(b'z' + 1)) - // 0-9 - .chain(b'0'..(b'9' + 1)) - .collect(); - print_encode_table(&bcrypt_alphabet, "BCRYPT_ENCODE", 0); - print_decode_table(&bcrypt_alphabet, "BCRYPT_DECODE", 0); - - // A-Z - let imap_alphabet: Vec = (0x41..0x5B) - // a-z - .chain(0x61..0x7B) - // 0-9 - .chain(0x30..0x3A) - // + - .chain(0x2B..0x2C) - // , - .chain(0x2C..0x2D) - .collect(); - print_encode_table(&imap_alphabet, "IMAP_MUTF7_ENCODE", 0); - print_decode_table(&imap_alphabet, "IMAP_MUTF7_DECODE", 0); - - // '!' - '-' - let binhex_alphabet: Vec = (0x21..0x2E) - // 0-9 - .chain(0x30..0x3A) - // @-N - .chain(0x40..0x4F) - // P-V - .chain(0x50..0x57) - // X-[ - .chain(0x58..0x5C) - // `-f - .chain(0x60..0x66) - // h-m - .chain(0x68..0x6E) - // p-r - .chain(0x70..0x73) - .collect(); - print_encode_table(&binhex_alphabet, "BINHEX_ENCODE", 0); - print_decode_table(&binhex_alphabet, "BINHEX_DECODE", 0); -} - -fn print_encode_table(alphabet: &[u8], const_name: &str, indent_depth: usize) { - check_alphabet(alphabet); - println!("#[rustfmt::skip]"); - println!( - "{:width$}pub const {}: &[u8; 64] = &[", - "", - const_name, - width = indent_depth - ); - - for (i, b) in alphabet.iter().enumerate() { - println!( - "{:width$}{}, // input {} (0x{:X}) => '{}' (0x{:X})", - "", - b, - i, - i, - String::from_utf8(vec![*b as u8]).unwrap(), - b, - width = indent_depth + 4 - ); - } - - println!("{:width$}];", "", width = indent_depth); -} - -fn print_decode_table(alphabet: &[u8], const_name: &str, indent_depth: usize) { - check_alphabet(alphabet); - // map of alphabet bytes to 6-bit morsels - let mut input_to_morsel = HashMap::::new(); - - // standard base64 alphabet bytes, in order - for (morsel, ascii_byte) in alphabet.iter().enumerate() { - // truncation cast is fine here - let _ = input_to_morsel.insert(*ascii_byte, morsel as u8); - } - - println!("#[rustfmt::skip]"); - println!( - "{:width$}pub const {}: &[u8; 256] = &[", - "", - const_name, - width = indent_depth - ); - for ascii_byte in 0..256 { - let (value, comment) = match input_to_morsel.get(&(ascii_byte as u8)) { - None => ( - "INVALID_VALUE".to_string(), - format!("input {} (0x{:X})", ascii_byte, ascii_byte), - ), - Some(v) => ( - format!("{}", *v), - format!( - "input {} (0x{:X} char '{}') => {} (0x{:X})", - ascii_byte, - ascii_byte, - String::from_utf8(vec![ascii_byte as u8]).unwrap(), - *v, - *v - ), - ), - }; - - println!( - "{:width$}{}, // {}", - "", - value, - comment, - width = indent_depth + 4 - ); - } - println!("{:width$}];", "", width = indent_depth); -} - -fn check_alphabet(alphabet: &[u8]) { - // ensure all characters are distinct - assert_eq!(64, alphabet.len()); - let mut set: HashSet = HashSet::new(); - set.extend(alphabet); - assert_eq!(64, set.len()); - - // must be ASCII to be valid as single UTF-8 bytes - for &b in alphabet { - assert!(b <= 0x7F_u8); - // = is assumed to be padding, so cannot be used as a symbol - assert_ne!(b'=', b); - } -} diff --git a/fuzz/fuzzers/decode_random.rs b/fuzz/fuzzers/decode_random.rs index 7c5045fb..0c0d9627 100644 --- a/fuzz/fuzzers/decode_random.rs +++ b/fuzz/fuzzers/decode_random.rs @@ -7,9 +7,9 @@ use base64::*; mod utils; fuzz_target!(|data: &[u8]| { - let config = utils::random_config(data); + let engine = utils::random_engine(data); // The data probably isn't valid base64 input, but as long as it returns an error instead // of crashing, that's correct behavior. - let _ = decode_config(&data, config); + let _ = decode_engine(&data, &engine); }); diff --git a/fuzz/fuzzers/roundtrip.rs b/fuzz/fuzzers/roundtrip.rs index e40fa485..2097f2a1 100644 --- a/fuzz/fuzzers/roundtrip.rs +++ b/fuzz/fuzzers/roundtrip.rs @@ -2,8 +2,10 @@ #[macro_use] extern crate libfuzzer_sys; extern crate base64; +use base64::engine::DEFAULT_ENGINE; + fuzz_target!(|data: &[u8]| { - let encoded = base64::encode_config(&data, base64::STANDARD); - let decoded = base64::decode_config(&encoded, base64::STANDARD).unwrap(); + let encoded = base64::encode_engine(&data, &DEFAULT_ENGINE); + let decoded = base64::decode_engine(&encoded, &DEFAULT_ENGINE).unwrap(); assert_eq!(data, decoded.as_slice()); }); diff --git a/fuzz/fuzzers/roundtrip_no_pad.rs b/fuzz/fuzzers/roundtrip_no_pad.rs index b2ee87fe..3fd7a68e 100644 --- a/fuzz/fuzzers/roundtrip_no_pad.rs +++ b/fuzz/fuzzers/roundtrip_no_pad.rs @@ -1,11 +1,13 @@ #![no_main] #[macro_use] extern crate libfuzzer_sys; extern crate base64; +use base64::engine::fast_portable; fuzz_target!(|data: &[u8]| { - let config = base64::Config::new(base64::CharacterSet::Standard, false); + let config = fast_portable::FastPortableConfig::from(false, false); + let engine = fast_portable::FastPortable::from(&base64::alphabet::STANDARD, config); - let encoded = base64::encode_config(&data, config); - let decoded = base64::decode_config(&encoded, config).unwrap(); + let encoded = base64::encode_engine(&data, &engine); + let decoded = base64::decode_engine(&encoded, &engine).unwrap(); assert_eq!(data, decoded.as_slice()); }); diff --git a/fuzz/fuzzers/roundtrip_random_config.rs b/fuzz/fuzzers/roundtrip_random_config.rs index 64f94b65..1f5a2c17 100644 --- a/fuzz/fuzzers/roundtrip_random_config.rs +++ b/fuzz/fuzzers/roundtrip_random_config.rs @@ -7,9 +7,9 @@ use base64::*; mod utils; fuzz_target!(|data: &[u8]| { - let config = utils::random_config(data); + let engine = utils::random_engine(data); - let encoded = encode_config(&data, config); - let decoded = decode_config(&encoded, config).unwrap(); + let encoded = encode_engine(&data, &engine); + let decoded = decode_engine(&encoded, &engine).unwrap(); assert_eq!(data, decoded.as_slice()); }); diff --git a/fuzz/fuzzers/utils.rs b/fuzz/fuzzers/utils.rs index 0a630d6b..ab80a929 100644 --- a/fuzz/fuzzers/utils.rs +++ b/fuzz/fuzzers/utils.rs @@ -3,12 +3,12 @@ extern crate rand; extern crate rand_pcg; extern crate ring; -use self::base64::*; +use base64::{alphabet, engine::fast_portable}; use self::rand::{Rng, SeedableRng}; use self::rand_pcg::Pcg32; use self::ring::digest; -pub fn random_config(data: &[u8]) -> Config { +pub fn random_engine(data: &[u8]) -> fast_portable::FastPortable { // use sha256 of data as rng seed so it's repeatable let sha = digest::digest(&digest::SHA256, data); @@ -17,11 +17,13 @@ pub fn random_config(data: &[u8]) -> Config { let mut rng = Pcg32::from_seed(seed); - let charset = if rng.gen() { - CharacterSet::UrlSafe + let alphabet = if rng.gen() { + alphabet::URL_SAFE } else { - CharacterSet::Standard + alphabet::STANDARD }; - Config::new(charset, rng.gen()) + let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen()); + + fast_portable::FastPortable::from(&alphabet, config) } diff --git a/src/alphabet.rs b/src/alphabet.rs new file mode 100644 index 00000000..650de43b --- /dev/null +++ b/src/alphabet.rs @@ -0,0 +1,164 @@ +//! Provides [Alphabet] and constants for alphabets commonly used in the wild. + +/// An alphabet defines the 64 ASCII characters (symbols) used for base64. +/// +/// Common alphabets are provided as constants, and custom alphabets +/// can be made via the [From](#impl-From) implementation. +/// +/// ``` +/// let custom = base64::alphabet::Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// +/// let engine = base64::engine::fast_portable::FastPortable::from( +/// &custom, +/// base64::engine::fast_portable::PAD); +/// ``` +#[derive(Clone, Copy, Debug)] +pub struct Alphabet { + pub(crate) symbols: [u8; 64], +} + +impl Alphabet { + /// Performs no checks so that it can be const. + /// Used only for known-valid strings. + const fn from_unchecked(alphabet: &str) -> Alphabet { + let mut symbols = [0_u8; 64]; + let source_bytes = alphabet.as_bytes(); + + // a way to copy that's allowed in const fn + let mut index = 0; + while index < 64 { + symbols[index] = source_bytes[index]; + index += 1; + } + + Alphabet { symbols } + } +} + +impl> From for Alphabet { + /// Create a `CharacterSet` from a string of 64 ASCII bytes. Each byte must be + /// unique, and the `=` byte is not allowed as it is used for padding. + /// + /// # Errors + /// + /// Panics if the text is an invalid base64 alphabet since the alphabet is + /// likely to be hardcoded, and therefore errors are generally unrecoverable + /// programmer errors. + fn from(string: T) -> Self { + let alphabet = string.as_ref(); + assert_eq!( + 64, + alphabet.as_bytes().len(), + "Base64 char set length must be 64" + ); + + // scope just to ensure not accidentally using the sorted copy + { + // Check uniqueness without allocating since this must be no_std. + // Could pull in heapless and use IndexSet, but this seems simple enough. + let mut bytes = [0_u8; 64]; + alphabet + .as_bytes() + .iter() + .enumerate() + .for_each(|(index, &byte)| bytes[index] = byte); + + bytes.sort_unstable(); + + // iterate over the sorted bytes, offset by one + bytes.iter().zip(bytes[1..].iter()).for_each(|(b1, b2)| { + // if any byte is the same as the next byte, there's a duplicate + assert_ne!(b1, b2, "Duplicate bytes"); + }); + } + + for &byte in alphabet.as_bytes() { + // must be ascii printable. 127 (DEL) is commonly considered printable + // for some reason but clearly unsuitable for base64. + assert!(byte >= 32_u8 && byte < 127_u8, "Bytes must be printable"); + // = is assumed to be padding, so cannot be used as a symbol + assert_ne!(b'=', byte, "Padding byte '=' is reserved"); + } + + Self::from_unchecked(alphabet) + } +} + +/// The standard alphabet (uses `+` and `/`). +/// +/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3). +pub const STANDARD: Alphabet = + Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + +/// The URL safe alphabet (uses `-` and `_`). +/// +/// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4). +pub const URL_SAFE: Alphabet = + Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); + +/// The `crypt(3)` alphabet (uses `.` and `/` as the first two values). +/// +/// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses. +pub const CRYPT: Alphabet = + Alphabet::from_unchecked("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); + +/// The bcrypt alphabet. +pub const BCRYPT: Alphabet = + Alphabet::from_unchecked("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + +/// The alphabet used in IMAP-modified UTF-7 (uses `+` and `,`). +/// +/// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3) +pub const IMAP_MUTF7: Alphabet = + Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"); + +/// The alphabet used in BinHex 4.0 files. +/// +/// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt) +pub const BIN_HEX: Alphabet = + Alphabet::from_unchecked("!\"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdehijklmpqr"); + +#[cfg(test)] +mod tests { + use crate::alphabet::Alphabet; + + #[should_panic(expected = "Duplicate bytes")] + #[test] + fn detects_duplicate_start() { + let _ = Alphabet::from("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + } + + #[should_panic(expected = "Duplicate bytes")] + #[test] + fn detects_duplicate_end() { + let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//"); + } + + #[should_panic(expected = "Duplicate bytes")] + #[test] + fn detects_duplicate_middle() { + let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/"); + } + + #[should_panic(expected = "Base64 char set length must be 64")] + #[test] + fn detects_length() { + let _ = Alphabet::from( + "xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/", + ); + } + + #[should_panic(expected = "Padding byte '=' is reserved")] + #[test] + fn detects_padding() { + let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+="); + } + + #[should_panic(expected = "Bytes must be printable")] + #[test] + fn detects_unprintable() { + // form feed + let _ = + Alphabet::from("\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + } +} diff --git a/src/chunked_encoder.rs b/src/chunked_encoder.rs index bd45ec9e..b27e27a9 100644 --- a/src/chunked_encoder.rs +++ b/src/chunked_encoder.rs @@ -1,13 +1,12 @@ -use crate::{ - encode::{add_padding, encode_to_slice}, - Config, -}; #[cfg(any(feature = "alloc", feature = "std", test))] use alloc::string::String; use core::cmp; #[cfg(any(feature = "alloc", feature = "std", test))] use core::str; +use crate::encode::add_padding; +use crate::engine::{Config, Engine}; + /// The output mechanism for ChunkedEncoder's encoded bytes. pub trait Sink { type Error; @@ -19,23 +18,21 @@ pub trait Sink { const BUF_SIZE: usize = 1024; /// A base64 encoder that emits encoded bytes in chunks without heap allocation. -pub struct ChunkedEncoder { - config: Config, +pub struct ChunkedEncoder<'e, E: Engine> { + engine: &'e E, max_input_chunk_len: usize, } -impl ChunkedEncoder { - pub fn new(config: Config) -> ChunkedEncoder { +impl<'e, E: Engine> ChunkedEncoder<'e, E> { + pub fn from(engine: &'e E) -> ChunkedEncoder<'e, E> { ChunkedEncoder { - config, - max_input_chunk_len: max_input_length(BUF_SIZE, config), + engine, + max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().padding()), } } pub fn encode(&self, bytes: &[u8], sink: &mut S) -> Result<(), S::Error> { let mut encode_buf: [u8; BUF_SIZE] = [0; BUF_SIZE]; - let encode_table = self.config.char_set.encode_table(); - let mut input_index = 0; while input_index < bytes.len() { @@ -44,12 +41,12 @@ impl ChunkedEncoder { let chunk = &bytes[input_index..(input_index + input_chunk_len)]; - let mut b64_bytes_written = encode_to_slice(chunk, &mut encode_buf, encode_table); + let mut b64_bytes_written = self.engine.encode(chunk, &mut encode_buf); input_index += input_chunk_len; let more_input_left = input_index < bytes.len(); - if self.config.pad && !more_input_left { + if self.engine.config().padding() && !more_input_left { // no more input, add padding if needed. Buffer will have room because // max_input_length leaves room for it. b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]); @@ -69,8 +66,8 @@ impl ChunkedEncoder { /// /// The input length will always be a multiple of 3 so that no encoding state has to be carried over /// between chunks. -fn max_input_length(encoded_buf_len: usize, config: Config) -> usize { - let effective_buf_len = if config.pad { +fn max_input_length(encoded_buf_len: usize, padded: bool) -> usize { + let effective_buf_len = if padded { // make room for padding encoded_buf_len .checked_sub(2) @@ -91,7 +88,7 @@ pub(crate) struct StringSink<'a> { #[cfg(any(feature = "alloc", feature = "std", test))] impl<'a> StringSink<'a> { - pub(crate) fn new(s: &mut String) -> StringSink { + pub(crate) fn from(s: &mut String) -> StringSink { StringSink { string: s } } } @@ -109,26 +106,27 @@ impl<'a> Sink for StringSink<'a> { #[cfg(test)] pub mod tests { - use super::*; - use crate::{encode_config_buf, tests::random_config, CharacterSet, STANDARD}; - use rand::{ distributions::{Distribution, Uniform}, FromEntropy, Rng, }; + use crate::alphabet::STANDARD; + use crate::encode_engine_string; + use crate::engine::fast_portable::{FastPortable, FastPortableConfig, PAD}; + use crate::tests::random_engine; + + use super::*; + #[test] fn chunked_encode_empty() { - assert_eq!("", chunked_encode_str(&[], STANDARD)); + assert_eq!("", chunked_encode_str(&[], PAD)); } #[test] fn chunked_encode_intermediate_fast_loop() { // > 8 bytes input, will enter the pretty fast loop - assert_eq!( - "Zm9vYmFyYmF6cXV4", - chunked_encode_str(b"foobarbazqux", STANDARD) - ); + assert_eq!("Zm9vYmFyYmF6cXV4", chunked_encode_str(b"foobarbazqux", PAD)); } #[test] @@ -136,14 +134,14 @@ pub mod tests { // > 32 bytes input, will enter the uber fast loop assert_eq!( "Zm9vYmFyYmF6cXV4cXV1eGNvcmdlZ3JhdWx0Z2FycGx5eg==", - chunked_encode_str(b"foobarbazquxquuxcorgegraultgarplyz", STANDARD) + chunked_encode_str(b"foobarbazquxquuxcorgegraultgarplyz", PAD) ); } #[test] fn chunked_encode_slow_loop_only() { // < 8 bytes input, slow loop only - assert_eq!("Zm9vYmFy", chunked_encode_str(b"foobar", STANDARD)); + assert_eq!("Zm9vYmFy", chunked_encode_str(b"foobar", PAD)); } #[test] @@ -154,32 +152,27 @@ pub mod tests { #[test] fn max_input_length_no_pad() { - let config = config_with_pad(false); - assert_eq!(768, max_input_length(1024, config)); + assert_eq!(768, max_input_length(1024, false)); } #[test] fn max_input_length_with_pad_decrements_one_triple() { - let config = config_with_pad(true); - assert_eq!(765, max_input_length(1024, config)); + assert_eq!(765, max_input_length(1024, true)); } #[test] fn max_input_length_with_pad_one_byte_short() { - let config = config_with_pad(true); - assert_eq!(765, max_input_length(1025, config)); + assert_eq!(765, max_input_length(1025, true)); } #[test] fn max_input_length_with_pad_fits_exactly() { - let config = config_with_pad(true); - assert_eq!(768, max_input_length(1026, config)); + assert_eq!(768, max_input_length(1026, true)); } #[test] fn max_input_length_cant_use_extra_single_encoded_byte() { - let config = Config::new(crate::CharacterSet::Standard, false); - assert_eq!(300, max_input_length(401, config)); + assert_eq!(300, max_input_length(401, false)); } pub fn chunked_encode_matches_normal_encode_random(sink_test_helper: &S) { @@ -197,47 +190,40 @@ pub mod tests { input_buf.push(rng.gen()); } - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); - let chunk_encoded_string = sink_test_helper.encode_to_string(config, &input_buf); - encode_config_buf(&input_buf, config, &mut output_buf); + let chunk_encoded_string = sink_test_helper.encode_to_string(&engine, &input_buf); + encode_engine_string(&input_buf, &mut output_buf, &engine); - assert_eq!( - output_buf, chunk_encoded_string, - "input len={}, config: pad={}", - buf_len, config.pad - ); + assert_eq!(output_buf, chunk_encoded_string, "input len={}", buf_len); } } - fn chunked_encode_str(bytes: &[u8], config: Config) -> String { + fn chunked_encode_str(bytes: &[u8], config: FastPortableConfig) -> String { let mut s = String::new(); { - let mut sink = StringSink::new(&mut s); - let encoder = ChunkedEncoder::new(config); + let mut sink = StringSink::from(&mut s); + let engine = FastPortable::from(&STANDARD, config); + let encoder = ChunkedEncoder::from(&engine); encoder.encode(bytes, &mut sink).unwrap(); } return s; } - fn config_with_pad(pad: bool) -> Config { - Config::new(CharacterSet::Standard, pad) - } - // An abstraction around sinks so that we can have tests that easily to any sink implementation pub trait SinkTestHelper { - fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String; + fn encode_to_string(&self, engine: &E, bytes: &[u8]) -> String; } struct StringSinkTestHelper; impl SinkTestHelper for StringSinkTestHelper { - fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String { - let encoder = ChunkedEncoder::new(config); + fn encode_to_string(&self, engine: &E, bytes: &[u8]) -> String { + let encoder = ChunkedEncoder::from(engine); let mut s = String::new(); { - let mut sink = StringSink::new(&mut s); + let mut sink = StringSink::from(&mut s); encoder.encode(bytes, &mut sink).unwrap(); } diff --git a/src/decode.rs b/src/decode.rs index 4cc937d5..0390aa16 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,27 +1,15 @@ -use crate::{tables, Config, PAD_BYTE}; - #[cfg(any(feature = "alloc", feature = "std", test))] -use crate::STANDARD; +use crate::engine::DecodeEstimate; +use crate::engine::Engine; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::engine::DEFAULT_ENGINE; #[cfg(any(feature = "alloc", feature = "std", test))] use alloc::vec::Vec; use core::fmt; #[cfg(any(feature = "std", test))] use std::error; -// decode logic operates on chunks of 8 input bytes without padding -const INPUT_CHUNK_LEN: usize = 8; -const DECODED_CHUNK_LEN: usize = 6; -// we read a u64 and write a u64, but a u64 of input only yields 6 bytes of output, so the last -// 2 bytes of any output u64 should not be counted as written to (but must be available in a -// slice). -const DECODED_CHUNK_SUFFIX: usize = 2; - -// how many u64's of input to handle at a time -const CHUNKS_PER_FAST_LOOP_BLOCK: usize = 4; -const INPUT_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * INPUT_CHUNK_LEN; -// includes the trailing 2 bytes for the final u64 write -const DECODED_BLOCK_LEN: usize = - CHUNKS_PER_FAST_LOOP_BLOCK * DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX; +// TODO how to handle InvalidLastSymbol and InvalidLength behavior across engines? /// Errors that can occur while decoding. #[derive(Clone, Debug, PartialEq, Eq)] @@ -70,9 +58,8 @@ impl error::Error for DecodeError { } } -///Decode from string reference as octets. -///Returns a Result containing a Vec. -///Convenience `decode_config(input, base64::STANDARD);`. +///Decode base64 using the [default engine](DEFAULT_ENGINE), alphabet, and config. +///Returns a `Result` containing a `Vec`. /// ///# Example /// @@ -86,11 +73,11 @@ impl error::Error for DecodeError { ///``` #[cfg(any(feature = "alloc", feature = "std", test))] pub fn decode>(input: T) -> Result, DecodeError> { - decode_config(input, STANDARD) + decode_engine(input, &DEFAULT_ENGINE) } -///Decode from string reference as octets. -///Returns a Result containing a Vec. +///Decode from string reference as octets using the specified [Engine]. +///Returns a `Result` containing a `Vec`. /// ///# Example /// @@ -98,62 +85,89 @@ pub fn decode>(input: T) -> Result, DecodeError> { ///extern crate base64; /// ///fn main() { -/// let bytes = base64::decode_config("aGVsbG8gd29ybGR+Cg==", base64::STANDARD).unwrap(); +/// let bytes = base64::decode_engine( +/// "aGVsbG8gd29ybGR+Cg==", +/// &base64::engine::DEFAULT_ENGINE, +/// ).unwrap(); /// println!("{:?}", bytes); /// -/// let bytes_url = base64::decode_config("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE).unwrap(); +/// // custom engine setup +/// let bytes_url = base64::decode_engine( +/// "aGVsbG8gaW50ZXJuZXR-Cg==", +/// &base64::engine::fast_portable::FastPortable::from( +/// &base64::alphabet::URL_SAFE, +/// base64::engine::fast_portable::PAD), +/// +/// ).unwrap(); /// println!("{:?}", bytes_url); ///} ///``` #[cfg(any(feature = "alloc", feature = "std", test))] -pub fn decode_config>(input: T, config: Config) -> Result, DecodeError> { +pub fn decode_engine>( + input: T, + engine: &E, +) -> Result, DecodeError> { let mut buffer = Vec::::with_capacity(input.as_ref().len() * 4 / 3); - decode_config_buf(input, config, &mut buffer).map(|_| buffer) + decode_engine_vec(input, &mut buffer, engine).map(|_| buffer) } ///Decode from string reference as octets. -///Writes into the supplied buffer to avoid allocation. -///Returns a Result containing an empty tuple, aka (). +///Writes into the supplied `Vec`, which may allocate if its internal buffer isn't big enough. +///Returns a `Result` containing an empty tuple, aka `()`. /// ///# Example /// ///```rust ///extern crate base64; /// +///const URL_SAFE_ENGINE: base64::engine::fast_portable::FastPortable = +/// base64::engine::fast_portable::FastPortable::from( +/// &base64::alphabet::URL_SAFE, +/// base64::engine::fast_portable::PAD); +/// ///fn main() { /// let mut buffer = Vec::::new(); -/// base64::decode_config_buf("aGVsbG8gd29ybGR+Cg==", base64::STANDARD, &mut buffer).unwrap(); +/// // with the default engine +/// base64::decode_engine_vec( +/// "aGVsbG8gd29ybGR+Cg==", +/// &mut buffer, +/// &base64::engine::DEFAULT_ENGINE +/// ).unwrap(); /// println!("{:?}", buffer); /// /// buffer.clear(); /// -/// base64::decode_config_buf("aGVsbG8gaW50ZXJuZXR-Cg==", base64::URL_SAFE, &mut buffer) -/// .unwrap(); +/// // with a custom engine +/// base64::decode_engine_vec( +/// "aGVsbG8gaW50ZXJuZXR-Cg==", +/// &mut buffer, +/// &URL_SAFE_ENGINE +/// ).unwrap(); /// println!("{:?}", buffer); ///} ///``` #[cfg(any(feature = "alloc", feature = "std", test))] -pub fn decode_config_buf>( +pub fn decode_engine_vec<'e, 'o, E: Engine, T: AsRef<[u8]>>( input: T, - config: Config, - buffer: &mut Vec, + buffer: &'o mut Vec, + engine: &'e E, ) -> Result<(), DecodeError> { let input_bytes = input.as_ref(); let starting_output_len = buffer.len(); - let num_chunks = num_chunks(input_bytes); - let decoded_len_estimate = num_chunks - .checked_mul(DECODED_CHUNK_LEN) - .and_then(|p| p.checked_add(starting_output_len)) + let estimate = engine.decoded_length_estimate(input_bytes.len()); + let total_len_estimate = estimate + .decoded_length_estimate() + .checked_add(starting_output_len) .expect("Overflow when calculating output buffer length"); - buffer.resize(decoded_len_estimate, 0); + buffer.resize(total_len_estimate, 0); let bytes_written; { let buffer_slice = &mut buffer.as_mut_slice()[starting_output_len..]; - bytes_written = decode_helper(input_bytes, num_chunks, config, buffer_slice)?; + bytes_written = engine.decode(input_bytes, buffer_slice, estimate)?; } buffer.truncate(starting_output_len + bytes_written); @@ -169,427 +183,35 @@ pub fn decode_config_buf>( /// conservative estimate for the decoded length of an input: 3 bytes of output for every 4 bytes of /// input, rounded up, or in other words `(input_len + 3) / 4 * 3`. /// +/// # Panics +/// /// If the slice is not large enough, this will panic. -pub fn decode_config_slice>( +pub fn decode_engine_slice<'e, 'o, E: Engine, T: AsRef<[u8]>>( input: T, - config: Config, - output: &mut [u8], + output: &'o mut [u8], + engine: &'e E, ) -> Result { let input_bytes = input.as_ref(); - decode_helper(input_bytes, num_chunks(input_bytes), config, output) -} - -/// Return the number of input chunks (including a possibly partial final chunk) in the input -fn num_chunks(input: &[u8]) -> usize { - input - .len() - .checked_add(INPUT_CHUNK_LEN - 1) - .expect("Overflow when calculating number of chunks in input") - / INPUT_CHUNK_LEN -} - -/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs. -/// Returns the number of bytes written, or an error. -// We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is -// inlined(always), a different slow. plain ol' inline makes the benchmarks happiest at the moment, -// but this is fragile and the best setting changes with only minor code modifications. -#[inline] -fn decode_helper( - input: &[u8], - num_chunks: usize, - config: Config, - output: &mut [u8], -) -> Result { - let char_set = config.char_set; - let decode_table = char_set.decode_table(); - - let remainder_len = input.len() % INPUT_CHUNK_LEN; - - // Because the fast decode loop writes in groups of 8 bytes (unrolled to - // CHUNKS_PER_FAST_LOOP_BLOCK times 8 bytes, where possible) and outputs 8 bytes at a time (of - // which only 6 are valid data), we need to be sure that we stop using the fast decode loop - // soon enough that there will always be 2 more bytes of valid data written after that loop. - let trailing_bytes_to_skip = match remainder_len { - // if input is a multiple of the chunk size, ignore the last chunk as it may have padding, - // and the fast decode logic cannot handle padding - 0 => INPUT_CHUNK_LEN, - // 1 and 5 trailing bytes are illegal: can't decode 6 bits of input into a byte - 1 | 5 => { - // trailing whitespace is so common that it's worth it to check the last byte to - // possibly return a better error message - if let Some(b) = input.last() { - if *b != PAD_BYTE && decode_table[*b as usize] == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte(input.len() - 1, *b)); - } - } - - return Err(DecodeError::InvalidLength); - } - // This will decode to one output byte, which isn't enough to overwrite the 2 extra bytes - // written by the fast decode loop. So, we have to ignore both these 2 bytes and the - // previous chunk. - 2 => INPUT_CHUNK_LEN + 2, - // If this is 3 unpadded chars, then it would actually decode to 2 bytes. However, if this - // is an erroneous 2 chars + 1 pad char that would decode to 1 byte, then it should fail - // with an error, not panic from going past the bounds of the output slice, so we let it - // use stage 3 + 4. - 3 => INPUT_CHUNK_LEN + 3, - // This can also decode to one output byte because it may be 2 input chars + 2 padding - // chars, which would decode to 1 byte. - 4 => INPUT_CHUNK_LEN + 4, - // Everything else is a legal decode len (given that we don't require padding), and will - // decode to at least 2 bytes of output. - _ => remainder_len, - }; - - // rounded up to include partial chunks - let mut remaining_chunks = num_chunks; - - let mut input_index = 0; - let mut output_index = 0; - - { - let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip); - - // Fast loop, stage 1 - // manual unroll to CHUNKS_PER_FAST_LOOP_BLOCK of u64s to amortize slice bounds checks - if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_BLOCK_LEN) { - while input_index <= max_start_index { - let input_slice = &input[input_index..(input_index + INPUT_BLOCK_LEN)]; - let output_slice = &mut output[output_index..(output_index + DECODED_BLOCK_LEN)]; - - decode_chunk( - &input_slice[0..], - input_index, - decode_table, - &mut output_slice[0..], - )?; - decode_chunk( - &input_slice[8..], - input_index + 8, - decode_table, - &mut output_slice[6..], - )?; - decode_chunk( - &input_slice[16..], - input_index + 16, - decode_table, - &mut output_slice[12..], - )?; - decode_chunk( - &input_slice[24..], - input_index + 24, - decode_table, - &mut output_slice[18..], - )?; - - input_index += INPUT_BLOCK_LEN; - output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX; - remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK; - } - } - - // Fast loop, stage 2 (aka still pretty fast loop) - // 8 bytes at a time for whatever we didn't do in stage 1. - if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_CHUNK_LEN) { - while input_index < max_start_index { - decode_chunk( - &input[input_index..(input_index + INPUT_CHUNK_LEN)], - input_index, - decode_table, - &mut output - [output_index..(output_index + DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX)], - )?; - - output_index += DECODED_CHUNK_LEN; - input_index += INPUT_CHUNK_LEN; - remaining_chunks -= 1; - } - } - } - - // Stage 3 - // If input length was such that a chunk had to be deferred until after the fast loop - // because decoding it would have produced 2 trailing bytes that wouldn't then be - // overwritten, we decode that chunk here. This way is slower but doesn't write the 2 - // trailing bytes. - // However, we still need to avoid the last chunk (partial or complete) because it could - // have padding, so we always do 1 fewer to avoid the last chunk. - for _ in 1..remaining_chunks { - decode_chunk_precise( - &input[input_index..], - input_index, - decode_table, - &mut output[output_index..(output_index + DECODED_CHUNK_LEN)], - )?; - - input_index += INPUT_CHUNK_LEN; - output_index += DECODED_CHUNK_LEN; - } - - // always have one more (possibly partial) block of 8 input - debug_assert!(input.len() - input_index > 1 || input.is_empty()); - debug_assert!(input.len() - input_index <= 8); - - // Stage 4 - // Finally, decode any leftovers that aren't a complete input block of 8 bytes. - // Use a u64 as a stack-resident 8 byte buffer. - let mut leftover_bits: u64 = 0; - let mut morsels_in_leftover = 0; - let mut padding_bytes = 0; - let mut first_padding_index: usize = 0; - let mut last_symbol = 0_u8; - let start_of_leftovers = input_index; - for (i, b) in input[start_of_leftovers..].iter().enumerate() { - // '=' padding - if *b == PAD_BYTE { - // There can be bad padding in a few ways: - // 1 - Padding with non-padding characters after it - // 2 - Padding after zero or one non-padding characters before it - // in the current quad. - // 3 - More than two characters of padding. If 3 or 4 padding chars - // are in the same quad, that implies it will be caught by #2. - // If it spreads from one quad to another, it will be caught by - // #2 in the second quad. - - if i % 4 < 2 { - // Check for case #2. - let bad_padding_index = start_of_leftovers - + if padding_bytes > 0 { - // If we've already seen padding, report the first padding index. - // This is to be consistent with the faster logic above: it will report an - // error on the first padding character (since it doesn't expect to see - // anything but actual encoded data). - first_padding_index - } else { - // haven't seen padding before, just use where we are now - i - }; - return Err(DecodeError::InvalidByte(bad_padding_index, *b)); - } - - if padding_bytes == 0 { - first_padding_index = i; - } - - padding_bytes += 1; - continue; - } - - // Check for case #1. - // To make '=' handling consistent with the main loop, don't allow - // non-suffix '=' in trailing chunk either. Report error as first - // erroneous padding. - if padding_bytes > 0 { - return Err(DecodeError::InvalidByte( - start_of_leftovers + first_padding_index, - PAD_BYTE, - )); - } - last_symbol = *b; - - // can use up to 8 * 6 = 48 bits of the u64, if last chunk has no padding. - // To minimize shifts, pack the leftovers from left to right. - let shift = 64 - (morsels_in_leftover + 1) * 6; - // tables are all 256 elements, lookup with a u8 index always succeeds - let morsel = decode_table[*b as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte(start_of_leftovers + i, *b)); - } - - leftover_bits |= (morsel as u64) << shift; - morsels_in_leftover += 1; - } - - let leftover_bits_ready_to_append = match morsels_in_leftover { - 0 => 0, - 2 => 8, - 3 => 16, - 4 => 24, - 6 => 32, - 7 => 40, - 8 => 48, - _ => unreachable!( - "Impossible: must only have 0 to 8 input bytes in last chunk, with no invalid lengths" - ), - }; - - // if there are bits set outside the bits we care about, last symbol encodes trailing bits that - // will not be included in the output - let mask = !0 >> leftover_bits_ready_to_append; - if !config.decode_allow_trailing_bits && (leftover_bits & mask) != 0 { - // last morsel is at `morsels_in_leftover` - 1 - return Err(DecodeError::InvalidLastSymbol( - start_of_leftovers + morsels_in_leftover - 1, - last_symbol, - )); - } - - let mut leftover_bits_appended_to_buf = 0; - while leftover_bits_appended_to_buf < leftover_bits_ready_to_append { - // `as` simply truncates the higher bits, which is what we want here - let selected_bits = (leftover_bits >> (56 - leftover_bits_appended_to_buf)) as u8; - output[output_index] = selected_bits; - output_index += 1; - - leftover_bits_appended_to_buf += 8; - } - - Ok(output_index) -} - -#[inline] -fn write_u64(output: &mut [u8], value: u64) { - output[..8].copy_from_slice(&value.to_be_bytes()); -} - -/// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the -/// first 6 of those contain meaningful data. -/// -/// `input` is the bytes to decode, of which the first 8 bytes will be processed. -/// `index_at_start_of_input` is the offset in the overall input (used for reporting errors -/// accurately) -/// `decode_table` is the lookup table for the particular base64 alphabet. -/// `output` will have its first 8 bytes overwritten, of which only the first 6 are valid decoded -/// data. -// yes, really inline (worth 30-50% speedup) -#[inline(always)] -fn decode_chunk( - input: &[u8], - index_at_start_of_input: usize, - decode_table: &[u8; 256], - output: &mut [u8], -) -> Result<(), DecodeError> { - let mut accum: u64; - - let morsel = decode_table[input[0] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); - } - accum = (morsel as u64) << 58; - - let morsel = decode_table[input[1] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 1, - input[1], - )); - } - accum |= (morsel as u64) << 52; - - let morsel = decode_table[input[2] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 2, - input[2], - )); - } - accum |= (morsel as u64) << 46; - - let morsel = decode_table[input[3] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 3, - input[3], - )); - } - accum |= (morsel as u64) << 40; - - let morsel = decode_table[input[4] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 4, - input[4], - )); - } - accum |= (morsel as u64) << 34; - - let morsel = decode_table[input[5] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 5, - input[5], - )); - } - accum |= (morsel as u64) << 28; - - let morsel = decode_table[input[6] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 6, - input[6], - )); - } - accum |= (morsel as u64) << 22; - - let morsel = decode_table[input[7] as usize]; - if morsel == tables::INVALID_VALUE { - return Err(DecodeError::InvalidByte( - index_at_start_of_input + 7, - input[7], - )); - } - accum |= (morsel as u64) << 16; - - write_u64(output, accum); - - Ok(()) -} - -/// Decode an 8-byte chunk, but only write the 6 bytes actually decoded instead of including 2 -/// trailing garbage bytes. -#[inline] -fn decode_chunk_precise( - input: &[u8], - index_at_start_of_input: usize, - decode_table: &[u8; 256], - output: &mut [u8], -) -> Result<(), DecodeError> { - let mut tmp_buf = [0_u8; 8]; - - decode_chunk( - input, - index_at_start_of_input, - decode_table, - &mut tmp_buf[..], - )?; - - output[0..6].copy_from_slice(&tmp_buf[0..6]); - - Ok(()) + engine.decode( + input_bytes, + output, + engine.decoded_length_estimate(input_bytes.len()), + ) } #[cfg(test)] mod tests { use super::*; - use crate::{ - encode::encode_config_buf, - encode::encode_config_slice, - tests::{assert_encode_sanity, random_config}, - }; + use crate::{encode::encode_engine_string, tests::assert_encode_sanity}; + use crate::engine::Config; + use crate::tests::random_engine; use rand::{ distributions::{Distribution, Uniform}, FromEntropy, Rng, }; - #[test] - fn decode_chunk_precise_writes_only_6_bytes() { - let input = b"Zm9vYmFy"; // "foobar" - let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; - decode_chunk_precise(&input[..], 0, tables::STANDARD_DECODE, &mut output).unwrap(); - assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output); - } - - #[test] - fn decode_chunk_writes_8_bytes() { - let input = b"Zm9vYmFy"; // "foobar" - let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; - decode_chunk(&input[..], 0, tables::STANDARD_DECODE, &mut output).unwrap(); - assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 0, 0], &output); - } - #[test] fn decode_into_nonempty_vec_doesnt_clobber_existing_prefix() { let mut orig_data = Vec::new(); @@ -616,9 +238,9 @@ mod tests { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut encoded_data); - assert_encode_sanity(&encoded_data, config, input_len); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut encoded_data, &engine); + assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); let prefix_len = prefix_len_range.sample(&mut rng); @@ -631,9 +253,9 @@ mod tests { decoded_with_prefix.copy_from_slice(&prefix); // decode into the non-empty buf - decode_config_buf(&encoded_data, config, &mut decoded_with_prefix).unwrap(); + decode_engine_vec(&encoded_data, &mut decoded_with_prefix, &engine).unwrap(); // also decode into the empty buf - decode_config_buf(&encoded_data, config, &mut decoded_without_prefix).unwrap(); + decode_engine_vec(&encoded_data, &mut decoded_without_prefix, &engine).unwrap(); assert_eq!( prefix_len + decoded_without_prefix.len(), @@ -671,9 +293,9 @@ mod tests { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut encoded_data); - assert_encode_sanity(&encoded_data, config, input_len); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut encoded_data, &engine); + assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); // fill the buffer with random garbage, long enough to have some room before and after for _ in 0..5000 { @@ -687,7 +309,7 @@ mod tests { // decode into the non-empty buf let decode_bytes_written = - decode_config_slice(&encoded_data, config, &mut decode_buf[offset..]).unwrap(); + decode_engine_slice(&encoded_data, &mut decode_buf[offset..], &engine).unwrap(); assert_eq!(orig_data.len(), decode_bytes_written); assert_eq!( @@ -723,151 +345,18 @@ mod tests { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut encoded_data); - assert_encode_sanity(&encoded_data, config, input_len); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut encoded_data, &engine); + assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); decode_buf.resize(input_len, 0); // decode into the non-empty buf let decode_bytes_written = - decode_config_slice(&encoded_data, config, &mut decode_buf[..]).unwrap(); + decode_engine_slice(&encoded_data, &mut decode_buf[..], &engine).unwrap(); assert_eq!(orig_data.len(), decode_bytes_written); assert_eq!(orig_data, decode_buf); } } - - #[test] - fn detect_invalid_last_symbol_two_bytes() { - let decode = - |input, forgiving| decode_config(input, STANDARD.decode_allow_trailing_bits(forgiving)); - - // example from https://github.com/marshallpierce/rust-base64/issues/75 - assert!(decode("iYU=", false).is_ok()); - // trailing 01 - assert_eq!( - Err(DecodeError::InvalidLastSymbol(2, b'V')), - decode("iYV=", false) - ); - assert_eq!(Ok(vec![137, 133]), decode("iYV=", true)); - // trailing 10 - assert_eq!( - Err(DecodeError::InvalidLastSymbol(2, b'W')), - decode("iYW=", false) - ); - assert_eq!(Ok(vec![137, 133]), decode("iYV=", true)); - // trailing 11 - assert_eq!( - Err(DecodeError::InvalidLastSymbol(2, b'X')), - decode("iYX=", false) - ); - assert_eq!(Ok(vec![137, 133]), decode("iYV=", true)); - - // also works when there are 2 quads in the last block - assert_eq!( - Err(DecodeError::InvalidLastSymbol(6, b'X')), - decode("AAAAiYX=", false) - ); - assert_eq!(Ok(vec![0, 0, 0, 137, 133]), decode("AAAAiYX=", true)); - } - - #[test] - fn detect_invalid_last_symbol_one_byte() { - // 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol - - assert!(decode("/w==").is_ok()); - // trailing 01 - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'x')), decode("/x==")); - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'z')), decode("/z==")); - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'0')), decode("/0==")); - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'9')), decode("/9==")); - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'+')), decode("/+==")); - assert_eq!(Err(DecodeError::InvalidLastSymbol(1, b'/')), decode("//==")); - - // also works when there are 2 quads in the last block - assert_eq!( - Err(DecodeError::InvalidLastSymbol(5, b'x')), - decode("AAAA/x==") - ); - } - - #[test] - fn detect_invalid_last_symbol_every_possible_three_symbols() { - let mut base64_to_bytes = ::std::collections::HashMap::new(); - - let mut bytes = [0_u8; 2]; - for b1 in 0_u16..256 { - bytes[0] = b1 as u8; - for b2 in 0_u16..256 { - bytes[1] = b2 as u8; - let mut b64 = vec![0_u8; 4]; - assert_eq!(4, encode_config_slice(&bytes, STANDARD, &mut b64[..])); - let mut v = ::std::vec::Vec::with_capacity(2); - v.extend_from_slice(&bytes[..]); - - assert!(base64_to_bytes.insert(b64, v).is_none()); - } - } - - // every possible combination of symbols must either decode to 2 bytes or get InvalidLastSymbol - - let mut symbols = [0_u8; 4]; - for &s1 in STANDARD.char_set.encode_table().iter() { - symbols[0] = s1; - for &s2 in STANDARD.char_set.encode_table().iter() { - symbols[1] = s2; - for &s3 in STANDARD.char_set.encode_table().iter() { - symbols[2] = s3; - symbols[3] = PAD_BYTE; - - match base64_to_bytes.get(&symbols[..]) { - Some(bytes) => { - assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD)) - } - None => assert_eq!( - Err(DecodeError::InvalidLastSymbol(2, s3)), - decode_config(&symbols[..], STANDARD) - ), - } - } - } - } - } - - #[test] - fn detect_invalid_last_symbol_every_possible_two_symbols() { - let mut base64_to_bytes = ::std::collections::HashMap::new(); - - for b in 0_u16..256 { - let mut b64 = vec![0_u8; 4]; - assert_eq!(4, encode_config_slice(&[b as u8], STANDARD, &mut b64[..])); - let mut v = ::std::vec::Vec::with_capacity(1); - v.push(b as u8); - - assert!(base64_to_bytes.insert(b64, v).is_none()); - } - - // every possible combination of symbols must either decode to 1 byte or get InvalidLastSymbol - - let mut symbols = [0_u8; 4]; - for &s1 in STANDARD.char_set.encode_table().iter() { - symbols[0] = s1; - for &s2 in STANDARD.char_set.encode_table().iter() { - symbols[1] = s2; - symbols[2] = PAD_BYTE; - symbols[3] = PAD_BYTE; - - match base64_to_bytes.get(&symbols[..]) { - Some(bytes) => { - assert_eq!(Ok(bytes.to_vec()), decode_config(&symbols, STANDARD)) - } - None => assert_eq!( - Err(DecodeError::InvalidLastSymbol(1, s2)), - decode_config(&symbols[..], STANDARD) - ), - } - } - } - } } diff --git a/src/display.rs b/src/display.rs index cc70aac2..e2fa9e26 100644 --- a/src/display.rs +++ b/src/display.rs @@ -2,35 +2,36 @@ //! //! ``` //! use base64::display::Base64Display; +//! use base64::engine::DEFAULT_ENGINE; //! //! let data = vec![0x0, 0x1, 0x2, 0x3]; -//! let wrapper = Base64Display::with_config(&data, base64::STANDARD); +//! let wrapper = Base64Display::from(&data, &DEFAULT_ENGINE); //! //! assert_eq!("base64: AAECAw==", format!("base64: {}", wrapper)); //! ``` use super::chunked_encoder::ChunkedEncoder; -use super::Config; +use crate::engine::Engine; use core::fmt::{Display, Formatter}; use core::{fmt, str}; /// A convenience wrapper for base64'ing bytes into a format string without heap allocation. -pub struct Base64Display<'a> { +pub struct Base64Display<'a, 'e, E: Engine> { bytes: &'a [u8], - chunked_encoder: ChunkedEncoder, + chunked_encoder: ChunkedEncoder<'e, E>, } -impl<'a> Base64Display<'a> { - /// Create a `Base64Display` with the provided config. - pub fn with_config(bytes: &[u8], config: Config) -> Base64Display { +impl<'a, 'e, E: Engine> Base64Display<'a, 'e, E> { + /// Create a `Base64Display` with the provided engine. + pub fn from(bytes: &'a [u8], engine: &'e E) -> Base64Display<'a, 'e, E> { Base64Display { bytes, - chunked_encoder: ChunkedEncoder::new(config), + chunked_encoder: ChunkedEncoder::from(engine), } } } -impl<'a> Display for Base64Display<'a> { +impl<'a, 'e, E: Engine> Display for Base64Display<'a, 'e, E> { fn fmt(&self, formatter: &mut Formatter) -> Result<(), fmt::Error> { let mut sink = FormatterSink { f: formatter }; self.chunked_encoder.encode(self.bytes, &mut sink) @@ -57,18 +58,18 @@ mod tests { use super::super::chunked_encoder::tests::{ chunked_encode_matches_normal_encode_random, SinkTestHelper, }; - use super::super::*; use super::*; + use crate::engine::DEFAULT_ENGINE; #[test] fn basic_display() { assert_eq!( "~$Zm9vYmFy#*", - format!("~${}#*", Base64Display::with_config(b"foobar", STANDARD)) + format!("~${}#*", Base64Display::from(b"foobar", &DEFAULT_ENGINE)) ); assert_eq!( "~$Zm9vYmFyZg==#*", - format!("~${}#*", Base64Display::with_config(b"foobarf", STANDARD)) + format!("~${}#*", Base64Display::from(b"foobarf", &DEFAULT_ENGINE)) ); } @@ -81,8 +82,8 @@ mod tests { struct DisplaySinkTestHelper; impl SinkTestHelper for DisplaySinkTestHelper { - fn encode_to_string(&self, config: Config, bytes: &[u8]) -> String { - format!("{}", Base64Display::with_config(bytes, config)) + fn encode_to_string(&self, engine: &E, bytes: &[u8]) -> String { + format!("{}", Base64Display::from(bytes, engine)) } } } diff --git a/src/encode.rs b/src/encode.rs index e2b4bdff..b4ccb424 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -1,13 +1,14 @@ #[cfg(any(feature = "alloc", feature = "std", test))] -use crate::{chunked_encoder, STANDARD}; -use crate::{Config, PAD_BYTE}; +use crate::chunked_encoder; +#[cfg(any(feature = "alloc", feature = "std", test))] +use crate::engine::DEFAULT_ENGINE; +use crate::engine::{Config, Engine}; +use crate::PAD_BYTE; #[cfg(any(feature = "alloc", feature = "std", test))] use alloc::{string::String, vec}; -use core::convert::TryInto; -///Encode arbitrary octets as base64. -///Returns a String. -///Convenience for `encode_config(input, base64::STANDARD);`. +///Encode arbitrary octets as base64 using the [default engine](DEFAULT_ENGINE), alphabet, and config. +///Returns a `String`. /// ///# Example /// @@ -21,62 +22,86 @@ use core::convert::TryInto; ///``` #[cfg(any(feature = "alloc", feature = "std", test))] pub fn encode>(input: T) -> String { - encode_config(input, STANDARD) + encode_engine(input, &DEFAULT_ENGINE) } -///Encode arbitrary octets as base64. -///Returns a String. +///Encode arbitrary octets as base64 using the provided `Engine`. +///Returns a `String`. /// ///# Example /// ///```rust ///extern crate base64; /// +///const URL_SAFE_ENGINE: base64::engine::fast_portable::FastPortable = +/// base64::engine::fast_portable::FastPortable::from( +/// &base64::alphabet::URL_SAFE, +/// base64::engine::fast_portable::NO_PAD); +/// ///fn main() { -/// let b64 = base64::encode_config(b"hello world~", base64::STANDARD); +/// let b64 = base64::encode_engine( +/// b"hello world~", +/// &base64::engine::DEFAULT_ENGINE +/// ); /// println!("{}", b64); /// -/// let b64_url = base64::encode_config(b"hello internet~", base64::URL_SAFE); +/// let b64_url = base64::encode_engine( +/// b"hello internet~", +/// &URL_SAFE_ENGINE +/// ); /// println!("{}", b64_url); ///} ///``` #[cfg(any(feature = "alloc", feature = "std", test))] -pub fn encode_config>(input: T, config: Config) -> String { - let mut buf = match encoded_size(input.as_ref().len(), config) { - Some(n) => vec![0; n], - None => panic!("integer overflow when calculating buffer size"), - }; +pub fn encode_engine>(input: T, engine: &E) -> String { + let encoded_size = encoded_len(input.as_ref().len(), engine.config().padding()) + .expect("integer overflow when calculating buffer size"); + let mut buf = vec![0; encoded_size]; - encode_with_padding(input.as_ref(), config, buf.len(), &mut buf[..]); + encode_with_padding(input.as_ref(), &mut buf[..], engine, encoded_size); String::from_utf8(buf).expect("Invalid UTF8") } ///Encode arbitrary octets as base64. -///Writes into the supplied output buffer, which will grow the buffer if needed. +///Writes into the supplied `String`, which may allocate if its internal buffer isn't big enough. /// ///# Example /// ///```rust ///extern crate base64; /// +///const URL_SAFE_ENGINE: base64::engine::fast_portable::FastPortable = +/// base64::engine::fast_portable::FastPortable::from( +/// &base64::alphabet::URL_SAFE, +/// base64::engine::fast_portable::NO_PAD); ///fn main() { /// let mut buf = String::new(); -/// base64::encode_config_buf(b"hello world~", base64::STANDARD, &mut buf); +/// base64::encode_engine_string( +/// b"hello world~", +/// &mut buf, +/// &base64::engine::DEFAULT_ENGINE); /// println!("{}", buf); /// /// buf.clear(); -/// base64::encode_config_buf(b"hello internet~", base64::URL_SAFE, &mut buf); +/// base64::encode_engine_string( +/// b"hello internet~", +/// &mut buf, +/// &URL_SAFE_ENGINE); /// println!("{}", buf); ///} ///``` #[cfg(any(feature = "alloc", feature = "std", test))] -pub fn encode_config_buf>(input: T, config: Config, buf: &mut String) { +pub fn encode_engine_string>( + input: T, + output_buf: &mut String, + engine: &E, +) { let input_bytes = input.as_ref(); { - let mut sink = chunked_encoder::StringSink::new(buf); - let encoder = chunked_encoder::ChunkedEncoder::new(config); + let mut sink = chunked_encoder::StringSink::from(output_buf); + let encoder = chunked_encoder::ChunkedEncoder::from(engine); encoder .encode(input_bytes, &mut sink) @@ -105,8 +130,10 @@ pub fn encode_config_buf>(input: T, config: Config, buf: &mut Str /// // make sure we'll have a slice big enough for base64 + padding /// buf.resize(s.len() * 4 / 3 + 4, 0); /// -/// let bytes_written = base64::encode_config_slice(s, -/// base64::STANDARD, &mut buf); +/// let bytes_written = base64::encode_engine_slice( +/// s, +/// &mut buf, +/// &base64::engine::DEFAULT_ENGINE); /// /// // shorten our vec down to just what was written /// buf.resize(bytes_written, 0); @@ -114,15 +141,19 @@ pub fn encode_config_buf>(input: T, config: Config, buf: &mut Str /// assert_eq!(s, base64::decode(&buf).unwrap().as_slice()); /// } /// ``` -pub fn encode_config_slice>(input: T, config: Config, output: &mut [u8]) -> usize { +pub fn encode_engine_slice>( + input: T, + output_buf: &mut [u8], + engine: &E, +) -> usize { let input_bytes = input.as_ref(); - let encoded_size = encoded_size(input_bytes.len(), config) + let encoded_size = encoded_len(input_bytes.len(), engine.config().padding()) .expect("usize overflow when calculating buffer size"); - let mut b64_output = &mut output[0..encoded_size]; + let mut b64_output = &mut output_buf[0..encoded_size]; - encode_with_padding(&input_bytes, config, encoded_size, &mut b64_output); + encode_with_padding(&input_bytes, &mut b64_output, engine, encoded_size); encoded_size } @@ -137,12 +168,17 @@ pub fn encode_config_slice>(input: T, config: Config, output: &mu /// `output` must be of size `encoded_size`. /// /// All bytes in `output` will be written to since it is exactly the size of the output. -fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output: &mut [u8]) { - debug_assert_eq!(encoded_size, output.len()); +fn encode_with_padding( + input: &[u8], + output: &mut [u8], + engine: &E, + expected_encoded_size: usize, +) { + debug_assert_eq!(expected_encoded_size, output.len()); - let b64_bytes_written = encode_to_slice(input, output, config.char_set.encode_table()); + let b64_bytes_written = engine.encode(input, output); - let padding_bytes = if config.pad { + let padding_bytes = if engine.config().padding() { add_padding(input.len(), &mut output[b64_bytes_written..]) } else { 0 @@ -152,144 +188,18 @@ fn encode_with_padding(input: &[u8], config: Config, encoded_size: usize, output .checked_add(padding_bytes) .expect("usize overflow when calculating b64 length"); - debug_assert_eq!(encoded_size, encoded_bytes); -} - -#[inline] -fn read_u64(s: &[u8]) -> u64 { - u64::from_be_bytes(s[..8].try_into().unwrap()) -} - -/// Encode input bytes to utf8 base64 bytes. Does not pad. -/// `output` must be long enough to hold the encoded `input` without padding. -/// Returns the number of bytes written. -#[inline] -pub fn encode_to_slice(input: &[u8], output: &mut [u8], encode_table: &[u8; 64]) -> usize { - let mut input_index: usize = 0; - - const BLOCKS_PER_FAST_LOOP: usize = 4; - const LOW_SIX_BITS: u64 = 0x3F; - - // we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need - // 2 trailing bytes to be available to read.. - let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2); - let mut output_index = 0; - - if last_fast_index > 0 { - while input_index <= last_fast_index { - // Major performance wins from letting the optimizer do the bounds check once, mostly - // on the output side - let input_chunk = &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))]; - let output_chunk = &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)]; - - // Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent - // to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for - // large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect - // that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte - // SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once. - // Plus, single-digit percentage performance differences might well be quite different - // on different hardware. - - let input_u64 = read_u64(&input_chunk[0..]); - - output_chunk[0] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; - output_chunk[1] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; - output_chunk[2] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; - output_chunk[3] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; - output_chunk[4] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; - output_chunk[5] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; - output_chunk[6] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; - output_chunk[7] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - - let input_u64 = read_u64(&input_chunk[6..]); - - output_chunk[8] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; - output_chunk[9] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; - output_chunk[10] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; - output_chunk[11] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; - output_chunk[12] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; - output_chunk[13] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; - output_chunk[14] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; - output_chunk[15] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - - let input_u64 = read_u64(&input_chunk[12..]); - - output_chunk[16] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; - output_chunk[17] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; - output_chunk[18] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; - output_chunk[19] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; - output_chunk[20] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; - output_chunk[21] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; - output_chunk[22] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; - output_chunk[23] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - - let input_u64 = read_u64(&input_chunk[18..]); - - output_chunk[24] = encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; - output_chunk[25] = encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; - output_chunk[26] = encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; - output_chunk[27] = encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; - output_chunk[28] = encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; - output_chunk[29] = encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; - output_chunk[30] = encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; - output_chunk[31] = encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; - - output_index += BLOCKS_PER_FAST_LOOP * 8; - input_index += BLOCKS_PER_FAST_LOOP * 6; - } - } - - // Encode what's left after the fast loop. - - const LOW_SIX_BITS_U8: u8 = 0x3F; - - let rem = input.len() % 3; - let start_of_rem = input.len() - rem; - - // start at the first index not handled by fast loop, which may be 0. - - while input_index < start_of_rem { - let input_chunk = &input[input_index..(input_index + 3)]; - let output_chunk = &mut output[output_index..(output_index + 4)]; - - output_chunk[0] = encode_table[(input_chunk[0] >> 2) as usize]; - output_chunk[1] = - encode_table[((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize]; - output_chunk[2] = - encode_table[((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize]; - output_chunk[3] = encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize]; - - input_index += 3; - output_index += 4; - } - - if rem == 2 { - output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize]; - output[output_index + 1] = encode_table[((input[start_of_rem] << 4 - | input[start_of_rem + 1] >> 4) - & LOW_SIX_BITS_U8) as usize]; - output[output_index + 2] = - encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize]; - output_index += 3; - } else if rem == 1 { - output[output_index] = encode_table[(input[start_of_rem] >> 2) as usize]; - output[output_index + 1] = - encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize]; - output_index += 2; - } - - output_index + debug_assert_eq!(expected_encoded_size, encoded_bytes); } /// calculate the base64 encoded string size, including padding if appropriate -pub fn encoded_size(bytes_len: usize, config: Config) -> Option { +pub fn encoded_len(bytes_len: usize, padding: bool) -> Option { let rem = bytes_len % 3; let complete_input_chunks = bytes_len / 3; let complete_chunk_output = complete_input_chunks.checked_mul(4); if rem > 0 { - if config.pad { + if padding { complete_chunk_output.and_then(|c| c.checked_add(4)) } else { let encoded_rem = match rem { @@ -305,10 +215,12 @@ pub fn encoded_size(bytes_len: usize, config: Config) -> Option { } /// Write padding characters. +/// `input_len` is the size of the original, not encoded, input. /// `output` is the slice where padding should be written, of length at least 2. /// /// Returns the number of padding bytes written. pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize { + // TODO base on encoded len to use cheaper mod by 4 (aka & 7) let rem = input_len % 3; let mut bytes_written = 0; for _ in 0..((3 - rem) % 3) { @@ -323,11 +235,13 @@ pub fn add_padding(input_len: usize, output: &mut [u8]) -> usize { mod tests { use super::*; use crate::{ - decode::decode_config_buf, + decode::decode_engine_vec, tests::{assert_encode_sanity, random_config}, - Config, STANDARD, URL_SAFE_NO_PAD, }; + use crate::alphabet::{IMAP_MUTF7, STANDARD, URL_SAFE}; + use crate::engine::fast_portable::{FastPortable, NO_PAD}; + use crate::tests::random_engine; use rand::{ distributions::{Distribution, Uniform}, FromEntropy, Rng, @@ -335,63 +249,65 @@ mod tests { use std; use std::str; + const URL_SAFE_NO_PAD_ENGINE: FastPortable = FastPortable::from(&URL_SAFE, NO_PAD); + #[test] fn encoded_size_correct_standard() { - assert_encoded_length(0, 0, STANDARD); + assert_encoded_length(0, 0, &DEFAULT_ENGINE, true); - assert_encoded_length(1, 4, STANDARD); - assert_encoded_length(2, 4, STANDARD); - assert_encoded_length(3, 4, STANDARD); + assert_encoded_length(1, 4, &DEFAULT_ENGINE, true); + assert_encoded_length(2, 4, &DEFAULT_ENGINE, true); + assert_encoded_length(3, 4, &DEFAULT_ENGINE, true); - assert_encoded_length(4, 8, STANDARD); - assert_encoded_length(5, 8, STANDARD); - assert_encoded_length(6, 8, STANDARD); + assert_encoded_length(4, 8, &DEFAULT_ENGINE, true); + assert_encoded_length(5, 8, &DEFAULT_ENGINE, true); + assert_encoded_length(6, 8, &DEFAULT_ENGINE, true); - assert_encoded_length(7, 12, STANDARD); - assert_encoded_length(8, 12, STANDARD); - assert_encoded_length(9, 12, STANDARD); + assert_encoded_length(7, 12, &DEFAULT_ENGINE, true); + assert_encoded_length(8, 12, &DEFAULT_ENGINE, true); + assert_encoded_length(9, 12, &DEFAULT_ENGINE, true); - assert_encoded_length(54, 72, STANDARD); + assert_encoded_length(54, 72, &DEFAULT_ENGINE, true); - assert_encoded_length(55, 76, STANDARD); - assert_encoded_length(56, 76, STANDARD); - assert_encoded_length(57, 76, STANDARD); + assert_encoded_length(55, 76, &DEFAULT_ENGINE, true); + assert_encoded_length(56, 76, &DEFAULT_ENGINE, true); + assert_encoded_length(57, 76, &DEFAULT_ENGINE, true); - assert_encoded_length(58, 80, STANDARD); + assert_encoded_length(58, 80, &DEFAULT_ENGINE, true); } #[test] fn encoded_size_correct_no_pad() { - assert_encoded_length(0, 0, URL_SAFE_NO_PAD); + assert_encoded_length(0, 0, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(1, 2, URL_SAFE_NO_PAD); - assert_encoded_length(2, 3, URL_SAFE_NO_PAD); - assert_encoded_length(3, 4, URL_SAFE_NO_PAD); + assert_encoded_length(1, 2, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(2, 3, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(3, 4, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(4, 6, URL_SAFE_NO_PAD); - assert_encoded_length(5, 7, URL_SAFE_NO_PAD); - assert_encoded_length(6, 8, URL_SAFE_NO_PAD); + assert_encoded_length(4, 6, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(5, 7, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(6, 8, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(7, 10, URL_SAFE_NO_PAD); - assert_encoded_length(8, 11, URL_SAFE_NO_PAD); - assert_encoded_length(9, 12, URL_SAFE_NO_PAD); + assert_encoded_length(7, 10, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(8, 11, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(9, 12, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(54, 72, URL_SAFE_NO_PAD); + assert_encoded_length(54, 72, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(55, 74, URL_SAFE_NO_PAD); - assert_encoded_length(56, 75, URL_SAFE_NO_PAD); - assert_encoded_length(57, 76, URL_SAFE_NO_PAD); + assert_encoded_length(55, 74, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(56, 75, &URL_SAFE_NO_PAD_ENGINE, false); + assert_encoded_length(57, 76, &URL_SAFE_NO_PAD_ENGINE, false); - assert_encoded_length(58, 78, URL_SAFE_NO_PAD); + assert_encoded_length(58, 78, &URL_SAFE_NO_PAD_ENGINE, false); } #[test] fn encoded_size_overflow() { - assert_eq!(None, encoded_size(std::usize::MAX, STANDARD)); + assert_eq!(None, encoded_len(std::usize::MAX, true)); } #[test] - fn encode_config_buf_into_nonempty_buffer_doesnt_clobber_prefix() { + fn encode_engine_string_into_nonempty_buffer_doesnt_clobber_prefix() { let mut orig_data = Vec::new(); let mut prefix = String::new(); let mut encoded_data_no_prefix = String::new(); @@ -424,29 +340,37 @@ mod tests { } encoded_data_with_prefix.push_str(&prefix); - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut encoded_data_no_prefix); - encode_config_buf(&orig_data, config, &mut encoded_data_with_prefix); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut encoded_data_no_prefix, &engine); + encode_engine_string(&orig_data, &mut encoded_data_with_prefix, &engine); assert_eq!( encoded_data_no_prefix.len() + prefix_len, encoded_data_with_prefix.len() ); - assert_encode_sanity(&encoded_data_no_prefix, config, input_len); - assert_encode_sanity(&encoded_data_with_prefix[prefix_len..], config, input_len); + assert_encode_sanity( + &encoded_data_no_prefix, + engine.config().padding(), + input_len, + ); + assert_encode_sanity( + &encoded_data_with_prefix[prefix_len..], + engine.config().padding(), + input_len, + ); // append plain encode onto prefix prefix.push_str(&mut encoded_data_no_prefix); assert_eq!(prefix, encoded_data_with_prefix); - decode_config_buf(&encoded_data_no_prefix, config, &mut decoded).unwrap(); + decode_engine_vec(&encoded_data_no_prefix, &mut decoded, &engine).unwrap(); assert_eq!(orig_data, decoded); } } #[test] - fn encode_config_slice_into_nonempty_buffer_doesnt_clobber_suffix() { + fn encode_engine_slice_into_nonempty_buffer_doesnt_clobber_suffix() { let mut orig_data = Vec::new(); let mut encoded_data = Vec::new(); let mut encoded_data_original_state = Vec::new(); @@ -475,18 +399,18 @@ mod tests { encoded_data_original_state.extend_from_slice(&encoded_data); - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); - let encoded_size = encoded_size(input_len, config).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); assert_eq!( encoded_size, - encode_config_slice(&orig_data, config, &mut encoded_data) + encode_engine_slice(&orig_data, &mut encoded_data, &engine) ); assert_encode_sanity( std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), - config, + engine.config().padding(), input_len, ); @@ -495,13 +419,13 @@ mod tests { &encoded_data_original_state[encoded_size..] ); - decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap(); + decode_engine_vec(&encoded_data[0..encoded_size], &mut decoded, &engine).unwrap(); assert_eq!(orig_data, decoded); } } #[test] - fn encode_config_slice_fits_into_precisely_sized_slice() { + fn encode_engine_slice_fits_into_precisely_sized_slice() { let mut orig_data = Vec::new(); let mut encoded_data = Vec::new(); let mut decoded = Vec::new(); @@ -521,24 +445,24 @@ mod tests { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); - let encoded_size = encoded_size(input_len, config).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); encoded_data.resize(encoded_size, 0); assert_eq!( encoded_size, - encode_config_slice(&orig_data, config, &mut encoded_data) + encode_engine_slice(&orig_data, &mut encoded_data, &engine) ); assert_encode_sanity( std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), - config, + engine.config().padding(), input_len, ); - decode_config_buf(&encoded_data[0..encoded_size], config, &mut decoded).unwrap(); + decode_engine_vec(&encoded_data[0..encoded_size], &mut decoded, &engine).unwrap(); assert_eq!(orig_data, decoded); } } @@ -563,17 +487,17 @@ mod tests { } let config = random_config(&mut rng); + let engine = random_engine(&mut rng); // fill up the output buffer with garbage - let encoded_size = encoded_size(input_len, config).unwrap(); + let encoded_size = encoded_len(input_len, config.padding()).unwrap(); for _ in 0..encoded_size { output.push(rng.gen()); } let orig_output_buf = output.to_vec(); - let bytes_written = - encode_to_slice(&input, &mut output, config.char_set.encode_table()); + let bytes_written = engine.encode(&input, &mut output); // make sure the part beyond bytes_written is the same garbage it was before assert_eq!(orig_output_buf[bytes_written..], output[bytes_written..]); @@ -602,17 +526,17 @@ mod tests { input.push(rng.gen()); } - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); // fill up the output buffer with garbage - let encoded_size = encoded_size(input_len, config).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); for _ in 0..encoded_size + 1000 { output.push(rng.gen()); } let orig_output_buf = output.to_vec(); - encode_with_padding(&input, config, encoded_size, &mut output[0..encoded_size]); + encode_with_padding(&input, &mut output[0..encoded_size], &engine, encoded_size); // make sure the part beyond b64 is the same garbage it was before assert_eq!(orig_output_buf[encoded_size..], output[encoded_size..]); @@ -649,8 +573,13 @@ mod tests { } } - fn assert_encoded_length(input_len: usize, encoded_len: usize, config: Config) { - assert_eq!(encoded_len, encoded_size(input_len, config).unwrap()); + fn assert_encoded_length( + input_len: usize, + enc_len: usize, + engine: &E, + padded: bool, + ) { + assert_eq!(enc_len, encoded_len(input_len, padded).unwrap()); let mut bytes: Vec = Vec::new(); let mut rng = rand::rngs::SmallRng::from_entropy(); @@ -659,17 +588,17 @@ mod tests { bytes.push(rng.gen()); } - let encoded = encode_config(&bytes, config); - assert_encode_sanity(&encoded, config, input_len); + let encoded = encode_engine(&bytes, engine); + assert_encode_sanity(&encoded, padded, input_len); - assert_eq!(encoded_len, encoded.len()); + assert_eq!(enc_len, encoded.len()); } #[test] fn encode_imap() { assert_eq!( - encode_config(b"\xFB\xFF", crate::IMAP_MUTF7), - encode_config(b"\xFB\xFF", crate::STANDARD_NO_PAD).replace("/", ",") + encode_engine(b"\xFB\xFF", &FastPortable::from(&IMAP_MUTF7, NO_PAD)), + encode_engine(b"\xFB\xFF", &FastPortable::from(&STANDARD, NO_PAD)).replace("/", ",") ); } } diff --git a/src/engine/fast_portable/decode.rs b/src/engine/fast_portable/decode.rs new file mode 100644 index 00000000..2c8994ca --- /dev/null +++ b/src/engine/fast_portable/decode.rs @@ -0,0 +1,457 @@ +use crate::engine::fast_portable::INVALID_VALUE; +use crate::engine::DecodeEstimate; +use crate::{DecodeError, PAD_BYTE}; + +// decode logic operates on chunks of 8 input bytes without padding +const INPUT_CHUNK_LEN: usize = 8; +const DECODED_CHUNK_LEN: usize = 6; + +// we read a u64 and write a u64, but a u64 of input only yields 6 bytes of output, so the last +// 2 bytes of any output u64 should not be counted as written to (but must be available in a +// slice). +const DECODED_CHUNK_SUFFIX: usize = 2; + +// how many u64's of input to handle at a time +const CHUNKS_PER_FAST_LOOP_BLOCK: usize = 4; + +const INPUT_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * INPUT_CHUNK_LEN; + +// includes the trailing 2 bytes for the final u64 write +const DECODED_BLOCK_LEN: usize = + CHUNKS_PER_FAST_LOOP_BLOCK * DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX; + +/// Estimate with metadata for FastPortable's decode logic +pub struct FastPortableEstimate { + /// Total number of decode chunks, including a possibly partial last chunk + num_chunks: usize, +} + +impl FastPortableEstimate { + pub(crate) fn from(input_len: usize) -> FastPortableEstimate { + FastPortableEstimate { + num_chunks: num_chunks(input_len), + } + } +} + +impl DecodeEstimate for FastPortableEstimate { + fn decoded_length_estimate(&self) -> usize { + self.num_chunks + .checked_mul(DECODED_CHUNK_LEN) + .expect("Overflow when calculating decoded length") + } +} + +/// Helper to avoid duplicating num_chunks calculation, which is costly on short inputs. +/// Returns the number of bytes written, or an error. +// We're on the fragile edge of compiler heuristics here. If this is not inlined, slow. If this is +// inlined(always), a different slow. plain ol' inline makes the benchmarks happiest at the moment, +// but this is fragile and the best setting changes with only minor code modifications. +#[inline] +pub fn decode_helper( + input: &[u8], + estimate: FastPortableEstimate, + output: &mut [u8], + decode_table: &[u8; 256], + decode_allow_trailing_bits: bool, +) -> Result { + let remainder_len = input.len() % INPUT_CHUNK_LEN; + + // Because the fast decode loop writes in groups of 8 bytes (unrolled to + // CHUNKS_PER_FAST_LOOP_BLOCK times 8 bytes, where possible) and outputs 8 bytes at a time (of + // which only 6 are valid data), we need to be sure that we stop using the fast decode loop + // soon enough that there will always be 2 more bytes of valid data written after that loop. + let trailing_bytes_to_skip = match remainder_len { + // if input is a multiple of the chunk size, ignore the last chunk as it may have padding, + // and the fast decode logic cannot handle padding + 0 => INPUT_CHUNK_LEN, + // 1 and 5 trailing bytes are illegal: can't decode 6 bits of input into a byte + 1 | 5 => { + // trailing whitespace is so common that it's worth it to check the last byte to + // possibly return a better error message + if let Some(b) = input.last() { + if *b != PAD_BYTE && decode_table[*b as usize] == INVALID_VALUE { + return Err(DecodeError::InvalidByte(input.len() - 1, *b)); + } + } + + return Err(DecodeError::InvalidLength); + } + // This will decode to one output byte, which isn't enough to overwrite the 2 extra bytes + // written by the fast decode loop. So, we have to ignore both these 2 bytes and the + // previous chunk. + 2 => INPUT_CHUNK_LEN + 2, + // If this is 3 unpadded chars, then it would actually decode to 2 bytes. However, if this + // is an erroneous 2 chars + 1 pad char that would decode to 1 byte, then it should fail + // with an error, not panic from going past the bounds of the output slice, so we let it + // use stage 3 + 4. + 3 => INPUT_CHUNK_LEN + 3, + // This can also decode to one output byte because it may be 2 input chars + 2 padding + // chars, which would decode to 1 byte. + 4 => INPUT_CHUNK_LEN + 4, + // Everything else is a legal decode len (given that we don't require padding), and will + // decode to at least 2 bytes of output. + _ => remainder_len, + }; + + // rounded up to include partial chunks + let mut remaining_chunks = estimate.num_chunks; + + let mut input_index = 0; + let mut output_index = 0; + + { + let length_of_fast_decode_chunks = input.len().saturating_sub(trailing_bytes_to_skip); + + // Fast loop, stage 1 + // manual unroll to CHUNKS_PER_FAST_LOOP_BLOCK of u64s to amortize slice bounds checks + if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_BLOCK_LEN) { + while input_index <= max_start_index { + let input_slice = &input[input_index..(input_index + INPUT_BLOCK_LEN)]; + let output_slice = &mut output[output_index..(output_index + DECODED_BLOCK_LEN)]; + + decode_chunk( + &input_slice[0..], + input_index, + decode_table, + &mut output_slice[0..], + )?; + decode_chunk( + &input_slice[8..], + input_index + 8, + decode_table, + &mut output_slice[6..], + )?; + decode_chunk( + &input_slice[16..], + input_index + 16, + decode_table, + &mut output_slice[12..], + )?; + decode_chunk( + &input_slice[24..], + input_index + 24, + decode_table, + &mut output_slice[18..], + )?; + + input_index += INPUT_BLOCK_LEN; + output_index += DECODED_BLOCK_LEN - DECODED_CHUNK_SUFFIX; + remaining_chunks -= CHUNKS_PER_FAST_LOOP_BLOCK; + } + } + + // Fast loop, stage 2 (aka still pretty fast loop) + // 8 bytes at a time for whatever we didn't do in stage 1. + if let Some(max_start_index) = length_of_fast_decode_chunks.checked_sub(INPUT_CHUNK_LEN) { + while input_index < max_start_index { + decode_chunk( + &input[input_index..(input_index + INPUT_CHUNK_LEN)], + input_index, + decode_table, + &mut output + [output_index..(output_index + DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX)], + )?; + + output_index += DECODED_CHUNK_LEN; + input_index += INPUT_CHUNK_LEN; + remaining_chunks -= 1; + } + } + } + + // Stage 3 + // If input length was such that a chunk had to be deferred until after the fast loop + // because decoding it would have produced 2 trailing bytes that wouldn't then be + // overwritten, we decode that chunk here. This way is slower but doesn't write the 2 + // trailing bytes. + // However, we still need to avoid the last chunk (partial or complete) because it could + // have padding, so we always do 1 fewer to avoid the last chunk. + for _ in 1..remaining_chunks { + decode_chunk_precise( + &input[input_index..], + input_index, + decode_table, + &mut output[output_index..(output_index + DECODED_CHUNK_LEN)], + )?; + + input_index += INPUT_CHUNK_LEN; + output_index += DECODED_CHUNK_LEN; + } + + // always have one more (possibly partial) block of 8 input + debug_assert!(input.len() - input_index > 1 || input.is_empty()); + debug_assert!(input.len() - input_index <= 8); + + // Stage 4 + // Finally, decode any leftovers that aren't a complete input block of 8 bytes. + // Use a u64 as a stack-resident 8 byte buffer. + let mut leftover_bits: u64 = 0; + let mut morsels_in_leftover = 0; + let mut padding_bytes = 0; + let mut first_padding_index: usize = 0; + let mut last_symbol = 0_u8; + let start_of_leftovers = input_index; + for (i, b) in input[start_of_leftovers..].iter().enumerate() { + // '=' padding + if *b == PAD_BYTE { + // There can be bad padding in a few ways: + // 1 - Padding with non-padding characters after it + // 2 - Padding after zero or one non-padding characters before it + // in the current quad. + // 3 - More than two characters of padding. If 3 or 4 padding chars + // are in the same quad, that implies it will be caught by #2. + // If it spreads from one quad to another, it will be an invalid byte + // in the first quad. + + if i % 4 < 2 { + // Check for case #2. + let bad_padding_index = start_of_leftovers + + if padding_bytes > 0 { + // If we've already seen padding, report the first padding index. + // This is to be consistent with the faster logic above: it will report an + // error on the first padding character (since it doesn't expect to see + // anything but actual encoded data). + first_padding_index + } else { + // haven't seen padding before, just use where we are now + i + }; + return Err(DecodeError::InvalidByte(bad_padding_index, *b)); + } + + if padding_bytes == 0 { + first_padding_index = i; + } + + padding_bytes += 1; + continue; + } + + // Check for case #1. + // To make '=' handling consistent with the main loop, don't allow + // non-suffix '=' in trailing chunk either. Report error as first + // erroneous padding. + if padding_bytes > 0 { + return Err(DecodeError::InvalidByte( + start_of_leftovers + first_padding_index, + PAD_BYTE, + )); + } + last_symbol = *b; + + // can use up to 8 * 6 = 48 bits of the u64, if last chunk has no padding. + // Pack the leftovers from left to right. + let shift = 64 - (morsels_in_leftover + 1) * 6; + let morsel = decode_table[*b as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte(start_of_leftovers + i, *b)); + } + + leftover_bits |= (morsel as u64) << shift; + morsels_in_leftover += 1; + } + + // When encoding 1 trailing byte (e.g. 0xFF), 2 base64 bytes ("/w") are needed. + // / is the symbol for 63 (0x3F, bottom 6 bits all set) and w is 48 (0x30, top 2 bits + // of bottom 6 bits set). + // When decoding two symbols back to one trailing byte, any final symbol higher than + // w would still decode to the original byte because we only care about the top two + // bits in the bottom 6, but would be a non-canonical encoding. So, we calculate a + // mask based on how many bits are used for just the canonical encoding, and optionally + // error if any other bits are set. In the example of one encoded byte -> 2 symbols, + // 2 symbols can technically encode 12 bits, but the last 4 are non canonical, and + // useless since there are no more symbols to provide the necessary 4 additional bits + // to finish the second original byte. + + let leftover_bits_ready_to_append = match morsels_in_leftover { + 0 => 0, + 2 => 8, + 3 => 16, + 4 => 24, + 6 => 32, + 7 => 40, + 8 => 48, + _ => unreachable!( + "Impossible: must only have 0 to 8 input bytes in last chunk, with no invalid lengths" + ), + }; + + // if there are bits set outside the bits we care about, last symbol encodes trailing bits that + // will not be included in the output + let mask = !0 >> leftover_bits_ready_to_append; + if !decode_allow_trailing_bits && (leftover_bits & mask) != 0 { + // last morsel is at `morsels_in_leftover` - 1 + return Err(DecodeError::InvalidLastSymbol( + start_of_leftovers + morsels_in_leftover - 1, + last_symbol, + )); + } + + // TODO benchmark simply converting to big endian bytes + let mut leftover_bits_appended_to_buf = 0; + while leftover_bits_appended_to_buf < leftover_bits_ready_to_append { + // `as` simply truncates the higher bits, which is what we want here + let selected_bits = (leftover_bits >> (56 - leftover_bits_appended_to_buf)) as u8; + output[output_index] = selected_bits; + output_index += 1; + + leftover_bits_appended_to_buf += 8; + } + + Ok(output_index) +} + +/// Decode 8 bytes of input into 6 bytes of output. 8 bytes of output will be written, but only the +/// first 6 of those contain meaningful data. +/// +/// `input` is the bytes to decode, of which the first 8 bytes will be processed. +/// `index_at_start_of_input` is the offset in the overall input (used for reporting errors +/// accurately) +/// `decode_table` is the lookup table for the particular base64 alphabet. +/// `output` will have its first 8 bytes overwritten, of which only the first 6 are valid decoded +/// data. +// yes, really inline (worth 30-50% speedup) +#[inline(always)] +fn decode_chunk( + input: &[u8], + index_at_start_of_input: usize, + decode_table: &[u8; 256], + output: &mut [u8], +) -> Result<(), DecodeError> { + let mut accum: u64; + + let morsel = decode_table[input[0] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte(index_at_start_of_input, input[0])); + } + accum = (morsel as u64) << 58; + + let morsel = decode_table[input[1] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 1, + input[1], + )); + } + accum |= (morsel as u64) << 52; + + let morsel = decode_table[input[2] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 2, + input[2], + )); + } + accum |= (morsel as u64) << 46; + + let morsel = decode_table[input[3] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 3, + input[3], + )); + } + accum |= (morsel as u64) << 40; + + let morsel = decode_table[input[4] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 4, + input[4], + )); + } + accum |= (morsel as u64) << 34; + + let morsel = decode_table[input[5] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 5, + input[5], + )); + } + accum |= (morsel as u64) << 28; + + let morsel = decode_table[input[6] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 6, + input[6], + )); + } + accum |= (morsel as u64) << 22; + + let morsel = decode_table[input[7] as usize]; + if morsel == INVALID_VALUE { + return Err(DecodeError::InvalidByte( + index_at_start_of_input + 7, + input[7], + )); + } + accum |= (morsel as u64) << 16; + + write_u64(output, accum); + + Ok(()) +} + +/// Return the number of input chunks (including a possibly partial final chunk) in the input +pub(crate) fn num_chunks(input_len: usize) -> usize { + input_len + .checked_add(INPUT_CHUNK_LEN - 1) + .expect("Overflow when calculating number of chunks in input") + / INPUT_CHUNK_LEN +} + +/// Decode an 8-byte chunk, but only write the 6 bytes actually decoded instead of including 2 +/// trailing garbage bytes. +#[inline] +fn decode_chunk_precise( + input: &[u8], + index_at_start_of_input: usize, + decode_table: &[u8; 256], + output: &mut [u8], +) -> Result<(), DecodeError> { + let mut tmp_buf = [0_u8; 8]; + + decode_chunk( + input, + index_at_start_of_input, + decode_table, + &mut tmp_buf[..], + )?; + + output[0..6].copy_from_slice(&tmp_buf[0..6]); + + Ok(()) +} + +#[inline] +fn write_u64(output: &mut [u8], value: u64) { + output[..8].copy_from_slice(&value.to_be_bytes()); +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::engine::DEFAULT_ENGINE; + + #[test] + fn decode_chunk_precise_writes_only_6_bytes() { + let input = b"Zm9vYmFy"; // "foobar" + let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; + + decode_chunk_precise(&input[..], 0, &DEFAULT_ENGINE.decode_table, &mut output).unwrap(); + assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 6, 7], &output); + } + + #[test] + fn decode_chunk_writes_8_bytes() { + let input = b"Zm9vYmFy"; // "foobar" + let mut output = [0_u8, 1, 2, 3, 4, 5, 6, 7]; + + decode_chunk(&input[..], 0, &DEFAULT_ENGINE.decode_table, &mut output).unwrap(); + assert_eq!(&vec![b'f', b'o', b'o', b'b', b'a', b'r', 0, 0], &output); + } +} diff --git a/src/engine/fast_portable/mod.rs b/src/engine/fast_portable/mod.rs new file mode 100644 index 00000000..ac7b17ba --- /dev/null +++ b/src/engine/fast_portable/mod.rs @@ -0,0 +1,285 @@ +//! Provides the [FastPortable] engine and associated config types. +use crate::alphabet::Alphabet; +use crate::engine::Config; +use crate::DecodeError; +use core::convert::TryInto; + +mod decode; +pub use decode::FastPortableEstimate; + +pub(crate) const INVALID_VALUE: u8 = 255; + +/// A general-purpose base64 engine. +/// +/// - It uses no vector CPU instructions, so it will work on any system. +/// - It is reasonably fast (~2GiB/s). +/// - It is not constant-time, though, so it is vulnerable to timing side-channel attacks. For loading cryptographic keys, etc, it is suggested to use the forthcoming constant-time implementation. +pub struct FastPortable { + encode_table: [u8; 64], + decode_table: [u8; 256], + config: FastPortableConfig, +} + +impl FastPortable { + /// Create a `FastPortable` engine from an [Alphabet]. + /// + /// While not very expensive to initialize, ideally these should be cached + /// if the engine will be used repeatedly. + pub const fn from(alphabet: &Alphabet, config: FastPortableConfig) -> FastPortable { + FastPortable { + encode_table: encode_table(&alphabet), + decode_table: decode_table(&alphabet), + config, + } + } +} + +impl super::Engine for FastPortable { + type Config = FastPortableConfig; + type DecodeEstimate = FastPortableEstimate; + + fn encode(&self, input: &[u8], output: &mut [u8]) -> usize { + let mut input_index: usize = 0; + + const BLOCKS_PER_FAST_LOOP: usize = 4; + const LOW_SIX_BITS: u64 = 0x3F; + + // we read 8 bytes at a time (u64) but only actually consume 6 of those bytes. Thus, we need + // 2 trailing bytes to be available to read.. + let last_fast_index = input.len().saturating_sub(BLOCKS_PER_FAST_LOOP * 6 + 2); + let mut output_index = 0; + + if last_fast_index > 0 { + while input_index <= last_fast_index { + // Major performance wins from letting the optimizer do the bounds check once, mostly + // on the output side + let input_chunk = + &input[input_index..(input_index + (BLOCKS_PER_FAST_LOOP * 6 + 2))]; + let output_chunk = + &mut output[output_index..(output_index + BLOCKS_PER_FAST_LOOP * 8)]; + + // Hand-unrolling for 32 vs 16 or 8 bytes produces yields performance about equivalent + // to unsafe pointer code on a Xeon E5-1650v3. 64 byte unrolling was slightly better for + // large inputs but significantly worse for 50-byte input, unsurprisingly. I suspect + // that it's a not uncommon use case to encode smallish chunks of data (e.g. a 64-byte + // SHA-512 digest), so it would be nice if that fit in the unrolled loop at least once. + // Plus, single-digit percentage performance differences might well be quite different + // on different hardware. + + let input_u64 = read_u64(&input_chunk[0..]); + + output_chunk[0] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; + output_chunk[1] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; + output_chunk[2] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; + output_chunk[3] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; + output_chunk[4] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; + output_chunk[5] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; + output_chunk[6] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; + output_chunk[7] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; + + let input_u64 = read_u64(&input_chunk[6..]); + + output_chunk[8] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; + output_chunk[9] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; + output_chunk[10] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; + output_chunk[11] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; + output_chunk[12] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; + output_chunk[13] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; + output_chunk[14] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; + output_chunk[15] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; + + let input_u64 = read_u64(&input_chunk[12..]); + + output_chunk[16] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; + output_chunk[17] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; + output_chunk[18] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; + output_chunk[19] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; + output_chunk[20] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; + output_chunk[21] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; + output_chunk[22] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; + output_chunk[23] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; + + let input_u64 = read_u64(&input_chunk[18..]); + + output_chunk[24] = self.encode_table[((input_u64 >> 58) & LOW_SIX_BITS) as usize]; + output_chunk[25] = self.encode_table[((input_u64 >> 52) & LOW_SIX_BITS) as usize]; + output_chunk[26] = self.encode_table[((input_u64 >> 46) & LOW_SIX_BITS) as usize]; + output_chunk[27] = self.encode_table[((input_u64 >> 40) & LOW_SIX_BITS) as usize]; + output_chunk[28] = self.encode_table[((input_u64 >> 34) & LOW_SIX_BITS) as usize]; + output_chunk[29] = self.encode_table[((input_u64 >> 28) & LOW_SIX_BITS) as usize]; + output_chunk[30] = self.encode_table[((input_u64 >> 22) & LOW_SIX_BITS) as usize]; + output_chunk[31] = self.encode_table[((input_u64 >> 16) & LOW_SIX_BITS) as usize]; + + output_index += BLOCKS_PER_FAST_LOOP * 8; + input_index += BLOCKS_PER_FAST_LOOP * 6; + } + } + + // Encode what's left after the fast loop. + + const LOW_SIX_BITS_U8: u8 = 0x3F; + + let rem = input.len() % 3; + let start_of_rem = input.len() - rem; + + // start at the first index not handled by fast loop, which may be 0. + + while input_index < start_of_rem { + let input_chunk = &input[input_index..(input_index + 3)]; + let output_chunk = &mut output[output_index..(output_index + 4)]; + + output_chunk[0] = self.encode_table[(input_chunk[0] >> 2) as usize]; + output_chunk[1] = self.encode_table + [((input_chunk[0] << 4 | input_chunk[1] >> 4) & LOW_SIX_BITS_U8) as usize]; + output_chunk[2] = self.encode_table + [((input_chunk[1] << 2 | input_chunk[2] >> 6) & LOW_SIX_BITS_U8) as usize]; + output_chunk[3] = self.encode_table[(input_chunk[2] & LOW_SIX_BITS_U8) as usize]; + + input_index += 3; + output_index += 4; + } + + if rem == 2 { + output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize]; + output[output_index + 1] = + self.encode_table[((input[start_of_rem] << 4 | input[start_of_rem + 1] >> 4) + & LOW_SIX_BITS_U8) as usize]; + output[output_index + 2] = + self.encode_table[((input[start_of_rem + 1] << 2) & LOW_SIX_BITS_U8) as usize]; + output_index += 3; + } else if rem == 1 { + output[output_index] = self.encode_table[(input[start_of_rem] >> 2) as usize]; + output[output_index + 1] = + self.encode_table[((input[start_of_rem] << 4) & LOW_SIX_BITS_U8) as usize]; + output_index += 2; + } + + output_index + } + + fn decoded_length_estimate(&self, input_len: usize) -> Self::DecodeEstimate { + FastPortableEstimate::from(input_len) + } + + fn decode( + &self, + input: &[u8], + output: &mut [u8], + estimate: Self::DecodeEstimate, + ) -> Result { + decode::decode_helper( + input, + estimate, + output, + &self.decode_table, + self.config.decode_allow_trailing_bits, + ) + } + + fn config(&self) -> Self::Config { + self.config + } +} + +/// Returns a table mapping a 6-bit index to the ASCII byte encoding of the index +pub(crate) const fn encode_table(alphabet: &Alphabet) -> [u8; 64] { + // the encode table is just the alphabet: + // 6-bit index lookup -> printable byte + let mut encode_table = [0_u8; 64]; + { + let mut index = 0; + while index < 64 { + encode_table[index] = alphabet.symbols[index]; + index += 1; + } + } + + return encode_table; +} + +/// Returns a table mapping base64 bytes as the lookup index to either: +/// - [INVALID_VALUE] for bytes that aren't members of the alphabet +/// - a byte whose lower 6 bits are the value that was encoded into the index byte +pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] { + let mut decode_table = [INVALID_VALUE; 256]; + + // Since the table is full of `INVALID_VALUE` already, we only need to overwrite + // the parts that are valid. + let mut index = 0; + while index < 64 { + // The index in the alphabet is the 6-bit value we care about. + // Since the index is in 0-63, it is safe to cast to u8. + decode_table[alphabet.symbols[index] as usize] = index as u8; + index += 1; + } + + return decode_table; +} + +#[inline] +fn read_u64(s: &[u8]) -> u64 { + u64::from_be_bytes(s[..8].try_into().unwrap()) +} + +/// Contains miscellaneous configuration parameters for base64 encoding and decoding. +/// +/// To specify the characters used, see [crate::alphabet::Alphabet]. +#[derive(Clone, Copy, Debug)] +pub struct FastPortableConfig { + /// `true` to pad output with `=` characters + padding: bool, + /// `true` to ignore excess nonzero bits in the last few symbols, otherwise an error is returned + decode_allow_trailing_bits: bool, +} + +impl FastPortableConfig { + /// Create a new config. + /// + /// - `padding`: if `true`, encoding will append `=` padding characters to produce an + /// output whose length is a multiple of 4. Padding is not needed for decoding and + /// only serves to waste bytes but it's in the spec. For new applications, consider + /// not using padding. + /// - `decode_allow_trailing_bits`: If unsure, use `false`. + /// Useful if you need to decode base64 produced by a buggy encoder that + /// has bits set in the unused space on the last base64 character as per + /// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode). + /// If invalid trailing bits are present and this `true`, those bits will + /// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted. + pub const fn from(padding: bool, decode_allow_trailing_bits: bool) -> FastPortableConfig { + FastPortableConfig { + padding, + decode_allow_trailing_bits, + } + } + + /// Create a new `Config` based on `self` with an updated `padding` parameter. + pub const fn with_padding(self, padding: bool) -> FastPortableConfig { + FastPortableConfig { padding, ..self } + } + + /// Create a new `Config` based on `self` with an updated `decode_allow_trailing_bits` parameter. + pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> FastPortableConfig { + FastPortableConfig { + decode_allow_trailing_bits: allow, + ..self + } + } +} + +impl Config for FastPortableConfig { + fn padding(&self) -> bool { + self.padding + } +} + +/// Include padding bytes when encoding. +pub const PAD: FastPortableConfig = FastPortableConfig { + padding: true, + decode_allow_trailing_bits: false, +}; + +/// Don't add padding when encoding. +pub const NO_PAD: FastPortableConfig = FastPortableConfig { + padding: false, + decode_allow_trailing_bits: false, +}; diff --git a/src/engine/mod.rs b/src/engine/mod.rs new file mode 100644 index 00000000..03902c5e --- /dev/null +++ b/src/engine/mod.rs @@ -0,0 +1,101 @@ +//! Provides the [Engine] abstraction and out of the box implementations. +use crate::engine::fast_portable::FastPortable; +use crate::{alphabet, DecodeError}; + +pub mod fast_portable; + +#[cfg(test)] +mod naive; + +#[cfg(test)] +mod tests; + +// TODO shared DecodeError or per-impl as an associated type? + +/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. +/// +/// Different implementations offer different characteristics. The library currently ships with +/// a general-purpose [FastPortable] that offers good speed and works on any CPU, with more choices +/// coming later, like a constant-time one when side channel resistance is called for, and vendor-specific vectorized ones for more speed. +/// +/// See [DEFAULT_ENGINE] if you just want standard base64. Otherwise, when possible, it's +/// recommended to store the engine in a `const` so that references to it won't pose any lifetime +/// issues, and to avoid repeating the cost of engine setup. +// When adding an implementation of Engine, include them in the engine test suite: +// - add an implementation of [engine::tests::EngineWrapper] +// - add the implementation to the `all_engines` macro +// All tests run on all engines listed in the macro. +pub trait Engine { + /// The config type used by this engine + type Config: Config; + /// The decode estimate used by this engine + type DecodeEstimate: DecodeEstimate; + + /// Encode the `input` bytes into the `output` buffer based on the mapping in `encode_table`. + /// + /// `output` will be long enough to hold the encoded data. + /// + /// Returns the number of bytes written. + /// + /// No padding should be written; that is handled separately. + /// + /// Must not write any bytes into the output slice other than the encoded data. + fn encode(&self, input: &[u8], output: &mut [u8]) -> usize; + + /// As an optimization, it is sometimes helpful to have a conservative estimate of the decoded + /// size before doing the decoding. + /// + /// The result of this must be passed to [Engine::decode()]. + fn decoded_length_estimate(&self, input_len: usize) -> Self::DecodeEstimate; + + /// Decode `input` base64 bytes into the `output` buffer. + /// + /// `decode_estimate` is the result of [Engine::decoded_length_estimate()], which is passed in to avoid + /// calculating it again (expensive on short inputs).` + /// + /// Returns the number of bytes written to `output`. + /// + /// Each complete 4-byte chunk of encoded data decodes to 3 bytes of decoded data, but this + /// function must also handle the final possibly partial chunk. + /// If the input length is not a multiple of 4, or uses padding bytes to reach a multiple of 4, + /// the trailing 2 or 3 bytes must decode to 1 or 2 bytes, respectively, as per the + /// [RFC](https://tools.ietf.org/html/rfc4648#section-3.5). + /// + /// Decoding must not write any bytes into the output slice other than the decoded data. + fn decode( + &self, + input: &[u8], + output: &mut [u8], + decode_estimate: Self::DecodeEstimate, + ) -> Result; + + /// Returns the config for this engine. + fn config(&self) -> Self::Config; +} + +/// The minimal level of configuration engines must expose. +pub trait Config { + /// Returns `true` if padding should be added after the encoded output. + /// + /// Padding is added outside the engine's encode() since the engine may be used + /// to encode only a chunk of the overall output, so it can't always know when + /// the output is "done" and would therefore need padding (if configured). + // It could be provided as a separate parameter when encoding, but that feels like + // leaking an implementation detail to the user, and it's hopefully more convenient + // to have to only pass one thing (the engine) to any part of the API. + fn padding(&self) -> bool; +} + +/// The decode estimate used by an engine implementation. +/// +/// Implementors may want to store relevant calculations when constructing this to avoid having +/// to calculate them again during actual decoding. +pub trait DecodeEstimate { + /// Returns a conservative (err on the side of too big) estimate of the decoded length to use + /// for pre-allocating buffers, etc. + fn decoded_length_estimate(&self) -> usize; +} + +/// An engine that will work on all CPUs using the standard base64 alphabet. +pub const DEFAULT_ENGINE: FastPortable = + FastPortable::from(&alphabet::STANDARD, fast_portable::PAD); diff --git a/src/engine/naive.rs b/src/engine/naive.rs new file mode 100644 index 00000000..dcc60dd7 --- /dev/null +++ b/src/engine/naive.rs @@ -0,0 +1,307 @@ +use crate::alphabet::Alphabet; +use crate::engine::fast_portable::{decode_table, encode_table}; +use crate::engine::{fast_portable, Config, DecodeEstimate, Engine}; +use crate::{DecodeError, PAD_BYTE}; +use alloc::ops::BitOr; +use std::ops::{BitAnd, Shl, Shr}; + +/// Comparatively simple implementation that can be used as something to compare against in tests +pub struct Naive { + encode_table: [u8; 64], + decode_table: [u8; 256], + config: NaiveConfig, +} + +impl Naive { + const ENCODE_INPUT_CHUNK_SIZE: usize = 3; + const DECODE_INPUT_CHUNK_SIZE: usize = 4; + + pub const fn from(alphabet: &Alphabet, config: NaiveConfig) -> Naive { + Naive { + encode_table: encode_table(&alphabet), + decode_table: decode_table(&alphabet), + config, + } + } + + fn decode_byte_into_u32(&self, offset: usize, byte: u8) -> Result { + let decoded = self.decode_table[byte as usize]; + + if decoded == fast_portable::INVALID_VALUE { + return Err(DecodeError::InvalidByte(offset, byte)); + } + + Ok(decoded as u32) + } +} + +impl Engine for Naive { + type Config = NaiveConfig; + type DecodeEstimate = NaiveEstimate; + + fn encode(&self, input: &[u8], output: &mut [u8]) -> usize { + // complete chunks first + + const LOW_SIX_BITS: u32 = 0x3F; + + let rem = input.len() % Naive::ENCODE_INPUT_CHUNK_SIZE; + // will never underflow + let complete_chunk_len = input.len() - rem; + + let mut input_index = 0_usize; + let mut output_index = 0_usize; + if let Some(last_complete_chunk_index) = + complete_chunk_len.checked_sub(Naive::ENCODE_INPUT_CHUNK_SIZE) + { + while input_index <= last_complete_chunk_index { + let chunk = &input[input_index..input_index + Naive::ENCODE_INPUT_CHUNK_SIZE]; + + // populate low 24 bits from 3 bytes + let chunk_int: u32 = + (chunk[0] as u32).shl(16) | (chunk[1] as u32).shl(8) | (chunk[2] as u32); + // encode 4x 6-bit output bytes + output[output_index] = self.encode_table[chunk_int.shr(18) as usize]; + output[output_index + 1] = + self.encode_table[chunk_int.shr(12_u8).bitand(LOW_SIX_BITS) as usize]; + output[output_index + 2] = + self.encode_table[chunk_int.shr(6_u8).bitand(LOW_SIX_BITS) as usize]; + output[output_index + 3] = + self.encode_table[chunk_int.bitand(LOW_SIX_BITS) as usize]; + + input_index += Naive::ENCODE_INPUT_CHUNK_SIZE; + output_index += 4; + } + } + + // then leftovers + if rem == 2 { + let chunk = &input[input_index..input_index + 2]; + + // high six bits of chunk[0] + output[output_index] = self.encode_table[chunk[0].shr(2) as usize]; + // bottom 2 bits of [0], high 4 bits of [1] + output[output_index + 1] = + self.encode_table[(chunk[0].shl(4_u8).bitor(chunk[1].shr(4_u8)) as u32) + .bitand(LOW_SIX_BITS) as usize]; + // bottom 4 bits of [1], with the 2 bottom bits as zero + output[output_index + 2] = + self.encode_table[(chunk[1].shl(2_u8) as u32).bitand(LOW_SIX_BITS) as usize]; + + output_index += 3; + } else if rem == 1 { + let byte = input[input_index]; + output[output_index] = self.encode_table[byte.shr(2) as usize]; + output[output_index + 1] = + self.encode_table[(byte.shl(4_u8) as u32).bitand(LOW_SIX_BITS) as usize]; + output_index += 2; + } + + output_index + } + + fn decoded_length_estimate(&self, input_len: usize) -> Self::DecodeEstimate { + NaiveEstimate::from(input_len) + } + + fn decode( + &self, + input: &[u8], + output: &mut [u8], + estimate: Self::DecodeEstimate, + ) -> Result { + match estimate.rem { + 1 => { + // trailing whitespace is so common that it's worth it to check the last byte to + // possibly return a better error message + if let Some(b) = input.last() { + if *b != PAD_BYTE + && self.decode_table[*b as usize] == fast_portable::INVALID_VALUE + { + return Err(DecodeError::InvalidByte(input.len() - 1, *b)); + } + } + + return Err(DecodeError::InvalidLength); + } + _ => {} + } + + let mut input_index = 0_usize; + let mut output_index = 0_usize; + const BOTTOM_BYTE: u32 = 0xFF; + + // can only use the main loop on non-trailing chunks + if input.len() > Naive::DECODE_INPUT_CHUNK_SIZE { + // skip the last chunk, whether it's partial or full, since it might + // have padding, and start at the beginning of the chunk before that + let last_complete_chunk_start_index = estimate.complete_chunk_len + - if estimate.rem == 0 { + // Trailing chunk is also full chunk, so there must be at least 2 chunks, and + // this won't underflow + Naive::DECODE_INPUT_CHUNK_SIZE * 2 + } else { + // Trailing chunk is partial, so it's already excluded in + // complete_chunk_len + Naive::DECODE_INPUT_CHUNK_SIZE + }; + + while input_index <= last_complete_chunk_start_index { + let chunk = &input[input_index..input_index + Naive::DECODE_INPUT_CHUNK_SIZE]; + let decoded_int: u32 = self.decode_byte_into_u32(input_index, chunk[0])?.shl(18) + | self + .decode_byte_into_u32(input_index + 1, chunk[1])? + .shl(12) + | self.decode_byte_into_u32(input_index + 2, chunk[2])?.shl(6) + | self.decode_byte_into_u32(input_index + 3, chunk[3])?; + + output[output_index] = decoded_int.shr(16_u8).bitand(BOTTOM_BYTE) as u8; + output[output_index + 1] = decoded_int.shr(8_u8).bitand(BOTTOM_BYTE) as u8; + output[output_index + 2] = decoded_int.bitand(BOTTOM_BYTE) as u8; + + input_index += Naive::DECODE_INPUT_CHUNK_SIZE; + output_index += 3; + } + } + + // handle incomplete chunk -- simplified version of FastPortable + let mut leftover_bits: u32 = 0; + let mut morsels_in_leftover = 0; + let mut padding_bytes = 0; + let mut first_padding_index: usize = 0; + let mut last_symbol = 0_u8; + let start_of_leftovers = input_index; + for (index, byte) in input[start_of_leftovers..].iter().enumerate() { + // '=' padding + if *byte == PAD_BYTE { + // There can be bad padding in a few ways: + // 1 - Padding with non-padding characters after it + // 2 - Padding after zero or one non-padding characters before it + // in the current quad. + // 3 - More than two characters of padding. If 3 or 4 padding chars + // are in the same quad, that implies it will be caught by #2. + // If it spreads from one quad to another, it will be an invalid byte + // in the first quad. + + if index < 2 { + // Check for case #2. + let bad_padding_index = start_of_leftovers + + if padding_bytes > 0 { + // If we've already seen padding, report the first padding index. + // This is to be consistent with the faster logic above: it will report an + // error on the first padding character (since it doesn't expect to see + // anything but actual encoded data). + first_padding_index + } else { + // haven't seen padding before, just use where we are now + index + }; + return Err(DecodeError::InvalidByte(bad_padding_index, *byte)); + } + + if padding_bytes == 0 { + first_padding_index = index; + } + + padding_bytes += 1; + continue; + } + + // Check for case #1. + // To make '=' handling consistent with the main loop, don't allow + // non-suffix '=' in trailing chunk either. Report error as first + // erroneous padding. + if padding_bytes > 0 { + return Err(DecodeError::InvalidByte( + start_of_leftovers + first_padding_index, + PAD_BYTE, + )); + } + last_symbol = *byte; + + // can use up to 4 * 6 = 24 bits of the u32, if last chunk has no padding. + // Pack the leftovers from left to right. + let shift = 32 - (morsels_in_leftover + 1) * 6; + let morsel = self.decode_table[*byte as usize]; + if morsel == fast_portable::INVALID_VALUE { + return Err(DecodeError::InvalidByte(start_of_leftovers + index, *byte)); + } + + leftover_bits |= (morsel as u32) << shift; + morsels_in_leftover += 1; + } + + let leftover_bits_ready_to_append = match morsels_in_leftover { + 0 => 0, + 2 => 8, + 3 => 16, + 4 => 24, + _ => unreachable!( + "Impossible: must only have 0 to 4 input bytes in last chunk, with no invalid lengths" + ), + }; + + // if there are bits set outside the bits we care about, last symbol encodes trailing + // bits that will not be included in the output + let mask = !0 >> leftover_bits_ready_to_append; + if !self.config.decode_allow_trailing_bits && (leftover_bits & mask) != 0 { + // last morsel is at `morsels_in_leftover` - 1 + return Err(DecodeError::InvalidLastSymbol( + start_of_leftovers + morsels_in_leftover - 1, + last_symbol, + )); + } + + let mut leftover_bits_appended_to_buf = 0; + while leftover_bits_appended_to_buf < leftover_bits_ready_to_append { + // `as` simply truncates the higher bits, which is what we want here + let selected_bits = (leftover_bits >> (24 - leftover_bits_appended_to_buf)) as u8; + output[output_index] = selected_bits; + output_index += 1; + + leftover_bits_appended_to_buf += 8; + } + + Ok(output_index) + } + + fn config(&self) -> Self::Config { + self.config + } +} + +pub struct NaiveEstimate { + /// remainder from dividing input by `Naive::DECODE_CHUNK_SIZE` + rem: usize, + /// Number of complete `Naive::DECODE_CHUNK_SIZE`-length chunks + complete_chunk_len: usize, +} + +impl NaiveEstimate { + fn from(input_len: usize) -> NaiveEstimate { + let rem = input_len % Naive::DECODE_INPUT_CHUNK_SIZE; + let complete_chunk_len = input_len - rem; + + NaiveEstimate { + rem, + complete_chunk_len, + } + } +} + +impl DecodeEstimate for NaiveEstimate { + fn decoded_length_estimate(&self) -> usize { + (self.complete_chunk_len + 1) * 3 + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NaiveConfig { + pub padding: bool, + pub decode_allow_trailing_bits: bool, +} + +impl Config for NaiveConfig { + fn padding(&self) -> bool { + self.padding + } +} diff --git a/src/engine/tests.rs b/src/engine/tests.rs new file mode 100644 index 00000000..2142a341 --- /dev/null +++ b/src/engine/tests.rs @@ -0,0 +1,901 @@ +// rstest_reuse template functions have unused variables +#![allow(unused_variables)] + +use rand::{self, distributions::Distribution, distributions::Uniform, FromEntropy, Rng}; +use rstest::rstest; +use rstest_reuse::{apply, template}; +use std::iter; + +use crate::{ + alphabet::STANDARD, + encode, + engine::{fast_portable, naive, Engine}, + tests::random_alphabet, + DecodeError, PAD_BYTE, +}; + +#[template] +#[rstest(engine_wrapper, +case(FastPortableWrapper {}), +case(NaiveWrapper {}), +)] +fn all_engines(engine_wrapper: E) {} + +#[apply(all_engines)] +fn rfc_test_vectors_std_alphabet(engine_wrapper: E) { + let data = vec![ + ("", ""), + ("f", "Zg"), + ("fo", "Zm8"), + ("foo", "Zm9v"), + ("foob", "Zm9vYg"), + ("fooba", "Zm9vYmE"), + ("foobar", "Zm9vYmFy"), + ]; + + let engine = E::standard(); + + for (orig, encoded) in &data { + let mut encode_buf = [0_u8; 8]; + let mut decode_buf = [0_u8; 6]; + + let encode_len = engine.encode(orig.as_bytes(), &mut encode_buf[..]); + + assert_eq!( + encoded, + &std::str::from_utf8(&encode_buf[0..encode_len]).unwrap() + ); + + // unpadded + { + let decode_len = engine + .decode_ez(&encode_buf[0..encode_len], &mut decode_buf[..]) + .unwrap(); + assert_eq!(orig.len(), decode_len); + + assert_eq!( + orig, + &std::str::from_utf8(&decode_buf[0..decode_len]).unwrap() + ); + } + + // padded + { + let _ = encode::add_padding(orig.len(), &mut encode_buf[encode_len..]); + + let decode_len = engine + .decode_ez(&encode_buf[0..encode_len], &mut decode_buf[..]) + .unwrap(); + assert_eq!(orig.len(), decode_len); + + assert_eq!( + orig, + &std::str::from_utf8(&decode_buf[0..decode_len]).unwrap() + ); + } + } +} + +#[apply(all_engines)] +fn roundtrip_random(engine_wrapper: E) { + let mut rng = rand::rngs::SmallRng::from_entropy(); + + let mut orig_data = Vec::::new(); + let mut encode_buf = Vec::::new(); + let mut decode_buf = Vec::::new(); + + let len_range = Uniform::new(1, 1_000); + + for _ in 0..10_000 { + let engine = E::random(&mut rng); + + orig_data.clear(); + encode_buf.clear(); + decode_buf.clear(); + + let (orig_len, _, encoded_len) = generate_random_encoded_data( + &engine, + &mut orig_data, + &mut encode_buf, + &mut rng, + &len_range, + ); + + // exactly the right size + decode_buf.resize(orig_len, 0); + + let dec_len = engine + .decode_ez(&encode_buf[0..encoded_len], &mut decode_buf[..]) + .unwrap(); + + assert_eq!(orig_len, dec_len); + assert_eq!(&orig_data[..], &decode_buf[..dec_len]); + } +} + +#[apply(all_engines)] +fn encode_doesnt_write_extra_bytes(engine_wrapper: E) { + let mut rng = rand::rngs::SmallRng::from_entropy(); + + let mut orig_data = Vec::::new(); + let mut encode_buf = Vec::::new(); + let mut encode_buf_backup = Vec::::new(); + + let len_range = Uniform::new(1, 1_000); + + for _ in 0..10_000 { + let engine = E::random(&mut rng); + + orig_data.clear(); + encode_buf.clear(); + encode_buf_backup.clear(); + + let orig_len = fill_rand(&mut orig_data, &mut rng, &len_range); + let expected_encode_len = engine_encoded_len(orig_len); + encode_buf.resize(expected_encode_len, 0); + + // oversize encode buffer so we can easily tell if it writes anything more than + // just the encoded data + fill_rand_len(&mut encode_buf, &mut rng, (expected_encode_len + 100) * 2); + encode_buf_backup.extend_from_slice(&encode_buf[..]); + + let encoded_len = engine.encode(&orig_data[..], &mut encode_buf[..]); + assert_eq!(expected_encode_len, encoded_len); + + // no writes past what it claimed to write + assert_eq!( + &encode_buf_backup[encoded_len..], + &encode_buf[encoded_len..] + ) + } +} + +#[apply(all_engines)] +fn decode_doesnt_write_extra_bytes(engine_wrapper: E) { + let mut rng = rand::rngs::SmallRng::from_entropy(); + + let mut orig_data = Vec::::new(); + let mut encode_buf = Vec::::new(); + let mut decode_buf = Vec::::new(); + let mut decode_buf_backup = Vec::::new(); + + let len_range = Uniform::new(1, 1_000); + + for _ in 0..10_000 { + let engine = E::random(&mut rng); + + orig_data.clear(); + encode_buf.clear(); + decode_buf.clear(); + decode_buf_backup.clear(); + + let orig_len = fill_rand(&mut orig_data, &mut rng, &len_range); + encode_buf.resize(engine_encoded_len(orig_len), 0); + + let encoded_len = engine.encode(&orig_data[..], &mut encode_buf[..]); + + // oversize decode buffer so we can easily tell if it writes anything more than + // just the decoded data + fill_rand_len(&mut decode_buf, &mut rng, (orig_len + 100) * 2); + decode_buf_backup.extend_from_slice(&decode_buf[..]); + + let dec_len = engine + .decode_ez(&encode_buf[0..encoded_len], &mut decode_buf[..]) + .unwrap(); + + assert_eq!(orig_len, dec_len); + assert_eq!(&orig_data[..], &decode_buf[..dec_len]); + assert_eq!(&decode_buf_backup[dec_len..], &decode_buf[dec_len..]); + } +} + +#[apply(all_engines)] +fn decode_detect_invalid_last_symbol_one_byte(engine_wrapper: E) { + // 0xFF -> "/w==", so all letters > w, 0-9, and '+', '/' should get InvalidLastSymbol + let engine = E::standard(); + + assert_eq!(Ok(vec![0xFF]), engine.decode_ez_str_vec("/w==")); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'x')), + engine.decode_ez_str_vec("/x==") + ); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'z')), + engine.decode_ez_str_vec("/z==") + ); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'0')), + engine.decode_ez_str_vec("/0==") + ); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'9')), + engine.decode_ez_str_vec("/9==") + ); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'+')), + engine.decode_ez_str_vec("/+==") + ); + assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, b'/')), + engine.decode_ez_str_vec("//==") + ); + + // also works when it's not the only chunk + let mut prefix = String::new(); + for _ in 0..50 { + prefix.push_str("AAAA"); + + let mut input = prefix.clone(); + input.push_str("/x=="); + + assert_eq!( + Err(DecodeError::InvalidLastSymbol(prefix.len() + 1, b'x')), + engine.decode_ez_str_vec(input.as_str()) + ); + } +} + +#[apply(all_engines)] +fn decode_detect_invalid_last_symbol_two_bytes(engine_wrapper: E) { + let engine = E::standard(); + + // example from https://github.com/marshallpierce/rust-base64/issues/75 + assert!(engine.decode_ez_str_vec("iYU=").is_ok()); + // trailing 01 + assert_eq!( + Err(DecodeError::InvalidLastSymbol(2, b'V')), + engine.decode_ez_str_vec("iYV=") + ); + // trailing 10 + assert_eq!( + Err(DecodeError::InvalidLastSymbol(2, b'W')), + engine.decode_ez_str_vec("iYW=") + ); + // trailing 11 + assert_eq!( + Err(DecodeError::InvalidLastSymbol(2, b'X')), + engine.decode_ez_str_vec("iYX=") + ); + + // also works when it's not the only chunk + let mut prefix = String::new(); + for _ in 0..50 { + prefix.push_str("AAAA"); + + let mut input = prefix.clone(); + input.push_str("iYX="); + + assert_eq!( + Err(DecodeError::InvalidLastSymbol(prefix.len() + 2, b'X')), + engine.decode_ez_str_vec(input.as_str()) + ); + } +} + +#[apply(all_engines)] +fn decode_detect_invalid_last_symbol_every_possible_two_symbols( + engine_wrapper: E, +) { + let engine = E::standard(); + + let mut base64_to_bytes = ::std::collections::HashMap::new(); + + for b in 0_u16..256 { + let mut b64 = vec![0_u8; 4]; + assert_eq!(2, engine.encode(&[b as u8], &mut b64[..])); + let _ = encode::add_padding(1, &mut b64[2..]); + + assert!(base64_to_bytes.insert(b64, vec![b as u8]).is_none()); + } + + // every possible combination of trailing symbols must either decode to 1 byte or get InvalidLastSymbol, with or without any leading chunks + + let mut prefix = Vec::new(); + for _ in 0..50 { + let mut clone = prefix.clone(); + + let mut symbols = [0_u8; 4]; + for &s1 in STANDARD.symbols.iter() { + symbols[0] = s1; + for &s2 in STANDARD.symbols.iter() { + symbols[1] = s2; + symbols[2] = PAD_BYTE; + symbols[3] = PAD_BYTE; + + // chop off previous symbols + clone.truncate(prefix.len()); + clone.extend_from_slice(&symbols[..]); + let decoded_prefix_len = prefix.len() / 4 * 3; + + match base64_to_bytes.get(&symbols[..]) { + Some(bytes) => { + let res = engine + .decode_ez_vec(&clone) + // remove prefix + .map(|decoded| decoded[decoded_prefix_len..].to_vec()); + + assert_eq!(Ok(bytes.clone()), res) + } + None => assert_eq!( + Err(DecodeError::InvalidLastSymbol(1, s2)), + engine.decode_ez_vec(&symbols[..]) + ), + } + } + } + + prefix.extend_from_slice("AAAA".as_bytes()); + } +} + +#[apply(all_engines)] +fn decode_detect_invalid_last_symbol_every_possible_three_symbols( + engine_wrapper: E, +) { + let engine = E::standard(); + + let mut base64_to_bytes = ::std::collections::HashMap::new(); + + let mut bytes = [0_u8; 2]; + for b1 in 0_u16..256 { + bytes[0] = b1 as u8; + for b2 in 0_u16..256 { + bytes[1] = b2 as u8; + let mut b64 = vec![0_u8; 4]; + assert_eq!(3, engine.encode(&bytes, &mut b64[..])); + let _ = encode::add_padding(2, &mut b64[3..]); + + let mut v = ::std::vec::Vec::with_capacity(2); + v.extend_from_slice(&bytes[..]); + + assert!(base64_to_bytes.insert(b64, v).is_none()); + } + } + + // every possible combination of symbols must either decode to 2 bytes or get InvalidLastSymbol, with or without any leading chunks + + let mut prefix = Vec::new(); + for _ in 0..50 { + let mut input = prefix.clone(); + + let mut symbols = [0_u8; 4]; + for &s1 in STANDARD.symbols.iter() { + symbols[0] = s1; + for &s2 in STANDARD.symbols.iter() { + symbols[1] = s2; + for &s3 in STANDARD.symbols.iter() { + symbols[2] = s3; + symbols[3] = PAD_BYTE; + + // chop off previous symbols + input.truncate(prefix.len()); + input.extend_from_slice(&symbols[..]); + let decoded_prefix_len = prefix.len() / 4 * 3; + + match base64_to_bytes.get(&symbols[..]) { + Some(bytes) => { + let res = engine + .decode_ez_vec(&input) + // remove prefix + .map(|decoded| decoded[decoded_prefix_len..].to_vec()); + + assert_eq!(Ok(bytes.clone()), res) + } + None => assert_eq!( + Err(DecodeError::InvalidLastSymbol(2, s3)), + engine.decode_ez_vec(&symbols[..]) + ), + } + } + } + } + prefix.extend_from_slice("AAAA".as_bytes()); + } +} + +#[apply(all_engines)] +fn decode_invalid_trailing_bits_ignored_when_configured(engine_wrapper: E) { + let strict = E::standard(); + let forgiving = E::standard_forgiving(); + + fn assert_tolerant_decode( + engine: &E, + input: &mut String, + b64_prefix_len: usize, + expected_decode_bytes: Vec, + data: &str, + ) { + let prefixed = prefixed_data(input, b64_prefix_len, data); + let decoded = engine.decode_ez_str_vec(prefixed); + // prefix is always complete chunks + let decoded_prefix_len = b64_prefix_len / 4 * 3; + assert_eq!( + Ok(expected_decode_bytes), + decoded.map(|v| v[decoded_prefix_len..].to_vec()) + ); + } + + let mut prefix = String::new(); + for _ in 0..50 { + let mut input = prefix.clone(); + + // example from https://github.com/marshallpierce/rust-base64/issues/75 + assert!(strict + .decode_ez_str_vec(prefixed_data(&mut input, prefix.len(), "/w==")) + .is_ok()); + assert!(strict + .decode_ez_str_vec(prefixed_data(&mut input, prefix.len(), "iYU=")) + .is_ok()); + // trailing 01 + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/x=="); + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYV="); + // trailing 10 + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/y=="); + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYW="); + // trailing 11 + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![255], "/z=="); + assert_tolerant_decode(&forgiving, &mut input, prefix.len(), vec![137, 133], "iYX="); + + prefix.push_str("AAAA"); + } +} + +#[apply(all_engines)] +fn decode_invalid_byte_error(engine_wrapper: E) { + let mut rng = rand::rngs::SmallRng::from_entropy(); + + let mut orig_data = Vec::::new(); + let mut encode_buf = Vec::::new(); + let mut decode_buf = Vec::::new(); + + let len_range = Uniform::new(1, 1_000); + + for _ in 0..10_000 { + let engine = E::random(&mut rng); + + orig_data.clear(); + encode_buf.clear(); + decode_buf.clear(); + + let (orig_len, encoded_len_just_data, encoded_len_with_padding) = + generate_random_encoded_data( + &engine, + &mut orig_data, + &mut encode_buf, + &mut rng, + &len_range, + ); + + // exactly the right size + decode_buf.resize(orig_len, 0); + + // replace one encoded byte with an invalid byte + let invalid_byte = 0x07; // BEL, non-printing, so never in an alphabet + + let invalid_range = Uniform::new(0, orig_len); + let invalid_index = invalid_range.sample(&mut rng); + encode_buf[invalid_index] = invalid_byte; + + assert_eq!( + Err(DecodeError::InvalidByte(invalid_index, invalid_byte)), + engine.decode_ez( + &encode_buf[0..encoded_len_with_padding], + &mut decode_buf[..], + ) + ); + } +} + +#[apply(all_engines)] +fn decode_single_pad_byte_after_2_chars_in_trailing_quad_ok(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + // weird padding, but should be allowed + s.push_str("Zg="); + + let input_len = num_prefix_quads * 3 + 1; + + assert_eq!(input_len, engine.decode_ez_str_vec(&s).unwrap().len()); + } +} + +//this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3 +#[apply(all_engines)] +fn decode_pad_byte_in_penultimate_quad_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + for num_valid_bytes_penultimate_quad in 0..4 { + // can't have 1 or it would be invalid length + for num_pad_bytes_in_final_quad in 2..4 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + + // varying amounts of padding in the penultimate quad + for _ in 0..num_valid_bytes_penultimate_quad { + s.push_str("A"); + } + // finish penultimate quad with padding + for _ in num_valid_bytes_penultimate_quad..4 { + s.push_str("="); + } + // and more padding in the final quad + for _ in 0..num_pad_bytes_in_final_quad { + s.push_str("="); + } + + // padding should be an invalid byte before the final quad. + // Could argue that the *next* padding byte (in the next quad) is technically the first + // erroneous one, but reporting that accurately is more complex and probably nobody cares + assert_eq!( + DecodeError::InvalidByte( + num_prefix_quads * 4 + num_valid_bytes_penultimate_quad, + b'=', + ), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + } + } +} + +#[apply(all_engines)] +fn decode_bytes_after_padding_in_final_quad_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + for bytes_after_padding in 1..4 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + + // every invalid padding position with a 3-byte final quad: 1 to 3 bytes after padding + for _ in 0..(3 - bytes_after_padding) { + s.push_str("A"); + } + s.push_str("="); + for _ in 0..bytes_after_padding { + s.push_str("A"); + } + + // First (and only) padding byte is invalid. + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4 + (3 - bytes_after_padding), b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + } +} + +#[apply(all_engines)] +fn decode_absurd_pad_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + s.push_str("==Y=Wx===pY=2U====="); + + // first padding byte + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } +} + +#[apply(all_engines)] +fn decode_too_much_padding_returns_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + // add enough padding to ensure that we'll hit all decode stages at the different lengths + for pad_bytes in 1..64 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + let padding: String = iter::repeat("=").take(pad_bytes).collect(); + s.push_str(&padding); + + if pad_bytes % 4 == 1 { + assert_eq!( + DecodeError::InvalidLength, + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } else { + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + } + } +} + +#[apply(all_engines)] +fn decode_padding_followed_by_non_padding_returns_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + for pad_bytes in 0..32 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + let padding: String = iter::repeat("=").take(pad_bytes).collect(); + s.push_str(&padding); + s.push_str("E"); + + if pad_bytes % 4 == 0 { + assert_eq!( + DecodeError::InvalidLength, + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } else { + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + } + } +} + +#[apply(all_engines)] +fn decode_one_char_in_final_quad_with_padding_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + s.push_str("E="); + + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + + // more padding doesn't change the error + s.push_str("="); + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + + s.push_str("="); + assert_eq!( + DecodeError::InvalidByte(num_prefix_quads * 4 + 1, b'='), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } +} + +#[apply(all_engines)] +fn decode_too_few_symbols_in_final_quad_error(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + for final_quad_symbols in 0..2 { + for padding_symbols in 0..(4 - final_quad_symbols) { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + + for _ in 0..final_quad_symbols { + s.push_str("A"); + } + for _ in 0..padding_symbols { + s.push_str("="); + } + + match final_quad_symbols + padding_symbols { + 0 => continue, + 1 => { + assert_eq!( + DecodeError::InvalidLength, + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + _ => { + // error reported at first padding byte + assert_eq!( + DecodeError::InvalidByte( + num_prefix_quads * 4 + final_quad_symbols, + b'=', + ), + engine.decode_ez_str_vec(&s).unwrap_err() + ); + } + } + } + } + } +} + +#[apply(all_engines)] +fn decode_invalid_trailing_bytes(engine_wrapper: E) { + let engine = E::standard(); + + for num_prefix_quads in 0..50 { + let mut s: String = iter::repeat("ABCD").take(num_prefix_quads).collect(); + s.push_str("Cg==\n"); + + // The case of trailing newlines is common enough to warrant a test for a good error + // message. + assert_eq!( + Err(DecodeError::InvalidByte(num_prefix_quads * 4 + 4, b'\n')), + engine.decode_ez_str_vec(&s) + ); + + // extra padding, however, is still InvalidLength + let s = s.replace("\n", "="); + assert_eq!( + Err(DecodeError::InvalidLength), + engine.decode_ez_str_vec(&s) + ); + } +} + +/// Returns a tuple of the original data length, the encoded data length (just data), and the length including padding. +/// +/// Vecs provided should be empty. +fn generate_random_encoded_data>( + engine: &E, + orig_data: &mut Vec, + encode_buf: &mut Vec, + rng: &mut R, + length_distribution: &D, +) -> (usize, usize, usize) { + let padding: bool = rng.gen(); + + let orig_len = fill_rand(orig_data, rng, length_distribution); + let expected_encoded_len = encode::encoded_len(orig_len, padding).unwrap(); + encode_buf.resize(expected_encoded_len, 0); + + let base_encoded_len = engine.encode(&orig_data[..], &mut encode_buf[..]); + + let enc_len_with_padding = if padding { + base_encoded_len + encode::add_padding(orig_len, &mut encode_buf[base_encoded_len..]) + } else { + base_encoded_len + }; + + assert_eq!(expected_encoded_len, enc_len_with_padding); + + (orig_len, base_encoded_len, enc_len_with_padding) +} + +fn engine_encoded_len(len: usize) -> usize { + // engines don't pad + encode::encoded_len(len, false).unwrap() +} + +// fill to a random length +fn fill_rand>( + vec: &mut Vec, + rng: &mut R, + length_distribution: &D, +) -> usize { + let len = length_distribution.sample(rng); + for _ in 0..len { + vec.push(rng.gen()); + } + + len +} + +fn fill_rand_len(vec: &mut Vec, rng: &mut R, len: usize) { + for _ in 0..len { + vec.push(rng.gen()); + } +} + +fn prefixed_data<'i, 'd>( + input_with_prefix: &'i mut String, + prefix_len: usize, + data: &'d str, +) -> &'i str { + input_with_prefix.truncate(prefix_len); + input_with_prefix.push_str(data); + input_with_prefix.as_str() +} + +/// A wrapper to make using engines in rstest fixtures easier. +/// The functions don't need to be instance methods, but rstest does seem +/// to want an instance, so instances are passed to test functions and then ignored. +trait EngineWrapper { + type Engine: Engine; + + /// Return an engine configured for RFC standard base64 + fn standard() -> Self::Engine; + + /// Return an engine configured for RFC standard + fn standard_forgiving() -> Self::Engine; + + fn random(rng: &mut R) -> Self::Engine; +} + +struct FastPortableWrapper {} + +impl EngineWrapper for FastPortableWrapper { + type Engine = fast_portable::FastPortable; + + fn standard() -> Self::Engine { + fast_portable::FastPortable::from(&STANDARD, fast_portable::PAD) + } + + fn standard_forgiving() -> Self::Engine { + fast_portable::FastPortable::from( + &STANDARD, + fast_portable::FastPortableConfig::from(true, true), + ) + } + + fn random(rng: &mut R) -> Self::Engine { + let alphabet = random_alphabet(rng); + + let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen()); + + fast_portable::FastPortable::from(alphabet, config) + } +} + +struct NaiveWrapper {} + +impl EngineWrapper for NaiveWrapper { + type Engine = naive::Naive; + + fn standard() -> Self::Engine { + naive::Naive::from( + &STANDARD, + naive::NaiveConfig { + padding: true, + decode_allow_trailing_bits: false, + }, + ) + } + + fn standard_forgiving() -> Self::Engine { + naive::Naive::from( + &STANDARD, + naive::NaiveConfig { + padding: true, + decode_allow_trailing_bits: true, + }, + ) + } + + fn random(rng: &mut R) -> Self::Engine { + let alphabet = random_alphabet(rng); + + let config = naive::NaiveConfig { + padding: rng.gen(), + decode_allow_trailing_bits: rng.gen(), + }; + + naive::Naive::from(alphabet, config) + } +} + +trait EngineExtensions: Engine { + // a convenience wrapper to avoid the separate estimate call in tests + fn decode_ez(&self, input: &[u8], output: &mut [u8]) -> Result { + let estimate = self.decoded_length_estimate(input.len()); + + self.decode(input, output, estimate) + } + + fn decode_ez_vec(&self, input: &[u8]) -> Result, DecodeError> { + let mut output = Vec::new(); + output.resize((input.len() + 3) / 4 * 3, 0_u8); + + self.decode_ez(input, &mut output[..]).map(|len| { + // shrink as needed + output.resize(len, 0_u8); + output + }) + } + fn decode_ez_str_vec(&self, input: &str) -> Result, DecodeError> { + let mut output = Vec::new(); + output.resize((input.len() + 3) / 4 * 3, 0_u8); + + self.decode_ez(input.as_bytes(), &mut output[..]) + .map(|len| { + // shrink as needed + output.resize(len, 0_u8); + output + }) + } +} + +impl EngineExtensions for E {} diff --git a/src/lib.rs b/src/lib.rs index 6bded160..4fdea298 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,26 @@ -//! # Configs //! -//! There isn't just one type of Base64; that would be too simple. You need to choose a character -//! set (standard, URL-safe, etc) and padding suffix (yes/no). -//! The `Config` struct encapsulates this info. There are some common configs included: `STANDARD`, -//! `URL_SAFE`, etc. You can also make your own `Config` if needed. +//! # Alphabets //! -//! The functions that don't have `config` in the name (e.g. `encode()` and `decode()`) use the -//! `STANDARD` config . +//! An [alphabet::Alphabet] defines what ASCII symbols are used to encode to or decode from. //! -//! The functions that write to a slice (the ones that end in `_slice`) are generally the fastest -//! because they don't need to resize anything. If it fits in your workflow and you care about -//! performance, keep using the same buffer (growing as need be) and use the `_slice` methods for -//! the best performance. +//! Constants in [alphabet] like [alphabet::STANDARD] or [alphabet::URL_SAFE] provide commonly used +//! alphabets, but you can also build your own custom `Alphabet` if needed. +//! +//! # Engines +//! +//! Once you have an `Alphabet`, you can pick which `Engine` you want. A few parts of the public +//! API provide a default, but otherwise the user must provide an `Engine` to use. +//! +//! See [engine::Engine] for more on what engine to choose, or use [engine::DEFAULT_ENGINE] if you +//! just want plain old standard base64 and don't have other requirements. +//! +//! ## Config +//! +//! In addition to an `Alphabet`, constructing an `Engine` also requires an [engine::Config]. Each +//! `Engine` has a corresponding `Config` implementation. +//! +//! [encode()] and [decode()] use the standard alphabet and default engine in an RFC 4648 standard +//! setup. //! //! # Encoding //! @@ -21,11 +30,11 @@ //! | Function | Output | Allocates | //! | ----------------------- | ---------------------------- | ------------------------------ | //! | `encode` | Returns a new `String` | Always | -//! | `encode_config` | Returns a new `String` | Always | -//! | `encode_config_buf` | Appends to provided `String` | Only if `String` needs to grow | -//! | `encode_config_slice` | Writes to provided `&[u8]` | Never | +//! | `encode_engine` | Returns a new `String` | Always | +//! | `encode_engine_string` | Appends to provided `String` | Only if `String` needs to grow | +//! | `encode_engine_slice` | Writes to provided `&[u8]` | Never - fastest | //! -//! All of the encoding functions that take a `Config` will pad as per the config. +//! All of the encoding functions that take an `Engine` will pad as per the engine's config. //! //! # Decoding //! @@ -34,28 +43,36 @@ //! | Function | Output | Allocates | //! | ----------------------- | ----------------------------- | ------------------------------ | //! | `decode` | Returns a new `Vec` | Always | -//! | `decode_config` | Returns a new `Vec` | Always | -//! | `decode_config_buf` | Appends to provided `Vec` | Only if `Vec` needs to grow | -//! | `decode_config_slice` | Writes to provided `&[u8]` | Never | +//! | `decode_engine` | Returns a new `Vec` | Always | +//! | `decode_engine_vec` | Appends to provided `Vec` | Only if `Vec` needs to grow | +//! | `decode_engine_slice` | Writes to provided `&[u8]` | Never - fastest | //! -//! Unlike encoding, where all possible input is valid, decoding can fail (see `DecodeError`). +//! Unlike encoding, where all possible input is valid, decoding can fail (see [DecodeError]). //! //! Input can be invalid because it has invalid characters or invalid padding. (No padding at all is -//! valid, but excess padding is not.) Whitespace in the input is invalid. +//! valid, but excess padding is not.) Whitespace in the input is invalid, just like any other +//! non-base64 byte. //! //! # `Read` and `Write` //! -//! To map a `Read` of b64 bytes to the decoded bytes, wrap a reader (file, network socket, etc) -//! with `base64::read::DecoderReader`. To write raw bytes and have them b64 encoded on the fly, -//! wrap a writer with `base64::write::EncoderWriter`. There is some performance overhead (15% or -//! so) because of the necessary buffer shuffling -- still fast enough that almost nobody cares. -//! Also, these implementations do not heap allocate. +//! To decode a [std::io::Read] of b64 bytes, wrap a reader (file, network socket, etc) with +//! [read::DecoderReader]. +//! +//! To write raw bytes and have them b64 encoded on the fly, wrap a [std::io::Write] with +//! [write::EncoderWriter]. +//! +//! There is some performance overhead (15% or so) because of the necessary buffer shuffling -- +//! still fast enough that almost nobody cares. Also, these implementations do not heap allocate. +//! +//! # `Display` +//! +//! See [display] for how to transparently base64 data via a `Display` implementation. //! //! # Panics //! //! If length calculations result in overflowing `usize`, a panic will result. //! -//! The `_slice` flavors of encode or decode will panic if the provided output slice is too small, +//! The `_slice` flavors of encode or decode will panic if the provided output slice is too small. #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_lossless))] #![deny( @@ -76,170 +93,32 @@ extern crate alloc; #[cfg(any(feature = "std", test))] extern crate std as alloc; +// has to be included at top level because of the way rstest_reuse defines its macros +#[cfg(test)] +use rstest_reuse; + mod chunked_encoder; pub mod display; #[cfg(any(feature = "std", test))] pub mod read; -mod tables; #[cfg(any(feature = "std", test))] pub mod write; +pub mod engine; + +pub mod alphabet; + mod encode; -pub use crate::encode::encode_config_slice; +pub use crate::encode::encode_engine_slice; #[cfg(any(feature = "alloc", feature = "std", test))] -pub use crate::encode::{encode, encode_config, encode_config_buf}; +pub use crate::encode::{encode, encode_engine, encode_engine_string}; mod decode; #[cfg(any(feature = "alloc", feature = "std", test))] -pub use crate::decode::{decode, decode_config, decode_config_buf}; -pub use crate::decode::{decode_config_slice, DecodeError}; +pub use crate::decode::{decode, decode_engine, decode_engine_vec}; +pub use crate::decode::{decode_engine_slice, DecodeError}; #[cfg(test)] mod tests; -/// Available encoding character sets -#[derive(Clone, Copy, Debug)] -pub enum CharacterSet { - /// The standard character set (uses `+` and `/`). - /// - /// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3). - Standard, - /// The URL safe character set (uses `-` and `_`). - /// - /// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4). - UrlSafe, - /// The `crypt(3)` character set (uses `./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`). - /// - /// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses. - Crypt, - /// The bcrypt character set (uses `./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`). - Bcrypt, - /// The character set used in IMAP-modified UTF-7 (uses `+` and `,`). - /// - /// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3) - ImapMutf7, - /// The character set used in BinHex 4.0 files. - /// - /// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt) - BinHex, -} - -impl CharacterSet { - fn encode_table(self) -> &'static [u8; 64] { - match self { - CharacterSet::Standard => tables::STANDARD_ENCODE, - CharacterSet::UrlSafe => tables::URL_SAFE_ENCODE, - CharacterSet::Crypt => tables::CRYPT_ENCODE, - CharacterSet::Bcrypt => tables::BCRYPT_ENCODE, - CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_ENCODE, - CharacterSet::BinHex => tables::BINHEX_ENCODE, - } - } - - fn decode_table(self) -> &'static [u8; 256] { - match self { - CharacterSet::Standard => tables::STANDARD_DECODE, - CharacterSet::UrlSafe => tables::URL_SAFE_DECODE, - CharacterSet::Crypt => tables::CRYPT_DECODE, - CharacterSet::Bcrypt => tables::BCRYPT_DECODE, - CharacterSet::ImapMutf7 => tables::IMAP_MUTF7_DECODE, - CharacterSet::BinHex => tables::BINHEX_DECODE, - } - } -} - -/// Contains configuration parameters for base64 encoding -#[derive(Clone, Copy, Debug)] -pub struct Config { - /// Character set to use - char_set: CharacterSet, - /// True to pad output with `=` characters - pad: bool, - /// True to ignore excess nonzero bits in the last few symbols, otherwise an error is returned. - decode_allow_trailing_bits: bool, -} - -impl Config { - /// Create a new `Config`. - pub const fn new(char_set: CharacterSet, pad: bool) -> Config { - Config { - char_set, - pad, - decode_allow_trailing_bits: false, - } - } - - /// Sets whether to pad output with `=` characters. - pub const fn pad(self, pad: bool) -> Config { - Config { pad, ..self } - } - - /// Sets whether to emit errors for nonzero trailing bits. - /// - /// This is useful when implementing - /// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode). - pub const fn decode_allow_trailing_bits(self, allow: bool) -> Config { - Config { - decode_allow_trailing_bits: allow, - ..self - } - } -} - -/// Standard character set with padding. -pub const STANDARD: Config = Config { - char_set: CharacterSet::Standard, - pad: true, - decode_allow_trailing_bits: false, -}; - -/// Standard character set without padding. -pub const STANDARD_NO_PAD: Config = Config { - char_set: CharacterSet::Standard, - pad: false, - decode_allow_trailing_bits: false, -}; - -/// URL-safe character set with padding -pub const URL_SAFE: Config = Config { - char_set: CharacterSet::UrlSafe, - pad: true, - decode_allow_trailing_bits: false, -}; - -/// URL-safe character set without padding -pub const URL_SAFE_NO_PAD: Config = Config { - char_set: CharacterSet::UrlSafe, - pad: false, - decode_allow_trailing_bits: false, -}; - -/// As per `crypt(3)` requirements -pub const CRYPT: Config = Config { - char_set: CharacterSet::Crypt, - pad: false, - decode_allow_trailing_bits: false, -}; - -/// Bcrypt character set -pub const BCRYPT: Config = Config { - char_set: CharacterSet::Bcrypt, - pad: false, - decode_allow_trailing_bits: false, -}; - -/// IMAP modified UTF-7 requirements -pub const IMAP_MUTF7: Config = Config { - char_set: CharacterSet::ImapMutf7, - pad: false, - decode_allow_trailing_bits: false, -}; - -/// BinHex character set -pub const BINHEX: Config = Config { - char_set: CharacterSet::BinHex, - pad: false, - decode_allow_trailing_bits: false, -}; - const PAD_BYTE: u8 = b'='; diff --git a/src/read/decoder.rs b/src/read/decoder.rs index 9e7babda..74f9d963 100644 --- a/src/read/decoder.rs +++ b/src/read/decoder.rs @@ -1,4 +1,5 @@ -use crate::{decode_config_slice, Config, DecodeError}; +use crate::engine::Engine; +use crate::{decode_engine_slice, DecodeError}; use std::io::Read; use std::{cmp, fmt, io}; @@ -19,8 +20,9 @@ const DECODED_CHUNK_SIZE: usize = 3; /// /// // use a cursor as the simplest possible `Read` -- in real code this is probably a file, etc. /// let mut wrapped_reader = Cursor::new(b"YXNkZg=="); -/// let mut decoder = base64::read::DecoderReader::new( -/// &mut wrapped_reader, base64::STANDARD); +/// let mut decoder = base64::read::DecoderReader::from( +/// &mut wrapped_reader, +/// &base64::engine::DEFAULT_ENGINE); /// /// // handle errors as you normally would /// let mut result = Vec::new(); @@ -29,8 +31,8 @@ const DECODED_CHUNK_SIZE: usize = 3; /// assert_eq!(b"asdf", &result[..]); /// /// ``` -pub struct DecoderReader { - config: Config, +pub struct DecoderReader<'e, E: Engine, R: io::Read> { + engine: &'e E, /// Where b64 data is read from inner: R, @@ -54,10 +56,9 @@ pub struct DecoderReader { total_b64_decoded: usize, } -impl fmt::Debug for DecoderReader { +impl<'e, E: Engine, R: io::Read> fmt::Debug for DecoderReader<'e, E, R> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("DecoderReader") - .field("config", &self.config) .field("b64_offset", &self.b64_offset) .field("b64_len", &self.b64_len) .field("decoded_buffer", &self.decoded_buffer) @@ -68,11 +69,11 @@ impl fmt::Debug for DecoderReader { } } -impl DecoderReader { - /// Create a new decoder that will read from the provided reader. - pub fn new(reader: R, config: Config) -> Self { +impl<'e, E: Engine, R: io::Read> DecoderReader<'e, E, R> { + /// Create a new decoder that will read from the provided reader `r`. + pub fn from(reader: R, engine: &'e E) -> Self { DecoderReader { - config, + engine, inner: reader, b64_buffer: [0; BUF_SIZE], b64_offset: 0, @@ -132,10 +133,10 @@ impl DecoderReader { debug_assert!(self.b64_offset + self.b64_len <= BUF_SIZE); debug_assert!(buf.len() > 0); - let decoded = decode_config_slice( + let decoded = decode_engine_slice( &self.b64_buffer[self.b64_offset..self.b64_offset + num_bytes], - self.config, &mut buf[..], + self.engine, ) .map_err(|e| match e { DecodeError::InvalidByte(offset, byte) => { @@ -168,7 +169,7 @@ impl DecoderReader { } } -impl Read for DecoderReader { +impl<'e, E: Engine, R: Read> Read for DecoderReader<'e, E, R> { /// Decode input from the wrapped reader. /// /// Under non-error circumstances, this returns `Ok` with the value being the number of bytes diff --git a/src/read/decoder_tests.rs b/src/read/decoder_tests.rs index 265d423a..46aa9cd0 100644 --- a/src/read/decoder_tests.rs +++ b/src/read/decoder_tests.rs @@ -4,9 +4,11 @@ use rand::{Rng, RngCore}; use std::{cmp, iter}; use super::decoder::{DecoderReader, BUF_SIZE}; -use crate::encode::encode_config_buf; -use crate::tests::random_config; -use crate::{decode_config_buf, DecodeError, STANDARD}; +use crate::encode::encode_engine_string; +use crate::engine::fast_portable::FastPortable; +use crate::engine::DEFAULT_ENGINE; +use crate::tests::{random_alphabet, random_config, random_engine}; +use crate::{decode_engine_vec, DecodeError}; #[test] fn simple() { @@ -27,7 +29,7 @@ fn simple() { // Read n bytes at a time. for n in 1..base64data.len() + 1 { let mut wrapped_reader = io::Cursor::new(base64data); - let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD); + let mut decoder = DecoderReader::from(&mut wrapped_reader, &DEFAULT_ENGINE); // handle errors as you normally would let mut text_got = Vec::new(); @@ -59,7 +61,7 @@ fn trailing_junk() { // Read n bytes at a time. for n in 1..base64data.len() + 1 { let mut wrapped_reader = io::Cursor::new(base64data); - let mut decoder = DecoderReader::new(&mut wrapped_reader, STANDARD); + let mut decoder = DecoderReader::from(&mut wrapped_reader, &DEFAULT_ENGINE); // handle errors as you normally would let mut buffer = vec![0u8; n]; @@ -98,8 +100,8 @@ fn handles_short_read_from_delegate() { rng.fill_bytes(&mut bytes[..size]); assert_eq!(size, bytes.len()); - let config = random_config(&mut rng); - encode_config_buf(&bytes[..], config, &mut b64); + let engine = random_engine(&mut rng); + encode_engine_string(&bytes[..], &mut b64, &engine); let mut wrapped_reader = io::Cursor::new(b64.as_bytes()); let mut short_reader = RandomShortRead { @@ -107,7 +109,7 @@ fn handles_short_read_from_delegate() { rng: &mut rng, }; - let mut decoder = DecoderReader::new(&mut short_reader, config); + let mut decoder = DecoderReader::from(&mut short_reader, &engine); let decoded_len = decoder.read_to_end(&mut decoded).unwrap(); assert_eq!(size, decoded_len); @@ -135,12 +137,12 @@ fn read_in_short_increments() { rng.fill_bytes(&mut bytes[..]); assert_eq!(size, bytes.len()); - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); - encode_config_buf(&bytes[..], config, &mut b64); + encode_engine_string(&bytes[..], &mut b64, &engine); let mut wrapped_reader = io::Cursor::new(&b64[..]); - let mut decoder = DecoderReader::new(&mut wrapped_reader, config); + let mut decoder = DecoderReader::from(&mut wrapped_reader, &engine); consume_with_short_reads_and_validate(&mut rng, &bytes[..], &mut decoded, &mut decoder); } @@ -166,12 +168,12 @@ fn read_in_short_increments_with_short_delegate_reads() { rng.fill_bytes(&mut bytes[..]); assert_eq!(size, bytes.len()); - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); - encode_config_buf(&bytes[..], config, &mut b64); + encode_engine_string(&bytes[..], &mut b64, &engine); let mut base_reader = io::Cursor::new(&b64[..]); - let mut decoder = DecoderReader::new(&mut base_reader, config); + let mut decoder = DecoderReader::from(&mut base_reader, &engine); let mut short_reader = RandomShortRead { delegate: &mut decoder, rng: &mut rand::thread_rng(), @@ -201,26 +203,26 @@ fn reports_invalid_last_symbol_correctly() { rng.fill_bytes(&mut bytes[..]); assert_eq!(size, bytes.len()); - let mut config = random_config(&mut rng); + let config = random_config(&mut rng); + let alphabet = random_alphabet(&mut rng); // changing padding will cause invalid padding errors when we twiddle the last byte - config.pad = false; - - encode_config_buf(&bytes[..], config, &mut b64); + let engine = FastPortable::from(alphabet, config.with_padding(false)); + encode_engine_string(&bytes[..], &mut b64, &engine); b64_bytes.extend(b64.bytes()); assert_eq!(b64_bytes.len(), b64.len()); // change the last character to every possible symbol. Should behave the same as bulk // decoding whether invalid or valid. - for &s1 in config.char_set.encode_table().iter() { + for &s1 in alphabet.symbols.iter() { decoded.clear(); bulk_decoded.clear(); // replace the last *b64_bytes.last_mut().unwrap() = s1; - let bulk_res = decode_config_buf(&b64_bytes[..], config, &mut bulk_decoded); + let bulk_res = decode_engine_vec(&b64_bytes[..], &mut bulk_decoded, &engine); let mut wrapped_reader = io::Cursor::new(&b64_bytes[..]); - let mut decoder = DecoderReader::new(&mut wrapped_reader, config); + let mut decoder = DecoderReader::from(&mut wrapped_reader, &engine); let stream_res = decoder.read_to_end(&mut decoded).map(|_| ()).map_err(|e| { e.into_inner() @@ -249,15 +251,16 @@ fn reports_invalid_byte_correctly() { rng.fill_bytes(&mut bytes[..size]); assert_eq!(size, bytes.len()); - let config = random_config(&mut rng); - encode_config_buf(&bytes[..], config, &mut b64); + let engine = random_engine(&mut rng); + + encode_engine_string(&bytes[..], &mut b64, &engine); // replace one byte, somewhere, with '*', which is invalid let bad_byte_pos = rng.gen_range(0, &b64.len()); let mut b64_bytes = b64.bytes().collect::>(); b64_bytes[bad_byte_pos] = b'*'; let mut wrapped_reader = io::Cursor::new(b64_bytes.clone()); - let mut decoder = DecoderReader::new(&mut wrapped_reader, config); + let mut decoder = DecoderReader::from(&mut wrapped_reader, &engine); // some gymnastics to avoid double-moving the io::Error, which is not Copy let read_decode_err = decoder @@ -273,7 +276,7 @@ fn reports_invalid_byte_correctly() { .and_then(|o| o); let mut bulk_buf = Vec::new(); - let bulk_decode_err = decode_config_buf(&b64_bytes[..], config, &mut bulk_buf).err(); + let bulk_decode_err = decode_engine_vec(&b64_bytes[..], &mut bulk_buf, &engine).err(); // it's tricky to predict where the invalid data's offset will be since if it's in the last // chunk it will be reported at the first padding location because it's treated as invalid diff --git a/src/tables.rs b/src/tables.rs deleted file mode 100644 index a45851cd..00000000 --- a/src/tables.rs +++ /dev/null @@ -1,1957 +0,0 @@ -pub const INVALID_VALUE: u8 = 255; -#[rustfmt::skip] -pub const STANDARD_ENCODE: &[u8; 64] = &[ - 65, // input 0 (0x0) => 'A' (0x41) - 66, // input 1 (0x1) => 'B' (0x42) - 67, // input 2 (0x2) => 'C' (0x43) - 68, // input 3 (0x3) => 'D' (0x44) - 69, // input 4 (0x4) => 'E' (0x45) - 70, // input 5 (0x5) => 'F' (0x46) - 71, // input 6 (0x6) => 'G' (0x47) - 72, // input 7 (0x7) => 'H' (0x48) - 73, // input 8 (0x8) => 'I' (0x49) - 74, // input 9 (0x9) => 'J' (0x4A) - 75, // input 10 (0xA) => 'K' (0x4B) - 76, // input 11 (0xB) => 'L' (0x4C) - 77, // input 12 (0xC) => 'M' (0x4D) - 78, // input 13 (0xD) => 'N' (0x4E) - 79, // input 14 (0xE) => 'O' (0x4F) - 80, // input 15 (0xF) => 'P' (0x50) - 81, // input 16 (0x10) => 'Q' (0x51) - 82, // input 17 (0x11) => 'R' (0x52) - 83, // input 18 (0x12) => 'S' (0x53) - 84, // input 19 (0x13) => 'T' (0x54) - 85, // input 20 (0x14) => 'U' (0x55) - 86, // input 21 (0x15) => 'V' (0x56) - 87, // input 22 (0x16) => 'W' (0x57) - 88, // input 23 (0x17) => 'X' (0x58) - 89, // input 24 (0x18) => 'Y' (0x59) - 90, // input 25 (0x19) => 'Z' (0x5A) - 97, // input 26 (0x1A) => 'a' (0x61) - 98, // input 27 (0x1B) => 'b' (0x62) - 99, // input 28 (0x1C) => 'c' (0x63) - 100, // input 29 (0x1D) => 'd' (0x64) - 101, // input 30 (0x1E) => 'e' (0x65) - 102, // input 31 (0x1F) => 'f' (0x66) - 103, // input 32 (0x20) => 'g' (0x67) - 104, // input 33 (0x21) => 'h' (0x68) - 105, // input 34 (0x22) => 'i' (0x69) - 106, // input 35 (0x23) => 'j' (0x6A) - 107, // input 36 (0x24) => 'k' (0x6B) - 108, // input 37 (0x25) => 'l' (0x6C) - 109, // input 38 (0x26) => 'm' (0x6D) - 110, // input 39 (0x27) => 'n' (0x6E) - 111, // input 40 (0x28) => 'o' (0x6F) - 112, // input 41 (0x29) => 'p' (0x70) - 113, // input 42 (0x2A) => 'q' (0x71) - 114, // input 43 (0x2B) => 'r' (0x72) - 115, // input 44 (0x2C) => 's' (0x73) - 116, // input 45 (0x2D) => 't' (0x74) - 117, // input 46 (0x2E) => 'u' (0x75) - 118, // input 47 (0x2F) => 'v' (0x76) - 119, // input 48 (0x30) => 'w' (0x77) - 120, // input 49 (0x31) => 'x' (0x78) - 121, // input 50 (0x32) => 'y' (0x79) - 122, // input 51 (0x33) => 'z' (0x7A) - 48, // input 52 (0x34) => '0' (0x30) - 49, // input 53 (0x35) => '1' (0x31) - 50, // input 54 (0x36) => '2' (0x32) - 51, // input 55 (0x37) => '3' (0x33) - 52, // input 56 (0x38) => '4' (0x34) - 53, // input 57 (0x39) => '5' (0x35) - 54, // input 58 (0x3A) => '6' (0x36) - 55, // input 59 (0x3B) => '7' (0x37) - 56, // input 60 (0x3C) => '8' (0x38) - 57, // input 61 (0x3D) => '9' (0x39) - 43, // input 62 (0x3E) => '+' (0x2B) - 47, // input 63 (0x3F) => '/' (0x2F) -]; -#[rustfmt::skip] -pub const STANDARD_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - INVALID_VALUE, // input 33 (0x21) - INVALID_VALUE, // input 34 (0x22) - INVALID_VALUE, // input 35 (0x23) - INVALID_VALUE, // input 36 (0x24) - INVALID_VALUE, // input 37 (0x25) - INVALID_VALUE, // input 38 (0x26) - INVALID_VALUE, // input 39 (0x27) - INVALID_VALUE, // input 40 (0x28) - INVALID_VALUE, // input 41 (0x29) - INVALID_VALUE, // input 42 (0x2A) - 62, // input 43 (0x2B char '+') => 62 (0x3E) - INVALID_VALUE, // input 44 (0x2C) - INVALID_VALUE, // input 45 (0x2D) - INVALID_VALUE, // input 46 (0x2E) - 63, // input 47 (0x2F char '/') => 63 (0x3F) - 52, // input 48 (0x30 char '0') => 52 (0x34) - 53, // input 49 (0x31 char '1') => 53 (0x35) - 54, // input 50 (0x32 char '2') => 54 (0x36) - 55, // input 51 (0x33 char '3') => 55 (0x37) - 56, // input 52 (0x34 char '4') => 56 (0x38) - 57, // input 53 (0x35 char '5') => 57 (0x39) - 58, // input 54 (0x36 char '6') => 58 (0x3A) - 59, // input 55 (0x37 char '7') => 59 (0x3B) - 60, // input 56 (0x38 char '8') => 60 (0x3C) - 61, // input 57 (0x39 char '9') => 61 (0x3D) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - INVALID_VALUE, // input 64 (0x40) - 0, // input 65 (0x41 char 'A') => 0 (0x0) - 1, // input 66 (0x42 char 'B') => 1 (0x1) - 2, // input 67 (0x43 char 'C') => 2 (0x2) - 3, // input 68 (0x44 char 'D') => 3 (0x3) - 4, // input 69 (0x45 char 'E') => 4 (0x4) - 5, // input 70 (0x46 char 'F') => 5 (0x5) - 6, // input 71 (0x47 char 'G') => 6 (0x6) - 7, // input 72 (0x48 char 'H') => 7 (0x7) - 8, // input 73 (0x49 char 'I') => 8 (0x8) - 9, // input 74 (0x4A char 'J') => 9 (0x9) - 10, // input 75 (0x4B char 'K') => 10 (0xA) - 11, // input 76 (0x4C char 'L') => 11 (0xB) - 12, // input 77 (0x4D char 'M') => 12 (0xC) - 13, // input 78 (0x4E char 'N') => 13 (0xD) - 14, // input 79 (0x4F char 'O') => 14 (0xE) - 15, // input 80 (0x50 char 'P') => 15 (0xF) - 16, // input 81 (0x51 char 'Q') => 16 (0x10) - 17, // input 82 (0x52 char 'R') => 17 (0x11) - 18, // input 83 (0x53 char 'S') => 18 (0x12) - 19, // input 84 (0x54 char 'T') => 19 (0x13) - 20, // input 85 (0x55 char 'U') => 20 (0x14) - 21, // input 86 (0x56 char 'V') => 21 (0x15) - 22, // input 87 (0x57 char 'W') => 22 (0x16) - 23, // input 88 (0x58 char 'X') => 23 (0x17) - 24, // input 89 (0x59 char 'Y') => 24 (0x18) - 25, // input 90 (0x5A char 'Z') => 25 (0x19) - INVALID_VALUE, // input 91 (0x5B) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - INVALID_VALUE, // input 95 (0x5F) - INVALID_VALUE, // input 96 (0x60) - 26, // input 97 (0x61 char 'a') => 26 (0x1A) - 27, // input 98 (0x62 char 'b') => 27 (0x1B) - 28, // input 99 (0x63 char 'c') => 28 (0x1C) - 29, // input 100 (0x64 char 'd') => 29 (0x1D) - 30, // input 101 (0x65 char 'e') => 30 (0x1E) - 31, // input 102 (0x66 char 'f') => 31 (0x1F) - 32, // input 103 (0x67 char 'g') => 32 (0x20) - 33, // input 104 (0x68 char 'h') => 33 (0x21) - 34, // input 105 (0x69 char 'i') => 34 (0x22) - 35, // input 106 (0x6A char 'j') => 35 (0x23) - 36, // input 107 (0x6B char 'k') => 36 (0x24) - 37, // input 108 (0x6C char 'l') => 37 (0x25) - 38, // input 109 (0x6D char 'm') => 38 (0x26) - 39, // input 110 (0x6E char 'n') => 39 (0x27) - 40, // input 111 (0x6F char 'o') => 40 (0x28) - 41, // input 112 (0x70 char 'p') => 41 (0x29) - 42, // input 113 (0x71 char 'q') => 42 (0x2A) - 43, // input 114 (0x72 char 'r') => 43 (0x2B) - 44, // input 115 (0x73 char 's') => 44 (0x2C) - 45, // input 116 (0x74 char 't') => 45 (0x2D) - 46, // input 117 (0x75 char 'u') => 46 (0x2E) - 47, // input 118 (0x76 char 'v') => 47 (0x2F) - 48, // input 119 (0x77 char 'w') => 48 (0x30) - 49, // input 120 (0x78 char 'x') => 49 (0x31) - 50, // input 121 (0x79 char 'y') => 50 (0x32) - 51, // input 122 (0x7A char 'z') => 51 (0x33) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; -#[rustfmt::skip] -pub const URL_SAFE_ENCODE: &[u8; 64] = &[ - 65, // input 0 (0x0) => 'A' (0x41) - 66, // input 1 (0x1) => 'B' (0x42) - 67, // input 2 (0x2) => 'C' (0x43) - 68, // input 3 (0x3) => 'D' (0x44) - 69, // input 4 (0x4) => 'E' (0x45) - 70, // input 5 (0x5) => 'F' (0x46) - 71, // input 6 (0x6) => 'G' (0x47) - 72, // input 7 (0x7) => 'H' (0x48) - 73, // input 8 (0x8) => 'I' (0x49) - 74, // input 9 (0x9) => 'J' (0x4A) - 75, // input 10 (0xA) => 'K' (0x4B) - 76, // input 11 (0xB) => 'L' (0x4C) - 77, // input 12 (0xC) => 'M' (0x4D) - 78, // input 13 (0xD) => 'N' (0x4E) - 79, // input 14 (0xE) => 'O' (0x4F) - 80, // input 15 (0xF) => 'P' (0x50) - 81, // input 16 (0x10) => 'Q' (0x51) - 82, // input 17 (0x11) => 'R' (0x52) - 83, // input 18 (0x12) => 'S' (0x53) - 84, // input 19 (0x13) => 'T' (0x54) - 85, // input 20 (0x14) => 'U' (0x55) - 86, // input 21 (0x15) => 'V' (0x56) - 87, // input 22 (0x16) => 'W' (0x57) - 88, // input 23 (0x17) => 'X' (0x58) - 89, // input 24 (0x18) => 'Y' (0x59) - 90, // input 25 (0x19) => 'Z' (0x5A) - 97, // input 26 (0x1A) => 'a' (0x61) - 98, // input 27 (0x1B) => 'b' (0x62) - 99, // input 28 (0x1C) => 'c' (0x63) - 100, // input 29 (0x1D) => 'd' (0x64) - 101, // input 30 (0x1E) => 'e' (0x65) - 102, // input 31 (0x1F) => 'f' (0x66) - 103, // input 32 (0x20) => 'g' (0x67) - 104, // input 33 (0x21) => 'h' (0x68) - 105, // input 34 (0x22) => 'i' (0x69) - 106, // input 35 (0x23) => 'j' (0x6A) - 107, // input 36 (0x24) => 'k' (0x6B) - 108, // input 37 (0x25) => 'l' (0x6C) - 109, // input 38 (0x26) => 'm' (0x6D) - 110, // input 39 (0x27) => 'n' (0x6E) - 111, // input 40 (0x28) => 'o' (0x6F) - 112, // input 41 (0x29) => 'p' (0x70) - 113, // input 42 (0x2A) => 'q' (0x71) - 114, // input 43 (0x2B) => 'r' (0x72) - 115, // input 44 (0x2C) => 's' (0x73) - 116, // input 45 (0x2D) => 't' (0x74) - 117, // input 46 (0x2E) => 'u' (0x75) - 118, // input 47 (0x2F) => 'v' (0x76) - 119, // input 48 (0x30) => 'w' (0x77) - 120, // input 49 (0x31) => 'x' (0x78) - 121, // input 50 (0x32) => 'y' (0x79) - 122, // input 51 (0x33) => 'z' (0x7A) - 48, // input 52 (0x34) => '0' (0x30) - 49, // input 53 (0x35) => '1' (0x31) - 50, // input 54 (0x36) => '2' (0x32) - 51, // input 55 (0x37) => '3' (0x33) - 52, // input 56 (0x38) => '4' (0x34) - 53, // input 57 (0x39) => '5' (0x35) - 54, // input 58 (0x3A) => '6' (0x36) - 55, // input 59 (0x3B) => '7' (0x37) - 56, // input 60 (0x3C) => '8' (0x38) - 57, // input 61 (0x3D) => '9' (0x39) - 45, // input 62 (0x3E) => '-' (0x2D) - 95, // input 63 (0x3F) => '_' (0x5F) -]; -#[rustfmt::skip] -pub const URL_SAFE_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - INVALID_VALUE, // input 33 (0x21) - INVALID_VALUE, // input 34 (0x22) - INVALID_VALUE, // input 35 (0x23) - INVALID_VALUE, // input 36 (0x24) - INVALID_VALUE, // input 37 (0x25) - INVALID_VALUE, // input 38 (0x26) - INVALID_VALUE, // input 39 (0x27) - INVALID_VALUE, // input 40 (0x28) - INVALID_VALUE, // input 41 (0x29) - INVALID_VALUE, // input 42 (0x2A) - INVALID_VALUE, // input 43 (0x2B) - INVALID_VALUE, // input 44 (0x2C) - 62, // input 45 (0x2D char '-') => 62 (0x3E) - INVALID_VALUE, // input 46 (0x2E) - INVALID_VALUE, // input 47 (0x2F) - 52, // input 48 (0x30 char '0') => 52 (0x34) - 53, // input 49 (0x31 char '1') => 53 (0x35) - 54, // input 50 (0x32 char '2') => 54 (0x36) - 55, // input 51 (0x33 char '3') => 55 (0x37) - 56, // input 52 (0x34 char '4') => 56 (0x38) - 57, // input 53 (0x35 char '5') => 57 (0x39) - 58, // input 54 (0x36 char '6') => 58 (0x3A) - 59, // input 55 (0x37 char '7') => 59 (0x3B) - 60, // input 56 (0x38 char '8') => 60 (0x3C) - 61, // input 57 (0x39 char '9') => 61 (0x3D) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - INVALID_VALUE, // input 64 (0x40) - 0, // input 65 (0x41 char 'A') => 0 (0x0) - 1, // input 66 (0x42 char 'B') => 1 (0x1) - 2, // input 67 (0x43 char 'C') => 2 (0x2) - 3, // input 68 (0x44 char 'D') => 3 (0x3) - 4, // input 69 (0x45 char 'E') => 4 (0x4) - 5, // input 70 (0x46 char 'F') => 5 (0x5) - 6, // input 71 (0x47 char 'G') => 6 (0x6) - 7, // input 72 (0x48 char 'H') => 7 (0x7) - 8, // input 73 (0x49 char 'I') => 8 (0x8) - 9, // input 74 (0x4A char 'J') => 9 (0x9) - 10, // input 75 (0x4B char 'K') => 10 (0xA) - 11, // input 76 (0x4C char 'L') => 11 (0xB) - 12, // input 77 (0x4D char 'M') => 12 (0xC) - 13, // input 78 (0x4E char 'N') => 13 (0xD) - 14, // input 79 (0x4F char 'O') => 14 (0xE) - 15, // input 80 (0x50 char 'P') => 15 (0xF) - 16, // input 81 (0x51 char 'Q') => 16 (0x10) - 17, // input 82 (0x52 char 'R') => 17 (0x11) - 18, // input 83 (0x53 char 'S') => 18 (0x12) - 19, // input 84 (0x54 char 'T') => 19 (0x13) - 20, // input 85 (0x55 char 'U') => 20 (0x14) - 21, // input 86 (0x56 char 'V') => 21 (0x15) - 22, // input 87 (0x57 char 'W') => 22 (0x16) - 23, // input 88 (0x58 char 'X') => 23 (0x17) - 24, // input 89 (0x59 char 'Y') => 24 (0x18) - 25, // input 90 (0x5A char 'Z') => 25 (0x19) - INVALID_VALUE, // input 91 (0x5B) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - 63, // input 95 (0x5F char '_') => 63 (0x3F) - INVALID_VALUE, // input 96 (0x60) - 26, // input 97 (0x61 char 'a') => 26 (0x1A) - 27, // input 98 (0x62 char 'b') => 27 (0x1B) - 28, // input 99 (0x63 char 'c') => 28 (0x1C) - 29, // input 100 (0x64 char 'd') => 29 (0x1D) - 30, // input 101 (0x65 char 'e') => 30 (0x1E) - 31, // input 102 (0x66 char 'f') => 31 (0x1F) - 32, // input 103 (0x67 char 'g') => 32 (0x20) - 33, // input 104 (0x68 char 'h') => 33 (0x21) - 34, // input 105 (0x69 char 'i') => 34 (0x22) - 35, // input 106 (0x6A char 'j') => 35 (0x23) - 36, // input 107 (0x6B char 'k') => 36 (0x24) - 37, // input 108 (0x6C char 'l') => 37 (0x25) - 38, // input 109 (0x6D char 'm') => 38 (0x26) - 39, // input 110 (0x6E char 'n') => 39 (0x27) - 40, // input 111 (0x6F char 'o') => 40 (0x28) - 41, // input 112 (0x70 char 'p') => 41 (0x29) - 42, // input 113 (0x71 char 'q') => 42 (0x2A) - 43, // input 114 (0x72 char 'r') => 43 (0x2B) - 44, // input 115 (0x73 char 's') => 44 (0x2C) - 45, // input 116 (0x74 char 't') => 45 (0x2D) - 46, // input 117 (0x75 char 'u') => 46 (0x2E) - 47, // input 118 (0x76 char 'v') => 47 (0x2F) - 48, // input 119 (0x77 char 'w') => 48 (0x30) - 49, // input 120 (0x78 char 'x') => 49 (0x31) - 50, // input 121 (0x79 char 'y') => 50 (0x32) - 51, // input 122 (0x7A char 'z') => 51 (0x33) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; -#[rustfmt::skip] -pub const CRYPT_ENCODE: &[u8; 64] = &[ - 46, // input 0 (0x0) => '.' (0x2E) - 47, // input 1 (0x1) => '/' (0x2F) - 48, // input 2 (0x2) => '0' (0x30) - 49, // input 3 (0x3) => '1' (0x31) - 50, // input 4 (0x4) => '2' (0x32) - 51, // input 5 (0x5) => '3' (0x33) - 52, // input 6 (0x6) => '4' (0x34) - 53, // input 7 (0x7) => '5' (0x35) - 54, // input 8 (0x8) => '6' (0x36) - 55, // input 9 (0x9) => '7' (0x37) - 56, // input 10 (0xA) => '8' (0x38) - 57, // input 11 (0xB) => '9' (0x39) - 65, // input 12 (0xC) => 'A' (0x41) - 66, // input 13 (0xD) => 'B' (0x42) - 67, // input 14 (0xE) => 'C' (0x43) - 68, // input 15 (0xF) => 'D' (0x44) - 69, // input 16 (0x10) => 'E' (0x45) - 70, // input 17 (0x11) => 'F' (0x46) - 71, // input 18 (0x12) => 'G' (0x47) - 72, // input 19 (0x13) => 'H' (0x48) - 73, // input 20 (0x14) => 'I' (0x49) - 74, // input 21 (0x15) => 'J' (0x4A) - 75, // input 22 (0x16) => 'K' (0x4B) - 76, // input 23 (0x17) => 'L' (0x4C) - 77, // input 24 (0x18) => 'M' (0x4D) - 78, // input 25 (0x19) => 'N' (0x4E) - 79, // input 26 (0x1A) => 'O' (0x4F) - 80, // input 27 (0x1B) => 'P' (0x50) - 81, // input 28 (0x1C) => 'Q' (0x51) - 82, // input 29 (0x1D) => 'R' (0x52) - 83, // input 30 (0x1E) => 'S' (0x53) - 84, // input 31 (0x1F) => 'T' (0x54) - 85, // input 32 (0x20) => 'U' (0x55) - 86, // input 33 (0x21) => 'V' (0x56) - 87, // input 34 (0x22) => 'W' (0x57) - 88, // input 35 (0x23) => 'X' (0x58) - 89, // input 36 (0x24) => 'Y' (0x59) - 90, // input 37 (0x25) => 'Z' (0x5A) - 97, // input 38 (0x26) => 'a' (0x61) - 98, // input 39 (0x27) => 'b' (0x62) - 99, // input 40 (0x28) => 'c' (0x63) - 100, // input 41 (0x29) => 'd' (0x64) - 101, // input 42 (0x2A) => 'e' (0x65) - 102, // input 43 (0x2B) => 'f' (0x66) - 103, // input 44 (0x2C) => 'g' (0x67) - 104, // input 45 (0x2D) => 'h' (0x68) - 105, // input 46 (0x2E) => 'i' (0x69) - 106, // input 47 (0x2F) => 'j' (0x6A) - 107, // input 48 (0x30) => 'k' (0x6B) - 108, // input 49 (0x31) => 'l' (0x6C) - 109, // input 50 (0x32) => 'm' (0x6D) - 110, // input 51 (0x33) => 'n' (0x6E) - 111, // input 52 (0x34) => 'o' (0x6F) - 112, // input 53 (0x35) => 'p' (0x70) - 113, // input 54 (0x36) => 'q' (0x71) - 114, // input 55 (0x37) => 'r' (0x72) - 115, // input 56 (0x38) => 's' (0x73) - 116, // input 57 (0x39) => 't' (0x74) - 117, // input 58 (0x3A) => 'u' (0x75) - 118, // input 59 (0x3B) => 'v' (0x76) - 119, // input 60 (0x3C) => 'w' (0x77) - 120, // input 61 (0x3D) => 'x' (0x78) - 121, // input 62 (0x3E) => 'y' (0x79) - 122, // input 63 (0x3F) => 'z' (0x7A) -]; -#[rustfmt::skip] -pub const CRYPT_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - INVALID_VALUE, // input 33 (0x21) - INVALID_VALUE, // input 34 (0x22) - INVALID_VALUE, // input 35 (0x23) - INVALID_VALUE, // input 36 (0x24) - INVALID_VALUE, // input 37 (0x25) - INVALID_VALUE, // input 38 (0x26) - INVALID_VALUE, // input 39 (0x27) - INVALID_VALUE, // input 40 (0x28) - INVALID_VALUE, // input 41 (0x29) - INVALID_VALUE, // input 42 (0x2A) - INVALID_VALUE, // input 43 (0x2B) - INVALID_VALUE, // input 44 (0x2C) - INVALID_VALUE, // input 45 (0x2D) - 0, // input 46 (0x2E char '.') => 0 (0x0) - 1, // input 47 (0x2F char '/') => 1 (0x1) - 2, // input 48 (0x30 char '0') => 2 (0x2) - 3, // input 49 (0x31 char '1') => 3 (0x3) - 4, // input 50 (0x32 char '2') => 4 (0x4) - 5, // input 51 (0x33 char '3') => 5 (0x5) - 6, // input 52 (0x34 char '4') => 6 (0x6) - 7, // input 53 (0x35 char '5') => 7 (0x7) - 8, // input 54 (0x36 char '6') => 8 (0x8) - 9, // input 55 (0x37 char '7') => 9 (0x9) - 10, // input 56 (0x38 char '8') => 10 (0xA) - 11, // input 57 (0x39 char '9') => 11 (0xB) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - INVALID_VALUE, // input 64 (0x40) - 12, // input 65 (0x41 char 'A') => 12 (0xC) - 13, // input 66 (0x42 char 'B') => 13 (0xD) - 14, // input 67 (0x43 char 'C') => 14 (0xE) - 15, // input 68 (0x44 char 'D') => 15 (0xF) - 16, // input 69 (0x45 char 'E') => 16 (0x10) - 17, // input 70 (0x46 char 'F') => 17 (0x11) - 18, // input 71 (0x47 char 'G') => 18 (0x12) - 19, // input 72 (0x48 char 'H') => 19 (0x13) - 20, // input 73 (0x49 char 'I') => 20 (0x14) - 21, // input 74 (0x4A char 'J') => 21 (0x15) - 22, // input 75 (0x4B char 'K') => 22 (0x16) - 23, // input 76 (0x4C char 'L') => 23 (0x17) - 24, // input 77 (0x4D char 'M') => 24 (0x18) - 25, // input 78 (0x4E char 'N') => 25 (0x19) - 26, // input 79 (0x4F char 'O') => 26 (0x1A) - 27, // input 80 (0x50 char 'P') => 27 (0x1B) - 28, // input 81 (0x51 char 'Q') => 28 (0x1C) - 29, // input 82 (0x52 char 'R') => 29 (0x1D) - 30, // input 83 (0x53 char 'S') => 30 (0x1E) - 31, // input 84 (0x54 char 'T') => 31 (0x1F) - 32, // input 85 (0x55 char 'U') => 32 (0x20) - 33, // input 86 (0x56 char 'V') => 33 (0x21) - 34, // input 87 (0x57 char 'W') => 34 (0x22) - 35, // input 88 (0x58 char 'X') => 35 (0x23) - 36, // input 89 (0x59 char 'Y') => 36 (0x24) - 37, // input 90 (0x5A char 'Z') => 37 (0x25) - INVALID_VALUE, // input 91 (0x5B) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - INVALID_VALUE, // input 95 (0x5F) - INVALID_VALUE, // input 96 (0x60) - 38, // input 97 (0x61 char 'a') => 38 (0x26) - 39, // input 98 (0x62 char 'b') => 39 (0x27) - 40, // input 99 (0x63 char 'c') => 40 (0x28) - 41, // input 100 (0x64 char 'd') => 41 (0x29) - 42, // input 101 (0x65 char 'e') => 42 (0x2A) - 43, // input 102 (0x66 char 'f') => 43 (0x2B) - 44, // input 103 (0x67 char 'g') => 44 (0x2C) - 45, // input 104 (0x68 char 'h') => 45 (0x2D) - 46, // input 105 (0x69 char 'i') => 46 (0x2E) - 47, // input 106 (0x6A char 'j') => 47 (0x2F) - 48, // input 107 (0x6B char 'k') => 48 (0x30) - 49, // input 108 (0x6C char 'l') => 49 (0x31) - 50, // input 109 (0x6D char 'm') => 50 (0x32) - 51, // input 110 (0x6E char 'n') => 51 (0x33) - 52, // input 111 (0x6F char 'o') => 52 (0x34) - 53, // input 112 (0x70 char 'p') => 53 (0x35) - 54, // input 113 (0x71 char 'q') => 54 (0x36) - 55, // input 114 (0x72 char 'r') => 55 (0x37) - 56, // input 115 (0x73 char 's') => 56 (0x38) - 57, // input 116 (0x74 char 't') => 57 (0x39) - 58, // input 117 (0x75 char 'u') => 58 (0x3A) - 59, // input 118 (0x76 char 'v') => 59 (0x3B) - 60, // input 119 (0x77 char 'w') => 60 (0x3C) - 61, // input 120 (0x78 char 'x') => 61 (0x3D) - 62, // input 121 (0x79 char 'y') => 62 (0x3E) - 63, // input 122 (0x7A char 'z') => 63 (0x3F) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; -#[rustfmt::skip] -pub const BCRYPT_ENCODE: &[u8; 64] = &[ - 46, // input 0 (0x0) => '.' (0x2E) - 47, // input 1 (0x1) => '/' (0x2F) - 65, // input 2 (0x2) => 'A' (0x41) - 66, // input 3 (0x3) => 'B' (0x42) - 67, // input 4 (0x4) => 'C' (0x43) - 68, // input 5 (0x5) => 'D' (0x44) - 69, // input 6 (0x6) => 'E' (0x45) - 70, // input 7 (0x7) => 'F' (0x46) - 71, // input 8 (0x8) => 'G' (0x47) - 72, // input 9 (0x9) => 'H' (0x48) - 73, // input 10 (0xA) => 'I' (0x49) - 74, // input 11 (0xB) => 'J' (0x4A) - 75, // input 12 (0xC) => 'K' (0x4B) - 76, // input 13 (0xD) => 'L' (0x4C) - 77, // input 14 (0xE) => 'M' (0x4D) - 78, // input 15 (0xF) => 'N' (0x4E) - 79, // input 16 (0x10) => 'O' (0x4F) - 80, // input 17 (0x11) => 'P' (0x50) - 81, // input 18 (0x12) => 'Q' (0x51) - 82, // input 19 (0x13) => 'R' (0x52) - 83, // input 20 (0x14) => 'S' (0x53) - 84, // input 21 (0x15) => 'T' (0x54) - 85, // input 22 (0x16) => 'U' (0x55) - 86, // input 23 (0x17) => 'V' (0x56) - 87, // input 24 (0x18) => 'W' (0x57) - 88, // input 25 (0x19) => 'X' (0x58) - 89, // input 26 (0x1A) => 'Y' (0x59) - 90, // input 27 (0x1B) => 'Z' (0x5A) - 97, // input 28 (0x1C) => 'a' (0x61) - 98, // input 29 (0x1D) => 'b' (0x62) - 99, // input 30 (0x1E) => 'c' (0x63) - 100, // input 31 (0x1F) => 'd' (0x64) - 101, // input 32 (0x20) => 'e' (0x65) - 102, // input 33 (0x21) => 'f' (0x66) - 103, // input 34 (0x22) => 'g' (0x67) - 104, // input 35 (0x23) => 'h' (0x68) - 105, // input 36 (0x24) => 'i' (0x69) - 106, // input 37 (0x25) => 'j' (0x6A) - 107, // input 38 (0x26) => 'k' (0x6B) - 108, // input 39 (0x27) => 'l' (0x6C) - 109, // input 40 (0x28) => 'm' (0x6D) - 110, // input 41 (0x29) => 'n' (0x6E) - 111, // input 42 (0x2A) => 'o' (0x6F) - 112, // input 43 (0x2B) => 'p' (0x70) - 113, // input 44 (0x2C) => 'q' (0x71) - 114, // input 45 (0x2D) => 'r' (0x72) - 115, // input 46 (0x2E) => 's' (0x73) - 116, // input 47 (0x2F) => 't' (0x74) - 117, // input 48 (0x30) => 'u' (0x75) - 118, // input 49 (0x31) => 'v' (0x76) - 119, // input 50 (0x32) => 'w' (0x77) - 120, // input 51 (0x33) => 'x' (0x78) - 121, // input 52 (0x34) => 'y' (0x79) - 122, // input 53 (0x35) => 'z' (0x7A) - 48, // input 54 (0x36) => '0' (0x30) - 49, // input 55 (0x37) => '1' (0x31) - 50, // input 56 (0x38) => '2' (0x32) - 51, // input 57 (0x39) => '3' (0x33) - 52, // input 58 (0x3A) => '4' (0x34) - 53, // input 59 (0x3B) => '5' (0x35) - 54, // input 60 (0x3C) => '6' (0x36) - 55, // input 61 (0x3D) => '7' (0x37) - 56, // input 62 (0x3E) => '8' (0x38) - 57, // input 63 (0x3F) => '9' (0x39) -]; -#[rustfmt::skip] -pub const BCRYPT_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - INVALID_VALUE, // input 33 (0x21) - INVALID_VALUE, // input 34 (0x22) - INVALID_VALUE, // input 35 (0x23) - INVALID_VALUE, // input 36 (0x24) - INVALID_VALUE, // input 37 (0x25) - INVALID_VALUE, // input 38 (0x26) - INVALID_VALUE, // input 39 (0x27) - INVALID_VALUE, // input 40 (0x28) - INVALID_VALUE, // input 41 (0x29) - INVALID_VALUE, // input 42 (0x2A) - INVALID_VALUE, // input 43 (0x2B) - INVALID_VALUE, // input 44 (0x2C) - INVALID_VALUE, // input 45 (0x2D) - 0, // input 46 (0x2E char '.') => 0 (0x0) - 1, // input 47 (0x2F char '/') => 1 (0x1) - 54, // input 48 (0x30 char '0') => 54 (0x36) - 55, // input 49 (0x31 char '1') => 55 (0x37) - 56, // input 50 (0x32 char '2') => 56 (0x38) - 57, // input 51 (0x33 char '3') => 57 (0x39) - 58, // input 52 (0x34 char '4') => 58 (0x3A) - 59, // input 53 (0x35 char '5') => 59 (0x3B) - 60, // input 54 (0x36 char '6') => 60 (0x3C) - 61, // input 55 (0x37 char '7') => 61 (0x3D) - 62, // input 56 (0x38 char '8') => 62 (0x3E) - 63, // input 57 (0x39 char '9') => 63 (0x3F) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - INVALID_VALUE, // input 64 (0x40) - 2, // input 65 (0x41 char 'A') => 2 (0x2) - 3, // input 66 (0x42 char 'B') => 3 (0x3) - 4, // input 67 (0x43 char 'C') => 4 (0x4) - 5, // input 68 (0x44 char 'D') => 5 (0x5) - 6, // input 69 (0x45 char 'E') => 6 (0x6) - 7, // input 70 (0x46 char 'F') => 7 (0x7) - 8, // input 71 (0x47 char 'G') => 8 (0x8) - 9, // input 72 (0x48 char 'H') => 9 (0x9) - 10, // input 73 (0x49 char 'I') => 10 (0xA) - 11, // input 74 (0x4A char 'J') => 11 (0xB) - 12, // input 75 (0x4B char 'K') => 12 (0xC) - 13, // input 76 (0x4C char 'L') => 13 (0xD) - 14, // input 77 (0x4D char 'M') => 14 (0xE) - 15, // input 78 (0x4E char 'N') => 15 (0xF) - 16, // input 79 (0x4F char 'O') => 16 (0x10) - 17, // input 80 (0x50 char 'P') => 17 (0x11) - 18, // input 81 (0x51 char 'Q') => 18 (0x12) - 19, // input 82 (0x52 char 'R') => 19 (0x13) - 20, // input 83 (0x53 char 'S') => 20 (0x14) - 21, // input 84 (0x54 char 'T') => 21 (0x15) - 22, // input 85 (0x55 char 'U') => 22 (0x16) - 23, // input 86 (0x56 char 'V') => 23 (0x17) - 24, // input 87 (0x57 char 'W') => 24 (0x18) - 25, // input 88 (0x58 char 'X') => 25 (0x19) - 26, // input 89 (0x59 char 'Y') => 26 (0x1A) - 27, // input 90 (0x5A char 'Z') => 27 (0x1B) - INVALID_VALUE, // input 91 (0x5B) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - INVALID_VALUE, // input 95 (0x5F) - INVALID_VALUE, // input 96 (0x60) - 28, // input 97 (0x61 char 'a') => 28 (0x1C) - 29, // input 98 (0x62 char 'b') => 29 (0x1D) - 30, // input 99 (0x63 char 'c') => 30 (0x1E) - 31, // input 100 (0x64 char 'd') => 31 (0x1F) - 32, // input 101 (0x65 char 'e') => 32 (0x20) - 33, // input 102 (0x66 char 'f') => 33 (0x21) - 34, // input 103 (0x67 char 'g') => 34 (0x22) - 35, // input 104 (0x68 char 'h') => 35 (0x23) - 36, // input 105 (0x69 char 'i') => 36 (0x24) - 37, // input 106 (0x6A char 'j') => 37 (0x25) - 38, // input 107 (0x6B char 'k') => 38 (0x26) - 39, // input 108 (0x6C char 'l') => 39 (0x27) - 40, // input 109 (0x6D char 'm') => 40 (0x28) - 41, // input 110 (0x6E char 'n') => 41 (0x29) - 42, // input 111 (0x6F char 'o') => 42 (0x2A) - 43, // input 112 (0x70 char 'p') => 43 (0x2B) - 44, // input 113 (0x71 char 'q') => 44 (0x2C) - 45, // input 114 (0x72 char 'r') => 45 (0x2D) - 46, // input 115 (0x73 char 's') => 46 (0x2E) - 47, // input 116 (0x74 char 't') => 47 (0x2F) - 48, // input 117 (0x75 char 'u') => 48 (0x30) - 49, // input 118 (0x76 char 'v') => 49 (0x31) - 50, // input 119 (0x77 char 'w') => 50 (0x32) - 51, // input 120 (0x78 char 'x') => 51 (0x33) - 52, // input 121 (0x79 char 'y') => 52 (0x34) - 53, // input 122 (0x7A char 'z') => 53 (0x35) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; -#[rustfmt::skip] -pub const IMAP_MUTF7_ENCODE: &[u8; 64] = &[ - 65, // input 0 (0x0) => 'A' (0x41) - 66, // input 1 (0x1) => 'B' (0x42) - 67, // input 2 (0x2) => 'C' (0x43) - 68, // input 3 (0x3) => 'D' (0x44) - 69, // input 4 (0x4) => 'E' (0x45) - 70, // input 5 (0x5) => 'F' (0x46) - 71, // input 6 (0x6) => 'G' (0x47) - 72, // input 7 (0x7) => 'H' (0x48) - 73, // input 8 (0x8) => 'I' (0x49) - 74, // input 9 (0x9) => 'J' (0x4A) - 75, // input 10 (0xA) => 'K' (0x4B) - 76, // input 11 (0xB) => 'L' (0x4C) - 77, // input 12 (0xC) => 'M' (0x4D) - 78, // input 13 (0xD) => 'N' (0x4E) - 79, // input 14 (0xE) => 'O' (0x4F) - 80, // input 15 (0xF) => 'P' (0x50) - 81, // input 16 (0x10) => 'Q' (0x51) - 82, // input 17 (0x11) => 'R' (0x52) - 83, // input 18 (0x12) => 'S' (0x53) - 84, // input 19 (0x13) => 'T' (0x54) - 85, // input 20 (0x14) => 'U' (0x55) - 86, // input 21 (0x15) => 'V' (0x56) - 87, // input 22 (0x16) => 'W' (0x57) - 88, // input 23 (0x17) => 'X' (0x58) - 89, // input 24 (0x18) => 'Y' (0x59) - 90, // input 25 (0x19) => 'Z' (0x5A) - 97, // input 26 (0x1A) => 'a' (0x61) - 98, // input 27 (0x1B) => 'b' (0x62) - 99, // input 28 (0x1C) => 'c' (0x63) - 100, // input 29 (0x1D) => 'd' (0x64) - 101, // input 30 (0x1E) => 'e' (0x65) - 102, // input 31 (0x1F) => 'f' (0x66) - 103, // input 32 (0x20) => 'g' (0x67) - 104, // input 33 (0x21) => 'h' (0x68) - 105, // input 34 (0x22) => 'i' (0x69) - 106, // input 35 (0x23) => 'j' (0x6A) - 107, // input 36 (0x24) => 'k' (0x6B) - 108, // input 37 (0x25) => 'l' (0x6C) - 109, // input 38 (0x26) => 'm' (0x6D) - 110, // input 39 (0x27) => 'n' (0x6E) - 111, // input 40 (0x28) => 'o' (0x6F) - 112, // input 41 (0x29) => 'p' (0x70) - 113, // input 42 (0x2A) => 'q' (0x71) - 114, // input 43 (0x2B) => 'r' (0x72) - 115, // input 44 (0x2C) => 's' (0x73) - 116, // input 45 (0x2D) => 't' (0x74) - 117, // input 46 (0x2E) => 'u' (0x75) - 118, // input 47 (0x2F) => 'v' (0x76) - 119, // input 48 (0x30) => 'w' (0x77) - 120, // input 49 (0x31) => 'x' (0x78) - 121, // input 50 (0x32) => 'y' (0x79) - 122, // input 51 (0x33) => 'z' (0x7A) - 48, // input 52 (0x34) => '0' (0x30) - 49, // input 53 (0x35) => '1' (0x31) - 50, // input 54 (0x36) => '2' (0x32) - 51, // input 55 (0x37) => '3' (0x33) - 52, // input 56 (0x38) => '4' (0x34) - 53, // input 57 (0x39) => '5' (0x35) - 54, // input 58 (0x3A) => '6' (0x36) - 55, // input 59 (0x3B) => '7' (0x37) - 56, // input 60 (0x3C) => '8' (0x38) - 57, // input 61 (0x3D) => '9' (0x39) - 43, // input 62 (0x3E) => '+' (0x2B) - 44, // input 63 (0x3F) => ',' (0x2C) -]; -#[rustfmt::skip] -pub const IMAP_MUTF7_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - INVALID_VALUE, // input 33 (0x21) - INVALID_VALUE, // input 34 (0x22) - INVALID_VALUE, // input 35 (0x23) - INVALID_VALUE, // input 36 (0x24) - INVALID_VALUE, // input 37 (0x25) - INVALID_VALUE, // input 38 (0x26) - INVALID_VALUE, // input 39 (0x27) - INVALID_VALUE, // input 40 (0x28) - INVALID_VALUE, // input 41 (0x29) - INVALID_VALUE, // input 42 (0x2A) - 62, // input 43 (0x2B char '+') => 62 (0x3E) - 63, // input 44 (0x2C char ',') => 63 (0x3F) - INVALID_VALUE, // input 45 (0x2D) - INVALID_VALUE, // input 46 (0x2E) - INVALID_VALUE, // input 47 (0x2F) - 52, // input 48 (0x30 char '0') => 52 (0x34) - 53, // input 49 (0x31 char '1') => 53 (0x35) - 54, // input 50 (0x32 char '2') => 54 (0x36) - 55, // input 51 (0x33 char '3') => 55 (0x37) - 56, // input 52 (0x34 char '4') => 56 (0x38) - 57, // input 53 (0x35 char '5') => 57 (0x39) - 58, // input 54 (0x36 char '6') => 58 (0x3A) - 59, // input 55 (0x37 char '7') => 59 (0x3B) - 60, // input 56 (0x38 char '8') => 60 (0x3C) - 61, // input 57 (0x39 char '9') => 61 (0x3D) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - INVALID_VALUE, // input 64 (0x40) - 0, // input 65 (0x41 char 'A') => 0 (0x0) - 1, // input 66 (0x42 char 'B') => 1 (0x1) - 2, // input 67 (0x43 char 'C') => 2 (0x2) - 3, // input 68 (0x44 char 'D') => 3 (0x3) - 4, // input 69 (0x45 char 'E') => 4 (0x4) - 5, // input 70 (0x46 char 'F') => 5 (0x5) - 6, // input 71 (0x47 char 'G') => 6 (0x6) - 7, // input 72 (0x48 char 'H') => 7 (0x7) - 8, // input 73 (0x49 char 'I') => 8 (0x8) - 9, // input 74 (0x4A char 'J') => 9 (0x9) - 10, // input 75 (0x4B char 'K') => 10 (0xA) - 11, // input 76 (0x4C char 'L') => 11 (0xB) - 12, // input 77 (0x4D char 'M') => 12 (0xC) - 13, // input 78 (0x4E char 'N') => 13 (0xD) - 14, // input 79 (0x4F char 'O') => 14 (0xE) - 15, // input 80 (0x50 char 'P') => 15 (0xF) - 16, // input 81 (0x51 char 'Q') => 16 (0x10) - 17, // input 82 (0x52 char 'R') => 17 (0x11) - 18, // input 83 (0x53 char 'S') => 18 (0x12) - 19, // input 84 (0x54 char 'T') => 19 (0x13) - 20, // input 85 (0x55 char 'U') => 20 (0x14) - 21, // input 86 (0x56 char 'V') => 21 (0x15) - 22, // input 87 (0x57 char 'W') => 22 (0x16) - 23, // input 88 (0x58 char 'X') => 23 (0x17) - 24, // input 89 (0x59 char 'Y') => 24 (0x18) - 25, // input 90 (0x5A char 'Z') => 25 (0x19) - INVALID_VALUE, // input 91 (0x5B) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - INVALID_VALUE, // input 95 (0x5F) - INVALID_VALUE, // input 96 (0x60) - 26, // input 97 (0x61 char 'a') => 26 (0x1A) - 27, // input 98 (0x62 char 'b') => 27 (0x1B) - 28, // input 99 (0x63 char 'c') => 28 (0x1C) - 29, // input 100 (0x64 char 'd') => 29 (0x1D) - 30, // input 101 (0x65 char 'e') => 30 (0x1E) - 31, // input 102 (0x66 char 'f') => 31 (0x1F) - 32, // input 103 (0x67 char 'g') => 32 (0x20) - 33, // input 104 (0x68 char 'h') => 33 (0x21) - 34, // input 105 (0x69 char 'i') => 34 (0x22) - 35, // input 106 (0x6A char 'j') => 35 (0x23) - 36, // input 107 (0x6B char 'k') => 36 (0x24) - 37, // input 108 (0x6C char 'l') => 37 (0x25) - 38, // input 109 (0x6D char 'm') => 38 (0x26) - 39, // input 110 (0x6E char 'n') => 39 (0x27) - 40, // input 111 (0x6F char 'o') => 40 (0x28) - 41, // input 112 (0x70 char 'p') => 41 (0x29) - 42, // input 113 (0x71 char 'q') => 42 (0x2A) - 43, // input 114 (0x72 char 'r') => 43 (0x2B) - 44, // input 115 (0x73 char 's') => 44 (0x2C) - 45, // input 116 (0x74 char 't') => 45 (0x2D) - 46, // input 117 (0x75 char 'u') => 46 (0x2E) - 47, // input 118 (0x76 char 'v') => 47 (0x2F) - 48, // input 119 (0x77 char 'w') => 48 (0x30) - 49, // input 120 (0x78 char 'x') => 49 (0x31) - 50, // input 121 (0x79 char 'y') => 50 (0x32) - 51, // input 122 (0x7A char 'z') => 51 (0x33) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; -#[rustfmt::skip] -pub const BINHEX_ENCODE: &[u8; 64] = &[ - 33, // input 0 (0x0) => '!' (0x21) - 34, // input 1 (0x1) => '"' (0x22) - 35, // input 2 (0x2) => '#' (0x23) - 36, // input 3 (0x3) => '$' (0x24) - 37, // input 4 (0x4) => '%' (0x25) - 38, // input 5 (0x5) => '&' (0x26) - 39, // input 6 (0x6) => ''' (0x27) - 40, // input 7 (0x7) => '(' (0x28) - 41, // input 8 (0x8) => ')' (0x29) - 42, // input 9 (0x9) => '*' (0x2A) - 43, // input 10 (0xA) => '+' (0x2B) - 44, // input 11 (0xB) => ',' (0x2C) - 45, // input 12 (0xC) => '-' (0x2D) - 48, // input 13 (0xD) => '0' (0x30) - 49, // input 14 (0xE) => '1' (0x31) - 50, // input 15 (0xF) => '2' (0x32) - 51, // input 16 (0x10) => '3' (0x33) - 52, // input 17 (0x11) => '4' (0x34) - 53, // input 18 (0x12) => '5' (0x35) - 54, // input 19 (0x13) => '6' (0x36) - 55, // input 20 (0x14) => '7' (0x37) - 56, // input 21 (0x15) => '8' (0x38) - 57, // input 22 (0x16) => '9' (0x39) - 64, // input 23 (0x17) => '@' (0x40) - 65, // input 24 (0x18) => 'A' (0x41) - 66, // input 25 (0x19) => 'B' (0x42) - 67, // input 26 (0x1A) => 'C' (0x43) - 68, // input 27 (0x1B) => 'D' (0x44) - 69, // input 28 (0x1C) => 'E' (0x45) - 70, // input 29 (0x1D) => 'F' (0x46) - 71, // input 30 (0x1E) => 'G' (0x47) - 72, // input 31 (0x1F) => 'H' (0x48) - 73, // input 32 (0x20) => 'I' (0x49) - 74, // input 33 (0x21) => 'J' (0x4A) - 75, // input 34 (0x22) => 'K' (0x4B) - 76, // input 35 (0x23) => 'L' (0x4C) - 77, // input 36 (0x24) => 'M' (0x4D) - 78, // input 37 (0x25) => 'N' (0x4E) - 80, // input 38 (0x26) => 'P' (0x50) - 81, // input 39 (0x27) => 'Q' (0x51) - 82, // input 40 (0x28) => 'R' (0x52) - 83, // input 41 (0x29) => 'S' (0x53) - 84, // input 42 (0x2A) => 'T' (0x54) - 85, // input 43 (0x2B) => 'U' (0x55) - 86, // input 44 (0x2C) => 'V' (0x56) - 88, // input 45 (0x2D) => 'X' (0x58) - 89, // input 46 (0x2E) => 'Y' (0x59) - 90, // input 47 (0x2F) => 'Z' (0x5A) - 91, // input 48 (0x30) => '[' (0x5B) - 96, // input 49 (0x31) => '`' (0x60) - 97, // input 50 (0x32) => 'a' (0x61) - 98, // input 51 (0x33) => 'b' (0x62) - 99, // input 52 (0x34) => 'c' (0x63) - 100, // input 53 (0x35) => 'd' (0x64) - 101, // input 54 (0x36) => 'e' (0x65) - 104, // input 55 (0x37) => 'h' (0x68) - 105, // input 56 (0x38) => 'i' (0x69) - 106, // input 57 (0x39) => 'j' (0x6A) - 107, // input 58 (0x3A) => 'k' (0x6B) - 108, // input 59 (0x3B) => 'l' (0x6C) - 109, // input 60 (0x3C) => 'm' (0x6D) - 112, // input 61 (0x3D) => 'p' (0x70) - 113, // input 62 (0x3E) => 'q' (0x71) - 114, // input 63 (0x3F) => 'r' (0x72) -]; -#[rustfmt::skip] -pub const BINHEX_DECODE: &[u8; 256] = &[ - INVALID_VALUE, // input 0 (0x0) - INVALID_VALUE, // input 1 (0x1) - INVALID_VALUE, // input 2 (0x2) - INVALID_VALUE, // input 3 (0x3) - INVALID_VALUE, // input 4 (0x4) - INVALID_VALUE, // input 5 (0x5) - INVALID_VALUE, // input 6 (0x6) - INVALID_VALUE, // input 7 (0x7) - INVALID_VALUE, // input 8 (0x8) - INVALID_VALUE, // input 9 (0x9) - INVALID_VALUE, // input 10 (0xA) - INVALID_VALUE, // input 11 (0xB) - INVALID_VALUE, // input 12 (0xC) - INVALID_VALUE, // input 13 (0xD) - INVALID_VALUE, // input 14 (0xE) - INVALID_VALUE, // input 15 (0xF) - INVALID_VALUE, // input 16 (0x10) - INVALID_VALUE, // input 17 (0x11) - INVALID_VALUE, // input 18 (0x12) - INVALID_VALUE, // input 19 (0x13) - INVALID_VALUE, // input 20 (0x14) - INVALID_VALUE, // input 21 (0x15) - INVALID_VALUE, // input 22 (0x16) - INVALID_VALUE, // input 23 (0x17) - INVALID_VALUE, // input 24 (0x18) - INVALID_VALUE, // input 25 (0x19) - INVALID_VALUE, // input 26 (0x1A) - INVALID_VALUE, // input 27 (0x1B) - INVALID_VALUE, // input 28 (0x1C) - INVALID_VALUE, // input 29 (0x1D) - INVALID_VALUE, // input 30 (0x1E) - INVALID_VALUE, // input 31 (0x1F) - INVALID_VALUE, // input 32 (0x20) - 0, // input 33 (0x21 char '!') => 0 (0x0) - 1, // input 34 (0x22 char '"') => 1 (0x1) - 2, // input 35 (0x23 char '#') => 2 (0x2) - 3, // input 36 (0x24 char '$') => 3 (0x3) - 4, // input 37 (0x25 char '%') => 4 (0x4) - 5, // input 38 (0x26 char '&') => 5 (0x5) - 6, // input 39 (0x27 char ''') => 6 (0x6) - 7, // input 40 (0x28 char '(') => 7 (0x7) - 8, // input 41 (0x29 char ')') => 8 (0x8) - 9, // input 42 (0x2A char '*') => 9 (0x9) - 10, // input 43 (0x2B char '+') => 10 (0xA) - 11, // input 44 (0x2C char ',') => 11 (0xB) - 12, // input 45 (0x2D char '-') => 12 (0xC) - INVALID_VALUE, // input 46 (0x2E) - INVALID_VALUE, // input 47 (0x2F) - 13, // input 48 (0x30 char '0') => 13 (0xD) - 14, // input 49 (0x31 char '1') => 14 (0xE) - 15, // input 50 (0x32 char '2') => 15 (0xF) - 16, // input 51 (0x33 char '3') => 16 (0x10) - 17, // input 52 (0x34 char '4') => 17 (0x11) - 18, // input 53 (0x35 char '5') => 18 (0x12) - 19, // input 54 (0x36 char '6') => 19 (0x13) - 20, // input 55 (0x37 char '7') => 20 (0x14) - 21, // input 56 (0x38 char '8') => 21 (0x15) - 22, // input 57 (0x39 char '9') => 22 (0x16) - INVALID_VALUE, // input 58 (0x3A) - INVALID_VALUE, // input 59 (0x3B) - INVALID_VALUE, // input 60 (0x3C) - INVALID_VALUE, // input 61 (0x3D) - INVALID_VALUE, // input 62 (0x3E) - INVALID_VALUE, // input 63 (0x3F) - 23, // input 64 (0x40 char '@') => 23 (0x17) - 24, // input 65 (0x41 char 'A') => 24 (0x18) - 25, // input 66 (0x42 char 'B') => 25 (0x19) - 26, // input 67 (0x43 char 'C') => 26 (0x1A) - 27, // input 68 (0x44 char 'D') => 27 (0x1B) - 28, // input 69 (0x45 char 'E') => 28 (0x1C) - 29, // input 70 (0x46 char 'F') => 29 (0x1D) - 30, // input 71 (0x47 char 'G') => 30 (0x1E) - 31, // input 72 (0x48 char 'H') => 31 (0x1F) - 32, // input 73 (0x49 char 'I') => 32 (0x20) - 33, // input 74 (0x4A char 'J') => 33 (0x21) - 34, // input 75 (0x4B char 'K') => 34 (0x22) - 35, // input 76 (0x4C char 'L') => 35 (0x23) - 36, // input 77 (0x4D char 'M') => 36 (0x24) - 37, // input 78 (0x4E char 'N') => 37 (0x25) - INVALID_VALUE, // input 79 (0x4F) - 38, // input 80 (0x50 char 'P') => 38 (0x26) - 39, // input 81 (0x51 char 'Q') => 39 (0x27) - 40, // input 82 (0x52 char 'R') => 40 (0x28) - 41, // input 83 (0x53 char 'S') => 41 (0x29) - 42, // input 84 (0x54 char 'T') => 42 (0x2A) - 43, // input 85 (0x55 char 'U') => 43 (0x2B) - 44, // input 86 (0x56 char 'V') => 44 (0x2C) - INVALID_VALUE, // input 87 (0x57) - 45, // input 88 (0x58 char 'X') => 45 (0x2D) - 46, // input 89 (0x59 char 'Y') => 46 (0x2E) - 47, // input 90 (0x5A char 'Z') => 47 (0x2F) - 48, // input 91 (0x5B char '[') => 48 (0x30) - INVALID_VALUE, // input 92 (0x5C) - INVALID_VALUE, // input 93 (0x5D) - INVALID_VALUE, // input 94 (0x5E) - INVALID_VALUE, // input 95 (0x5F) - 49, // input 96 (0x60 char '`') => 49 (0x31) - 50, // input 97 (0x61 char 'a') => 50 (0x32) - 51, // input 98 (0x62 char 'b') => 51 (0x33) - 52, // input 99 (0x63 char 'c') => 52 (0x34) - 53, // input 100 (0x64 char 'd') => 53 (0x35) - 54, // input 101 (0x65 char 'e') => 54 (0x36) - INVALID_VALUE, // input 102 (0x66) - INVALID_VALUE, // input 103 (0x67) - 55, // input 104 (0x68 char 'h') => 55 (0x37) - 56, // input 105 (0x69 char 'i') => 56 (0x38) - 57, // input 106 (0x6A char 'j') => 57 (0x39) - 58, // input 107 (0x6B char 'k') => 58 (0x3A) - 59, // input 108 (0x6C char 'l') => 59 (0x3B) - 60, // input 109 (0x6D char 'm') => 60 (0x3C) - INVALID_VALUE, // input 110 (0x6E) - INVALID_VALUE, // input 111 (0x6F) - 61, // input 112 (0x70 char 'p') => 61 (0x3D) - 62, // input 113 (0x71 char 'q') => 62 (0x3E) - 63, // input 114 (0x72 char 'r') => 63 (0x3F) - INVALID_VALUE, // input 115 (0x73) - INVALID_VALUE, // input 116 (0x74) - INVALID_VALUE, // input 117 (0x75) - INVALID_VALUE, // input 118 (0x76) - INVALID_VALUE, // input 119 (0x77) - INVALID_VALUE, // input 120 (0x78) - INVALID_VALUE, // input 121 (0x79) - INVALID_VALUE, // input 122 (0x7A) - INVALID_VALUE, // input 123 (0x7B) - INVALID_VALUE, // input 124 (0x7C) - INVALID_VALUE, // input 125 (0x7D) - INVALID_VALUE, // input 126 (0x7E) - INVALID_VALUE, // input 127 (0x7F) - INVALID_VALUE, // input 128 (0x80) - INVALID_VALUE, // input 129 (0x81) - INVALID_VALUE, // input 130 (0x82) - INVALID_VALUE, // input 131 (0x83) - INVALID_VALUE, // input 132 (0x84) - INVALID_VALUE, // input 133 (0x85) - INVALID_VALUE, // input 134 (0x86) - INVALID_VALUE, // input 135 (0x87) - INVALID_VALUE, // input 136 (0x88) - INVALID_VALUE, // input 137 (0x89) - INVALID_VALUE, // input 138 (0x8A) - INVALID_VALUE, // input 139 (0x8B) - INVALID_VALUE, // input 140 (0x8C) - INVALID_VALUE, // input 141 (0x8D) - INVALID_VALUE, // input 142 (0x8E) - INVALID_VALUE, // input 143 (0x8F) - INVALID_VALUE, // input 144 (0x90) - INVALID_VALUE, // input 145 (0x91) - INVALID_VALUE, // input 146 (0x92) - INVALID_VALUE, // input 147 (0x93) - INVALID_VALUE, // input 148 (0x94) - INVALID_VALUE, // input 149 (0x95) - INVALID_VALUE, // input 150 (0x96) - INVALID_VALUE, // input 151 (0x97) - INVALID_VALUE, // input 152 (0x98) - INVALID_VALUE, // input 153 (0x99) - INVALID_VALUE, // input 154 (0x9A) - INVALID_VALUE, // input 155 (0x9B) - INVALID_VALUE, // input 156 (0x9C) - INVALID_VALUE, // input 157 (0x9D) - INVALID_VALUE, // input 158 (0x9E) - INVALID_VALUE, // input 159 (0x9F) - INVALID_VALUE, // input 160 (0xA0) - INVALID_VALUE, // input 161 (0xA1) - INVALID_VALUE, // input 162 (0xA2) - INVALID_VALUE, // input 163 (0xA3) - INVALID_VALUE, // input 164 (0xA4) - INVALID_VALUE, // input 165 (0xA5) - INVALID_VALUE, // input 166 (0xA6) - INVALID_VALUE, // input 167 (0xA7) - INVALID_VALUE, // input 168 (0xA8) - INVALID_VALUE, // input 169 (0xA9) - INVALID_VALUE, // input 170 (0xAA) - INVALID_VALUE, // input 171 (0xAB) - INVALID_VALUE, // input 172 (0xAC) - INVALID_VALUE, // input 173 (0xAD) - INVALID_VALUE, // input 174 (0xAE) - INVALID_VALUE, // input 175 (0xAF) - INVALID_VALUE, // input 176 (0xB0) - INVALID_VALUE, // input 177 (0xB1) - INVALID_VALUE, // input 178 (0xB2) - INVALID_VALUE, // input 179 (0xB3) - INVALID_VALUE, // input 180 (0xB4) - INVALID_VALUE, // input 181 (0xB5) - INVALID_VALUE, // input 182 (0xB6) - INVALID_VALUE, // input 183 (0xB7) - INVALID_VALUE, // input 184 (0xB8) - INVALID_VALUE, // input 185 (0xB9) - INVALID_VALUE, // input 186 (0xBA) - INVALID_VALUE, // input 187 (0xBB) - INVALID_VALUE, // input 188 (0xBC) - INVALID_VALUE, // input 189 (0xBD) - INVALID_VALUE, // input 190 (0xBE) - INVALID_VALUE, // input 191 (0xBF) - INVALID_VALUE, // input 192 (0xC0) - INVALID_VALUE, // input 193 (0xC1) - INVALID_VALUE, // input 194 (0xC2) - INVALID_VALUE, // input 195 (0xC3) - INVALID_VALUE, // input 196 (0xC4) - INVALID_VALUE, // input 197 (0xC5) - INVALID_VALUE, // input 198 (0xC6) - INVALID_VALUE, // input 199 (0xC7) - INVALID_VALUE, // input 200 (0xC8) - INVALID_VALUE, // input 201 (0xC9) - INVALID_VALUE, // input 202 (0xCA) - INVALID_VALUE, // input 203 (0xCB) - INVALID_VALUE, // input 204 (0xCC) - INVALID_VALUE, // input 205 (0xCD) - INVALID_VALUE, // input 206 (0xCE) - INVALID_VALUE, // input 207 (0xCF) - INVALID_VALUE, // input 208 (0xD0) - INVALID_VALUE, // input 209 (0xD1) - INVALID_VALUE, // input 210 (0xD2) - INVALID_VALUE, // input 211 (0xD3) - INVALID_VALUE, // input 212 (0xD4) - INVALID_VALUE, // input 213 (0xD5) - INVALID_VALUE, // input 214 (0xD6) - INVALID_VALUE, // input 215 (0xD7) - INVALID_VALUE, // input 216 (0xD8) - INVALID_VALUE, // input 217 (0xD9) - INVALID_VALUE, // input 218 (0xDA) - INVALID_VALUE, // input 219 (0xDB) - INVALID_VALUE, // input 220 (0xDC) - INVALID_VALUE, // input 221 (0xDD) - INVALID_VALUE, // input 222 (0xDE) - INVALID_VALUE, // input 223 (0xDF) - INVALID_VALUE, // input 224 (0xE0) - INVALID_VALUE, // input 225 (0xE1) - INVALID_VALUE, // input 226 (0xE2) - INVALID_VALUE, // input 227 (0xE3) - INVALID_VALUE, // input 228 (0xE4) - INVALID_VALUE, // input 229 (0xE5) - INVALID_VALUE, // input 230 (0xE6) - INVALID_VALUE, // input 231 (0xE7) - INVALID_VALUE, // input 232 (0xE8) - INVALID_VALUE, // input 233 (0xE9) - INVALID_VALUE, // input 234 (0xEA) - INVALID_VALUE, // input 235 (0xEB) - INVALID_VALUE, // input 236 (0xEC) - INVALID_VALUE, // input 237 (0xED) - INVALID_VALUE, // input 238 (0xEE) - INVALID_VALUE, // input 239 (0xEF) - INVALID_VALUE, // input 240 (0xF0) - INVALID_VALUE, // input 241 (0xF1) - INVALID_VALUE, // input 242 (0xF2) - INVALID_VALUE, // input 243 (0xF3) - INVALID_VALUE, // input 244 (0xF4) - INVALID_VALUE, // input 245 (0xF5) - INVALID_VALUE, // input 246 (0xF6) - INVALID_VALUE, // input 247 (0xF7) - INVALID_VALUE, // input 248 (0xF8) - INVALID_VALUE, // input 249 (0xF9) - INVALID_VALUE, // input 250 (0xFA) - INVALID_VALUE, // input 251 (0xFB) - INVALID_VALUE, // input 252 (0xFC) - INVALID_VALUE, // input 253 (0xFD) - INVALID_VALUE, // input 254 (0xFE) - INVALID_VALUE, // input 255 (0xFF) -]; diff --git a/src/tests.rs b/src/tests.rs index 88748de7..441e2d52 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,7 +1,9 @@ -use crate::{decode_config, encode::encoded_size, encode_config_buf, CharacterSet, Config}; +use crate::{alphabet, decode_engine, encode::encoded_len, encode_engine_string}; use std::str; +use crate::engine::fast_portable::{FastPortable, FastPortableConfig}; +use crate::engine::{Config, Engine}; use rand::{ distributions::{Distribution, Uniform}, seq::SliceRandom, @@ -19,10 +21,10 @@ fn roundtrip_random_config_long() { roundtrip_random_config(Uniform::new(0, 1000), 10_000); } -pub fn assert_encode_sanity(encoded: &str, config: Config, input_len: usize) { +pub fn assert_encode_sanity(encoded: &str, padded: bool, input_len: usize) { let input_rem = input_len % 3; let expected_padding_len = if input_rem > 0 { - if config.pad { + if padded { 3 - input_rem } else { 0 @@ -31,7 +33,7 @@ pub fn assert_encode_sanity(encoded: &str, config: Config, input_len: usize) { 0 }; - let expected_encoded_len = encoded_size(input_len, config).unwrap(); + let expected_encoded_len = encoded_len(input_len, padded).unwrap(); assert_eq!(expected_encoded_len, encoded.len()); @@ -53,29 +55,39 @@ fn roundtrip_random_config(input_len_range: Uniform, iterations: u32) { let input_len = input_len_range.sample(&mut rng); - let config = random_config(&mut rng); + let engine = random_engine(&mut rng); for _ in 0..input_len { input_buf.push(rng.gen()); } - encode_config_buf(&input_buf, config, &mut encoded_buf); + encode_engine_string(&input_buf, &mut encoded_buf, &engine); - assert_encode_sanity(&encoded_buf, config, input_len); + assert_encode_sanity(&encoded_buf, engine.config().padding(), input_len); - assert_eq!(input_buf, decode_config(&encoded_buf, config).unwrap()); + assert_eq!(input_buf, decode_engine(&encoded_buf, &engine).unwrap()); } } -pub fn random_config(rng: &mut R) -> Config { - const CHARSETS: &[CharacterSet] = &[ - CharacterSet::UrlSafe, - CharacterSet::Standard, - CharacterSet::Crypt, - CharacterSet::ImapMutf7, - CharacterSet::BinHex, - ]; - let charset = *CHARSETS.choose(rng).unwrap(); - - Config::new(charset, rng.gen()) +pub fn random_config(rng: &mut R) -> FastPortableConfig { + FastPortableConfig::from(rng.gen(), rng.gen()) } + +pub fn random_alphabet(rng: &mut R) -> &'static alphabet::Alphabet { + &ALPHABETS.choose(rng).unwrap() +} + +pub fn random_engine(rng: &mut R) -> FastPortable { + let alphabet = random_alphabet(rng); + let config = random_config(rng); + FastPortable::from(alphabet, config) +} + +const ALPHABETS: &[alphabet::Alphabet] = &[ + alphabet::URL_SAFE, + alphabet::STANDARD, + alphabet::CRYPT, + alphabet::BCRYPT, + alphabet::IMAP_MUTF7, + alphabet::BIN_HEX, +]; diff --git a/src/write/encoder.rs b/src/write/encoder.rs index 4e24df62..643e3471 100644 --- a/src/write/encoder.rs +++ b/src/write/encoder.rs @@ -1,8 +1,8 @@ -use crate::encode::encode_to_slice; -use crate::{encode_config_slice, Config}; +use crate::encode_engine_slice; +use crate::engine::Engine; use std::{ - cmp, fmt, - io::{ErrorKind, Result, Write}, + cmp, fmt, io, + io::{ErrorKind, Result}, }; pub(crate) const BUF_SIZE: usize = 1024; @@ -25,7 +25,9 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3; /// use std::io::Write; /// /// // use a vec as the simplest possible `Write` -- in real code this is probably a file, etc. -/// let mut enc = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD); +/// let mut enc = base64::write::EncoderWriter::from( +/// Vec::new(), +/// &base64::engine::DEFAULT_ENGINE); /// /// // handle errors as you normally would /// enc.write_all(b"asdf").unwrap(); @@ -53,8 +55,8 @@ const MIN_ENCODE_CHUNK_SIZE: usize = 3; /// /// It has some minor performance loss compared to encoding slices (a couple percent). /// It does not do any heap allocation. -pub struct EncoderWriter { - config: Config, +pub struct EncoderWriter<'e, E: Engine, W: io::Write> { + engine: &'e E, /// Where encoded data is written to. It's an Option as it's None immediately before Drop is /// called so that finish() can return the underlying writer. None implies that finish() has /// been called successfully. @@ -73,7 +75,7 @@ pub struct EncoderWriter { panicked: bool, } -impl fmt::Debug for EncoderWriter { +impl<'e, E: Engine, W: io::Write> fmt::Debug for EncoderWriter<'e, E, W> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, @@ -86,12 +88,12 @@ impl fmt::Debug for EncoderWriter { } } -impl EncoderWriter { - /// Create a new encoder that will write to the provided delegate writer `w`. - pub fn new(w: W, config: Config) -> EncoderWriter { +impl<'e, E: Engine, W: io::Write> EncoderWriter<'e, E, W> { + /// Create a new encoder that will write to the provided delegate writer. + pub fn from(delegate: W, engine: &'e E) -> EncoderWriter<'e, E, W> { EncoderWriter { - config, - delegate: Some(w), + engine, + delegate: Some(delegate), extra_input: [0u8; MIN_ENCODE_CHUNK_SIZE], extra_input_occupied_len: 0, output: [0u8; BUF_SIZE], @@ -141,10 +143,10 @@ impl EncoderWriter { self.write_all_encoded_output()?; if self.extra_input_occupied_len > 0 { - let encoded_len = encode_config_slice( + let encoded_len = encode_engine_slice( &self.extra_input[..self.extra_input_occupied_len], - self.config, &mut self.output[..], + self.engine, ); self.output_occupied_len = encoded_len; @@ -182,7 +184,7 @@ impl EncoderWriter { self.output_occupied_len = current_output_len.checked_sub(consumed).unwrap(); // If we're blocking on I/O, the minor inefficiency of copying bytes to the // start of the buffer is the least of our concerns... - // Rotate moves more than we need to, but copy_within isn't stabilized yet. + // TODO Rotate moves more than we need to; copy_within now stable. self.output.rotate_left(consumed); } else { self.output_occupied_len = 0; @@ -236,7 +238,7 @@ impl EncoderWriter { } } -impl Write for EncoderWriter { +impl<'e, E: Engine, W: io::Write> io::Write for EncoderWriter<'e, E, W> { /// Encode input and then write to the delegate writer. /// /// Under non-error circumstances, this returns `Ok` with the value being the number of bytes @@ -305,10 +307,9 @@ impl Write for EncoderWriter { self.extra_input[self.extra_input_occupied_len..MIN_ENCODE_CHUNK_SIZE] .copy_from_slice(&input[0..extra_input_read_len]); - let len = encode_to_slice( + let len = self.engine.encode( &self.extra_input[0..MIN_ENCODE_CHUNK_SIZE], &mut self.output[..], - self.config.char_set.encode_table(), ); debug_assert_eq!(4, len); @@ -354,10 +355,9 @@ impl Write for EncoderWriter { debug_assert_eq!(0, max_input_len % MIN_ENCODE_CHUNK_SIZE); debug_assert_eq!(0, input_chunks_to_encode_len % MIN_ENCODE_CHUNK_SIZE); - encoded_size += encode_to_slice( + encoded_size += self.engine.encode( &input[..(input_chunks_to_encode_len)], &mut self.output[encoded_size..], - self.config.char_set.encode_table(), ); // not updating `self.output_occupied_len` here because if the below write fails, it should @@ -390,7 +390,7 @@ impl Write for EncoderWriter { } } -impl Drop for EncoderWriter { +impl<'e, E: Engine, W: io::Write> Drop for EncoderWriter<'e, E, W> { fn drop(&mut self) { if !self.panicked { // like `BufWriter`, ignore errors during drop diff --git a/src/write/encoder_string_writer.rs b/src/write/encoder_string_writer.rs index 58b1c0ab..6680c0c7 100644 --- a/src/write/encoder_string_writer.rs +++ b/src/write/encoder_string_writer.rs @@ -1,10 +1,11 @@ use super::encoder::EncoderWriter; -use crate::Config; +use crate::engine::Engine; use std::io; use std::io::Write; /// A `Write` implementation that base64-encodes data using the provided config and accumulates the -/// resulting base64 in memory, which is then exposed as a String via `into_inner()`. +/// resulting base64 utf8 `&str` in a [StrConsumer] implementation (typically `String`), which is +/// then exposed via `into_inner()`. /// /// # Examples /// @@ -13,7 +14,8 @@ use std::io::Write; /// ``` /// use std::io::Write; /// -/// let mut enc = base64::write::EncoderStringWriter::new(base64::STANDARD); +/// let mut enc = base64::write::EncoderStringWriter::from( +/// &base64::engine::DEFAULT_ENGINE); /// /// enc.write_all(b"asdf").unwrap(); /// @@ -23,14 +25,16 @@ use std::io::Write; /// assert_eq!("YXNkZg==", &b64_string); /// ``` /// -/// Or, append to an existing String: +/// Or, append to an existing `String`, which implements `StrConsumer`: /// /// ``` /// use std::io::Write; /// /// let mut buf = String::from("base64: "); /// -/// let mut enc = base64::write::EncoderStringWriter::from(&mut buf, base64::STANDARD); +/// let mut enc = base64::write::EncoderStringWriter::from_consumer( +/// &mut buf, +/// &base64::engine::DEFAULT_ENGINE); /// /// enc.write_all(b"asdf").unwrap(); /// @@ -49,40 +53,38 @@ use std::io::Write; /// /// Because it has to validate that the base64 is UTF-8, it is about 80% as fast as writing plain /// bytes to a `io::Write`. -pub struct EncoderStringWriter { - encoder: EncoderWriter>, +pub struct EncoderStringWriter<'e, E: Engine, S: StrConsumer> { + encoder: EncoderWriter<'e, E, Utf8SingleCodeUnitWriter>, } -impl EncoderStringWriter { +impl<'e, E: Engine, S: StrConsumer> EncoderStringWriter<'e, E, S> { /// Create a EncoderStringWriter that will append to the provided `StrConsumer`. - pub fn from(str_consumer: S, config: Config) -> Self { + pub fn from_consumer(str_consumer: S, engine: &'e E) -> Self { EncoderStringWriter { - encoder: EncoderWriter::new(Utf8SingleCodeUnitWriter { str_consumer }, config), + encoder: EncoderWriter::from(Utf8SingleCodeUnitWriter { str_consumer }, engine), } } /// Encode all remaining buffered data, including any trailing incomplete input triples and /// associated padding. /// - /// Once this succeeds, no further writes or calls to this method are allowed. - /// /// Returns the base64-encoded form of the accumulated written data. pub fn into_inner(mut self) -> S { self.encoder .finish() - .expect("Writing to a Vec should never fail") + .expect("Writing to a consumer should never fail") .str_consumer } } -impl EncoderStringWriter { - /// Create a EncoderStringWriter that will encode into a new String with the provided config. - pub fn new(config: Config) -> Self { - EncoderStringWriter::from(String::new(), config) +impl<'e, E: Engine> EncoderStringWriter<'e, E, String> { + /// Create a EncoderStringWriter that will encode into a new `String` with the provided config. + pub fn from(engine: &'e E) -> Self { + EncoderStringWriter::from_consumer(String::new(), engine) } } -impl Write for EncoderStringWriter { +impl<'e, E: Engine, S: StrConsumer> Write for EncoderStringWriter<'e, E, S> { fn write(&mut self, buf: &[u8]) -> io::Result { self.encoder.write(buf) } @@ -138,8 +140,8 @@ impl io::Write for Utf8SingleCodeUnitWriter { #[cfg(test)] mod tests { - use crate::encode_config_buf; - use crate::tests::random_config; + use crate::encode_engine_string; + use crate::tests::random_engine; use crate::write::encoder_string_writer::EncoderStringWriter; use rand::Rng; use std::io::Write; @@ -160,10 +162,10 @@ mod tests { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut normal_encoded); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut normal_encoded, &engine); - let mut stream_encoder = EncoderStringWriter::new(config); + let mut stream_encoder = EncoderStringWriter::from(&engine); // Write the first i bytes, then the rest stream_encoder.write_all(&orig_data[0..i]).unwrap(); stream_encoder.write_all(&orig_data[i..]).unwrap(); diff --git a/src/write/encoder_tests.rs b/src/write/encoder_tests.rs index 09b4d3a2..52753fda 100644 --- a/src/write/encoder_tests.rs +++ b/src/write/encoder_tests.rs @@ -1,29 +1,38 @@ -use super::EncoderWriter; -use crate::tests::random_config; -use crate::{encode_config, encode_config_buf, STANDARD_NO_PAD, URL_SAFE}; - use std::io::{Cursor, Write}; use std::{cmp, io, str}; use rand::Rng; +use crate::alphabet::{STANDARD, URL_SAFE}; +use crate::engine::fast_portable::{FastPortable, NO_PAD, PAD}; +use crate::tests::random_engine; +use crate::{encode_engine, encode_engine_string}; + +use super::EncoderWriter; + +const URL_SAFE_ENGINE: FastPortable = FastPortable::from(&URL_SAFE, PAD); +const NO_PAD_ENGINE: FastPortable = FastPortable::from(&STANDARD, NO_PAD); + #[test] fn encode_three_bytes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); let sz = enc.write(b"abc").unwrap(); assert_eq!(sz, 3); } - assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes()); + assert_eq!( + &c.get_ref()[..], + encode_engine("abc", &URL_SAFE_ENGINE).as_bytes() + ); } #[test] fn encode_nine_bytes_two_writes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); let sz = enc.write(b"abcdef").unwrap(); assert_eq!(sz, 6); @@ -32,7 +41,7 @@ fn encode_nine_bytes_two_writes() { } assert_eq!( &c.get_ref()[..], - encode_config("abcdefghi", URL_SAFE).as_bytes() + encode_engine("abcdefghi", &URL_SAFE_ENGINE).as_bytes() ); } @@ -40,21 +49,24 @@ fn encode_nine_bytes_two_writes() { fn encode_one_then_two_bytes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); let sz = enc.write(b"a").unwrap(); assert_eq!(sz, 1); let sz = enc.write(b"bc").unwrap(); assert_eq!(sz, 2); } - assert_eq!(&c.get_ref()[..], encode_config("abc", URL_SAFE).as_bytes()); + assert_eq!( + &c.get_ref()[..], + encode_engine("abc", &URL_SAFE_ENGINE).as_bytes() + ); } #[test] fn encode_one_then_five_bytes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); let sz = enc.write(b"a").unwrap(); assert_eq!(sz, 1); @@ -63,7 +75,7 @@ fn encode_one_then_five_bytes() { } assert_eq!( &c.get_ref()[..], - encode_config("abcdef", URL_SAFE).as_bytes() + encode_engine("abcdef", &URL_SAFE_ENGINE).as_bytes() ); } @@ -71,7 +83,7 @@ fn encode_one_then_five_bytes() { fn encode_1_2_3_bytes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); let sz = enc.write(b"a").unwrap(); assert_eq!(sz, 1); @@ -82,7 +94,7 @@ fn encode_1_2_3_bytes() { } assert_eq!( &c.get_ref()[..], - encode_config("abcdef", URL_SAFE).as_bytes() + encode_engine("abcdef", &URL_SAFE_ENGINE).as_bytes() ); } @@ -90,20 +102,23 @@ fn encode_1_2_3_bytes() { fn encode_with_padding() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); enc.write_all(b"abcd").unwrap(); enc.flush().unwrap(); } - assert_eq!(&c.get_ref()[..], encode_config("abcd", URL_SAFE).as_bytes()); + assert_eq!( + &c.get_ref()[..], + encode_engine("abcd", &URL_SAFE_ENGINE).as_bytes() + ); } #[test] fn encode_with_padding_multiple_writes() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(2, enc.write(b"bc").unwrap()); @@ -114,7 +129,7 @@ fn encode_with_padding_multiple_writes() { } assert_eq!( &c.get_ref()[..], - encode_config("abcdefg", URL_SAFE).as_bytes() + encode_engine("abcdefg", &URL_SAFE_ENGINE).as_bytes() ); } @@ -122,7 +137,7 @@ fn encode_with_padding_multiple_writes() { fn finish_writes_extra_byte() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, URL_SAFE); + let mut enc = EncoderWriter::from(&mut c, &URL_SAFE_ENGINE); assert_eq!(6, enc.write(b"abcdef").unwrap()); @@ -134,7 +149,7 @@ fn finish_writes_extra_byte() { } assert_eq!( &c.get_ref()[..], - encode_config("abcdefg", URL_SAFE).as_bytes() + encode_engine("abcdefg", &URL_SAFE_ENGINE).as_bytes() ); } @@ -142,7 +157,7 @@ fn finish_writes_extra_byte() { fn write_partial_chunk_encodes_partial_chunk() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); // nothing encoded yet assert_eq!(2, enc.write(b"ab").unwrap()); @@ -151,7 +166,7 @@ fn write_partial_chunk_encodes_partial_chunk() { } assert_eq!( &c.get_ref()[..], - encode_config("ab", STANDARD_NO_PAD).as_bytes() + encode_engine("ab", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(3, c.get_ref().len()); } @@ -160,14 +175,14 @@ fn write_partial_chunk_encodes_partial_chunk() { fn write_1_chunk_encodes_complete_chunk() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(3, enc.write(b"abc").unwrap()); let _ = enc.finish().unwrap(); } assert_eq!( &c.get_ref()[..], - encode_config("abc", STANDARD_NO_PAD).as_bytes() + encode_engine("abc", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(4, c.get_ref().len()); } @@ -176,15 +191,15 @@ fn write_1_chunk_encodes_complete_chunk() { fn write_1_chunk_and_partial_encodes_only_complete_chunk() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); - // "d" not written + // "d" not consumed since it's not a full chunk assert_eq!(3, enc.write(b"abcd").unwrap()); let _ = enc.finish().unwrap(); } assert_eq!( &c.get_ref()[..], - encode_config("abc", STANDARD_NO_PAD).as_bytes() + encode_engine("abc", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(4, c.get_ref().len()); } @@ -193,7 +208,7 @@ fn write_1_chunk_and_partial_encodes_only_complete_chunk() { fn write_2_partials_to_exactly_complete_chunk_encodes_complete_chunk() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); assert_eq!(2, enc.write(b"bc").unwrap()); @@ -201,7 +216,7 @@ fn write_2_partials_to_exactly_complete_chunk_encodes_complete_chunk() { } assert_eq!( &c.get_ref()[..], - encode_config("abc", STANDARD_NO_PAD).as_bytes() + encode_engine("abc", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(4, c.get_ref().len()); } @@ -211,7 +226,7 @@ fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_en ) { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); // doesn't consume "d" @@ -220,7 +235,7 @@ fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_en } assert_eq!( &c.get_ref()[..], - encode_config("abc", STANDARD_NO_PAD).as_bytes() + encode_engine("abc", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(4, c.get_ref().len()); } @@ -229,7 +244,7 @@ fn write_partial_then_enough_to_complete_chunk_but_not_complete_another_chunk_en fn write_partial_then_enough_to_complete_chunk_and_another_chunk_encodes_complete_chunks() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); // completes partial chunk, and another chunk @@ -238,7 +253,7 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_encodes_complet } assert_eq!( &c.get_ref()[..], - encode_config("abcdef", STANDARD_NO_PAD).as_bytes() + encode_engine("abcdef", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(8, c.get_ref().len()); } @@ -248,7 +263,7 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par ) { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); // completes partial chunk, and another chunk, with one more partial chunk that's not @@ -258,7 +273,7 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par } assert_eq!( &c.get_ref()[..], - encode_config("abcdef", STANDARD_NO_PAD).as_bytes() + encode_engine("abcdef", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(8, c.get_ref().len()); } @@ -267,12 +282,12 @@ fn write_partial_then_enough_to_complete_chunk_and_another_chunk_and_another_par fn drop_calls_finish_for_you() { let mut c = Cursor::new(Vec::new()); { - let mut enc = EncoderWriter::new(&mut c, STANDARD_NO_PAD); + let mut enc = EncoderWriter::from(&mut c, &NO_PAD_ENGINE); assert_eq!(1, enc.write(b"a").unwrap()); } assert_eq!( &c.get_ref()[..], - encode_config("a", STANDARD_NO_PAD).as_bytes() + encode_engine("a", &NO_PAD_ENGINE).as_bytes() ); assert_eq!(2, c.get_ref().len()); } @@ -295,11 +310,11 @@ fn every_possible_split_of_input() { orig_data.push(rng.gen()); } - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut normal_encoded); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut normal_encoded, &engine); { - let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config); + let mut stream_encoder = EncoderWriter::from(&mut stream_encoded, &engine); // Write the first i bytes, then the rest stream_encoder.write_all(&orig_data[0..i]).unwrap(); stream_encoder.write_all(&orig_data[i..]).unwrap(); @@ -338,8 +353,8 @@ fn retrying_writes_that_error_with_interrupted_works() { } // encode the normal way - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut normal_encoded); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut normal_encoded, &engine); // encode via the stream encoder { @@ -350,7 +365,7 @@ fn retrying_writes_that_error_with_interrupted_works() { fraction: 0.8, }; - let mut stream_encoder = EncoderWriter::new(&mut interrupting_writer, config); + let mut stream_encoder = EncoderWriter::from(&mut interrupting_writer, &engine); let mut bytes_consumed = 0; while bytes_consumed < orig_len { // use short inputs since we want to use `extra` a lot as that's what needs rollback @@ -402,8 +417,8 @@ fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_ } // encode the normal way - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut normal_encoded); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut normal_encoded, &engine); // encode via the stream encoder { @@ -415,7 +430,7 @@ fn writes_that_only_write_part_of_input_and_sometimes_interrupt_produce_correct_ no_interrupt_fraction: 0.1, }; - let mut stream_encoder = EncoderWriter::new(&mut partial_writer, config); + let mut stream_encoder = EncoderWriter::from(&mut partial_writer, &engine); let mut bytes_consumed = 0; while bytes_consumed < orig_len { // use at most medium-length inputs to exercise retry logic more aggressively @@ -481,12 +496,12 @@ fn do_encode_random_config_matches_normal_encode(max_input_len: usize) { } // encode the normal way - let config = random_config(&mut rng); - encode_config_buf(&orig_data, config, &mut normal_encoded); + let engine = random_engine(&mut rng); + encode_engine_string(&orig_data, &mut normal_encoded, &engine); // encode via the stream encoder { - let mut stream_encoder = EncoderWriter::new(&mut stream_encoded, config); + let mut stream_encoder = EncoderWriter::from(&mut stream_encoded, &engine); let mut bytes_consumed = 0; while bytes_consumed < orig_len { let input_len: usize = diff --git a/src/write/mod.rs b/src/write/mod.rs index 98cb48c4..ef9be613 100644 --- a/src/write/mod.rs +++ b/src/write/mod.rs @@ -3,6 +3,7 @@ mod encoder; mod encoder_string_writer; pub use self::encoder::EncoderWriter; pub use self::encoder_string_writer::EncoderStringWriter; +pub use self::encoder_string_writer::StrConsumer; #[cfg(test)] mod encoder_tests; diff --git a/tests/decode.rs b/tests/decode.rs index 282bccd9..d7e29a76 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -1,11 +1,13 @@ extern crate base64; +use base64::engine::fast_portable::{FastPortable, NO_PAD}; +use base64::engine::DEFAULT_ENGINE; use base64::*; -mod helpers; - use self::helpers::*; +mod helpers; + #[test] fn decode_rfc4648_0() { compare_decode("", ""); @@ -71,260 +73,14 @@ fn decode_rfc4648_6() { fn decode_reject_null() { assert_eq!( DecodeError::InvalidByte(3, 0x0), - decode_config("YWx\0pY2U==", config_std_pad()).unwrap_err() + decode_engine("YWx\0pY2U==", &DEFAULT_ENGINE).unwrap_err() ); } -#[test] -fn decode_single_pad_byte_after_2_chars_in_trailing_quad_ok() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("Zg="); - - let input_len = num_quads * 3 + 1; - - // Since there are 3 bytes in the trailing quad, want to be sure this allows for the fact - // that it could be bad padding rather than assuming that it will decode to 2 bytes and - // therefore allow 1 extra round of fast decode logic (stage 1 / 2). - - let mut decoded = Vec::new(); - decoded.resize(input_len, 0); - - assert_eq!( - input_len, - decode_config_slice(&s, STANDARD, &mut decoded).unwrap() - ); - } -} - -//this is a MAY in the rfc: https://tools.ietf.org/html/rfc4648#section-3.3 -#[test] -fn decode_1_pad_byte_in_fast_loop_then_extra_padding_chunk_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("YWxpY2U====="); - - // since the first 8 bytes are handled in stage 1 or 2, the padding is detected as a - // generic invalid byte, not specifcally a padding issue. - // Could argue that the *next* padding byte (in the next quad) is technically the first - // erroneous one, but reporting that accurately is more complex and probably nobody cares - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 7, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_2_pad_bytes_in_leftovers_then_extra_padding_chunk_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("YWxpY2UABB===="); - - // 6 bytes (4 padding) after last 8-byte chunk, so it's decoded by stage 4. - // First padding byte is invalid. - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 10, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_valid_bytes_after_padding_in_leftovers_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("YWxpY2UABB=B"); - - // 4 bytes after last 8-byte chunk, so it's decoded by stage 4. - // First (and only) padding byte is invalid. - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 10, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_absurd_pad_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("==Y=Wx===pY=2U====="); - - // Plenty of remaining bytes, so handled by stage 1 or 2. - // first padding byte - assert_eq!( - DecodeError::InvalidByte(num_quads * 4, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_extra_padding_after_1_pad_bytes_in_trailing_quad_returns_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("EEE==="); - - // handled by stage 1, 2, or 4 depending on length - // first padding byte -- which would be legal if it was the only padding - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 3, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_extra_padding_after_2_pad_bytes_in_trailing_quad_2_returns_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("EE===="); - - // handled by stage 1, 2, or 4 depending on length - // first padding byte -- which would be legal if it was by itself - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 2, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_start_quad_with_padding_returns_error() { - for num_quads in 0..25 { - // add enough padding to ensure that we'll hit all 4 stages at the different lengths - for pad_bytes in 1..32 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - let padding: String = std::iter::repeat("=").take(pad_bytes).collect(); - s.push_str(&padding); - - if pad_bytes % 4 == 1 { - // detected in early length check - assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err()); - } else { - // padding lengths 2 - 8 are handled by stage 4 - // padding length >= 8 will hit at least one chunk at stages 1, 2, 3 at different - // prefix lengths - assert_eq!( - DecodeError::InvalidByte(num_quads * 4, b'='), - decode(&s).unwrap_err() - ); - } - } - } -} - -#[test] -fn decode_padding_followed_by_non_padding_returns_error() { - for num_quads in 0..25 { - for pad_bytes in 0..31 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - let padding: String = std::iter::repeat("=").take(pad_bytes).collect(); - s.push_str(&padding); - s.push_str("E"); - - if pad_bytes % 4 == 0 { - assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err()); - } else { - // pad len 1 - 8 will be handled by stage 4 - // pad len 9 (suffix len 10) will have 8 bytes of padding handled by stage 3 - // first padding byte - assert_eq!( - DecodeError::InvalidByte(num_quads * 4, b'='), - decode(&s).unwrap_err() - ); - } - } - } -} - -#[test] -fn decode_one_char_in_quad_with_padding_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push_str("E="); - - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 1, b'='), - decode(&s).unwrap_err() - ); - - // more padding doesn't change the error - s.push_str("="); - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 1, b'='), - decode(&s).unwrap_err() - ); - - s.push_str("="); - assert_eq!( - DecodeError::InvalidByte(num_quads * 4 + 1, b'='), - decode(&s).unwrap_err() - ); - } -} - -#[test] -fn decode_one_char_in_quad_without_padding_error() { - for num_quads in 0..25 { - let mut s: String = std::iter::repeat("ABCD").take(num_quads).collect(); - s.push('E'); - - assert_eq!(DecodeError::InvalidLength, decode(&s).unwrap_err()); - } -} - -#[test] -fn decode_reject_invalid_bytes_with_correct_error() { - for length in 1..100 { - for index in 0_usize..length { - for invalid_byte in " \t\n\r\x0C\x0B\x00%*.".bytes() { - let prefix: String = std::iter::repeat("A").take(index).collect(); - let suffix: String = std::iter::repeat("B").take(length - index - 1).collect(); - - let input = prefix + &String::from_utf8(vec![invalid_byte]).unwrap() + &suffix; - assert_eq!( - length, - input.len(), - "length {} error position {}", - length, - index - ); - - if length % 4 == 1 && !suffix.is_empty() { - assert_eq!(DecodeError::InvalidLength, decode(&input).unwrap_err()); - } else { - assert_eq!( - DecodeError::InvalidByte(index, invalid_byte), - decode(&input).unwrap_err() - ); - } - } - } - } -} - #[test] fn decode_imap() { assert_eq!( - decode_config(b"+,,+", crate::IMAP_MUTF7), - decode_config(b"+//+", crate::STANDARD_NO_PAD) - ); -} - -#[test] -fn decode_invalid_trailing_bytes() { - // The case of trailing newlines is common enough to warrant a test for a good error - // message. - assert_eq!( - Err(DecodeError::InvalidByte(8, b'\n')), - decode(b"Zm9vCg==\n") + decode_engine(b"+,,+", &FastPortable::from(&alphabet::IMAP_MUTF7, NO_PAD),), + decode_engine(b"+//+", &DEFAULT_ENGINE) ); - // extra padding, however, is still InvalidLength - assert_eq!(Err(DecodeError::InvalidLength), decode(b"Zm9vCg===")); -} - -fn config_std_pad() -> Config { - Config::new(CharacterSet::Standard, true) } diff --git a/tests/encode.rs b/tests/encode.rs index 0004be00..7b3561e4 100644 --- a/tests/encode.rs +++ b/tests/encode.rs @@ -1,5 +1,7 @@ extern crate base64; +use base64::alphabet::URL_SAFE; +use base64::engine::fast_portable::{NO_PAD, PAD}; use base64::*; fn compare_encode(expected: &str, target: &[u8]) { @@ -90,13 +92,19 @@ fn encode_all_bytes_url() { -AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq\ -wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj5OXm5-jp6uvs7e7v8PHy\ 8_T19vf4-fr7_P3-_w==", - encode_config(&bytes, URL_SAFE) + encode_engine( + &bytes, + &engine::fast_portable::FastPortable::from(&URL_SAFE, PAD) + ) ); } #[test] fn encode_url_safe_without_padding() { - let encoded = encode_config(b"alice", URL_SAFE_NO_PAD); + let encoded = encode_engine( + b"alice", + &engine::fast_portable::FastPortable::from(&URL_SAFE, NO_PAD), + ); assert_eq!(&encoded, "YWxpY2U"); assert_eq!( String::from_utf8(decode(&encoded).unwrap()).unwrap(), diff --git a/tests/tests.rs b/tests/tests.rs index 11fed960..89655dc2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -3,16 +3,20 @@ extern crate rand; use rand::{FromEntropy, Rng}; +use base64::engine::{Engine, DEFAULT_ENGINE}; use base64::*; -mod helpers; use self::helpers::*; +use base64::alphabet::STANDARD; +use base64::engine::fast_portable::{FastPortable, NO_PAD}; + +mod helpers; // generate random contents of the specified length and test encode/decode roundtrip -fn roundtrip_random( +fn roundtrip_random( byte_buf: &mut Vec, str_buf: &mut String, - config: Config, + engine: &E, byte_len: usize, approx_values_per_byte: u8, max_rounds: u64, @@ -30,8 +34,8 @@ fn roundtrip_random( byte_buf.push(r.gen::()); } - encode_config_buf(&byte_buf, config, str_buf); - decode_config_buf(&str_buf, config, &mut decode_buf).unwrap(); + encode_engine_string(&byte_buf, str_buf, engine); + decode_engine_vec(&str_buf, &mut decode_buf, engine).unwrap(); assert_eq!(byte_buf, &decode_buf); } @@ -52,17 +56,20 @@ fn calculate_number_of_rounds(byte_len: usize, approx_values_per_byte: u8, max: prod } -fn no_pad_config() -> Config { - Config::new(CharacterSet::Standard, false) -} - #[test] fn roundtrip_random_short_standard() { let mut byte_buf: Vec = Vec::new(); let mut str_buf = String::new(); for input_len in 0..40 { - roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 10000); + roundtrip_random( + &mut byte_buf, + &mut str_buf, + &DEFAULT_ENGINE, + input_len, + 4, + 10000, + ); } } @@ -72,7 +79,14 @@ fn roundtrip_random_with_fast_loop_standard() { let mut str_buf = String::new(); for input_len in 40..100 { - roundtrip_random(&mut byte_buf, &mut str_buf, STANDARD, input_len, 4, 1000); + roundtrip_random( + &mut byte_buf, + &mut str_buf, + &DEFAULT_ENGINE, + input_len, + 4, + 1000, + ); } } @@ -81,15 +95,9 @@ fn roundtrip_random_short_no_padding() { let mut byte_buf: Vec = Vec::new(); let mut str_buf = String::new(); + let engine = FastPortable::from(&STANDARD, NO_PAD); for input_len in 0..40 { - roundtrip_random( - &mut byte_buf, - &mut str_buf, - no_pad_config(), - input_len, - 4, - 10000, - ); + roundtrip_random(&mut byte_buf, &mut str_buf, &engine, input_len, 4, 10000); } } @@ -98,15 +106,10 @@ fn roundtrip_random_no_padding() { let mut byte_buf: Vec = Vec::new(); let mut str_buf = String::new(); + let engine = FastPortable::from(&STANDARD, NO_PAD); + for input_len in 40..100 { - roundtrip_random( - &mut byte_buf, - &mut str_buf, - no_pad_config(), - input_len, - 4, - 1000, - ); + roundtrip_random(&mut byte_buf, &mut str_buf, &engine, input_len, 4, 1000); } } @@ -126,7 +129,10 @@ fn roundtrip_decode_trailing_10_bytes() { let decoded = decode(&s).unwrap(); assert_eq!(num_quads * 3 + 7, decoded.len()); - assert_eq!(s, encode_config(&decoded, STANDARD_NO_PAD)); + assert_eq!( + s, + encode_engine(&decoded, &FastPortable::from(&STANDARD, NO_PAD)) + ); } } @@ -143,7 +149,7 @@ fn display_wrapper_matches_normal_encode() { encode(&bytes), format!( "{}", - base64::display::Base64Display::with_config(&bytes, STANDARD) + base64::display::Base64Display::from(&bytes, &DEFAULT_ENGINE) ) ); } @@ -156,32 +162,37 @@ fn because_we_can() { } #[test] -fn encode_config_slice_can_use_inline_buffer() { +fn encode_engine_slice_can_use_inline_buffer() { let mut buf: [u8; 22] = [0; 22]; let mut larger_buf: [u8; 24] = [0; 24]; let mut input: [u8; 16] = [0; 16]; + let engine = FastPortable::from(&STANDARD, NO_PAD); + let mut rng = rand::rngs::SmallRng::from_entropy(); for elt in &mut input { *elt = rng.gen(); } - assert_eq!(22, encode_config_slice(&input, STANDARD_NO_PAD, &mut buf)); - let decoded = decode_config(&buf, STANDARD_NO_PAD).unwrap(); + assert_eq!(22, encode_engine_slice(&input, &mut buf, &engine)); + let decoded = decode_engine(&buf, &engine).unwrap(); assert_eq!(decoded, input); // let's try it again with padding - assert_eq!(24, encode_config_slice(&input, STANDARD, &mut larger_buf)); - let decoded = decode_config(&buf, STANDARD).unwrap(); + assert_eq!( + 24, + encode_engine_slice(&input, &mut larger_buf, &DEFAULT_ENGINE) + ); + let decoded = decode_engine(&buf, &DEFAULT_ENGINE).unwrap(); assert_eq!(decoded, input); } #[test] #[should_panic(expected = "index 24 out of range for slice of length 22")] -fn encode_config_slice_panics_when_buffer_too_small() { +fn encode_engine_slice_panics_when_buffer_too_small() { let mut buf: [u8; 22] = [0; 22]; let mut input: [u8; 16] = [0; 16]; @@ -190,5 +201,5 @@ fn encode_config_slice_panics_when_buffer_too_small() { *elt = rng.gen(); } - encode_config_slice(&input, STANDARD, &mut buf); + encode_engine_slice(&input, &mut buf, &DEFAULT_ENGINE); } From dbbce4656d9310f641437a7c03fa88bc20e11d69 Mon Sep 17 00:00:00 2001 From: Marshall Pierce Date: Thu, 18 Feb 2021 06:36:14 -0700 Subject: [PATCH 2/5] Make decode invalid byte test more specific --- src/engine/fast_portable/mod.rs | 17 +++++++++++++++++ src/engine/tests.rs | 29 +++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/engine/fast_portable/mod.rs b/src/engine/fast_portable/mod.rs index ac7b17ba..fdab4453 100644 --- a/src/engine/fast_portable/mod.rs +++ b/src/engine/fast_portable/mod.rs @@ -216,6 +216,23 @@ pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] { return decode_table; } +// fn decode_aligned(symbol: u8, decode_table: &[u8; 256]) -> u8 { +// let mut result: u8 = 0x00; +// // If `symbol` is inside the printable range, one of these two derived indices will be equal to +// // the original index, and the decoded byte will end up in `result`. If `symbol` is not +// // printable, neither will equal the original symbol, and so both decoded bytes will have 0x00 +// // as a mask. +// // TODO invalid bytes decoded to 0x00 instead of 0xFF? +// let idx: [u8; 2] = [symbol % 64, symbol % 64 + 64]; +// for i in 0..2 { +// let symbol_eq_mod = idx[i] == symbol; +// // if symbol equals its mod flavor, 0xFF, else 0x00 +// let mask = ((symbol_eq_mod) as i8 - 1) as u8; +// result = result | (decode_table[idx[i] as usize] & mask); +// } +// result +// } + #[inline] fn read_u64(s: &[u8]) -> u64 { u64::from_be_bytes(s[..8].try_into().unwrap()) diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 2142a341..3001bd3b 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -7,7 +7,7 @@ use rstest_reuse::{apply, template}; use std::iter; use crate::{ - alphabet::STANDARD, + alphabet::{Alphabet, STANDARD}, encode, engine::{fast_portable, naive, Engine}, tests::random_alphabet, @@ -451,7 +451,8 @@ fn decode_invalid_byte_error(engine_wrapper: E) { let len_range = Uniform::new(1, 1_000); for _ in 0..10_000 { - let engine = E::random(&mut rng); + let alphabet = random_alphabet(&mut rng); + let engine = E::random_alphabet(&mut rng, &alphabet); orig_data.clear(); encode_buf.clear(); @@ -470,7 +471,15 @@ fn decode_invalid_byte_error(engine_wrapper: E) { decode_buf.resize(orig_len, 0); // replace one encoded byte with an invalid byte - let invalid_byte = 0x07; // BEL, non-printing, so never in an alphabet + let invalid_byte: u8 = loop { + let byte: u8 = rng.gen(); + + if alphabet.symbols.contains(&byte) { + continue; + } else { + break byte; + } + }; let invalid_range = Uniform::new(0, orig_len); let invalid_index = invalid_range.sample(&mut rng); @@ -799,10 +808,14 @@ trait EngineWrapper { /// Return an engine configured for RFC standard base64 fn standard() -> Self::Engine; - /// Return an engine configured for RFC standard + /// Return an engine configured for RFC standard base64 that allows invalid trailing bits fn standard_forgiving() -> Self::Engine; + /// Return an engine configured with a randomized alphabet and config fn random(rng: &mut R) -> Self::Engine; + + /// Return an engine configured with the specified alphabet and randomized config + fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine; } struct FastPortableWrapper {} @@ -824,6 +837,10 @@ impl EngineWrapper for FastPortableWrapper { fn random(rng: &mut R) -> Self::Engine { let alphabet = random_alphabet(rng); + Self::random_alphabet(rng, &alphabet) + } + + fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine { let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen()); fast_portable::FastPortable::from(alphabet, config) @@ -858,6 +875,10 @@ impl EngineWrapper for NaiveWrapper { fn random(rng: &mut R) -> Self::Engine { let alphabet = random_alphabet(rng); + Self::random_alphabet(rng, alphabet) + } + + fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine { let config = naive::NaiveConfig { padding: rng.gen(), decode_allow_trailing_bits: rng.gen(), From ce8bb8404f49201d908df068063e38ebdd8bee7a Mon Sep 17 00:00:00 2001 From: Marshall Pierce <575695+marshallpierce@users.noreply.github.com> Date: Fri, 6 Aug 2021 11:37:10 -0600 Subject: [PATCH 3/5] Clean up FastPortableConfig creation also a few other Config tweaks --- Cargo.toml | 4 +- src/chunked_encoder.rs | 4 +- src/decode.rs | 6 +-- src/encode.rs | 22 ++++---- src/engine/fast_portable/decode.rs | 2 +- src/engine/fast_portable/mod.rs | 87 ++++++++++++++++++------------ src/engine/mod.rs | 15 +++--- src/engine/naive.rs | 6 +-- src/engine/tests.rs | 11 ++-- src/read/decoder_tests.rs | 2 +- src/tests.rs | 6 ++- 11 files changed, 94 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 64c146c8..c8ef36c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,8 @@ criterion = "0.3.4" rand = "0.6.1" structopt = "0.3.21" # test fixtures for engine tests -rstest = "0.6.4" -rstest_reuse = "0.1.1" +rstest = "0.11.0" +rstest_reuse = "0.1.3" [features] default = ["std"] diff --git a/src/chunked_encoder.rs b/src/chunked_encoder.rs index b27e27a9..bd5d8426 100644 --- a/src/chunked_encoder.rs +++ b/src/chunked_encoder.rs @@ -27,7 +27,7 @@ impl<'e, E: Engine> ChunkedEncoder<'e, E> { pub fn from(engine: &'e E) -> ChunkedEncoder<'e, E> { ChunkedEncoder { engine, - max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().padding()), + max_input_chunk_len: max_input_length(BUF_SIZE, engine.config().encode_padding()), } } @@ -46,7 +46,7 @@ impl<'e, E: Engine> ChunkedEncoder<'e, E> { input_index += input_chunk_len; let more_input_left = input_index < bytes.len(); - if self.engine.config().padding() && !more_input_left { + if self.engine.config().encode_padding() && !more_input_left { // no more input, add padding if needed. Buffer will have room because // max_input_length leaves room for it. b64_bytes_written += add_padding(bytes.len(), &mut encode_buf[b64_bytes_written..]); diff --git a/src/decode.rs b/src/decode.rs index 0390aa16..d82010da 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -240,7 +240,7 @@ mod tests { let engine = random_engine(&mut rng); encode_engine_string(&orig_data, &mut encoded_data, &engine); - assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); + assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len); let prefix_len = prefix_len_range.sample(&mut rng); @@ -295,7 +295,7 @@ mod tests { let engine = random_engine(&mut rng); encode_engine_string(&orig_data, &mut encoded_data, &engine); - assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); + assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len); // fill the buffer with random garbage, long enough to have some room before and after for _ in 0..5000 { @@ -347,7 +347,7 @@ mod tests { let engine = random_engine(&mut rng); encode_engine_string(&orig_data, &mut encoded_data, &engine); - assert_encode_sanity(&encoded_data, engine.config().padding(), input_len); + assert_encode_sanity(&encoded_data, engine.config().encode_padding(), input_len); decode_buf.resize(input_len, 0); diff --git a/src/encode.rs b/src/encode.rs index b4ccb424..3a8fac8e 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -54,7 +54,7 @@ pub fn encode>(input: T) -> String { ///``` #[cfg(any(feature = "alloc", feature = "std", test))] pub fn encode_engine>(input: T, engine: &E) -> String { - let encoded_size = encoded_len(input.as_ref().len(), engine.config().padding()) + let encoded_size = encoded_len(input.as_ref().len(), engine.config().encode_padding()) .expect("integer overflow when calculating buffer size"); let mut buf = vec![0; encoded_size]; @@ -148,7 +148,7 @@ pub fn encode_engine_slice>( ) -> usize { let input_bytes = input.as_ref(); - let encoded_size = encoded_len(input_bytes.len(), engine.config().padding()) + let encoded_size = encoded_len(input_bytes.len(), engine.config().encode_padding()) .expect("usize overflow when calculating buffer size"); let mut b64_output = &mut output_buf[0..encoded_size]; @@ -178,7 +178,7 @@ fn encode_with_padding( let b64_bytes_written = engine.encode(input, output); - let padding_bytes = if engine.config().padding() { + let padding_bytes = if engine.config().encode_padding() { add_padding(input.len(), &mut output[b64_bytes_written..]) } else { 0 @@ -350,12 +350,12 @@ mod tests { ); assert_encode_sanity( &encoded_data_no_prefix, - engine.config().padding(), + engine.config().encode_padding(), input_len, ); assert_encode_sanity( &encoded_data_with_prefix[prefix_len..], - engine.config().padding(), + engine.config().encode_padding(), input_len, ); @@ -401,7 +401,7 @@ mod tests { let engine = random_engine(&mut rng); - let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap(); assert_eq!( encoded_size, @@ -410,7 +410,7 @@ mod tests { assert_encode_sanity( std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), - engine.config().padding(), + engine.config().encode_padding(), input_len, ); @@ -447,7 +447,7 @@ mod tests { let engine = random_engine(&mut rng); - let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap(); encoded_data.resize(encoded_size, 0); @@ -458,7 +458,7 @@ mod tests { assert_encode_sanity( std::str::from_utf8(&encoded_data[0..encoded_size]).unwrap(), - engine.config().padding(), + engine.config().encode_padding(), input_len, ); @@ -490,7 +490,7 @@ mod tests { let engine = random_engine(&mut rng); // fill up the output buffer with garbage - let encoded_size = encoded_len(input_len, config.padding()).unwrap(); + let encoded_size = encoded_len(input_len, config.encode_padding()).unwrap(); for _ in 0..encoded_size { output.push(rng.gen()); } @@ -529,7 +529,7 @@ mod tests { let engine = random_engine(&mut rng); // fill up the output buffer with garbage - let encoded_size = encoded_len(input_len, engine.config().padding()).unwrap(); + let encoded_size = encoded_len(input_len, engine.config().encode_padding()).unwrap(); for _ in 0..encoded_size + 1000 { output.push(rng.gen()); } diff --git a/src/engine/fast_portable/decode.rs b/src/engine/fast_portable/decode.rs index 2c8994ca..016ac885 100644 --- a/src/engine/fast_portable/decode.rs +++ b/src/engine/fast_portable/decode.rs @@ -20,7 +20,7 @@ const INPUT_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * INPUT_CHUNK_LEN; const DECODED_BLOCK_LEN: usize = CHUNKS_PER_FAST_LOOP_BLOCK * DECODED_CHUNK_LEN + DECODED_CHUNK_SUFFIX; -/// Estimate with metadata for FastPortable's decode logic +#[doc(hidden)] pub struct FastPortableEstimate { /// Total number of decode chunks, including a possibly partial last chunk num_chunks: usize, diff --git a/src/engine/fast_portable/mod.rs b/src/engine/fast_portable/mod.rs index fdab4453..b70f8d01 100644 --- a/src/engine/fast_portable/mod.rs +++ b/src/engine/fast_portable/mod.rs @@ -176,8 +176,8 @@ impl super::Engine for FastPortable { ) } - fn config(&self) -> Self::Config { - self.config + fn config(&self) -> &Self::Config { + &self.config } } @@ -238,43 +238,61 @@ fn read_u64(s: &[u8]) -> u64 { u64::from_be_bytes(s[..8].try_into().unwrap()) } -/// Contains miscellaneous configuration parameters for base64 encoding and decoding. +/// Contains configuration parameters for base64 encoding and decoding. +/// +/// ``` +/// # use base64::engine::fast_portable::FastPortableConfig; +/// let config = FastPortableConfig::new() +/// .with_encode_padding(false); +/// // further customize using `.with_*` methods as needed +/// ``` +/// +/// The constants [PAD] and [NO_PAD] cover most use cases. /// /// To specify the characters used, see [crate::alphabet::Alphabet]. #[derive(Clone, Copy, Debug)] pub struct FastPortableConfig { - /// `true` to pad output with `=` characters - padding: bool, - /// `true` to ignore excess nonzero bits in the last few symbols, otherwise an error is returned + encode_padding: bool, decode_allow_trailing_bits: bool, } impl FastPortableConfig { - /// Create a new config. + /// Create a new config with `padding` = `true` and `decode_allow_trailing_bits` = `false`. /// - /// - `padding`: if `true`, encoding will append `=` padding characters to produce an - /// output whose length is a multiple of 4. Padding is not needed for decoding and - /// only serves to waste bytes but it's in the spec. For new applications, consider - /// not using padding. - /// - `decode_allow_trailing_bits`: If unsure, use `false`. - /// Useful if you need to decode base64 produced by a buggy encoder that - /// has bits set in the unused space on the last base64 character as per - /// [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode). - /// If invalid trailing bits are present and this `true`, those bits will - /// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted. - pub const fn from(padding: bool, decode_allow_trailing_bits: bool) -> FastPortableConfig { + /// This probably matches most people's expectations, but consider disabling padding to save + /// a few bytes unless you specifically need it for compatibility with some legacy system. + pub const fn new() -> FastPortableConfig { FastPortableConfig { - padding, - decode_allow_trailing_bits, + // RFC states that padding must be applied by default + encode_padding: true, + decode_allow_trailing_bits: false, } } - /// Create a new `Config` based on `self` with an updated `padding` parameter. - pub const fn with_padding(self, padding: bool) -> FastPortableConfig { - FastPortableConfig { padding, ..self } + /// Create a new config based on `self` with an updated `padding` parameter. + /// + /// If `true`, encoding will append either 1 or 2 `=` padding characters to produce an + /// output whose length is a multiple of 4. + /// + /// Padding is not needed for correct decoding and only serves to waste bytes, but it's in the + /// [spec](https://datatracker.ietf.org/doc/html/rfc4648#section-3.2). + /// + /// For new applications, consider not using padding if the decoders you're using don't require + /// padding to be present. + pub const fn with_encode_padding(self, padding: bool) -> FastPortableConfig { + FastPortableConfig { + encode_padding: padding, + ..self + } } - /// Create a new `Config` based on `self` with an updated `decode_allow_trailing_bits` parameter. + /// Create a new config based on `self` with an updated `decode_allow_trailing_bits` parameter. + /// + /// Most users will not need to configure this. It's useful if you need to decode base64 + /// produced by a buggy encoder that has bits set in the unused space on the last base64 + /// character as per [forgiving-base64 decode](https://infra.spec.whatwg.org/#forgiving-base64-decode). + /// If invalid trailing bits are present and this is `true`, those bits will + /// be silently ignored, else `DecodeError::InvalidLastSymbol` will be emitted. pub const fn with_decode_allow_trailing_bits(self, allow: bool) -> FastPortableConfig { FastPortableConfig { decode_allow_trailing_bits: allow, @@ -283,20 +301,21 @@ impl FastPortableConfig { } } +impl Default for FastPortableConfig { + /// Delegates to [FastPortableConfig::new]. + fn default() -> Self { + FastPortableConfig::new() + } +} + impl Config for FastPortableConfig { - fn padding(&self) -> bool { - self.padding + fn encode_padding(&self) -> bool { + self.encode_padding } } /// Include padding bytes when encoding. -pub const PAD: FastPortableConfig = FastPortableConfig { - padding: true, - decode_allow_trailing_bits: false, -}; +pub const PAD: FastPortableConfig = FastPortableConfig::new(); /// Don't add padding when encoding. -pub const NO_PAD: FastPortableConfig = FastPortableConfig { - padding: false, - decode_allow_trailing_bits: false, -}; +pub const NO_PAD: FastPortableConfig = FastPortableConfig::new().with_encode_padding(false); diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 03902c5e..5a4f8dfe 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -10,8 +10,6 @@ mod naive; #[cfg(test)] mod tests; -// TODO shared DecodeError or per-impl as an associated type? - /// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. /// /// Different implementations offer different characteristics. The library currently ships with @@ -25,7 +23,7 @@ mod tests; // - add an implementation of [engine::tests::EngineWrapper] // - add the implementation to the `all_engines` macro // All tests run on all engines listed in the macro. -pub trait Engine { +pub trait Engine: Send + Sync { /// The config type used by this engine type Config: Config; /// The decode estimate used by this engine @@ -70,10 +68,10 @@ pub trait Engine { ) -> Result; /// Returns the config for this engine. - fn config(&self) -> Self::Config; + fn config(&self) -> &Self::Config; } -/// The minimal level of configuration engines must expose. +/// The minimal level of configuration that engines must support. pub trait Config { /// Returns `true` if padding should be added after the encoded output. /// @@ -83,10 +81,11 @@ pub trait Config { // It could be provided as a separate parameter when encoding, but that feels like // leaking an implementation detail to the user, and it's hopefully more convenient // to have to only pass one thing (the engine) to any part of the API. - fn padding(&self) -> bool; + fn encode_padding(&self) -> bool; } -/// The decode estimate used by an engine implementation. +/// The decode estimate used by an engine implementation. Users do not need to interact with this; +/// it is only for engine implementors. /// /// Implementors may want to store relevant calculations when constructing this to avoid having /// to calculate them again during actual decoding. @@ -96,6 +95,6 @@ pub trait DecodeEstimate { fn decoded_length_estimate(&self) -> usize; } -/// An engine that will work on all CPUs using the standard base64 alphabet. +/// An engine that will work on all CPUs using the standard base64 alphabet and config. pub const DEFAULT_ENGINE: FastPortable = FastPortable::from(&alphabet::STANDARD, fast_portable::PAD); diff --git a/src/engine/naive.rs b/src/engine/naive.rs index dcc60dd7..a86f8390 100644 --- a/src/engine/naive.rs +++ b/src/engine/naive.rs @@ -264,8 +264,8 @@ impl Engine for Naive { Ok(output_index) } - fn config(&self) -> Self::Config { - self.config + fn config(&self) -> &Self::Config { + &self.config } } @@ -301,7 +301,7 @@ pub struct NaiveConfig { } impl Config for NaiveConfig { - fn padding(&self) -> bool { + fn encode_padding(&self) -> bool { self.padding } } diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 3001bd3b..756a9e02 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -14,10 +14,11 @@ use crate::{ DecodeError, PAD_BYTE, }; +// the case::foo syntax includes the "foo" in the generated test method names #[template] #[rstest(engine_wrapper, -case(FastPortableWrapper {}), -case(NaiveWrapper {}), +case::fast_portable(FastPortableWrapper {}), +case::naive(NaiveWrapper {}), )] fn all_engines(engine_wrapper: E) {} @@ -830,7 +831,7 @@ impl EngineWrapper for FastPortableWrapper { fn standard_forgiving() -> Self::Engine { fast_portable::FastPortable::from( &STANDARD, - fast_portable::FastPortableConfig::from(true, true), + fast_portable::FastPortableConfig::new().with_decode_allow_trailing_bits(true), ) } @@ -841,7 +842,9 @@ impl EngineWrapper for FastPortableWrapper { } fn random_alphabet(rng: &mut R, alphabet: &Alphabet) -> Self::Engine { - let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen()); + let config = fast_portable::FastPortableConfig::new() + .with_encode_padding(rng.gen()) + .with_decode_allow_trailing_bits(rng.gen()); fast_portable::FastPortable::from(alphabet, config) } diff --git a/src/read/decoder_tests.rs b/src/read/decoder_tests.rs index 46aa9cd0..e881e252 100644 --- a/src/read/decoder_tests.rs +++ b/src/read/decoder_tests.rs @@ -206,7 +206,7 @@ fn reports_invalid_last_symbol_correctly() { let config = random_config(&mut rng); let alphabet = random_alphabet(&mut rng); // changing padding will cause invalid padding errors when we twiddle the last byte - let engine = FastPortable::from(alphabet, config.with_padding(false)); + let engine = FastPortable::from(alphabet, config.with_encode_padding(false)); encode_engine_string(&bytes[..], &mut b64, &engine); b64_bytes.extend(b64.bytes()); assert_eq!(b64_bytes.len(), b64.len()); diff --git a/src/tests.rs b/src/tests.rs index 441e2d52..d1beea94 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -63,14 +63,16 @@ fn roundtrip_random_config(input_len_range: Uniform, iterations: u32) { encode_engine_string(&input_buf, &mut encoded_buf, &engine); - assert_encode_sanity(&encoded_buf, engine.config().padding(), input_len); + assert_encode_sanity(&encoded_buf, engine.config().encode_padding(), input_len); assert_eq!(input_buf, decode_engine(&encoded_buf, &engine).unwrap()); } } pub fn random_config(rng: &mut R) -> FastPortableConfig { - FastPortableConfig::from(rng.gen(), rng.gen()) + FastPortableConfig::new() + .with_encode_padding(rng.gen()) + .with_decode_allow_trailing_bits(rng.gen()) } pub fn random_alphabet(rng: &mut R) -> &'static alphabet::Alphabet { From 3c62bd49a21cc2513d0ae6de6c437edc5b3b853e Mon Sep 17 00:00:00 2001 From: Marshall Pierce <575695+marshallpierce@users.noreply.github.com> Date: Fri, 6 Aug 2021 13:53:57 -0600 Subject: [PATCH 4/5] Make constructing an Alphabet from a str `const`. Not useful yet since unwrap() and friends aren't const, but some future rust version can make use of it. --- .circleci/config.yml | 4 +- fuzz/fuzzers/roundtrip_no_pad.rs | 2 +- fuzz/fuzzers/utils.rs | 4 +- src/alphabet.rs | 230 +++++++++++++++++++++---------- 4 files changed, 162 insertions(+), 78 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42df9e0e..f3680c89 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -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: diff --git a/fuzz/fuzzers/roundtrip_no_pad.rs b/fuzz/fuzzers/roundtrip_no_pad.rs index 3fd7a68e..3c41beba 100644 --- a/fuzz/fuzzers/roundtrip_no_pad.rs +++ b/fuzz/fuzzers/roundtrip_no_pad.rs @@ -4,7 +4,7 @@ extern crate base64; use base64::engine::fast_portable; fuzz_target!(|data: &[u8]| { - let config = fast_portable::FastPortableConfig::from(false, false); + let config = fast_portable::FastPortableConfig::new().with_encode_padding(false); let engine = fast_portable::FastPortable::from(&base64::alphabet::STANDARD, config); let encoded = base64::encode_engine(&data, &engine); diff --git a/fuzz/fuzzers/utils.rs b/fuzz/fuzzers/utils.rs index ab80a929..138ca944 100644 --- a/fuzz/fuzzers/utils.rs +++ b/fuzz/fuzzers/utils.rs @@ -23,7 +23,9 @@ pub fn random_engine(data: &[u8]) -> fast_portable::FastPortable { alphabet::STANDARD }; - let config = fast_portable::FastPortableConfig::from(rng.gen(), rng.gen()); + let config = fast_portable::FastPortableConfig::new() + .with_encode_padding(rng.gen()) + .with_decode_allow_trailing_bits(rng.gen()); fast_portable::FastPortable::from(&alphabet, config) } diff --git a/src/alphabet.rs b/src/alphabet.rs index 650de43b..7782b85d 100644 --- a/src/alphabet.rs +++ b/src/alphabet.rs @@ -1,164 +1,246 @@ //! Provides [Alphabet] and constants for alphabets commonly used in the wild. +#[cfg(any(feature = "std", test))] +use std::{convert, error, fmt}; + +const ALPHABET_SIZE: usize = 64; + /// An alphabet defines the 64 ASCII characters (symbols) used for base64. /// /// Common alphabets are provided as constants, and custom alphabets -/// can be made via the [From](#impl-From) implementation. +/// can be made via `from_str` or the `TryFrom` implementation. /// /// ``` -/// let custom = base64::alphabet::Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +/// let custom = base64::alphabet::Alphabet::from_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/").unwrap(); /// /// let engine = base64::engine::fast_portable::FastPortable::from( /// &custom, /// base64::engine::fast_portable::PAD); /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct Alphabet { - pub(crate) symbols: [u8; 64], + pub(crate) symbols: [u8; ALPHABET_SIZE], } impl Alphabet { /// Performs no checks so that it can be const. /// Used only for known-valid strings. - const fn from_unchecked(alphabet: &str) -> Alphabet { - let mut symbols = [0_u8; 64]; + const fn from_str_unchecked(alphabet: &str) -> Alphabet { + let mut symbols = [0_u8; ALPHABET_SIZE]; let source_bytes = alphabet.as_bytes(); // a way to copy that's allowed in const fn let mut index = 0; - while index < 64 { + while index < ALPHABET_SIZE { symbols[index] = source_bytes[index]; index += 1; } Alphabet { symbols } } -} -impl> From for Alphabet { - /// Create a `CharacterSet` from a string of 64 ASCII bytes. Each byte must be - /// unique, and the `=` byte is not allowed as it is used for padding. + /// Create a `CharacterSet` from a string of 64 unique printable ASCII bytes. /// - /// # Errors + /// The `=` byte is not allowed as it is used for padding. /// - /// Panics if the text is an invalid base64 alphabet since the alphabet is - /// likely to be hardcoded, and therefore errors are generally unrecoverable - /// programmer errors. - fn from(string: T) -> Self { - let alphabet = string.as_ref(); - assert_eq!( - 64, - alphabet.as_bytes().len(), - "Base64 char set length must be 64" - ); + /// The `const`-ness of this function isn't useful as of rust 1.54.0 since `const` `unwrap()`, + /// etc, haven't shipped yet, but that's [on the roadmap](https://github.com/rust-lang/rust/issues/85194). + pub const fn from_str(alphabet: &str) -> Result { + let bytes = alphabet.as_bytes(); + if bytes.len() != ALPHABET_SIZE { + return Err(ParseAlphabetError::InvalidLength); + } - // scope just to ensure not accidentally using the sorted copy { - // Check uniqueness without allocating since this must be no_std. - // Could pull in heapless and use IndexSet, but this seems simple enough. - let mut bytes = [0_u8; 64]; - alphabet - .as_bytes() - .iter() - .enumerate() - .for_each(|(index, &byte)| bytes[index] = byte); - - bytes.sort_unstable(); - - // iterate over the sorted bytes, offset by one - bytes.iter().zip(bytes[1..].iter()).for_each(|(b1, b2)| { - // if any byte is the same as the next byte, there's a duplicate - assert_ne!(b1, b2, "Duplicate bytes"); - }); + let mut index = 0; + while index < ALPHABET_SIZE { + let byte = bytes[index]; + + // must be ascii printable. 127 (DEL) is commonly considered printable + // for some reason but clearly unsuitable for base64. + if !(byte >= 32_u8 && byte <= 126_u8) { + return Err(ParseAlphabetError::UnprintableByte(byte)); + } + // = is assumed to be padding, so cannot be used as a symbol + if b'=' == byte { + return Err(ParseAlphabetError::ReservedByte(byte)); + } + + // Check for duplicates while staying within what const allows. + // It's n^2, but only over 64 hot bytes, and only once, so it's likely in the single digit + // microsecond range. + + let mut probe_index = 0; + while probe_index < ALPHABET_SIZE { + if probe_index == index { + probe_index += 1; + continue; + } + + let probe_byte = bytes[probe_index]; + + if byte == probe_byte { + return Err(ParseAlphabetError::DuplicatedByte(byte)); + } + + probe_index += 1; + } + + index += 1; + } } - for &byte in alphabet.as_bytes() { - // must be ascii printable. 127 (DEL) is commonly considered printable - // for some reason but clearly unsuitable for base64. - assert!(byte >= 32_u8 && byte < 127_u8, "Bytes must be printable"); - // = is assumed to be padding, so cannot be used as a symbol - assert_ne!(b'=', byte, "Padding byte '=' is reserved"); - } + Ok(Self::from_str_unchecked(alphabet)) + } +} + +#[cfg(any(feature = "std", test))] +impl convert::TryFrom<&str> for Alphabet { + type Error = ParseAlphabetError; + + fn try_from(value: &str) -> Result { + Alphabet::from_str(value) + } +} + +/// Possible errors when constructing an [Alphabet] from a `str`. +#[derive(Debug, Eq, PartialEq)] +pub enum ParseAlphabetError { + /// Alphabets must be 64 ASCII bytes + InvalidLength, + /// All bytes must be unique + DuplicatedByte(u8), + /// All bytes must be printable (in the range `[32, 126]`). + UnprintableByte(u8), + /// `=` cannot be used + ReservedByte(u8), +} - Self::from_unchecked(alphabet) +#[cfg(any(feature = "std", test))] +impl fmt::Display for ParseAlphabetError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseAlphabetError::InvalidLength => write!(f, "Invalid length - must be 64 bytes"), + ParseAlphabetError::DuplicatedByte(b) => write!(f, "Duplicated byte: {}", b), + ParseAlphabetError::UnprintableByte(b) => write!(f, "Unprintable byte: {}", b), + ParseAlphabetError::ReservedByte(b) => write!(f, "Reserved byte: {}", b), + } } } +#[cfg(any(feature = "std", test))] +impl error::Error for ParseAlphabetError {} + /// The standard alphabet (uses `+` and `/`). /// /// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-3). -pub const STANDARD: Alphabet = - Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); +pub const STANDARD: Alphabet = Alphabet::from_str_unchecked( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", +); /// The URL safe alphabet (uses `-` and `_`). /// /// See [RFC 3548](https://tools.ietf.org/html/rfc3548#section-4). -pub const URL_SAFE: Alphabet = - Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"); +pub const URL_SAFE: Alphabet = Alphabet::from_str_unchecked( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", +); /// The `crypt(3)` alphabet (uses `.` and `/` as the first two values). /// /// Not standardized, but folk wisdom on the net asserts that this alphabet is what crypt uses. -pub const CRYPT: Alphabet = - Alphabet::from_unchecked("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"); +pub const CRYPT: Alphabet = Alphabet::from_str_unchecked( + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", +); /// The bcrypt alphabet. -pub const BCRYPT: Alphabet = - Alphabet::from_unchecked("./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); +pub const BCRYPT: Alphabet = Alphabet::from_str_unchecked( + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", +); /// The alphabet used in IMAP-modified UTF-7 (uses `+` and `,`). /// /// See [RFC 3501](https://tools.ietf.org/html/rfc3501#section-5.1.3) -pub const IMAP_MUTF7: Alphabet = - Alphabet::from_unchecked("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"); +pub const IMAP_MUTF7: Alphabet = Alphabet::from_str_unchecked( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,", +); /// The alphabet used in BinHex 4.0 files. /// /// See [BinHex 4.0 Definition](http://files.stairways.com/other/binhex-40-specs-info.txt) -pub const BIN_HEX: Alphabet = - Alphabet::from_unchecked("!\"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdehijklmpqr"); +pub const BIN_HEX: Alphabet = Alphabet::from_str_unchecked( + "!\"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdehijklmpqr", +); #[cfg(test)] mod tests { - use crate::alphabet::Alphabet; + use crate::alphabet::*; + use std::convert::TryFrom as _; - #[should_panic(expected = "Duplicate bytes")] #[test] fn detects_duplicate_start() { - let _ = Alphabet::from("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + assert_eq!( + ParseAlphabetError::DuplicatedByte(b'A'), + Alphabet::from_str("AACDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + .unwrap_err() + ); } - #[should_panic(expected = "Duplicate bytes")] #[test] fn detects_duplicate_end() { - let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//"); + assert_eq!( + ParseAlphabetError::DuplicatedByte(b'/'), + Alphabet::from_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789//") + .unwrap_err() + ); } - #[should_panic(expected = "Duplicate bytes")] #[test] fn detects_duplicate_middle() { - let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/"); + assert_eq!( + ParseAlphabetError::DuplicatedByte(b'Z'), + Alphabet::from_str("ABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/") + .unwrap_err() + ); } - #[should_panic(expected = "Base64 char set length must be 64")] #[test] fn detects_length() { - let _ = Alphabet::from( - "xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/", + assert_eq!( + ParseAlphabetError::InvalidLength, + Alphabet::from_str( + "xxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZZbcdefghijklmnopqrstuvwxyz0123456789+/", + ) + .unwrap_err() ); } - #[should_panic(expected = "Padding byte '=' is reserved")] #[test] fn detects_padding() { - let _ = Alphabet::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+="); + assert_eq!( + ParseAlphabetError::ReservedByte(b'='), + Alphabet::from_str("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=") + .unwrap_err() + ); } - #[should_panic(expected = "Bytes must be printable")] #[test] fn detects_unprintable() { // form feed - let _ = - Alphabet::from("\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); + assert_eq!( + ParseAlphabetError::UnprintableByte(0xc), + Alphabet::from_str( + "\x0cBCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + ) + .unwrap_err() + ); + } + + #[test] + fn same_as_unchecked() { + assert_eq!( + STANDARD, + Alphabet::try_from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + .unwrap() + ) } } From 3155ca72305efed8fbdfcbc955cd26555877d11c Mon Sep 17 00:00:00 2001 From: Marshall Pierce <575695+marshallpierce@users.noreply.github.com> Date: Wed, 18 Aug 2021 13:41:43 -0600 Subject: [PATCH 5/5] Various cleanups - Improve engine tests - Improve comments - Remove dead code - Improve error message byte formatting --- .gitignore | 2 +- Cargo.toml | 4 ++ RELEASE-NOTES.md | 6 +-- src/alphabet.rs | 13 +++--- src/decode.rs | 8 ++-- src/encode.rs | 2 +- src/engine/fast_portable/mod.rs | 20 ++------- src/engine/mod.rs | 17 ++++---- src/engine/tests.rs | 74 +++++++++++++++++++++++++++------ 9 files changed, 91 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index bca35b19..339636bd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,5 @@ main.rs *.iml # `perf record` files -perf.data* +/*perf.data* /tmp diff --git a/Cargo.toml b/Cargo.toml index c8ef36c1..a46b3602 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,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 diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index fd012212..5720a4a8 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -5,9 +5,9 @@ - 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 -- DecoderReader now owns its inner reader, and can expose it via `into_inner()`. For symmetry, `EncoderWriter` can do the same with its writer. +- 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 diff --git a/src/alphabet.rs b/src/alphabet.rs index 7782b85d..99d304ed 100644 --- a/src/alphabet.rs +++ b/src/alphabet.rs @@ -1,5 +1,6 @@ //! Provides [Alphabet] and constants for alphabets commonly used in the wild. +use crate::PAD_BYTE; #[cfg(any(feature = "std", test))] use std::{convert, error, fmt}; @@ -17,7 +18,7 @@ const ALPHABET_SIZE: usize = 64; /// &custom, /// base64::engine::fast_portable::PAD); /// ``` -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Alphabet { pub(crate) symbols: [u8; ALPHABET_SIZE], } @@ -39,7 +40,7 @@ impl Alphabet { Alphabet { symbols } } - /// Create a `CharacterSet` from a string of 64 unique printable ASCII bytes. + /// Create an `Alphabet` from a string of 64 unique printable ASCII bytes. /// /// The `=` byte is not allowed as it is used for padding. /// @@ -62,7 +63,7 @@ impl Alphabet { return Err(ParseAlphabetError::UnprintableByte(byte)); } // = is assumed to be padding, so cannot be used as a symbol - if b'=' == byte { + if byte == PAD_BYTE { return Err(ParseAlphabetError::ReservedByte(byte)); } @@ -121,9 +122,9 @@ impl fmt::Display for ParseAlphabetError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ParseAlphabetError::InvalidLength => write!(f, "Invalid length - must be 64 bytes"), - ParseAlphabetError::DuplicatedByte(b) => write!(f, "Duplicated byte: {}", b), - ParseAlphabetError::UnprintableByte(b) => write!(f, "Unprintable byte: {}", b), - ParseAlphabetError::ReservedByte(b) => write!(f, "Reserved byte: {}", b), + ParseAlphabetError::DuplicatedByte(b) => write!(f, "Duplicated byte: {:#04x}", b), + ParseAlphabetError::UnprintableByte(b) => write!(f, "Unprintable byte: {:#04x}", b), + ParseAlphabetError::ReservedByte(b) => write!(f, "Reserved byte: {:#04x}", b), } } } diff --git a/src/decode.rs b/src/decode.rs index d82010da..527f5e84 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -9,8 +9,6 @@ use core::fmt; #[cfg(any(feature = "std", test))] use std::error; -// TODO how to handle InvalidLastSymbol and InvalidLength behavior across engines? - /// Errors that can occur while decoding. #[derive(Clone, Debug, PartialEq, Eq)] pub enum DecodeError { @@ -58,7 +56,7 @@ impl error::Error for DecodeError { } } -///Decode base64 using the [default engine](DEFAULT_ENGINE), alphabet, and config. +///Decode base64 using the [default engine](DEFAULT_ENGINE). ///Returns a `Result` containing a `Vec`. /// ///# Example @@ -93,10 +91,10 @@ pub fn decode>(input: T) -> Result, DecodeError> { /// /// // custom engine setup /// let bytes_url = base64::decode_engine( -/// "aGVsbG8gaW50ZXJuZXR-Cg==", +/// "aGVsbG8gaW50ZXJuZXR-Cg", /// &base64::engine::fast_portable::FastPortable::from( /// &base64::alphabet::URL_SAFE, -/// base64::engine::fast_portable::PAD), +/// base64::engine::fast_portable::NO_PAD), /// /// ).unwrap(); /// println!("{:?}", bytes_url); diff --git a/src/encode.rs b/src/encode.rs index 3a8fac8e..696261cd 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -7,7 +7,7 @@ use crate::PAD_BYTE; #[cfg(any(feature = "alloc", feature = "std", test))] use alloc::{string::String, vec}; -///Encode arbitrary octets as base64 using the [default engine](DEFAULT_ENGINE), alphabet, and config. +///Encode arbitrary octets as base64 using the [default engine](DEFAULT_ENGINE). ///Returns a `String`. /// ///# Example diff --git a/src/engine/fast_portable/mod.rs b/src/engine/fast_portable/mod.rs index b70f8d01..982c8480 100644 --- a/src/engine/fast_portable/mod.rs +++ b/src/engine/fast_portable/mod.rs @@ -216,23 +216,6 @@ pub(crate) const fn decode_table(alphabet: &Alphabet) -> [u8; 256] { return decode_table; } -// fn decode_aligned(symbol: u8, decode_table: &[u8; 256]) -> u8 { -// let mut result: u8 = 0x00; -// // If `symbol` is inside the printable range, one of these two derived indices will be equal to -// // the original index, and the decoded byte will end up in `result`. If `symbol` is not -// // printable, neither will equal the original symbol, and so both decoded bytes will have 0x00 -// // as a mask. -// // TODO invalid bytes decoded to 0x00 instead of 0xFF? -// let idx: [u8; 2] = [symbol % 64, symbol % 64 + 64]; -// for i in 0..2 { -// let symbol_eq_mod = idx[i] == symbol; -// // if symbol equals its mod flavor, 0xFF, else 0x00 -// let mask = ((symbol_eq_mod) as i8 - 1) as u8; -// result = result | (decode_table[idx[i] as usize] & mask); -// } -// result -// } - #[inline] fn read_u64(s: &[u8]) -> u64 { u64::from_be_bytes(s[..8].try_into().unwrap()) @@ -315,6 +298,9 @@ impl Config for FastPortableConfig { } /// Include padding bytes when encoding. +/// +/// This is the standard per the base64 RFC, but consider using [NO_PAD] instead as padding serves +/// little purpose in practice. pub const PAD: FastPortableConfig = FastPortableConfig::new(); /// Don't add padding when encoding. diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 5a4f8dfe..764653fb 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -10,10 +10,10 @@ mod naive; #[cfg(test)] mod tests; -/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. +/// An `Engine` provides low-level encoding and decoding operations that all other higher-level parts of the API use. Users of the library will generally not need to implement this. /// /// Different implementations offer different characteristics. The library currently ships with -/// a general-purpose [FastPortable] that offers good speed and works on any CPU, with more choices +/// a general-purpose [FastPortable] impl that offers good speed and works on any CPU, with more choices /// coming later, like a constant-time one when side channel resistance is called for, and vendor-specific vectorized ones for more speed. /// /// See [DEFAULT_ENGINE] if you just want standard base64. Otherwise, when possible, it's @@ -40,10 +40,9 @@ pub trait Engine: Send + Sync { /// Must not write any bytes into the output slice other than the encoded data. fn encode(&self, input: &[u8], output: &mut [u8]) -> usize; - /// As an optimization, it is sometimes helpful to have a conservative estimate of the decoded - /// size before doing the decoding. - /// - /// The result of this must be passed to [Engine::decode()]. + /// As an optimization to prevent the decoded length from being calculated twice, it is + /// sometimes helpful to have a conservative estimate of the decoded size before doing the + /// decoding, so this calculation is done separately and passed to [Engine::decode()] as needed. fn decoded_length_estimate(&self, input_len: usize) -> Self::DecodeEstimate; /// Decode `input` base64 bytes into the `output` buffer. @@ -87,14 +86,14 @@ pub trait Config { /// The decode estimate used by an engine implementation. Users do not need to interact with this; /// it is only for engine implementors. /// -/// Implementors may want to store relevant calculations when constructing this to avoid having -/// to calculate them again during actual decoding. +/// Implementors may store relevant data here when constructing this to avoid having to calculate +/// them again during actual decoding. pub trait DecodeEstimate { /// Returns a conservative (err on the side of too big) estimate of the decoded length to use /// for pre-allocating buffers, etc. fn decoded_length_estimate(&self) -> usize; } -/// An engine that will work on all CPUs using the standard base64 alphabet and config. +/// A [FastPortable] engine using the [crate::alphabet::STANDARD] base64 alphabet and [crate::engine::fast_portable::PAD] config. pub const DEFAULT_ENGINE: FastPortable = FastPortable::from(&alphabet::STANDARD, fast_portable::PAD); diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 756a9e02..9ec70f8a 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -6,9 +6,10 @@ use rstest::rstest; use rstest_reuse::{apply, template}; use std::iter; +use crate::tests::assert_encode_sanity; use crate::{ alphabet::{Alphabet, STANDARD}, - encode, + decode_engine, encode, engine::{fast_portable, naive, Engine}, tests::random_alphabet, DecodeError, PAD_BYTE, @@ -122,7 +123,9 @@ fn encode_doesnt_write_extra_bytes(engine_wrapper: E) { let mut encode_buf = Vec::::new(); let mut encode_buf_backup = Vec::::new(); - let len_range = Uniform::new(1, 1_000); + let input_len_range = Uniform::new(0, 5); + let prefix_len_range = Uniform::new(0, 5); + let suffix_len_range = Uniform::new(0, 5); for _ in 0..10_000 { let engine = E::random(&mut rng); @@ -131,23 +134,37 @@ fn encode_doesnt_write_extra_bytes(engine_wrapper: E) { encode_buf.clear(); encode_buf_backup.clear(); - let orig_len = fill_rand(&mut orig_data, &mut rng, &len_range); - let expected_encode_len = engine_encoded_len(orig_len); - encode_buf.resize(expected_encode_len, 0); + let orig_len = fill_rand(&mut orig_data, &mut rng, &input_len_range); + + // write a random prefix + let prefix_len = fill_rand(&mut encode_buf, &mut rng, &prefix_len_range); + let expected_encode_len_no_pad = engine_encoded_len(orig_len); + // leave space for encoded data + encode_buf.resize(expected_encode_len_no_pad + prefix_len, 0); + // and a random suffix + let suffix_len = fill_rand(&mut encode_buf, &mut rng, &suffix_len_range); - // oversize encode buffer so we can easily tell if it writes anything more than - // just the encoded data - fill_rand_len(&mut encode_buf, &mut rng, (expected_encode_len + 100) * 2); encode_buf_backup.extend_from_slice(&encode_buf[..]); - let encoded_len = engine.encode(&orig_data[..], &mut encode_buf[..]); - assert_eq!(expected_encode_len, encoded_len); + let encoded_len_no_pad = engine.encode(&orig_data[..], &mut encode_buf[prefix_len..]); + assert_eq!(expected_encode_len_no_pad, encoded_len_no_pad); // no writes past what it claimed to write + assert_eq!(&encode_buf_backup[..prefix_len], &encode_buf[..prefix_len]); assert_eq!( - &encode_buf_backup[encoded_len..], - &encode_buf[encoded_len..] - ) + &encode_buf_backup[(prefix_len + encoded_len_no_pad)..], + &encode_buf[(prefix_len + encoded_len_no_pad)..] + ); + + let encoded_data = &encode_buf[prefix_len..(prefix_len + encoded_len_no_pad)]; + assert_encode_sanity( + std::str::from_utf8(encoded_data).unwrap(), + // engines don't pad + false, + orig_len, + ); + + assert_eq!(orig_data, decode_engine(encoded_data, &engine).unwrap()); } } @@ -273,6 +290,37 @@ fn decode_detect_invalid_last_symbol_two_bytes(engine_wrapper: } } +#[apply(all_engines)] +fn decode_detect_invalid_last_symbol_when_length_is_also_invalid( + engine_wrapper: E, +) { + let mut rng = rand::rngs::SmallRng::from_entropy(); + + // check across enough lengths that it would likely cover any implementation's various internal + // small/large input division + for len in 0_usize..1000 { + if len % 4 != 1 { + continue; + } + + let engine = E::random_alphabet(&mut rng, &STANDARD); + + let mut input = vec![b'A'; len]; + + // with a valid last char, it's InvalidLength + assert_eq!( + Err(DecodeError::InvalidLength), + decode_engine(&input, &engine) + ); + // after mangling the last char, it's InvalidByte + input[len - 1] = b'*'; + assert_eq!( + Err(DecodeError::InvalidByte(len - 1, b'*')), + decode_engine(&input, &engine) + ); + } +} + #[apply(all_engines)] fn decode_detect_invalid_last_symbol_every_possible_two_symbols( engine_wrapper: E,