Skip to content

Commit 66642f6

Browse files
authored
feat(model): add uniform distr for delay per packet (#24)
1 parent 182841d commit 66642f6

2 files changed

Lines changed: 195 additions & 3 deletions

File tree

src/model/delay_per_packet.rs

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub trait DelayPerPacketTraceConfig: DynClone + Send + std::fmt::Debug {
7474
dyn_clone::clone_trait_object!(DelayPerPacketTraceConfig);
7575

7676
use rand::{rngs::StdRng, RngCore, SeedableRng};
77-
use rand_distr::{Distribution, LogNormal, Normal};
77+
use rand_distr::{Distribution, LogNormal, Normal, Uniform};
7878
#[cfg(feature = "serde")]
7979
use serde::{Deserialize, Serialize};
8080

@@ -210,6 +210,67 @@ pub struct RepeatedDelayPerPacketPatternConfig {
210210
pub count: usize,
211211
}
212212

213+
/// The model of a per-packet delay trace subjects to a uniform distribution.
214+
///
215+
/// The delay will subject to a uniform distribution in [lower_bound, upper_bound]
216+
///
217+
/// If the `count` is 0, the delay will be repeated forever, else it will be repeated `count` times.
218+
///
219+
/// ## Examples
220+
///
221+
/// Default values for lower_bound and upper_bound are 0ms and 10ms, respectively.
222+
///
223+
/// ```
224+
/// # use netem_trace::model::UniformDelayPerPacketConfig;
225+
/// # use netem_trace::{Delay, DelayPerPacketTrace};
226+
/// let mut uniform_delay = UniformDelayPerPacketConfig::new()
227+
/// .upper_bound(Delay::from_micros(12100))
228+
/// .lower_bound(Delay::from_micros(11900))
229+
/// .count(2)
230+
/// .seed(42)
231+
/// .build();
232+
/// assert_eq!(uniform_delay.next_delay(), Some(Delay::from_nanos(12005311)));
233+
/// assert_eq!(uniform_delay.next_delay(), Some(Delay::from_nanos(12008545)));
234+
/// assert_eq!(uniform_delay.next_delay(), None);
235+
/// ```
236+
#[derive(Debug, Clone, Copy, PartialEq)]
237+
pub struct UniformDelayPerPacket<Rng = StdRng>
238+
where
239+
Rng: RngCore,
240+
{
241+
pub upper_bound: Delay,
242+
pub lower_bound: Delay,
243+
pub seed: u64,
244+
pub count: usize,
245+
current_count: usize,
246+
rng: Rng,
247+
uniform: Uniform<f64>,
248+
}
249+
250+
/// The configuration struct for [`UniformDelayPerPacket`].
251+
///
252+
/// See [`UniformDelayPerPacket`] for more details.
253+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
254+
#[derive(Default, Debug, Clone, Copy, PartialEq)]
255+
pub struct UniformDelayPerPacketConfig {
256+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
257+
#[cfg_attr(
258+
all(feature = "serde", feature = "human"),
259+
serde(with = "humantime_serde")
260+
)]
261+
pub upper_bound: Option<Delay>,
262+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
263+
#[cfg_attr(
264+
all(feature = "serde", feature = "human"),
265+
serde(with = "humantime_serde")
266+
)]
267+
pub lower_bound: Option<Delay>,
268+
#[cfg_attr(feature = "serde", serde(default))]
269+
pub count: usize,
270+
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
271+
pub seed: Option<u64>,
272+
}
273+
213274
/// The model of a per-packet delay trace subjects to a normal distribution.
214275
///
215276
/// The delay will subject to N(mean, std_dev²), but bounded within [lower_bound, upper_bound] (optional)
@@ -435,6 +496,18 @@ impl DelayPerPacketTrace for RepeatedDelayPerPacketPattern {
435496
}
436497
}
437498

