Skip to main content

maybenot/
framework.rs

1//! Maybenot is a framework for traffic analysis defenses that hide patterns in
2//! encrypted communication.
3
4use rand_core::RngCore;
5
6use crate::{Error, Machine, TriggerAction, TriggerEvent, action, constants, counter, event};
7
8use self::action::Action;
9use self::constants::{STATE_END, STATE_LIMIT_MAX, STATE_SIGNAL};
10use self::counter::Operation;
11use self::event::Event;
12use crate::time::Duration as _;
13
14/// An opaque token representing one machine running inside the framework.
15/// Values are guaranteed to be in the range 0..[Framework::num_machines], so
16/// raw values using [`MachineId::into_raw`] are suitable for indexing a slice
17/// of of at least [`Framework::num_machines`] elements. This is handy for
18/// framework integration and keeping associated state with a performance focus,
19/// but care must be taken to avoid out-of-bounds accesses (i.e., not very Rust
20/// idiomatic).
21#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
22pub struct MachineId(usize);
23
24impl MachineId {
25    /// Create a new machine identifier from a raw integer. Intended for use
26    /// with the `machine` field of [`TriggerAction`] and [`TriggerEvent`]. For
27    /// testing and FFI-wrapper purposes only. For regular use, use
28    /// [`MachineId`] returned by [Framework::trigger_events]. Triggering an
29    /// event in the framework for a machine that does not exist does not raise
30    /// a panic or any error.
31    pub fn from_raw(raw: usize) -> Self {
32        MachineId(raw)
33    }
34
35    /// Return the raw integer representation of the machine identifier. For
36    /// testing and FFI-wrapper purposes only. For regular use, use the
37    /// [`MachineId`] returned by [Framework::trigger_events].
38    pub fn into_raw(self) -> usize {
39        self.0
40    }
41}
42
43#[derive(Debug, Clone)]
44struct MachineRuntime<T: crate::time::Instant> {
45    current_state: usize,
46    state_limit: u64,
47    padding_sent: u64,
48    normal_sent: u64,
49    blocking_duration: T::Duration,
50    machine_start: T,
51    allowed_blocked_microsec: T::Duration,
52    counter_a: u64,
53    counter_b: u64,
54}
55
56#[derive(PartialEq)]
57enum StateChange {
58    Changed,
59    Unchanged,
60}
61
62/// An internal signal target for signaling other machines. A machine will not
63/// signal itself, but, if multiple machines send signals at the same time, then
64/// a signal will be sent to all machines.
65#[derive(Clone, Debug)]
66enum SignalTarget {
67    All,
68    AllExcept(usize),
69}
70
71/// An instance of the Maybenot framework.
72///
73/// An instance of the [`Framework`] repeatedly takes as *input* one or more
74/// [`TriggerEvent`] describing the encrypted traffic going over an encrypted
75/// channel, and produces as *output* zero or more [`TriggerAction`], such as to
76/// *send padding* traffic or *block outgoing* traffic. One or more [`Machine`]
77/// determine what [`TriggerAction`] to take based on [`TriggerEvent`].
78#[derive(Clone, Debug)]
79pub struct Framework<M, R, T = std::time::Instant>
80where
81    T: crate::time::Instant,
82{
83    // updated each time the framework is triggered
84    pub(crate) current_time: T,
85    // random number generator, used for sampling distributions and transitions
86    rng: R,
87    // we allocate the actions vector once and reuse it, handing out references
88    // as part of the iterator in [`Framework::trigger_events`].
89    pub(crate) actions: Vec<Option<TriggerAction<T>>>,
90    // the machines are immutable, but we need to keep track of their runtime
91    // state (size independent of number of states in the machine).
92    machines: M,
93    runtime: Vec<MachineRuntime<T>>,
94    // padding accounting
95    max_padding_frac: f64,
96    normal_sent_packets: u64,
97    padding_sent_packets: u64,
98    // blocking accounting
99    max_blocking_frac: f64,
100    blocking_duration: T::Duration,
101    blocking_started: T,
102    blocking_active: bool,
103    // for internal signaling: if set, specifies the target machines to signal
104    signal_pending: Option<SignalTarget>,
105    // only allow each counter to be zeroed once per trigger_events call
106    counter_zeroed_once: (bool, bool),
107    framework_start: T,
108}
109
110impl<M, R, T> Framework<M, R, T>
111where
112    M: AsRef<[Machine]>,
113    R: RngCore,
114    T: crate::time::Instant,
115{
116    /// Create a new framework instance with zero or more [`Machine`].
117    ///
118    /// The max padding/blocking fractions are enforced as a total across all machines.
119    /// The only way those limits can be violated are through
120    /// [`Machine::allowed_padding_packets`] and
121    /// [`Machine::allowed_blocked_microsec`], respectively.
122    ///
123    /// The current time is handed to the framework here (and later in [`Self::trigger_events()`])
124    /// to make some types of use cases of the framework easier (weird machines and
125    /// for simulation). The generic time type also allows for using custom time sources.
126    /// This can for example improve performance.
127    ///
128    /// Returns an error on any invalid [`Machine`] or limits not being fractions [0.0, 1.0].
129    pub fn new(
130        machines: M,
131        max_padding_frac: f64,
132        max_blocking_frac: f64,
133        current_time: T,
134        rng: R,
135    ) -> Result<Self, Error> {
136        if !(0.0..=1.0).contains(&max_padding_frac) {
137            Err(Error::PaddingLimit)?;
138        }
139        if !(0.0..=1.0).contains(&max_blocking_frac) {
140            Err(Error::BlockingLimit)?;
141        }
142
143        let mut runtime = Vec::with_capacity(machines.as_ref().len());
144        for m in machines.as_ref() {
145            m.validate()?;
146            runtime.push(MachineRuntime {
147                current_state: 0,
148                state_limit: 0,
149                padding_sent: 0,
150                normal_sent: 0,
151                blocking_duration: T::Duration::zero(),
152                machine_start: current_time,
153                allowed_blocked_microsec: T::Duration::from_micros(m.allowed_blocked_microsec),
154                counter_a: 0,
155                counter_b: 0,
156            });
157        }
158
159        let actions = vec![None; machines.as_ref().len()];
160
161        // take ownership of rng before using it below to sample limits
162        let mut s = Self {
163            actions,
164            machines,
165            runtime,
166            current_time,
167            rng,
168            max_blocking_frac,
169            max_padding_frac,
170            framework_start: current_time,
171            blocking_active: false,
172            blocking_started: current_time,
173            blocking_duration: T::Duration::zero(),
174            padding_sent_packets: 0,
175            normal_sent_packets: 0,
176            signal_pending: None,
177            counter_zeroed_once: (false, false),
178        };
179
180        for (runtime, machine) in s.runtime.iter_mut().zip(s.machines.as_ref().iter()) {
181            if let Some(action) = machine.states[0].action {
182                runtime.state_limit = action.sample_limit(&mut s.rng);
183            }
184        }
185
186        Ok(s)
187    }
188
189    /// Returns the number of machines in the framework.
190    pub fn num_machines(&self) -> usize {
191        self.machines.as_ref().len()
192    }
193
194    /// Returns true if all machines have reached the end state. This typically
195    /// means that the framework should be dropped (remember to let all
196    /// triggered actions expire and their effects be fully realized though).
197    pub fn all_machines_ended(&self) -> bool {
198        // TODO: consider if this functionality should be a TriggerAction
199        // instead, but could be problematic with the action rate limiting
200        self.runtime.iter().all(|r| r.current_state == STATE_END)
201    }
202
203    /// Trigger zero or more [`TriggerEvent`] for all machines running in the
204    /// framework.
205    ///
206    /// The `current_time` SHOULD be the current time at the time of calling the
207    /// method (e.g., [`Instant::now()`](std::time::Instant::now())).
208    ///
209    /// In more detail, the `current_time` SHOULD be a monotonically
210    /// nondecreasing clock. This means that the time passed SHOULD never be
211    /// earlier than what was given to [`Framework::new()`] or a previous call
212    /// to `trigger_events` for the same framework instance. If this requirement
213    /// is not followed, blocking durations MAY be inaccurately accounted for,
214    /// leading to less or more [`TriggerAction::BlockOutgoing`] than intended
215    /// by set framework and machine limits. The consequences of this depend on
216    /// the running machines (e.g., a machine may also pad as a consequence of
217    /// blocking) and the use-case for the user of the framework.
218    ///
219    /// Returns an iterator of zero or more [`TriggerAction`] that MUST be taken
220    /// by the caller.
221    pub fn trigger_events<'a>(
222        &'a mut self,
223        events: &[TriggerEvent],
224        current_time: T,
225    ) -> impl Iterator<Item = &'a TriggerAction<T>> + use<'a, M, R, T> {
226        // reset all actions
227        self.actions.fill(None);
228
229        // reset flags for zeroed counters (allowed to zero once per call)
230        self.counter_zeroed_once = (false, false);
231
232        // Process all events: note that each event may lead to up to one action
233        // per machine, but that future events may replace those actions. Under
234        // load, this is preferable (because something already happened before
235        // we could cause an action, so better to catch up).
236        self.current_time = current_time;
237        for e in events {
238            self.process_event(e);
239        }
240
241        // handle internal signaling: at most one signal per call to
242        // trigger_events for sake of batching remaining a safety mechanism for
243        // integrators (NOTE how self.signal_pending is consumed here with
244        // take())
245        if let Some(signal) = self.signal_pending.take() {
246            // keep track of if we should exclude a machine
247            let excluded = match signal {
248                SignalTarget::All => None,
249                SignalTarget::AllExcept(excluded) => Some(excluded),
250            };
251
252            // signal all machines, except the excluded one
253            for mi in 0..self.runtime.len() {
254                if let Some(excluded) = excluded {
255                    if excluded == mi {
256                        continue;
257                    }
258                }
259                self.transition(mi, Event::Signal);
260            }
261
262            // edge case: if the signalling above resulted in another signal AND
263            // we excluded a machine, then we need to signal the excluded
264            // machine as well (per definition, the signal must have come from
265            // another machine)
266            if self.signal_pending.take().is_some() {
267                if let Some(excluded) = excluded {
268                    self.transition(excluded, Event::Signal);
269                }
270            }
271        }
272
273        // only return actions, no None
274        self.actions.iter().filter_map(|action| action.as_ref())
275    }
276
277    fn process_event(&mut self, e: &TriggerEvent) {
278        match e {
279            TriggerEvent::NormalRecv => {
280                // no special accounting needed
281                for mi in 0..self.runtime.len() {
282                    self.transition(mi, Event::NormalRecv);
283                }
284            }
285            TriggerEvent::PaddingRecv => {
286                // no special accounting needed
287                for mi in 0..self.runtime.len() {
288                    self.transition(mi, Event::PaddingRecv);
289                }
290            }
291            TriggerEvent::TunnelRecv => {
292                // no special accounting needed
293                for mi in 0..self.runtime.len() {
294                    self.transition(mi, Event::TunnelRecv);
295                }
296            }
297            TriggerEvent::NormalSent => {
298                self.normal_sent_packets = self.normal_sent_packets.saturating_add(1);
299
300                for mi in 0..self.runtime.len() {
301                    self.runtime[mi].normal_sent = self.runtime[mi].normal_sent.saturating_add(1);
302
303                    self.transition(mi, Event::NormalSent);
304                }
305            }
306            TriggerEvent::PaddingSent { machine } => {
307                self.padding_sent_packets = self.padding_sent_packets.saturating_add(1);
308
309                let mi = machine.into_raw();
310                if mi >= self.runtime.len() {
311                    return;
312                }
313                self.runtime[mi].padding_sent = self.runtime[mi].padding_sent.saturating_add(1);
314                if self.transition(mi, Event::PaddingSent) == StateChange::Unchanged
315                    && self.runtime[mi].current_state != STATE_END
316                {
317                    // decrement only makes sense if we didn't change state
318                    self.decrement_limit(mi);
319                }
320            }
321            TriggerEvent::TunnelSent => {
322                // accounting is based on normal/padding sent, not tunnel
323                for mi in 0..self.runtime.len() {
324                    self.transition(mi, Event::TunnelSent);
325                }
326            }
327            TriggerEvent::BlockingBegin { machine } => {
328                // keep track of when we start blocking (for accounting in BlockingEnd)
329                if !self.blocking_active {
330                    self.blocking_active = true;
331                    self.blocking_started = self.current_time;
332                }
333
334                // blocking is a global event
335                for mi in 0..self.runtime.len() {
336                    if self.transition(mi, Event::BlockingBegin) == StateChange::Unchanged
337                        && self.runtime[mi].current_state != STATE_END
338                        && mi == machine.into_raw()
339                    {
340                        // decrement only makes sense if we didn't
341                        // change state and for the machine in question
342                        self.decrement_limit(mi);
343                    }
344                }
345            }
346            TriggerEvent::BlockingEnd => {
347                let mut blocked = T::Duration::zero();
348                if self.blocking_active {
349                    blocked = self
350                        .current_time
351                        .saturating_duration_since(self.blocking_started);
352                    self.blocking_duration += blocked; // Duration has AddAssign trait with overflow protection
353                    self.blocking_active = false;
354                }
355
356                for mi in 0..self.runtime.len() {
357                    // since block is global, every machine was blocked the
358                    // same duration
359                    if !blocked.is_zero() {
360                        self.runtime[mi].blocking_duration += blocked; // Duration has AddAssign trait with overflow protection
361                    }
362                    self.transition(mi, Event::BlockingEnd);
363                }
364            }
365            TriggerEvent::TimerBegin { machine } => {
366                let mi = machine.into_raw();
367                if mi >= self.runtime.len() {
368                    return;
369                }
370                if self.transition(mi, Event::TimerBegin) == StateChange::Unchanged
371                    && self.runtime[mi].current_state != STATE_END
372                {
373                    // decrement only makes sense if we didn't change state
374                    self.decrement_limit(machine.into_raw());
375                }
376            }
377            TriggerEvent::TimerEnd { machine } => {
378                let mi = machine.into_raw();
379                if mi >= self.runtime.len() {
380                    return;
381                }
382                self.transition(mi, Event::TimerEnd);
383            }
384        }
385    }
386
387    fn transition(&mut self, mi: usize, event: Event) -> StateChange {
388        // a machine in end state cannot transition
389        if self.runtime[mi].current_state == STATE_END {
390            return StateChange::Unchanged;
391        }
392
393        // sample next state
394        // new block for immutable ref, makes things less ugly
395        let next_state = {
396            let machine = &self.machines.as_ref()[mi];
397            let state = &machine.states[self.runtime[mi].current_state];
398            state.sample_state(event, &mut self.rng)
399        };
400
401        // if no next state on event, done
402        let Some(next_state) = next_state else {
403            return StateChange::Unchanged;
404        };
405
406        // we got a next state, act on it
407        match next_state {
408            STATE_END => {
409                // this is a state change (because we can never reach here if already in
410                // STATE_END, see first check above), but we don't cancel any pending
411                // action, nor schedule any new action
412                self.runtime[mi].current_state = STATE_END;
413                StateChange::Changed
414            }
415            STATE_SIGNAL => {
416                // this is not a state change, just signal *other* machines
417                self.signal_pending = match self.signal_pending {
418                    // no signal pending, so signal all *other* machines
419                    None => Some(SignalTarget::AllExcept(mi)),
420                    // signal already pending from another machine, so signal
421                    // all machines (including this one)
422                    _ => Some(SignalTarget::All),
423                };
424                StateChange::Unchanged
425            }
426            _ => {
427                let curr_state = self.runtime[mi].current_state;
428
429                // transition to same or different state?
430                if curr_state != next_state {
431                    self.runtime[mi].current_state = next_state;
432                    self.runtime[mi].state_limit = if let Some(action) =
433                        self.machines.as_ref()[mi].states[next_state].action
434                    {
435                        action.sample_limit(&mut self.rng)
436                    } else {
437                        STATE_LIMIT_MAX
438                    };
439                }
440
441                // update the counter, possible recursion: we need to update the
442                // counter before scheduling an action; otherwise, counters will
443                // be updated in reverse order. but we also don't want to
444                // overwrite actions from later transitions, so check here.
445                // finally, two chained transitions in and out of a state should
446                // count as a changed state, so we need to keep track of it to
447                // not prematurely decrement any limit.
448                let below_limits =
449                    self.below_action_limits(&self.runtime[mi], &self.machines.as_ref()[mi]);
450                let (allow_schedule, state_changed) = self.update_counter(mi);
451
452                // schedule an action if allowed by counter update and below all limits
453                if allow_schedule && below_limits {
454                    self.schedule_action(mi, next_state);
455                }
456
457                if curr_state == self.runtime[mi].current_state && !state_changed {
458                    StateChange::Unchanged
459                } else {
460                    StateChange::Changed
461                }
462            }
463        }
464    }
465
466    fn update_counter(&mut self, mi: usize) -> (bool, bool) {
467        let state = &self.machines.as_ref()[mi].states[self.runtime[mi].current_state];
468
469        let old_value_a = self.runtime[mi].counter_a;
470        let old_value_b = self.runtime[mi].counter_b;
471        let mut any_counter_zeroed = false;
472
473        // counter A and B are independent, so we update them separately
474        if let Some(counter_a) = state.counter.0 {
475            let change = if counter_a.copy {
476                old_value_b
477            } else {
478                counter_a.sample_value(&mut self.rng)
479            };
480
481            let updated_value_a = &mut self.runtime[mi].counter_a;
482            match counter_a.operation {
483                Operation::Increment => {
484                    *updated_value_a = updated_value_a.saturating_add(change);
485                }
486                Operation::Decrement => {
487                    *updated_value_a = updated_value_a.saturating_sub(change);
488                }
489                Operation::Set => {
490                    *updated_value_a = change;
491                }
492            }
493
494            if old_value_a != 0 && *updated_value_a == 0 && !self.counter_zeroed_once.0 {
495                any_counter_zeroed = true;
496                self.counter_zeroed_once.0 = true;
497            }
498        }
499
500        if let Some(counter_b) = state.counter.1 {
501            let change = if counter_b.copy {
502                old_value_a
503            } else {
504                counter_b.sample_value(&mut self.rng)
505            };
506
507            let updated_value_b = &mut self.runtime[mi].counter_b;
508            match counter_b.operation {
509                Operation::Increment => {
510                    *updated_value_b = updated_value_b.saturating_add(change);
511                }
512                Operation::Decrement => {
513                    *updated_value_b = updated_value_b.saturating_sub(change);
514                }
515                Operation::Set => {
516                    *updated_value_b = change;
517                }
518            }
519
520            if old_value_b != 0 && *updated_value_b == 0 && !self.counter_zeroed_once.1 {
521                any_counter_zeroed = true;
522                self.counter_zeroed_once.1 = true;
523            }
524        }
525
526        if any_counter_zeroed {
527            let state_changed = self.transition(mi, Event::CounterZero);
528            return (
529                self.actions[mi].is_none(),
530                state_changed == StateChange::Changed,
531            );
532        }
533
534        // no action scheduled, and state unchanged
535        (true, false)
536    }
537
538    fn schedule_action(&mut self, mi: usize, state: usize) {
539        let index = MachineId(mi);
540        let action = self.machines.as_ref()[mi].states[state].action;
541
542        self.actions[mi] = match action {
543            Some(action) => match action {
544                Action::Cancel { timer } => Some(TriggerAction::Cancel {
545                    machine: index,
546                    timer,
547                }),
548                Action::SendPadding {
549                    bypass, replace, ..
550                } => Some(TriggerAction::SendPadding {
551                    timeout: T::Duration::from_micros(action.sample_timeout(&mut self.rng)),
552                    bypass,
553                    replace,
554                    machine: index,
555                }),
556                Action::BlockOutgoing {
557                    bypass, replace, ..
558                } => Some(TriggerAction::BlockOutgoing {
559                    timeout: T::Duration::from_micros(action.sample_timeout(&mut self.rng)),
560                    duration: T::Duration::from_micros(action.sample_duration(&mut self.rng)),
561                    bypass,
562                    replace,
563                    machine: index,
564                }),
565                Action::UpdateTimer { replace, .. } => Some(TriggerAction::UpdateTimer {
566                    duration: T::Duration::from_micros(action.sample_duration(&mut self.rng)),
567                    replace,
568                    machine: index,
569                }),
570            },
571            None => None,
572        };
573    }
574
575    fn decrement_limit(&mut self, mi: usize) {
576        if self.runtime[mi].state_limit > 0 {
577            self.runtime[mi].state_limit -= 1;
578        }
579        let cs = self.runtime[mi].current_state;
580
581        if let Some(action) = self.machines.as_ref()[mi].states[cs].action {
582            if self.runtime[mi].state_limit == 0 && action.has_limit() {
583                // take no action and trigger limit reached
584                self.actions[mi] = None;
585                // next, we trigger internally event LimitReached
586                self.transition(mi, Event::LimitReached);
587            }
588        }
589    }
590
591    fn below_action_limits(&self, runtime: &MachineRuntime<T>, machine: &Machine) -> bool {
592        let current = &machine.states[runtime.current_state];
593
594        let Some(action) = current.action else {
595            return false;
596        };
597
598        match action {
599            Action::BlockOutgoing { .. } => self.below_limit_blocking(runtime, machine),
600            Action::SendPadding { .. } => self.below_limit_padding(runtime, machine),
601            Action::UpdateTimer { .. } => runtime.state_limit > 0,
602            _ => true,
603        }
604    }
605
606    fn below_limit_blocking(&self, runtime: &MachineRuntime<T>, machine: &Machine) -> bool {
607        let current = &machine.states[runtime.current_state];
608        // blocking action
609
610        // special case: we always allow overwriting existing blocking
611        let replace = if let Some(Action::BlockOutgoing { replace, .. }) = current.action {
612            replace
613        } else {
614            false
615        };
616
617        if replace && self.blocking_active {
618            // we still check against state limit, because it's machine internal
619            return runtime.state_limit > 0;
620        }
621
622        // compute durations we've been blocking
623        let mut m_block_dur = runtime.blocking_duration;
624        let mut g_block_dur = self.blocking_duration;
625        if self.blocking_active {
626            // account for ongoing blocking as well, add duration
627            m_block_dur += self
628                .current_time
629                .saturating_duration_since(self.blocking_started);
630            g_block_dur += self
631                .current_time
632                .saturating_duration_since(self.blocking_started);
633        }
634
635        // machine allowed blocking duration first, since it bypasses the
636        // other two types of limits
637        if m_block_dur < runtime.allowed_blocked_microsec {
638            // we still check against state limit, because it's machine internal
639            return runtime.state_limit > 0;
640        }
641
642        // does the machine limit say no, if set?
643        if machine.max_blocking_frac > 0.0 {
644            let f: f64 = m_block_dur.div_duration_f64(
645                self.current_time
646                    .saturating_duration_since(runtime.machine_start),
647            );
648            if f >= machine.max_blocking_frac {
649                return false;
650            }
651        }
652
653        // does the framework say no?
654        if self.max_blocking_frac > 0.0 {
655            let f: f64 = g_block_dur.div_duration_f64(
656                self.current_time
657                    .saturating_duration_since(self.framework_start),
658            );
659            if f >= self.max_blocking_frac {
660                return false;
661            }
662        }
663
664        // only state-limit left to consider
665        runtime.state_limit > 0
666    }
667
668    fn below_limit_padding(&self, runtime: &MachineRuntime<T>, machine: &Machine) -> bool {
669        // no limits apply if not made up padding count
670        if runtime.padding_sent < machine.allowed_padding_packets {
671            return runtime.state_limit > 0;
672        }
673
674        // hit machine limits?
675        if machine.max_padding_frac > 0.0 {
676            let total = runtime.normal_sent + runtime.padding_sent;
677            if total == 0 {
678                return true;
679            }
680            if runtime.padding_sent as f64 / total as f64 >= machine.max_padding_frac {
681                return false;
682            }
683        }
684
685        // hit global limits?
686        if self.max_padding_frac > 0.0 {
687            let total = self.padding_sent_packets + self.normal_sent_packets;
688            if total == 0 {
689                return true;
690            }
691            if self.padding_sent_packets as f64 / total as f64 >= self.max_padding_frac {
692                return false;
693            }
694        }
695
696        // only state-limit left to consider
697        runtime.state_limit > 0
698    }
699}
700
701#[cfg(test)]
702mod tests {
703    use crate::counter::Counter;
704    use crate::dist::*;
705    use crate::framework::*;
706    use crate::state::*;
707    use enum_map::enum_map;
708    use std::ops::Add;
709    use std::time::Duration;
710    use std::time::Instant;
711
712    #[test]
713    fn no_machines() {
714        let machines = vec![];
715        let f = Framework::new(&machines, 0.0, 0.0, Instant::now(), rand::rng());
716        assert!(f.is_ok());
717    }
718
719    #[test]
720    fn reuse_machines() {
721        let machines = vec![];
722        let f1 = Framework::new(&machines, 0.0, 0.0, Instant::now(), rand::rng());
723        assert!(f1.is_ok());
724        let f2 = Framework::new(&machines, 0.0, 0.0, Instant::now(), rand::rng());
725        assert!(f2.is_ok());
726    }
727
728    #[test]
729    fn noop_machine() {
730        let s0 = State::new(enum_map! {
731        _ => vec![],
732        });
733        let m = Machine::new(0, 0.0, 0, 0.0, vec![s0]).unwrap();
734        assert_eq!(m.serialize(), "02eNpjYEAHjOgCAAA0AAI=");
735    }
736
737    #[test]
738    fn trigger_events_actions() {
739        // plan: create a machine that swaps between two states, trigger one
740        // then multiple events and check the resulting actions
741
742        // state 0: go to state 1 on PaddingSent, pad after 10 usec
743        let mut s0 = State::new(enum_map! {
744            Event::PaddingSent => vec![Trans(1, 1.0)],
745        _ => vec![],
746        });
747        s0.action = Some(Action::SendPadding {
748            bypass: false,
749            replace: false,
750            timeout: Dist {
751                dist: DistType::Uniform {
752                    low: 10.0,
753                    high: 10.0,
754                },
755                start: 0.0,
756                max: 0.0,
757            },
758            limit: None,
759        });
760
761        // state 1: go to state 0 on PaddingRecv, pad after 1 usec
762        let mut s1 = State::new(enum_map! {
763            Event::PaddingRecv => vec![Trans(0, 1.0)],
764        _ => vec![],
765        });
766        s1.action = Some(Action::SendPadding {
767            bypass: false,
768            replace: false,
769            timeout: Dist {
770                dist: DistType::Uniform {
771                    low: 1.0,
772                    high: 1.0,
773                },
774
775                start: 0.0,
776                max: 0.0,
777            },
778            limit: None,
779        });
780
781        // create a simple machine
782        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1]).unwrap();
783
784        let mut current_time = Instant::now();
785        let machines = vec![m];
786        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
787
788        assert_eq!(f.actions.len(), 1);
789
790        // start triggering
791        _ = f.trigger_events(
792            &[TriggerEvent::BlockingBegin {
793                machine: MachineId(0),
794            }],
795            current_time,
796        );
797        assert_eq!(f.actions[0], None);
798
799        // move time forward, trigger again to make sure no scheduled timer
800        current_time = current_time.add(Duration::from_micros(20));
801        _ = f.trigger_events(
802            &[TriggerEvent::BlockingBegin {
803                machine: MachineId(0),
804            }],
805            current_time,
806        );
807        assert_eq!(f.actions[0], None);
808
809        // trigger transition to next state
810        _ = f.trigger_events(
811            &[TriggerEvent::PaddingSent {
812                machine: MachineId(0),
813            }],
814            current_time,
815        );
816        assert_eq!(
817            f.actions[0],
818            Some(TriggerAction::SendPadding {
819                timeout: Duration::from_micros(1),
820                bypass: false,
821                replace: false,
822                machine: MachineId(0),
823            })
824        );
825
826        // increase time, trigger event, make sure no further action
827        current_time = current_time.add(Duration::from_micros(20));
828        _ = f.trigger_events(
829            &[TriggerEvent::PaddingSent {
830                machine: MachineId(0),
831            }],
832            current_time,
833        );
834        assert_eq!(f.actions[0], None);
835
836        // go back to state 0
837        _ = f.trigger_events(&[TriggerEvent::PaddingRecv], current_time);
838        assert_eq!(
839            f.actions[0],
840            Some(TriggerAction::SendPadding {
841                timeout: Duration::from_micros(10),
842                bypass: false,
843                replace: false,
844                machine: MachineId(0),
845            })
846        );
847
848        // test multiple triggers overwriting actions
849        for _ in 0..10 {
850            _ = f.trigger_events(
851                &[
852                    TriggerEvent::PaddingSent {
853                        machine: MachineId(0),
854                    },
855                    TriggerEvent::PaddingRecv,
856                ],
857                current_time,
858            );
859            assert_eq!(
860                f.actions[0],
861                Some(TriggerAction::SendPadding {
862                    timeout: Duration::from_micros(10),
863                    bypass: false,
864                    replace: false,
865                    machine: MachineId(0),
866                })
867            );
868        }
869
870        // triple trigger, swapping between states
871        for i in 0..10 {
872            if i % 2 == 0 {
873                _ = f.trigger_events(
874                    &[
875                        TriggerEvent::PaddingRecv,
876                        TriggerEvent::PaddingSent {
877                            machine: MachineId(0),
878                        },
879                        TriggerEvent::PaddingRecv,
880                    ],
881                    current_time,
882                );
883                assert_eq!(
884                    f.actions[0],
885                    Some(TriggerAction::SendPadding {
886                        timeout: Duration::from_micros(10),
887                        bypass: false,
888                        replace: false,
889                        machine: MachineId(0),
890                    })
891                );
892            } else {
893                _ = f.trigger_events(
894                    &[
895                        TriggerEvent::PaddingSent {
896                            machine: MachineId(0),
897                        },
898                        TriggerEvent::PaddingRecv,
899                        TriggerEvent::PaddingSent {
900                            machine: MachineId(0),
901                        },
902                    ],
903                    current_time,
904                );
905                assert_eq!(
906                    f.actions[0],
907                    Some(TriggerAction::SendPadding {
908                        timeout: Duration::from_micros(1),
909                        bypass: false,
910                        replace: false,
911                        machine: MachineId(0),
912                    })
913                );
914            }
915        }
916    }
917
918    #[test]
919    fn blocking_machine() {
920        // a machine that blocks for 10us, 1us after NormalSent
921
922        // state 0
923        let mut s0 = State::new(enum_map! {
924                 Event::NormalSent => vec![Trans(0, 1.0)],
925             _ => vec![],
926        });
927        s0.action = Some(Action::BlockOutgoing {
928            bypass: false,
929            replace: false,
930            timeout: Dist {
931                dist: DistType::Uniform {
932                    low: 1.0,
933                    high: 1.0,
934                },
935                start: 0.0,
936                max: 0.0,
937            },
938            duration: Dist {
939                dist: DistType::Uniform {
940                    low: 10.0,
941                    high: 10.0,
942                },
943                start: 0.0,
944                max: 0.0,
945            },
946            limit: None,
947        });
948
949        // machine
950        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0]).unwrap();
951
952        let mut current_time = Instant::now();
953        let machines = vec![m];
954        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
955
956        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
957        assert_eq!(
958            f.actions[0],
959            Some(TriggerAction::BlockOutgoing {
960                timeout: Duration::from_micros(1),
961                duration: Duration::from_micros(10),
962                bypass: false,
963                replace: false,
964                machine: MachineId(0),
965            })
966        );
967
968        current_time = current_time.add(Duration::from_micros(20));
969        _ = f.trigger_events(
970            &[TriggerEvent::BlockingBegin {
971                machine: MachineId(0),
972            }],
973            current_time,
974        );
975        assert_eq!(f.actions[0], None);
976
977        for _ in 0..10 {
978            current_time = current_time.add(Duration::from_micros(1));
979            _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
980            assert_eq!(
981                f.actions[0],
982                Some(TriggerAction::BlockOutgoing {
983                    timeout: Duration::from_micros(1),
984                    duration: Duration::from_micros(10),
985                    bypass: false,
986                    replace: false,
987                    machine: MachineId(0),
988                })
989            );
990        }
991    }
992
993    #[test]
994    fn timer_machine() {
995        // a machine that sets the timer to 1 ms after PaddingSent
996
997        // state 0
998        let mut s0 = State::new(enum_map! {
999                 Event::PaddingSent => vec![Trans(1, 1.0)],
1000             _ => vec![],
1001        });
1002        s0.action = Some(Action::SendPadding {
1003            bypass: false,
1004            replace: false,
1005            timeout: Dist {
1006                dist: DistType::Uniform {
1007                    low: 1.0,
1008                    high: 1.0,
1009                },
1010
1011                start: 0.0,
1012                max: 0.0,
1013            },
1014            limit: None,
1015        });
1016
1017        // state 1
1018        let mut s1 = State::new(enum_map! {
1019                 Event::TimerEnd => vec![Trans(0, 1.0)],
1020             _ => vec![],
1021        });
1022        s1.action = Some(Action::UpdateTimer {
1023            replace: false,
1024            duration: Dist {
1025                dist: DistType::Uniform {
1026                    low: 1000.0,
1027                    high: 1000.0,
1028                },
1029                start: 0.0,
1030                max: 0.0,
1031            },
1032            limit: None,
1033        });
1034
1035        // machine
1036        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1]).unwrap();
1037
1038        let mut current_time = Instant::now();
1039        let machines = vec![m];
1040        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1041
1042        _ = f.trigger_events(
1043            &[TriggerEvent::PaddingSent {
1044                machine: MachineId(0),
1045            }],
1046            current_time,
1047        );
1048        assert_eq!(
1049            f.actions[0],
1050            Some(TriggerAction::UpdateTimer {
1051                duration: Duration::from_micros(1000),
1052                replace: false,
1053                machine: MachineId(0),
1054            })
1055        );
1056
1057        current_time = current_time.add(Duration::from_micros(20));
1058        _ = f.trigger_events(
1059            &[TriggerEvent::TimerEnd {
1060                machine: MachineId(0),
1061            }],
1062            current_time,
1063        );
1064        assert_eq!(
1065            f.actions[0],
1066            Some(TriggerAction::SendPadding {
1067                timeout: Duration::from_micros(1),
1068                bypass: false,
1069                replace: false,
1070                machine: MachineId(0),
1071            })
1072        );
1073    }
1074
1075    #[test]
1076    fn counter_machine() {
1077        // count PaddingSent - NormalSent with counter A
1078        // pad and increment counter B by 4 on CounterZero
1079
1080        // state 0
1081        let mut s0 = State::new(enum_map! {
1082            Event::PaddingSent => vec![Trans(1, 1.0)],
1083            Event::CounterZero => vec![Trans(2, 1.0)],
1084        _ => vec![],
1085        });
1086        s0.counter = (Some(Counter::new(Operation::Decrement)), None);
1087
1088        // state 1
1089        let mut s1 = State::new(enum_map! {
1090            Event::NormalSent => vec![Trans(0, 1.0)],
1091        _ => vec![],
1092        });
1093        s1.counter = (Some(Counter::new(Operation::Increment)), None);
1094
1095        // state 2
1096        let mut s2 = State::new(enum_map! {
1097            Event::NormalSent => vec![Trans(0, 1.0)],
1098            Event::PaddingSent => vec![Trans(1, 1.0)],
1099        _ => vec![],
1100        });
1101        s2.action = Some(Action::SendPadding {
1102            bypass: false,
1103            replace: false,
1104            timeout: Dist {
1105                dist: DistType::Uniform {
1106                    low: 2.0,
1107                    high: 2.0,
1108                },
1109                start: 0.0,
1110                max: 0.0,
1111            },
1112            limit: None,
1113        });
1114        s2.counter = (
1115            None,
1116            Some(Counter::new_dist(
1117                Operation::Increment,
1118                Dist {
1119                    dist: DistType::Uniform {
1120                        low: 4.0,
1121                        high: 4.0,
1122                    },
1123                    start: 0.0,
1124                    max: 0.0,
1125                },
1126            )),
1127        );
1128
1129        // machine
1130        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1131
1132        let mut current_time = Instant::now();
1133        let machines = vec![m];
1134        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1135
1136        _ = f.trigger_events(
1137            &[TriggerEvent::PaddingSent {
1138                machine: MachineId(0),
1139            }],
1140            current_time,
1141        );
1142        assert_eq!(f.actions[0], None);
1143        assert_eq!(f.runtime[0].counter_a, 1);
1144
1145        current_time = current_time.add(Duration::from_micros(20));
1146        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1147        assert_eq!(
1148            f.actions[0],
1149            Some(TriggerAction::SendPadding {
1150                timeout: Duration::from_micros(2),
1151                bypass: false,
1152                replace: false,
1153                machine: MachineId(0),
1154            })
1155        );
1156        assert_eq!(f.runtime[0].counter_a, 0);
1157        assert_eq!(f.runtime[0].counter_b, 4);
1158    }
1159
1160    #[test]
1161    fn counter_underflow_machine() {
1162        // check that underflow of counter value cannot occur
1163        // ensure CounterZero is not triggered when counter is already 0
1164
1165        // state 0, decrement counter
1166        let mut s0 = State::new(enum_map! {
1167            Event::NormalSent => vec![Trans(0, 1.0)],
1168            Event::NormalRecv => vec![Trans(1, 1.0)],
1169            Event::CounterZero => vec![Trans(2, 1.0)],
1170        _ => vec![],
1171        });
1172        s0.counter = (
1173            None,
1174            // NOTE decrement
1175            Some(Counter::new_dist(
1176                Operation::Decrement,
1177                Dist {
1178                    dist: DistType::Uniform {
1179                        low: 10.0,
1180                        high: 10.0,
1181                    },
1182                    start: 0.0,
1183                    max: 0.0,
1184                },
1185            )),
1186        );
1187
1188        // state 1, set counter
1189        let mut s1 = State::new(enum_map! {
1190            Event::NormalSent => vec![Trans(0, 1.0)],
1191            Event::NormalRecv => vec![Trans(1, 1.0)],
1192            Event::CounterZero => vec![Trans(2, 1.0)],
1193        _ => vec![],
1194        });
1195        s1.counter = (
1196            None,
1197            Some(Counter::new_dist(
1198                Operation::Set,
1199                Dist {
1200                    dist: DistType::Uniform {
1201                        low: 0.0, // NOTE
1202                        high: 0.0,
1203                    },
1204                    start: 0.0,
1205                    max: 0.0,
1206                },
1207            )),
1208        );
1209
1210        // state 2, pad
1211        let mut s2 = State::new(enum_map! {
1212            Event::NormalSent => vec![Trans(0, 1.0)],
1213            Event::NormalRecv => vec![Trans(1, 1.0)],
1214        _ => vec![],
1215        });
1216        s2.action = Some(Action::SendPadding {
1217            bypass: false,
1218            replace: false,
1219            timeout: Dist {
1220                dist: DistType::Uniform {
1221                    low: 2.0,
1222                    high: 2.0,
1223                },
1224                start: 0.0,
1225                max: 0.0,
1226            },
1227            limit: None,
1228        });
1229
1230        // machine
1231        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1232
1233        let current_time = Instant::now();
1234        let machines = vec![m];
1235        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1236
1237        // decrement counter to 0
1238        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1239        assert_eq!(f.actions[0], None);
1240        assert_eq!(f.runtime[0].counter_b, 0);
1241
1242        // set counter to 0
1243        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
1244        assert_eq!(f.actions[0], None);
1245        assert_eq!(f.runtime[0].counter_b, 0);
1246    }
1247
1248    #[test]
1249    fn counter_overflow_machine() {
1250        // check that overflow of counter value cannot occur
1251        // set to max value, then try to add and make sure no change
1252
1253        // state 0, increment counter
1254        let mut s0 = State::new(enum_map! {
1255           Event::NormalSent => vec![Trans(0, 1.0)],
1256           Event::NormalRecv => vec![Trans(1, 1.0)],
1257           _ => vec![],
1258        });
1259        s0.counter = (
1260            // NOTE increment
1261            Some(Counter::new_dist(
1262                Operation::Increment,
1263                Dist {
1264                    dist: DistType::Uniform {
1265                        low: 1000.0,
1266                        high: 1000.0,
1267                    },
1268                    start: 0.0,
1269                    max: 0.0,
1270                },
1271            )),
1272            None,
1273        );
1274
1275        // state 1, set counter
1276        let mut s1 = State::new(enum_map! {
1277            Event::NormalSent => vec![Trans(0, 1.0)],
1278            Event::NormalRecv => vec![Trans(1, 1.0)],
1279        _ => vec![],
1280        });
1281        s1.counter = (
1282            Some(Counter::new_dist(
1283                Operation::Set,
1284                Dist {
1285                    dist: DistType::Uniform {
1286                        low: u64::MAX as f64, // NOTE
1287                        high: u64::MAX as f64,
1288                    },
1289                    start: 0.0,
1290                    max: 0.0,
1291                },
1292            )),
1293            None,
1294        );
1295
1296        // machine
1297        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1]).unwrap();
1298
1299        let current_time = Instant::now();
1300        let machines = vec![m];
1301        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1302
1303        // set counter to u64::MAX
1304        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
1305        assert_eq!(f.runtime[0].counter_a, u64::MAX);
1306
1307        // try to increment counter by 1000
1308        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1309        assert_eq!(f.runtime[0].counter_a, u64::MAX);
1310    }
1311
1312    #[test]
1313    fn counter_convergence_machine() {
1314        // set and decrement both counters at once, check correctness
1315        // then decrement both to zero and ensure only one transition
1316
1317        // state 0
1318        let mut s0 = State::new(enum_map! {
1319           Event::NormalSent => vec![Trans(0, 1.0)],
1320           Event::PaddingSent => vec![Trans(1, 1.0)],
1321           _ => vec![],
1322        });
1323        s0.counter = (
1324            Some(Counter::new_dist(
1325                Operation::Set,
1326                Dist {
1327                    dist: DistType::Uniform {
1328                        low: 44.0,
1329                        high: 44.0,
1330                    },
1331                    start: 0.0,
1332                    max: 0.0,
1333                },
1334            )),
1335            Some(Counter::new_dist(
1336                Operation::Set,
1337                Dist {
1338                    dist: DistType::Uniform {
1339                        low: 28.0,
1340                        high: 28.0,
1341                    },
1342                    start: 0.0,
1343                    max: 0.0,
1344                },
1345            )),
1346        );
1347
1348        // state 1
1349        let mut s1 = State::new(enum_map! {
1350           Event::NormalSent => vec![Trans(1, 1.0)],
1351           Event::CounterZero => vec![Trans(2, 1.0)],
1352           _ => vec![],
1353        });
1354        s1.counter = (
1355            Some(Counter::new_dist(
1356                Operation::Decrement,
1357                Dist {
1358                    dist: DistType::Uniform {
1359                        low: 32.0,
1360                        high: 32.0,
1361                    },
1362                    start: 0.0,
1363                    max: 0.0,
1364                },
1365            )),
1366            Some(Counter::new_dist(
1367                Operation::Decrement,
1368                Dist {
1369                    dist: DistType::Uniform {
1370                        low: 15.0,
1371                        high: 15.0,
1372                    },
1373                    start: 0.0,
1374                    max: 0.0,
1375                },
1376            )),
1377        );
1378
1379        // state 2
1380        let s2 = State::new(enum_map! {
1381           Event::CounterZero => vec![Trans(0, 1.0)],
1382           _ => vec![],
1383        });
1384
1385        // machine
1386        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1387
1388        let mut current_time = Instant::now();
1389        let machines = vec![m];
1390        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1391
1392        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1393        assert_eq!(f.actions[0], None);
1394        assert_eq!(f.runtime[0].counter_a, 44);
1395        assert_eq!(f.runtime[0].counter_b, 28);
1396
1397        current_time = current_time.add(Duration::from_micros(20));
1398        _ = f.trigger_events(
1399            &[TriggerEvent::PaddingSent {
1400                machine: MachineId(0),
1401            }],
1402            current_time,
1403        );
1404        assert_eq!(f.actions[0], None);
1405        assert_eq!(f.runtime[0].counter_a, 12);
1406        assert_eq!(f.runtime[0].counter_b, 13);
1407
1408        current_time = current_time.add(Duration::from_micros(20));
1409        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1410        assert_eq!(f.actions[0], None);
1411        assert_eq!(f.runtime[0].counter_a, 0);
1412        assert_eq!(f.runtime[0].counter_b, 0);
1413    }
1414
1415    #[test]
1416    fn counter_divergence_machine() {
1417        // set both counters at once, check correctness
1418        // decrement only one to zero and ensure CounterZero
1419
1420        // state 0
1421        let mut s0 = State::new(enum_map! {
1422           Event::NormalSent => vec![Trans(0, 1.0)],
1423           Event::PaddingSent => vec![Trans(1, 1.0)],
1424           _ => vec![],
1425        });
1426        s0.counter = (
1427            Some(Counter::new_dist(
1428                Operation::Set,
1429                Dist {
1430                    dist: DistType::Uniform {
1431                        low: 50.0,
1432                        high: 50.0,
1433                    },
1434                    start: 0.0,
1435                    max: 0.0,
1436                },
1437            )),
1438            Some(Counter::new_dist(
1439                Operation::Set,
1440                Dist {
1441                    dist: DistType::Uniform {
1442                        low: 50.0,
1443                        high: 50.0,
1444                    },
1445                    start: 0.0,
1446                    max: 0.0,
1447                },
1448            )),
1449        );
1450
1451        // state 1
1452        let mut s1 = State::new(enum_map! {
1453           Event::CounterZero => vec![Trans(2, 1.0)],
1454           _ => vec![],
1455        });
1456        s1.counter = (
1457            None,
1458            Some(Counter::new_dist(
1459                Operation::Decrement,
1460                Dist {
1461                    dist: DistType::Uniform {
1462                        low: 50.0,
1463                        high: 50.0,
1464                    },
1465                    start: 0.0,
1466                    max: 0.0,
1467                },
1468            )),
1469        );
1470
1471        // state 2
1472        let mut s2 = State::new(enum_map! {
1473           _ => vec![],
1474        });
1475        s2.counter = (
1476            Some(Counter::new_dist(
1477                Operation::Increment,
1478                Dist {
1479                    dist: DistType::Uniform {
1480                        low: 25.0,
1481                        high: 25.0,
1482                    },
1483                    start: 0.0,
1484                    max: 0.0,
1485                },
1486            )),
1487            None,
1488        );
1489
1490        // machine
1491        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1492
1493        let mut current_time = Instant::now();
1494        let machines = vec![m];
1495        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1496
1497        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1498        assert_eq!(f.actions[0], None);
1499        assert_eq!(f.runtime[0].counter_a, 50);
1500        assert_eq!(f.runtime[0].counter_b, 50);
1501
1502        current_time = current_time.add(Duration::from_micros(20));
1503        _ = f.trigger_events(
1504            &[TriggerEvent::PaddingSent {
1505                machine: MachineId(0),
1506            }],
1507            current_time,
1508        );
1509        assert_eq!(f.actions[0], None);
1510        assert_eq!(f.runtime[0].counter_a, 75);
1511        assert_eq!(f.runtime[0].counter_b, 0);
1512    }
1513
1514    #[test]
1515    fn counter_chain_machine() {
1516        // set both counters at once, then decrement one after the other
1517        // ensure that the updates happen in the correct order (recursion)
1518        // also, ensure that the *latest* specified action gets scheduled
1519
1520        // state 0
1521        let mut s0 = State::new(enum_map! {
1522           Event::NormalSent => vec![Trans(0, 1.0)],
1523           Event::PaddingSent => vec![Trans(1, 1.0)],
1524           _ => vec![],
1525        });
1526        s0.counter = (
1527            Some(Counter::new(Operation::Set)), // (1, 1)
1528            Some(Counter::new(Operation::Set)),
1529        );
1530
1531        // state 1
1532        let mut s1 = State::new(enum_map! {
1533           Event::CounterZero => vec![Trans(2, 1.0)], // the "chain reaction"
1534           _ => vec![],
1535        });
1536        s1.action = Some(Action::SendPadding {
1537            bypass: false,
1538            replace: false,
1539            timeout: Dist {
1540                dist: DistType::Uniform {
1541                    low: 2.0,
1542                    high: 2.0,
1543                },
1544                start: 0.0,
1545                max: 0.0,
1546            },
1547            limit: None,
1548        });
1549        s1.counter = (
1550            Some(Counter::new(Operation::Decrement)),
1551            None, // (0, 1)
1552        );
1553
1554        // state 2
1555        let mut s2 = State::new(enum_map! {
1556           Event::CounterZero => vec![Trans(3, 1.0)],
1557           _ => vec![],
1558        });
1559        s2.action = Some(Action::SendPadding {
1560            bypass: true,
1561            replace: false,
1562            timeout: Dist {
1563                dist: DistType::Uniform {
1564                    low: 67.0,
1565                    high: 67.0,
1566                },
1567                start: 0.0,
1568                max: 0.0,
1569            },
1570            limit: None,
1571        });
1572        s2.counter = (
1573            None,
1574            Some(Counter::new(Operation::Decrement)), // (0, 0)
1575        );
1576
1577        // state 3
1578        let mut s3 = State::new(enum_map! {
1579           _ => vec![],
1580        });
1581        s3.counter = (
1582            Some(Counter::new_dist(
1583                Operation::Set,
1584                Dist {
1585                    dist: DistType::Uniform {
1586                        low: 13.0,
1587                        high: 13.0,
1588                    },
1589                    start: 0.0,
1590                    max: 0.0,
1591                },
1592            )),
1593            None,
1594        );
1595
1596        // machine
1597        let m = Machine::new(0, 0.0, 0, 0.0, vec![s0, s1, s2, s3]).unwrap();
1598
1599        let mut current_time = Instant::now();
1600        let machines = vec![m];
1601        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1602
1603        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1604        assert_eq!(f.actions[0], None);
1605        assert_eq!(f.runtime[0].counter_a, 1);
1606        assert_eq!(f.runtime[0].counter_b, 1);
1607
1608        current_time = current_time.add(Duration::from_micros(20));
1609        _ = f.trigger_events(
1610            &[TriggerEvent::PaddingSent {
1611                machine: MachineId(0),
1612            }],
1613            current_time,
1614        );
1615        assert_eq!(
1616            f.actions[0],
1617            Some(TriggerAction::SendPadding {
1618                timeout: Duration::from_micros(67),
1619                bypass: true,
1620                replace: false,
1621                machine: MachineId(0),
1622            })
1623        );
1624        assert_eq!(f.runtime[0].counter_a, 13);
1625        assert_eq!(f.runtime[0].counter_b, 0);
1626    }
1627
1628    #[test]
1629    fn counter_copy_machine() {
1630        // set both counters at once, then copy their values
1631
1632        // state 0
1633        let mut s0 = State::new(enum_map! {
1634           Event::NormalSent => vec![Trans(0, 1.0)],
1635           Event::PaddingSent => vec![Trans(1, 1.0)],
1636           _ => vec![],
1637        });
1638        s0.counter = (
1639            Some(Counter::new_dist(
1640                Operation::Set,
1641                Dist {
1642                    dist: DistType::Uniform {
1643                        low: 4.0,
1644                        high: 4.0,
1645                    },
1646                    start: 0.0,
1647                    max: 0.0,
1648                },
1649            )),
1650            Some(Counter::new_dist(
1651                Operation::Set,
1652                Dist {
1653                    dist: DistType::Uniform {
1654                        low: 13.0,
1655                        high: 13.0,
1656                    },
1657                    start: 0.0,
1658                    max: 0.0,
1659                },
1660            )),
1661        );
1662
1663        // state 1
1664        let mut s1 = State::new(enum_map! {
1665           Event::PaddingSent => vec![Trans(2, 1.0)],
1666           _ => vec![],
1667        });
1668        s1.counter = (
1669            // should be 13.0
1670            Some(Counter::new_copy(Operation::Set)),
1671            // should be 9.0
1672            Some(Counter::new_copy(Operation::Decrement)),
1673        );
1674
1675        // state 2
1676        let mut s2 = State::new(enum_map! {
1677           _ => vec![],
1678        });
1679        s2.counter = (
1680            // should be 22.0
1681            Some(Counter::new_copy(Operation::Increment)),
1682            // should still be 9.0
1683            None,
1684        );
1685
1686        // machine
1687        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1688
1689        let mut current_time = Instant::now();
1690        let machines = vec![m];
1691        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1692
1693        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1694        assert_eq!(f.actions[0], None);
1695        assert_eq!(f.runtime[0].counter_a, 4);
1696        assert_eq!(f.runtime[0].counter_b, 13);
1697
1698        current_time = current_time.add(Duration::from_micros(20));
1699        _ = f.trigger_events(
1700            &[TriggerEvent::PaddingSent {
1701                machine: MachineId(0),
1702            }],
1703            current_time,
1704        );
1705        assert_eq!(f.actions[0], None);
1706        assert_eq!(f.runtime[0].counter_a, 13);
1707        assert_eq!(f.runtime[0].counter_b, 9);
1708
1709        current_time = current_time.add(Duration::from_micros(20));
1710        _ = f.trigger_events(
1711            &[TriggerEvent::PaddingSent {
1712                machine: MachineId(0),
1713            }],
1714            current_time,
1715        );
1716        assert_eq!(f.actions[0], None);
1717        assert_eq!(f.runtime[0].counter_a, 22);
1718        assert_eq!(f.runtime[0].counter_b, 9);
1719    }
1720
1721    #[test]
1722    fn counter_triggered_no_early_limit_decrement() {
1723        // a machine that uses two CounterZero events to trigger a transition
1724        // away and back again to the same state, refreshing limits
1725
1726        // state 0, counters (2, 1)
1727        let mut s0 = State::new(enum_map! {
1728           Event::NormalSent => vec![Trans(0, 1.0)],
1729           Event::PaddingSent => vec![Trans(1, 1.0)],
1730           _ => vec![],
1731        });
1732        s0.counter = (
1733            Some(Counter::new_dist(
1734                Operation::Set,
1735                Dist {
1736                    dist: DistType::Uniform {
1737                        low: 2.0,
1738                        high: 2.0,
1739                    },
1740                    start: 0.0,
1741                    max: 0.0,
1742                },
1743            )),
1744            Some(Counter::new(Operation::Set)),
1745        );
1746
1747        // state 1, diff (-1, 0)
1748        let mut s1 = State::new(enum_map! {
1749           Event::CounterZero => vec![Trans(2, 1.0)],
1750           Event::PaddingSent => vec![Trans(1, 1.0)],
1751           _ => vec![],
1752        });
1753        s1.counter = (Some(Counter::new(Operation::Decrement)), None);
1754        s1.action = Some(Action::SendPadding {
1755            bypass: false,
1756            replace: false,
1757            timeout: Dist {
1758                dist: DistType::Uniform {
1759                    low: 1.0,
1760                    high: 1.0,
1761                },
1762                start: 0.0,
1763                max: 0.0,
1764            },
1765            limit: Some(Dist {
1766                dist: DistType::Uniform {
1767                    low: 2.0,
1768                    high: 2.0,
1769                },
1770                start: 0.0,
1771                max: 0.0,
1772            }),
1773        });
1774
1775        // state 2, diff (0, -1)
1776        let mut s2 = State::new(enum_map! {
1777           Event::CounterZero => vec![Trans(1, 1.0)],
1778           _ => vec![],
1779        });
1780        s2.counter = (None, Some(Counter::new(Operation::Decrement)));
1781
1782        let m = Machine::new(1000, 1.0, 0, 0.0, vec![s0, s1, s2]).unwrap();
1783
1784        let current_time = Instant::now();
1785        let machines = vec![m];
1786        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1787
1788        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1789        assert_eq!(f.actions[0], None);
1790        assert_eq!(f.runtime[0].counter_a, 2);
1791        assert_eq!(f.runtime[0].counter_b, 1);
1792
1793        _ = f.trigger_events(
1794            &[TriggerEvent::PaddingSent {
1795                machine: MachineId(0),
1796            }],
1797            current_time,
1798        );
1799        assert!(f.actions[0].is_some());
1800        assert_eq!(f.runtime[0].counter_a, 1);
1801        assert_eq!(f.runtime[0].counter_b, 1);
1802        assert_eq!(f.runtime[0].state_limit, 2);
1803
1804        _ = f.trigger_events(
1805            &[TriggerEvent::PaddingSent {
1806                machine: MachineId(0),
1807            }],
1808            current_time,
1809        );
1810        assert!(f.actions[0].is_some());
1811        assert_eq!(f.runtime[0].counter_a, 0);
1812        assert_eq!(f.runtime[0].counter_b, 0);
1813        // this should be 2, because both the counters hitting zero transition
1814        // out of state 1 and back again, refreshing the limit
1815        assert_eq!(f.runtime[0].state_limit, 2);
1816    }
1817
1818    #[test]
1819    fn test_infinite_loop_counter() {
1820        // just to get started
1821        let s0 = State::new(enum_map! {
1822           Event::NormalSent => vec![Trans(1, 1.0)],
1823           _ => vec![],
1824        });
1825
1826        // set counter A to 1, B is 0
1827        let mut init = State::new(enum_map! {
1828        Event::NormalSent => vec![Trans(2, 1.0)],
1829        _ => vec![],
1830        });
1831        init.counter = (Some(Counter::new(Operation::Set)), None);
1832
1833        // decrement counter A, triggering CounterZero, and set B to 1
1834        let mut state_a = State::new(enum_map! {
1835        // to state_b
1836        Event::CounterZero => vec![Trans(3, 1.0)],
1837        // to state_pad
1838        Event::NormalRecv => vec![Trans(4, 1.0)],
1839        _ => vec![],
1840        });
1841        state_a.counter = (
1842            Some(Counter::new(Operation::Decrement)),
1843            Some(Counter::new(Operation::Set)),
1844        );
1845
1846        // decrement counter B, triggering CounterZero, and set A to 1
1847        let mut state_b = State::new(enum_map! {
1848        // back to state_a
1849        Event::CounterZero => vec![Trans(2, 1.0)],
1850        _ => vec![],
1851        });
1852        state_b.counter = (
1853            Some(Counter::new(Operation::Set)),
1854            Some(Counter::new(Operation::Decrement)),
1855        );
1856
1857        let mut state_pad = State::new(enum_map! {
1858            _ => vec![],
1859        });
1860        state_pad.action = Some(Action::SendPadding {
1861            bypass: false,
1862            replace: false,
1863            timeout: Dist {
1864                dist: DistType::Uniform {
1865                    low: 1.0,
1866                    high: 1.0,
1867                },
1868                start: 0.0,
1869                max: 0.0,
1870            },
1871            limit: None,
1872        });
1873
1874        let m = Machine::new(
1875            1000,
1876            1.0,
1877            0,
1878            0.0,
1879            vec![s0, init, state_a, state_b, state_pad],
1880        )
1881        .unwrap();
1882        let machines = vec![m];
1883        let current_time = Instant::now();
1884        let mut f = Framework::new(machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1885        // get into init state
1886        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1887        // transition to state_a: this should not loop forever, but be limited
1888        // to one zeroing of each counter, get stuck in state_a (after having
1889        // zeroed counter b in state_b), then transition to state_pad, returning
1890        // one action
1891        assert_eq!(
1892            f.trigger_events(
1893                &[TriggerEvent::NormalSent, TriggerEvent::NormalRecv],
1894                current_time
1895            )
1896            .count(),
1897            1
1898        );
1899    }
1900
1901    #[test]
1902    fn signal_one_machine() {
1903        // send a signal from one machine, ensure that the signal is received
1904        // by another machine but not the originating machine
1905
1906        // state 0
1907        let s0_m0 = State::new(enum_map! {
1908           Event::NormalSent => vec![Trans(STATE_SIGNAL, 1.0)],
1909           Event::Signal => vec![Trans(1, 1.0)],
1910           _ => vec![],
1911        });
1912        let s0_m1 = State::new(enum_map! {
1913           Event::Signal => vec![Trans(1, 1.0)],
1914           _ => vec![],
1915        });
1916
1917        // state 1
1918        let mut s1 = State::new(enum_map! {
1919           _ => vec![],
1920        });
1921        s1.action = Some(Action::SendPadding {
1922            bypass: false,
1923            replace: false,
1924            timeout: Dist {
1925                dist: DistType::Uniform {
1926                    low: 2.0,
1927                    high: 2.0,
1928                },
1929                start: 0.0,
1930                max: 0.0,
1931            },
1932            limit: None,
1933        });
1934
1935        // machines
1936        let m0 = Machine::new(1000, 1.0, 0, 0.0, vec![s0_m0, s1.clone()]).unwrap();
1937        let m1 = Machine::new(1000, 1.0, 0, 0.0, vec![s0_m1, s1.clone()]).unwrap();
1938
1939        let current_time = Instant::now();
1940        let machines = vec![m0, m1];
1941        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1942
1943        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1944        assert_eq!(f.actions[0], None);
1945        assert_eq!(
1946            f.actions[1],
1947            Some(TriggerAction::SendPadding {
1948                timeout: Duration::from_micros(2),
1949                bypass: false,
1950                replace: false,
1951                machine: MachineId(1),
1952            })
1953        );
1954    }
1955
1956    #[test]
1957    fn signal_two_machine() {
1958        // send a signal from two machines, ensure that both get a signal
1959
1960        // state 0
1961        let s0 = State::new(enum_map! {
1962           Event::NormalSent => vec![Trans(STATE_SIGNAL, 1.0)],
1963           Event::Signal => vec![Trans(1, 1.0)],
1964           _ => vec![],
1965        });
1966
1967        // state 1
1968        let mut s1 = State::new(enum_map! {
1969           _ => vec![],
1970        });
1971        s1.action = Some(Action::SendPadding {
1972            bypass: false,
1973            replace: false,
1974            timeout: Dist {
1975                dist: DistType::Uniform {
1976                    low: 2.0,
1977                    high: 2.0,
1978                },
1979                start: 0.0,
1980                max: 0.0,
1981            },
1982            limit: None,
1983        });
1984
1985        // machines
1986        let m0 = Machine::new(1000, 1.0, 0, 0.0, vec![s0.clone(), s1.clone()]).unwrap();
1987        let m1 = Machine::new(1000, 1.0, 0, 0.0, vec![s0.clone(), s1.clone()]).unwrap();
1988
1989        let current_time = Instant::now();
1990        let machines = vec![m0, m1];
1991        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
1992
1993        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
1994        assert_eq!(
1995            f.actions[0],
1996            Some(TriggerAction::SendPadding {
1997                timeout: Duration::from_micros(2),
1998                bypass: false,
1999                replace: false,
2000                machine: MachineId(0),
2001            })
2002        );
2003        assert_eq!(
2004            f.actions[1],
2005            Some(TriggerAction::SendPadding {
2006                timeout: Duration::from_micros(2),
2007                bypass: false,
2008                replace: false,
2009                machine: MachineId(1),
2010            })
2011        );
2012    }
2013
2014    #[test]
2015    fn signal_response() {
2016        // send a signal and respond to it, ensure that both machines get a signal
2017
2018        // state 0
2019        let s0_m0 = State::new(enum_map! {
2020           Event::NormalSent => vec![Trans(STATE_SIGNAL, 1.0)],
2021           Event::Signal => vec![Trans(1, 1.0)],
2022           _ => vec![],
2023        });
2024        let s0_m1 = State::new(enum_map! {
2025           Event::Signal => vec![Trans(STATE_SIGNAL, 1.0)],
2026           _ => vec![],
2027        });
2028
2029        // state 1
2030        let mut s1 = State::new(enum_map! {
2031           _ => vec![],
2032        });
2033        s1.action = Some(Action::SendPadding {
2034            bypass: false,
2035            replace: false,
2036            timeout: Dist {
2037                dist: DistType::Uniform {
2038                    low: 2.0,
2039                    high: 2.0,
2040                },
2041                start: 0.0,
2042                max: 0.0,
2043            },
2044            limit: None,
2045        });
2046
2047        // machines
2048        let m0 = Machine::new(1000, 1.0, 0, 0.0, vec![s0_m0, s1.clone()]).unwrap();
2049        let m1 = Machine::new(1000, 1.0, 0, 0.0, vec![s0_m1, s1.clone()]).unwrap();
2050
2051        let current_time = Instant::now();
2052        let machines = vec![m0, m1];
2053        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
2054
2055        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2056        assert_eq!(
2057            f.actions[0],
2058            Some(TriggerAction::SendPadding {
2059                timeout: Duration::from_micros(2),
2060                bypass: false,
2061                replace: false,
2062                machine: MachineId(0),
2063            })
2064        );
2065        assert_eq!(f.actions[1], None);
2066    }
2067
2068    #[test]
2069    fn machine_max_padding_frac() {
2070        // We create a machine that should be allowed to send 100 padding
2071        // packets before machine padding limits are applied, then the machine
2072        // should be limited from sending any padding until at least 100
2073        // normal packets have been sent, given the set max padding fraction
2074        // of 0.5.
2075
2076        // state 0
2077        let mut s0 = State::new(enum_map! {
2078            // we use sent for checking limits and recv as an event to check
2079            // without adding bytes sent
2080            Event::PaddingSent | Event::NormalSent | Event::NormalRecv => vec![Trans(0, 1.0)],
2081            _ => vec![],
2082        });
2083        s0.action = Some(Action::SendPadding {
2084            bypass: false,
2085            replace: false,
2086            timeout: Dist {
2087                dist: DistType::Uniform {
2088                    low: 2.0,
2089                    high: 2.0,
2090                },
2091                start: 0.0,
2092                max: 0.0,
2093            },
2094            limit: None,
2095        });
2096
2097        // machine
2098        let m = Machine::new(100, 0.5, 0, 0.0, vec![s0]).unwrap();
2099
2100        let current_time = Instant::now();
2101        let machines = vec![m];
2102        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
2103
2104        // transition to get the loop going
2105        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2106
2107        // we expect 100 padding actions
2108        for _ in 0..100 {
2109            assert_eq!(
2110                f.actions[0],
2111                Some(TriggerAction::SendPadding {
2112                    timeout: Duration::from_micros(2),
2113                    bypass: false,
2114                    replace: false,
2115                    machine: MachineId(0),
2116                })
2117            );
2118
2119            _ = f.trigger_events(
2120                &[TriggerEvent::PaddingSent {
2121                    machine: MachineId(0),
2122                }],
2123                current_time,
2124            );
2125        }
2126
2127        // limit hit, last event should prevent the action
2128        assert_eq!(f.actions[0], None);
2129
2130        // trigger and check limit again
2131        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2132        assert_eq!(f.actions[0], None);
2133
2134        // verify that no padding is scheduled until we've sent the same amount
2135        // of bytes
2136        for _ in 0..100 {
2137            _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2138            assert_eq!(f.actions[0], None);
2139        }
2140
2141        // send one byte of normal, putting us just over the limit
2142        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2143
2144        assert_eq!(
2145            f.actions[0],
2146            Some(TriggerAction::SendPadding {
2147                timeout: Duration::from_micros(2),
2148                bypass: false,
2149                replace: false,
2150                machine: MachineId(0),
2151            })
2152        );
2153    }
2154
2155    #[test]
2156    fn framework_max_padding_frac() {
2157        // to test the global limits of the framework we create two machines with
2158        // the same allowed padding, where both machines pad in parallel
2159
2160        // state 0
2161        let mut s0 = State::new(enum_map! {
2162            // we use sent for checking limits and recv as an event to check
2163            // without adding bytes sent
2164            Event::PaddingSent | Event::NormalSent | Event::NormalRecv => vec![Trans(0, 1.0)],
2165        _ => vec![],
2166        });
2167        s0.action = Some(Action::SendPadding {
2168            bypass: false,
2169            replace: false,
2170            timeout: Dist {
2171                dist: DistType::Uniform {
2172                    low: 2.0,
2173                    high: 2.0,
2174                },
2175                start: 0.0,
2176                max: 0.0,
2177            },
2178            limit: None,
2179        });
2180
2181        // machines
2182        let m1 = Machine::new(100, 0.0, 0, 0.0, vec![s0]).unwrap();
2183        let m2 = m1.clone();
2184
2185        // NOTE 0.5 max_padding_frac below
2186        let current_time = Instant::now();
2187        let machines = vec![m1, m2];
2188        let mut f = Framework::new(&machines, 0.5, 0.0, current_time, rand::rng()).unwrap();
2189
2190        // we have two machines that each can send 100 packets before their own
2191        // or any framework limits are applied (by design, see
2192        // allowed_padding_packets) trigger transition to get the loop going
2193        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2194
2195        // we expect 100 padding actions per machine
2196        for _ in 0..100 {
2197            assert_eq!(
2198                f.actions[0],
2199                Some(TriggerAction::SendPadding {
2200                    timeout: Duration::from_micros(2),
2201                    bypass: false,
2202                    replace: false,
2203                    machine: MachineId(0),
2204                })
2205            );
2206            assert_eq!(
2207                f.actions[1],
2208                Some(TriggerAction::SendPadding {
2209                    timeout: Duration::from_micros(2),
2210                    bypass: false,
2211                    replace: false,
2212                    machine: MachineId(1),
2213                })
2214            );
2215            _ = f.trigger_events(
2216                &[
2217                    TriggerEvent::PaddingSent {
2218                        machine: MachineId(0),
2219                    },
2220                    TriggerEvent::PaddingSent {
2221                        machine: MachineId(1),
2222                    },
2223                    TriggerEvent::TunnelSent,
2224                    TriggerEvent::TunnelSent,
2225                ],
2226                current_time,
2227            );
2228        }
2229
2230        // limit hit, last event should prevent the action and future actions
2231        assert_eq!(f.actions[0], None);
2232        assert_eq!(f.actions[1], None);
2233        _ = f.trigger_events(
2234            &[TriggerEvent::NormalRecv, TriggerEvent::NormalRecv],
2235            current_time,
2236        );
2237        assert_eq!(f.actions[0], None);
2238        assert_eq!(f.actions[1], None);
2239
2240        // in sync?
2241        assert_eq!(f.runtime[0].padding_sent, f.runtime[1].padding_sent);
2242        assert_eq!(f.runtime[0].padding_sent, 100);
2243
2244        // OK, so we've sent in total 2*100*mtu of padding using two machines. This
2245        // means that we should need to send at least 2*100*mtu + 1 bytes before
2246        // padding is scheduled again
2247        for _ in 0..200 {
2248            _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2249            assert_eq!(f.actions[0], None);
2250            assert_eq!(f.actions[1], None);
2251        }
2252
2253        // the last byte should tip it over
2254        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2255
2256        assert_eq!(
2257            f.actions[0],
2258            Some(TriggerAction::SendPadding {
2259                timeout: Duration::from_micros(2),
2260                bypass: false,
2261                replace: false,
2262                machine: MachineId(0),
2263            })
2264        );
2265        assert_eq!(
2266            f.actions[1],
2267            Some(TriggerAction::SendPadding {
2268                timeout: Duration::from_micros(2),
2269                bypass: false,
2270                replace: false,
2271                machine: MachineId(1),
2272            })
2273        );
2274    }
2275
2276    #[test]
2277    fn machine_max_blocking_frac() {
2278        // We create a machine that should be allowed to block for 10us before
2279        // machine limits are applied, then the machine should be limited from
2280        // blocking until after 10us, given the set max blocking fraction of
2281        // 0.5.
2282
2283        // state 0
2284        let mut s0 = State::new(enum_map! {
2285           Event::BlockingBegin | Event::BlockingEnd | Event::NormalRecv => vec![Trans(0, 1.0)],
2286           _ => vec![],
2287        });
2288        // block every 2us for 2us
2289        s0.action = Some(Action::BlockOutgoing {
2290            bypass: false,
2291            replace: false,
2292            timeout: Dist {
2293                dist: DistType::Uniform {
2294                    low: 2.0,
2295                    high: 2.0,
2296                },
2297                start: 0.0,
2298                max: 0.0,
2299            },
2300            duration: Dist {
2301                dist: DistType::Uniform {
2302                    low: 2.0,
2303                    high: 2.0,
2304                },
2305                start: 0.0,
2306                max: 0.0,
2307            },
2308            limit: None,
2309        });
2310
2311        // machine
2312        let m = Machine::new(0, 0.0, 10, 0.5, vec![s0]).unwrap();
2313
2314        let mut current_time = Instant::now();
2315        let machines = vec![m];
2316        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
2317
2318        // trigger self to start the blocking (triggers action)
2319        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2320
2321        // verify that we can block for 5*2=10us
2322        for _ in 0..5 {
2323            assert_eq!(
2324                f.actions[0],
2325                Some(TriggerAction::BlockOutgoing {
2326                    timeout: Duration::from_micros(2),
2327                    duration: Duration::from_micros(2),
2328                    bypass: false,
2329                    replace: false,
2330                    machine: MachineId(0),
2331                })
2332            );
2333
2334            _ = f.trigger_events(
2335                &[TriggerEvent::BlockingBegin {
2336                    machine: MachineId(0),
2337                }],
2338                current_time,
2339            );
2340            assert_eq!(
2341                f.actions[0],
2342                Some(TriggerAction::BlockOutgoing {
2343                    timeout: Duration::from_micros(2),
2344                    duration: Duration::from_micros(2),
2345                    bypass: false,
2346                    replace: false,
2347                    machine: MachineId(0),
2348                })
2349            );
2350            current_time = current_time.add(Duration::from_micros(2));
2351            _ = f.trigger_events(&[TriggerEvent::BlockingEnd], current_time);
2352        }
2353        assert_eq!(f.actions[0], None);
2354        assert_eq!(f.runtime[0].blocking_duration, Duration::from_micros(10));
2355
2356        // now we've burned our blocking budget, should be blocked for 10us
2357        for _ in 0..5 {
2358            current_time = current_time.add(Duration::from_micros(2));
2359            _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2360            assert_eq!(f.actions[0], None);
2361        }
2362        assert_eq!(f.runtime[0].blocking_duration, Duration::from_micros(10));
2363        assert_eq!(
2364            current_time.duration_since(f.runtime[0].machine_start),
2365            Duration::from_micros(20)
2366        );
2367
2368        // push over the limit, should be allowed
2369        current_time = current_time.add(Duration::from_micros(2));
2370        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2371        assert_eq!(
2372            f.actions[0],
2373            Some(TriggerAction::BlockOutgoing {
2374                timeout: Duration::from_micros(2),
2375                duration: Duration::from_micros(2),
2376                bypass: false,
2377                replace: false,
2378                machine: MachineId(0),
2379            })
2380        );
2381    }
2382
2383    #[test]
2384    fn framework_max_blocking_frac() {
2385        // We create a machine that should be allowed to block for 10us before
2386        // machine limits are applied, then the machine should be limited from
2387        // blocking until after 10us, given the set max blocking fraction of
2388        // 0.5 in the framework.
2389
2390        // state 0
2391        let mut s0 = State::new(enum_map! {
2392            Event::BlockingBegin | Event::BlockingEnd | Event::NormalRecv => vec![Trans(0, 1.0)],
2393        _ => vec![],
2394        });
2395        // block every 2us for 2us
2396        s0.action = Some(Action::BlockOutgoing {
2397            bypass: false,
2398            replace: false,
2399            timeout: Dist {
2400                dist: DistType::Uniform {
2401                    low: 2.0,
2402                    high: 2.0,
2403                },
2404
2405                start: 0.0,
2406                max: 0.0,
2407            },
2408            duration: Dist {
2409                dist: DistType::Uniform {
2410                    low: 2.0,
2411                    high: 2.0,
2412                },
2413
2414                start: 0.0,
2415                max: 0.0,
2416            },
2417            limit: None,
2418        });
2419
2420        // machine
2421        let m = Machine::new(0, 0.0, 10, 0.0, vec![s0]).unwrap();
2422
2423        let mut current_time = Instant::now();
2424        let machines = vec![m];
2425        let mut f = Framework::new(&machines, 0.0, 0.5, current_time, rand::rng()).unwrap();
2426
2427        // trigger self to start the blocking (triggers action)
2428        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2429
2430        // verify that we can block for 5*2=10us
2431        for _ in 0..5 {
2432            assert_eq!(
2433                f.actions[0],
2434                Some(TriggerAction::BlockOutgoing {
2435                    timeout: Duration::from_micros(2),
2436                    duration: Duration::from_micros(2),
2437                    bypass: false,
2438                    replace: false,
2439                    machine: MachineId(0),
2440                })
2441            );
2442
2443            _ = f.trigger_events(
2444                &[TriggerEvent::BlockingBegin {
2445                    machine: MachineId(0),
2446                }],
2447                current_time,
2448            );
2449            assert_eq!(
2450                f.actions[0],
2451                Some(TriggerAction::BlockOutgoing {
2452                    timeout: Duration::from_micros(2),
2453                    duration: Duration::from_micros(2),
2454                    bypass: false,
2455                    replace: false,
2456                    machine: MachineId(0),
2457                })
2458            );
2459            current_time = current_time.add(Duration::from_micros(2));
2460            _ = f.trigger_events(&[TriggerEvent::BlockingEnd], current_time);
2461        }
2462        assert_eq!(f.actions[0], None);
2463        assert_eq!(f.runtime[0].blocking_duration, Duration::from_micros(10));
2464
2465        // now we've burned our blocking budget, should be blocked for 10us
2466        for _ in 0..5 {
2467            current_time = current_time.add(Duration::from_micros(2));
2468            _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2469            assert_eq!(f.actions[0], None);
2470        }
2471        assert_eq!(f.runtime[0].blocking_duration, Duration::from_micros(10));
2472        assert_eq!(
2473            current_time.duration_since(f.runtime[0].machine_start),
2474            Duration::from_micros(20)
2475        );
2476
2477        // push over the limit, should be allowed
2478        current_time = current_time.add(Duration::from_micros(2));
2479        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2480        assert_eq!(
2481            f.actions[0],
2482            Some(TriggerAction::BlockOutgoing {
2483                timeout: Duration::from_micros(2),
2484                duration: Duration::from_micros(2),
2485                bypass: false,
2486                replace: false,
2487                machine: MachineId(0),
2488            })
2489        );
2490    }
2491
2492    #[test]
2493    fn framework_replace_blocking() {
2494        // Plan: create two machines. #0 will exceed its blocking limit
2495        // and no longer be allowed to block. #1 will then enable blocking,
2496        // so #0 should now be able to overwrite that blocking regardless
2497        // of its limit (special case in below_limit_blocking).
2498
2499        // state 0, first machine
2500        let mut s0 = State::new(enum_map! {
2501            Event::NormalRecv => vec![Trans(0, 1.0)],
2502        _ => vec![],
2503        });
2504        // block every 2us for 2us
2505        s0.action = Some(Action::BlockOutgoing {
2506            bypass: false,
2507            replace: true, // NOTE
2508            timeout: Dist {
2509                dist: DistType::Uniform {
2510                    low: 2.0,
2511                    high: 2.0,
2512                },
2513                start: 0.0,
2514                max: 0.0,
2515            },
2516            duration: Dist {
2517                dist: DistType::Uniform {
2518                    low: 2.0,
2519                    high: 2.0,
2520                },
2521                start: 0.0,
2522                max: 0.0,
2523            },
2524            limit: None,
2525        });
2526
2527        // machine 0
2528        let m0 = Machine::new(0, 0.0, 2, 0.5, vec![s0]).unwrap();
2529
2530        // state 0, second machine
2531        let mut s0 = State::new(enum_map! {
2532            Event::NormalSent => vec![Trans(0, 1.0)],
2533        _ => vec![],
2534        });
2535        // block instantly for 1000us
2536        s0.action = Some(Action::BlockOutgoing {
2537            bypass: false,
2538            replace: false,
2539            timeout: Dist {
2540                dist: DistType::Uniform {
2541                    low: 0.0,
2542                    high: 0.0,
2543                },
2544
2545                start: 0.0,
2546                max: 0.0,
2547            },
2548            duration: Dist {
2549                dist: DistType::Uniform {
2550                    low: 1000.0,
2551                    high: 1000.0,
2552                },
2553                start: 0.0,
2554                max: 0.0,
2555            },
2556            limit: None,
2557        });
2558
2559        // machine 1
2560        let m1 = Machine::new(0, 0.0, 0, 0.0, vec![s0]).unwrap();
2561
2562        let mut current_time = Instant::now();
2563        let machines = vec![m0, m1];
2564        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
2565
2566        // trigger to make machine 0 block
2567        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2568
2569        // verify machine 0 can block for 2us
2570        assert_eq!(
2571            f.actions[0],
2572            Some(TriggerAction::BlockOutgoing {
2573                timeout: Duration::from_micros(2),
2574                duration: Duration::from_micros(2),
2575                bypass: false,
2576                replace: true,
2577                machine: MachineId(0),
2578            })
2579        );
2580
2581        _ = f.trigger_events(
2582            &[TriggerEvent::BlockingBegin {
2583                machine: MachineId(0),
2584            }],
2585            current_time,
2586        );
2587
2588        current_time = current_time.add(Duration::from_micros(2));
2589        _ = f.trigger_events(&[TriggerEvent::BlockingEnd], current_time);
2590
2591        // ensure machine 0 can no longer block
2592        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2593
2594        assert_eq!(f.actions[0], None);
2595        assert_eq!(f.runtime[0].blocking_duration, Duration::from_micros(2));
2596
2597        // now cause machine 1 to start blocking
2598        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2599
2600        // verify machine 1 blocks as expected
2601        assert_eq!(
2602            f.actions[1],
2603            Some(TriggerAction::BlockOutgoing {
2604                timeout: Duration::from_micros(0),
2605                duration: Duration::from_micros(1000),
2606                bypass: false,
2607                replace: false,
2608                machine: MachineId(1),
2609            })
2610        );
2611
2612        _ = f.trigger_events(
2613            &[TriggerEvent::BlockingBegin {
2614                machine: MachineId(1),
2615            }],
2616            current_time,
2617        );
2618
2619        // machine 0 should now be able to replace the blocking
2620        _ = f.trigger_events(&[TriggerEvent::NormalRecv], current_time);
2621
2622        assert_eq!(
2623            f.actions[0],
2624            Some(TriggerAction::BlockOutgoing {
2625                timeout: Duration::from_micros(2),
2626                duration: Duration::from_micros(2),
2627                bypass: false,
2628                replace: true,
2629                machine: MachineId(0),
2630            })
2631        );
2632    }
2633
2634    #[test]
2635    fn framework_machine_sampled_limit() {
2636        // we create a machine that samples a padding limit of 4 padding sent,
2637        // then should be prevented from padding further by transitioning to
2638        // self
2639
2640        // state 0
2641        let s0 = State::new(enum_map! {
2642            Event::NormalSent => vec![Trans(1, 1.0)],
2643        _ => vec![],
2644        });
2645
2646        // state 1
2647        let mut s1 = State::new(enum_map! {
2648            Event::PaddingSent => vec![Trans(1, 1.0)],
2649        _ => vec![],
2650        });
2651        s1.action = Some(Action::SendPadding {
2652            bypass: false,
2653            replace: false,
2654            timeout: Dist {
2655                dist: DistType::Uniform {
2656                    low: 1.0,
2657                    high: 1.0,
2658                },
2659                start: 0.0,
2660                max: 0.0,
2661            },
2662            limit: Some(Dist {
2663                dist: DistType::Uniform {
2664                    low: 4.0,
2665                    high: 4.0,
2666                },
2667                start: 0.0,
2668                max: 0.0,
2669            }),
2670        });
2671
2672        // machine
2673        let m = Machine::new(100000, 0.0, 0, 0.0, vec![s0, s1]).unwrap();
2674
2675        let mut current_time = Instant::now();
2676        let machines = vec![m];
2677        let mut f = Framework::new(&machines, 0.0, 0.0, current_time, rand::rng()).unwrap();
2678
2679        // trigger self to start the padding
2680        _ = f.trigger_events(&[TriggerEvent::NormalSent], current_time);
2681
2682        assert_eq!(f.runtime[0].state_limit, 4);
2683
2684        // verify that we can send 4 padding
2685        for _ in 0..4 {
2686            assert_eq!(
2687                f.actions[0],
2688                Some(TriggerAction::SendPadding {
2689                    timeout: Duration::from_micros(1),
2690                    bypass: false,
2691                    replace: false,
2692                    machine: MachineId(0),
2693                })
2694            );
2695            current_time = current_time.add(Duration::from_micros(1));
2696            _ = f.trigger_events(
2697                &[TriggerEvent::PaddingSent {
2698                    machine: MachineId(0),
2699                }],
2700                current_time,
2701            );
2702        }
2703
2704        // padding accounting correct
2705        assert_eq!(f.runtime[0].padding_sent, 4);
2706        assert_eq!(f.runtime[0].normal_sent, 1);
2707
2708        // limit should be reached after 4 padding, blocking next action
2709        assert_eq!(f.actions[0], None);
2710        assert_eq!(f.runtime[0].state_limit, 0);
2711    }
2712}