Skip to main content

cbtop/adversarial/
types.rs

1//! Types, enums, and small helper structs for adversarial testing.
2
3use std::time::{Duration, Instant};
4
5/// Error types for adversarial testing
6#[derive(Debug, Clone, PartialEq)]
7pub enum AdversarialError {
8    /// Input tensor contains corrupted data
9    CorruptedInput {
10        byte_index: usize,
11        expected_checksum: u32,
12        actual_checksum: u32,
13    },
14    /// Memory allocation failed under pressure
15    AllocationFailed { requested_bytes: usize },
16    /// Zero-size input detected
17    ZeroSizeInput,
18    /// Input exceeds maximum allowed size
19    MaxSizeExceeded { size: usize, max: usize },
20    /// Clock skew detected (non-monotonic timestamp)
21    ClockSkew { prev: Instant, curr: Instant },
22    /// Concurrent access violation detected
23    RaceCondition { description: String },
24    /// Configuration parsing failed
25    ConfigParseError { field: String, reason: String },
26    /// Configuration value out of bounds
27    ConfigOutOfBounds {
28        field: String,
29        value: String,
30        min: String,
31        max: String,
32    },
33    /// Integer overflow detected
34    IntegerOverflow { operation: String },
35    /// Division by zero attempted
36    DivisionByZero { numerator: f64 },
37    /// NaN value detected in input
38    NaNDetected { index: usize },
39    /// Infinity value detected in input
40    InfinityDetected { index: usize, positive: bool },
41    /// Stack depth exceeded
42    StackOverflow { depth: usize, max_depth: usize },
43    /// Resource exhaustion (memory, handles, etc.)
44    ResourceExhausted { resource: String },
45    /// Operation timed out
46    Timeout {
47        operation: String,
48        elapsed: Duration,
49        limit: Duration,
50    },
51    /// Operation was cancelled
52    Cancelled { operation: String },
53    /// Recovery failed after error
54    RecoveryFailed {
55        original_error: String,
56        recovery_error: String,
57    },
58}
59
60/// Result type for adversarial operations
61pub type AdversarialResult<T> = Result<T, AdversarialError>;
62
63/// Adversarial tactic category
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
65pub enum AdversarialTactic {
66    /// Random bit flips in input data
67    BitFlipInjection,
68    /// Simulate memory/CPU pressure
69    ResourceStarvation,
70    /// Test with manipulated timestamps
71    ClockSkew,
72    /// Simulate network failures
73    NetworkPartition,
74    /// Generate pathological configurations
75    ConfigFuzzing,
76}
77
78impl AdversarialTactic {
79    /// Get all tactics
80    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    /// Get the tactic name
91    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    /// Get the tool used for this tactic
102    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/// Numeric operations with overflow/underflow detection
114#[derive(Debug, Clone, Copy, Default)]
115pub struct CheckedArithmetic;
116
117impl CheckedArithmetic {
118    /// Create a new checked arithmetic helper
119    pub fn new() -> Self {
120        Self
121    }
122
123    /// Add with overflow check (F1012)
124    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    /// Multiply with overflow check (F1012)
132    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    /// Add with overflow check for usize
140    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    /// Multiply with overflow check for usize
148    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    /// Division with zero check (F1013)
156    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    /// Division with zero check for integers
164    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/// Monotonic timestamp tracker (F1006)
175#[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    /// Create a new monotonic clock tracker
188    pub fn new() -> Self {
189        Self {
190            last_timestamp: None,
191        }
192    }
193
194    /// Record a timestamp and verify monotonicity
195    pub fn tick(&mut self) -> AdversarialResult<Instant> {
196        let now = Instant::now();
197
198        if let Some(prev) = self.last_timestamp {
199            // In Rust, Instant is guaranteed monotonic, but we check anyway
200            // for systems with unreliable clock sources
201            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    /// Get elapsed time since last tick
211    pub fn elapsed(&self) -> Option<Duration> {
212        self.last_timestamp.map(|t| t.elapsed())
213    }
214
215    /// Reset the clock
216    pub fn reset(&mut self) {
217        self.last_timestamp = None;
218    }
219}
220
221/// Current resource usage statistics
222#[derive(Debug, Clone, Copy)]
223pub struct ResourceUsage {
224    /// Current recursion depth
225    pub stack_depth: usize,
226    /// Current memory allocated
227    pub memory_bytes: usize,
228    /// Elapsed time since operation start
229    pub elapsed: Option<Duration>,
230}
231
232/// Summary of adversarial test results
233#[derive(Debug, Clone)]
234pub struct AdversarialTestSummary {
235    /// Total tests run
236    pub total_tests: usize,
237    /// Tests that passed (handled adversarial input correctly)
238    pub passed: usize,
239    /// Tests that failed (panicked or incorrect behavior)
240    pub failed: usize,
241    /// Tactics tested
242    pub tactics_tested: Vec<AdversarialTactic>,
243    /// Errors encountered (expected - means system handled correctly)
244    pub errors_handled: Vec<String>,
245}
246
247impl AdversarialTestSummary {
248    /// Create a new empty summary
249    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    /// Record a passing test
260    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    /// Record a failing test
269    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    /// Record a handled error (this is good - system detected the adversarial input)
279    pub fn record_error_handled(&mut self, error: &AdversarialError) {
280        self.errors_handled.push(format!("{error:?}"));
281    }
282
283    /// Get pass rate
284    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    /// Check if all tests passed
292    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}