1 use std::str;
2 
3 use rand::{
4     distributions,
5     distributions::{Distribution as _, Uniform},
6     seq::SliceRandom,
7     Rng, SeedableRng,
8 };
9 
10 use crate::{
11     alphabet,
12     encode::encoded_len,
13     engine::{
14         general_purpose::{GeneralPurpose, GeneralPurposeConfig},
15         Config, DecodePaddingMode, Engine,
16     },
17 };
18 
19 #[test]
roundtrip_random_config_short()20 fn roundtrip_random_config_short() {
21     // exercise the slower encode/decode routines that operate on shorter buffers more vigorously
22     roundtrip_random_config(Uniform::new(0, 50), 10_000);
23 }
24 
25 #[test]
roundtrip_random_config_long()26 fn roundtrip_random_config_long() {
27     roundtrip_random_config(Uniform::new(0, 1000), 10_000);
28 }
29 
assert_encode_sanity(encoded: &str, padded: bool, input_len: usize)30 pub fn assert_encode_sanity(encoded: &str, padded: bool, input_len: usize) {
31     let input_rem = input_len % 3;
32     let expected_padding_len = if input_rem > 0 {
33         if padded {
34             3 - input_rem
35         } else {
36             0
37         }
38     } else {
39         0
40     };
41 
42     let expected_encoded_len = encoded_len(input_len, padded).unwrap();
43 
44     assert_eq!(expected_encoded_len, encoded.len());
45 
46     let padding_len = encoded.chars().filter(|&c| c == '=').count();
47 
48     assert_eq!(expected_padding_len, padding_len);
49 
50     let _ = str::from_utf8(encoded.as_bytes()).expect("Base64 should be valid utf8");
51 }
52 
roundtrip_random_config(input_len_range: Uniform<usize>, iterations: u32)53 fn roundtrip_random_config(input_len_range: Uniform<usize>, iterations: u32) {
54     let mut input_buf: Vec<u8> = Vec::new();
55     let mut encoded_buf = String::new();
56     let mut rng = rand::rngs::SmallRng::from_entropy();
57 
58     for _ in 0..iterations {
59         input_buf.clear();
60         encoded_buf.clear();
61 
62         let input_len = input_len_range.sample(&mut rng);
63 
64         let engine = random_engine(&mut rng);
65 
66         for _ in 0..input_len {
67             input_buf.push(rng.gen());
68         }
69 
70         engine.encode_string(&input_buf, &mut encoded_buf);
71 
72         assert_encode_sanity(&encoded_buf, engine.config().encode_padding(), input_len);
73 
74         assert_eq!(input_buf, engine.decode(&encoded_buf).unwrap());
75     }
76 }
77 
random_config<R: Rng>(rng: &mut R) -> GeneralPurposeConfig78 pub fn random_config<R: Rng>(rng: &mut R) -> GeneralPurposeConfig {
79     let mode = rng.gen();
80     GeneralPurposeConfig::new()
81         .with_encode_padding(match mode {
82             DecodePaddingMode::Indifferent => rng.gen(),
83             DecodePaddingMode::RequireCanonical => true,
84             DecodePaddingMode::RequireNone => false,
85         })
86         .with_decode_padding_mode(mode)
87         .with_decode_allow_trailing_bits(rng.gen())
88 }
89 
90 impl distributions::Distribution<DecodePaddingMode> for distributions::Standard {
sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DecodePaddingMode91     fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> DecodePaddingMode {
92         match rng.gen_range(0..=2) {
93             0 => DecodePaddingMode::Indifferent,
94             1 => DecodePaddingMode::RequireCanonical,
95             _ => DecodePaddingMode::RequireNone,
96         }
97     }
98 }
99 
random_alphabet<R: Rng>(rng: &mut R) -> &'static alphabet::Alphabet100 pub fn random_alphabet<R: Rng>(rng: &mut R) -> &'static alphabet::Alphabet {
101     ALPHABETS.choose(rng).unwrap()
102 }
103 
random_engine<R: Rng>(rng: &mut R) -> GeneralPurpose104 pub fn random_engine<R: Rng>(rng: &mut R) -> GeneralPurpose {
105     let alphabet = random_alphabet(rng);
106     let config = random_config(rng);
107     GeneralPurpose::new(alphabet, config)
108 }
109 
110 const ALPHABETS: &[alphabet::Alphabet] = &[
111     alphabet::URL_SAFE,
112     alphabet::STANDARD,
113     alphabet::CRYPT,
114     alphabet::BCRYPT,
115     alphabet::IMAP_MUTF7,
116     alphabet::BIN_HEX,
117 ];
118