use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use pcap_file::pcap::PcapReader;
use pcap_file::pcapng::Block;
use pcap_file::pcapng::PcapNgReader;
use crate::capture::{compile_filter, FilterSpec};
use crate::codec::datalink_to_link_type;
use crate::error::{Error, Result};
use crate::packet::{LinkType, Packet};
struct RawPkt {
data: Vec<u8>,
ts_sec: u64,
ts_nsec: u32,
orig_len: u32,
link_type: LinkType,
}
enum Inner {
Pcap(PcapReader<BufReader<File>>),
PcapNg(PcapNgReader<BufReader<File>>),
}
pub struct FileCapture {
inner: Inner,
filter: Option<pktbaffle::bpf::Program>,
link_type: LinkType,
idb_link_types: Vec<LinkType>,
}
impl FileCapture {
pub fn open(path: &Path, filter: Option<FilterSpec>) -> Result<Self> {
let file = File::open(path)?;
let reader = BufReader::new(file);
use std::io::BufRead;
let mut reader = reader;
let magic = {
let buf = reader.fill_buf().map_err(Error::Io)?;
if buf.len() < 4 {
return Err(Error::Platform("file too short".into()));
}
[buf[0], buf[1], buf[2], buf[3]]
};
let is_pcapng = magic == [0x0A, 0x0D, 0x0D, 0x0A];
if is_pcapng {
let ng = PcapNgReader::new(reader).map_err(Error::Pcap)?;
let link_type = LinkType::Ethernet;
let compiled = compile_filter(filter, link_type)?;
Ok(Self {
inner: Inner::PcapNg(ng),
filter: compiled,
link_type,
idb_link_types: Vec::new(),
})
} else {
let pcap = PcapReader::new(reader).map_err(Error::Pcap)?;
let link_type = datalink_to_link_type(pcap.header().datalink);
let compiled = compile_filter(filter, link_type)?;
Ok(Self {
inner: Inner::Pcap(pcap),
filter: compiled,
link_type,
idb_link_types: Vec::new(),
})
}
}
pub fn link_type(&self) -> LinkType {
self.link_type
}
pub fn next_packet(&mut self) -> Result<Option<Packet>> {
loop {
let raw = match &mut self.inner {
Inner::Pcap(r) => fetch_pcap(r, self.link_type)?,
Inner::PcapNg(r) => fetch_pcapng(r, &mut self.idb_link_types)?,
};
let raw = match raw {
None => return Ok(None),
Some(r) => r,
};
if let Some(prog) = &self.filter {
if !prog.matches(&raw.data) {
continue;
}
}
return Ok(Some(Packet::new(
raw.data,
raw.ts_sec,
raw.ts_nsec,
raw.orig_len,
raw.link_type,
)));
}
}
}
fn fetch_pcap(r: &mut PcapReader<BufReader<File>>, link_type: LinkType) -> Result<Option<RawPkt>> {
match r.next_packet() {
None => Ok(None),
Some(Err(e)) => Err(Error::Pcap(e)),
Some(Ok(pkt)) => {
let ts = pkt.timestamp;
Ok(Some(RawPkt {
data: pkt.data.into_owned(),
ts_sec: ts.as_secs(),
ts_nsec: ts.subsec_nanos(),
orig_len: pkt.orig_len,
link_type,
}))
}
}
}
fn fetch_pcapng(
r: &mut PcapNgReader<BufReader<File>>,
idb_types: &mut Vec<LinkType>,
) -> Result<Option<RawPkt>> {
loop {
match r.next_block() {
None => return Ok(None),
Some(Err(e)) => return Err(Error::Pcap(e)),
Some(Ok(block)) => match block {
Block::InterfaceDescription(idb) => {
idb_types.push(datalink_to_link_type(idb.linktype));
continue;
}
Block::EnhancedPacket(epb) => {
let link_type = idb_types
.get(epb.interface_id as usize)
.copied()
.unwrap_or(LinkType::Ethernet);
let ts = epb.timestamp;
return Ok(Some(RawPkt {
data: epb.data.into_owned(),
ts_sec: ts.as_secs(),
ts_nsec: ts.subsec_nanos(),
orig_len: epb.original_len,
link_type,
}));
}
Block::SimplePacket(spb) => {
let link_type = idb_types.first().copied().unwrap_or(LinkType::Ethernet);
return Ok(Some(RawPkt {
data: spb.data.into_owned(),
ts_sec: 0,
ts_nsec: 0,
orig_len: spb.original_len,
link_type,
}));
}
Block::Packet(pb) => {
let link_type = idb_types
.get(pb.interface_id as usize)
.copied()
.unwrap_or(LinkType::Ethernet);
let ts_usec = pb.timestamp;
return Ok(Some(RawPkt {
data: pb.data.into_owned(),
ts_sec: ts_usec / 1_000_000,
ts_nsec: ((ts_usec % 1_000_000) * 1000) as u32,
orig_len: pb.original_len,
link_type,
}));
}
_ => continue,
},
}
}
}