1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use crate::dictionary::Dictionary;
use crate::{CharSet, PasswordOptions};
use rand::prelude::ThreadRng;
use rand::seq::SliceRandom;
use rand::thread_rng;

/// Describes a password generator. Wrapper around a ThreadRng so it's
/// cached to improve performance when generating random characters.
/// Dictionary also loaded when instantiated so it doesn't have to be called
/// multiple times.
pub struct PasswordGenerator {
    rng: ThreadRng,
    dictionary: Dictionary,
}

impl PasswordGenerator {
    /// Initialises a new PasswordGenerator, instantiating the
    /// ThreadRng and dictionary
    ///
    /// ## Example
    /// ```
    /// use password_gen::PasswordGenerator;
    /// let generator = PasswordGenerator::new();
    /// ```
    pub fn new() -> PasswordGenerator {
        PasswordGenerator {
            rng: thread_rng(),
            dictionary: Dictionary::new(),
        }
    }
}

impl PasswordGenerator {
    /// Generates a random password that match the given requirements.
    ///
    /// ## Example
    /// ```
    /// use password_gen::{PasswordOptions, PasswordGenerator};
    /// use password_gen::password_options::CharSet;
    /// let mut generator = PasswordGenerator::new();
    /// let options = PasswordOptions::new(15, CharSet::Ascii);
    /// let password = generator.generate_password(&options);
    /// ```
    pub fn generate_password(&mut self, options: &PasswordOptions) -> String {
        let mut password = String::new();
        match &options.character_set {
            CharSet::Xkcd => {
                let separator = *select_random_value(self.dictionary.separators(), &mut self.rng);
                for i in 0..options.min_length as usize {
                    let word = *select_random_value(&self.dictionary.words(), &mut self.rng);
                    if rand::random() {
                        password.push_str(&word.to_uppercase());
                    } else {
                        password.push_str(&word);
                    }
                    if i < (options.min_length as usize) - 1 {
                        password.push_str(&separator);
                    }
                }
            }
            _ => {
                for _i in 0..options.min_length {
                    password.push_str(*select_random_value(
                        self.dictionary.list_from_charset(&options.character_set),
                        &mut self.rng,
                    ));
                }
            }
        }
        return password;
    }
}

/// Selects a random value from &Vec<T> and returns &T.
///
/// ## Example
/// ```
/// use rand::{thread_rng, Rng};
/// use password_gen::password_generator::select_random_value;
///
/// let t: Vec<u32> = vec![1,2,3,4,5];
/// let mut rng = thread_rng();
/// let a:&u32 = select_random_value(&t, &mut rng);
/// ```
pub fn select_random_value<'a, T>(c: &'a Vec<T>, rng: &mut ThreadRng) -> &'a T {
    let val = c.choose(rng).unwrap();
    return val;
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::CharSet;

    #[test]
    fn correct_length() {
        let mut generator = PasswordGenerator::new();
        let options = PasswordOptions::new(12, CharSet::Ascii);
        let options2 = PasswordOptions::new(13, CharSet::AsciiExtended);
        let options3 = PasswordOptions::new(14, CharSet::Numbers);
        let options4 = PasswordOptions::new(15, CharSet::Alphanumeric);
        let password = generator.generate_password(&options);
        let password2 = generator.generate_password(&options2);
        let password3 = generator.generate_password(&options3);
        let password4 = generator.generate_password(&options4);
        assert_eq!(password.chars().count(), 12);
        assert_eq!(password2.chars().count(), 13);
        assert_eq!(password3.chars().count(), 14);
        assert_eq!(password4.chars().count(), 15);
    }

    #[test]
    fn it_generates_an_xkcd_password() {
        let mut generator = PasswordGenerator::new();
        let options = PasswordOptions::new(3, CharSet::Xkcd);
        let password = generator.generate_password(&options);
        assert!(password.chars().count() > 3 as usize);
    }

    #[test]
    fn it_generates_pin() {
        let options = PasswordOptions::new(4, CharSet::Numbers);
        let mut generator = PasswordGenerator::new();
        let password = generator.generate_password(&options);
        for c in password.chars() {
            assert!(c.is_numeric())
        }
    }

    #[test]
    fn it_generates_alphanumeric() {
        let options = PasswordOptions::new(10, CharSet::Alphanumeric);
        let mut generator = PasswordGenerator::new();
        let password = generator.generate_password(&options);
        for c in password.chars() {
            assert!(c.is_alphanumeric())
        }
    }
}