1use ahash::AHashMap;
2use indicatif::ProgressBar;
3
4pub fn password_generator_count(charset_len: usize, min_size: usize, max_size: usize) -> usize {
6    let mut total_password_count = 0;
7    for i in min_size..=max_size {
8        total_password_count += charset_len.pow(i as u32);
9    }
10    total_password_count
11}
12
13struct PasswordGenerator {
14    charset: Vec<char>,
15    charset_indices: AHashMap<char, usize>,
16    charset_len: usize,
17    charset_first: char,
18    charset_last: char,
19    max_size: usize,
20    current_len: usize,
21    current_index: usize,
22    generated_count: usize,
23    total_to_generate: usize,
24    password: Vec<char>,
25    progress_bar: ProgressBar,
26}
27
28impl PasswordGenerator {
29    fn new(
30        charset: Vec<char>,
31        min_size: usize,
32        max_size: usize,
33        progress_bar: ProgressBar,
34    ) -> PasswordGenerator {
35        let charset_len = charset.len();
36        let charset_first = *charset.first().expect("charset non empty");
37        let charset_last = *charset.last().expect("charset non empty");
38        let possibilities = charset_len.pow(min_size as u32);
40
41        let charset_indices = charset
43            .iter()
44            .enumerate()
45            .map(|(i, c)| (*c, i))
46            .collect::<AHashMap<char, usize>>();
47
48        progress_bar.println(format!(
49            "Starting search space for password length {min_size} ({possibilities} possibilities) "
50        ));
51        let password = vec![charset_first; min_size];
52        let current_len = password.len();
53        let current_index = current_len - 1;
54
55        let generated_count = 0;
56        let total_to_generate = password_generator_count(charset_len, min_size, max_size);
57
58        PasswordGenerator {
59            charset,
60            charset_indices,
61            charset_len,
62            charset_first,
63            charset_last,
64            max_size,
65            current_len,
66            current_index,
67            generated_count,
68            total_to_generate,
69            password,
70            progress_bar,
71        }
72    }
73}
74
75impl Iterator for PasswordGenerator {
76    type Item = String;
77
78    fn next(&mut self) -> Option<Self::Item> {
79        if self.password.len() > self.max_size {
80            return None;
81        }
82
83        if self.generated_count == 0 {
85            self.generated_count += 1;
86            return Some(self.password.iter().collect());
87        }
88
89        if self.generated_count == self.total_to_generate {
91            return None;
92        }
93
94        if self.current_len == self.current_index + 1
96            && !self.password.iter().any(|&c| c != self.charset_last)
97        {
98            self.current_index += 1;
100            self.current_len += 1;
101            self.password = vec![self.charset_first; self.current_len];
102            let possibilities = self.charset_len.pow(self.current_len as u32);
103            self.progress_bar.println(
104                format!(
105                    "Starting search space for password length {} ({} possibilities) ({} passwords generated so far)",
106                    self.current_len, possibilities, self.generated_count
107                ));
108        } else {
109            let current_char = *self.password.get(self.current_index).unwrap();
110            if current_char == self.charset_last {
111                let at_prev = self
113                    .password
114                    .iter()
115                    .rposition(|&c| c != self.charset_last)
116                    .unwrap_or_else(|| {
117                        panic!(
118                            "Must find something else than {} in {:?}",
119                            self.charset_last, self.password
120                        )
121                    });
122                let next_prev = if at_prev == self.charset_len - 1 {
123                    self.charset.get(self.charset_len - 1).unwrap()
124                } else {
125                    let prev_char = *self.password.get(at_prev).unwrap();
126                    let prev_index_charset =
127                        self.charset.iter().position(|&c| c == prev_char).unwrap();
128                    self.charset.get(prev_index_charset + 1).unwrap()
129                };
130
131                self.password[self.current_index] = self.charset_first;
132                self.password[at_prev] = *next_prev;
133
134                for (i, x) in self.password.iter_mut().enumerate() {
136                    if *x == self.charset_last && i > at_prev {
137                        *x = self.charset_first;
138                    }
139                }
140            } else {
141                let at = *self.charset_indices.get(¤t_char).unwrap();
143                let next = *self.charset.get(at + 1).unwrap();
144                self.password[self.current_index] = next;
145            }
146        }
147        self.generated_count += 1;
148        Some(self.password.iter().collect::<String>())
150    }
151
152    fn size_hint(&self) -> (usize, Option<usize>) {
153        let remaining = self.total_to_generate - self.generated_count;
154        (remaining, Some(remaining))
155    }
156}
157
158pub fn password_generator_iter(
159    charset: &[char],
160    min_size: usize,
161    max_size: usize,
162    progress_bar: ProgressBar,
163) -> impl Iterator<Item = String> {
164    progress_bar.println(format!(
166        "Generating passwords with length from {} to {} for charset with length {}\n{}",
167        min_size,
168        max_size,
169        charset.len(),
170        charset.iter().collect::<String>()
171    ));
172    PasswordGenerator::new(charset.to_vec(), min_size, max_size, progress_bar)
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::charsets::charset_lowercase_letters;
179    use std::fs;
180
181    #[test]
182    fn generate_password_max_size_two() {
183        let mut iter = password_generator_iter(&['a', 'b', 'c'], 1, 2, ProgressBar::hidden());
184        assert_eq!(iter.next(), Some("a".into()));
185        assert_eq!(iter.next(), Some("b".into()));
186        assert_eq!(iter.next(), Some("c".into()));
187        assert_eq!(iter.next(), Some("aa".into()));
188        assert_eq!(iter.next(), Some("ab".into()));
189        assert_eq!(iter.next(), Some("ac".into()));
190        assert_eq!(iter.next(), Some("ba".into()));
191        assert_eq!(iter.next(), Some("bb".into()));
192        assert_eq!(iter.next(), Some("bc".into()));
193        assert_eq!(iter.next(), Some("ca".into()));
194        assert_eq!(iter.next(), Some("cb".into()));
195        assert_eq!(iter.next(), Some("cc".into()));
196        assert_eq!(iter.next(), None);
197    }
198
199    #[test]
200    fn generate_password_min_max_size_two() {
201        let mut iter = password_generator_iter(&['a', 'b', 'c'], 2, 2, ProgressBar::hidden());
202        assert_eq!(iter.next(), Some("aa".into()));
203        assert_eq!(iter.next(), Some("ab".into()));
204        assert_eq!(iter.next(), Some("ac".into()));
205        assert_eq!(iter.next(), Some("ba".into()));
206        assert_eq!(iter.next(), Some("bb".into()));
207        assert_eq!(iter.next(), Some("bc".into()));
208        assert_eq!(iter.next(), Some("ca".into()));
209        assert_eq!(iter.next(), Some("cb".into()));
210        assert_eq!(iter.next(), Some("cc".into()));
211        assert_eq!(iter.next(), None);
212    }
213
214    #[test]
215    fn generate_password_large() {
216        let mut iter =
217            password_generator_iter(&charset_lowercase_letters(), 1, 3, ProgressBar::hidden());
218        let gold_path = "test-files/generated-passwords-lowercase.txt";
219        let gold = fs::read_to_string(gold_path).expect("gold file not found!");
220        for (i1, l1) in gold.lines().enumerate() {
221            let l2 = iter.next().unwrap();
222            if l1.trim_end() != l2.trim_end() {
223                eprintln!("## GOLD line {} ##", i1 + 1);
224                eprintln!("{}", l1.trim_end());
225                eprintln!("## ACTUAL ##");
226                eprintln!("{}", l2.trim_end());
227                eprintln!("#####");
228                assert_eq!(l1, l2);
229            }
230        }
231    }
232}