Skip to main content

nxtquic_sim/
network.rs

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