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}