use std::time::Duration;
pub use str0m_proto::{Bitrate, DataSize};
#[derive(Debug, Clone, Copy)]
pub struct NetemConfig {
pub(crate) latency: Duration,
pub(crate) jitter: Duration,
pub(crate) delay_correlation: Probability,
pub(crate) loss: LossModel,
pub(crate) duplicate: Probability,
pub(crate) reorder_gap: Option<u32>,
pub(crate) link: Option<Link>,
pub(crate) seed: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct Link {
pub(crate) rate: Bitrate,
pub(crate) buffer: DataSize,
}
impl Default for NetemConfig {
fn default() -> Self {
Self::new()
}
}
impl NetemConfig {
pub const fn new() -> Self {
Self {
latency: Duration::ZERO,
jitter: Duration::ZERO,
delay_correlation: Probability::ZERO,
loss: LossModel::None,
duplicate: Probability::ZERO,
reorder_gap: None,
link: None,
seed: 0,
}
}
pub fn wifi() -> Self {
Self::new()
.latency(Duration::from_millis(5))
.jitter(Duration::from_millis(2))
.loss(GilbertElliot::wifi())
.link(Bitrate::mbps(100), DataSize::kbytes(200))
}
pub fn wifi_lossy() -> Self {
Self::new()
.latency(Duration::from_millis(15))
.jitter(Duration::from_millis(10))
.loss(GilbertElliot::wifi_lossy())
.link(Bitrate::mbps(50), DataSize::kbytes(100))
}
pub fn wifi_congested() -> Self {
Self::new()
.latency(Duration::from_millis(10))
.jitter(Duration::from_millis(20))
.loss(GilbertElliot::congested())
.link(Bitrate::mbps(5), DataSize::kbytes(30))
}
pub fn cellular() -> Self {
Self::new()
.latency(Duration::from_millis(50))
.jitter(Duration::from_millis(15))
.loss(GilbertElliot::cellular())
.link(Bitrate::mbps(30), DataSize::kbytes(100))
}
pub fn satellite() -> Self {
Self::new()
.latency(Duration::from_millis(600))
.jitter(Duration::from_millis(30))
.loss(GilbertElliot::satellite())
.link(Bitrate::mbps(15), DataSize::kbytes(500))
}
pub fn congested() -> Self {
Self::new()
.latency(Duration::from_millis(80))
.jitter(Duration::from_millis(40))
.loss(GilbertElliot::congested())
.link(Bitrate::mbps(10), DataSize::kbytes(50))
}
pub fn latency(mut self, latency: Duration) -> Self {
self.latency = latency;
self
}
pub fn jitter(mut self, jitter: Duration) -> Self {
self.jitter = jitter;
self
}
pub fn delay_correlation(mut self, correlation: Probability) -> Self {
self.delay_correlation = correlation;
self
}
pub fn loss(mut self, loss: impl Into<LossModel>) -> Self {
self.loss = loss.into();
self
}
pub fn duplicate(mut self, probability: Probability) -> Self {
self.duplicate = probability;
self
}
pub fn reorder_gap(mut self, gap: u32) -> Self {
self.reorder_gap = Some(gap);
self
}
pub fn link(mut self, rate: Bitrate, buffer: DataSize) -> Self {
self.link = Some(Link { rate, buffer });
self
}
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Probability(pub f32);
impl Probability {
pub const ZERO: Probability = Probability(0.0);
pub const ONE: Probability = Probability(1.0);
pub fn new(value: f32) -> Self {
debug_assert!(
(0.0..=1.0).contains(&value),
"Probability must be in 0.0..=1.0"
);
Probability(value.clamp(0.0, 1.0))
}
pub fn value(self) -> f32 {
self.0
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum LossModel {
#[default]
None,
Random(RandomLoss),
GilbertElliot(GilbertElliot),
}
#[derive(Debug, Clone, Copy)]
pub struct RandomLoss {
pub(crate) probability: f32,
pub(crate) correlation: f32,
}
impl RandomLoss {
pub fn new(probability: Probability) -> Self {
Self {
probability: probability.0,
correlation: 0.0,
}
}
pub fn correlation(mut self, correlation: Probability) -> Self {
self.correlation = correlation.0;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct GilbertElliot {
pub(crate) p: f32,
pub(crate) r: f32,
pub(crate) h: f32,
pub(crate) k: f32,
}
impl Default for GilbertElliot {
fn default() -> Self {
Self::new()
}
}
impl GilbertElliot {
pub fn new() -> Self {
Self {
p: 0.0, r: 1.0, h: 1.0, k: 0.0, }
}
pub fn good_duration(mut self, avg_packets: f32) -> Self {
self.p = if avg_packets > 0.0 {
1.0 / avg_packets
} else {
0.0
};
self
}
pub fn bad_duration(mut self, avg_packets: f32) -> Self {
self.r = if avg_packets > 0.0 {
1.0 / avg_packets
} else {
1.0
};
self
}
pub fn loss_in_bad(mut self, prob: Probability) -> Self {
self.h = prob.0;
self
}
pub fn loss_in_good(mut self, prob: Probability) -> Self {
self.k = prob.0;
self
}
pub fn wifi() -> Self {
Self::new()
.good_duration(200.0) .bad_duration(2.0) .loss_in_bad(Probability::ONE)
}
pub fn wifi_lossy() -> Self {
Self::new()
.good_duration(40.0) .bad_duration(2.0) .loss_in_bad(Probability::ONE)
}
pub fn cellular() -> Self {
Self::new()
.good_duration(100.0) .bad_duration(2.0) .loss_in_bad(Probability::ONE)
}
pub fn satellite() -> Self {
Self::new()
.good_duration(100.0) .bad_duration(3.0) .loss_in_bad(Probability::ONE)
}
pub fn congested() -> Self {
Self::new()
.good_duration(20.0) .bad_duration(2.0) .loss_in_bad(Probability::ONE)
}
}
impl From<RandomLoss> for LossModel {
fn from(value: RandomLoss) -> Self {
LossModel::Random(value)
}
}
impl From<GilbertElliot> for LossModel {
fn from(value: GilbertElliot) -> Self {
LossModel::GilbertElliot(value)
}
}