use std::io::Read;
pub fn bytes(length: usize) -> Vec<u8> {
let mut buffer = vec![0u8; length];
if let Ok(mut source) = std::fs::File::open("/dev/urandom")
&& source.read_exact(&mut buffer).is_ok()
{
return buffer;
}
rustlavel_core::error!(
"could not read /dev/urandom: session ids and tokens are being generated from a \
clock-seeded fallback. Do not run this configuration in production."
);
fallback(&mut buffer);
buffer
}
fn fallback(buffer: &mut [u8]) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let mut state = now ^ (Box::into_raw(Box::new(0u8)) as u64) | 1;
for slot in buffer.iter_mut() {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*slot = (state >> 24) as u8;
}
}
pub fn hex(length: usize) -> String {
let mut out = String::with_capacity(length * 2);
for byte in bytes(length) {
out.push(char::from_digit((byte >> 4) as u32, 16).unwrap_or('0'));
out.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap_or('0'));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_the_requested_number_of_bytes() {
assert_eq!(bytes(32).len(), 32);
assert_eq!(bytes(0).len(), 0);
}
#[test]
fn hex_output_is_twice_as_long_and_lowercase_hexadecimal() {
let value = hex(32);
assert_eq!(value.len(), 64);
assert!(value.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
}
#[test]
fn two_draws_of_the_same_length_differ() {
assert_ne!(hex(32), hex(32));
}
#[test]
fn the_fallback_mixer_still_fills_every_byte() {
let mut buffer = vec![0u8; 64];
fallback(&mut buffer);
assert!(buffer.iter().any(|byte| *byte != 0));
}
}