use byteorder::{ByteOrder, NativeEndian};
use core::cell::RefCell;
use phy::Medium;
#[cfg(feature = "std")]
use std::io::Write;
use crate::phy::{self, Device, DeviceCapabilities};
use crate::time::Instant;
use crate::Result;
enum_with_unknown! {
pub enum PcapLinkType(u32) {
Ethernet = 1,
Ip = 101,
Ieee802154WithFcs = 195,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PcapMode {
Both,
RxOnly,
TxOnly,
}
pub trait PcapSink {
fn write(&mut self, data: &[u8]);
fn flush(&mut self) {}
fn write_u16(&mut self, value: u16) {
let mut bytes = [0u8; 2];
NativeEndian::write_u16(&mut bytes, value);
self.write(&bytes[..])
}
fn write_u32(&mut self, value: u32) {
let mut bytes = [0u8; 4];
NativeEndian::write_u32(&mut bytes, value);
self.write(&bytes[..])
}
fn global_header(&mut self, link_type: PcapLinkType) {
self.write_u32(0xa1b2c3d4); self.write_u16(2); self.write_u16(4); self.write_u32(0); self.write_u32(0); self.write_u32(65535); self.write_u32(link_type.into()); }
fn packet_header(&mut self, timestamp: Instant, length: usize) {
assert!(length <= 65535);
self.write_u32(timestamp.secs() as u32); self.write_u32(timestamp.micros() as u32); self.write_u32(length as u32); self.write_u32(length as u32); }
fn packet(&mut self, timestamp: Instant, packet: &[u8]) {
self.packet_header(timestamp, packet.len());
self.write(packet);
self.flush();
}
}
#[cfg(feature = "std")]
impl<T: Write> PcapSink for T {
fn write(&mut self, data: &[u8]) {
T::write_all(self, data).expect("cannot write")
}
fn flush(&mut self) {
T::flush(self).expect("cannot flush")
}
}
#[derive(Debug)]
pub struct PcapWriter<D, S>
where
D: for<'a> Device<'a>,
S: PcapSink,
{
lower: D,
sink: RefCell<S>,
mode: PcapMode,
}
impl<D: for<'a> Device<'a>, S: PcapSink> PcapWriter<D, S> {
pub fn new(lower: D, mut sink: S, mode: PcapMode) -> PcapWriter<D, S> {
let medium = lower.capabilities().medium;
let link_type = match medium {
#[cfg(feature = "medium-ip")]
Medium::Ip => PcapLinkType::Ip,
#[cfg(feature = "medium-ethernet")]
Medium::Ethernet => PcapLinkType::Ethernet,
#[cfg(feature = "medium-ieee802154")]
Medium::Ieee802154 => PcapLinkType::Ieee802154WithFcs,
};
sink.global_header(link_type);
PcapWriter {
lower,
sink: RefCell::new(sink),
mode,
}
}
pub fn get_ref(&self) -> &D {
&self.lower
}
pub fn get_mut(&mut self) -> &mut D {
&mut self.lower
}
}
impl<'a, D, S> Device<'a> for PcapWriter<D, S>
where
D: for<'b> Device<'b>,
S: PcapSink + 'a,
{
type RxToken = RxToken<'a, <D as Device<'a>>::RxToken, S>;
type TxToken = TxToken<'a, <D as Device<'a>>::TxToken, S>;
fn capabilities(&self) -> DeviceCapabilities {
self.lower.capabilities()
}
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
let sink = &self.sink;
let mode = self.mode;
self.lower.receive().map(move |(rx_token, tx_token)| {
let rx = RxToken {
token: rx_token,
sink,
mode,
};
let tx = TxToken {
token: tx_token,
sink,
mode,
};
(rx, tx)
})
}
fn transmit(&'a mut self) -> Option<Self::TxToken> {
let sink = &self.sink;
let mode = self.mode;
self.lower
.transmit()
.map(move |token| TxToken { token, sink, mode })
}
}
#[doc(hidden)]
pub struct RxToken<'a, Rx: phy::RxToken, S: PcapSink> {
token: Rx,
sink: &'a RefCell<S>,
mode: PcapMode,
}
impl<'a, Rx: phy::RxToken, S: PcapSink> phy::RxToken for RxToken<'a, Rx, S> {
fn consume<R, F: FnOnce(&mut [u8]) -> Result<R>>(self, timestamp: Instant, f: F) -> Result<R> {
let Self { token, sink, mode } = self;
token.consume(timestamp, |buffer| {
match mode {
PcapMode::Both | PcapMode::RxOnly => {
sink.borrow_mut().packet(timestamp, buffer.as_ref())
}
PcapMode::TxOnly => (),
}
f(buffer)
})
}
}
#[doc(hidden)]
pub struct TxToken<'a, Tx: phy::TxToken, S: PcapSink> {
token: Tx,
sink: &'a RefCell<S>,
mode: PcapMode,
}
impl<'a, Tx: phy::TxToken, S: PcapSink> phy::TxToken for TxToken<'a, Tx, S> {
fn consume<R, F>(self, timestamp: Instant, len: usize, f: F) -> Result<R>
where
F: FnOnce(&mut [u8]) -> Result<R>,
{
let Self { token, sink, mode } = self;
token.consume(timestamp, len, |buffer| {
let result = f(buffer);
match mode {
PcapMode::Both | PcapMode::TxOnly => sink.borrow_mut().packet(timestamp, buffer),
PcapMode::RxOnly => (),
};
result
})
}
}