t_rust_less_lib/service/pw_generator/
chars.rs

1use crate::api::PasswordGeneratorCharsParam;
2use rand::seq::SliceRandom;
3use rand::{thread_rng, Rng};
4
5const LOWERS: &[u8] = b"abcdefghijklmnopqrstuvwxyz";
6const UPPERS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
7const NUMBERS: &[u8] = b"0123456789";
8const SYMBOLS: &[u8] = b"!-+*#_$%&/()=?{}[]()/\\'\"`-,;:.<>";
9const AMBIGOUS_CHARS: &[u8] = b"{}[]()/\\'\"`-,;:.<>";
10const SIMILAR_CHARS: &[u8] = b"QO01lIB8S5G62ZUV";
11
12pub fn generate_chars(params: &PasswordGeneratorCharsParam) -> String {
13  let mut rng = thread_rng();
14  let mut pool = Vec::with_capacity(params.num_chars as usize);
15
16  if params.require_upper {
17    pool.push(pick_char_from(&mut rng, UPPERS, params));
18  }
19  if params.require_number {
20    pool.push(pick_char_from(&mut rng, NUMBERS, params));
21  }
22  if params.require_symbol {
23    pool.push(pick_char_from(&mut rng, SYMBOLS, params));
24  }
25  let candidates = create_base_set(params);
26  while pool.len() < params.num_chars as usize {
27    pool.push(*candidates.choose(&mut rng).unwrap());
28  }
29
30  pool.shuffle(&mut rng);
31
32  String::from_utf8(pool).unwrap()
33}
34
35fn create_base_set(params: &PasswordGeneratorCharsParam) -> Vec<u8> {
36  let mut candidates = Vec::with_capacity(LOWERS.len() + UPPERS.len() + NUMBERS.len() + SYMBOLS.len());
37
38  filter_set(&mut candidates, LOWERS, params);
39  if params.include_uppers {
40    filter_set(&mut candidates, UPPERS, params);
41  }
42  if params.include_numbers {
43    filter_set(&mut candidates, NUMBERS, params);
44  }
45  if params.include_symbols {
46    filter_set(&mut candidates, SYMBOLS, params);
47  }
48
49  candidates
50}
51
52fn filter_set(candidates: &mut Vec<u8>, set: &[u8], params: &PasswordGeneratorCharsParam) {
53  for ch in set {
54    if params.exclude_similar && SIMILAR_CHARS.contains(ch) {
55      continue;
56    }
57    if params.exclude_ambiguous && AMBIGOUS_CHARS.contains(ch) {
58      continue;
59    }
60    candidates.push(*ch);
61  }
62}
63
64fn pick_char_from<R: Rng>(rng: &mut R, set: &[u8], params: &PasswordGeneratorCharsParam) -> u8 {
65  let mut candidates = Vec::with_capacity(set.len());
66  filter_set(&mut candidates, set, params);
67
68  *candidates.choose(rng).unwrap()
69}
70
71#[cfg(test)]
72mod tests {
73  use super::*;
74  use spectral::prelude::*;
75
76  #[test]
77  fn test_generate_chars() {
78    let pw1 = generate_chars(&PasswordGeneratorCharsParam {
79      num_chars: 14,
80      include_uppers: false,
81      include_numbers: false,
82      include_symbols: false,
83      require_number: false,
84      require_upper: false,
85      require_symbol: false,
86      exclude_similar: false,
87      exclude_ambiguous: false,
88    });
89
90    assert_that(&pw1.len()).is_equal_to(14);
91    assert_that(&pw1.chars().all(|ch| ch.is_lowercase())).is_true();
92
93    let pw2: String = generate_chars(&PasswordGeneratorCharsParam {
94      num_chars: 20,
95      include_uppers: true,
96      include_numbers: false,
97      include_symbols: false,
98      require_number: false,
99      require_upper: true,
100      require_symbol: false,
101      exclude_similar: false,
102      exclude_ambiguous: false,
103    });
104
105    assert_that(&pw2.len()).is_equal_to(20);
106    assert_that(&pw2.chars().any(|ch| ch.is_uppercase())).is_true();
107  }
108}