1use std::io::Read;
10
11pub fn bytes(length: usize) -> Vec<u8> {
18 let mut buffer = vec![0u8; length];
19
20 if let Ok(mut source) = std::fs::File::open("/dev/urandom")
21 && source.read_exact(&mut buffer).is_ok()
22 {
23 return buffer;
24 }
25
26 rustlavel_core::error!(
27 "could not read /dev/urandom: session ids and tokens are being generated from a \
28 clock-seeded fallback. Do not run this configuration in production."
29 );
30 fallback(&mut buffer);
31 buffer
32}
33
34fn fallback(buffer: &mut [u8]) {
36 let now = std::time::SystemTime::now()
37 .duration_since(std::time::UNIX_EPOCH)
38 .map(|d| d.as_nanos() as u64)
39 .unwrap_or(0);
40 let mut state = now ^ (Box::into_raw(Box::new(0u8)) as u64) | 1;
41
42 for slot in buffer.iter_mut() {
43 state ^= state << 13;
44 state ^= state >> 7;
45 state ^= state << 17;
46 *slot = (state >> 24) as u8;
47 }
48}
49
50pub fn hex(length: usize) -> String {
56 let mut out = String::with_capacity(length * 2);
57 for byte in bytes(length) {
58 out.push(char::from_digit((byte >> 4) as u32, 16).unwrap_or('0'));
59 out.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap_or('0'));
60 }
61 out
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn returns_the_requested_number_of_bytes() {
70 assert_eq!(bytes(32).len(), 32);
71 assert_eq!(bytes(0).len(), 0);
72 }
73
74 #[test]
75 fn hex_output_is_twice_as_long_and_lowercase_hexadecimal() {
76 let value = hex(32);
77
78 assert_eq!(value.len(), 64);
79 assert!(value.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()));
80 }
81
82 #[test]
83 fn two_draws_of_the_same_length_differ() {
84 assert_ne!(hex(32), hex(32));
85 }
86
87 #[test]
88 fn the_fallback_mixer_still_fills_every_byte() {
89 let mut buffer = vec![0u8; 64];
92 fallback(&mut buffer);
93 assert!(buffer.iter().any(|byte| *byte != 0));
94 }
95}