pub struct Rng {
state: u64,
}
impl Rng {
pub fn seeded(seed: u64) -> Self {
Self { state: seed }
}
pub fn next_u64(&mut self) -> u64 {
self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
pub fn below(&mut self, n: usize) -> usize {
if n == 0 {
return 0;
}
(self.next_u64() % n as u64) as usize
}
pub fn byte(&mut self) -> u8 {
self.next_u64() as u8
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttackOutcome {
Advanced,
Breach(String),
NoProgress,
}
pub trait Attack {
fn name(&self) -> &'static str;
fn step(&mut self, rng: &mut Rng) -> AttackOutcome;
}
#[derive(Debug, Clone)]
pub struct LabReport {
pub name: &'static str,
pub attempts: usize,
pub advances: usize,
pub breaches: Vec<String>,
}
impl LabReport {
pub fn is_clean(&self) -> bool {
self.breaches.is_empty()
}
}
pub fn run(attack: &mut dyn Attack, seed: u64, budget: usize) -> LabReport {
let mut rng = Rng::seeded(seed);
let mut report = LabReport {
name: attack.name(),
attempts: 0,
advances: 0,
breaches: Vec::new(),
};
for _ in 0..budget {
report.attempts += 1;
match attack.step(&mut rng) {
AttackOutcome::Advanced => report.advances += 1,
AttackOutcome::Breach(detail) => report.breaches.push(detail),
AttackOutcome::NoProgress => {}
}
}
report
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prng_is_deterministic_for_a_seed() {
let mut a = Rng::seeded(42);
let mut b = Rng::seeded(42);
let seq_a: Vec<u64> = (0..8).map(|_| a.next_u64()).collect();
let seq_b: Vec<u64> = (0..8).map(|_| b.next_u64()).collect();
assert_eq!(seq_a, seq_b, "misma semilla debe dar misma secuencia");
}
#[test]
fn prng_differs_across_seeds() {
let mut a = Rng::seeded(1);
let mut b = Rng::seeded(2);
assert_ne!(a.next_u64(), b.next_u64(), "semillas distintas divergen");
}
#[test]
fn below_stays_in_range() {
let mut r = Rng::seeded(7);
for _ in 0..1000 {
assert!(r.below(10) < 10);
}
}
struct BreachOnThird {
count: usize,
}
impl Attack for BreachOnThird {
fn name(&self) -> &'static str {
"breach-on-third"
}
fn step(&mut self, _rng: &mut Rng) -> AttackOutcome {
self.count += 1;
if self.count == 3 {
AttackOutcome::Breach("simulada".into())
} else {
AttackOutcome::Advanced
}
}
}
#[test]
fn run_collects_breaches_and_is_reproducible() {
let mut a = BreachOnThird { count: 0 };
let report = run(&mut a, 99, 5);
assert_eq!(report.attempts, 5);
assert_eq!(report.advances, 4);
assert_eq!(report.breaches, vec!["simulada".to_string()]);
assert!(!report.is_clean());
}
#[test]
fn clean_report_has_no_breaches() {
struct Always(AttackOutcome);
impl Attack for Always {
fn name(&self) -> &'static str {
"always"
}
fn step(&mut self, _rng: &mut Rng) -> AttackOutcome {
match self.0 {
AttackOutcome::NoProgress => AttackOutcome::NoProgress,
_ => AttackOutcome::Advanced,
}
}
}
let mut a = Always(AttackOutcome::NoProgress);
let report = run(&mut a, 1, 10);
assert!(report.is_clean());
assert_eq!(report.advances, 0);
}
}