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
18struct 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}