From 9a128c501223d1a3f9868adf23c96380010b9781 Mon Sep 17 00:00:00 2001 From: Maxime Bruno Date: Mon, 9 Jun 2025 18:06:42 +0800 Subject: [PATCH] Adding support for user chose random generator --- Cargo.toml | 8 +- src/model/bw.rs | 188 +++++++++++++++++++++++++++++++--- src/model/delay_per_packet.rs | 184 ++++++++++++++++++++++++++++++--- 3 files changed, 350 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 061f125..2387b1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,15 +18,15 @@ dyn-clone = { version = "1.0.10", optional = true } human-bandwidth = { version = "0.1.3", optional = true } humantime-serde = { version = "1.1.1", optional = true } itertools = { version = "0.14.0", optional = true } -once_cell = { version = "1.17.0", optional = true } -rand = { version = "0.9.0", optional = true } -rand_distr = { version = "0.5.0", optional = true } +rand = { version = "0.9.1", optional = true } +rand_distr = { version = "0.5.1", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } statrs = { version = "0.18.0", optional = true } typetag = { version = "0.2.5", optional = true } [dev-dependencies] figment = { version = "0.10.19", features = ["json"] } +rand_chacha = "0.9" serde_json = "1.0" [features] @@ -38,7 +38,7 @@ model = [ "loss-model", "duplicate-model" ] -bw-model = ["dep:rand", "dep:rand_distr", "dep:once_cell", "dep:dyn-clone"] +bw-model = ["dep:rand", "dep:rand_distr", "dep:dyn-clone"] delay-model = ["dep:dyn-clone"] delay-per-packet-model = ["dep:dyn-clone"] loss-model = ["dep:dyn-clone"] diff --git a/src/model/bw.rs b/src/model/bw.rs index b340a5d..6599415 100644 --- a/src/model/bw.rs +++ b/src/model/bw.rs @@ -57,8 +57,7 @@ //! ``` use crate::{Bandwidth, BwTrace, Duration}; use dyn_clone::DynClone; -use rand::rngs::StdRng; -use rand::SeedableRng; +use rand::{rngs::StdRng, RngCore, SeedableRng}; use rand_distr::{Distribution, Normal}; const DEFAULT_RNG_SEED: u64 = 42; @@ -162,7 +161,10 @@ pub struct StaticBwConfig { /// assert_eq!(normal_bw.next_bw(), Some((Bandwidth::from_bps(12100000), Duration::from_millis(100)))); /// ``` #[derive(Debug, Clone)] -pub struct NormalizedBw { +pub struct NormalizedBw +where + Rng: RngCore, +{ pub mean: Bandwidth, pub std_dev: Bandwidth, pub upper_bound: Option, @@ -170,7 +172,7 @@ pub struct NormalizedBw { pub duration: Duration, pub step: Duration, pub seed: u64, - rng: StdRng, + rng: Rng, normal: Normal, } @@ -317,7 +319,10 @@ pub struct NormalizedBwConfig { /// ); /// ``` #[derive(Debug, Clone)] -pub struct SawtoothBw { +pub struct SawtoothBw +where + Rng: RngCore, +{ pub bottom: Bandwidth, pub top: Bandwidth, pub interval: Duration, @@ -329,7 +334,7 @@ pub struct SawtoothBw { pub upper_noise_bound: Option, pub lower_noise_bound: Option, current: Duration, - rng: StdRng, + rng: Rng, noise: Normal, } @@ -768,7 +773,7 @@ impl BwTrace for StaticBw { } } -impl BwTrace for NormalizedBw { +impl BwTrace for NormalizedBw { fn next_bw(&mut self) -> Option<(Bandwidth, Duration)> { if self.duration.is_zero() { None @@ -788,7 +793,7 @@ impl BwTrace for NormalizedBw { } } -impl BwTrace for SawtoothBw { +impl BwTrace for SawtoothBw { fn next_bw(&mut self) -> Option<(Bandwidth, Duration)> { if self.duration.is_zero() { None @@ -870,7 +875,7 @@ impl BwTrace for TraceBw { } } -impl NormalizedBw { +impl NormalizedBw { pub fn sample(&mut self) -> f64 { self.normal.sample(&mut self.rng) } @@ -912,6 +917,7 @@ macro_rules! saturating_bandwidth_as_bps_u64 { } impl NormalizedBwConfig { + /// Creates an uninitialized config pub fn new() -> Self { Self { mean: None, @@ -924,42 +930,119 @@ impl NormalizedBwConfig { } } + /// Sets the mean + /// + /// If the mean is not set, 12Mbps will be used. pub fn mean(mut self, mean: Bandwidth) -> Self { self.mean = Some(mean); self } + /// Sets the standard deviation + /// + /// If the standard deviation is not set, 0Mbps will be used. pub fn std_dev(mut self, std_dev: Bandwidth) -> Self { self.std_dev = Some(std_dev); self } + /// Sets the upper bound + /// + /// If the upper bound is not set, the upper bound will be the one of [`Bandwidth`]. pub fn upper_bound(mut self, upper_bound: Bandwidth) -> Self { self.upper_bound = Some(upper_bound); self } + /// Sets the lower bound + /// + /// If the lower bound is not set, the lower bound will be the one of [`Bandwidth`]. pub fn lower_bound(mut self, lower_bound: Bandwidth) -> Self { self.lower_bound = Some(lower_bound); self } + /// Sets the total duration of the trace + /// + /// If the total duration is not set, 1 second will be used. pub fn duration(mut self, duration: Duration) -> Self { self.duration = Some(duration); self } + /// Sets the duration of each value + /// + /// If the step is not set, 1ms will be used. pub fn step(mut self, step: Duration) -> Self { self.step = Some(step); self } + /// Set the seed for a random generator + /// + /// If the seed is not set, `42` will be used. pub fn seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } + /// Allows to use a randomly generated seed + /// + /// This is equivalent to: `self.seed(rand::random())` + pub fn random_seed(mut self) -> Self { + self.seed = Some(rand::random()); + self + } + + /// Creates a new [`NormalizedBw`] corresponding to this config. + /// + /// The created model will use [`StdRng`] as source of randomness (the call is equivalent to `self.build_with_rng::()`). + /// It should be sufficient for most cases, but [`StdRng`] is not a portable random number generator, + /// so one may want to use a portable random number generator like [`ChaCha`](https://crates.io/crates/rand_chacha), + /// to this end one can use [`build_with_rng`](Self::build_with_rng). pub fn build(self) -> NormalizedBw { + self.build_with_rng() + } + + /// Creates a new [`NormalizedBw`] corresponding to this config. + /// + /// Unlike [`build`](Self::build), this method let you choose the random generator. + /// + /// # Example + /// ```rust + /// # use netem_trace::model::NormalizedBwConfig; + /// # use netem_trace::{Bandwidth, BwTrace}; + /// # use std::time::Duration; + /// # use rand::rngs::StdRng; + /// # use rand_chacha::ChaCha20Rng; + /// + /// let normal_bw = NormalizedBwConfig::new() + /// .mean(Bandwidth::from_mbps(12)) + /// .std_dev(Bandwidth::from_mbps(1)) + /// .duration(Duration::from_millis(3)) + /// .seed(42); + /// + /// let mut default_build = normal_bw.clone().build(); + /// let mut std_build = normal_bw.clone().build_with_rng::(); + /// // ChaCha is deterministic and portable, unlike StdRng + /// let mut chacha_build = normal_bw.clone().build_with_rng::(); + /// + /// for cha in [12044676, 11754367, 11253775] { + /// let default = default_build.next_bw(); + /// let std = std_build.next_bw(); + /// let chacha = chacha_build.next_bw(); + /// + /// assert!(default.is_some()); + /// assert_eq!(default, std); + /// assert_ne!(default, chacha); + /// assert_eq!(chacha, Some((Bandwidth::from_bps(cha), Duration::from_millis(1)))); + /// } + /// + /// assert_eq!(default_build.next_bw(), None); + /// assert_eq!(std_build.next_bw(), None); + /// assert_eq!(chacha_build.next_bw(), None); + /// ``` + pub fn build_with_rng(self) -> NormalizedBw { let mean = self.mean.unwrap_or_else(|| Bandwidth::from_mbps(12)); let std_dev = self.std_dev.unwrap_or_else(|| Bandwidth::from_mbps(0)); let upper_bound = self.upper_bound; @@ -967,7 +1050,7 @@ impl NormalizedBwConfig { let duration = self.duration.unwrap_or_else(|| Duration::from_secs(1)); let step = self.step.unwrap_or_else(|| Duration::from_millis(1)); let seed = self.seed.unwrap_or(DEFAULT_RNG_SEED); - let rng = StdRng::seed_from_u64(seed); + let rng = Rng::seed_from_u64(seed); let bw_mean = saturating_bandwidth_as_bps_u64!(mean) as f64; let bw_std_dev = saturating_bandwidth_as_bps_u64!(std_dev) as f64; let normal: Normal = Normal::new(bw_mean, bw_std_dev).unwrap(); @@ -1034,7 +1117,14 @@ impl NormalizedBwConfig { /// assert_eq!(avg_mbps(truncate_build), 11.978819427569897); /// /// ``` - pub fn build_truncated(mut self) -> NormalizedBw { + pub fn build_truncated(self) -> NormalizedBw { + self.build_truncated_with_rng() + } + + /// Similar to [`build_truncated`](Self::build_truncated) but let you choose the random generator. + /// + /// See [`build`](Self::build) for details about the reason for using another random number generator than [`StdRng`]. + pub fn build_truncated_with_rng(mut self) -> NormalizedBw { let mean = self .mean .unwrap_or_else(|| Bandwidth::from_mbps(12)) @@ -1052,11 +1142,12 @@ impl NormalizedBwConfig { let upper = self.upper_bound.map(|upper| upper.as_gbps_f64() / mean); let new_mean = mean * solve(1f64, sigma, Some(lower), upper).unwrap_or(1f64); self.mean = Some(Bandwidth::from_gbps_f64(new_mean)); - self.build() + self.build_with_rng() } } impl SawtoothBwConfig { + /// Creates an uninitialized config pub fn new() -> Self { Self { bottom: None, @@ -1092,21 +1183,41 @@ impl SawtoothBwConfig { self } + /// Sets the total duration of the trace + /// + /// If the total duration is not set, 1 second will be used. pub fn duration(mut self, duration: Duration) -> Self { self.duration = Some(duration); self } + /// Sets the duration of each value + /// + /// If the step is not set, 1ms will be used. pub fn step(mut self, step: Duration) -> Self { self.step = Some(step); self } + /// Set the seed for a random generator + /// + /// If the seed is not set, `42` will be used. pub fn seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } + /// Allows to use a randomly generated seed + /// + /// This is equivalent to: `self.seed(rand::random())` + pub fn random_seed(mut self) -> Self { + self.seed = Some(rand::random()); + self + } + + /// Sets the standard deviation + /// + /// If the standard deviation is not set, 0Mbps will be used. pub fn std_dev(mut self, std_dev: Bandwidth) -> Self { self.std_dev = Some(std_dev); self @@ -1122,7 +1233,58 @@ impl SawtoothBwConfig { self } + /// Creates a new [`SawtoothBw`] corresponding to this config. + /// + /// The created model will use [`StdRng`] as source of randomness (the call is equivalent to `self.build_with_rng::()`). + /// It should be sufficient for most cases, but [`StdRng`] is not a portable random number generator, + /// so one may want to use a portable random number generator like [`ChaCha`](https://crates.io/crates/rand_chacha), + /// to this end one can use [`build_with_rng`](Self::build_with_rng). pub fn build(self) -> SawtoothBw { + self.build_with_rng() + } + + /// Creates a new [`SawtoothBw`] corresponding to this config. + /// + /// Unlike [`build`](Self::build), this method let you choose the random generator. + /// + /// # Example + /// ```rust + /// # use netem_trace::model::SawtoothBwConfig; + /// # use netem_trace::{Bandwidth, BwTrace}; + /// # use std::time::Duration; + /// # use rand::rngs::StdRng; + /// # use rand_chacha::ChaCha20Rng; + /// + /// let sawtooth_bw = SawtoothBwConfig::new() + /// .bottom(Bandwidth::from_mbps(12)) + /// .top(Bandwidth::from_mbps(16)) + /// .std_dev(Bandwidth::from_mbps(1)) + /// .duration(Duration::from_millis(3)) + /// .interval(Duration::from_millis(5)) + /// .duty_ratio(0.8) + /// .seed(42); + /// + /// let mut default_build = sawtooth_bw.clone().build(); + /// let mut std_build = sawtooth_bw.clone().build_with_rng::(); + /// // ChaCha is deterministic and portable, unlike StdRng + /// let mut chacha_build = sawtooth_bw.clone().build_with_rng::(); + /// + /// for cha in [12044676, 12754367, 13253775] { + /// let default = default_build.next_bw(); + /// let std = std_build.next_bw(); + /// let chacha = chacha_build.next_bw(); + /// + /// assert!(default.is_some()); + /// assert_eq!(default, std); + /// assert_ne!(default, chacha); + /// assert_eq!(chacha, Some((Bandwidth::from_bps(cha), Duration::from_millis(1)))); + /// } + /// + /// assert_eq!(default_build.next_bw(), None); + /// assert_eq!(std_build.next_bw(), None); + /// assert_eq!(chacha_build.next_bw(), None); + /// ``` + pub fn build_with_rng(self) -> SawtoothBw { let bottom = self.bottom.unwrap_or_else(|| Bandwidth::from_mbps(0)); let top = self.top.unwrap_or_else(|| Bandwidth::from_mbps(12)); if bottom > top { @@ -1133,7 +1295,7 @@ impl SawtoothBwConfig { let duration = self.duration.unwrap_or_else(|| Duration::from_secs(1)); let step = self.step.unwrap_or_else(|| Duration::from_millis(1)); let seed = self.seed.unwrap_or(DEFAULT_RNG_SEED); - let rng = StdRng::seed_from_u64(seed); + let rng = Rng::seed_from_u64(seed); let std_dev = self.std_dev.unwrap_or_else(|| Bandwidth::from_mbps(0)); let upper_noise_bound = self.upper_noise_bound; let lower_noise_bound = self.lower_noise_bound; diff --git a/src/model/delay_per_packet.rs b/src/model/delay_per_packet.rs index 2ef5196..2613d15 100644 --- a/src/model/delay_per_packet.rs +++ b/src/model/delay_per_packet.rs @@ -58,7 +58,7 @@ use crate::{Delay, DelayPerPacketTrace}; use dyn_clone::DynClone; -const DEFAULT_RNG_SEED: u64 = 42; +const DEFAULT_RNG_SEED: u64 = 42; // Some documentation will need corrections if this changes /// This trait is used to convert a per-packet delay trace configuration into a per-packet delay trace model. /// @@ -73,7 +73,7 @@ pub trait DelayPerPacketTraceConfig: DynClone + Send { dyn_clone::clone_trait_object!(DelayPerPacketTraceConfig); -use rand::{rngs::StdRng, SeedableRng as _}; +use rand::{rngs::StdRng, RngCore, SeedableRng}; use rand_distr::{Distribution, LogNormal, Normal}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -251,7 +251,10 @@ pub struct RepeatedDelayPerPacketPatternConfig { /// assert_eq!(normal_delay.next_delay(), None); /// ``` #[derive(Debug, Clone)] -pub struct NormalizedDelayPerPacket { +pub struct NormalizedDelayPerPacket +where + Rng: RngCore, +{ pub mean: Delay, pub std_dev: Delay, pub upper_bound: Option, @@ -259,7 +262,7 @@ pub struct NormalizedDelayPerPacket { pub seed: u64, pub count: usize, current_count: usize, - rng: StdRng, + rng: Rng, normal: Normal, } @@ -342,7 +345,10 @@ pub struct NormalizedDelayPerPacketConfig { /// assert_eq!(log_normal_delay.next_delay(), None); /// ``` #[derive(Debug, Clone)] -pub struct LogNormalizedDelayPerPacket { +pub struct LogNormalizedDelayPerPacket +where + Rng: RngCore, +{ pub mean: Delay, pub std_dev: Delay, pub upper_bound: Option, @@ -350,7 +356,7 @@ pub struct LogNormalizedDelayPerPacket { pub seed: u64, pub count: usize, current_count: usize, - rng: StdRng, + rng: Rng, log_normal: LogNormal, } @@ -428,7 +434,7 @@ impl DelayPerPacketTrace for RepeatedDelayPerPacketPattern { } } -impl DelayPerPacketTrace for NormalizedDelayPerPacket { +impl DelayPerPacketTrace for NormalizedDelayPerPacket { fn next_delay(&mut self) -> Option { if self.count != 0 && self.count == self.current_count { None @@ -445,7 +451,7 @@ impl DelayPerPacketTrace for NormalizedDelayPerPacket { } } -impl DelayPerPacketTrace for LogNormalizedDelayPerPacket { +impl DelayPerPacketTrace for LogNormalizedDelayPerPacket { fn next_delay(&mut self) -> Option { if self.count != 0 && self.count == self.current_count { None @@ -519,6 +525,7 @@ impl RepeatedDelayPerPacketPatternConfig { } impl NormalizedDelayPerPacketConfig { + /// Creates an uninitialized config pub fn new() -> Self { Self { mean: None, @@ -530,49 +537,117 @@ impl NormalizedDelayPerPacketConfig { } } + /// Sets the mean + /// + /// If the mean is not set, 10ms will be used. pub fn mean(mut self, mean: Delay) -> Self { self.mean = Some(mean); self } + /// Sets the standard deviation + /// + /// If the standard deviation is not set, 0ms will be used. pub fn std_dev(mut self, std_dev: Delay) -> Self { self.std_dev = Some(std_dev); self } + /// Sets the upper bound + /// + /// If the upper bound is not set, the upper bound will be the one of [`Delay`]. pub fn upper_bound(mut self, upper_bound: Delay) -> Self { self.upper_bound = Some(upper_bound); self } + /// Sets the lower bound + /// + /// If the lower bound is not set, 0ms will be used. pub fn lower_bound(mut self, lower_bound: Delay) -> Self { self.lower_bound = Some(lower_bound); self } + /// Sets the number of packets to repeat + /// + /// If the count is not set, it will be set to 0 (ie, infinite repeat). pub fn count(mut self, count: usize) -> Self { self.count = count; self } + /// Set the seed for a random generator + /// + /// If the seed is not set, `42` will be used. pub fn seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } + /// Allows to use a randomly generated seed + /// + /// This is equivalent to: `self.seed(rand::random())` pub fn random_seed(mut self) -> Self { self.seed = Some(rand::random()); self } + /// Creates a new [`NormalizedDelayPerPacket`] corresponding to this config. + /// + /// The created model will use [`StdRng`] as source of randomness (the call is equivalent to `self.build_with_rng::()`). + /// It should be sufficient for most cases, but [`StdRng`] is not a portable random number generator, + /// so one may want to use a portable random number generator like [`ChaCha`](https://crates.io/crates/rand_chacha), + /// to this end one can use [`build_with_rng`](Self::build_with_rng). pub fn build(self) -> NormalizedDelayPerPacket { + self.build_with_rng() + } + + /// Creates a new [`NormalizedDelayPerPacket`] corresponding to this config. + /// + /// Unlike [`build`](Self::build), this method let you choose the random generator. + /// + /// # Example + /// ```rust + /// # use netem_trace::model::NormalizedDelayPerPacketConfig; + /// # use netem_trace::{Delay, DelayPerPacketTrace}; + /// # use rand::rngs::StdRng; + /// # use rand_chacha::ChaCha20Rng; + /// + /// let normal_delay = NormalizedDelayPerPacketConfig::new() + /// .mean(Delay::from_millis(12)) + /// .std_dev(Delay::from_millis(1)) + /// .count(3) + /// .seed(42); + /// + /// let mut default_build = normal_delay.clone().build(); + /// let mut std_build = normal_delay.clone().build_with_rng::(); + /// // ChaCha is deterministic and portable, unlike StdRng + /// let mut chacha_build = normal_delay.clone().build_with_rng::(); + /// + /// for cha in [12044676, 11754367, 11253775] { + /// let default = default_build.next_delay(); + /// let std = std_build.next_delay(); + /// let chacha = chacha_build.next_delay(); + /// + /// assert!(default.is_some()); + /// assert_eq!(default, std); + /// assert_ne!(default, chacha); + /// assert_eq!(chacha, Some(Delay::from_nanos(cha))); + /// } + /// + /// assert_eq!(default_build.next_delay(), None); + /// assert_eq!(std_build.next_delay(), None); + /// assert_eq!(chacha_build.next_delay(), None); + /// ``` + pub fn build_with_rng(self) -> NormalizedDelayPerPacket { let mean = self.mean.unwrap_or_else(|| Delay::from_millis(10)); let std_dev = self.std_dev.unwrap_or(Delay::ZERO); let upper_bound = self.upper_bound; let lower_bound = self.lower_bound.unwrap_or(Delay::ZERO); let count = self.count; let seed = self.seed.unwrap_or(DEFAULT_RNG_SEED); - let rng = StdRng::seed_from_u64(seed); + let rng = Rng::seed_from_u64(seed); let delay_mean = mean.as_secs_f64(); let delay_std_dev = std_dev.as_secs_f64(); let normal: Normal = Normal::new(delay_mean, delay_std_dev).unwrap(); @@ -602,7 +677,7 @@ impl NormalizedDelayPerPacketConfig { /// /// # use netem_trace::model::NormalizedDelayPerPacketConfig; /// # use netem_trace::{Delay, DelayPerPacketTrace}; - /// # use crate::netem_trace::model::Forever; + /// /// let normal_delay = NormalizedDelayPerPacketConfig::new() /// .mean(Delay::from_millis(12)) /// .std_dev(Delay::from_millis(12)) @@ -637,7 +712,16 @@ impl NormalizedDelayPerPacketConfig { /// assert_eq!(avg_delay(truncate_build), Delay::from_nanos(11999151)); /// /// ``` - pub fn build_truncated(mut self) -> NormalizedDelayPerPacket { + pub fn build_truncated(self) -> NormalizedDelayPerPacket { + self.build_truncated_with_rng() + } + + /// Similar to [`build_truncated`](Self::build_truncated) but let you choose the random generator. + /// + /// See [`build`](Self::build) for details about the reason for using another random number generator than [`StdRng`]. + pub fn build_truncated_with_rng( + mut self, + ) -> NormalizedDelayPerPacket { let mean = self .mean .unwrap_or_else(|| Delay::from_millis(12)) @@ -647,11 +731,12 @@ impl NormalizedDelayPerPacketConfig { let upper = self.upper_bound.map(|upper| upper.as_secs_f64() / mean); let new_mean = mean * solve(1f64, sigma, Some(lower), upper).unwrap_or(1f64); self.mean = Some(Delay::from_secs_f64(new_mean)); - self.build() + self.build_with_rng() } } impl LogNormalizedDelayPerPacketConfig { + /// Creates an uninitialized config pub fn new() -> Self { Self { mean: None, @@ -663,56 +748,129 @@ impl LogNormalizedDelayPerPacketConfig { } } + /// Sets the mean + /// + /// If the mean is not set, 10ms will be used. pub fn mean(mut self, mean: Delay) -> Self { self.mean = Some(mean); self } + /// Sets the standard deviation + /// + /// If the standard deviation is not set, 0ms will be used. pub fn std_dev(mut self, std_dev: Delay) -> Self { self.std_dev = Some(std_dev); self } + /// Sets the upper bound + /// + /// If the upper bound is not set, the upper bound will be the one of [`Delay`]. pub fn upper_bound(mut self, upper_bound: Delay) -> Self { self.upper_bound = Some(upper_bound); self } + /// Sets the lower bound + /// + /// If the lower bound is not set, 0ms will be used. pub fn lower_bound(mut self, lower_bound: Delay) -> Self { self.lower_bound = Some(lower_bound); self } + /// Sets the number of packets to repeat + /// + /// If the count is not set, it will be set to 0 (ie, infinite repeat). pub fn count(mut self, count: usize) -> Self { self.count = count; self } + /// Set the seed for a random generator + /// + /// If the seed is not set, `42` will be used. pub fn seed(mut self, seed: u64) -> Self { self.seed = Some(seed); self } + /// Allows to use a randomly generated seed + /// + /// This is equivalent to: `self.seed(rand::random())` pub fn random_seed(mut self) -> Self { self.seed = Some(rand::random()); self } + /// Creates a new [`LogNormalizedDelayPerPacket`] corresponding to this config. + /// + /// The created model will use [`StdRng`] as source of randomness (the call is equivalent to `self.build_with_rng::()`). + /// It should be sufficient for most cases, but [`StdRng`] is not a portable random number generator, + /// so one may want to use a portable random number generator like [`ChaCha`](https://crates.io/crates/rand_chacha), + /// to this end one can use [`build_with_rng`](Self::build_with_rng). pub fn build(self) -> LogNormalizedDelayPerPacket { + self.build_with_rng() + } + + /// Creates a new [`LogNormalizedDelayPerPacket`] corresponding to this config. + /// + /// Unlike [`build`](Self::build), this method let you choose the random generator. + /// + /// # Example + /// ```rust + /// # use netem_trace::model::LogNormalizedDelayPerPacketConfig; + /// # use netem_trace::{Delay, DelayPerPacketTrace}; + /// # use rand::rngs::StdRng; + /// # use rand_chacha::ChaCha20Rng; + /// + /// let log_normal_delay = LogNormalizedDelayPerPacketConfig::new() + /// .mean(Delay::from_millis(12)) + /// .std_dev(Delay::from_millis(1)) + /// .count(3) + /// .seed(42); + /// + /// let mut default_build = log_normal_delay.clone().build(); + /// let mut std_build = log_normal_delay.clone().build_with_rng::(); + /// // ChaCha is deterministic and portable, unlike StdRng + /// let mut chacha_build = log_normal_delay.clone().build_with_rng::(); + /// + /// for cha in [12003077, 11716668, 11238761] { + /// let default = default_build.next_delay(); + /// let std = std_build.next_delay(); + /// let chacha = chacha_build.next_delay(); + /// + /// assert!(default.is_some()); + /// assert_eq!(default, std); + /// assert_ne!(default, chacha); + /// assert_eq!(chacha, Some(Delay::from_nanos(cha))); + /// } + /// + /// assert_eq!(default_build.next_delay(), None); + /// assert_eq!(std_build.next_delay(), None); + /// assert_eq!(chacha_build.next_delay(), None); + /// ``` + pub fn build_with_rng(self) -> LogNormalizedDelayPerPacket { let mean = self.mean.unwrap_or_else(|| Delay::from_millis(10)); let std_dev = self.std_dev.unwrap_or(Delay::ZERO); let upper_bound = self.upper_bound; let lower_bound = self.lower_bound.unwrap_or(Delay::ZERO); let count = self.count; let seed = self.seed.unwrap_or(DEFAULT_RNG_SEED); - let rng = StdRng::seed_from_u64(seed); + let rng = Rng::seed_from_u64(seed); let delay_mean = mean.as_secs_f64(); let delay_std_dev = std_dev.as_secs_f64(); + + // Computing the mean and standard deviation of underlying normal Law + // Because Lognormal(μ , σ²) has a mean of exp(μ + σ²/2) and a standard deviation of sqrt((exp(σ²) - 1) exp(2μ + σ²)) + // So we need to comput μ and σ, given the mean and standard deviation of the log-normal law let normal_std_dev = f64::sqrt(f64::ln( 1.0 + (delay_std_dev.powi(2)) / (delay_mean.powi(2)), )); let normal_mean = f64::ln(delay_mean) - normal_std_dev.powi(2) / 2.; let log_normal: LogNormal = LogNormal::new(normal_mean, normal_std_dev).unwrap(); + LogNormalizedDelayPerPacket { mean, std_dev,