use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq)]
pub enum AdversarialError {
CorruptedInput {
byte_index: usize,
expected_checksum: u32,
actual_checksum: u32,
},
AllocationFailed { requested_bytes: usize },
ZeroSizeInput,
MaxSizeExceeded { size: usize, max: usize },
ClockSkew { prev: Instant, curr: Instant },
RaceCondition { description: String },
ConfigParseError { field: String, reason: String },
ConfigOutOfBounds {
field: String,
value: String,
min: String,
max: String,
},
IntegerOverflow { operation: String },
DivisionByZero { numerator: f64 },
NaNDetected { index: usize },
InfinityDetected { index: usize, positive: bool },
StackOverflow { depth: usize, max_depth: usize },
ResourceExhausted { resource: String },
Timeout {
operation: String,
elapsed: Duration,
limit: Duration,
},
Cancelled { operation: String },
RecoveryFailed {
original_error: String,
recovery_error: String,
},
}
pub type AdversarialResult<T> = Result<T, AdversarialError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AdversarialTactic {
BitFlipInjection,
ResourceStarvation,
ClockSkew,
NetworkPartition,
ConfigFuzzing,
}
impl AdversarialTactic {
pub fn all() -> &'static [AdversarialTactic] {
&[
AdversarialTactic::BitFlipInjection,
AdversarialTactic::ResourceStarvation,
AdversarialTactic::ClockSkew,
AdversarialTactic::NetworkPartition,
AdversarialTactic::ConfigFuzzing,
]
}
pub fn name(&self) -> &'static str {
match self {
AdversarialTactic::BitFlipInjection => "Bit-Flip Injection",
AdversarialTactic::ResourceStarvation => "Resource Starvation",
AdversarialTactic::ClockSkew => "Clock Skew",
AdversarialTactic::NetworkPartition => "Network Partition",
AdversarialTactic::ConfigFuzzing => "Config Fuzzing",
}
}
pub fn tool(&self) -> &'static str {
match self {
AdversarialTactic::BitFlipInjection => "proptest",
AdversarialTactic::ResourceStarvation => "stress simulation",
AdversarialTactic::ClockSkew => "libfaketime simulation",
AdversarialTactic::NetworkPartition => "timeout simulation",
AdversarialTactic::ConfigFuzzing => "proptest",
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CheckedArithmetic;
impl CheckedArithmetic {
pub fn new() -> Self {
Self
}
pub fn checked_add_i64(a: i64, b: i64) -> AdversarialResult<i64> {
a.checked_add(b)
.ok_or_else(|| AdversarialError::IntegerOverflow {
operation: format!("{a} + {b}"),
})
}
pub fn checked_mul_i64(a: i64, b: i64) -> AdversarialResult<i64> {
a.checked_mul(b)
.ok_or_else(|| AdversarialError::IntegerOverflow {
operation: format!("{a} * {b}"),
})
}
pub fn checked_add_usize(a: usize, b: usize) -> AdversarialResult<usize> {
a.checked_add(b)
.ok_or_else(|| AdversarialError::IntegerOverflow {
operation: format!("{a} + {b}"),
})
}
pub fn checked_mul_usize(a: usize, b: usize) -> AdversarialResult<usize> {
a.checked_mul(b)
.ok_or_else(|| AdversarialError::IntegerOverflow {
operation: format!("{a} * {b}"),
})
}
pub fn checked_div_f64(a: f64, b: f64) -> AdversarialResult<f64> {
if b == 0.0 {
return Err(AdversarialError::DivisionByZero { numerator: a });
}
Ok(a / b)
}
pub fn checked_div_i64(a: i64, b: i64) -> AdversarialResult<i64> {
if b == 0 {
return Err(AdversarialError::DivisionByZero {
numerator: a as f64,
});
}
Ok(a / b)
}
}
#[derive(Debug, Clone)]
pub struct MonotonicClock {
last_timestamp: Option<Instant>,
}
impl Default for MonotonicClock {
fn default() -> Self {
Self::new()
}
}
impl MonotonicClock {
pub fn new() -> Self {
Self {
last_timestamp: None,
}
}
pub fn tick(&mut self) -> AdversarialResult<Instant> {
let now = Instant::now();
if let Some(prev) = self.last_timestamp {
if now < prev {
return Err(AdversarialError::ClockSkew { prev, curr: now });
}
}
self.last_timestamp = Some(now);
Ok(now)
}
pub fn elapsed(&self) -> Option<Duration> {
self.last_timestamp.map(|t| t.elapsed())
}
pub fn reset(&mut self) {
self.last_timestamp = None;
}
}
#[derive(Debug, Clone, Copy)]
pub struct ResourceUsage {
pub stack_depth: usize,
pub memory_bytes: usize,
pub elapsed: Option<Duration>,
}
#[derive(Debug, Clone)]
pub struct AdversarialTestSummary {
pub total_tests: usize,
pub passed: usize,
pub failed: usize,
pub tactics_tested: Vec<AdversarialTactic>,
pub errors_handled: Vec<String>,
}
impl AdversarialTestSummary {
pub fn new() -> Self {
Self {
total_tests: 0,
passed: 0,
failed: 0,
tactics_tested: Vec::new(),
errors_handled: Vec::new(),
}
}
pub fn record_pass(&mut self, tactic: AdversarialTactic) {
self.total_tests += 1;
self.passed += 1;
if !self.tactics_tested.contains(&tactic) {
self.tactics_tested.push(tactic);
}
}
pub fn record_fail(&mut self, tactic: AdversarialTactic, reason: &str) {
self.total_tests += 1;
self.failed += 1;
if !self.tactics_tested.contains(&tactic) {
self.tactics_tested.push(tactic);
}
self.errors_handled.push(reason.to_string());
}
pub fn record_error_handled(&mut self, error: &AdversarialError) {
self.errors_handled.push(format!("{error:?}"));
}
pub fn pass_rate(&self) -> f64 {
if self.total_tests == 0 {
return 0.0;
}
(self.passed as f64) / (self.total_tests as f64) * 100.0
}
pub fn all_passed(&self) -> bool {
self.failed == 0 && self.total_tests > 0
}
}
impl Default for AdversarialTestSummary {
fn default() -> Self {
Self::new()
}
}