use crate::format::pcap::PCAPNG_BYTE_ORDER_MAGIC;
use crate::Error;
pub mod block_type {
pub const SHB: u32 = 0x0a0d0d0a;
pub const IDB: u32 = 0x00000001;
pub const PB: u32 = 0x00000002;
pub const SPB: u32 = 0x00000003;
pub const NRB: u32 = 0x00000004;
pub const ISB: u32 = 0x00000005;
pub const EPB: u32 = 0x00000006;
pub const CUSTOM: u32 = 0x00040000;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockType {
SectionHeader,
InterfaceDescription,
Packet,
SimplePacket,
NameResolution,
InterfaceStatistics,
EnhancedPacket,
Unknown(u32),
}
impl From<u32> for BlockType {
fn from(value: u32) -> Self {
match value {
block_type::SHB => BlockType::SectionHeader,
block_type::IDB => BlockType::InterfaceDescription,
block_type::PB => BlockType::Packet,
block_type::SPB => BlockType::SimplePacket,
block_type::NRB => BlockType::NameResolution,
block_type::ISB => BlockType::InterfaceStatistics,
block_type::EPB => BlockType::EnhancedPacket,
other => BlockType::Unknown(other),
}
}
}
fn read_u16(data: &[u8], offset: usize) -> Result<u16, Error> {
if data.len() < offset + 2 {
return Err(Error::truncated(offset + 2, data.len()));
}
Ok(u16::from_le_bytes([data[offset], data[offset + 1]]))
}
fn read_u32(data: &[u8], offset: usize) -> Result<u32, Error> {
if data.len() < offset + 4 {
return Err(Error::truncated(offset + 4, data.len()));
}
Ok(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]))
}
fn read_i64(data: &[u8], offset: usize) -> Result<i64, Error> {
if data.len() < offset + 8 {
return Err(Error::truncated(offset + 8, data.len()));
}
let bytes: [u8; 8] = [
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
];
Ok(i64::from_le_bytes(bytes))
}
fn read_u64(data: &[u8], offset: usize) -> Result<u64, Error> {
if data.len() < offset + 8 {
return Err(Error::truncated(offset + 8, data.len()));
}
let bytes: [u8; 8] = [
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
data[offset + 4],
data[offset + 5],
data[offset + 6],
data[offset + 7],
];
Ok(u64::from_le_bytes(bytes))
}
#[derive(Debug, Clone)]
pub struct SectionHeaderBlock {
pub byte_order_magic: u32,
pub version_major: u16,
pub version_minor: u16,
pub section_length: i64,
}
#[derive(Debug, Clone)]
pub struct InterfaceDescriptionBlock {
pub interface_id: u16,
pub link_type: u16,
pub snap_len: u32,
}
#[derive(Debug, Clone)]
pub struct EnhancedPacketBlock {
pub interface_id: u32,
pub timestamp_high: u32,
pub timestamp_low: u32,
pub captured_length: u32,
pub original_length: u32,
pub data: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct InterfaceStatisticsBlock {
pub interface_id: u32,
pub timestamp_high: u32,
pub timestamp_low: u32,
pub packets_received: u64,
pub packets_dropped: u64,
pub packets_discarded: u64,
}
#[derive(Debug, Clone, PartialEq)]
pub enum NameRecord {
IPv4([u8; 4], String),
IPv6([u8; 16], String),
End,
}
#[derive(Debug, Clone)]
pub struct NameResolutionBlock {
pub records: Vec<NameRecord>,
}
#[derive(Debug, Clone)]
pub enum Block {
SectionHeader(SectionHeaderBlock),
InterfaceDescription(InterfaceDescriptionBlock),
EnhancedPacket(EnhancedPacketBlock),
NameResolution(NameResolutionBlock),
InterfaceStatistics(InterfaceStatisticsBlock),
Unknown,
}
pub fn parse_block(input: &[u8], offset: usize) -> Result<(Block, usize), Error> {
if input.len() < 12 {
return Err(Error::truncated(12, input.len()));
}
let block_type = read_u32(input, 0)?;
let block_len = read_u32(input, 4)? as usize;
if block_len < 12 || block_len > input.len() {
return Err(Error::parse(
offset,
format!("Invalid block length: {}", block_len),
));
}
let block_data = &input[8..block_len - 4];
let block = match BlockType::from(block_type) {
BlockType::SectionHeader => {
if block_data.len() < 16 {
return Err(Error::truncated(16, block_data.len()));
}
let byte_order_magic = read_u32(block_data, 0)?;
if byte_order_magic != PCAPNG_BYTE_ORDER_MAGIC {
return Err(Error::parse(offset, "Invalid byte-order magic".to_string()));
}
Block::SectionHeader(SectionHeaderBlock {
byte_order_magic,
version_major: read_u16(block_data, 4)?,
version_minor: read_u16(block_data, 6)?,
section_length: read_i64(block_data, 8)?,
})
}
BlockType::InterfaceDescription => {
if block_data.len() < 8 {
return Err(Error::truncated(8, block_data.len()));
}
Block::InterfaceDescription(InterfaceDescriptionBlock {
interface_id: 0, link_type: read_u16(block_data, 0)?,
snap_len: read_u32(block_data, 4)?,
})
}
BlockType::EnhancedPacket => {
if block_data.len() < 20 {
return Err(Error::truncated(20, block_data.len()));
}
let captured_len = read_u32(block_data, 12)? as usize;
let data_len = captured_len + (4 - captured_len % 4) % 4;
if block_data.len() < 20 + data_len {
return Err(Error::truncated(20 + data_len, block_data.len()));
}
let data = block_data[20..20 + captured_len].to_vec();
Block::EnhancedPacket(EnhancedPacketBlock {
interface_id: read_u32(block_data, 0)?,
timestamp_high: read_u32(block_data, 4)?,
timestamp_low: read_u32(block_data, 8)?,
captured_length: captured_len as u32,
original_length: read_u32(block_data, 16)?,
data,
})
}
BlockType::NameResolution => {
let mut records = Vec::new();
let mut pos = 0;
while pos + 4 <= block_data.len() {
let record_type = read_u16(block_data, pos)?;
pos += 2;
if record_type == 0 {
break;
}
let record_len = read_u16(block_data, pos)? as usize;
pos += 2;
if pos + record_len > block_data.len() {
break;
}
match record_type {
1 => {
if record_len >= 4 {
let addr = [
block_data[pos],
block_data[pos + 1],
block_data[pos + 2],
block_data[pos + 3],
];
let name_start = pos + 4;
let name_end = name_start + record_len - 4;
if name_end <= block_data.len() {
let name =
String::from_utf8_lossy(&block_data[name_start..name_end])
.trim_end_matches('\0')
.to_string();
if !name.is_empty() {
records.push(NameRecord::IPv4(addr, name));
}
}
}
}
2 => {
if record_len >= 16 {
let mut addr = [0u8; 16];
addr.copy_from_slice(&block_data[pos..pos + 16]);
let name_start = pos + 16;
let name_end = name_start + record_len - 16;
if name_end <= block_data.len() {
let name =
String::from_utf8_lossy(&block_data[name_start..name_end])
.trim_end_matches('\0')
.to_string();
if !name.is_empty() {
records.push(NameRecord::IPv6(addr, name));
}
}
}
}
_ => {}
}
let consumed = 4 + record_len;
pos += (consumed + 3) & !3;
}
Block::NameResolution(NameResolutionBlock { records })
}
BlockType::InterfaceStatistics => {
if block_data.len() < 24 {
return Err(Error::truncated(24, block_data.len()));
}
let interface_id = read_u32(block_data, 0)?;
let ts_high = read_u32(block_data, 4)?;
let ts_low = read_u32(block_data, 8)?;
let mut packets_received = 0u64;
let mut packets_dropped = 0u64;
let mut packets_discarded = 0u64;
if block_data.len() >= 32 {
packets_received = read_u64(block_data, 24)?;
packets_dropped = read_u64(block_data, 32)?;
}
if block_data.len() >= 40 {
packets_discarded = read_u64(block_data, 40)?;
}
Block::InterfaceStatistics(InterfaceStatisticsBlock {
interface_id,
timestamp_high: ts_high,
timestamp_low: ts_low,
packets_received,
packets_dropped,
packets_discarded,
})
}
BlockType::Unknown(_) | BlockType::Packet | BlockType::SimplePacket => Block::Unknown,
};
Ok((block, offset + block_len))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_shb() {
let data = vec![
0x0a, 0x0d, 0x0d, 0x0a, 0x1c, 0x00, 0x00, 0x00, 0x4d, 0x3c, 0x2b, 0x1a, 0x02, 0x00, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1c, 0x00, 0x00, 0x00,
];
let (block, _) = parse_block(&data, 0).unwrap();
match block {
Block::SectionHeader(shb) => {
assert_eq!(shb.byte_order_magic, PCAPNG_BYTE_ORDER_MAGIC);
assert_eq!(shb.version_major, 2);
assert_eq!(shb.version_minor, 4);
assert_eq!(shb.section_length, -1);
}
_ => panic!("Expected SectionHeader block"),
}
}
#[test]
fn test_parse_idb() {
let data = vec![
0x01, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
];
assert_eq!(data.len(), 24, "IDB should be exactly 24 bytes");
let (block, _) = parse_block(&data, 0).unwrap();
match block {
Block::InterfaceDescription(idb) => {
assert_eq!(idb.link_type, 1);
assert_eq!(idb.snap_len, 65535);
}
_ => panic!("Expected InterfaceDescription block"),
}
}
#[test]
fn test_parse_epb() {
let packet_data = vec![0xde, 0xad, 0xbe, 0xef];
let block_len = 32 + packet_data.len();
let mut data = vec![
0x06, 0x00, 0x00, 0x00,
];
data.extend_from_slice(&(block_len as u32).to_le_bytes());
data.extend_from_slice(&0u32.to_le_bytes());
data.extend_from_slice(&1000u32.to_le_bytes());
data.extend_from_slice(&500u32.to_le_bytes());
data.extend_from_slice(&(packet_data.len() as u32).to_le_bytes());
data.extend_from_slice(&(packet_data.len() as u32).to_le_bytes());
data.extend_from_slice(&packet_data);
#[allow(clippy::manual_is_multiple_of)]
while data.len() % 4 != 0 {
data.push(0);
}
data.extend_from_slice(&(block_len as u32).to_le_bytes());
let (block, _) = parse_block(&data, 0).unwrap();
match block {
Block::EnhancedPacket(epb) => {
assert_eq!(epb.interface_id, 0);
assert_eq!(epb.timestamp_high, 1000);
assert_eq!(epb.timestamp_low, 500);
assert_eq!(epb.captured_length, 4);
assert_eq!(epb.original_length, 4);
assert_eq!(&epb.data, &packet_data);
}
_ => panic!("Expected EnhancedPacket block"),
}
}
#[test]
fn test_parse_truncated() {
let data = vec![0u8; 8];
let result = parse_block(&data, 0);
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_block_length() {
let data = vec![
0x0a, 0x0d, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00, ];
let result = parse_block(&data, 0);
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_byte_order() {
let data = vec![
0x0a, 0x0d, 0x0d, 0x0a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, ];
let result = parse_block(&data, 0);
assert!(result.is_err());
}
}