Skip to main content

nxtquic_sim/
network.rs

1//! Network simulation engine with configurable latency, loss, jitter, reordering, and bandwidth.
2
3use crate::channel::{QueuedDatagram, SimChannel};
4use rand::{rng, Rng};
5use std::net::SocketAddr;
6use std::time::{Duration, Instant};
7
8/// Configuration for the simulated network conditions.
9#[derive(Debug, Clone)]
10pub struct NetworkConfig {
11    /// Base latency for packets.
12    pub latency: Duration,
13    /// Maximum additional random delay added to packets.
14    pub jitter: Duration,
15    /// Probability of a packet being dropped (0.0 to 1.0).
16    pub loss_rate: f64,
17    /// Probability of entering or continuing a burst loss state (0.0 to 1.0).
18    pub burst_loss_prob: f64,
19    /// Probability of a packet being delayed extra to cause reordering (0.0 to 1.0).
20    pub reorder_rate: f64,
21    /// Probability of a packet being duplicated (0.0 to 1.0).
22    pub duplicate_prob: f64,
23    /// Maximum bandwidth in bytes per second (for transmission rate delay calculation).
24    pub bandwidth: Option<u64>,
25    /// Probability of a NAT rebinding occurring, changing the source port.
26    pub nat_rebind_prob: f64,
27}
28
29impl Default for NetworkConfig {
30    fn default() -> Self {
31        Self {
32            latency: Duration::from_millis(10),
33            jitter: Duration::from_millis(0),
34            loss_rate: 0.0,
35            burst_loss_prob: 0.0,
36            reorder_rate: 0.0,
37            duplicate_prob: 0.0,
38            bandwidth: None,
39            nat_rebind_prob: 0.0,
40        }
41    }
42}
43
44/// A simulated network that models latency, jitter, loss, duplication, and reordering.
45pub struct SimulatedNetwork {
46    config: NetworkConfig,
47    channel: SimChannel,
48    in_burst_loss: bool,
49    current_src_port_offset: u16,
50}
51
52impl SimulatedNetwork {
53    /// Creates a new `SimulatedNetwork` with the given configuration.
54    pub fn new(config: NetworkConfig) -> Self {
55        Self {
56            config,
57            channel: SimChannel::new(),
58            in_burst_loss: false,
59            current_src_port_offset: 0,
60        }
61    }
62
63    /// Returns a reference to the active network configuration.
64    pub fn config(&self) -> &NetworkConfig {
65        &self.config
66    }
67
68    /// Mutably updates the network configuration during simulation.
69    pub fn config_mut(&mut self) -> &mut NetworkConfig {
70        &mut self.config
71    }
72
73    /// Sends a payload through the simulated network.
74    pub fn send(&mut self, payload: Vec<u8>, mut src: SocketAddr, dst: SocketAddr, now: Instant) {
75        let mut rng = rng();
76
77        // 1. Packet Loss simulation
78        if self.in_burst_loss {
79            if rng.random::<f64>() > self.config.burst_loss_prob {
80                self.in_burst_loss = false;
81            } else {
82                return; // Dropped due to burst loss
83            }
84        } else if rng.random::<f64>() < self.config.loss_rate {
85            if rng.random::<f64>() < self.config.burst_loss_prob {
86                self.in_burst_loss = true;
87            }
88            return; // Dropped due to random loss
89        }
90
91        // 2. Latency and Jitter simulation
92        let mut delay = self.config.latency;
93        if self.config.jitter > Duration::ZERO {
94            let jitter_ms = rng.random_range(0..=self.config.jitter.as_millis() as u64);
95            delay += Duration::from_millis(jitter_ms);
96        }
97
98        // 3. Bandwidth throttle simulation
99        if let Some(bw) = self.config.bandwidth {
100            if bw > 0 {
101                let tx_nanos = (payload.len() as u64 * 1_000_000_000) / bw;
102                delay += Duration::from_nanos(tx_nanos);
103            }
104        }
105
106        // 4. Reordering simulation (by adding an extra delay)
107        if rng.random::<f64>() < self.config.reorder_rate {
108            delay += Duration::from_millis(rng.random_range(10..50));
109        }
110
111        // 5. NAT rebinding simulation
112        if rng.random::<f64>() < self.config.nat_rebind_prob {
113            self.current_src_port_offset = self.current_src_port_offset.wrapping_add(1);
114        }
115
116        src.set_port(src.port().wrapping_add(self.current_src_port_offset));
117
118        let delivery_time = now + delay;
119
120        // 6. Packet Duplication simulation
121        if rng.random::<f64>() < self.config.duplicate_prob {
122            self.channel.push(QueuedDatagram {
123                delivery_time: delivery_time + Duration::from_millis(1),
124                payload: payload.clone(),
125                src,
126                dst,
127            });
128        }
129
130        self.channel.push(QueuedDatagram {
131            delivery_time,
132            payload,
133            src,
134            dst,
135        });
136    }
137
138    /// Receives a datagram from the network if it's ready.
139    pub fn receive(&mut self, now: Instant) -> Option<QueuedDatagram> {
140        self.channel.pop_ready(now)
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_latency_delay() {
150        let mut net = SimulatedNetwork::new(NetworkConfig {
151            latency: Duration::from_millis(50),
152            ..Default::default()
153        });
154
155        let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
156        let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
157        let now = Instant::now();
158
159        net.send(vec![1, 2, 3], src, dst, now);
160
161        assert!(net.receive(now).is_none());
162        assert!(net.receive(now + Duration::from_millis(20)).is_none());
163        assert!(net.receive(now + Duration::from_millis(50)).is_some());
164    }
165
166    #[test]
167    fn test_packet_loss_dropping() {
168        let mut net = SimulatedNetwork::new(NetworkConfig {
169            loss_rate: 1.0,
170            ..Default::default()
171        });
172
173        let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
174        let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
175        let now = Instant::now();
176
177        net.send(vec![1, 2, 3], src, dst, now);
178
179        assert!(net.receive(now + Duration::from_secs(10)).is_none());
180    }
181
182    #[test]
183    fn test_reordering() {
184        let mut net = SimulatedNetwork::new(NetworkConfig {
185            latency: Duration::from_millis(10),
186            reorder_rate: 1.0,
187            ..Default::default()
188        });
189
190        let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
191        let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
192        let now = Instant::now();
193
194        net.send(vec![1], src, dst, now);
195
196        assert!(net.receive(now + Duration::from_millis(10)).is_none());
197        assert!(net.receive(now + Duration::from_millis(100)).is_some());
198    }
199}