rs_password_utils/dice/
mod.rs

1// Copyright 2020 astonbitecode
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14use rand::RngCore;
15use rand::rngs::OsRng;
16
17mod diceware8k;
18
19/// Generates a passphrase, defining the amount of words that comprise it.
20///
21/// The words are separated with spaces. (Separation with spaces is proposed [here](http://world.std.com/~reinhold/dicewarefaq.html#spaces))
22pub fn generate(size: usize) -> String {
23    generate_passphrase(size, None)
24}
25
26/// Generates a passphrase by defining a separator to be used between the words.
27///
28/// size is the amount of words that comprise the passphrase.
29pub fn generate_with_separator(size: usize, separator: &str) -> String {
30    generate_passphrase(size, separator)
31}
32
33fn generate_passphrase<'a, SEP>(size: usize, separator: SEP) -> String
34    where SEP: Into<Option<&'a str>> {
35    let separator = separator.into().unwrap_or(" ");
36    if size <= 0 {
37        "".to_string()
38    } else {
39        (0..size)
40            .map(|_| {
41                let mut buf = [0u8; 16];
42                OsRng.fill_bytes(&mut buf);
43                let random_u64 = OsRng.next_u64() as usize;
44                diceware8k::get_dice_word(random_u64)
45            })
46            .fold("".to_string(), |mut acc, passphrase| {
47                if !acc.is_empty() {
48                    acc.push_str(separator);
49                }
50                acc.push_str(passphrase);
51                acc
52            })
53    }
54}
55
56#[cfg(test)]
57mod dice_unit_tests {
58    use super::*;
59
60    #[test]
61    fn test_generate_passphrase() {
62        assert!(generate_passphrase(0, None) == "");
63        let mut all: Vec<String> = Vec::new();
64
65        for _ in 0..1000 {
66            let passphrase = generate_passphrase(10, None);
67            if all.contains(&passphrase) {
68                assert!(false, "Same passphrase has already been generated: {}", passphrase);
69            }
70            all.push(passphrase);
71        }
72
73        let passphrase = generate_passphrase(3, "~");
74        let s: Vec<&str> = passphrase.split('~').collect();
75        assert_eq!(s.len(), 3);
76    }
77
78    #[test]
79    fn test_generate_with_separator() {
80        let passphrase = generate_with_separator(3, "~");
81        let s: Vec<&str> = passphrase.split('~').collect();
82        assert_eq!(s.len(), 3);
83
84        let passphrase = generate_with_separator(3, "");
85        let s: Vec<&str> = passphrase.split(" ").collect();
86        assert_eq!(s.len(), 1);
87    }
88
89    #[test]
90    fn test_generate() {
91        let passphrase = generate_with_separator(3, " ");
92        let s: Vec<&str> = passphrase.split(' ').collect();
93        assert_eq!(s.len(), 3);
94    }
95
96}