use rand::seq::IteratorRandom as _;
use zeroize::Zeroize as _;
use crate::locked;
const SYMBOLS: &[u8] = b"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
const NUMBERS: &[u8] = b"0123456789";
const LETTERS: &[u8] =
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const NONCONFUSABLES: &[u8] = b"34678abcdefhjkmnpqrtuwxy";
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum Type {
AllChars,
NoSymbols,
Numbers,
NonConfusables,
Diceware,
}
pub fn pwgen(ty: Type, len: usize) -> locked::Password {
let mut rng = rand::rng();
let alphabet = match ty {
Type::AllChars => {
let mut v = vec![];
v.extend(SYMBOLS.iter().copied());
v.extend(NUMBERS.iter().copied());
v.extend(LETTERS.iter().copied());
v
}
Type::NoSymbols => {
let mut v = vec![];
v.extend(NUMBERS.iter().copied());
v.extend(LETTERS.iter().copied());
v
}
Type::Numbers => {
let mut v = vec![];
v.extend(NUMBERS.iter().copied());
v
}
Type::NonConfusables => {
let mut v = vec![];
v.extend(NONCONFUSABLES.iter().copied());
v
}
Type::Diceware => {
return diceware(&mut rng, len);
}
};
let mut buf = locked::Vec::new();
buf.extend(
std::iter::repeat_with(|| *alphabet.iter().choose(&mut rng).unwrap())
.take(len),
);
locked::Password::new(buf)
}
fn diceware(rng: &mut impl rand::RngCore, len: usize) -> locked::Password {
let mut words = vec![];
for _ in 0..len {
words.push(*crate::wordlist::EFF_LONG.iter().choose(rng).unwrap());
}
let mut joined = words.join(" ");
let mut buf = locked::Vec::new();
buf.extend(joined.as_bytes().iter().copied());
joined.zeroize();
locked::Password::new(buf)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_pwgen() {
let pw = pwgen(Type::AllChars, 50);
assert_eq!(pw.password().len(), 50);
assert_duplicates(pw.password());
let pw = pwgen(Type::AllChars, 100);
assert_eq!(pw.password().len(), 100);
assert_duplicates(pw.password());
let pw = pwgen(Type::NoSymbols, 100);
assert_eq!(pw.password().len(), 100);
assert_duplicates(pw.password());
let pw = pwgen(Type::Numbers, 100);
assert_eq!(pw.password().len(), 100);
assert_duplicates(pw.password());
let pw = pwgen(Type::NonConfusables, 100);
assert_eq!(pw.password().len(), 100);
assert_duplicates(pw.password());
}
#[track_caller]
fn assert_duplicates(bytes: &[u8]) {
let s = std::str::from_utf8(bytes).unwrap();
let mut set = std::collections::HashSet::new();
for c in s.chars() {
set.insert(c);
}
assert!(set.len() < s.len());
}
}