Skip to content

tracer: update public API for smoltcp::phy::Tracer to allow custom inspection and printing of packet #1076

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/phy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pub use self::loopback::Loopback;
pub use self::pcap_writer::{PcapLinkType, PcapMode, PcapSink, PcapWriter};
#[cfg(all(feature = "phy-raw_socket", unix))]
pub use self::raw_socket::RawSocket;
pub use self::tracer::Tracer;
pub use self::tracer::{Tracer, TracerDirection, TracerPacket};
#[cfg(all(
feature = "phy-tuntap_interface",
any(target_os = "linux", target_os = "android")
Expand Down
192 changes: 178 additions & 14 deletions src/phy/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
/// device.
pub struct Tracer<D: Device> {
inner: D,
writer: fn(Instant, Packet),
writer: fn(Instant, TracerPacket),
}

impl<D: Device> Tracer<D> {
/// Create a tracer device.
pub fn new(inner: D, writer: fn(timestamp: Instant, packet: Packet)) -> Tracer<D> {
pub fn new(inner: D, writer: fn(timestamp: Instant, packet: TracerPacket)) -> Tracer<D> {
Tracer { inner, writer }
}

Expand Down Expand Up @@ -88,7 +88,7 @@ impl<D: Device> Device for Tracer<D> {
#[doc(hidden)]
pub struct RxToken<Rx: phy::RxToken> {
token: Rx,
writer: fn(Instant, Packet),
writer: fn(Instant, TracerPacket),
medium: Medium,
timestamp: Instant,
}
Expand All @@ -101,10 +101,10 @@ impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
self.token.consume(|buffer| {
(self.writer)(
self.timestamp,
Packet {
TracerPacket {
buffer,
medium: self.medium,
prefix: "<- ",
direction: TracerDirection::RX,
},
);
f(buffer)
Expand All @@ -119,7 +119,7 @@ impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
#[doc(hidden)]
pub struct TxToken<Tx: phy::TxToken> {
token: Tx,
writer: fn(Instant, Packet),
writer: fn(Instant, TracerPacket),
medium: Medium,
timestamp: Instant,
}
Expand All @@ -133,10 +133,10 @@ impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
let result = f(buffer);
(self.writer)(
self.timestamp,
Packet {
TracerPacket {
buffer,
medium: self.medium,
prefix: "-> ",
direction: TracerDirection::TX,
},
);
result
Expand All @@ -148,15 +148,34 @@ impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
}
}

pub struct Packet<'a> {
buffer: &'a [u8],
medium: Medium,
prefix: &'static str,
/// Packet which is being traced by [Tracer](struct.Tracer.html) device.
#[derive(Debug, Clone, Copy)]
pub struct TracerPacket<'a> {
/// Packet buffer
pub buffer: &'a [u8],
/// Packet medium
pub medium: Medium,
/// Direction in which packet is being traced
pub direction: TracerDirection,
}

/// Direction on which packet is being traced
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TracerDirection {
/// Packet is received by Smoltcp interface
RX,
/// Packet is transmitted by Smoltcp interface
TX,
}

impl<'a> fmt::Display for Packet<'a> {
impl<'a> fmt::Display for TracerPacket<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut indent = PrettyIndent::new(self.prefix);
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(
Expand Down Expand Up @@ -189,3 +208,148 @@ impl<'a> fmt::Display for Packet<'a> {
}
}
}

#[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");
}
}