ace_sim/can_bus.rs
1//! CAN-aware simulation bus
2//!
3//! Models a CAN bus between an ISO-TP node and one or more ECU nodes. CAN is connection-less -no
4//! session state, no handshake. The bus carries raw CAN frames with CAN-specific fault injection
5//! layered on top of the message-level faults from `FaultConfig`.
6//!
7//! CAN-specific faults:
8//! - Bit error: a transmitted frame is corrupted at the bit level
9//! - Arbitration loss: a frame is silently dropped as if lost to arbitration (another node won
10//! the bus)
11//! - Bus-off: the bus enters an error state and stops delivering frames until reset.
12
13// region: Imports
14
15use heapless::Vec;
16
17use crate::{
18 bus::{Envelope, SimBus},
19 clock::{Duration, Instant},
20 fault::FaultConfig,
21 io::NodeAddress,
22 rng::{Rng, Xorshift64},
23};
24
25// endregion: Imports
26
27// region: CanFaultConfig
28
29// CAN-level fault configuration.
30#[derive(Debug, Clone)]
31pub struct CanFaultConfig {
32 /// Underlying message-level fault config.
33 pub message: FaultConfig,
34
35 /// Probability a frame is dropped due to arbitration loss
36 pub arbitration_loss: (u32, u32),
37
38 /// Probability a frame triggers a bit error (corruption + retry drop).
39 pub bit_error: (u32, u32),
40
41 /// Probability the bus enters bus-off state on any given tick. When bus-off, all frames are
42 /// dropped until `reset_bus_off()`.
43 pub bus_off: (u32, u32),
44}
45
46impl CanFaultConfig {
47 pub fn none() -> Self {
48 Self {
49 message: FaultConfig::none(),
50 arbitration_loss: (0, 1),
51 bit_error: (0, 1),
52 bus_off: (0, 1),
53 }
54 }
55
56 pub fn light() -> Self {
57 Self {
58 message: FaultConfig::light(),
59 arbitration_loss: (1, 200),
60 bit_error: (1, 200),
61 bus_off: (1, 200),
62 }
63 }
64
65 pub fn chaos() -> Self {
66 Self {
67 message: FaultConfig::chaos(),
68 arbitration_loss: (1, 10),
69 bit_error: (1, 10),
70 bus_off: (1, 10),
71 }
72 }
73}
74
75// endregion: CanFaultConfig
76
77// region: CanBusState
78
79// The operational state of the simulated CAN bus.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum CanBusState {
82 /// Bus is operational - frames are delivered normally.
83 Active,
84
85 /// Bus is in error-passive state - frames may still be delivered but error counts are
86 /// elevated.
87 ErrorPassive,
88
89 /// Bus-off - no frames are delivered until the bus is reset.
90 BusOff { since: Instant },
91}
92
93impl CanBusState {
94 pub fn is_operational(&self) -> bool {
95 matches!(self, Self::Active | Self::ErrorPassive)
96 }
97}
98
99// endregion: CanBusState
100
101// region: CanEvent
102
103/// Events the `CanSimBus` delivers to nodes alongside messages.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub enum CanEvent {
106 /// Bus transitioned to bus-off state.
107 BusOff,
108
109 /// Bus recovered from bus-off state.
110 BusRecovered,
111
112 /// A bit error was detected on a frame from `src`.
113 BitError { src: NodeAddress },
114}
115
116// endregion: CanEvent
117
118// region: CanSimBus
119
120/// A CAN-aware simulation bus.
121///
122/// Wraps `SimBus` for message delivery and adds CAN bus state and CAN-specific fault injection.
123/// Connection-less - any node may send to any other node as long as the bus is operational.
124///
125/// `N` - max frame payload bytes (8 for classic CAN, 64 for CAN FD)
126/// `Q` - max frames in-flight simultaneously
127#[derive(Debug)]
128pub struct CanSimBus<const N: usize, const Q: usize> {
129 /// Underlying message bus.
130 inner: SimBus<N, Q>,
131
132 /// CAN fault configuration.
133 can_faults: CanFaultConfig,
134
135 /// Current bus state.
136 bus_state: CanBusState,
137
138 /// Accumulated CAN events for nodes to drain.
139 events: Vec<CanEvent, 16>,
140
141 /// Dedicated RNG for CAN-level fault decisions.
142 rng: Xorshift64,
143}
144
145impl<const N: usize, const Q: usize> CanSimBus<N, Q> {
146 /// Creates a new `CanSimBus`.
147 ///
148 /// `seed` - seeds both the message bus RNG and the CAN fault RNG. The CAN RNG uses
149 /// `seed.wrapping_add(2)` for independence.
150 pub fn new(seed: u64, faults: CanFaultConfig) -> Self {
151 Self {
152 inner: SimBus::new(seed, faults.message.clone()),
153 can_faults: faults,
154 bus_state: CanBusState::Active,
155 events: heapless::Vec::new(),
156 rng: Xorshift64::new(seed.wrapping_add(2)),
157 }
158 }
159
160 // region: Message Delivery
161
162 /// Enqueues a CAN frame - rejected if the bus is in bus-off state or CAN-level fault injection
163 /// drops it.
164 ///
165 /// Returns `true` if the frame was accepted.
166 pub fn send(&mut self, src: NodeAddress, dst: NodeAddress, data: &[u8]) -> bool {
167 if !self.bus_state.is_operational() {
168 return false;
169 }
170
171 if self.rng.chance(
172 self.can_faults.arbitration_loss.0,
173 self.can_faults.arbitration_loss.1,
174 ) {
175 return false;
176 }
177
178 if self
179 .rng
180 .chance(self.can_faults.bit_error.0, self.can_faults.bit_error.1)
181 {
182 let _ = self.events.push(CanEvent::BitError { src: src.clone() });
183 return false;
184 }
185
186 self.inner.send(src, dst, data)
187 }
188
189 // endregion: Message Delivery
190
191 // region: Bus State Management
192
193 /// Manually triggers bus-off - useful for DST fault injection.
194 pub fn trigger_bus_off(&mut self) {
195 let now = self.inner.now();
196
197 self.bus_state = CanBusState::BusOff { since: now };
198
199 let _ = self.events.push(CanEvent::BusOff);
200 }
201
202 /// Resets the bus from bus-off state back to Active.
203 pub fn reset_bus_off(&mut self) {
204 if matches!(self.bus_state, CanBusState::BusOff { .. }) {
205 self.bus_state = CanBusState::Active;
206 let _ = self.events.push(CanEvent::BusRecovered);
207 }
208 }
209
210 pub fn bus_state(&self) -> &CanBusState {
211 &self.bus_state
212 }
213
214 // endregion: Bus State Management
215
216 // region: Tick
217
218 /// Advances simulation time, delivers due frames, and checks bus-off fault injection.
219 pub fn tick(&mut self, duration: Duration) -> heapless::Vec<Envelope<N>, Q> {
220 if self.bus_state.is_operational() {
221 if self
222 .rng
223 .chance(self.can_faults.bus_off.0, self.can_faults.bus_off.1)
224 {
225 let now = self.inner.now();
226
227 self.bus_state = CanBusState::BusOff { since: now };
228
229 let _ = self.events.push(CanEvent::BusOff);
230 }
231 }
232
233 if !self.bus_state.is_operational() {
234 let _ = self.inner.tick(duration);
235 return heapless::Vec::new();
236 }
237
238 self.inner.tick(duration)
239 }
240
241 // endregion: Tick
242
243 // region: Accessors
244
245 pub fn now(&self) -> Instant {
246 self.inner.now()
247 }
248
249 /// Drains accumulated CAN events.
250 pub fn drain_events(&mut self) -> impl Iterator<Item = CanEvent> + '_ {
251 self.events.drain(..)
252 }
253
254 pub fn set_faults(&mut self, faults: CanFaultConfig) {
255 self.inner.set_faults(faults.message.clone());
256 self.can_faults = faults;
257 }
258
259 pub fn inner_mut(&mut self) -> &mut SimBus<N, Q> {
260 &mut self.inner
261 }
262
263 // endregion: Accessors
264}
265
266// endregion: CanSimBus