use crate::core::app_errors::Result as AppResult;
use getrandom;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use zeroize::Zeroizing;
pub struct SecureRng {
output_counter: AtomicU64,
request_counter: AtomicU64,
last_reseed_time: AtomicU64,
reseed_threshold_bytes: u64,
reseed_threshold_requests: u64,
reseed_threshold_time: u64,
}
impl SecureRng {
const DEFAULT_RESEED_BYTES: u64 = 1_048_576;
const DEFAULT_RESEED_REQUESTS: u64 = 10_000;
const DEFAULT_RESEED_TIME: u64 = 3600;
pub fn new() -> AppResult<Self> {
let mut probe = Zeroizing::new([0u8; 32]);
getrandom::fill(probe.as_mut())?;
Ok(Self {
output_counter: AtomicU64::new(0),
request_counter: AtomicU64::new(0),
last_reseed_time: AtomicU64::new(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
),
reseed_threshold_bytes: Self::DEFAULT_RESEED_BYTES,
reseed_threshold_requests: Self::DEFAULT_RESEED_REQUESTS,
reseed_threshold_time: Self::DEFAULT_RESEED_TIME,
})
}
pub fn generate_bytes(&self, dest: &mut [u8]) -> AppResult<()> {
if self.should_auto_reseed()? {
self.reseed()?;
}
getrandom::fill(dest)?;
self.output_counter
.fetch_add(dest.len() as u64, Ordering::Relaxed);
self.request_counter.fetch_add(1, Ordering::Relaxed);
Ok(())
}
fn should_auto_reseed(&self) -> AppResult<bool> {
let output_bytes = self.output_counter.load(Ordering::Relaxed);
let requests = self.request_counter.load(Ordering::Relaxed);
let current_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| {
crate::core::app_errors::GenerationError::IoError(std::io::Error::other(
"Time error",
))
})?
.as_secs();
let last_reseed = self.last_reseed_time.load(Ordering::Relaxed);
let elapsed = current_time.saturating_sub(last_reseed);
Ok(should_auto_reseed_by(
output_bytes,
requests,
elapsed,
self.reseed_threshold_bytes,
self.reseed_threshold_requests,
self.reseed_threshold_time,
))
}
pub fn reseed(&self) -> AppResult<()> {
let mut probe = Zeroizing::new([0u8; 32]);
getrandom::fill(probe.as_mut())?;
self.output_counter.store(0, Ordering::Relaxed);
self.request_counter.store(0, Ordering::Relaxed);
self.last_reseed_time.store(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
Ordering::Relaxed,
);
Ok(())
}
pub fn get_statistics(&self) -> RngStatistics {
RngStatistics {
output_bytes: self.output_counter.load(Ordering::Relaxed),
requests: self.request_counter.load(Ordering::Relaxed),
last_reseed: self.last_reseed_time.load(Ordering::Relaxed),
}
}
}
#[derive(Debug, Clone)]
pub struct RngStatistics {
pub output_bytes: u64,
pub requests: u64,
pub last_reseed: u64,
}
fn should_auto_reseed_by(
output_bytes: u64,
requests: u64,
elapsed_secs: u64,
bytes_threshold: u64,
request_threshold: u64,
time_threshold: u64,
) -> bool {
output_bytes >= bytes_threshold
|| requests >= request_threshold
|| elapsed_secs >= time_threshold
}