Skip to main content

smoltcp/phy/
tracer.rs

1use core::fmt;
2
3use crate::phy::{self, Device, DeviceCapabilities, Medium};
4use crate::time::Instant;
5use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
6
7/// A tracer device.
8///
9/// A tracer is a device that pretty prints all packets traversing it
10/// using the provided writer function, and then passes them to another
11/// device.
12pub struct Tracer<D: Device> {
13    inner: D,
14    writer: fn(Instant, TracerPacket),
15}
16
17impl<D: Device> Tracer<D> {
18    /// Create a tracer device.
19    pub fn new(inner: D, writer: fn(timestamp: Instant, packet: TracerPacket)) -> Tracer<D> {
20        Tracer { inner, writer }
21    }
22
23    /// Get a reference to the underlying device.
24    ///
25    /// Even if the device offers reading through a standard reference, it is inadvisable to
26    /// directly read from the device as doing so will circumvent the tracing.
27    pub fn get_ref(&self) -> &D {
28        &self.inner
29    }
30
31    /// Get a mutable reference to the underlying device.
32    ///
33    /// It is inadvisable to directly read from the device as doing so will circumvent the tracing.
34    pub fn get_mut(&mut self) -> &mut D {
35        &mut self.inner
36    }
37
38    /// Return the underlying device, consuming the tracer.
39    pub fn into_inner(self) -> D {
40        self.inner
41    }
42}
43
44impl<D: Device> Device for Tracer<D> {
45    type RxToken<'a>
46        = RxToken<D::RxToken<'a>>
47    where
48        Self: 'a;
49    type TxToken<'a>
50        = TxToken<D::TxToken<'a>>
51    where
52        Self: 'a;
53
54    fn capabilities(&self) -> DeviceCapabilities {
55        self.inner.capabilities()
56    }
57
58    fn receive(&mut self, timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
59        let medium = self.inner.capabilities().medium;
60        self.inner.receive(timestamp).map(|(rx_token, tx_token)| {
61            let rx = RxToken {
62                token: rx_token,
63                writer: self.writer,
64                medium,
65                timestamp,
66            };
67            let tx = TxToken {
68                token: tx_token,
69                writer: self.writer,
70                medium,
71                timestamp,
72            };
73            (rx, tx)
74        })
75    }
76
77    fn transmit(&mut self, timestamp: Instant) -> Option<Self::TxToken<'_>> {
78        let medium = self.inner.capabilities().medium;
79        self.inner.transmit(timestamp).map(|tx_token| TxToken {
80            token: tx_token,
81            medium,
82            writer: self.writer,
83            timestamp,
84        })
85    }
86}
87
88#[doc(hidden)]
89pub struct RxToken<Rx: phy::RxToken> {
90    token: Rx,
91    writer: fn(Instant, TracerPacket),
92    medium: Medium,
93    timestamp: Instant,
94}
95
96impl<Rx: phy::RxToken> phy::RxToken for RxToken<Rx> {
97    fn consume<R, F>(self, f: F) -> R
98    where
99        F: FnOnce(&[u8]) -> R,
100    {
101        self.token.consume(|buffer| {
102            (self.writer)(
103                self.timestamp,
104                TracerPacket {
105                    buffer,
106                    medium: self.medium,
107                    direction: TracerDirection::RX,
108                },
109            );
110            f(buffer)
111        })
112    }
113
114    fn meta(&self) -> phy::PacketMeta {
115        self.token.meta()
116    }
117}
118
119#[doc(hidden)]
120pub struct TxToken<Tx: phy::TxToken> {
121    token: Tx,
122    writer: fn(Instant, TracerPacket),
123    medium: Medium,
124    timestamp: Instant,
125}
126
127impl<Tx: phy::TxToken> phy::TxToken for TxToken<Tx> {
128    fn consume<R, F>(self, len: usize, f: F) -> R
129    where
130        F: FnOnce(&mut [u8]) -> R,
131    {
132        self.token.consume(len, |buffer| {
133            let result = f(buffer);
134            (self.writer)(
135                self.timestamp,
136                TracerPacket {
137                    buffer,
138                    medium: self.medium,
139                    direction: TracerDirection::TX,
140                },
141            );
142            result
143        })
144    }
145
146    fn set_meta(&mut self, meta: phy::PacketMeta) {
147        self.token.set_meta(meta)
148    }
149}
150
151/// Packet which is being traced by [Tracer](struct.Tracer.html) device.
152#[derive(Debug, Clone, Copy)]
153pub struct TracerPacket<'a> {
154    /// Packet buffer
155    pub buffer: &'a [u8],
156    /// Packet medium
157    pub medium: Medium,
158    /// Direction in which packet is being traced
159    pub direction: TracerDirection,
160}
161
162/// Direction on which packet is being traced
163#[derive(Debug, Clone, Copy, PartialEq)]
164pub enum TracerDirection {
165    /// Packet is received by Smoltcp interface
166    RX,
167    /// Packet is transmitted by Smoltcp interface
168    TX,
169}
170
171impl<'a> fmt::Display for TracerPacket<'a> {
172    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
173        let prefix = match self.direction {
174            TracerDirection::RX => "<- ",
175            TracerDirection::TX => "-> ",
176        };
177
178        let mut indent = PrettyIndent::new(prefix);
179        match self.medium {
180            #[cfg(feature = "medium-ethernet")]
181            Medium::Ethernet => crate::wire::EthernetFrame::<&'static [u8]>::pretty_print(
182                &self.buffer,
183                f,
184                &mut indent,
185            ),
186            #[cfg(feature = "medium-ip")]
187            Medium::Ip => match crate::wire::IpVersion::of_packet(self.buffer) {
188                #[cfg(feature = "proto-ipv4")]
189                Ok(crate::wire::IpVersion::Ipv4) => {
190                    crate::wire::Ipv4Packet::<&'static [u8]>::pretty_print(
191                        &self.buffer,
192                        f,
193                        &mut indent,
194                    )
195                }
196                #[cfg(feature = "proto-ipv6")]
197                Ok(crate::wire::IpVersion::Ipv6) => {
198                    crate::wire::Ipv6Packet::<&'static [u8]>::pretty_print(
199                        &self.buffer,
200                        f,
201                        &mut indent,
202                    )
203                }
204                _ => f.write_str("unrecognized IP version"),
205            },
206            #[cfg(feature = "medium-ieee802154")]
207            Medium::Ieee802154 => Ok(()), // XXX
208        }
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use core::cell::RefCell;
215    use std::collections::VecDeque;
216
217    use super::*;
218
219    use crate::phy::ChecksumCapabilities;
220    use crate::{
221        phy::{Device, Loopback, RxToken, TxToken},
222        time::Instant,
223    };
224
225    #[cfg(any(
226        feature = "medium-ethernet",
227        feature = "medium-ip",
228        feature = "medium-ieee802154"
229    ))]
230    #[test]
231    fn test_tracer() {
232        type TracerEvent = (Instant, Vec<u8>, Medium, TracerDirection);
233        thread_local! {
234            static TRACE_EVENTS: RefCell<VecDeque<TracerEvent>> = const { RefCell::new(VecDeque::new()) };
235        }
236        TRACE_EVENTS.replace(VecDeque::new());
237
238        let medium = Medium::default();
239
240        let loopback_device = Loopback::new(medium);
241        let mut tracer_device = Tracer::new(loopback_device, |instant, packet| {
242            TRACE_EVENTS.with_borrow_mut(|events| {
243                events.push_back((
244                    instant,
245                    packet.buffer.to_owned(),
246                    packet.medium,
247                    packet.direction,
248                ))
249            });
250        });
251
252        let expected_payload = [1, 2, 3, 4, 5, 6, 7, 8];
253
254        let tx_instant = Instant::from_secs(1);
255        let tx_token = tracer_device.transmit(tx_instant).unwrap();
256
257        tx_token.consume(expected_payload.len(), |buf| {
258            buf.copy_from_slice(&expected_payload)
259        });
260        let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
261        assert_eq!(
262            last_event,
263            Some((
264                tx_instant,
265                expected_payload.into(),
266                medium,
267                TracerDirection::TX
268            ))
269        );
270        let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
271        assert_eq!(last_event, None);
272
273        let rx_instant = Instant::from_secs(2);
274        let (rx_token, _) = tracer_device.receive(rx_instant).unwrap();
275        let mut rx_pkt = [0; 8];
276        rx_token.consume(|buf| rx_pkt.copy_from_slice(buf));
277
278        assert_eq!(rx_pkt, expected_payload);
279
280        let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
281        assert_eq!(
282            last_event,
283            Some((
284                rx_instant,
285                expected_payload.into(),
286                medium,
287                TracerDirection::RX
288            ))
289        );
290        let last_event = TRACE_EVENTS.with_borrow_mut(|events| events.pop_front());
291        assert_eq!(last_event, None);
292    }
293
294    #[cfg(feature = "medium-ethernet")]
295    #[test]
296    fn test_tracer_packet_display_ether() {
297        use crate::wire::{EthernetAddress, EthernetProtocol, EthernetRepr};
298
299        let repr = EthernetRepr {
300            src_addr: EthernetAddress([0, 1, 2, 3, 4, 5]),
301            dst_addr: EthernetAddress([5, 4, 3, 2, 1, 0]),
302            ethertype: EthernetProtocol::Unknown(0),
303        };
304        let mut buffer = vec![0_u8; repr.buffer_len()];
305        {
306            use crate::wire::EthernetFrame;
307
308            let mut frame = EthernetFrame::new_unchecked(&mut buffer);
309            repr.emit(&mut frame);
310        }
311
312        let pkt = TracerPacket {
313            buffer: &buffer,
314            medium: Medium::Ethernet,
315            direction: TracerDirection::RX,
316        };
317
318        let pkt_pretty = pkt.to_string();
319        assert_eq!(
320            pkt_pretty,
321            "<- EthernetII src=00-01-02-03-04-05 dst=05-04-03-02-01-00 type=0x0000"
322        );
323    }
324
325    #[cfg(all(feature = "medium-ip", feature = "proto-ipv4"))]
326    #[test]
327    fn test_tracer_packet_display_ip() {
328        use crate::wire::{IpProtocol, Ipv4Address, Ipv4Repr};
329
330        let repr = Ipv4Repr {
331            src_addr: Ipv4Address::new(10, 0, 0, 1),
332            dst_addr: Ipv4Address::new(10, 0, 0, 2),
333            next_header: IpProtocol::Unknown(255),
334            payload_len: 0,
335            hop_limit: 64,
336        };
337
338        let mut buffer = vec![0_u8; repr.buffer_len()];
339        {
340            use crate::wire::Ipv4Packet;
341
342            let mut packet = Ipv4Packet::new_unchecked(&mut buffer);
343            repr.emit(&mut packet, &ChecksumCapabilities::default());
344        }
345
346        let pkt = TracerPacket {
347            buffer: &buffer,
348            medium: Medium::Ip,
349            direction: TracerDirection::TX,
350        };
351
352        let pkt_pretty = pkt.to_string();
353        assert_eq!(pkt_pretty, "-> IPv4 src=10.0.0.1 dst=10.0.0.2 proto=0xff");
354    }
355}