use crate::channel::{QueuedDatagram, SimChannel};
use rand::{rng, Rng};
use std::net::SocketAddr;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct NetworkConfig {
pub latency: Duration,
pub jitter: Duration,
pub loss_rate: f64,
pub burst_loss_prob: f64,
pub reorder_rate: f64,
pub duplicate_prob: f64,
pub bandwidth: Option<u64>,
pub nat_rebind_prob: f64,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
latency: Duration::from_millis(10),
jitter: Duration::from_millis(0),
loss_rate: 0.0,
burst_loss_prob: 0.0,
reorder_rate: 0.0,
duplicate_prob: 0.0,
bandwidth: None,
nat_rebind_prob: 0.0,
}
}
}
pub struct SimulatedNetwork {
config: NetworkConfig,
channel: SimChannel,
in_burst_loss: bool,
current_src_port_offset: u16,
}
impl SimulatedNetwork {
pub fn new(config: NetworkConfig) -> Self {
Self {
config,
channel: SimChannel::new(),
in_burst_loss: false,
current_src_port_offset: 0,
}
}
pub fn config(&self) -> &NetworkConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut NetworkConfig {
&mut self.config
}
pub fn send(&mut self, payload: Vec<u8>, mut src: SocketAddr, dst: SocketAddr, now: Instant) {
let mut rng = rng();
if self.in_burst_loss {
if rng.random::<f64>() > self.config.burst_loss_prob {
self.in_burst_loss = false;
} else {
return; }
} else if rng.random::<f64>() < self.config.loss_rate {
if rng.random::<f64>() < self.config.burst_loss_prob {
self.in_burst_loss = true;
}
return; }
let mut delay = self.config.latency;
if self.config.jitter > Duration::ZERO {
let jitter_ms = rng.random_range(0..=self.config.jitter.as_millis() as u64);
delay += Duration::from_millis(jitter_ms);
}
if let Some(bw) = self.config.bandwidth {
if bw > 0 {
let tx_nanos = (payload.len() as u64 * 1_000_000_000) / bw;
delay += Duration::from_nanos(tx_nanos);
}
}
if rng.random::<f64>() < self.config.reorder_rate {
delay += Duration::from_millis(rng.random_range(10..50));
}
if rng.random::<f64>() < self.config.nat_rebind_prob {
self.current_src_port_offset = self.current_src_port_offset.wrapping_add(1);
}
src.set_port(src.port().wrapping_add(self.current_src_port_offset));
let delivery_time = now + delay;
if rng.random::<f64>() < self.config.duplicate_prob {
self.channel.push(QueuedDatagram {
delivery_time: delivery_time + Duration::from_millis(1),
payload: payload.clone(),
src,
dst,
});
}
self.channel.push(QueuedDatagram {
delivery_time,
payload,
src,
dst,
});
}
pub fn receive(&mut self, now: Instant) -> Option<QueuedDatagram> {
self.channel.pop_ready(now)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_latency_delay() {
let mut net = SimulatedNetwork::new(NetworkConfig {
latency: Duration::from_millis(50),
..Default::default()
});
let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
let now = Instant::now();
net.send(vec![1, 2, 3], src, dst, now);
assert!(net.receive(now).is_none());
assert!(net.receive(now + Duration::from_millis(20)).is_none());
assert!(net.receive(now + Duration::from_millis(50)).is_some());
}
#[test]
fn test_packet_loss_dropping() {
let mut net = SimulatedNetwork::new(NetworkConfig {
loss_rate: 1.0,
..Default::default()
});
let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
let now = Instant::now();
net.send(vec![1, 2, 3], src, dst, now);
assert!(net.receive(now + Duration::from_secs(10)).is_none());
}
#[test]
fn test_reordering() {
let mut net = SimulatedNetwork::new(NetworkConfig {
latency: Duration::from_millis(10),
reorder_rate: 1.0,
..Default::default()
});
let src: SocketAddr = "127.0.0.1:1000".parse().unwrap();
let dst: SocketAddr = "127.0.0.1:2000".parse().unwrap();
let now = Instant::now();
net.send(vec![1], src, dst, now);
assert!(net.receive(now + Duration::from_millis(10)).is_none());
assert!(net.receive(now + Duration::from_millis(100)).is_some());
}
}