Skip to main content

embeddenator_testkit/
chaos.rs

1//! Chaos injection and resilience testing utilities
2//!
3//! Provides tools for testing system resilience:
4//! - Random bitflip injection on byte data
5//! - Packet loss simulation
6//! - Corruption simulation
7//! - Noise tolerance testing
8
9/// Chaos injection utilities for resilience testing
10pub struct ChaosInjector {
11    /// Random seed for reproducibility
12    seed: u64,
13    /// Injection probability (0.0 - 1.0)
14    probability: f64,
15}
16
17impl ChaosInjector {
18    /// Create new chaos injector with seed
19    pub fn new(seed: u64) -> Self {
20        Self {
21            seed,
22            probability: 0.01, // 1% default
23        }
24    }
25
26    /// Set injection probability
27    pub fn with_probability(mut self, p: f64) -> Self {
28        self.probability = p.clamp(0.0, 1.0);
29        self
30    }
31
32    /// Inject random noise into byte data
33    ///
34    /// # Arguments
35    /// * `data` - Data to corrupt (modified in place)
36    /// * `error_rate` - Fraction of bits to flip (0.0-1.0)
37    pub fn corrupt_bytes(&self, data: &mut [u8], error_rate: f64) {
38        let mut state = self.seed;
39        let num_errors = ((data.len() as f64) * error_rate) as usize;
40
41        for _ in 0..num_errors {
42            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
43            let pos = (state as usize) % data.len();
44            let bit = (state >> 8) % 8;
45            data[pos] ^= 1u8 << bit;
46        }
47    }
48
49    /// Create corrupted copy of byte data
50    pub fn corrupt_copy(&self, data: &[u8], error_rate: f64) -> Vec<u8> {
51        let mut corrupted = data.to_vec();
52        self.corrupt_bytes(&mut corrupted, error_rate);
53        corrupted
54    }
55
56    /// Simulate packet loss by erasing random chunks
57    ///
58    /// # Arguments
59    /// * `data` - Data to corrupt (modified in place)
60    /// * `loss_rate` - Fraction of packets to drop (0.0-1.0)
61    /// * `packet_size` - Size of each packet in bytes
62    pub fn simulate_packet_loss(&self, data: &mut [u8], loss_rate: f64, packet_size: usize) {
63        use std::collections::HashSet;
64
65        let num_packets = data.len().div_ceil(packet_size);
66        let packets_to_drop = ((num_packets as f64) * loss_rate) as usize;
67
68        let mut state = self.seed;
69        let mut dropped = HashSet::new();
70
71        for _ in 0..packets_to_drop {
72            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
73            let packet_idx = (state as usize) % num_packets;
74            dropped.insert(packet_idx);
75        }
76
77        for packet_idx in dropped {
78            let start = packet_idx * packet_size;
79            let end = (start + packet_size).min(data.len());
80            data[start..end].fill(0);
81        }
82    }
83
84    /// Inject random erasures (zero out bytes)
85    pub fn inject_erasures(&self, data: &mut [u8], count: usize) -> Vec<usize> {
86        let mut erased = Vec::new();
87        let mut state = self.seed.wrapping_add(12345);
88
89        for _ in 0..count.min(data.len()) {
90            state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
91            let pos = (state as usize) % data.len();
92
93            if data[pos] != 0 {
94                data[pos] = 0;
95                erased.push(pos);
96            }
97        }
98
99        erased
100    }
101}
102
103impl Default for ChaosInjector {
104    fn default() -> Self {
105        Self::new(0)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_corrupt_bytes() {
115        let mut data = vec![0u8; 100];
116        let injector = ChaosInjector::new(42);
117
118        injector.corrupt_bytes(&mut data, 0.1);
119
120        let corrupted_count = data.iter().filter(|&&b| b != 0).count();
121        assert!(corrupted_count > 0);
122    }
123
124    #[test]
125    fn test_corrupt_copy() {
126        let data = vec![0xFF; 100];
127        let injector = ChaosInjector::new(42);
128
129        let corrupted = injector.corrupt_copy(&data, 0.1);
130
131        // Original unchanged
132        assert!(data.iter().all(|&b| b == 0xFF));
133
134        // Corrupted is different
135        assert_ne!(data, corrupted);
136    }
137
138    #[test]
139    fn test_simulate_packet_loss() {
140        let mut data = vec![0xFF; 100];
141        let injector = ChaosInjector::new(42);
142
143        injector.simulate_packet_loss(&mut data, 0.2, 10); // 20% loss, 10 byte packets
144
145        let zero_count = data.iter().filter(|&&b| b == 0).count();
146        assert!(zero_count > 0);
147    }
148
149    #[test]
150    fn test_inject_erasures() {
151        let mut data = vec![0xFF; 100];
152        let injector = ChaosInjector::new(42);
153
154        let erased = injector.inject_erasures(&mut data, 10);
155
156        assert!(erased.len() <= 10);
157
158        // Check that erased positions are now zero
159        for &pos in &erased {
160            assert_eq!(data[pos], 0);
161        }
162    }
163
164    #[test]
165    fn test_determinism() {
166        let data = vec![0xFF; 100];
167
168        let injector1 = ChaosInjector::new(42);
169        let corrupted1 = injector1.corrupt_copy(&data, 0.1);
170
171        let injector2 = ChaosInjector::new(42);
172        let corrupted2 = injector2.corrupt_copy(&data, 0.1);
173
174        assert_eq!(corrupted1, corrupted2);
175    }
176}