Skip to main content

netem_trace/
lib.rs

1//! This crate provides a set of tools to generate traces for network emulation.
2//!
3//! ## Examples
4//!
5//! If you want to use the pre-defined models, please enable the `model` or `bw-model` feature.
6//!
7//! And if you want read configuration from file, `serde` feature should also be enabled.
8//! We else recommend you to enable `human` feature to make the configuration file more human-readable.
9//!
10//! An example to build model from configuration:
11//!
12//! ```
13//! # use netem_trace::model::StaticBwConfig;
14//! # use netem_trace::{Bandwidth, Duration, BwTrace};
15//! let mut static_bw = StaticBwConfig::new()
16//!     .bw(Bandwidth::from_mbps(24))
17//!     .duration(Duration::from_secs(1))
18//!     .build();
19//! assert_eq!(static_bw.next_bw(), Some((Bandwidth::from_mbps(24), Duration::from_secs(1))));
20//! assert_eq!(static_bw.next_bw(), None);
21//! ```
22//!
23//! A more common use case is to build model from a configuration file (e.g. json file):
24//!
25//! ```
26//! # use netem_trace::model::{StaticBwConfig, BwTraceConfig};
27//! # use netem_trace::{Bandwidth, Duration, BwTrace};
28//! # #[cfg(feature = "human")]
29//! # let config_file_content = "{\"RepeatedBwPatternConfig\":{\"pattern\":[{\"StaticBwConfig\":{\"bw\":\"12Mbps\",\"duration\":\"1s\"}},{\"StaticBwConfig\":{\"bw\":\"24Mbps\",\"duration\":\"1s\"}}],\"count\":2}}";
30//! // The content would be "{\"RepeatedBwPatternConfig\":{\"pattern\":[{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":12000000},\"duration\":{\"secs\":1,\"nanos\":0}}},{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":24000000},\"duration\":{\"secs\":1,\"nanos\":0}}}],\"count\":2}}"
31//! // if the `human` feature is not enabled.
32//! # #[cfg(not(feature = "human"))]
33//! let config_file_content = "{\"RepeatedBwPatternConfig\":{\"pattern\":[{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":12000000},\"duration\":{\"secs\":1,\"nanos\":0}}},{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":24000000},\"duration\":{\"secs\":1,\"nanos\":0}}}],\"count\":2}}";
34//! let des: Box<dyn BwTraceConfig> = serde_json::from_str(config_file_content).unwrap();
35//! let mut model = des.into_model();
36//! assert_eq!(
37//!     model.next_bw(),
38//!     Some((Bandwidth::from_mbps(12), Duration::from_secs(1)))
39//! );
40//! assert_eq!(
41//!     model.next_bw(),
42//!     Some((Bandwidth::from_mbps(24), Duration::from_secs(1)))
43//! );
44//! assert_eq!(
45//!     model.next_bw(),
46//!     Some((Bandwidth::from_mbps(12), Duration::from_secs(1)))
47//! );
48//! assert_eq!(
49//!     model.next_bw(),
50//!     Some((Bandwidth::from_mbps(24), Duration::from_secs(1)))
51//! );
52//! assert_eq!(model.next_bw(), None);
53//! ```
54//!
55//! ## Make your own model
56//!
57//! Here is an simple example of how to do this. For more complicated examples, please refer to our pre-defined models.
58//!
59//! ```
60//! use netem_trace::BwTrace;
61//! use netem_trace::{Bandwidth, Duration};
62//!
63//! struct MyStaticBw {
64//!    bw: Bandwidth,
65//!    duration: Option<Duration>,
66//! }
67//!
68//! impl BwTrace for MyStaticBw {
69//!     fn next_bw(&mut self) -> Option<(Bandwidth, Duration)> {
70//!         if let Some(duration) = self.duration.take() {
71//!             if duration.is_zero() {
72//!                 None
73//!             } else {
74//!                 Some((self.bw, duration))
75//!             }
76//!         } else {
77//!             None
78//!         }
79//!     }
80//! }
81//! ```
82//!
83//! This is almost the same as how this library implements the [`model::StaticBw`] model.
84//!
85//! ## Features
86//!
87//! ### Model Features
88//!
89//! - `model`: Enable this feature if you want to use all pre-defined models.
90//!     - `bw-model`: Enable this feature if you want to use the pre-defined [`BwTrace`] models.
91//!     - `truncated-normal`: Enable this feature if you want to use truncated normal distribution in [`model::NormalizedBw`] models.
92//!
93//! ### Trace Format Features
94//!
95//! - `mahimahi`: Enable this feature if you want to load or output traces in [mahimahi](https://github.com/ravinet/mahimahi) format.
96//!
97//! ### Trace Extension Features
98//!
99//! - `trace-ext`: Enable this feature to use the series expansion and export functionality for plotting and visualization. See [`series`] module for details.
100//!
101//! ### Other Features
102//!
103//! - `serde`: Enable this features if you want some structs to be serializable/deserializable. Often used with model features.
104//! - `human`: Enable this feature if you want to use human-readable format in configuration files. Often used with model features.
105
106#[cfg(feature = "mahimahi")]
107pub mod mahimahi;
108#[cfg(feature = "mahimahi")]
109pub use mahimahi::{load_mahimahi_trace, Mahimahi, MahimahiExt};
110
111#[cfg(any(
112    feature = "bw-model",
113    feature = "delay-model",
114    feature = "loss-model",
115    feature = "duplicate-model",
116    feature = "rwnd-model",
117    feature = "model",
118))]
119pub mod model;
120
121#[cfg(feature = "trace-ext")]
122pub mod series;
123
124pub use bandwidth::Bandwidth;
125pub use std::time::Duration;
126
127/// The delay describes how long a packet is delayed when going through.
128pub type Delay = std::time::Duration;
129
130/// The loss_pattern describes how the packets are dropped when going through.
131///
132/// The loss_pattern is a sequence of conditional probabilities describing how packets are dropped.
133/// The probability is a f64 between 0 and 1.
134///
135/// The meaning of the loss_pattern sequence is as follows:
136///
137/// - The probability on index 0 describes how likely a packet will be dropped **if the previous packet was not lost**.
138/// - The probability on index 1 describes how likely a packet will be dropped **if the previous packet was lost**.
139/// - The probability on index 2 describes how likely a packet will be dropped **if the previous 2 packet was lost**.
140/// - ...
141///
142/// For example, if the loss_pattern is [0.1, 0.2], and packet 100 is not lost,
143/// then the probability of packet 101 being lost is 0.1.
144///
145/// If the packet 101 is lost, then the probability of packet 102 being lost is 0.2.
146/// If the packet 101 is not lost, then the probability of packet 102 being lost is still 0.1.
147pub type LossPattern = Vec<f64>;
148
149/// The duplicate_pattern describes how the packets are duplicated.
150///
151/// The duplicate_pattern is a sequence of conditional probabilities describing how packets are duplicated.
152/// The probability is a f64 between 0 and 1.
153///
154/// The meaning of the duplicate_pattern sequence is:
155///
156/// - The probability on index 0 describes how likely a packet will be duplicated
157///   **if the previous packet was transmitted normally**.
158/// - The probability on index 1 describes how likely a packet will be duplicated
159///   **if the previous packet was duplicated**.
160/// - ...
161///
162/// For example, if the duplicate_pattern is [0.8, 0.1], and packet 100 is not duplicated, then the
163/// probability of packet 101 being duplicated is 0.8.
164///
165/// If the packet 101 is duplicated, the the probability of packet 102 being duplicated is 0.1; if
166/// the packet 101 is not duplicated, then the probability of packet 102 being duplicated is still 0.8.
167///
168/// If both packet 101 and 102 were duplicated, then the probability of packet 103 being duplicated
169/// is still 0.1, and as long as the packets were duplicated, the probability of the next packet
170/// being duplicated is always the last element - in this case, 0.1.
171pub type DuplicatePattern = Vec<f64>;
172
173/// This is a trait that represents a trace of bandwidths.
174///
175/// The trace is a sequence of `(bandwidth, duration)` pairs.
176/// The bandwidth describes how many bits can be sent per second.
177/// The duration is the time that the bandwidth lasts.
178///
179/// For example, if the sequence is [(1Mbps, 1s), (2Mbps, 2s), (3Mbps, 3s)],
180/// then the bandwidth will be 1Mbps for 1s, then 2Mbps for 2s, then 3Mbps for 3s.
181///
182/// The next_bw function either returns **the next bandwidth and its duration**
183/// in the sequence, or **None** if the trace goes to end.
184pub trait BwTrace: Send {
185    fn next_bw(&mut self) -> Option<(Bandwidth, Duration)>;
186}
187
188/// This is a trait that represents a trace of delays.
189///
190/// The trace is a sequence of `(delay, duration)` pairs.
191/// The delay describes how long a packet is delayed when going through.
192/// The duration is the time that the delay lasts.
193///
194/// For example, if the sequence is [(10ms, 1s), (20ms, 2s), (30ms, 3s)],
195/// then the delay will be 10ms for 1s, then 20ms for 2s, then 30ms for 3s.
196///
197/// The next_delay function either returns **the next delay and its duration**
198/// in the sequence, or **None** if the trace goes to end.
199pub trait DelayTrace: Send {
200    fn next_delay(&mut self) -> Option<(Delay, Duration)>;
201}
202
203/// This is a trait that represents a trace of per-packet delays.
204///
205/// The trace is a sequence of `delay`.
206/// The delay describes how long the packet is delayed when going through.
207///
208/// For example, if the sequence is [10ms, 20ms, 30ms],
209/// then the delay will be 10ms for the first packet, then 20ms for second, then 30ms for third.
210///
211/// The next_delay function either returns **the next delay**
212/// in the sequence, or **None** if the trace goes to end.
213pub trait DelayPerPacketTrace: Send {
214    fn next_delay(&mut self) -> Option<Delay>;
215}
216
217/// This is a trait that represents a trace of loss patterns.
218///
219/// The trace is a sequence of `(loss_pattern, duration)` pairs.
220/// The loss_pattern describes how packets are dropped when going through.
221/// The duration is the time that the loss_pattern lasts.
222///
223/// The next_loss function either returns **the next loss_pattern and its duration**
224/// in the sequence, or **None** if the trace goes to end.
225pub trait LossTrace: Send {
226    fn next_loss(&mut self) -> Option<(LossPattern, Duration)>;
227}
228
229/// This is a trait that represents a trace of duplicate patterns.
230///
231/// The trace is a sequence of `(duplicate_pattern, duration)` pairs.
232/// The duplicate_pattern describes how packets are duplicated when going through.
233/// The duration is the time that the duplicate_pattern lasts.
234///
235/// The next_duplicate function either returns **the next duplicate_pattern and its duration** in
236/// the sequence, or **None** if the trace goes to end.
237pub trait DuplicateTrace: Send {
238    fn next_duplicate(&mut self) -> Option<(DuplicatePattern, Duration)>;
239}
240
241/// The action a rwnd trace instructs the receiver to take at a single step.
242///
243/// At most one action is present per step; a step that only reconfigures the
244/// receive buffer (`set_rcv_buf`) without any read or observed-remaining update
245/// leaves [`RwndDecision::action`] as `None`.
246///
247/// - `AppRead` drives the receiver model by simulating the application reading
248///   `bytes` from the receive buffer; the resulting rwnd is computed from the
249///   buffer state.
250/// - `Remaining` skips the simulation and directly enforces an observed rwnd
251///   of `rwnd` bytes — useful for replaying captured traces where only the
252///   advertised window is known.
253#[derive(Debug, Clone, PartialEq)]
254pub enum RwndAction {
255    /// The simulated application reads this many bytes from the receive buffer at this step.
256    AppRead { bytes: u64 },
257    /// The remaining rwnd value observed immediately after the app consumes data at this step.
258    Remaining { rwnd: u64 },
259}
260
261/// A single receive-side decision emitted by a [`RwndTrace`].
262///
263/// Each step of a rwnd trace produces one `RwndDecision` paired with a
264/// [`Duration`] (see [`RwndTrace`]). Both fields are optional and independent:
265/// a step may resize the socket buffer, advance the receive model, both, or
266/// neither (though a step that sets neither is effectively a no-op).
267#[derive(Debug, Clone, PartialEq)]
268pub struct RwndDecision {
269    /// If `Some`, reconfigure the socket's receive buffer to this size at this step.
270    pub set_rcv_buf: Option<u64>,
271    /// If `Some`, the app-read or observed-remaining action for this step.
272    pub action: Option<RwndAction>,
273}
274
275/// This is a trait that represents a trace of receive-window decisions over time.
276///
277/// The trace is a sequence of `(rwnd_decision, duration)` pairs. The decision
278/// describes how the socket's receive buffer, the application's read behavior,
279/// and/or the observed remaining window change at this step; the duration is
280/// how long this configuration lasts before the next step applies.
281///
282/// For example, if the sequence is
283/// `[(set_rcv_buf=64KB, app_read=1KB, 1s), (rwnd_remaining=32KB, 2s)]`,
284/// then the receive buffer is resized to 64KB and the app reads 1KB for 1s,
285/// then the observed rwnd becomes 32KB for 2s.
286///
287/// Each `next_rwnd` call returns **the next decision and its duration** in the
288/// sequence, or **None** when the trace is exhausted. Mirrors the shape of
289/// [`BwTrace`], [`DelayTrace`], and [`LossTrace`].
290pub trait RwndTrace: Send {
291    fn next_rwnd(&mut self) -> Option<(RwndDecision, Duration)>;
292}
293
294#[cfg(test)]
295mod test {
296    use model::TraceBwConfig;
297
298    use self::model::bw::Forever;
299
300    use super::*;
301    #[cfg(feature = "serde")]
302    use crate::model::RepeatedBwPatternConfig;
303    use crate::model::{BwTraceConfig, NormalizedBwConfig, SawtoothBwConfig, StaticBwConfig};
304
305    #[test]
306    fn test_static_bw_model() {
307        let mut static_bw = StaticBwConfig::new()
308            .bw(Bandwidth::from_mbps(24))
309            .duration(Duration::from_secs(1))
310            .build();
311        assert_eq!(
312            static_bw.next_bw(),
313            Some((Bandwidth::from_mbps(24), Duration::from_secs(1)))
314        );
315    }
316
317    #[test]
318    fn test_normalized_bw_model() {
319        let mut normal_bw = NormalizedBwConfig::new()
320            .mean(Bandwidth::from_mbps(12))
321            .std_dev(Bandwidth::from_mbps(1))
322            .duration(Duration::from_secs(1))
323            .step(Duration::from_millis(100))
324            .seed(42)
325            .build();
326        assert_eq!(
327            normal_bw.next_bw(),
328            Some((Bandwidth::from_bps(12069427), Duration::from_millis(100)))
329        );
330        assert_eq!(
331            normal_bw.next_bw(),
332            Some((Bandwidth::from_bps(12132938), Duration::from_millis(100)))
333        );
334        let mut normal_bw = NormalizedBwConfig::new()
335            .mean(Bandwidth::from_mbps(12))
336            .std_dev(Bandwidth::from_mbps(1))
337            .duration(Duration::from_secs(1))
338            .step(Duration::from_millis(100))
339            .seed(42)
340            .upper_bound(Bandwidth::from_kbps(12100))
341            .lower_bound(Bandwidth::from_kbps(11900))
342            .build();
343        assert_eq!(
344            normal_bw.next_bw(),
345            Some((Bandwidth::from_bps(12069427), Duration::from_millis(100)))
346        );
347        assert_eq!(
348            normal_bw.next_bw(),
349            Some((Bandwidth::from_bps(12100000), Duration::from_millis(100)))
350        );
351    }
352
353    #[test]
354    fn test_sawtooth_bw_model() {
355        let mut sawtooth_bw = SawtoothBwConfig::new()
356            .bottom(Bandwidth::from_mbps(12))
357            .top(Bandwidth::from_mbps(16))
358            .duration(Duration::from_secs(1))
359            .step(Duration::from_millis(100))
360            .interval(Duration::from_millis(500))
361            .duty_ratio(0.8)
362            .build();
363        assert_eq!(
364            sawtooth_bw.next_bw(),
365            Some((Bandwidth::from_mbps(12), Duration::from_millis(100)))
366        );
367        assert_eq!(
368            sawtooth_bw.next_bw(),
369            Some((Bandwidth::from_mbps(13), Duration::from_millis(100)))
370        );
371        assert_eq!(
372            sawtooth_bw.next_bw(),
373            Some((Bandwidth::from_mbps(14), Duration::from_millis(100)))
374        );
375        assert_eq!(
376            sawtooth_bw.next_bw(),
377            Some((Bandwidth::from_mbps(15), Duration::from_millis(100)))
378        );
379        assert_eq!(
380            sawtooth_bw.next_bw(),
381            Some((Bandwidth::from_mbps(16), Duration::from_millis(100)))
382        );
383        assert_eq!(
384            sawtooth_bw.next_bw(),
385            Some((Bandwidth::from_mbps(12), Duration::from_millis(100)))
386        );
387        assert_eq!(
388            sawtooth_bw.next_bw(),
389            Some((Bandwidth::from_mbps(13), Duration::from_millis(100)))
390        );
391        assert_eq!(
392            sawtooth_bw.next_bw(),
393            Some((Bandwidth::from_mbps(14), Duration::from_millis(100)))
394        );
395        assert_eq!(
396            sawtooth_bw.next_bw(),
397            Some((Bandwidth::from_mbps(15), Duration::from_millis(100)))
398        );
399        let mut sawtooth_bw = SawtoothBwConfig::new()
400            .bottom(Bandwidth::from_mbps(12))
401            .top(Bandwidth::from_mbps(16))
402            .duration(Duration::from_secs(1))
403            .step(Duration::from_millis(100))
404            .interval(Duration::from_millis(500))
405            .duty_ratio(0.8)
406            .std_dev(Bandwidth::from_mbps(5))
407            .upper_noise_bound(Bandwidth::from_mbps(1))
408            .lower_noise_bound(Bandwidth::from_kbps(500))
409            .build();
410        assert_eq!(
411            sawtooth_bw.next_bw(),
412            Some((Bandwidth::from_bps(12347139), Duration::from_millis(100)))
413        );
414        assert_eq!(
415            sawtooth_bw.next_bw(),
416            Some((Bandwidth::from_bps(13664690), Duration::from_millis(100)))
417        );
418        assert_eq!(
419            sawtooth_bw.next_bw(),
420            Some((Bandwidth::from_mbps(15), Duration::from_millis(100)))
421        );
422        assert_eq!(
423            sawtooth_bw.next_bw(),
424            Some((Bandwidth::from_bps(14500000), Duration::from_millis(100)))
425        );
426    }
427
428    #[test]
429    fn test_trace_bw() {
430        let mut trace_bw = TraceBwConfig::new()
431            .pattern(vec![
432                (
433                    Duration::from_millis(1),
434                    vec![
435                        Bandwidth::from_kbps(29123),
436                        Bandwidth::from_kbps(41242),
437                        Bandwidth::from_kbps(7395),
438                    ],
439                ),
440                (
441                    Duration::from_millis(2),
442                    vec![Bandwidth::from_mbps(1), Bandwidth::from_kbps(8542)],
443                ),
444            ])
445            .build();
446
447        assert_eq!(
448            trace_bw.next_bw(),
449            Some((Bandwidth::from_bps(29123000), Duration::from_millis(1)))
450        );
451        assert_eq!(
452            trace_bw.next_bw(),
453            Some((Bandwidth::from_bps(41242000), Duration::from_millis(1)))
454        );
455        assert_eq!(
456            trace_bw.next_bw(),
457            Some((Bandwidth::from_bps(7395000), Duration::from_millis(1)))
458        );
459        assert_eq!(
460            trace_bw.next_bw(),
461            Some((Bandwidth::from_bps(1000000), Duration::from_millis(2)))
462        );
463        assert_eq!(
464            trace_bw.next_bw(),
465            Some((Bandwidth::from_bps(8542000), Duration::from_millis(2)))
466        );
467        assert_eq!(trace_bw.next_bw(), None);
468    }
469
470    #[test]
471    #[cfg(feature = "serde")]
472    fn test_model_serde() {
473        let a = vec![
474            Box::new(
475                StaticBwConfig::new()
476                    .bw(Bandwidth::from_mbps(12))
477                    .duration(Duration::from_secs(1)),
478            ) as Box<dyn BwTraceConfig>,
479            Box::new(
480                StaticBwConfig::new()
481                    .bw(Bandwidth::from_mbps(24))
482                    .duration(Duration::from_secs(1)),
483            ) as Box<dyn BwTraceConfig>,
484        ];
485        let ser =
486            Box::new(RepeatedBwPatternConfig::new().pattern(a).count(2)) as Box<dyn BwTraceConfig>;
487        let ser_str = serde_json::to_string(&ser).unwrap();
488        #[cfg(not(feature = "human"))]
489        let des_str = "{\"RepeatedBwPatternConfig\":{\"pattern\":[{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":12000000},\"duration\":{\"secs\":1,\"nanos\":0}}},{\"StaticBwConfig\":{\"bw\":{\"gbps\":0,\"bps\":24000000},\"duration\":{\"secs\":1,\"nanos\":0}}}],\"count\":2}}";
490        #[cfg(feature = "human")]
491        let des_str = "{\"RepeatedBwPatternConfig\":{\"pattern\":[{\"StaticBwConfig\":{\"bw\":\"12Mbps\",\"duration\":\"1s\"}},{\"StaticBwConfig\":{\"bw\":\"24Mbps\",\"duration\":\"1s\"}}],\"count\":2}}";
492        assert_eq!(ser_str, des_str);
493        let des: Box<dyn BwTraceConfig> = serde_json::from_str(des_str).unwrap();
494        let mut model = des.into_model();
495        assert_eq!(
496            model.next_bw(),
497            Some((Bandwidth::from_mbps(12), Duration::from_secs(1)))
498        );
499    }
500
501    #[test]
502    fn test_forever() {
503        let mut normal_bw = NormalizedBwConfig::new()
504            .mean(Bandwidth::from_mbps(12))
505            .std_dev(Bandwidth::from_mbps(1))
506            .duration(Duration::from_millis(200))
507            .step(Duration::from_millis(100))
508            .seed(42)
509            .build();
510        assert_eq!(
511            normal_bw.next_bw(),
512            Some((Bandwidth::from_bps(12069427), Duration::from_millis(100)))
513        );
514        assert_eq!(
515            normal_bw.next_bw(),
516            Some((Bandwidth::from_bps(12132938), Duration::from_millis(100)))
517        );
518        assert_eq!(normal_bw.next_bw(), None);
519        let normal_bw_config = NormalizedBwConfig::new()
520            .mean(Bandwidth::from_mbps(12))
521            .std_dev(Bandwidth::from_mbps(1))
522            .duration(Duration::from_millis(200))
523            .step(Duration::from_millis(100))
524            .seed(42);
525        let normal_bw_repeated = normal_bw_config.forever();
526        let mut model = Box::new(normal_bw_repeated).into_model();
527        assert_eq!(
528            model.next_bw(),
529            Some((Bandwidth::from_bps(12069427), Duration::from_millis(100)))
530        );
531        assert_eq!(
532            model.next_bw(),
533            Some((Bandwidth::from_bps(12132938), Duration::from_millis(100)))
534        );
535        assert_eq!(
536            model.next_bw(),
537            Some((Bandwidth::from_bps(12069427), Duration::from_millis(100)))
538        );
539        assert_eq!(
540            model.next_bw(),
541            Some((Bandwidth::from_bps(12132938), Duration::from_millis(100)))
542        );
543    }
544
545    #[test]
546    #[cfg(feature = "human")]
547    fn test_compatibility_with_figment() {
548        use figment::{
549            providers::{Format, Json},
550            Figment,
551        };
552
553        let config = r##"
554{
555   "RepeatedBwPatternConfig":{
556      "pattern":[
557         {
558            "TraceBwConfig":{
559               "pattern":[
560                  [
561                     "25ms",
562                     [
563                        "10Mbps",
564                        "20Mbps"
565                     ]
566                  ],
567                  [
568                     "2ms",
569                     [
570                        "11Mbps",
571                        "23Mbps"
572                     ]
573                  ]
574               ]
575            }
576         },
577         {
578            "SawtoothBwConfig":{
579               "bottom":"10Mbps",
580               "top":"20Mbps",
581               "step":"1ms",
582               "interval":"10ms",
583               "duty_ratio":0.5
584            }
585         }
586      ],
587      "count":0
588   }
589}"##;
590
591        let trace: Box<dyn BwTraceConfig> = Figment::new()
592            .merge(Json::string(config))
593            .extract()
594            .unwrap();
595
596        let mut model = trace.into_model();
597
598        assert_eq!(
599            model.next_bw(),
600            Some((Bandwidth::from_mbps(10), Duration::from_millis(25)))
601        );
602        assert_eq!(
603            model.next_bw(),
604            Some((Bandwidth::from_mbps(20), Duration::from_millis(25)))
605        );
606        assert_eq!(
607            model.next_bw(),
608            Some((Bandwidth::from_mbps(11), Duration::from_millis(2)))
609        );
610        assert_eq!(
611            model.next_bw(),
612            Some((Bandwidth::from_mbps(23), Duration::from_millis(2)))
613        );
614        assert_eq!(
615            model.next_bw(),
616            Some((Bandwidth::from_mbps(10), Duration::from_millis(1)))
617        );
618        assert_eq!(
619            model.next_bw(),
620            Some((Bandwidth::from_mbps(12), Duration::from_millis(1)))
621        );
622        assert_eq!(
623            model.next_bw(),
624            Some((Bandwidth::from_mbps(14), Duration::from_millis(1)))
625        );
626        assert_eq!(
627            model.next_bw(),
628            Some((Bandwidth::from_mbps(16), Duration::from_millis(1)))
629        );
630        assert_eq!(
631            model.next_bw(),
632            Some((Bandwidth::from_mbps(18), Duration::from_millis(1)))
633        );
634    }
635}