Skip to main content

rustlavel_db/
random.rs

1//! Random bytes, for SCRAM nonces.
2//!
3//! Reads the operating system's entropy source directly rather than adding a
4//! dependency. A nonce only needs to be unpredictable and unique per exchange.
5
6use std::io::Read;
7
8/// Fill a buffer with random bytes from the OS.
9pub fn bytes(length: usize) -> Vec<u8> {
10    let mut buffer = vec![0u8; length];
11
12    if let Ok(mut source) = std::fs::File::open("/dev/urandom")
13        && source.read_exact(&mut buffer).is_ok() {
14            return buffer;
15        }
16
17    // Fallback for a system without /dev/urandom: seed from the clock and the
18    // address of a fresh allocation, then run a counter-based mixer. Weaker,
19    // but a nonce that is merely unique still keeps the exchange correct.
20    let now = std::time::SystemTime::now()
21        .duration_since(std::time::UNIX_EPOCH)
22        .map(|d| d.as_nanos() as u64)
23        .unwrap_or(0);
24    let mut state = now ^ (Box::into_raw(Box::new(0u8)) as u64);
25
26    for slot in buffer.iter_mut() {
27        state ^= state << 13;
28        state ^= state >> 7;
29        state ^= state << 17;
30        *slot = (state >> 24) as u8;
31    }
32    buffer
33}
34
35/// A printable nonce, using the characters SCRAM allows (no comma).
36pub fn nonce(length: usize) -> String {
37    const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
38
39    bytes(length)
40        .into_iter()
41        .map(|byte| ALPHABET[byte as usize % ALPHABET.len()] as char)
42        .collect()
43}
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn nonces_are_the_requested_length_and_printable() {
51        let value = nonce(24);
52
53        assert_eq!(value.len(), 24);
54        assert!(value.chars().all(|c| c.is_ascii_alphanumeric()));
55    }
56
57    #[test]
58    fn two_nonces_differ() {
59        assert_ne!(nonce(24), nonce(24));
60    }
61}