use rand::RngCore;
use rand_chacha::ChaCha20Rng;
use rand_chacha::rand_core::SeedableRng;
pub trait RngSource {
fn next_u8(&mut self) -> u8;
fn gen_range(&mut self, upper: u32) -> u32 {
assert!(upper > 0, "gen_range upper must be > 0");
let bound = (u32::MAX / upper) * upper;
loop {
let v = self.next_u32();
if v < bound {
return v % upper;
}
}
}
fn next_u32(&mut self) -> u32 {
let bytes = [
self.next_u8(),
self.next_u8(),
self.next_u8(),
self.next_u8(),
];
u32::from_le_bytes(bytes)
}
}
pub struct OsRngSource;
impl Default for OsRngSource {
fn default() -> Self {
Self
}
}
impl OsRngSource {
pub fn new() -> Self {
Self
}
}
impl RngSource for OsRngSource {
fn next_u8(&mut self) -> u8 {
let mut buf = [0u8; 1];
rand::rngs::OsRng.fill_bytes(&mut buf);
buf[0]
}
fn next_u32(&mut self) -> u32 {
rand::rngs::OsRng.next_u32()
}
}
pub struct SeededSource {
inner: ChaCha20Rng,
}
impl SeededSource {
pub fn from_seed(seed: [u8; 32]) -> Self {
Self {
inner: ChaCha20Rng::from_seed(seed),
}
}
}
impl RngSource for SeededSource {
fn next_u8(&mut self) -> u8 {
let mut buf = [0u8; 1];
self.inner.fill_bytes(&mut buf);
buf[0]
}
fn next_u32(&mut self) -> u32 {
self.inner.next_u32()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seeded_source_is_deterministic() {
let seed = [42u8; 32];
let mut a = SeededSource::from_seed(seed);
let mut b = SeededSource::from_seed(seed);
for _ in 0..1000 {
assert_eq!(a.next_u8(), b.next_u8());
}
}
#[test]
fn os_rng_produces_varied_output() {
let mut rng = OsRngSource::new();
let first = rng.next_u8();
let mut all_same = true;
for _ in 0..256 {
if rng.next_u8() != first {
all_same = false;
break;
}
}
assert!(!all_same, "OsRng should produce varied output");
}
#[test]
fn gen_range_stays_in_bounds() {
let mut rng = OsRngSource::new();
for _ in 0..1000 {
let v = rng.gen_range(10);
assert!(v < 10);
}
}
#[test]
#[should_panic]
fn gen_range_zero_upper_panics() {
let mut rng = OsRngSource::new();
let _ = rng.gen_range(0);
}
}