mockforge_chaos/
latency.rs

1//! Latency injection for simulating network delays
2
3use crate::config::LatencyConfig;
4use rand::Rng;
5use std::time::Duration;
6use tokio::time::sleep;
7use tracing::debug;
8
9/// Latency injector for simulating network delays
10#[derive(Clone)]
11pub struct LatencyInjector {
12    config: LatencyConfig,
13}
14
15impl LatencyInjector {
16    /// Create a new latency injector
17    pub fn new(config: LatencyConfig) -> Self {
18        Self { config }
19    }
20
21    /// Check if latency injection is enabled
22    pub fn is_enabled(&self) -> bool {
23        self.config.enabled
24    }
25
26    /// Inject latency based on configuration
27    /// Returns the delay amount in milliseconds that was injected (0 if no delay was injected)
28    pub async fn inject(&self) -> u64 {
29        if !self.config.enabled {
30            return 0;
31        }
32
33        // Check probability
34        let mut rng = rand::rng();
35        if rng.random::<f64>() > self.config.probability {
36            return 0;
37        }
38
39        let delay_ms = self.calculate_delay();
40        if delay_ms > 0 {
41            debug!("Injecting latency: {}ms", delay_ms);
42            sleep(Duration::from_millis(delay_ms)).await;
43        }
44
45        delay_ms
46    }
47
48    /// Calculate the delay in milliseconds
49    fn calculate_delay(&self) -> u64 {
50        let mut rng = rand::rng();
51
52        // Base delay
53        let base_delay = if let Some(fixed) = self.config.fixed_delay_ms {
54            fixed
55        } else if let Some((min, max)) = self.config.random_delay_range_ms {
56            rng.random_range(min..=max)
57        } else {
58            0
59        };
60
61        // Apply jitter
62        if self.config.jitter_percent > 0.0 {
63            let jitter = (base_delay as f64 * self.config.jitter_percent / 100.0) as u64;
64            let jitter_offset = rng.random_range(0..=jitter);
65            if rng.random_bool(0.5) {
66                base_delay + jitter_offset
67            } else {
68                base_delay.saturating_sub(jitter_offset)
69            }
70        } else {
71            base_delay
72        }
73    }
74
75    /// Get configuration
76    pub fn config(&self) -> &LatencyConfig {
77        &self.config
78    }
79
80    /// Update configuration
81    pub fn update_config(&mut self, config: LatencyConfig) {
82        self.config = config;
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_calculate_fixed_delay() {
92        let config = LatencyConfig {
93            enabled: true,
94            fixed_delay_ms: Some(100),
95            random_delay_range_ms: None,
96            jitter_percent: 0.0,
97            probability: 1.0,
98        };
99
100        let injector = LatencyInjector::new(config);
101        let delay = injector.calculate_delay();
102        assert_eq!(delay, 100);
103    }
104
105    #[test]
106    fn test_calculate_random_delay() {
107        let config = LatencyConfig {
108            enabled: true,
109            fixed_delay_ms: None,
110            random_delay_range_ms: Some((50, 150)),
111            jitter_percent: 0.0,
112            probability: 1.0,
113        };
114
115        let injector = LatencyInjector::new(config);
116        for _ in 0..100 {
117            let delay = injector.calculate_delay();
118            assert!((50..=150).contains(&delay));
119        }
120    }
121
122    #[test]
123    fn test_jitter() {
124        let config = LatencyConfig {
125            enabled: true,
126            fixed_delay_ms: Some(100),
127            random_delay_range_ms: None,
128            jitter_percent: 10.0, // 10% jitter = +/- 10ms
129            probability: 1.0,
130        };
131
132        let injector = LatencyInjector::new(config);
133        for _ in 0..100 {
134            let delay = injector.calculate_delay();
135            // Should be within 90-110ms range
136            assert!((90..=110).contains(&delay));
137        }
138    }
139
140    #[tokio::test]
141    async fn test_inject_latency() {
142        let config = LatencyConfig {
143            enabled: true,
144            fixed_delay_ms: Some(10),
145            random_delay_range_ms: None,
146            jitter_percent: 0.0,
147            probability: 1.0,
148        };
149
150        let injector = LatencyInjector::new(config);
151        let start = std::time::Instant::now();
152        let delay_ms = injector.inject().await;
153        let elapsed = start.elapsed();
154
155        // Should have delayed at least 10ms
156        assert!(elapsed >= Duration::from_millis(10));
157        // Should return the delay amount
158        assert_eq!(delay_ms, 10);
159    }
160
161    #[tokio::test]
162    async fn test_probability() {
163        let config = LatencyConfig {
164            enabled: true,
165            fixed_delay_ms: Some(10),
166            random_delay_range_ms: None,
167            jitter_percent: 0.0,
168            probability: 0.0, // Never inject
169        };
170
171        let injector = LatencyInjector::new(config);
172        let start = std::time::Instant::now();
173        let delay_ms = injector.inject().await;
174        let elapsed = start.elapsed();
175
176        // Should not have delayed
177        assert!(elapsed < Duration::from_millis(5));
178        // Should return 0 when probability prevents injection
179        assert_eq!(delay_ms, 0);
180    }
181}