use std::time::{Duration, SystemTime};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum TimeMode {
Frozen(u64),
Stepped { seed: u64, step_ms: u64 },
Real,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum RngMode {
Seeded(u64),
Real,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum FsMode {
Ephemeral,
ReadOnly { root: String },
Real,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum NetMode {
Offline,
Limited { allow_hosts: Vec<String> },
Real,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ProcMode {
NonRoot {
uid: u32,
gid: u32,
drop_caps: Vec<String>,
},
Real,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeterministicSurfaces {
pub time: TimeMode,
pub rng: RngMode,
pub fs: FsMode,
pub net: NetMode,
pub proc: ProcMode,
}
impl DeterministicSurfaces {
pub fn new(
time: TimeMode,
rng: RngMode,
fs: FsMode,
net: NetMode,
proc: ProcMode,
) -> Self {
Self {
time,
rng,
fs,
net,
proc,
}
}
pub fn deterministic(seed: u64) -> Self {
Self {
time: TimeMode::Frozen(seed),
rng: RngMode::Seeded(seed),
fs: FsMode::Ephemeral,
net: NetMode::Offline,
proc: ProcMode::NonRoot {
uid: 1000,
gid: 1000,
drop_caps: vec![
"CAP_NET_ADMIN".to_string(),
"CAP_SYS_ADMIN".to_string(),
"CAP_SYS_MODULE".to_string(),
],
},
}
}
pub fn permissive() -> Self {
Self {
time: TimeMode::Real,
rng: RngMode::Real,
fs: FsMode::Real,
net: NetMode::Real,
proc: ProcMode::Real,
}
}
pub fn is_fully_deterministic(&self) -> bool {
!matches!(self.time, TimeMode::Real)
&& !matches!(self.rng, RngMode::Real)
&& !matches!(self.fs, FsMode::Real)
&& !matches!(self.net, NetMode::Real)
&& !matches!(self.proc, ProcMode::Real)
}
pub fn time_mode(&self) -> &TimeMode {
&self.time
}
pub fn rng_mode(&self) -> &RngMode {
&self.rng
}
pub fn fs_mode(&self) -> &FsMode {
&self.fs
}
pub fn net_mode(&self) -> &NetMode {
&self.net
}
pub fn proc_mode(&self) -> &ProcMode {
&self.proc
}
pub fn determinism_score(&self) -> f64 {
let mut score = 0.0;
let mut count = 0;
if !matches!(self.time, TimeMode::Real) {
score += 0.2;
}
count += 1;
if !matches!(self.rng, RngMode::Real) {
score += 0.2;
}
count += 1;
if !matches!(self.fs, FsMode::Real) {
score += 0.2;
}
count += 1;
if !matches!(self.net, NetMode::Real) {
score += 0.2;
}
count += 1;
if !matches!(self.proc, ProcMode::Real) {
score += 0.2;
}
count += 1;
score
}
}
pub struct ControlledTime {
mode: TimeMode,
start: SystemTime,
steps: u64,
}
impl ControlledTime {
pub fn new(mode: TimeMode) -> Self {
Self {
mode,
start: SystemTime::now(),
steps: 0,
}
}
pub fn now(&mut self) -> SystemTime {
match self.mode {
TimeMode::Frozen(seed) => {
SystemTime::UNIX_EPOCH + Duration::from_secs(seed)
}
TimeMode::Stepped { seed, step_ms } => {
let offset = Duration::from_millis(step_ms * self.steps);
self.steps += 1;
SystemTime::UNIX_EPOCH + Duration::from_secs(seed) + offset
}
TimeMode::Real => {
SystemTime::now()
}
}
}
pub fn reset(&mut self) {
self.steps = 0;
}
}
pub struct ControlledRng {
mode: RngMode,
state: u64,
}
impl ControlledRng {
pub fn new(mode: RngMode) -> Self {
let state = match mode {
RngMode::Seeded(seed) => seed,
RngMode::Real => {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(0);
hasher.finish()
}
};
Self { mode, state }
}
pub fn next(&mut self) -> u64 {
match self.mode {
RngMode::Seeded(_) => {
self.state ^= self.state << 13;
self.state ^= self.state >> 7;
self.state ^= self.state << 17;
self.state
}
RngMode::Real => {
use std::collections::hash_map::RandomState;
use std::hash::{BuildHasher, Hasher};
let mut hasher = RandomState::new().build_hasher();
hasher.write_u64(self.state);
self.state = hasher.finish();
self.state
}
}
}
pub fn range(&mut self, min: u64, max: u64) -> u64 {
let range = max - min;
if range == 0 {
return min;
}
min + (self.next() % range)
}
pub fn bool(&mut self) -> bool {
(self.next() & 1) == 1
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_time_frozen() {
let mut time = ControlledTime::new(TimeMode::Frozen(42));
let t1 = time.now();
let t2 = time.now();
assert_eq!(t1, t2);
}
#[test]
fn test_time_stepped() {
let mut time = ControlledTime::new(TimeMode::Stepped {
seed: 42,
step_ms: 1000,
});
let t1 = time.now();
let t2 = time.now();
assert!(t2 > t1);
}
#[test]
fn test_rng_seeded_deterministic() {
let mut rng1 = ControlledRng::new(RngMode::Seeded(42));
let mut rng2 = ControlledRng::new(RngMode::Seeded(42));
let nums1: Vec<u64> = (0..10).map(|_| rng1.next()).collect();
let nums2: Vec<u64> = (0..10).map(|_| rng2.next()).collect();
assert_eq!(nums1, nums2);
}
#[test]
fn test_determinism_score() {
let det = DeterministicSurfaces::deterministic(42);
assert_eq!(det.determinism_score(), 1.0);
let perm = DeterministicSurfaces::permissive();
assert_eq!(perm.determinism_score(), 0.0);
}
#[test]
fn test_is_fully_deterministic() {
let det = DeterministicSurfaces::deterministic(42);
assert!(det.is_fully_deterministic());
let perm = DeterministicSurfaces::permissive();
assert!(!perm.is_fully_deterministic());
}
}