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