use rand_xoshiro::rand_core::{Rng, SeedableRng};
use rand_xoshiro::Xoshiro256PlusPlus;
#[derive(Debug, Clone)]
pub struct FaultConfig {
pub send_reject_prob: f64,
pub delay_prob: f64,
pub delay_ticks_min: u64,
pub delay_ticks_max: u64,
pub duplicate_prob: f64,
pub crash_prob: f64,
}
impl Default for FaultConfig {
fn default() -> Self {
Self {
send_reject_prob: 0.0,
delay_prob: 0.0,
delay_ticks_min: 1,
delay_ticks_max: 8,
duplicate_prob: 0.0,
crash_prob: 0.0,
}
}
}
impl FaultConfig {
pub fn chaos(crash_prob: f64) -> Self {
Self {
send_reject_prob: 0.01,
delay_prob: 0.05,
delay_ticks_min: 1,
delay_ticks_max: 8,
duplicate_prob: 0.02,
crash_prob,
}
}
pub fn is_fault_free(&self) -> bool {
self.send_reject_prob == 0.0
&& self.delay_prob == 0.0
&& self.duplicate_prob == 0.0
&& self.crash_prob == 0.0
}
}
pub(crate) struct SimRng {
inner: Xoshiro256PlusPlus,
}
impl SimRng {
pub fn new(seed: u64) -> Self {
Self {
inner: Xoshiro256PlusPlus::seed_from_u64(seed),
}
}
pub fn next_u64(&mut self) -> u64 {
self.inner.next_u64()
}
pub fn next_f64(&mut self) -> f64 {
((self.next_u64() >> 11) as f64) * (1.0 / (1u64 << 53) as f64)
}
pub fn below(&mut self, n: u64) -> u64 {
debug_assert!(n > 0);
let wide = (self.next_u64() as u128 * n as u128) >> 64;
wide as u64
}
pub fn range(&mut self, lo: u64, hi: u64) -> u64 {
if hi <= lo {
return lo;
}
lo + self.below(hi - lo + 1)
}
pub fn roll(&mut self, p: f64) -> bool {
if p <= 0.0 {
return false;
}
self.next_f64() < p
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn same_seed_same_stream() {
let mut a = SimRng::new(12345);
let mut b = SimRng::new(12345);
for _ in 0..1000 {
assert_eq!(a.next_u64(), b.next_u64());
}
}
#[test]
fn next_f64_in_range() {
let mut rng = SimRng::new(7);
for _ in 0..10_000 {
let x = rng.next_f64();
assert!((0.0..1.0).contains(&x), "out of range: {x}");
}
}
#[test]
fn below_bounded_and_covers() {
let mut rng = SimRng::new(99);
let mut seen = [false; 8];
for _ in 0..10_000 {
let v = rng.below(8);
assert!(v < 8);
seen[v as usize] = true;
}
assert!(seen.iter().all(|&s| s), "all buckets hit");
}
#[test]
fn roll_zero_never_true_and_is_deterministic() {
let mut a = SimRng::new(5);
let mut b = SimRng::new(5);
for _ in 0..100 {
assert!(!a.roll(0.0));
assert_eq!(
a.next_u64(),
b.next_u64(),
"zero-prob draws consume nothing"
);
}
assert!((0..100).filter(|_| a.roll(1.0)).count() == 100);
}
#[test]
fn range_inclusive() {
let mut rng = SimRng::new(11);
for _ in 0..1000 {
assert_eq!(rng.range(3, 3), 3);
let v = rng.range(2, 5);
assert!((2..=5).contains(&v));
}
}
}