use std::collections::HashMap;
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use rand::rngs::StdRng;
use rand::Rng;
mod transport;
pub use transport::{Impairments, NetemTransport};
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Probability(f64);
impl Probability {
pub const ZERO: Probability = Probability(0.0);
pub const ALWAYS: Probability = Probability(1.0);
pub fn percent(percent: f64) -> Probability {
Probability((percent.max(0.0) / 100.0).min(1.0))
}
pub fn as_fraction(self) -> f64 {
self.0
}
pub fn label(self) -> String {
let tenths = (self.0 * 1_000.0).round() as u64;
if tenths % 10 == 0 {
format!("loss={}%", tenths / 10)
} else {
format!("loss={}.{}%", tenths / 10, tenths % 10)
}
}
fn hits(self, rng: &mut StdRng) -> bool {
rng.gen_bool(self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Rtt(Duration);
impl Rtt {
pub const ZERO: Rtt = Rtt(Duration::ZERO);
pub fn from_millis(millis: f64) -> Rtt {
Rtt(Duration::from_secs_f64(millis.max(0.0) / 1_000.0))
}
pub fn one_way(self) -> Duration {
self.0 / 2
}
pub fn label(self) -> String {
let micros = self.0.as_micros();
let (millis, tenths) = (micros / 1_000, (micros % 1_000) / 100);
if tenths == 0 {
format!("rtt={millis}ms")
} else {
format!("rtt={millis}.{tenths}ms")
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Seed(u64);
impl Seed {
pub const DEFAULT: Seed = Seed(0x5eed_0280);
pub fn new(seed: u64) -> Seed {
Seed(seed)
}
pub fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug)]
pub struct Link {
delay: Duration,
jitter: Duration,
loss: Probability,
reorder: Probability,
}
impl Link {
pub const PERFECT: Link = Link {
delay: Duration::ZERO,
jitter: Duration::ZERO,
loss: Probability::ZERO,
reorder: Probability::ZERO,
};
pub fn at(rtt: Rtt) -> Link {
Link {
delay: rtt.one_way(),
..Link::PERFECT
}
}
pub fn with_jitter(mut self, jitter: Duration) -> Link {
self.jitter = jitter;
self
}
pub fn with_loss(mut self, loss: Probability) -> Link {
self.loss = loss;
self
}
pub fn with_reorder(mut self, reorder: Probability) -> Link {
self.reorder = reorder;
self
}
fn draw(self, rng: &mut StdRng) -> Option<Duration> {
let lost = self.loss.hits(rng);
let swing: f64 = rng.gen_range(-1.0..=1.0);
let reordered = self.reorder.hits(rng);
if lost {
return None;
}
let offset = self.jitter.mul_f64(swing.abs());
let jittered = if swing < 0.0 {
self.delay.saturating_sub(offset)
} else {
self.delay + offset
};
Some(if reordered {
jittered + self.delay
} else {
jittered
})
}
}
#[derive(Clone, Debug)]
pub struct Netem {
default_link: Link,
per_destination: HashMap<IpAddr, Link>,
seed: Seed,
}
impl Netem {
pub fn uniform(link: Link, seed: Seed) -> Netem {
Netem {
default_link: link,
per_destination: HashMap::new(),
seed,
}
}
pub fn with_link_to(mut self, destination: IpAddr, link: Link) -> Netem {
self.per_destination.insert(destination, link);
self
}
fn link_to(&self, destination: &SocketAddr) -> Link {
self.per_destination
.get(&destination.ip())
.copied()
.unwrap_or(self.default_link)
}
}
fn stream_seed(seed: Seed, source: SocketAddr, destination: SocketAddr) -> u64 {
fn mix(state: &mut u64, value: u64) {
*state = state
.wrapping_add(value)
.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = *state;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
*state = z ^ (z >> 31);
}
fn endpoint(state: &mut u64, addr: SocketAddr) {
match addr.ip() {
IpAddr::V4(v4) => mix(state, u32::from(v4) as u64),
IpAddr::V6(v6) => {
let bits = u128::from(v6);
mix(state, (bits >> 64) as u64);
mix(state, bits as u64);
}
}
mix(state, addr.port() as u64);
}
let mut state = seed.get();
endpoint(&mut state, source);
endpoint(&mut state, destination);
state
}