ace_sim/tcp_runner.rs
1//! TcpSimRunner - drives SimNodeErased slices over a TcpSimBus.
2//!
3//! Extends the SimRunner concept for TCP-aware scenarios. Alongside message delivery it:
4//! - Delivers TcpEvents to nodes that implement TcpEventHandler
5//! - Handles connection state transitions visible to nodes
6//! - Drains node outboxes back onto the TCP bus (rejected if disconnected)
7
8// region: Imports
9
10use crate::{
11 clock::{Duration, Instant},
12 io::NodeAddress,
13 node::SimNodeErased,
14 tcp_bus::{TcpEvent, TcpSimBus},
15};
16use ace_core::Vec;
17
18// endregion: Imports
19
20// region: TcpEventHandler
21
22/// Optional trait for nodes that need to observe TCP connection events.
23///
24/// Nodes on TCP bus may implement this to react to connection establishment, reset, and closure.
25/// The `TcpSimRunner` calls this after delivering messages on each tick.
26///
27/// Not all nodes need TCP event awareness - only the `DoipTester` and gateway face nodes need it.
28/// Nodes that do not implement this trait simply ignore connection events.
29pub trait TcpEventHandler {
30 fn on_tcp_event(&mut self, event: &TcpEvent, now: Instant);
31}
32
33// endregion: TcpEventHandler
34
35// region: TcpSimRunner
36
37/// Drives `SimNodeErased` slices over a `TcpSimBus`.
38///
39/// `N` - max message payload bytes
40/// `Q` - max messages in-flight on the bus
41pub struct TcpSimRunner<
42 const MAX_DATA: usize,
43 const MAX_QUEUED: usize,
44 const TCP_MAX_EVENTS: usize,
45 const MAX_OUTBOX: usize,
46> {
47 bus: TcpSimBus<MAX_DATA, MAX_QUEUED, TCP_MAX_EVENTS>,
48}
49
50impl<
51 const MAX_DATA: usize,
52 const MAX_QUEUED: usize,
53 const TCP_MAX_EVENTS: usize,
54 const MAX_OUTBOX: usize,
55 > TcpSimRunner<MAX_DATA, MAX_QUEUED, TCP_MAX_EVENTS, MAX_OUTBOX>
56{
57 pub fn new(bus: TcpSimBus<MAX_DATA, MAX_QUEUED, TCP_MAX_EVENTS>) -> Self {
58 Self { bus }
59 }
60
61 pub fn bus(&mut self) -> &mut TcpSimBus<MAX_DATA, MAX_QUEUED, TCP_MAX_EVENTS> {
62 &mut self.bus
63 }
64
65 pub fn now(&self) -> Instant {
66 self.bus.now()
67 }
68
69 /// Ticks the simulation by `duration`.
70 ///
71 /// Order of operations per tick:
72 /// 1. Advance bus clock, apply TCP fault injection, deliver due messages
73 /// 2. Deliver TCP connection events to nodes implementing `TcpEventHandler`
74 /// 3. Tick all nodes for time-based transitions
75 /// 4. Drain node outboxes and enqueue onto the bus
76 ///
77 /// Returns the number of messages delivered.
78 pub fn tick(
79 &mut self,
80 nodes: &mut [&mut dyn SimNodeErased<MAX_DATA, MAX_OUTBOX>],
81 tcp_event_nodes: &mut [&mut dyn TcpEventHandler],
82 duration: Duration,
83 ) -> usize {
84 let delivered = self.bus.tick(duration);
85 let now = self.bus.now();
86 let mut count = 0;
87
88 for envelope in &delivered {
89 for node in nodes.iter_mut() {
90 if *node.address() == envelope.dst {
91 node.handle(&envelope.src, &envelope.data, now);
92 count += 1;
93 }
94 }
95 }
96
97 let tcp_events: Vec<TcpEvent, TCP_MAX_EVENTS> = self.bus.drain_events().collect();
98 for event in &tcp_events {
99 for handler in tcp_event_nodes.iter_mut() {
100 handler.on_tcp_event(event, now);
101 }
102 }
103
104 for node in nodes.iter_mut() {
105 node.tick(now);
106 }
107
108 let mut outbox: Vec<(NodeAddress, Vec<u8, MAX_DATA>), MAX_OUTBOX> = Vec::new();
109 for node in nodes.iter_mut() {
110 outbox.clear();
111 node.drain_outbox(&mut outbox);
112
113 let src = node.address().clone();
114
115 for (dst, data) in outbox.iter() {
116 self.bus.send(src.clone(), dst.clone(), data);
117 }
118 }
119
120 count
121 }
122}
123
124// endregion: TcpSimRunner