499+
impl<Rng: RngCore + Send> DelayPerPacketTrace for UniformDelayPerPacket<Rng> {
500+
fn next_delay(&mut self) -> Option<Delay> {
501+
if self.count != 0 && self.count == self.current_count {
502+
None
503+
} else {
504+
self.current_count += 1;
505+
let delay = self.uniform.sample(&mut self.rng).max(0.0);
506+
Some(Delay::from_secs_f64(delay))
507+
}
508+
}
509+
}
510+
438511
impl<Rng: RngCore + Send> DelayPerPacketTrace for NormalizedDelayPerPacket<Rng> {
439512
fn next_delay(&mut self) -> Option<Delay> {
440513
if self.count != 0 && self.count == self.current_count {
@@ -525,6 +598,124 @@ impl RepeatedDelayPerPacketPatternConfig {
525598
}
526599
}
527600

601+
impl UniformDelayPerPacketConfig {
602+
/// Creates an uninitialized config
603+
pub fn new() -> Self {
604+
Self {
605+
upper_bound: None,
606+
lower_bound: None,
607+
count: 0,
608+
seed: None,
609+
}
610+
}
611+
612+
/// Sets the upper bound
613+
///
614+
/// If the upper bound is not set, the upper bound will be 10ms.
615+
pub fn upper_bound(mut self, upper_bound: Delay) -> Self {
616+
self.upper_bound = Some(upper_bound);
617+
self
618+
}
619+
620+
/// Sets the lower bound
621+
///
622+
/// If the lower bound is not set, 0ms will be used.
623+
pub fn lower_bound(mut self, lower_bound: Delay) -> Self {
624+
self.lower_bound = Some(lower_bound);
625+
self
626+
}
627+
628+
/// Sets the number of packets to repeat
629+
///
630+
/// If the count is not set, it will be set to 0 (ie, infinite repeat).
631+
pub fn count(mut self, count: usize) -> Self {
632+
self.count = count;
633+
self
634+
}
635+
636+
/// Set the seed for a random generator
637+
///
638+
/// If the seed is not set, `42` will be used.
639+
pub fn seed(mut self, seed: u64) -> Self {
640+
self.seed = Some(seed);
641+
self
642+
}
643+
644+
/// Allows to use a randomly generated seed
645+
///
646+
/// This is equivalent to: `self.seed(rand::random())`
647+
pub fn random_seed(mut self) -> Self {
648+
self.seed = Some(rand::random());
649+
self
650+
}
651+
652+
/// Creates a new [`UniformDelayPerPacket`] corresponding to this config.
653+
///
654+
/// The created model will use [`StdRng`] as source of randomness (the call is equivalent to `self.build_with_rng::<StdRng>()`).
655+
/// It should be sufficient for most cases, but [`StdRng`] is not a portable random number generator,
656+
/// so one may want to use a portable random number generator like [`ChaCha`](https://crates.io/crates/rand_chacha),
657+
/// to this end one can use [`build_with_rng`](Self::build_with_rng).
658+
pub fn build(self) -> UniformDelayPerPacket {
659+
self.build_with_rng()
660+
}
661+
662+
/// Creates a new [`UniformDelayPerPacket`] corresponding to this config.
663+
///
664+
/// Unlike [`build`](Self::build), this method let you choose the random generator.
665+
///
666+
/// # Example
667+
/// ```rust
668+
/// # use netem_trace::model::UniformDelayPerPacketConfig;
669+
/// # use netem_trace::{Delay, DelayPerPacketTrace};
670+
/// # use rand::rngs::StdRng;
671+
/// # use rand_chacha::ChaCha20Rng;
672+
///
673+
/// let uniform_delay = UniformDelayPerPacketConfig::new()
674+
/// .upper_bound(Delay::from_micros(12100))
675+
/// .lower_bound(Delay::from_micros(11900))
676+
/// .count(3)
677+
/// .seed(42);
678+
///
679+
/// let mut default_build = uniform_delay.clone().build();
680+
/// let mut std_build = uniform_delay.clone().build_with_rng::<StdRng>();
681+
/// // ChaCha is deterministic and portable, unlike StdRng
682+
/// let mut chacha_build = uniform_delay.clone().build_with_rng::<ChaCha20Rng>();
683+
///
684+
/// for cha in [12002810, 11982040, 11919563] {
685+
/// let default = default_build.next_delay();
686+
/// let std = std_build.next_delay();
687+
/// let chacha = chacha_build.next_delay();
688+
///
689+
/// assert!(default.is_some());
690+
/// assert_eq!(default, std);
691+
/// assert_ne!(default, chacha);
692+
/// assert_eq!(chacha, Some(Delay::from_nanos(cha)));
693+
/// }
694+
///
695+
/// assert_eq!(default_build.next_delay(), None);
696+
/// assert_eq!(std_build.next_delay(), None);
697+
/// assert_eq!(chacha_build.next_delay(), None);
698+
/// ```
699+
pub fn build_with_rng<Rng: RngCore + SeedableRng>(self) -> UniformDelayPerPacket<Rng> {
700+
let upper_bound = self.upper_bound.unwrap_or(Delay::from_millis(10));
701+
let lower_bound = self.lower_bound.unwrap_or(Delay::ZERO);
702+
let count = self.count;
703+
let seed = self.seed.unwrap_or(DEFAULT_RNG_SEED);
704+
let rng = Rng::seed_from_u64(seed);
705+
let uniform: Uniform<f64> =
706+
Uniform::new(lower_bound.as_secs_f64(), upper_bound.as_secs_f64()).unwrap();
707+
UniformDelayPerPacket {
708+
upper_bound,
709+
lower_bound,
710+
count,
711+
current_count: 0,
712+
seed,
713+
rng,
714+
uniform,
715+
}
716+
}
717+
}
718+
528719
impl NormalizedDelayPerPacketConfig {
529720
/// Creates an uninitialized config
530721
pub fn new() -> Self {
@@ -898,6 +1089,7 @@ macro_rules! impl_delay_per_packet_trace_config {
8981089
}
8991090

9001091
impl_delay_per_packet_trace_config!(StaticDelayPerPacketConfig);
1092+
impl_delay_per_packet_trace_config!(UniformDelayPerPacketConfig);
9011093
impl_delay_per_packet_trace_config!(NormalizedDelayPerPacketConfig);
9021094
impl_delay_per_packet_trace_config!(LogNormalizedDelayPerPacketConfig);
9031095
impl_delay_per_packet_trace_config!(RepeatedDelayPerPacketPatternConfig);

src/model/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ pub mod delay_per_packet;
3636
#[cfg(feature = "delay-per-packet-model")]
3737
pub use delay_per_packet::{
3838
DelayPerPacketTraceConfig, LogNormalizedDelayPerPacketConfig, NormalizedDelayPerPacketConfig,
39-
RepeatedDelayPerPacketPatternConfig, StaticDelayPerPacketConfig,
39+
RepeatedDelayPerPacketPatternConfig, StaticDelayPerPacketConfig, UniformDelayPerPacketConfig,
4040
};
4141
#[cfg(feature = "delay-per-packet-model")]
4242
pub use delay_per_packet::{
4343
LogNormalizedDelayPerPacket, NormalizedDelayPerPacket, RepeatedDelayPerPacketPattern,
44-
StaticDelayPerPacket,
44+
StaticDelayPerPacket, UniformDelayPerPacket,
4545
};
4646

4747
#[cfg(feature = "loss-model")]

0 commit comments

Comments
 (0)