use nom::{
bytes::complete::take,
number::complete::{be_u32, le_u16, le_u32},
IResult,
};
const MAGIC_LE: u32 = 0xa1b2c3d4; const MAGIC_BE: u32 = 0xd4c3b2a1;
#[derive(Debug, Clone)]
pub struct PcapHeader {
pub magic: u32,
pub version_major: u16,
pub version_minor: u16,
pub snaplen: u32,
pub network: u32,
pub big_endian: bool,
}
#[derive(Debug, Clone)]
pub struct RawPacket<'a> {
pub ts_sec: u32,
pub ts_usec: u32,
pub data: &'a [u8],
}
pub fn parse_pcap_header(input: &[u8]) -> IResult<&[u8], PcapHeader> {
let (input, magic) = le_u32(input)?;
let big_endian = match magic {
MAGIC_LE => false,
MAGIC_BE => true,
_ => {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
)))
}
};
if big_endian {
let (input, version_major) = nom::number::complete::be_u16(input)?;
let (input, version_minor) = nom::number::complete::be_u16(input)?;
let (input, _thiszone) = be_u32(input)?;
let (input, _sigfigs) = be_u32(input)?;
let (input, snaplen) = be_u32(input)?;
let (input, network) = be_u32(input)?;
Ok((
input,
PcapHeader { magic, version_major, version_minor, snaplen, network, big_endian },
))
} else {
let (input, version_major) = le_u16(input)?;
let (input, version_minor) = le_u16(input)?;
let (input, _thiszone) = le_u32(input)?;
let (input, _sigfigs) = le_u32(input)?;
let (input, snaplen) = le_u32(input)?;
let (input, network) = le_u32(input)?;
Ok((
input,
PcapHeader { magic, version_major, version_minor, snaplen, network, big_endian },
))
}
}
pub fn parse_packet(input: &[u8], big_endian: bool) -> IResult<&[u8], RawPacket<'_>> {
let (input, ts_sec) = if big_endian { be_u32(input)? } else { le_u32(input)? };
let (input, ts_usec) = if big_endian { be_u32(input)? } else { le_u32(input)? };
let (input, incl_len) = if big_endian { be_u32(input)? } else { le_u32(input)? };
let (input, _orig_len) = if big_endian { be_u32(input)? } else { le_u32(input)? };
let (input, data) = take(incl_len)(input)?;
Ok((input, RawPacket { ts_sec, ts_usec, data }))
}
pub fn iter_packets(mut input: &[u8]) -> Result<(PcapHeader, Vec<RawPacket<'_>>), String> {
let (rest, header) =
parse_pcap_header(input).map_err(|e| format!("failed to parse PCAP header: {e:?}"))?;
if header.network != 1 {
return Err(format!(
"unsupported link type: {} (only Ethernet/1 is supported)",
header.network
));
}
input = rest;
let mut packets = Vec::new();
while !input.is_empty() {
match parse_packet(input, header.big_endian) {
Ok((rest, pkt)) => {
packets.push(pkt);
input = rest;
}
Err(_) => break,
}
}
Ok((header, packets))
}
#[cfg(test)]
mod tests {
use super::*;
fn le_u32_bytes(v: u32) -> [u8; 4] {
v.to_le_bytes()
}
fn le_u16_bytes(v: u16) -> [u8; 2] {
v.to_le_bytes()
}
fn build_pcap_le(frames: &[&[u8]]) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&le_u32_bytes(MAGIC_LE));
buf.extend_from_slice(&le_u16_bytes(2)); buf.extend_from_slice(&le_u16_bytes(4)); buf.extend_from_slice(&le_u32_bytes(0)); buf.extend_from_slice(&le_u32_bytes(0)); buf.extend_from_slice(&le_u32_bytes(65535)); buf.extend_from_slice(&le_u32_bytes(1));
for (i, frame) in frames.iter().enumerate() {
buf.extend_from_slice(&le_u32_bytes(1_700_000_000 + i as u32)); buf.extend_from_slice(&le_u32_bytes(0)); buf.extend_from_slice(&le_u32_bytes(frame.len() as u32)); buf.extend_from_slice(&le_u32_bytes(frame.len() as u32)); buf.extend_from_slice(frame);
}
buf
}
#[test]
fn parses_header_little_endian() {
let file = build_pcap_le(&[]);
let (_, header) = parse_pcap_header(&file).unwrap();
assert!(!header.big_endian);
assert_eq!(header.network, 1);
assert_eq!(header.snaplen, 65535);
}
#[test]
fn rejects_bad_magic() {
let mut file = build_pcap_le(&[]);
file[0] = 0xff;
assert!(parse_pcap_header(&file).is_err());
}
#[test]
fn iterates_multiple_packets() {
let frame_a = [0xAAu8; 20];
let frame_b = [0xBBu8; 34];
let file = build_pcap_le(&[&frame_a, &frame_b]);
let (header, packets) = iter_packets(&file).unwrap();
assert_eq!(header.network, 1);
assert_eq!(packets.len(), 2);
assert_eq!(packets[0].data, &frame_a[..]);
assert_eq!(packets[1].data, &frame_b[..]);
}
#[test]
fn rejects_non_ethernet_linktype() {
let mut file = build_pcap_le(&[]);
file[20..24].copy_from_slice(&le_u32_bytes(101));
assert!(iter_packets(&file).is_err());
}
#[test]
fn truncated_trailing_record_is_dropped_not_fatal() {
let frame_a = [0xCCu8; 16];
let mut file = build_pcap_le(&[&frame_a]);
file.extend_from_slice(&[0u8; 5]);
let (_, packets) = iter_packets(&file).unwrap();
assert_eq!(packets.len(), 1);
}
}