1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone, PartialEq)]
7pub enum AdversarialError {
8 CorruptedInput {
10 byte_index: usize,
11 expected_checksum: u32,
12 actual_checksum: u32,
13 },
14 AllocationFailed { requested_bytes: usize },
16 ZeroSizeInput,
18 MaxSizeExceeded { size: usize, max: usize },
20 ClockSkew { prev: Instant, curr: Instant },
22 RaceCondition { description: String },
24 ConfigParseError { field: String, reason: String },
26 ConfigOutOfBounds {
28 field: String,
29 value: String,
30 min: String,
31 max: String,
32 },
33 IntegerOverflow { operation: String },
35 DivisionByZero { numerator: f64 },
37 NaNDetected { index: usize },
39 InfinityDetected { index: usize, positive: bool },
41 StackOverflow { depth: usize, max_depth: usize },
43 ResourceExhausted { resource: String },
45 Timeout {
47 operation: String,
48 elapsed: Duration,
49 limit: Duration,
50 },
51 Cancelled { operation: String },
53 RecoveryFailed {
55 original_error: String,
56 recovery_error: String,
57 },
58}
59
60pub type AdversarialResult<T> = Result<T, AdversarialError>;
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum AdversarialTactic {
66 BitFlipInjection,
68 ResourceStarvation,
70 ClockSkew,
72 NetworkPartition,
74 ConfigFuzzing,
76}
77
78impl AdversarialTactic {
79 pub fn all() -> &'static [AdversarialTactic] {
81 &[
82 AdversarialTactic::BitFlipInjection,
83 AdversarialTactic::ResourceStarvation,
84 AdversarialTactic::ClockSkew,
85 AdversarialTactic::NetworkPartition,
86 AdversarialTactic::ConfigFuzzing,
87 ]
88 }
89
90 pub fn name(&self) -> &'static str {
92 match self {
93 AdversarialTactic::BitFlipInjection => "Bit-Flip Injection",
94 AdversarialTactic::ResourceStarvation => "Resource Starvation",
95 AdversarialTactic::ClockSkew => "Clock Skew",
96 AdversarialTactic::NetworkPartition => "Network Partition",
97 AdversarialTactic::ConfigFuzzing => "Config Fuzzing",
98 }
99 }
100
101 pub fn tool(&self) -> &'static str {
103 match self {
104 AdversarialTactic::BitFlipInjection => "proptest",
105 AdversarialTactic::ResourceStarvation => "stress simulation",
106 AdversarialTactic::ClockSkew => "libfaketime simulation",
107 AdversarialTactic::NetworkPartition => "timeout simulation",
108 AdversarialTactic::ConfigFuzzing => "proptest",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, Default)]
115pub struct CheckedArithmetic;
116
117impl CheckedArithmetic {
118 pub fn new() -> Self {
120 Self
121 }
122
123 pub fn checked_add_i64(a: i64, b: i64) -> AdversarialResult<i64> {
125 a.checked_add(b)
126 .ok_or_else(|| AdversarialError::IntegerOverflow {
127 operation: format!("{a} + {b}"),
128 })
129 }
130
131 pub fn checked_mul_i64(a: i64, b: i64) -> AdversarialResult<i64> {
133 a.checked_mul(b)
134 .ok_or_else(|| AdversarialError::IntegerOverflow {
135 operation: format!("{a} * {b}"),
136 })
137 }
138
139 pub fn checked_add_usize(a: usize, b: usize) -> AdversarialResult<usize> {
141 a.checked_add(b)
142 .ok_or_else(|| AdversarialError::IntegerOverflow {
143 operation: format!("{a} + {b}"),
144 })
145 }
146
147 pub fn checked_mul_usize(a: usize, b: usize) -> AdversarialResult<usize> {
149 a.checked_mul(b)
150 .ok_or_else(|| AdversarialError::IntegerOverflow {
151 operation: format!("{a} * {b}"),
152 })
153 }
154
155 pub fn checked_div_f64(a: f64, b: f64) -> AdversarialResult<f64> {
157 if b == 0.0 {
158 return Err(AdversarialError::DivisionByZero { numerator: a });
159 }
160 Ok(a / b)
161 }
162
163 pub fn checked_div_i64(a: i64, b: i64) -> AdversarialResult<i64> {
165 if b == 0 {
166 return Err(AdversarialError::DivisionByZero {
167 numerator: a as f64,
168 });
169 }
170 Ok(a / b)
171 }
172}
173
174#[derive(Debug, Clone)]
176pub struct MonotonicClock {
177 last_timestamp: Option<Instant>,
178}
179
180impl Default for MonotonicClock {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186impl MonotonicClock {
187 pub fn new() -> Self {
189 Self {
190 last_timestamp: None,
191 }
192 }
193
194 pub fn tick(&mut self) -> AdversarialResult<Instant> {
196 let now = Instant::now();
197
198 if let Some(prev) = self.last_timestamp {
199 if now < prev {
202 return Err(AdversarialError::ClockSkew { prev, curr: now });
203 }
204 }
205
206 self.last_timestamp = Some(now);
207 Ok(now)
208 }
209
210 pub fn elapsed(&self) -> Option<Duration> {
212 self.last_timestamp.map(|t| t.elapsed())
213 }
214
215 pub fn reset(&mut self) {
217 self.last_timestamp = None;
218 }
219}
220
221#[derive(Debug, Clone, Copy)]
223pub struct ResourceUsage {
224 pub stack_depth: usize,
226 pub memory_bytes: usize,
228 pub elapsed: Option<Duration>,
230}
231
232#[derive(Debug, Clone)]
234pub struct AdversarialTestSummary {
235 pub total_tests: usize,
237 pub passed: usize,
239 pub failed: usize,
241 pub tactics_tested: Vec<AdversarialTactic>,
243 pub errors_handled: Vec<String>,
245}
246
247impl AdversarialTestSummary {
248 pub fn new() -> Self {
250 Self {
251 total_tests: 0,
252 passed: 0,
253 failed: 0,
254 tactics_tested: Vec::new(),
255 errors_handled: Vec::new(),
256 }
257 }
258
259 pub fn record_pass(&mut self, tactic: AdversarialTactic) {
261 self.total_tests += 1;
262 self.passed += 1;
263 if !self.tactics_tested.contains(&tactic) {
264 self.tactics_tested.push(tactic);
265 }
266 }
267
268 pub fn record_fail(&mut self, tactic: AdversarialTactic, reason: &str) {
270 self.total_tests += 1;
271 self.failed += 1;
272 if !self.tactics_tested.contains(&tactic) {
273 self.tactics_tested.push(tactic);
274 }
275 self.errors_handled.push(reason.to_string());
276 }
277
278 pub fn record_error_handled(&mut self, error: &AdversarialError) {
280 self.errors_handled.push(format!("{error:?}"));
281 }
282
283 pub fn pass_rate(&self) -> f64 {
285 if self.total_tests == 0 {
286 return 0.0;
287 }
288 (self.passed as f64) / (self.total_tests as f64) * 100.0
289 }
290
291 pub fn all_passed(&self) -> bool {
293 self.failed == 0 && self.total_tests > 0
294 }
295}
296
297impl Default for AdversarialTestSummary {
298 fn default() -> Self {
299 Self::new()
300 }
301}