Skip to main content

ace_sim/
bus.rs

1// region: Imports
2
3use crate::clock::{Clock, Duration, Instant, SimClock};
4use crate::fault::FaultConfig;
5use crate::io::NodeAddress;
6use crate::rng::{Rng, Xorshift64};
7
8// endregion: Imports
9
10// region: Envelope
11
12/// A message in-flight on the simulation bus.
13#[derive(Debug, Clone)]
14pub struct Envelope<const N: usize> {
15    pub src: NodeAddress,
16    pub dst: NodeAddress,
17    pub data: heapless::Vec<u8, N>,
18    /// Earliest time this message may be delivered.
19    pub deliver_at: Instant,
20}
21
22// endregion: Envelope
23
24// region: SimBus
25
26/// The simulation message bus.
27///
28/// Connects all nodes in the simulation. Drives time forward, delivers messages, and injects
29/// faults according to [`FaultConfig`].
30///
31/// `N` - max message payload bytes
32/// `Q` - max messages in-flight simultaneously
33pub struct SimBus<const N: usize, const Q: usize> {
34    clock: SimClock,
35    rng: Xorshift64,
36    faults: FaultConfig,
37    queue: heapless::Vec<Envelope<N>, Q>,
38}
39
40impl<const N: usize, const Q: usize> SimBus<N, Q> {
41    pub fn new(seed: u64, faults: FaultConfig) -> Self {
42        Self {
43            clock: SimClock::new(),
44            rng: Xorshift64::new(seed),
45            faults,
46            queue: heapless::Vec::new(),
47        }
48    }
49
50    /// Returns the current simulation time.
51    pub fn now(&self) -> Instant {
52        self.clock.now()
53    }
54
55    /// Advances simulation time by `duration` and returns all messages that are due for delivery
56    /// at or before the new time.
57    pub fn tick(&mut self, duration: Duration) -> heapless::Vec<Envelope<N>, Q> {
58        self.clock.advance(duration);
59        let now = self.clock.now();
60
61        let mut delivered = heapless::Vec::new();
62        let mut remaining = heapless::Vec::new();
63
64        for envelope in self.queue.drain(..) {
65            if envelope.deliver_at <= now {
66                let _ = delivered.push(envelope);
67            } else {
68                let _ = remaining.push(envelope);
69            }
70        }
71
72        if delivered.len() > 1 {
73            for i in 0..delivered.len() - 1 {
74                if self
75                    .rng
76                    .chance(self.faults.message_reorder.0, self.faults.message_reorder.1)
77                {
78                    delivered.swap(i, i + 1);
79                }
80            }
81        }
82
83        self.queue = remaining;
84        delivered
85    }
86
87    /// Enqueues a message from `src` to `dst` with fault injection applied.
88    ///
89    /// Returns `true` if the message was enqueued, `false` if it was dropped by fault injection or
90    /// the queue is full
91    pub fn send(&mut self, src: NodeAddress, dst: NodeAddress, data: &[u8]) -> bool {
92        if self
93            .rng
94            .chance(self.faults.message_loss.0, self.faults.message_loss.1)
95        {
96            return false;
97        }
98
99        if self
100            .rng
101            .chance(self.faults.timeout.0, self.faults.timeout.1)
102        {
103            return false;
104        }
105
106        let mut payload = heapless::Vec::new();
107
108        for &byte in data {
109            if self
110                .rng
111                .chance(self.faults.corruption.0, self.faults.corruption.1)
112            {
113                let _ = payload.push(byte ^ self.rng.next_u8());
114            } else {
115                let _ = payload.push(byte);
116            }
117        }
118
119        let deliver_at = if self
120            .rng
121            .chance(self.faults.message_delay.0, self.faults.message_delay.1)
122        {
123            let delay_us = self.rng.next_u64() % self.faults.max_delay.as_micros().max(1);
124            self.clock.now() + Duration::from_micros(delay_us)
125        } else {
126            self.clock.now()
127        };
128
129        let envelope = Envelope {
130            src,
131            dst,
132            data: payload,
133            deliver_at,
134        };
135
136        self.queue.push(envelope).is_ok()
137    }
138
139    /// Returns a reference to the current fault config
140    pub fn faults(&self) -> &FaultConfig {
141        &self.faults
142    }
143
144    /// Returns the fault config - allows escalating fault severity during a simulation run.
145    pub fn set_faults(&mut self, faults: FaultConfig) {
146        self.faults = faults;
147    }
148
149    /// Returns the RNG seed-derived next value - useful for injecting spontaneous NRCs at the node
150    /// level.
151    pub fn next_u8(&mut self) -> u8 {
152        self.rng.next_u8()
153    }
154
155    pub fn chance(&mut self, numerator: u32, denominator: u32) -> bool {
156        self.rng.chance(numerator, denominator)
157    }
158}
159
160// endregion: SimBus