use std::borrow::Borrow;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
use crate::Packet;
use super::codec::{write_header, write_u32, Endian};
use super::{
PcapError, PcapHeader, PcapLinkType, PcapRecord, PcapTimestamp, Result, TimestampPrecision,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PcapWriterOptions {
header: PcapHeader,
}
impl PcapWriterOptions {
pub fn new(link_type: impl Into<PcapLinkType>) -> Self {
Self {
header: PcapHeader::new(link_type),
}
}
pub fn snaplen(mut self, snaplen: u32) -> Self {
self.header.snaplen = snaplen;
self
}
pub fn precision(mut self, precision: TimestampPrecision) -> Self {
self.header.precision = precision;
self
}
pub fn thiszone(mut self, thiszone: i32) -> Self {
self.header.thiszone = thiszone;
self
}
pub fn sigfigs(mut self, sigfigs: u32) -> Self {
self.header.sigfigs = sigfigs;
self
}
pub const fn header(self) -> PcapHeader {
self.header
}
}
#[derive(Debug)]
pub struct PcapWriter<W = BufWriter<File>> {
writer: W,
header: PcapHeader,
endian: Endian,
}
impl PcapWriter<BufWriter<File>> {
pub fn create(path: impl AsRef<Path>, link_type: impl Into<PcapLinkType>) -> Result<Self> {
Self::from_writer(BufWriter::new(File::create(path)?), link_type)
}
pub fn create_with_options(path: impl AsRef<Path>, options: PcapWriterOptions) -> Result<Self> {
Self::from_writer_with_options(BufWriter::new(File::create(path)?), options)
}
}
impl<W> PcapWriter<W>
where
W: Write,
{
pub fn from_writer(writer: W, link_type: impl Into<PcapLinkType>) -> Result<Self> {
Self::from_writer_with_options(writer, PcapWriterOptions::new(link_type))
}
pub fn from_writer_with_options(mut writer: W, options: PcapWriterOptions) -> Result<Self> {
let endian = Endian::Little;
let header = options.header();
write_header(&mut writer, header, endian)?;
Ok(Self {
writer,
header,
endian,
})
}
pub const fn header(&self) -> PcapHeader {
self.header
}
pub fn write_record(&mut self, record: &PcapRecord) -> Result<&mut Self> {
if record.pcap_link_type() != self.header.link_type {
return Err(PcapError::InvalidRecord(
"record link type must match writer link type",
));
}
self.write_raw(
record.timestamp(),
record.data(),
Some(record.original_len()),
)
}
pub fn write_packet(&mut self, packet: &Packet) -> Result<&mut Self> {
self.write_packet_with_timestamp(packet, PcapTimestamp::zero())
}
pub fn write_packet_with_timestamp(
&mut self,
packet: &Packet,
timestamp: PcapTimestamp,
) -> Result<&mut Self> {
let bytes = packet.compile()?;
self.write_raw(timestamp, bytes.as_bytes(), None)
}
pub fn write_packets<I, P>(&mut self, packets: I) -> Result<&mut Self>
where
I: IntoIterator<Item = P>,
P: Borrow<Packet>,
{
for packet in packets {
self.write_packet(packet.borrow())?;
}
Ok(self)
}
pub fn flush(&mut self) -> Result<()> {
self.writer.flush()?;
Ok(())
}
pub fn into_inner(mut self) -> Result<W> {
self.flush()?;
Ok(self.writer)
}
fn write_raw(
&mut self,
timestamp: PcapTimestamp,
data: &[u8],
original_len: Option<u32>,
) -> Result<&mut Self> {
if data.len() > self.header.snaplen as usize {
return Err(PcapError::RecordTooLarge {
field: "captured length",
max: self.header.snaplen as u64,
actual: data.len() as u64,
});
}
let captured_len = u32::try_from(data.len()).map_err(|_| PcapError::RecordTooLarge {
field: "captured length",
max: u32::MAX as u64,
actual: data.len() as u64,
})?;
let original_len = original_len.unwrap_or(captured_len);
if original_len < captured_len {
return Err(PcapError::InvalidRecord(
"original length must be at least captured length",
));
}
let (seconds, fractional) = timestamp.pcap_fields(self.header.precision)?;
write_u32(&mut self.writer, self.endian, seconds)?;
write_u32(&mut self.writer, self.endian, fractional)?;
write_u32(&mut self.writer, self.endian, captured_len)?;
write_u32(&mut self.writer, self.endian, original_len)?;
self.writer.write_all(data)?;
Ok(self)
}
}