use core::fmt;
use crate::phy::{self, Device, DeviceCapabilities, Medium};
use crate::time::Instant;
use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
pub struct Tracer<D: Device> {
inner: D,
writer: fn(Instant, TracerPacket),
}
impl<D: Device> Tracer<D> {
pub fn new(inner: D, writer: fn(timestamp: Instant, packet: TracerPacket)) -> Tracer<D> {
Tracer { inner, writer }
}
pub fn get_ref(&self) -> &D {
&self.inner
}
pub fn get_mut(&mut self) -> &mut D {
&mut self.inner
}
pub fn into_inner(self) -> D {
self.inner
}
}
impl<D: Device> Device for Tracer<D> {
type RxToken<'a>
= RxToken<D::RxToken<'a>>
where
Self: 'a;
type TxToken<'a>
= TxToken<D::TxToken<'a>>
where
Self: 'a;
fn capabilities(&self) -> DeviceCapabilities {
self.inner.capabilities()
}
fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
let medium = self.inner.capabilities().medium;
self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
let rx = RxToken {
token: rx_token,
writer: self.writer,
medium,
timestamp,
};
let tx = TxToken {
token: tx_token,
writer: self.writer,
medium,
timestamp,
};
(rx, tx)
})
}
fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
let medium = self.inner.capabilities().medium;
self.inner.transmit(timestamp).map(|tx_token| TxToken {
token: tx_token,
medium,
writer: self.writer,
timestamp,
})
}
}
#[doc(hidden)]
pub struct RxToken<Rx: phy::RxToken> {
token: Rx,
writer: fn(Instant, TracerPacket),
medium: Medium,
timestamp: Instant,
}
impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&[u8]) -> R,
{
self.token.consume(|buffer| {
(self.writer)(
self.timestamp,
TracerPacket {
buffer,
medium: self.medium,
direction: TracerDirection::RX,
},
);
f(buffer)
})
}
fn meta(&self) -> phy::PacketMeta {
self.token.meta()
}
}
#[doc(hidden)]
pub struct TxToken<Tx: phy::TxToken> {
token: Tx,
writer: fn(Instant, TracerPacket),
medium: Medium,
timestamp: Instant,
}
impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
self.token.consume(len, |buffer| {
let result = f(buffer);
(self.writer)(
self.timestamp,
TracerPacket {
buffer,
medium: self.medium,
direction: TracerDirection::TX,
},
);
result
})
}
fn set_meta(&mut self, meta: phy::PacketMeta) {
self.token.set_meta(meta)
}
}
#[derive(Debug, Clone, Copy)]
pub struct TracerPacket<'a> {
pub buffer: &'a [u8],
pub medium: Medium,
pub direction: TracerDirection,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TracerDirection {
RX,
TX,
}
impl<'a> fmt::Display for TracerPacket<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prefix = match self.direction {
TracerDirection::RX => "<- ",
TracerDirection::TX => "-> ",
};
let mut indent = PrettyIndent::new(prefix);
match self.medium {
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(
&self.buffer,
f,
&mut indent,
),
#[cfg(feature = "medium-ip")]
Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) {
#[cfg(feature = "proto-ipv4")]
Ok(crate::wire::IpVersion::Ipv4) => {
crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(
&self.buffer,
f,
&mut indent,
)
}
#[cfg(feature = "proto-ipv6")]
Ok(crate::wire::IpVersion::Ipv6) => {
crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(
&self.buffer,
f,
&mut indent,
)
}
_ => f.write_str("unrecognized IP version"),
},
#[cfg(feature = "medium-ieee802154")]
Medium::Ieee802154 => Ok(()), }
}
}
#[cfg(test)]
mod tests {
use core::cell::RefCell;
use std::collections::VecDeque;
use super::*;
use crate::phy::ChecksumCapabilities;
use crate::{
phy::{Device, Loopback, RxToken, TxToken},
time::Instant,
};
#[cfg(any(
feature = "medium-ethernet",
feature = "medium-ip",
feature = "medium-ieee802154"
))]
#[test]
fn test_tracer() {
type TracerEvent = (Instant, Vec<u8>, Medium, TracerDirection);
thread_local! {
static TRACE_EVENTS: RefCell<VecDeque<TracerEvent>> = const { RefCell::new(VecDeque::new()) };
}
TRACE_EVENTS.replace(VecDeque::new());
let medium = Medium::default();
let loopback_device = Loopback::new(medium);
let mut tracer_device = Tracer::new(loopback_device, |instant, packet| {
TRACE_EVENTS.with_borrow_mut(|events| {
events.push_back((
instant,
packet.buffer.to_owned(),
packet.medium,
packet.direction,
))
});
});
let expected_payload = [1, 2, 3, 4, 5, 6, 7, 8];
let tx_instant = Instant::from_secs(1);
let tx_token = tracer_device.transmit(tx_instant).unwrap();
tx_token.consume(expected_payload.len(), |buf| {
buf.copy_from_slice(&expected_payload)
});
let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
assert_eq!(
last_event,
Some((
tx_instant,
expected_payload.into(),
medium,
TracerDirection::TX
))
);
let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
assert_eq!(last_event, None);
let rx_instant = Instant::from_secs(2);
let (rx_token, _) = tracer_device.receive(rx_instant).unwrap();
let mut rx_pkt = [0; 8];
rx_token.consume(|buf| rx_pkt.copy_from_slice(buf));
assert_eq!(rx_pkt, expected_payload);
let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
assert_eq!(
last_event,
Some((
rx_instant,
expected_payload.into(),
medium,
TracerDirection::RX
))
);
let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
assert_eq!(last_event, None);
}
#[cfg(feature = "medium-ethernet")]
#[test]
fn test_tracer_packet_display_ether() {
use crate::wire::{EthernetAddress, EthernetProtocol, EthernetRepr};
let repr = EthernetRepr {
src_addr: EthernetAddress([0, 1, 2, 3, 4, 5]),
dst_addr: EthernetAddress([5, 4, 3, 2, 1, 0]),
ethertype: EthernetProtocol::Unknown(0),
};
let mut buffer = vec![0_u8; repr.buffer_len()];
{
use crate::wire::EthernetFrame;
let mut frame = EthernetFrame::new_unchecked(&mut buffer);
repr.emit(&mut frame);
}
let pkt = TracerPacket {
buffer: &buffer,
medium: Medium::Ethernet,
direction: TracerDirection::RX,
};
let pkt_pretty = pkt.to_string();
assert_eq!(
pkt_pretty,
"<- EthernetII src=00-01-02-03-04-05 dst=05-04-03-02-01-00 type=0x0000"
);
}
#[cfg(all(feature = "medium-ip", feature = "proto-ipv4"))]
#[test]
fn test_tracer_packet_display_ip() {
use crate::wire::{IpProtocol, Ipv4Address, Ipv4Repr};
let repr = Ipv4Repr {
src_addr: Ipv4Address::new(10, 0, 0, 1),
dst_addr: Ipv4Address::new(10, 0, 0, 2),
next_header: IpProtocol::Unknown(255),
payload_len: 0,
hop_limit: 64,
};
let mut buffer = vec![0_u8; repr.buffer_len()];
{
use crate::wire::Ipv4Packet;
let mut packet = Ipv4Packet::new_unchecked(&mut buffer);
repr.emit(&mut packet, &ChecksumCapabilities::default());
}
let pkt = TracerPacket {
buffer: &buffer,
medium: Medium::Ip,
direction: TracerDirection::TX,
};
let pkt_pretty = pkt.to_string();
assert_eq!(pkt_pretty, "-> IPv4 src=10.0.0.1 dst=10.0.0.2 proto=0xff");
}
}