use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hash};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
const TOKEN_BYTES: usize = 16;
pub(crate) fn generate() -> String {
let bytes = os_random_bytes().unwrap_or_else(fallback_random_bytes);
let mut token = String::with_capacity(TOKEN_BYTES * 2);
for byte in bytes {
use std::fmt::Write as _;
let _ = write!(token, "{byte:02x}");
}
token
}
pub(crate) fn matches(expected: &str, presented: &str) -> bool {
let expected = expected.as_bytes();
let presented = presented.as_bytes();
if expected.len() != presented.len() {
return false;
}
let mut diff = 0u8;
for (a, b) in expected.iter().zip(presented.iter()) {
diff |= a ^ b;
}
diff == 0
}
fn os_random_bytes() -> Option<[u8; TOKEN_BYTES]> {
#[cfg(unix)]
{
use std::io::Read as _;
let mut file = std::fs::File::open("/dev/urandom").ok()?;
let mut bytes = [0u8; TOKEN_BYTES];
file.read_exact(&mut bytes).ok()?;
Some(bytes)
}
#[cfg(not(unix))]
{
None
}
}
fn fallback_random_bytes() -> [u8; TOKEN_BYTES] {
let stack_marker = 0u8;
let stack_addr = std::ptr::addr_of!(stack_marker) as usize as u64;
let now_nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let pid = u64::from(std::process::id());
let high = hash_of(&(0xf1_u64, pid, now_nanos, stack_addr));
let elapsed = Instant::now().elapsed().as_nanos() as u64;
let low = hash_of(&(0x5c_u64, stack_addr, now_nanos ^ elapsed, high));
let mut bytes = [0u8; TOKEN_BYTES];
bytes[..8].copy_from_slice(&high.to_le_bytes());
bytes[8..].copy_from_slice(&low.to_le_bytes());
bytes
}
fn hash_of<T: Hash>(value: &T) -> u64 {
RandomState::new().hash_one(value)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn a_token_is_hex_of_the_declared_width_and_carries_no_whitespace() {
let token = generate();
assert_eq!(token.len(), TOKEN_BYTES * 2);
assert!(token.chars().all(|c| c.is_ascii_hexdigit()));
assert!(!token.chars().any(char::is_whitespace));
}
#[test]
fn tokens_do_not_repeat() {
let tokens: HashSet<String> = (0..64).map(|_| generate()).collect();
assert_eq!(tokens.len(), 64, "a repeated token means no entropy at all");
}
#[test]
fn the_fallback_path_is_itself_non_repeating() {
let a = fallback_random_bytes();
let b = fallback_random_bytes();
assert_ne!(a, b);
assert_ne!(a, [0u8; TOKEN_BYTES]);
}
#[test]
fn matches_only_the_exact_token() {
let token = generate();
assert!(matches(&token, &token.clone()));
assert!(!matches(&token, ""));
assert!(!matches(&token, &token[..token.len() - 1]));
assert!(!matches(&token, &format!("{token}x")));
let mut flipped: Vec<char> = token.chars().collect();
flipped[TOKEN_BYTES * 2 - 1] = if flipped[TOKEN_BYTES * 2 - 1] == 'a' {
'b'
} else {
'a'
};
assert!(!matches(&token, &flipped.into_iter().collect::<String>()));
}
}