use std::collections::VecDeque;
use std::sync::{Arc, Condvar, Mutex};
use std::thread;
use crate::entropy::{self, EntropySnapshot};
use crate::error::Result;
pub struct EntropyPool {
inner: Arc<PoolInner>,
}
struct PoolInner {
state: Mutex<PoolState>,
need_refill: Condvar,
snapshot_added: Condvar,
capacity: usize,
watermark: usize,
}
struct PoolState {
buf: VecDeque<EntropySnapshot>,
shutdown: bool,
}
impl EntropyPool {
pub fn new(capacity: usize) -> Result<Self> {
assert!(capacity > 0, "EntropyPool capacity must be > 0");
let capacity = capacity.clamp(8, 1024);
let watermark = capacity / 2;
let prewarm = capacity.min(8);
let inner = Arc::new(PoolInner {
state: Mutex::new(PoolState {
buf: VecDeque::with_capacity(capacity),
shutdown: false,
}),
need_refill: Condvar::new(),
snapshot_added: Condvar::new(),
capacity,
watermark,
});
let worker = Arc::clone(&inner);
thread::spawn(move || refill_loop(worker));
inner.need_refill.notify_one();
{
let mut state = inner.state.lock().unwrap();
while state.buf.len() < prewarm {
state = inner.snapshot_added.wait(state).unwrap();
}
}
Ok(Self { inner })
}
pub fn draw(&self) -> Result<EntropySnapshot> {
let mut state = self.inner.state.lock().unwrap();
if let Some(snap) = state.buf.pop_front() {
let below_watermark = state.buf.len() < self.inner.watermark;
drop(state);
if below_watermark {
self.inner.need_refill.notify_one();
}
Ok(snap)
} else {
drop(state);
entropy::gather()
}
}
pub fn len(&self) -> usize {
self.inner.state.lock().unwrap().buf.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl Drop for EntropyPool {
fn drop(&mut self) {
let mut state = self.inner.state.lock().unwrap();
state.shutdown = true;
drop(state);
self.inner.need_refill.notify_one();
}
}
fn refill_loop(inner: Arc<PoolInner>) {
loop {
let mut state = inner.state.lock().unwrap();
while !state.shutdown && state.buf.len() >= inner.capacity {
state = inner.need_refill.wait(state).unwrap();
}
if state.shutdown {
return;
}
let slots = inner.capacity - state.buf.len();
drop(state);
for _ in 0..slots {
{
let st = inner.state.lock().unwrap();
if st.shutdown {
return;
}
}
if let Ok(snap) = entropy::gather() {
let mut state = inner.state.lock().unwrap();
if state.buf.len() < inner.capacity {
state.buf.push_back(snap);
drop(state);
inner.snapshot_added.notify_one();
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pool_creates_and_prewarms() {
let pool = EntropyPool::new(16).unwrap();
assert!(pool.len() >= 8, "pool should pre-warm at least 8 snapshots");
}
#[test]
fn draw_returns_valid_snapshot() {
let pool = EntropyPool::new(16).unwrap();
let snap = pool.draw().unwrap();
assert_ne!(snap.bytes, [0u8; 32], "snapshot bytes must be non-zero");
assert_ne!(snap.timestamp_nanos, 0, "timestamp must be non-zero");
}
#[test]
fn successive_draws_differ() {
let pool = EntropyPool::new(16).unwrap();
let s1 = pool.draw().unwrap();
let s2 = pool.draw().unwrap();
assert_ne!(
s1.bytes, s2.bytes,
"two draws must produce different snapshots"
);
}
#[test]
fn draw_under_exhaustion_still_works() {
let pool = EntropyPool::new(8).unwrap();
for _ in 0..8 {
let _ = pool.draw().unwrap();
}
let snap = pool.draw().unwrap();
assert_ne!(snap.bytes, [0u8; 32]);
}
#[test]
fn pool_refills_after_drain() {
let pool = EntropyPool::new(16).unwrap();
for _ in 0..8 {
let _ = pool.draw().unwrap();
}
std::thread::sleep(std::time::Duration::from_secs(2));
assert!(!pool.is_empty(), "pool should refill after draws");
}
}