use std::borrow::Cow;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use pcap_file::pcap::{PcapHeader, PcapPacket, PcapWriter};
use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
use pcap_file::pcapng::{Block, PcapNgWriter};
use crate::codec::link_type_to_datalink;
use crate::error::{Error, Result};
use crate::packet::{LinkType, Packet};
enum Inner {
Pcap(PcapWriter<File>),
PcapNg(PcapNgWriter<File>),
}
pub struct Dump {
inner: Inner,
}
pub struct DumpBuilder {
path: PathBuf,
link_type: Option<LinkType>,
}
impl Dump {
pub fn to_file(path: impl AsRef<Path>) -> DumpBuilder {
DumpBuilder {
path: path.as_ref().to_owned(),
link_type: None,
}
}
pub fn write_packet(&mut self, pkt: &Packet) -> Result<()> {
let ts = pkt.timestamp.duration_since(UNIX_EPOCH).unwrap_or_default();
match &mut self.inner {
Inner::Pcap(w) => w
.write_packet(&PcapPacket {
timestamp: ts,
orig_len: pkt.orig_len,
data: Cow::Borrowed(&pkt.data),
})
.map(|_| ())
.map_err(Error::Pcap),
Inner::PcapNg(w) => w
.write_block(&Block::EnhancedPacket(EnhancedPacketBlock {
interface_id: 0,
timestamp: ts,
original_len: pkt.orig_len,
data: Cow::Borrowed(&pkt.data),
options: vec![],
}))
.map(|_| ())
.map_err(Error::Pcap),
}
}
pub fn flush(&mut self) -> Result<()> {
Ok(())
}
}
impl Drop for Dump {
fn drop(&mut self) {
let _ = self.flush();
}
}
impl DumpBuilder {
pub fn link_type(mut self, lt: LinkType) -> Self {
self.link_type = Some(lt);
self
}
pub fn open(self) -> Result<Dump> {
let lt = self.link_type.ok_or_else(|| {
Error::Platform(
"Dump::open requires a link type — call .link_type() on the builder".into(),
)
})?;
let ext = self
.path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_ascii_lowercase();
let file = File::create(&self.path)?;
let inner = match ext.as_str() {
"pcap" => {
let header = PcapHeader {
datalink: link_type_to_datalink(lt),
..Default::default()
};
let w = PcapWriter::with_header(file, header).map_err(Error::Pcap)?;
Inner::Pcap(w)
}
"pcapng" => {
let mut w = PcapNgWriter::new(file).map_err(Error::Pcap)?;
w.write_block(&Block::InterfaceDescription(InterfaceDescriptionBlock {
linktype: link_type_to_datalink(lt),
snaplen: 65535,
options: vec![],
}))
.map_err(Error::Pcap)?;
Inner::PcapNg(w)
}
other => {
return Err(Error::Platform(format!(
"unknown extension '.{other}': Dump requires .pcap or .pcapng"
)))
}
};
Ok(Dump { inner })
}
}