pub mod output;
use crate::error::GivError;
pub use output::KeyOutput;
use rand::RngCore;
pub const DEFAULT_KEY_SIZE: usize = 36;
const KEY_ALPHABET: &str = concat!(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"abcdefghijklmnopqrstuvwxyz",
"0123456789",
);
const U8_MAX: usize = u8::MAX as usize;
const RNG_RANGE: usize = U8_MAX - (U8_MAX % KEY_ALPHABET.len());
const KEY_PREFIX: &str = "key_";
fn get_key(size: usize) -> Result<String, GivError> {
debug_assert!(
KEY_ALPHABET.len() <= U8_MAX,
"The key alphabet is too large for the RNG byte range."
);
let mut rng = rand::rng();
let mut result = String::with_capacity(size);
while result.len() < size {
let rand_u64 = rng.next_u64();
for idx in 0..u64::BITS / u8::BITS {
let byte = ((rand_u64 >> (idx * u8::BITS)) & (u8::MAX as u64)) as u8;
let byte = byte as usize;
if byte >= RNG_RANGE {
continue;
}
let idx = byte % KEY_ALPHABET.len();
let char = KEY_ALPHABET.chars().nth(idx).unwrap();
result.push(char);
if result.len() >= size {
break;
}
}
}
debug_assert!(
result.len() == size,
"Generated key length does not match requested size."
);
Ok(result)
}
pub fn generate_key(size: Option<usize>) -> Result<KeyOutput, GivError> {
let size = size.unwrap_or(DEFAULT_KEY_SIZE);
let key = get_key(size)?;
Ok(KeyOutput {
key: format!("{KEY_PREFIX}{key}"),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_key() {
let sizes = [0, 1, 10, 36, 100, 1000];
for size in sizes {
let key = get_key(size).unwrap();
assert_eq!(key.len(), size);
assert!(key.chars().all(|ch| KEY_ALPHABET.contains(ch)));
}
}
}