1use std::{cell::RefCell, rc::Rc};
4
5use sha2::{Digest, Sha256};
6
7#[derive(Clone, Debug)]
14pub struct TestEntropy {
15 seed: [u8; 32],
16 state: Rc<RefCell<EntropyState>>,
17}
18
19#[derive(Debug)]
20struct EntropyState {
21 next_block: u64,
22 block: [u8; 32],
23 offset: usize,
24}
25
26impl TestEntropy {
27 pub fn seeded(seed: [u8; 32]) -> Self {
29 Self {
30 seed,
31 state: Rc::new(RefCell::new(EntropyState {
32 next_block: 0,
33 block: [0; 32],
34 offset: 32,
35 })),
36 }
37 }
38
39 pub fn fill(&self, output: &mut [u8]) {
41 let mut copied = 0;
42 let mut state = self.state.borrow_mut();
43 while copied < output.len() {
44 if state.offset == state.block.len() {
45 let mut hasher = Sha256::new();
46 hasher.update(b"lenso-test-entropy-v1\0");
47 hasher.update(self.seed);
48 hasher.update(state.next_block.to_be_bytes());
49 state.block.copy_from_slice(&hasher.finalize());
50 state.next_block = state
51 .next_block
52 .checked_add(1)
53 .expect("a test entropy stream cannot exceed u64 blocks");
54 state.offset = 0;
55 }
56 let remaining = output.len() - copied;
57 let available = state.block.len() - state.offset;
58 let count = remaining.min(available);
59 output[copied..copied + count]
60 .copy_from_slice(&state.block[state.offset..state.offset + count]);
61 copied += count;
62 state.offset += count;
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn equal_seeds_produce_equal_bytes_across_multiple_blocks() {
73 let left = TestEntropy::seeded([7; 32]);
74 let right = TestEntropy::seeded([7; 32]);
75 let mut left_bytes = [0; 80];
76 let mut right_bytes = [0; 80];
77
78 left.fill(&mut left_bytes);
79 right.fill(&mut right_bytes);
80
81 assert_eq!(left_bytes, right_bytes);
82 }
83
84 #[test]
85 fn clones_share_one_explicit_stream() {
86 let entropy = TestEntropy::seeded([3; 32]);
87 let clone = entropy.clone();
88 let baseline = TestEntropy::seeded([3; 32]);
89 let mut first = [0; 16];
90 let mut second = [0; 16];
91 let mut expected = [0; 32];
92
93 entropy.fill(&mut first);
94 clone.fill(&mut second);
95 baseline.fill(&mut expected);
96
97 assert_eq!([first.as_slice(), second.as_slice()].concat(), expected);
98 }
99}