Skip to main content

acex_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 crate::{
16    bus::{Envelope, SimBus},
17    clock::{Duration, Instant},
18    fault::FaultConfig,
19    io::NodeAddress,
20    rng::{Rng, Xorshift64},
21};
22use acex_core::Vec;
23
24// endregion: Imports
25
26// region: CanFaultConfig
27
28// CAN-level fault configuration.
29#[derive(Debug, Clone)]
30#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82pub enum CanBusState {
83    /// Bus is operational - frames are delivered normally.
84    Active,
85
86    /// Bus is in error-passive state - frames may still be delivered but error counts are
87    /// elevated.
88    ErrorPassive,
89
90    /// Bus-off - no frames are delivered until the bus is reset.
91    BusOff { since: Instant },
92}
93
94impl CanBusState {
95    pub fn is_operational(&self) -> bool {
96        matches!(self, Self::Active | Self::ErrorPassive)
97    }
98}
99
100// endregion: CanBusState
101
102// region: CanEvent
103
104/// Events the `CanSimBus` delivers to nodes alongside messages.
105#[derive(Debug, Clone, PartialEq, Eq)]
106#[cfg_attr(feature = "defmt", derive(defmt::Format))]
107pub enum CanEvent {
108    /// Bus transitioned to bus-off state.
109    BusOff,
110
111    /// Bus recovered from bus-off state.
112    BusRecovered,
113
114    /// A bit error was detected on a frame from `src`.
115    BitError { src: NodeAddress },
116}
117
118// endregion: CanEvent
119
120// region: CanSimBus
121
122/// A CAN-aware simulation bus.
123///
124/// Wraps `SimBus` for message delivery and adds CAN bus state and CAN-specific fault injection.
125/// Connection-less - any node may send to any other node as long as the bus is operational.
126///
127/// `N` - max frame payload bytes (8 for classic CAN, 64 for CAN FD)
128/// `Q` - max frames in-flight simultaneously
129#[derive(Debug)]
130#[cfg_attr(all(feature = "defmt", not(feature = "alloc")), derive(defmt::Format))]
131pub struct CanSimBus<const MAX_DATA: usize, const MAX_QUEUED: usize> {
132    /// Underlying message bus.
133    inner: SimBus<MAX_DATA, MAX_QUEUED>,
134
135    /// CAN fault configuration.
136    can_faults: CanFaultConfig,
137
138    /// Current bus state.
139    bus_state: CanBusState,
140
141    /// Accumulated CAN events for nodes to drain.
142    events: Vec<CanEvent, 16>,
143
144    /// Dedicated RNG for CAN-level fault decisions.
145    rng: Xorshift64,
146}
147
148impl<const MAX_DATA: usize, const MAX_QUEUED: usize> CanSimBus<MAX_DATA, MAX_QUEUED> {
149    /// Creates a new `CanSimBus`.
150    ///
151    /// `seed` - seeds both the message bus RNG and the CAN fault RNG. The CAN RNG uses
152    /// `seed.wrapping_add(2)` for independence.
153    pub fn new(seed: u64, faults: CanFaultConfig) -> Self {
154        Self {
155            inner: SimBus::new(seed, faults.message.clone()),
156            can_faults: faults,
157            bus_state: CanBusState::Active,
158            events: Vec::new(),
159            rng: Xorshift64::new(seed.wrapping_add(2)),
160        }
161    }
162
163    // region: Message Delivery
164
165    /// Enqueues a CAN frame - rejected if the bus is in bus-off state or CAN-level fault injection
166    /// drops it.
167    ///
168    /// Returns `true` if the frame was accepted.
169    pub fn send(&mut self, src: NodeAddress, dst: NodeAddress, data: &[u8]) -> bool {
170        if !self.bus_state.is_operational() {
171            return false;
172        }
173
174        if self.rng.chance(
175            self.can_faults.arbitration_loss.0,
176            self.can_faults.arbitration_loss.1,
177        ) {
178            return false;
179        }
180
181        if self
182            .rng
183            .chance(self.can_faults.bit_error.0, self.can_faults.bit_error.1)
184        {
185            let _ = self.events.push(CanEvent::BitError { src: src.clone() });
186            return false;
187        }
188
189        self.inner.send(src, dst, data)
190    }
191
192    // endregion: Message Delivery
193
194    // region: Bus State Management
195
196    /// Manually triggers bus-off - useful for DST fault injection.
197    pub fn trigger_bus_off(&mut self) {
198        let now = self.inner.now();
199
200        self.bus_state = CanBusState::BusOff { since: now };
201
202        let _ = self.events.push(CanEvent::BusOff);
203    }
204
205    /// Resets the bus from bus-off state back to Active.
206    pub fn reset_bus_off(&mut self) {
207        if matches!(self.bus_state, CanBusState::BusOff { .. }) {
208            self.bus_state = CanBusState::Active;
209            let _ = self.events.push(CanEvent::BusRecovered);
210        }
211    }
212
213    pub fn bus_state(&self) -> &CanBusState {
214        &self.bus_state
215    }
216
217    // endregion: Bus State Management
218
219    // region: Tick
220
221    /// Advances simulation time, delivers due frames, and checks bus-off fault injection.
222    pub fn tick(&mut self, duration: Duration) -> Vec<Envelope<MAX_DATA>, MAX_QUEUED> {
223        if self.bus_state.is_operational() {
224            if self
225                .rng
226                .chance(self.can_faults.bus_off.0, self.can_faults.bus_off.1)
227            {
228                let now = self.inner.now();
229
230                self.bus_state = CanBusState::BusOff { since: now };
231
232                let _ = self.events.push(CanEvent::BusOff);
233            }
234        }
235
236        if !self.bus_state.is_operational() {
237            let _ = self.inner.tick(duration);
238            return Vec::new();
239        }
240
241        self.inner.tick(duration)
242    }
243
244    // endregion: Tick
245
246    // region: Accessors
247
248    pub fn now(&self) -> Instant {
249        self.inner.now()
250    }
251
252    /// Drains accumulated CAN events.
253    pub fn drain_events(&mut self) -> impl Iterator<Item = CanEvent> + '_ {
254        self.events.drain(..)
255    }
256
257    pub fn set_faults(&mut self, faults: CanFaultConfig) {
258        self.inner.set_faults(faults.message.clone());
259        self.can_faults = faults;
260    }
261
262    pub fn inner_mut(&mut self) -> &mut SimBus<MAX_DATA, MAX_QUEUED> {
263        &mut self.inner
264    }
265
266    // endregion: Accessors
267}
268
269// endregion: CanSimBus