use std::str; use rand::{ distributions, distributions::{Distribution as _, Uniform}, seq::SliceRandom, Rng, SeedableRng, }; use crate::{ alphabet, encode::encoded_len, engine::{ general_purpose::{GeneralPurpose, GeneralPurposeConfig}, Config, DecodePaddingMode, Engine, }, }; #[test] fn roundtrip_random_config_short() { // exercise the slower encode/decode routines that operate on shorter buffers more vigorously roundtrip_random_config(Uniform::new(0, 50), 10_000); } #[test] fn roundtrip_random_config_long() { roundtrip_random_config(Uniform::new(0, 1000), 10_000); } 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 padded { 3 - input_rem } else { 0 } } else { 0 }; let expected_encoded_len = encoded_len(input_len, padded).unwrap(); assert_eq!(expected_encoded_len, encoded.len()); let padding_len = encoded.chars().filter(|&c| c == '=').count(); assert_eq!(expected_padding_len, padding_len); let _ = str::from_utf8(encoded.as_bytes()).expect("Base64 should be valid utf8"); } fn roundtrip_random_config(input_len_range: Uniform, iterations: u32) { let mut input_buf: Vec = Vec::new(); let mut encoded_buf = String::new(); let mut rng = rand::rngs::SmallRng::from_entropy(); for _ in 0..iterations { input_buf.clear(); encoded_buf.clear(); let input_len = input_len_range.sample(&mut rng); let engine = random_engine(&mut rng); for _ in 0..input_len { input_buf.push(rng.gen()); } engine.encode_string(&input_buf, &mut encoded_buf); assert_encode_sanity(&encoded_buf, engine.config().encode_padding(), input_len); assert_eq!(input_buf, engine.decode(&encoded_buf).unwrap()); } } pub fn random_config(rng: &mut R) -> GeneralPurposeConfig { let mode = rng.gen(); GeneralPurposeConfig::new() .with_encode_padding(match mode { DecodePaddingMode::Indifferent => rng.gen(), DecodePaddingMode::RequireCanonical => true, DecodePaddingMode::RequireNone => false, }) .with_decode_padding_mode(mode) .with_decode_allow_trailing_bits(rng.gen()) } impl distributions::Distribution for distributions::Standard { fn sample(&self, rng: &mut R) -> DecodePaddingMode { match rng.gen_range(0..=2) { 0 => DecodePaddingMode::Indifferent, 1 => DecodePaddingMode::RequireCanonical, _ => DecodePaddingMode::RequireNone, } } } pub fn random_alphabet(rng: &mut R) -> &'static alphabet::Alphabet { ALPHABETS.choose(rng).unwrap() } pub fn random_engine(rng: &mut R) -> GeneralPurpose { let alphabet = random_alphabet(rng); let config = random_config(rng); GeneralPurpose::new(alphabet, config) } const ALPHABETS: &[alphabet::Alphabet] = &[ alphabet::URL_SAFE, alphabet::STANDARD, alphabet::CRYPT, alphabet::BCRYPT, alphabet::IMAP_MUTF7, alphabet::BIN_HEX, ];