use std::fs::File;
use std::io::{self, Read};
use std::ops::ControlFlow;
use std::path::Path;
use crate::error::{DsctError, Result, ResultExt};
use crate::serialize::PacketMeta;
pub struct CaptureReader {
reader: Box<dyn Read>,
}
impl CaptureReader {
pub fn open(path: &Path) -> Result<Self> {
let reader: Box<dyn Read> = if path.as_os_str() == "-" {
Box::new(io::stdin().lock())
} else {
let file = File::open(path)
.context(format!("failed to open capture file: {}", path.display()))?;
Box::new(file)
};
Ok(Self { reader })
}
#[cfg(test)]
fn from_bytes(data: Vec<u8>) -> Self {
Self {
reader: Box::new(std::io::Cursor::new(data)),
}
}
pub fn for_each_packet<F>(self, mut f: F) -> Result<()>
where
F: FnMut(PacketMeta, &[u8]) -> Result<ControlFlow<()>>,
{
let mut counter = 0u64;
let mut error: Option<DsctError> = None;
let stream_result =
packet_dissector_pcap::stream_packets(self.reader, |record, pkt_data| {
counter += 1;
let meta = PacketMeta {
number: counter,
timestamp_secs: record.timestamp_secs,
timestamp_usecs: record.timestamp_usecs,
captured_length: record.captured_len,
original_length: record.original_len,
link_type: record.link_type as u32,
};
match f(meta, pkt_data) {
Ok(flow) => flow,
Err(e) => {
error = Some(e);
ControlFlow::Break(())
}
}
});
if let Some(e) = error {
return Err(e);
}
stream_result.map_err(|e| DsctError::from(e).context("invalid capture file"))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_pcap(n: usize) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&0xA1B2C3D4u32.to_le_bytes()); buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&4u16.to_le_bytes()); buf.extend_from_slice(&0i32.to_le_bytes()); buf.extend_from_slice(&0u32.to_le_bytes()); buf.extend_from_slice(&65535u32.to_le_bytes()); buf.extend_from_slice(&1u32.to_le_bytes());
let pkt: &[u8] = &[0xff; 42];
for i in 0..n {
buf.extend_from_slice(&(i as u32).to_le_bytes()); buf.extend_from_slice(&((i * 1000) as u32).to_le_bytes()); buf.extend_from_slice(&(pkt.len() as u32).to_le_bytes()); buf.extend_from_slice(&(pkt.len() as u32).to_le_bytes()); buf.extend_from_slice(pkt);
}
buf
}
#[test]
fn open_nonexistent_file_errors() {
let result = CaptureReader::open(Path::new("/tmp/nonexistent_dsct_test_file.pcap"));
assert!(result.is_err());
}
#[test]
fn from_bytes_empty_errors() {
let reader = CaptureReader::from_bytes(vec![]);
let result = reader.for_each_packet(|_, _| Ok(ControlFlow::Continue(())));
assert!(result.is_err());
}
#[test]
fn from_bytes_invalid_magic_errors() {
let reader = CaptureReader::from_bytes(vec![0x00; 24]);
let result = reader.for_each_packet(|_, _| Ok(ControlFlow::Continue(())));
assert!(result.is_err());
}
#[test]
fn for_each_packet_counts_all() {
let pcap = build_pcap(5);
let reader = CaptureReader::from_bytes(pcap);
let mut count = 0u64;
reader
.for_each_packet(|meta, data| {
count += 1;
assert_eq!(meta.number, count);
assert_eq!(data.len(), 42);
assert_eq!(meta.captured_length, 42);
assert_eq!(meta.original_length, 42);
assert_eq!(meta.link_type, 1);
Ok(ControlFlow::Continue(()))
})
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn for_each_packet_timestamps() {
let pcap = build_pcap(3);
let reader = CaptureReader::from_bytes(pcap);
let mut timestamps = Vec::new();
reader
.for_each_packet(|meta, _| {
timestamps.push((meta.timestamp_secs, meta.timestamp_usecs));
Ok(ControlFlow::Continue(()))
})
.unwrap();
assert_eq!(timestamps[0], (0, 0));
assert_eq!(timestamps[1], (1, 1000));
assert_eq!(timestamps[2], (2, 2000));
}
#[test]
fn for_each_packet_early_stop() {
let pcap = build_pcap(10);
let reader = CaptureReader::from_bytes(pcap);
let mut count = 0u64;
reader
.for_each_packet(|_, _| {
count += 1;
if count >= 3 {
Ok(ControlFlow::Break(()))
} else {
Ok(ControlFlow::Continue(()))
}
})
.unwrap();
assert_eq!(count, 3);
}
#[test]
fn for_each_packet_callback_error_propagates() {
let pcap = build_pcap(5);
let reader = CaptureReader::from_bytes(pcap);
let result = reader.for_each_packet(|meta, _| {
if meta.number == 3 {
Err(DsctError::msg("test error"))
} else {
Ok(ControlFlow::Continue(()))
}
});
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("test error"));
}
#[test]
fn for_each_packet_empty_capture() {
let pcap = build_pcap(0);
let reader = CaptureReader::from_bytes(pcap);
let mut count = 0u64;
reader
.for_each_packet(|_, _| {
count += 1;
Ok(ControlFlow::Continue(()))
})
.unwrap();
assert_eq!(count, 0);
}
#[test]
fn for_each_packet_with_clear_into_reuse() {
use packet_dissector_core::packet::DissectBuffer;
let pcap = build_pcap(5);
let reader = CaptureReader::from_bytes(pcap);
let mut dissect_buf = DissectBuffer::new();
let mut count = 0u64;
reader
.for_each_packet(|meta, data| {
count += 1;
let buf = dissect_buf.clear_into();
assert_eq!(buf.field_count(), 0);
assert_eq!(buf.layers().len(), 0);
assert_eq!(meta.number, count);
assert_eq!(data.len(), 42);
Ok(ControlFlow::Continue(()))
})
.unwrap();
assert_eq!(count, 5);
}
#[test]
fn open_valid_file() {
let pcap = build_pcap(2);
let path =
std::env::temp_dir().join(format!("dsct_input_test_{}.pcap", std::process::id()));
std::fs::write(&path, &pcap).unwrap();
let reader = CaptureReader::open(&path).unwrap();
let mut count = 0u64;
reader
.for_each_packet(|_, _| {
count += 1;
Ok(ControlFlow::Continue(()))
})
.unwrap();
assert_eq!(count, 2);
let _ = std::fs::remove_file(&path);
}
}