use std::num::NonZeroU32;
use crate::SafeRand;
use rand::CryptoRng;
use vitaminc_protected::{Controlled, Protected};
pub trait BoundedRng<T> {
type Output;
fn next_below(&mut self, n: T) -> Self::Output;
}
#[deprecated(
note = "inclusive `0..=max`; use `BoundedRng::next_below(max + 1)`, or `next_below(n)` when you have a length `n`"
)]
pub trait BoundedRngInclusive<T> {
fn next_bounded(&mut self, max: T) -> T;
}
impl BoundedRng<u32> for SafeRand {
type Output = u32;
fn next_below(&mut self, n: u32) -> u32 {
below_u32(self, n)
}
}
impl BoundedRng<Protected<u32>> for SafeRand {
type Output = Protected<u32>;
fn next_below(&mut self, n: Protected<u32>) -> Protected<u32> {
assert!(*n.risky_ref() != 0, "range must be non-zero");
n.map(|n| below_u32(self, n))
}
}
impl BoundedRng<Protected<NonZeroU32>> for SafeRand {
type Output = Protected<u32>;
fn next_below(&mut self, n: Protected<NonZeroU32>) -> Protected<u32> {
n.map(|n| below_u32(self, n.get()))
}
}
#[allow(deprecated)]
impl BoundedRngInclusive<u32> for SafeRand {
fn next_bounded(&mut self, max: u32) -> u32 {
upto_u32(self, max)
}
}
#[allow(deprecated)]
impl BoundedRngInclusive<Protected<u32>> for SafeRand {
fn next_bounded(&mut self, max: Protected<u32>) -> Protected<u32> {
max.map(|max| upto_u32(self, max))
}
}
pub(crate) fn below_u64<R: CryptoRng>(rng: &mut R, range: u64) -> u64 {
assert!(range > 0, "range must be non-zero");
((u128::from(rng.next_u64()) * u128::from(range)) >> 64) as u64
}
pub(crate) fn below_u32<R: CryptoRng>(rng: &mut R, range: u32) -> u32 {
below_u64(rng, u64::from(range)) as u32
}
pub(crate) fn upto_u32<R: CryptoRng>(rng: &mut R, max: u32) -> u32 {
below_u64(rng, u64::from(max) + 1) as u32
}
#[cfg(test)]
mod test {
use std::convert::Infallible;
use std::num::NonZeroU32;
use rand::TryCryptoRng;
use super::{below_u32, upto_u32};
struct FixedDraw(u64);
impl rand::TryRng for FixedDraw {
type Error = Infallible;
fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
Ok(self.0 as u32)
}
fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
Ok(self.0)
}
fn try_fill_bytes(&mut self, _dest: &mut [u8]) -> Result<(), Self::Error> {
unimplemented!()
}
}
impl TryCryptoRng for FixedDraw {}
#[test]
fn min_draw_maps_to_zero() {
assert_eq!(0, upto_u32(&mut FixedDraw(0), 9));
assert_eq!(0, below_u32(&mut FixedDraw(0), 10));
}
#[test]
fn max_draw_maps_to_the_top_of_the_range() {
assert_eq!(9, upto_u32(&mut FixedDraw(u64::MAX), 9));
assert_eq!(31, upto_u32(&mut FixedDraw(u64::MAX), 31));
assert_eq!(32, upto_u32(&mut FixedDraw(u64::MAX), 32));
assert_eq!(9, below_u32(&mut FixedDraw(u64::MAX), 10));
assert_eq!(31, below_u32(&mut FixedDraw(u64::MAX), 32));
}
#[test]
fn midpoint_draw_maps_to_half_range() {
assert_eq!(5, upto_u32(&mut FixedDraw(1 << 63), 9));
assert_eq!(5, below_u32(&mut FixedDraw(1 << 63), 10));
}
#[test]
fn a_bound_of_zero_is_always_zero_inclusively() {
assert_eq!(0, upto_u32(&mut FixedDraw(u64::MAX), 0));
assert_eq!(0, below_u32(&mut FixedDraw(u64::MAX), 1));
}
#[test]
#[should_panic(expected = "range must be non-zero")]
fn an_empty_half_open_range_panics() {
below_u32(&mut FixedDraw(0), 0);
}
#[test]
fn max_of_u32_max_covers_the_whole_word() {
assert_eq!(u32::MAX, upto_u32(&mut FixedDraw(u64::MAX), u32::MAX));
assert_eq!(0, upto_u32(&mut FixedDraw(0), u32::MAX));
}
#[test]
fn a_zero_protected_bound_panics_before_it_is_unwrapped() {
use crate::SafeRand;
use rand::SeedableRng;
use vitaminc_protected::Protected;
let mut rng = SafeRand::from_seed([5u8; 32]);
let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _: Protected<u32> = rng.next_below(Protected::new(0));
}));
let msg = caught.expect_err("a zero bound must panic");
let msg = msg
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| msg.downcast_ref::<&str>().copied())
.unwrap_or_default();
assert!(msg.contains("range must be non-zero"), "{msg}");
let mut untouched = SafeRand::from_seed([5u8; 32]);
assert_eq!(untouched.next_below(1000), rng.next_below(1000));
}
#[test]
fn trait_impls_route_through_the_shared_helpers() {
use super::BoundedRng;
#[allow(deprecated)]
use super::BoundedRngInclusive;
use crate::SafeRand;
use rand::SeedableRng;
use vitaminc_protected::{Controlled, Protected};
let mut helper = SafeRand::from_seed([5u8; 32]);
let mut plain = SafeRand::from_seed([5u8; 32]);
let mut protected = SafeRand::from_seed([5u8; 32]);
let mut nonzero = SafeRand::from_seed([5u8; 32]);
let mut inherent = SafeRand::from_seed([5u8; 32]);
for _ in 0..100 {
let want = below_u32(&mut helper, 1000);
assert_eq!(want, BoundedRng::next_below(&mut plain, 1000u32));
let p: Protected<u32> = protected.next_below(Protected::new(1000));
assert_eq!(want, p.risky_unwrap());
let bound = Protected::new(NonZeroU32::new(1000).unwrap());
let p: Protected<u32> = nonzero.next_below(bound);
assert_eq!(want, p.risky_unwrap());
assert_eq!(want, inherent.next_below(1000));
}
#[allow(deprecated)]
for _ in 0..100 {
let want = upto_u32(&mut helper, 999);
assert_eq!(want, plain.next_bounded(999u32));
let p: Protected<u32> = protected.next_bounded(Protected::new(999));
assert_eq!(want, p.risky_unwrap());
assert_eq!(want, inherent.next_bounded_u32(999));
}
}
#[test]
fn a_nonzero_protected_bound_of_one_draws_without_rejecting() {
use crate::SafeRand;
use rand::SeedableRng;
use vitaminc_protected::{Controlled, Protected};
let mut rng = SafeRand::from_seed([5u8; 32]);
for _ in 0..16 {
let p: Protected<u32> = rng.next_below(Protected::new(NonZeroU32::MIN));
assert_eq!(0, p.risky_unwrap(), "`0..1` has only one value to draw");
}
}
}