1use crate::{packet::PacketFrame, EmbeddedError, EmbeddedResult};
2use alloc::{collections::VecDeque, vec::Vec};
3
4#[derive(Debug, Clone, Copy, Eq, PartialEq)]
5pub enum LinkState {
6 Down,
7 Connecting,
8 Up,
9}
10
11#[derive(Debug, Clone, Copy, Eq, PartialEq)]
12pub struct TransportCaps {
13 pub mtu_hint: u16,
14 pub ordered_delivery: bool,
15}
16
17pub trait EmbeddedTransport {
18 fn link_state(&self) -> LinkState;
19 fn capabilities(&self) -> TransportCaps;
20 fn send_frame(&mut self, frame: &PacketFrame) -> EmbeddedResult<()>;
21 fn poll_frame(&mut self) -> EmbeddedResult<Option<PacketFrame>>;
22}
23
24#[derive(Debug, Clone, Copy, Eq, PartialEq)]
25pub enum FaultMode {
26 None,
27 DropEvery(usize),
28 DuplicateEvery(usize),
29 ReorderPairEvery(usize),
30 BackpressureEvery(usize),
31}
32
33pub struct FaultInjectingMockTransport {
34 caps: TransportCaps,
35 state: LinkState,
36 sent_count: usize,
37 recv_count: usize,
38 outbound: VecDeque<PacketFrame>,
39 inbound: VecDeque<PacketFrame>,
40 faults: Vec<FaultMode>,
41}
42
43impl FaultInjectingMockTransport {
44 pub fn new(caps: TransportCaps) -> Self {
45 Self {
46 caps,
47 state: LinkState::Up,
48 sent_count: 0,
49 recv_count: 0,
50 outbound: VecDeque::new(),
51 inbound: VecDeque::new(),
52 faults: Vec::new(),
53 }
54 }
55
56 pub fn with_faults(mut self, faults: Vec<FaultMode>) -> Self {
57 self.faults = faults;
58 self
59 }
60
61 pub fn set_state(&mut self, state: LinkState) {
62 self.state = state;
63 }
64
65 pub fn enqueue_inbound(&mut self, frames: impl IntoIterator<Item = PacketFrame>) {
66 self.inbound.extend(frames);
67 }
68
69 pub fn drain_outbound(&mut self) -> Vec<PacketFrame> {
70 self.outbound.drain(..).collect()
71 }
72
73 fn should_trigger(step: usize, n: usize) -> bool {
74 n > 0 && step > 0 && step % n == 0
75 }
76}
77
78impl EmbeddedTransport for FaultInjectingMockTransport {
79 fn link_state(&self) -> LinkState {
80 self.state
81 }
82
83 fn capabilities(&self) -> TransportCaps {
84 self.caps
85 }
86
87 fn send_frame(&mut self, frame: &PacketFrame) -> EmbeddedResult<()> {
88 if self.state != LinkState::Up {
89 return Err(EmbeddedError::Disconnected);
90 }
91 if frame.payload.len() > usize::from(self.caps.mtu_hint) {
92 return Err(EmbeddedError::InvalidArgument);
93 }
94
95 self.sent_count = self.sent_count.saturating_add(1);
96 for mode in &self.faults {
97 match *mode {
98 FaultMode::BackpressureEvery(n) if Self::should_trigger(self.sent_count, n) => {
99 return Err(EmbeddedError::Backpressure);
100 }
101 FaultMode::DropEvery(n) if Self::should_trigger(self.sent_count, n) => {
102 return Ok(());
103 }
104 FaultMode::DuplicateEvery(n) if Self::should_trigger(self.sent_count, n) => {
105 self.outbound.push_back(frame.clone());
106 self.outbound.push_back(frame.clone());
107 return Ok(());
108 }
109 FaultMode::ReorderPairEvery(n) if Self::should_trigger(self.sent_count, n) => {
110 if let Some(prev) = self.outbound.pop_back() {
111 self.outbound.push_back(frame.clone());
112 self.outbound.push_back(prev);
113 } else {
114 self.outbound.push_back(frame.clone());
115 }
116 return Ok(());
117 }
118 _ => {}
119 }
120 }
121
122 self.outbound.push_back(frame.clone());
123 Ok(())
124 }
125
126 fn poll_frame(&mut self) -> EmbeddedResult<Option<PacketFrame>> {
127 if self.state == LinkState::Down {
128 return Err(EmbeddedError::Disconnected);
129 }
130 self.recv_count = self.recv_count.saturating_add(1);
131 Ok(self.inbound.pop_front())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::{
138 EmbeddedTransport, FaultInjectingMockTransport, FaultMode, LinkState, TransportCaps,
139 };
140 use crate::{packet::PacketFrame, EmbeddedError};
141
142 fn frame(kind: u8, seq: u32, payload: &[u8]) -> PacketFrame {
143 PacketFrame::new(kind, seq, payload.to_vec()).expect("frame")
144 }
145
146 #[test]
147 fn drop_every_n_discards_target_frame() {
148 let caps = TransportCaps { mtu_hint: 64, ordered_delivery: false };
149 let mut tx =
150 FaultInjectingMockTransport::new(caps).with_faults(vec![FaultMode::DropEvery(2)]);
151 tx.send_frame(&frame(1, 1, b"aa")).expect("send1");
152 tx.send_frame(&frame(1, 2, b"bb")).expect("send2");
153 tx.send_frame(&frame(1, 3, b"cc")).expect("send3");
154
155 let out = tx.drain_outbound();
156 assert_eq!(out.len(), 2);
157 assert_eq!(out[0].sequence, 1);
158 assert_eq!(out[1].sequence, 3);
159 }
160
161 #[test]
162 fn duplicate_every_n_emits_duplicate_frame() {
163 let caps = TransportCaps { mtu_hint: 64, ordered_delivery: false };
164 let mut tx =
165 FaultInjectingMockTransport::new(caps).with_faults(vec![FaultMode::DuplicateEvery(2)]);
166 tx.send_frame(&frame(1, 1, b"aa")).expect("send1");
167 tx.send_frame(&frame(1, 2, b"bb")).expect("send2");
168
169 let out = tx.drain_outbound();
170 assert_eq!(out.len(), 3);
171 assert_eq!(out[0].sequence, 1);
172 assert_eq!(out[1].sequence, 2);
173 assert_eq!(out[2].sequence, 2);
174 }
175
176 #[test]
177 fn reorder_pair_swaps_adjacent_frames_on_trigger() {
178 let caps = TransportCaps { mtu_hint: 64, ordered_delivery: false };
179 let mut tx = FaultInjectingMockTransport::new(caps)
180 .with_faults(vec![FaultMode::ReorderPairEvery(2)]);
181 tx.send_frame(&frame(1, 1, b"aa")).expect("send1");
182 tx.send_frame(&frame(1, 2, b"bb")).expect("send2");
183
184 let out = tx.drain_outbound();
185 assert_eq!(out.len(), 2);
186 assert_eq!(out[0].sequence, 2);
187 assert_eq!(out[1].sequence, 1);
188 }
189
190 #[test]
191 fn backpressure_every_n_maps_to_error() {
192 let caps = TransportCaps { mtu_hint: 64, ordered_delivery: false };
193 let mut tx = FaultInjectingMockTransport::new(caps)
194 .with_faults(vec![FaultMode::BackpressureEvery(2)]);
195 tx.send_frame(&frame(1, 1, b"aa")).expect("send1");
196 let err = tx.send_frame(&frame(1, 2, b"bb")).expect_err("backpressure");
197 assert_eq!(err, EmbeddedError::Backpressure);
198 }
199
200 #[test]
201 fn disconnected_state_rejects_send_and_poll() {
202 let caps = TransportCaps { mtu_hint: 64, ordered_delivery: false };
203 let mut tx = FaultInjectingMockTransport::new(caps);
204 tx.set_state(LinkState::Down);
205 let err = tx.send_frame(&frame(1, 1, b"aa")).expect_err("send disconnected");
206 assert_eq!(err, EmbeddedError::Disconnected);
207
208 let err = tx.poll_frame().expect_err("poll disconnected");
209 assert_eq!(err, EmbeddedError::Disconnected);
210 }
211
212 #[test]
213 fn mtu_violation_maps_to_invalid_argument() {
214 let caps = TransportCaps { mtu_hint: 4, ordered_delivery: false };
215 let mut tx = FaultInjectingMockTransport::new(caps);
216 let err = tx.send_frame(&frame(1, 1, b"payload-too-large")).expect_err("mtu violation");
217 assert_eq!(err, EmbeddedError::InvalidArgument);
218 }
219}