Skip to main content

sofie_core/
random.rs

1use std::cell::RefCell;
2
3use ring::rand::{SecureRandom, SystemRandom};
4
5use crate::RandomnessUnavailable;
6
7const BLOCK_BYTES: usize = 256;
8
9thread_local! {
10    static BLOCK: RefCell<Block> = const {
11        RefCell::new(Block {
12            bytes: [0; BLOCK_BYTES],
13            next: BLOCK_BYTES,
14        })
15    };
16}
17
18/// Every byte is handed out once and cleared.
19struct Block {
20    bytes: [u8; BLOCK_BYTES],
21    next: usize,
22}
23
24pub fn fill(out: &mut [u8]) -> Result<(), RandomnessUnavailable> {
25    if out.len() > BLOCK_BYTES {
26        return system_fill(out);
27    }
28    BLOCK
29        .try_with(|block| block.borrow_mut().take(out))
30        .unwrap_or_else(|_| system_fill(out))
31}
32
33impl Block {
34    fn take(&mut self, out: &mut [u8]) -> Result<(), RandomnessUnavailable> {
35        if BLOCK_BYTES - self.next < out.len() {
36            system_fill(&mut self.bytes)?;
37            self.next = 0;
38        }
39        let taken = &mut self.bytes[self.next..self.next + out.len()];
40        out.copy_from_slice(taken);
41        taken.fill(0);
42        self.next += out.len();
43        Ok(())
44    }
45}
46
47fn system_fill(out: &mut [u8]) -> Result<(), RandomnessUnavailable> {
48    SystemRandom::new()
49        .fill(out)
50        .map_err(|_| RandomnessUnavailable)
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn draws_never_repeat_across_blocks_and_large_ones_go_to_the_system() {
59        let mut seen = std::collections::HashSet::new();
60        for _ in 0..100 {
61            let mut id = [0; 16];
62            fill(&mut id).unwrap();
63            assert!(seen.insert(id), "a draw repeated");
64        }
65        let mut large = [0; BLOCK_BYTES + 1];
66        fill(&mut large).unwrap();
67        assert!(large.iter().any(|&byte| byte != 0));
68    }
69}