Skip to main content

smoltcp/phy/
fault_injector.rs

1use crate::phy::{self, Device, DeviceCapabilities};
2use crate::time::{Duration, Instant};
3
4use super::PacketMeta;
5
6// We use our own RNG to stay compatible with #![no_std].
7// The use of the RNG below has a slight bias, but it doesn't matter.
8fn xorshift32(state: &mut u32) -> u32 {
9    let mut x = *state;
10    x ^= x << 13;
11    x ^= x >> 17;
12    x ^= x << 5;
13    *state = x;
14    x
15}
16
17// This could be fixed once associated consts are stable.
18const MTU: usize = 1536;
19
20#[derive(Debug, Default, Clone, Copy)]
21#[cfg_attr(feature = "defmt", derive(defmt::Format))]
22struct Config {
23    corrupt_pct: u8,
24    drop_pct: u8,
25    max_size: usize,
26    max_tx_rate: u64,
27    max_rx_rate: u64,
28    interval: Duration,
29}
30
31#[derive(Debug, Clone)]
32#[cfg_attr(feature = "defmt", derive(defmt::Format))]
33struct State {
34    rng_seed: u32,
35    refilled_at: Instant,
36    tx_bucket: u64,
37    rx_bucket: u64,
38}
39
40impl State {
41    fn maybe(&mut self, pct: u8) -> bool {
42        xorshift32(&mut self.rng_seed) % 100 < pct as u32
43    }
44
45    fn corrupt<T: AsMut<[u8]>>(&mut self, mut buffer: T) {
46        let buffer = buffer.as_mut();
47        // We introduce a single bitflip, as the most likely, and the hardest to detect, error.
48        let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len();
49        let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
50        buffer[index] ^= bit;
51    }
52
53    fn refill(&mut self, config: &Config, timestamp: Instant) {
54        if timestamp - self.refilled_at > config.interval {
55            self.tx_bucket = config.max_tx_rate;
56            self.rx_bucket = config.max_rx_rate;
57            self.refilled_at = timestamp;
58        }
59    }
60
61    fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool {
62        if config.max_tx_rate == 0 {
63            return true;
64        }
65
66        self.refill(config, timestamp);
67        if self.tx_bucket > 0 {
68            self.tx_bucket -= 1;
69            true
70        } else {
71            false
72        }
73    }
74
75    fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool {
76        if config.max_rx_rate == 0 {
77            return true;
78        }
79
80        self.refill(config, timestamp);
81        if self.rx_bucket > 0 {
82            self.rx_bucket -= 1;
83            true
84        } else {
85            false
86        }
87    }
88}
89
90/// A fault injector device.
91///
92/// A fault injector is a device that alters packets traversing through it to simulate
93/// adverse network conditions (such as random packet loss or corruption), or software
94/// or hardware limitations (such as a limited number or size of usable network buffers).
95#[derive(Debug)]
96pub struct FaultInjector<D: Device> {
97    inner: D,
98    state: State,
99    config: Config,
100    rx_buf: [u8; MTU],
101}
102
103impl<D: Device> FaultInjector<D> {
104    /// Create a fault injector device, using the given random number generator seed.
105    pub fn new(inner: D, seed: u32) -> FaultInjector<D> {
106        FaultInjector {
107            inner,
108            state: State {
109                rng_seed: seed,
110                refilled_at: Instant::from_millis(0),
111                tx_bucket: 0,
112                rx_bucket: 0,
113            },
114            config: Config::default(),
115            rx_buf: [0u8; MTU],
116        }
117    }
118
119    /// Return the underlying device, consuming the fault injector.
120    pub fn into_inner(self) -> D {
121        self.inner
122    }
123
124    /// Return the probability of corrupting a packet, in percents.
125    pub fn corrupt_chance(&self) -> u8 {
126        self.config.corrupt_pct
127    }
128
129    /// Return the probability of dropping a packet, in percents.
130    pub fn drop_chance(&self) -> u8 {
131        self.config.drop_pct
132    }
133
134    /// Return the maximum packet size, in octets.
135    pub fn max_packet_size(&self) -> usize {
136        self.config.max_size
137    }
138
139    /// Return the maximum packet transmission rate, in packets per second.
140    pub fn max_tx_rate(&self) -> u64 {
141        self.config.max_tx_rate
142    }
143
144    /// Return the maximum packet reception rate, in packets per second.
145    pub fn max_rx_rate(&self) -> u64 {
146        self.config.max_rx_rate
147    }
148
149    /// Return the interval for packet rate limiting, in milliseconds.
150    pub fn bucket_interval(&self) -> Duration {
151        self.config.interval
152    }
153
154    /// Set the probability of corrupting a packet, in percents.
155    ///
156    /// # Panics
157    /// This function panics if the probability is not between 0% and 100%.
158    pub fn set_corrupt_chance(&mut self, pct: u8) {
159        if pct > 100 {
160            panic!("percentage out of range")
161        }
162        self.config.corrupt_pct = pct
163    }
164
165    /// Set the probability of dropping a packet, in percents.
166    ///
167    /// # Panics
168    /// This function panics if the probability is not between 0% and 100%.
169    pub fn set_drop_chance(&mut self, pct: u8) {
170        if pct > 100 {
171            panic!("percentage out of range")
172        }
173        self.config.drop_pct = pct
174    }
175
176    /// Set the maximum packet size, in octets.
177    pub fn set_max_packet_size(&mut self, size: usize) {
178        self.config.max_size = size
179    }
180
181    /// Set the maximum packet transmission rate, in packets per interval.
182    pub fn set_max_tx_rate(&mut self, rate: u64) {
183        self.config.max_tx_rate = rate
184    }
185
186    /// Set the maximum packet reception rate, in packets per interval.
187    pub fn set_max_rx_rate(&mut self, rate: u64) {
188        self.config.max_rx_rate = rate
189    }
190
191    /// Set the interval for packet rate limiting, in milliseconds.
192    pub fn set_bucket_interval(&mut self, interval: Duration) {
193        self.state.refilled_at = Instant::from_millis(0);
194        self.config.interval = interval
195    }
196}
197
198impl<D: Device> Device for FaultInjector<D> {
199    type RxToken<'a>
200        = RxToken<'a>
201    where
202        Self: 'a;
203    type TxToken<'a>
204        = TxToken<'a, D::TxToken<'a>>
205    where
206        Self: 'a;
207
208    fn capabilities(&self) -> DeviceCapabilities {
209        let mut caps = self.inner.capabilities();
210        if caps.max_transmission_unit > MTU {
211            caps.max_transmission_unit = MTU;
212        }
213        caps
214    }
215
216    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
217        let (rx_token, tx_token) = self.inner.receive(timestamp)?;
218        let rx_meta = <D::RxToken<'_> as phy::RxToken>::meta(&rx_token);
219
220        let len = super::RxToken::consume(rx_token, |buffer| {
221            if (self.config.max_size > 0 && buffer.len() > self.config.max_size)
222                || buffer.len() > self.rx_buf.len()
223            {
224                net_trace!("rx: dropping a packet that is too large");
225                return None;
226            }
227            self.rx_buf[..buffer.len()].copy_from_slice(buffer);
228            Some(buffer.len())
229        })?;
230
231        let buf = &mut self.rx_buf[..len];
232
233        if self.state.maybe(self.config.drop_pct) {
234            net_trace!("rx: randomly dropping a packet");
235            return None;
236        }
237
238        if !self.state.maybe_receive(&self.config, timestamp) {
239            net_trace!("rx: dropping a packet because of rate limiting");
240            return None;
241        }
242
243        if self.state.maybe(self.config.corrupt_pct) {
244            net_trace!("rx: randomly corrupting a packet");
245            self.state.corrupt(&mut buf[..]);
246        }
247
248        let rx = RxToken { buf, meta: rx_meta };
249        let tx = TxToken {
250            state: &mut self.state,
251            config: self.config,
252            token: tx_token,
253            junk: [0; MTU],
254            timestamp,
255        };
256        Some((rx, tx))
257    }
258
259    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
260        self.inner.transmit(timestamp).map(|token| TxToken {
261            state: &mut self.state,
262            config: self.config,
263            token,
264            junk: [0; MTU],
265            timestamp,
266        })
267    }
268}
269
270#[doc(hidden)]
271pub struct RxToken<'a> {
272    buf: &'a mut [u8],
273    meta: PacketMeta,
274}
275
276impl<'a> phy::RxToken for RxToken<'a> {
277    fn consume<R, F>(self, f: F) -> R
278    where
279        F: FnOnce(&[u8]) -> R,
280    {
281        f(self.buf)
282    }
283
284    fn meta(&self) -> phy::PacketMeta {
285        self.meta
286    }
287}
288
289#[doc(hidden)]
290pub struct TxToken<'a, Tx: phy::TxToken> {
291    state: &'a mut State,
292    config: Config,
293    token: Tx,
294    junk: [u8; MTU],
295    timestamp: Instant,
296}
297
298impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
299    fn consume<R, F>(mut self, len: usize, f: F) -> R
300    where
301        F: FnOnce(&mut [u8]) -> R,
302    {
303        let drop = if self.state.maybe(self.config.drop_pct) {
304            net_trace!("tx: randomly dropping a packet");
305            true
306        } else if self.config.max_size > 0 && len > self.config.max_size {
307            net_trace!("tx: dropping a packet that is too large");
308            true
309        } else if !self.state.maybe_transmit(&self.config, self.timestamp) {
310            net_trace!("tx: dropping a packet because of rate limiting");
311            true
312        } else {
313            false
314        };
315
316        if drop {
317            return f(&mut self.junk[..len]);
318        }
319
320        self.token.consume(len, |buf| {
321            if self.state.maybe(self.config.corrupt_pct) {
322                net_trace!("tx: corrupting a packet");
323                self.state.corrupt(&mut *buf);
324            }
325            f(buf)
326        })
327    }
328
329    fn set_meta(&mut self, meta: PacketMeta) {
330        self.token.set_meta(meta);
331    }
332}