Skip to main content

netem_trace/model/
rwnd.rs

1//! This module contains some predefined rwnd trace models.
2//!
3//! Enabled with feature `rwnd-model` or `model`.
4//!
5//! ## Predefined models
6//!
7//! - [`StaticRwnd`]: A trace model with a single rwnd decision.
8//! - [`RepeatedRwndPattern`]: A trace model with a repeated rwnd pattern.
9//!
10//! ## Step semantics
11//!
12//! A step carries two independent fields, `set_rcv_buf` and `app_read_bytes`,
13//! and between them they say what the receiver does for the step's duration.
14//! See [`RwndDecision`] for the full table; in short, `set_rcv_buf` states a
15//! buffer the application keeps drained (so the window stays at that value),
16//! and `app_read_bytes` states an application that reads only so much (so the
17//! window decays as the backlog grows).
18//!
19//! A step carrying **both** resizes the buffer first and takes the read against
20//! the new size. The window is then `set_rcv_buf - unread`, not pinned at
21//! `set_rcv_buf`: stating a read means the application is the bottleneck, so the
22//! "application keeps up" half of a buffer-only step no longer applies. The
23//! unread backlog carries across the resize untouched, so a resize to at or
24//! below the current backlog advertises a zero window until the application
25//! catches up.
26//!
27//! ## Examples
28//!
29//! An example to build model from configuration:
30//!
31//! ```
32//! # use netem_trace::model::StaticRwndConfig;
33//! # use netem_trace::{Duration, RwndTrace};
34//! let mut static_rwnd = StaticRwndConfig::new()
35//!     .set_rcv_buf(65536)
36//!     .app_read(1024)
37//!     .duration(Duration::from_secs(1))
38//!     .build();
39//! let (decision, duration) = static_rwnd.next_rwnd().unwrap();
40//! assert_eq!(decision.set_rcv_buf, Some(65536));
41//! assert_eq!(decision.app_read_bytes, Some(1024));
42//! assert_eq!(duration, Duration::from_secs(1));
43//! assert_eq!(static_rwnd.next_rwnd(), None);
44//! ```
45//!
46//! The step above carries both fields, so it resizes the buffer to 64 KiB and
47//! then has the application read 1 KiB from it. The window that follows is
48//! `65536 - unread` rather than a window pinned at 65536 -- compare the
49//! buffer-only step below, which does pin it:
50//!
51//! ```
52//! # use netem_trace::model::StaticRwndConfig;
53//! # use netem_trace::{Duration, RwndTrace};
54//! // Buffer only: the window is held at 65536 and the application keeps up.
55//! let mut pinned = StaticRwndConfig::new()
56//!     .set_rcv_buf(65536)
57//!     .duration(Duration::from_secs(1))
58//!     .build();
59//! let (decision, _) = pinned.next_rwnd().unwrap();
60//! assert_eq!(decision.set_rcv_buf, Some(65536));
61//! assert_eq!(decision.app_read_bytes, None);
62//!
63//! // Both: same buffer, but the application now reads only 1 KiB per step, so
64//! // the window follows the backlog instead of staying at 65536.
65//! let mut app_limited = StaticRwndConfig::new()
66//!     .set_rcv_buf(65536)
67//!     .app_read(1024)
68//!     .duration(Duration::from_secs(1))
69//!     .build();
70//! let (decision, _) = app_limited.next_rwnd().unwrap();
71//! assert_eq!(decision.set_rcv_buf, Some(65536));
72//! assert_eq!(decision.app_read_bytes, Some(1024));
73//!
74//! // Shrinking the buffer under a standing backlog is stated the same way; the
75//! // window saturates at zero until the application reads its way back under it.
76//! let mut shrink = StaticRwndConfig::new()
77//!     .set_rcv_buf(8192)
78//!     .app_read(0)
79//!     .duration(Duration::from_secs(1))
80//!     .build();
81//! let (decision, _) = shrink.next_rwnd().unwrap();
82//! assert_eq!(decision.set_rcv_buf, Some(8192));
83//! assert_eq!(decision.app_read_bytes, Some(0));
84//! ```
85//!
86//! A more common use case is to build model from a configuration file (e.g. json file):
87//!
88//! ```
89//! # use netem_trace::model::{StaticRwndConfig, RwndTraceConfig};
90//! # use netem_trace::{Duration, RwndTrace};
91//! # #[cfg(feature = "human")]
92//! # let config_file_content = "{\"RepeatedRwndPatternConfig\":{\"pattern\":[{\"StaticRwndConfig\":{\"duration\":\"1s\",\"set_rcv_buf\":65536}},{\"StaticRwndConfig\":{\"duration\":\"1s\",\"app_read_bytes\":1024}}],\"count\":2}}";
93//! // The content would be "{\"RepeatedRwndPatternConfig\":{\"pattern\":[{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"set_rcv_buf\":65536}},{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"app_read_bytes\":1024}}],\"count\":2}}"
94//! // if the `human` feature is not enabled.
95//! # #[cfg(not(feature = "human"))]
96//! let config_file_content = "{\"RepeatedRwndPatternConfig\":{\"pattern\":[{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"set_rcv_buf\":65536}},{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"app_read_bytes\":1024}}],\"count\":2}}";
97//! let des: Box<dyn RwndTraceConfig> = serde_json::from_str(config_file_content).unwrap();
98//! let mut model = des.into_model();
99//! let (decision, _) = model.next_rwnd().unwrap();
100//! assert_eq!(decision.set_rcv_buf, Some(65536));
101//! let (decision, _) = model.next_rwnd().unwrap();
102//! assert_eq!(decision.app_read_bytes, Some(1024));
103//! ```
104use crate::{Duration, RwndDecision, RwndTrace};
105use dyn_clone::DynClone;
106
107/// This trait is used to convert a rwnd trace configuration into a rwnd trace model.
108///
109/// Since trace model is often configured with files and often has inner states which
110/// is not suitable to be serialized/deserialized, this trait makes it possible to
111/// separate the configuration part into a simple struct for serialization/deserialization, and
112/// construct the model from the configuration.
113#[cfg_attr(feature = "serde", typetag::serde)]
114pub trait RwndTraceConfig: DynClone + Send {
115    fn into_model(self: Box<Self>) -> Box<dyn RwndTrace>;
116}
117
118dyn_clone::clone_trait_object!(RwndTraceConfig);
119
120#[cfg(feature = "serde")]
121use serde::{Deserialize, Serialize};
122
123/// The model of a static rwnd trace: a single decision valid for one duration.
124///
125/// ## Examples
126///
127/// ```
128/// # use netem_trace::model::StaticRwndConfig;
129/// # use netem_trace::{Duration, RwndTrace};
130/// let mut static_rwnd = StaticRwndConfig::new()
131///     .set_rcv_buf(65536)
132///     .duration(Duration::from_secs(1))
133///     .build();
134/// let (decision, duration) = static_rwnd.next_rwnd().unwrap();
135/// assert_eq!(decision.set_rcv_buf, Some(65536));
136/// assert_eq!(decision.app_read_bytes, None);
137/// assert_eq!(duration, Duration::from_secs(1));
138/// assert_eq!(static_rwnd.next_rwnd(), None);
139/// ```
140#[derive(Debug, Clone)]
141pub struct StaticRwnd {
142    pub decision: RwndDecision,
143    pub duration: Option<Duration>,
144}
145
146/// The configuration struct for [`StaticRwnd`].
147///
148/// The serialized JSON form is **flat**: a step looks like
149/// `{"duration":"1s","set_rcv_buf":65536}` or
150/// `{"duration":"1s","app_read_bytes":1024}`, and may carry both keys.
151///
152/// The two fields are independent -- there is no invariant to enforce, so
153/// `Serialize`/`Deserialize` are derived. A step with neither field is valid and
154/// carries the previous configuration forward.
155///
156/// Unknown keys are rejected rather than ignored. The schema dropped
157/// `rwnd_remaining`, and serde's default of skipping what it does not recognise
158/// would turn a trace written against the old schema into a run of steps that
159/// state nothing -- a replay that looks healthy while reproducing no receiver at
160/// all. Failing to deserialize names the offending field instead.
161#[cfg_attr(
162    feature = "serde",
163    derive(Serialize, Deserialize),
164    serde(default, deny_unknown_fields)
165)]
166#[derive(Debug, Clone, Default)]
167pub struct StaticRwndConfig {
168    #[cfg_attr(
169        feature = "human",
170        serde(with = "humantime_serde"),
171        serde(skip_serializing_if = "Option::is_none")
172    )]
173    #[cfg_attr(
174        all(feature = "serde", not(feature = "human")),
175        serde(skip_serializing_if = "Option::is_none")
176    )]
177    pub duration: Option<Duration>,
178    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
179    pub set_rcv_buf: Option<u64>,
180    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
181    pub app_read_bytes: Option<u64>,
182}
183
184/// The model contains an array of rwnd trace models.
185///
186/// Combine multiple rwnd trace models into one rwnd pattern,
187/// and repeat the pattern for `count` times.
188///
189/// If `count` is 0, the pattern will be repeated forever.
190///
191/// ## Examples
192///
193/// The most common use case is to read from a configuration file and
194/// deserialize it into a [`RepeatedRwndPatternConfig`]:
195///
196/// ```
197/// # use netem_trace::model::{StaticRwndConfig, RwndTraceConfig};
198/// # use netem_trace::{Duration, RwndTrace};
199/// # #[cfg(feature = "human")]
200/// # let config_file_content = "{\"RepeatedRwndPatternConfig\":{\"pattern\":[{\"StaticRwndConfig\":{\"duration\":\"1s\",\"set_rcv_buf\":65536,\"app_read_bytes\":1024}}],\"count\":2}}";
201/// # #[cfg(not(feature = "human"))]
202/// let config_file_content = "{\"RepeatedRwndPatternConfig\":{\"pattern\":[{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"set_rcv_buf\":65536,\"app_read_bytes\":1024}}],\"count\":2}}";
203/// let des: Box<dyn RwndTraceConfig> = serde_json::from_str(config_file_content).unwrap();
204/// let mut model = des.into_model();
205/// let (decision, _) = model.next_rwnd().unwrap();
206/// assert_eq!(decision.set_rcv_buf, Some(65536));
207/// assert_eq!(decision.app_read_bytes, Some(1024));
208/// ```
209pub struct RepeatedRwndPattern {
210    pub pattern: Vec<Box<dyn RwndTraceConfig>>,
211    pub count: usize,
212    current_model: Option<Box<dyn RwndTrace>>,
213    current_cycle: usize,
214    current_pattern: usize,
215}
216
217/// The configuration struct for [`RepeatedRwndPattern`].
218///
219/// See [`RepeatedRwndPattern`] for more details.
220#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(default))]
221#[derive(Default, Clone)]
222pub struct RepeatedRwndPatternConfig {
223    pub pattern: Vec<Box<dyn RwndTraceConfig>>,
224    pub count: usize,
225}
226
227impl RwndTrace for StaticRwnd {
228    fn next_rwnd(&mut self) -> Option<(RwndDecision, Duration)> {
229        if let Some(duration) = self.duration.take() {
230            if duration.is_zero() {
231                None
232            } else {
233                Some((self.decision, duration))
234            }
235        } else {
236            None
237        }
238    }
239}
240
241impl RwndTrace for RepeatedRwndPattern {
242    fn next_rwnd(&mut self) -> Option<(RwndDecision, Duration)> {
243        let pattern_len = self.pattern.len();
244        // Allow at most pattern_len + 1 consecutive inner-None results before
245        // giving up. The +1 covers a possibly-exhausted current_model at entry;
246        // after that, each remaining slot is a fresh clone whose behaviour is
247        // deterministic. If all pattern_len fresh clones return None, the
248        // pattern will never produce a value regardless of count.
249        let mut budget = pattern_len + 1;
250        loop {
251            if pattern_len == 0 || (self.count != 0 && self.current_cycle >= self.count) {
252                return None;
253            }
254            if budget == 0 {
255                return None;
256            }
257            if self.current_model.is_none() {
258                self.current_model = Some(self.pattern[self.current_pattern].clone().into_model());
259            }
260            match self.current_model.as_mut().unwrap().next_rwnd() {
261                Some(item) => return Some(item),
262                None => {
263                    self.current_model = None;
264                    budget -= 1;
265                    self.current_pattern += 1;
266                    if self.current_pattern >= pattern_len {
267                        self.current_pattern = 0;
268                        self.current_cycle += 1;
269                        if self.count != 0 && self.current_cycle >= self.count {
270                            return None;
271                        }
272                    }
273                }
274            }
275        }
276    }
277}
278
279impl StaticRwndConfig {
280    pub fn new() -> Self {
281        Self {
282            duration: None,
283            set_rcv_buf: None,
284            app_read_bytes: None,
285        }
286    }
287
288    pub fn duration(mut self, duration: Duration) -> Self {
289        self.duration = Some(duration);
290        self
291    }
292
293    /// Size the receive buffer.
294    ///
295    /// On its own this also holds the advertised window at that value, with the
296    /// application draining continuously. Combined with [`Self::app_read`] it
297    /// only sets the size: the resize applies first, the read is taken against
298    /// the new size, and the window then follows `set_rcv_buf - unread` rather
299    /// than being pinned.
300    pub fn set_rcv_buf(mut self, set_rcv_buf: u64) -> Self {
301        self.set_rcv_buf = Some(set_rcv_buf);
302        self
303    }
304
305    /// The application reads exactly this many bytes over the step, then stops.
306    ///
307    /// The window follows from what is left unread of the standing buffer, or
308    /// of the buffer this step sets when combined with [`Self::set_rcv_buf`].
309    /// The backlog carries across such a resize untouched, so the window
310    /// saturates at zero if the new size is at or below it.
311    pub fn app_read(mut self, bytes: u64) -> Self {
312        self.app_read_bytes = Some(bytes);
313        self
314    }
315
316    pub fn build(self) -> StaticRwnd {
317        StaticRwnd {
318            decision: RwndDecision {
319                set_rcv_buf: self.set_rcv_buf,
320                app_read_bytes: self.app_read_bytes,
321            },
322            duration: Some(self.duration.unwrap_or_else(|| Duration::from_secs(1))),
323        }
324    }
325}
326
327impl RepeatedRwndPatternConfig {
328    pub fn new() -> Self {
329        Self {
330            pattern: vec![],
331            count: 0,
332        }
333    }
334
335    pub fn pattern(mut self, pattern: Vec<Box<dyn RwndTraceConfig>>) -> Self {
336        self.pattern = pattern;
337        self
338    }
339
340    pub fn count(mut self, count: usize) -> Self {
341        self.count = count;
342        self
343    }
344
345    pub fn build(self) -> RepeatedRwndPattern {
346        RepeatedRwndPattern {
347            pattern: self.pattern,
348            count: self.count,
349            current_model: None,
350            current_cycle: 0,
351            current_pattern: 0,
352        }
353    }
354}
355
356macro_rules! impl_rwnd_trace_config {
357    ($name:ident) => {
358        #[cfg_attr(feature = "serde", typetag::serde)]
359        impl RwndTraceConfig for $name {
360            fn into_model(self: Box<$name>) -> Box<dyn RwndTrace> {
361                Box::new(self.build())
362            }
363        }
364    };
365}
366
367impl_rwnd_trace_config!(StaticRwndConfig);
368impl_rwnd_trace_config!(RepeatedRwndPatternConfig);
369
370#[cfg(test)]
371mod test {
372    use super::*;
373    use crate::RwndTrace;
374
375    /// Both fields on one step: the buffer is resized and the read is taken
376    /// against the new size, so the decision carries both rather than one
377    /// overriding the other.
378    #[test]
379    fn test_static_rwnd_model_buffer_and_app_read() {
380        let mut static_rwnd = StaticRwndConfig::new()
381            .set_rcv_buf(65536)
382            .app_read(1024)
383            .duration(Duration::from_secs(1))
384            .build();
385        let (decision, duration) = static_rwnd.next_rwnd().unwrap();
386        assert_eq!(decision.set_rcv_buf, Some(65536));
387        assert_eq!(decision.app_read_bytes, Some(1024));
388        assert_eq!(duration, Duration::from_secs(1));
389        assert_eq!(static_rwnd.next_rwnd(), None);
390    }
391
392    /// A buffer on its own is a complete statement: the window sits there and
393    /// the application keeps up. This is what used to need `rwnd_remaining`.
394    #[test]
395    fn test_static_rwnd_model_buffer_only() {
396        let mut static_rwnd = StaticRwndConfig::new()
397            .set_rcv_buf(32768)
398            .duration(Duration::from_secs(2))
399            .build();
400        let (decision, duration) = static_rwnd.next_rwnd().unwrap();
401        assert_eq!(decision.set_rcv_buf, Some(32768));
402        assert_eq!(decision.app_read_bytes, None);
403        assert_eq!(duration, Duration::from_secs(2));
404        assert_eq!(static_rwnd.next_rwnd(), None);
405    }
406
407    /// A step may carry neither field, and then it simply holds whatever the
408    /// previous step configured for its duration.
409    #[test]
410    fn test_static_rwnd_model_carries_forward() {
411        let mut model = StaticRwndConfig::new()
412            .duration(Duration::from_secs(1))
413            .build();
414        let (decision, duration) = model.next_rwnd().unwrap();
415        assert_eq!(decision.set_rcv_buf, None);
416        assert_eq!(decision.app_read_bytes, None);
417        assert_eq!(duration, Duration::from_secs(1));
418    }
419
420    #[test]
421    fn test_repeated_rwnd_pattern() {
422        let pat = vec![
423            Box::new(
424                StaticRwndConfig::new()
425                    .set_rcv_buf(65536)
426                    .duration(Duration::from_secs(1)),
427            ) as Box<dyn RwndTraceConfig>,
428            Box::new(
429                StaticRwndConfig::new()
430                    .app_read(1024)
431                    .duration(Duration::from_secs(1)),
432            ) as Box<dyn RwndTraceConfig>,
433        ];
434        let mut model = RepeatedRwndPatternConfig::new()
435            .pattern(pat)
436            .count(2)
437            .build();
438        let next = model.next_rwnd().unwrap();
439        assert_eq!(next.0.set_rcv_buf, Some(65536));
440        assert_eq!(next.1, Duration::from_secs(1));
441        assert_eq!(model.next_rwnd().unwrap().0.app_read_bytes, Some(1024));
442        assert_eq!(model.next_rwnd().unwrap().0.set_rcv_buf, Some(65536));
443        assert_eq!(model.next_rwnd().unwrap().0.app_read_bytes, Some(1024));
444        assert_eq!(model.next_rwnd(), None);
445    }
446
447    #[test]
448    #[cfg(feature = "serde")]
449    fn test_serde_roundtrip_buffer_only() {
450        let cfg = Box::new(
451            StaticRwndConfig::new()
452                .set_rcv_buf(65536)
453                .duration(Duration::from_secs(1)),
454        ) as Box<dyn RwndTraceConfig>;
455        let ser_str = serde_json::to_string(&cfg).unwrap();
456        #[cfg(feature = "human")]
457        let expected = "{\"StaticRwndConfig\":{\"duration\":\"1s\",\"set_rcv_buf\":65536}}";
458        #[cfg(not(feature = "human"))]
459        let expected =
460            "{\"StaticRwndConfig\":{\"duration\":{\"secs\":1,\"nanos\":0},\"set_rcv_buf\":65536}}";
461        assert_eq!(ser_str, expected);
462
463        let des: Box<dyn RwndTraceConfig> = serde_json::from_str(&ser_str).unwrap();
464        let mut model = des.into_model();
465        let (decision, duration) = model.next_rwnd().unwrap();
466        assert_eq!(decision.set_rcv_buf, Some(65536));
467        assert_eq!(decision.app_read_bytes, None);
468        assert_eq!(duration, Duration::from_secs(1));
469    }
470
471    /// Both keys on one step is legal now, and round-trips.
472    #[test]
473    #[cfg(feature = "serde")]
474    fn test_serde_roundtrip_both_fields() {
475        let json = "{\"StaticRwndConfig\":{\"set_rcv_buf\":131072,\"app_read_bytes\":4096}}";
476        let des: Box<dyn RwndTraceConfig> = serde_json::from_str(json).unwrap();
477        let mut model = des.into_model();
478        let (decision, _) = model.next_rwnd().unwrap();
479        assert_eq!(decision.set_rcv_buf, Some(131072));
480        assert_eq!(decision.app_read_bytes, Some(4096));
481    }
482
483    #[test]
484    #[cfg(feature = "serde")]
485    fn test_serde_omits_absent_fields() {
486        let cfg = Box::new(StaticRwndConfig::new().app_read(0)) as Box<dyn RwndTraceConfig>;
487        let ser_str = serde_json::to_string(&cfg).unwrap();
488        assert!(!ser_str.contains("set_rcv_buf"), "got: {ser_str}");
489        assert!(ser_str.contains("app_read_bytes"), "got: {ser_str}");
490    }
491
492    /// A trace written against the old schema names a field that no longer
493    /// exists. Rejecting it is the point: silently ignoring `rwnd_remaining`
494    /// would turn every window step into a no-op and replay a trace that says
495    /// nothing, which looks like a healthy run producing wrong numbers.
496    #[test]
497    #[cfg(feature = "serde")]
498    fn test_serde_rejects_the_old_rwnd_remaining_field() {
499        let json = "{\"StaticRwndConfig\":{\"rwnd_remaining\":32768}}";
500        let result: Result<Box<dyn RwndTraceConfig>, _> = serde_json::from_str(json);
501        let err = result
502            .err()
503            .expect("a trace using the removed field should not deserialize")
504            .to_string();
505        assert!(err.contains("rwnd_remaining"), "got: {err}");
506    }
507
508    #[test]
509    fn test_repeated_rwnd_pattern_all_zero_duration_terminates() {
510        // All inner models have duration == 0 and return None immediately.
511        // With count == 0 (infinite repeat) a recursive implementation would
512        // spin forever; the loop-based one must return None promptly.
513        let pat = vec![
514            Box::new(
515                StaticRwndConfig::new()
516                    .app_read(1024)
517                    .duration(Duration::ZERO),
518            ) as Box<dyn RwndTraceConfig>,
519            Box::new(
520                StaticRwndConfig::new()
521                    .set_rcv_buf(32768)
522                    .duration(Duration::ZERO),
523            ) as Box<dyn RwndTraceConfig>,
524        ];
525        let mut model = RepeatedRwndPatternConfig::new()
526            .pattern(pat)
527            .count(0) // infinite
528            .build();
529        assert_eq!(model.next_rwnd(), None);
530    }
531}