kk_crypto/
entropy_pool.rs1use std::collections::VecDeque;
24use std::sync::{Arc, Condvar, Mutex};
25use std::thread;
26
27use crate::entropy::{self, EntropySnapshot};
28use crate::error::Result;
29
30pub struct EntropyPool {
39 inner: Arc<PoolInner>,
40}
41
42struct PoolInner {
43 state: Mutex<PoolState>,
44 need_refill: Condvar,
46 snapshot_added: Condvar,
48 capacity: usize,
49 watermark: usize,
50}
51
52struct PoolState {
53 buf: VecDeque<EntropySnapshot>,
54 shutdown: bool,
55}
56
57impl EntropyPool {
58 pub fn new(capacity: usize) -> Result<Self> {
66 assert!(capacity > 0, "EntropyPool capacity must be > 0");
67 let capacity = capacity.clamp(8, 1024);
68 let watermark = capacity / 2;
69 let prewarm = capacity.min(8);
70
71 let inner = Arc::new(PoolInner {
72 state: Mutex::new(PoolState {
73 buf: VecDeque::with_capacity(capacity),
74 shutdown: false,
75 }),
76 need_refill: Condvar::new(),
77 snapshot_added: Condvar::new(),
78 capacity,
79 watermark,
80 });
81
82 let worker = Arc::clone(&inner);
84 thread::spawn(move || refill_loop(worker));
85
86 inner.need_refill.notify_one();
88
89 {
91 let mut state = inner.state.lock().unwrap();
92 while state.buf.len() < prewarm {
93 state = inner.snapshot_added.wait(state).unwrap();
94 }
95 }
96
97 Ok(Self { inner })
98 }
99
100 pub fn draw(&self) -> Result<EntropySnapshot> {
105 let mut state = self.inner.state.lock().unwrap();
106 if let Some(snap) = state.buf.pop_front() {
107 let below_watermark = state.buf.len() < self.inner.watermark;
108 drop(state);
109 if below_watermark {
110 self.inner.need_refill.notify_one();
111 }
112 Ok(snap)
113 } else {
114 drop(state);
115 entropy::gather()
117 }
118 }
119
120 pub fn len(&self) -> usize {
122 self.inner.state.lock().unwrap().buf.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
127 self.len() == 0
128 }
129}
130
131impl Drop for EntropyPool {
132 fn drop(&mut self) {
133 let mut state = self.inner.state.lock().unwrap();
134 state.shutdown = true;
135 drop(state);
136 self.inner.need_refill.notify_one();
137 }
138}
139
140fn refill_loop(inner: Arc<PoolInner>) {
143 loop {
144 let mut state = inner.state.lock().unwrap();
145
146 while !state.shutdown && state.buf.len() >= inner.capacity {
148 state = inner.need_refill.wait(state).unwrap();
149 }
150
151 if state.shutdown {
152 return;
153 }
154
155 let slots = inner.capacity - state.buf.len();
156 drop(state);
157
158 for _ in 0..slots {
160 {
162 let st = inner.state.lock().unwrap();
163 if st.shutdown {
164 return;
165 }
166 }
167
168 if let Ok(snap) = entropy::gather() {
169 let mut state = inner.state.lock().unwrap();
170 if state.buf.len() < inner.capacity {
171 state.buf.push_back(snap);
172 drop(state);
173 inner.snapshot_added.notify_one();
174 }
175 }
176 }
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn pool_creates_and_prewarms() {
186 let pool = EntropyPool::new(16).unwrap();
187 assert!(pool.len() >= 8, "pool should pre-warm at least 8 snapshots");
188 }
189
190 #[test]
191 fn draw_returns_valid_snapshot() {
192 let pool = EntropyPool::new(16).unwrap();
193 let snap = pool.draw().unwrap();
194 assert_ne!(snap.bytes, [0u8; 32], "snapshot bytes must be non-zero");
195 assert_ne!(snap.timestamp_nanos, 0, "timestamp must be non-zero");
196 }
197
198 #[test]
199 fn successive_draws_differ() {
200 let pool = EntropyPool::new(16).unwrap();
201 let s1 = pool.draw().unwrap();
202 let s2 = pool.draw().unwrap();
203 assert_ne!(
204 s1.bytes, s2.bytes,
205 "two draws must produce different snapshots"
206 );
207 }
208
209 #[test]
210 fn draw_under_exhaustion_still_works() {
211 let pool = EntropyPool::new(8).unwrap();
213 for _ in 0..8 {
215 let _ = pool.draw().unwrap();
216 }
217 let snap = pool.draw().unwrap();
219 assert_ne!(snap.bytes, [0u8; 32]);
220 }
221
222 #[test]
223 fn pool_refills_after_drain() {
224 let pool = EntropyPool::new(16).unwrap();
225 for _ in 0..8 {
227 let _ = pool.draw().unwrap();
228 }
229 std::thread::sleep(std::time::Duration::from_secs(2));
231 assert!(!pool.is_empty(), "pool should refill after draws");
232 }
233}