use crate::phy::{self, Device, DeviceCapabilities};
use crate::time::{Duration, Instant};
use super::PacketMeta;
fn xorshift32(state: &mut u32) -> u32 {
let mut x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*state = x;
x
}
const MTU: usize = 1536;
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct Config {
corrupt_pct: u8,
drop_pct: u8,
max_size: usize,
max_tx_rate: u64,
max_rx_rate: u64,
interval: Duration,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
struct State {
rng_seed: u32,
refilled_at: Instant,
tx_bucket: u64,
rx_bucket: u64,
}
impl State {
fn maybe(&mut self, pct: u8) -> bool {
xorshift32(&mut self.rng_seed) % 100 < pct as u32
}
fn corrupt<T: AsMut<[u8]>>(&mut self, mut buffer: T) {
let buffer = buffer.as_mut();
let index = (xorshift32(&mut self.rng_seed) as usize) % buffer.len();
let bit = 1 << (xorshift32(&mut self.rng_seed) % 8) as u8;
buffer[index] ^= bit;
}
fn refill(&mut self, config: &Config, timestamp: Instant) {
if timestamp - self.refilled_at > config.interval {
self.tx_bucket = config.max_tx_rate;
self.rx_bucket = config.max_rx_rate;
self.refilled_at = timestamp;
}
}
fn maybe_transmit(&mut self, config: &Config, timestamp: Instant) -> bool {
if config.max_tx_rate == 0 {
return true;
}
self.refill(config, timestamp);
if self.tx_bucket > 0 {
self.tx_bucket -= 1;
true
} else {
false
}
}
fn maybe_receive(&mut self, config: &Config, timestamp: Instant) -> bool {
if config.max_rx_rate == 0 {
return true;
}
self.refill(config, timestamp);
if self.rx_bucket > 0 {
self.rx_bucket -= 1;
true
} else {
false
}
}
}
#[derive(Debug)]
pub struct FaultInjector<D: Device> {
inner: D,
state: State,
config: Config,
rx_buf: [u8; MTU],
}
impl<D: Device> FaultInjector<D> {
pub fn new(inner: D, seed: u32) -> FaultInjector<D> {
FaultInjector {
inner,
state: State {
rng_seed: seed,
refilled_at: Instant::from_millis(0),
tx_bucket: 0,
rx_bucket: 0,
},
config: Config::default(),
rx_buf: [0u8; MTU],
}
}
pub fn into_inner(self) -> D {
self.inner
}
pub fn corrupt_chance(&self) -> u8 {
self.config.corrupt_pct
}
pub fn drop_chance(&self) -> u8 {
self.config.drop_pct
}
pub fn max_packet_size(&self) -> usize {
self.config.max_size
}
pub fn max_tx_rate(&self) -> u64 {
self.config.max_tx_rate
}
pub fn max_rx_rate(&self) -> u64 {
self.config.max_rx_rate
}
pub fn bucket_interval(&self) -> Duration {
self.config.interval
}
pub fn set_corrupt_chance(&mut self, pct: u8) {
if pct > 100 {
panic!("percentage out of range")
}
self.config.corrupt_pct = pct
}
pub fn set_drop_chance(&mut self, pct: u8) {
if pct > 100 {
panic!("percentage out of range")
}
self.config.drop_pct = pct
}
pub fn set_max_packet_size(&mut self, size: usize) {
self.config.max_size = size
}
pub fn set_max_tx_rate(&mut self, rate: u64) {
self.config.max_tx_rate = rate
}
pub fn set_max_rx_rate(&mut self, rate: u64) {
self.config.max_rx_rate = rate
}
pub fn set_bucket_interval(&mut self, interval: Duration) {
self.state.refilled_at = Instant::from_millis(0);
self.config.interval = interval
}
}
impl<D: Device> Device for FaultInjector<D> {
type RxToken<'a> = RxToken<'a>
where
Self: 'a;
type TxToken<'a> = TxToken<'a, D::TxToken<'a>>
where
Self: 'a;
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = self.inner.capabilities();
if caps.max_transmission_unit > MTU {
caps.max_transmission_unit = MTU;
}
caps
}
fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let (rx_token, tx_token) = self.inner.receive(timestamp)?;
let rx_meta = <D::RxToken<'_> as phy::RxToken>::meta(&rx_token);
let len = super::RxToken::consume(rx_token, |buffer| {
if (self.config.max_size > 0 && buffer.len() > self.config.max_size)
|| buffer.len() > self.rx_buf.len()
{
net_trace!("rx: dropping a packet that is too large");
return None;
}
self.rx_buf[..buffer.len()].copy_from_slice(buffer);
Some(buffer.len())
})?;
let buf = &mut self.rx_buf[..len];
if self.state.maybe(self.config.drop_pct) {
net_trace!("rx: randomly dropping a packet");
return None;
}
if !self.state.maybe_receive(&self.config, timestamp) {
net_trace!("rx: dropping a packet because of rate limiting");
return None;
}
if self.state.maybe(self.config.corrupt_pct) {
net_trace!("rx: randomly corrupting a packet");
self.state.corrupt(&mut buf[..]);
}
let rx = RxToken { buf, meta: rx_meta };
let tx = TxToken {
state: &mut self.state,
config: self.config,
token: tx_token,
junk: [0; MTU],
timestamp,
};
Some((rx, tx))
}
fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
self.inner.transmit(timestamp).map(|token| TxToken {
state: &mut self.state,
config: self.config,
token,
junk: [0; MTU],
timestamp,
})
}
}
#[doc(hidden)]
pub struct RxToken<'a> {
buf: &'a mut [u8],
meta: PacketMeta,
}
impl<'a> phy::RxToken for RxToken<'a> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
f(self.buf)
}
fn meta(&self) -> phy::PacketMeta {
self.meta
}
}
#[doc(hidden)]
pub struct TxToken<'a, Tx: phy::TxToken> {
state: &'a mut State,
config: Config,
token: Tx,
junk: [u8; MTU],
timestamp: Instant,
}
impl<'a, Tx: phy::TxToken> phy::TxToken for TxToken<'a, Tx> {
fn consume<R, F>(mut self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let drop = if self.state.maybe(self.config.drop_pct) {
net_trace!("tx: randomly dropping a packet");
true
} else if self.config.max_size > 0 && len > self.config.max_size {
net_trace!("tx: dropping a packet that is too large");
true
} else if !self.state.maybe_transmit(&self.config, self.timestamp) {
net_trace!("tx: dropping a packet because of rate limiting");
true
} else {
false
};
if drop {
return f(&mut self.junk[..len]);
}
self.token.consume(len, |mut buf| {
if self.state.maybe(self.config.corrupt_pct) {
net_trace!("tx: corrupting a packet");
self.state.corrupt(&mut buf)
}
f(buf)
})
}
fn set_meta(&mut self, meta: PacketMeta) {
self.token.set_meta(meta);
}
}