1use core::fmt;
2
3use crate::phy::{self, Device, DeviceCapabilities, Medium};
4use crate::time::Instant;
5use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};
6
7pub struct Tracer<D: Device> {
13 inner: D,
14 writer: fn(Instant, TracerPacket),
15}
16
17impl<D: Device> Tracer<D> {
18 pub fn new(inner: D, writer: fn(timestamp: Instant, packet: TracerPacket)) -> Tracer<D> {
20 Tracer { inner, writer }
21 }
22
23 pub fn get_ref(&self) -> &D {
28 &self.inner
29 }
30
31 pub fn get_mut(&mut self) -> &mut D {
35 &mut self.inner
36 }
37
38 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#[derive(Debug, Clone, Copy)]
153pub struct TracerPacket<'a> {
154 pub buffer: &'a [u8],
156 pub medium: Medium,
158 pub direction: TracerDirection,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq)]
164pub enum TracerDirection {
165 RX,
167 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(()), }
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}