embeddenator_testkit/
chaos.rs1pub struct ChaosInjector {
11 seed: u64,
13 probability: f64,
15}
16
17impl ChaosInjector {
18 pub fn new(seed: u64) -> Self {
20 Self {
21 seed,
22 probability: 0.01, }
24 }
25
26 pub fn with_probability(mut self, p: f64) -> Self {
28 self.probability = p.clamp(0.0, 1.0);
29 self
30 }
31
32 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 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 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 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 assert!(data.iter().all(|&b| b == 0xFF));
133
134 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); 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 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}