use std::io::Write;
use std::time::Duration;
use pcap_file::pcap::{PcapHeader, PcapPacket, PcapWriter};
use crate::packet::{OwnedPacket, Packet};
const LINKTYPE_ETHERNET: u32 = 1;
pub struct CaptureWriter<W: Write> {
inner: PcapWriter<W>,
}
impl<W: Write> CaptureWriter<W> {
pub fn create(out: W) -> Result<Self, pcap_file::PcapError> {
Self::new_with_linktype(out, LINKTYPE_ETHERNET)
}
pub fn new_with_linktype(out: W, linktype: u32) -> Result<Self, pcap_file::PcapError> {
let header = PcapHeader {
version_major: 2,
version_minor: 4,
ts_correction: 0,
ts_accuracy: 0,
snaplen: u32::MAX,
datalink: pcap_file::DataLink::from(linktype),
ts_resolution: pcap_file::TsResolution::NanoSecond,
endianness: pcap_file::Endianness::native(),
};
Ok(Self {
inner: PcapWriter::with_header(out, header)?,
})
}
pub fn write_packet(&mut self, pkt: &Packet<'_>) -> Result<(), pcap_file::PcapError> {
let ts = pkt.timestamp();
let record = PcapPacket::new_owned(
Duration::new(ts.sec as u64, ts.nsec),
pkt.original_len() as u32,
pkt.data().to_vec(),
);
self.inner.write_packet(&record).map(|_| ())
}
pub fn write_owned(&mut self, pkt: &OwnedPacket) -> Result<(), pcap_file::PcapError> {
let record = PcapPacket::new(
Duration::new(pkt.timestamp.sec as u64, pkt.timestamp.nsec),
pkt.original_len as u32,
&pkt.data,
);
self.inner.write_packet(&record).map(|_| ())
}
pub fn into_inner(self) -> W {
self.inner.into_writer()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::{PacketDirection, PacketStatus, Timestamp};
use std::io::Cursor;
fn make_owned(data: Vec<u8>) -> OwnedPacket {
OwnedPacket {
data,
timestamp: Timestamp::new(1234, 567_890_000),
original_len: 100,
status: PacketStatus::default(),
direction: PacketDirection::Host,
rxhash: 0,
vlan_tci: 0,
vlan_tpid: 0,
ll_protocol: 0x0800,
source_ll_addr: [0; 8],
source_ll_addr_len: 0,
}
}
#[test]
fn writes_pcap_header() {
let mut buf = Vec::new();
{
let cursor = Cursor::new(&mut buf);
let _w = CaptureWriter::create(cursor).expect("create");
}
let magic = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
let valid = matches!(magic, 0xa1b2_c3d4 | 0xd4c3_b2a1 | 0xa1b2_3c4d | 0x4d3c_b2a1);
assert!(valid, "PCAP magic missing: 0x{magic:08x}");
}
#[test]
fn round_trip_owned_packet() {
let mut buf = Vec::new();
{
let cursor = Cursor::new(&mut buf);
let mut w = CaptureWriter::create(cursor).expect("create");
let pkt = make_owned(vec![1, 2, 3, 4, 5]);
w.write_owned(&pkt).expect("write");
}
let cursor = Cursor::new(&buf);
let mut reader = pcap_file::pcap::PcapReader::new(cursor).expect("reader");
let record = reader.next_packet().expect("first").expect("record");
assert_eq!(record.data.as_ref(), &[1, 2, 3, 4, 5]);
assert_eq!(record.orig_len, 100);
}
#[test]
fn round_trip_preserves_nanosecond_timestamp() {
let ts_in = Timestamp::new(1_700_000_000, 123_456_789);
let mut buf = Vec::new();
{
let cursor = Cursor::new(&mut buf);
let mut w = CaptureWriter::create(cursor).expect("create");
let mut pkt = make_owned(vec![1, 2, 3, 4, 5]);
pkt.timestamp = ts_in;
w.write_owned(&pkt).expect("write");
}
let cursor = Cursor::new(&buf);
let mut reader = pcap_file::pcap::PcapReader::new(cursor).expect("reader");
let record = reader.next_packet().expect("first").expect("record");
let expected = Duration::new(ts_in.sec as u64, ts_in.nsec);
assert_eq!(
record.timestamp, expected,
"nanosecond precision lost across pcap round-trip"
);
}
}