use std::convert::Infallible;
use rand::{rngs::SysRng, Rng, SeedableRng, TryCryptoRng, TryRng};
use vitaminc_protected::Controlled;
use zeroize::Zeroize;
pub struct SafeRand(rand::rngs::ChaCha20Rng);
impl SafeRand {
pub fn next_bounded_u32(&mut self, max: u32) -> u32 {
if max.is_power_of_two() {
self.0.next_u32() % max
} else {
let cap = max.next_power_of_two();
let mut value = self.0.next_u32() % cap;
while value > max {
value = self.next_u32() % cap;
}
value
}
}
pub fn from_entropy() -> Result<Self, crate::RandomError> {
Ok(Self::try_from_rng(&mut SysRng)?)
}
pub fn from_controlled_seed<C>(seed: C) -> Self
where
C: Controlled<Inner = [u8; 32]>,
{
let mut seed = seed.risky_unwrap();
let rng = Self(rand::rngs::ChaCha20Rng::from_seed(seed));
seed.zeroize();
rng
}
}
impl TryCryptoRng for SafeRand {}
impl TryRng for SafeRand {
type Error = Infallible;
#[inline]
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(self.0.next_u32())
}
#[inline]
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(self.0.next_u64())
}
#[inline]
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
self.0.fill_bytes(dst);
Ok(())
}
}
impl SeedableRng for SafeRand {
type Seed = [u8; 32];
fn from_seed(seed: Self::Seed) -> Self {
Self(rand::rngs::ChaCha20Rng::from_seed(seed))
}
}
#[cfg(test)]
mod tests {
use super::SafeRand;
#[test]
fn test_next_bounded_u32() -> Result<(), crate::RandomError> {
let mut rng = SafeRand::from_entropy()?;
let value = rng.next_bounded_u32(4);
assert!(value < 4);
Ok(())
}
#[test]
fn test_next_bounded_u32_non_power_of_two() -> Result<(), crate::RandomError> {
let mut rng = SafeRand::from_entropy()?;
let value = rng.next_bounded_u32(5);
assert!(value <= 5);
Ok(())
}
}