#[cfg(test)]
use rand::SeedableRng;
use rand::rngs::OsRng;
use rand::{CryptoRng, Rng, RngCore};
use crate::bytes::FixedByteBuffer;
pub trait SupportRng {
fn random_byte_array<const T: usize>(&mut self) -> [u8; T];
fn random_byte_buffer<const T: usize>(&mut self) -> FixedByteBuffer<T>;
#[cfg(feature = "client")]
fn random_item<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T>;
}
pub enum TyphoonRng {
Os(OsRng),
#[cfg(test)]
Seeded(rand::rngs::StdRng),
}
impl RngCore for TyphoonRng {
fn next_u32(&mut self) -> u32 {
match self {
TyphoonRng::Os(r) => r.next_u32(),
#[cfg(test)]
TyphoonRng::Seeded(r) => r.next_u32(),
}
}
fn next_u64(&mut self) -> u64 {
match self {
TyphoonRng::Os(r) => r.next_u64(),
#[cfg(test)]
TyphoonRng::Seeded(r) => r.next_u64(),
}
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
match self {
TyphoonRng::Os(r) => r.fill_bytes(dest),
#[cfg(test)]
TyphoonRng::Seeded(r) => r.fill_bytes(dest),
}
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
match self {
TyphoonRng::Os(r) => r.try_fill_bytes(dest),
#[cfg(test)]
TyphoonRng::Seeded(r) => r.try_fill_bytes(dest),
}
}
}
impl CryptoRng for TyphoonRng {}
impl SupportRng for TyphoonRng {
fn random_byte_array<const T: usize>(&mut self) -> [u8; T] {
let mut buf = [0u8; T];
self.fill_bytes(&mut buf);
buf
}
fn random_byte_buffer<const T: usize>(&mut self) -> FixedByteBuffer<T> {
FixedByteBuffer::from_array(self.random_byte_array::<T>())
}
#[cfg(feature = "client")]
fn random_item<'a, T>(&mut self, slice: &'a [T]) -> Option<&'a T> {
if slice.is_empty() {
None
} else {
Some(&slice[self.gen_range(0..slice.len())])
}
}
}
#[cfg(test)]
use std::cell::RefCell;
#[cfg(test)]
thread_local! {
static TEST_RNG: RefCell<Option<rand::rngs::StdRng>> = const { RefCell::new(None) };
}
#[cfg(test)]
pub fn set_test_rng_seed(seed: u64) {
TEST_RNG.with(|r| *r.borrow_mut() = Some(rand::rngs::StdRng::seed_from_u64(seed)));
}
#[cfg(test)]
pub fn clear_test_rng() {
TEST_RNG.with(|r| *r.borrow_mut() = None);
}
#[inline]
pub fn get_rng() -> TyphoonRng {
#[cfg(test)]
{
let forked = TEST_RNG.with(|r| r.borrow_mut().as_mut().map(|rng| rand::rngs::StdRng::seed_from_u64(rng.next_u64())));
if let Some(seeded) = forked {
return TyphoonRng::Seeded(seeded);
}
}
TyphoonRng::Os(OsRng)
}
#[cfg(test)]
#[path = "../../tests/utils/random.rs"]
mod tests;
#[inline]
pub fn jittered_chunk_size(max_payload: usize, chunk: usize, jitter: f64) -> usize {
let target = if chunk == 0 {
max_payload
} else {
chunk
};
if max_payload <= 1 {
return max_payload;
}
let target_f = target as f64;
let delta = (target_f * jitter).round() as usize;
let lo = target.saturating_sub(delta).max(1);
let hi = target.saturating_add(delta).min(max_payload);
if lo >= hi {
return hi;
}
get_rng().gen_range(lo..=hi)
}
#[macro_export]
macro_rules! weighted_random {
(@parse {} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {{
use weighted_rand::builder::NewBuilder as _;
let __weights: &[u32] = &[$( ($weights) as u32 ),*];
let __table = weighted_rand::builder::WalkerTableBuilder::new(__weights).build();
let mut __rng = $crate::utils::random::get_rng();
let __idx = __table.next_rng(&mut __rng);
'wr: {
let mut __i = 0usize;
$(
if __idx == __i { break 'wr ($bodies); }
#[allow(unused_assignments)]
{ __i += 1; }
)*
unreachable!()
}
}};
(@parse {, $($rest:tt)*} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {$($rest)*} -> ($($weights,)*) ($($bodies,)*))
};
(@parse {$weight:expr => $body:block $($rest:tt)*} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {$($rest)*} -> ($($weights,)* $weight,) ($($bodies,)* $body,))
};
(@parse {$body:block $($rest:tt)*} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {$($rest)*} -> ($($weights,)* 1u32,) ($($bodies,)* $body,))
};
(@parse {$weight:expr => $body:expr, $($rest:tt)*} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {$($rest)*} -> ($($weights,)* $weight,) ($($bodies,)* $body,))
};
(@parse {$weight:expr => $body:expr} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {} -> ($($weights,)* $weight,) ($($bodies,)* $body,))
};
(@parse {$body:expr, $($rest:tt)*} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {$($rest)*} -> ($($weights,)* 1u32,) ($($bodies,)* $body,))
};
(@parse {$body:expr} -> ($($weights:expr,)*) ($($bodies:expr,)*)) => {
$crate::weighted_random!(@parse {} -> ($($weights,)* 1u32,) ($($bodies,)* $body,))
};
(@parse $($_rest:tt)*) => {
::core::compile_error!(
"malformed `weighted_random!` input — expected comma-separated `weight => expr` or `expr` branches"
)
};
($($input:tt)*) => {
$crate::weighted_random!(@parse {$($input)*} -> () ())
};
}