From c0ff4bce57f51ec464c538e7ea80aaf65db1a599 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 20:41:23 +0000 Subject: [PATCH 01/13] Introduce the PcapNgState type to standardise format state handling. --- src/pcapng/mod.rs | 3 ++ src/pcapng/parser.rs | 49 +++++++++++------------------- src/pcapng/state.rs | 68 ++++++++++++++++++++++++++++++++++++++++++ src/pcapng/writer.rs | 71 ++++++++++++++++++++------------------------ 4 files changed, 121 insertions(+), 70 deletions(-) create mode 100644 src/pcapng/state.rs diff --git a/src/pcapng/mod.rs b/src/pcapng/mod.rs index a9bbe02..ac51d54 100644 --- a/src/pcapng/mod.rs +++ b/src/pcapng/mod.rs @@ -3,6 +3,9 @@ pub mod blocks; pub use blocks::{Block, PcapNgBlock, RawBlock}; +pub(crate) mod state; +pub use state::PcapNgState; + pub(crate) mod parser; pub use parser::*; diff --git a/src/pcapng/parser.rs b/src/pcapng/parser.rs index c46e932..94f55d7 100644 --- a/src/pcapng/parser.rs +++ b/src/pcapng/parser.rs @@ -1,10 +1,10 @@ use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; +use super::PcapNgState; use super::blocks::block_common::{Block, RawBlock}; use super::blocks::enhanced_packet::EnhancedPacketBlock; -use super::blocks::interface_description::{InterfaceDescriptionBlock, TsResolution}; +use super::blocks::interface_description::InterfaceDescriptionBlock; use super::blocks::section_header::SectionHeaderBlock; -use super::blocks::{INTERFACE_DESCRIPTION_BLOCK, SECTION_HEADER_BLOCK}; use crate::errors::PcapError; use crate::Endianness; @@ -44,12 +44,8 @@ use crate::Endianness; /// } /// ``` pub struct PcapNgParser { - /// Current section of the pcapng - section: SectionHeaderBlock<'static>, - /// List of the interfaces of the current section of the pcapng - interfaces: Vec>, - /// Timestamp resolutions corresponding to the interfaces - ts_resolutions: Vec, + /// Current state of the pcapng format. + state: PcapNgState<'static>, } impl PcapNgParser { @@ -64,7 +60,13 @@ impl PcapNgParser { _ => return Err(PcapError::InvalidField("PcapNg: SectionHeader invalid or missing")), }; - let parser = PcapNgParser { section, interfaces: Vec::new(), ts_resolutions: Vec::new() }; + let state = PcapNgState { + section, + interfaces: Vec::new(), + ts_resolutions: Vec::new(), + }; + + let parser = PcapNgParser { state }; Ok((rem, parser)) } @@ -72,7 +74,7 @@ impl PcapNgParser { /// Returns the remainder and the next [`Block`]. pub fn next_block<'a>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], Block<'a>), PcapError> { // Read next Block - let mut res = match self.section.endianness { + let mut res = match self.state.section.endianness { Endianness::Big => { let (rem, raw_block) = self.next_raw_block_inner::(src)?; let block = raw_block.try_into_block::()?; @@ -88,6 +90,7 @@ impl PcapNgParser { // If the block is an EnhancedPacketBlock, adjust its timestamp with the correct TsResolution if let Ok((_, Block::EnhancedPacket(ref mut blk))) = &mut res { let ts_resol = self + .state .ts_resolutions .get(blk.interface_id as usize) .ok_or(PcapError::InvalidInterfaceId(blk.interface_id))?; @@ -101,7 +104,7 @@ impl PcapNgParser { /// Returns the remainder and the next [`RawBlock`]. pub fn next_raw_block<'a>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], RawBlock<'a>), PcapError> { // Read next Block - match self.section.endianness { + match self.state.section.endianness { Endianness::Big => self.next_raw_block_inner::(src), Endianness::Little => self.next_raw_block_inner::(src), } @@ -110,38 +113,22 @@ impl PcapNgParser { /// Inner function to parse the next raw block. fn next_raw_block_inner<'a, B: ByteOrder>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], RawBlock<'a>), PcapError> { let (rem, raw_block) = RawBlock::from_slice::(src)?; - - match raw_block.type_ { - SECTION_HEADER_BLOCK => { - self.section = raw_block.clone().try_into_block::()?.into_owned().into_section_header().unwrap(); - self.interfaces.clear(); - self.ts_resolutions.clear(); - }, - INTERFACE_DESCRIPTION_BLOCK => { - let interface = raw_block.clone().try_into_block::()?.into_owned().into_interface_description().unwrap(); - let ts_resolution = interface.ts_resolution()?; - - self.interfaces.push(interface); - self.ts_resolutions.push(ts_resolution); - }, - _ => {}, - } - + self.state.update_for_raw_block::(&raw_block)?; Ok((rem, raw_block)) } /// Returns the current [`SectionHeaderBlock`]. pub fn section(&self) -> &SectionHeaderBlock<'static> { - &self.section + &self.state.section } /// Returns all the current [`InterfaceDescriptionBlock`]. pub fn interfaces(&self) -> &[InterfaceDescriptionBlock<'static>] { - &self.interfaces[..] + &self.state.interfaces[..] } /// Returns the [`InterfaceDescriptionBlock`] corresponding to the given packet. pub fn packet_interface(&self, packet: &EnhancedPacketBlock) -> Option<&InterfaceDescriptionBlock> { - self.interfaces.get(packet.interface_id as usize) + self.state.interfaces.get(packet.interface_id as usize) } } diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs new file mode 100644 index 0000000..58b7aee --- /dev/null +++ b/src/pcapng/state.rs @@ -0,0 +1,68 @@ +use byteorder_slice::ByteOrder; + +use super::blocks::block_common::{Block, RawBlock}; +use super::blocks::interface_description::{InterfaceDescriptionBlock, TsResolution}; +use super::blocks::section_header::SectionHeaderBlock; +use super::blocks::{INTERFACE_DESCRIPTION_BLOCK, SECTION_HEADER_BLOCK}; +use crate::errors::PcapError; + +#[cfg(doc)] +use { + super::blocks::interface_description::InterfaceDescriptionOption, + crate::pcapng::{PcapNgReader, PcapNgWriter}, + crate::Endianness, +}; + +/// State that must be maintained whilst reading or writing a PcapNg stream. +/// +/// This state is necessary because the encoding of blocks depends on +/// information seen earlier in the stream, such as the [Endianness] of the +/// [SectionHeaderBlock] and the [InterfaceDescriptionOption::IfTsResol] +/// option on each [InterfaceDescriptionBlock]. +/// +/// Normally this state is maintained internally by a [PcapNgReader] or +/// [PcapNgWriter], but it's also possible to create a [PcapNgState] with +/// [PcapNgState::default], and then update it by calling +/// [PcapNgState::update_for_block] or [PcapNgState::update_for_raw_block]. +/// +#[derive(Default)] +pub struct PcapNgState<'s> { + /// Current section of the pcapng + pub(crate) section: SectionHeaderBlock<'s>, + /// List of the interfaces of the current section of the pcapng + pub(crate) interfaces: Vec>, + /// Timestamp resolutions corresponding to the interfaces + pub(crate) ts_resolutions: Vec, +} + +impl<'s> PcapNgState<'s> { + /// Update the state based on the next [Block]. + pub fn update_for_block(&mut self, block: &Block) -> Result<(), PcapError> { + match block { + Block::SectionHeader(blk) => { + self.section = blk.clone().into_owned(); + self.interfaces.clear(); + self.ts_resolutions.clear(); + }, + Block::InterfaceDescription(blk) => { + let ts_resolution = blk.ts_resolution()?; + self.ts_resolutions.push(ts_resolution); + + self.interfaces.push(blk.clone().into_owned()); + }, + _ => {}, + } + Ok(()) + } + + /// Update the state based on the next [RawBlock]. + pub fn update_for_raw_block(&mut self, raw_block: &RawBlock) -> Result<(), PcapError> { + match raw_block.type_ { + SECTION_HEADER_BLOCK | INTERFACE_DESCRIPTION_BLOCK => { + let block = raw_block.clone().try_into_block::()?; + self.update_for_block(&block) + }, + _ => Ok(()) + } + } +} diff --git a/src/pcapng/writer.rs b/src/pcapng/writer.rs index d2e55c5..4c7db8c 100644 --- a/src/pcapng/writer.rs +++ b/src/pcapng/writer.rs @@ -1,12 +1,11 @@ use std::io::Write; -use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; +use byteorder_slice::{BigEndian, LittleEndian}; use super::blocks::block_common::{Block, PcapNgBlock}; -use super::blocks::interface_description::{InterfaceDescriptionBlock, TsResolution}; +use super::blocks::interface_description::InterfaceDescriptionBlock; use super::blocks::section_header::SectionHeaderBlock; -use super::blocks::SECTION_HEADER_BLOCK; -use super::RawBlock; +use super::{PcapNgState, RawBlock}; use crate::{Endianness, PcapError, PcapResult}; @@ -34,12 +33,8 @@ use crate::{Endianness, PcapError, PcapResult}; /// } /// ``` pub struct PcapNgWriter { - /// Current section of the pcapng - section: SectionHeaderBlock<'static>, - /// List of the interfaces of the current section of the pcapng - interfaces: Vec>, - /// Timestamp resolutions corresponding to the interfaces - ts_resolutions: Vec, + /// Current state of the pcapng format. + state: PcapNgState<'static>, /// Wrapped writer to which the block are written to. writer: W, @@ -82,7 +77,13 @@ impl PcapNgWriter { Endianness::Little => section.clone().into_block().write_to::(&mut writer).map_err(PcapError::IoError)?, }; - Ok(Self { section, interfaces: Vec::new(), ts_resolutions: Vec::new(), writer }) + let state = PcapNgState { + section, + interfaces: Vec::new(), + ts_resolutions: Vec::new() + }; + + Ok(Self { state, writer }) } /// Write a [`Block`]. @@ -117,36 +118,27 @@ impl PcapNgWriter { /// pcap_ng_writer.write_block(&packet.into_block()).unwrap(); /// ``` pub fn write_block(&mut self, block: &Block) -> PcapResult { - match block { - Block::SectionHeader(blk) => { - self.section = blk.clone().into_owned(); - self.interfaces.clear(); - self.ts_resolutions.clear(); - }, - Block::InterfaceDescription(blk) => { - let ts_resolution = blk.ts_resolution()?; - self.ts_resolutions.push(ts_resolution); + self.state.update_for_block(block)?; - self.interfaces.push(blk.clone().into_owned()); - }, + match block { Block::InterfaceStatistics(blk) => { - if blk.interface_id as usize >= self.interfaces.len() { + if blk.interface_id as usize >= self.state.interfaces.len() { return Err(PcapError::InvalidInterfaceId(blk.interface_id)); } }, Block::EnhancedPacket(blk) => { - if blk.interface_id as usize >= self.interfaces.len() { + if blk.interface_id as usize >= self.state.interfaces.len() { return Err(PcapError::InvalidInterfaceId(blk.interface_id)); } - let ts_resol = self.ts_resolutions[blk.interface_id as usize]; + let ts_resol = self.state.ts_resolutions[blk.interface_id as usize]; blk.set_write_ts_resolution(ts_resol); }, _ => (), } - match self.section.endianness { + match self.state.section.endianness { Endianness::Big => block.write_to::(&mut self.writer).map_err(PcapError::IoError), Endianness::Little => block.write_to::(&mut self.writer).map_err(PcapError::IoError), } @@ -191,18 +183,19 @@ impl PcapNgWriter { /// /// Doesn't check the validity of the written blocks. pub fn write_raw_block(&mut self, block: &RawBlock) -> PcapResult { - return match self.section.endianness { - Endianness::Big => inner::(&mut self.section, block, &mut self.writer), - Endianness::Little => inner::(&mut self.section, block, &mut self.writer), - }; - - // Write a RawBlock to a writer - fn inner(section: &mut SectionHeaderBlock, block: &RawBlock, writer: &mut W) -> PcapResult { - if block.type_ == SECTION_HEADER_BLOCK { - *section = block.clone().try_into_block::()?.into_owned().into_section_header().unwrap(); + match self.state.section.endianness { + Endianness::Big => { + let written = block.write_to::(&mut self.writer) + .map_err(PcapError::IoError)?; + self.state.update_for_raw_block::(block)?; + Ok(written) + }, + Endianness::Little => { + let written = block.write_to::(&mut self.writer) + .map_err(PcapError::IoError)?; + self.state.update_for_raw_block::(block)?; + Ok(written) } - - block.write_to::(writer).map_err(PcapError::IoError) } } @@ -225,11 +218,11 @@ impl PcapNgWriter { /// Return the current [`SectionHeaderBlock`]. pub fn section(&self) -> &SectionHeaderBlock<'static> { - &self.section + &self.state.section } /// Return all the current [`InterfaceDescriptionBlock`]. pub fn interfaces(&self) -> &[InterfaceDescriptionBlock<'static>] { - &self.interfaces + &self.state.interfaces } } From f3430758d30fabd66bcd264ebd22f6efc950d612 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 20:50:37 +0000 Subject: [PATCH 02/13] Add helper functions to encode and decode timestamps. --- src/pcapng/state.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs index 58b7aee..37bf10b 100644 --- a/src/pcapng/state.rs +++ b/src/pcapng/state.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use byteorder_slice::ByteOrder; use super::blocks::block_common::{Block, RawBlock}; @@ -65,4 +67,25 @@ impl<'s> PcapNgState<'s> { _ => Ok(()) } } + + pub(crate) fn decode_timestamp(&self, interface_id: u32, ts_raw: u64) -> Result { + let ts_resolution = self + .ts_resolutions + .get(interface_id as usize) + .ok_or(PcapError::InvalidInterfaceId(interface_id))?; + let ts_nanos = ts_raw * ts_resolution.to_nano_secs() as u64; + Ok(Duration::from_nanos(ts_nanos)) + } + + pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration) -> std::io::Result { + let ts_resolution = self + .ts_resolutions + .get(interface_id as usize) + .ok_or(std::io::Error::other("Invalid interface ID"))?; + let ts_raw = timestamp.as_nanos() / ts_resolution.to_nano_secs() as u128; + ts_raw + .try_into() + .map_err(|_| std::io::Error::other( + "Timestamp too big, please use a bigger timestamp resolution")) + } } From b72527f3f51f320411763fd20f966939066367cc Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 21:28:34 +0000 Subject: [PATCH 03/13] Require a PcapNgState to encode or decode blocks. Block::from_slice(), Block::write_to(), and all conversions from RawBlock to Block require a PcapNgState, because without state information the encoding is ambiguous. This was previously handled in an ad-hoc manner in the reader, writer and parser but is now centralised into the PcapNgState type. The previous functions for adjusting EPB timestamps are removed as they are no longer necessary. Timestamps are now converted as required in from_slice() and write_to(), according to the current PcapNgState. --- src/pcapng/blocks/block_common.rs | 69 +++++++++++---------- src/pcapng/blocks/enhanced_packet.rs | 50 +++------------ src/pcapng/blocks/interface_description.rs | 5 +- src/pcapng/blocks/interface_statistics.rs | 5 +- src/pcapng/blocks/name_resolution.rs | 5 +- src/pcapng/blocks/packet.rs | 5 +- src/pcapng/blocks/section_header.rs | 5 +- src/pcapng/blocks/simple_packet.rs | 5 +- src/pcapng/blocks/systemd_journal_export.rs | 5 +- src/pcapng/blocks/unknown.rs | 5 +- src/pcapng/parser.rs | 34 +++------- src/pcapng/state.rs | 16 ++--- src/pcapng/writer.rs | 44 +++++-------- 13 files changed, 98 insertions(+), 155 deletions(-) diff --git a/src/pcapng/blocks/block_common.rs b/src/pcapng/blocks/block_common.rs index c03f3bc..3c5de05 100644 --- a/src/pcapng/blocks/block_common.rs +++ b/src/pcapng/blocks/block_common.rs @@ -17,6 +17,7 @@ use super::section_header::SectionHeaderBlock; use super::simple_packet::SimplePacketBlock; use super::systemd_journal_export::SystemdJournalExportBlock; use super::unknown::UnknownBlock; +use crate::pcapng::PcapNgState; use crate::errors::PcapError; use crate::PcapResult; @@ -136,9 +137,9 @@ impl<'a> RawBlock<'a> { Ok(self.body.len() + 12) } - /// Tries to convert a [`RawBlock`] into a [`Block`] - pub fn try_into_block(self) -> PcapResult> { - Block::try_from_raw_block::(self) + /// Tries to convert a [`RawBlock`] into a [`Block`], using a [`PcapNgState`]. + pub fn try_into_block<'s, B: ByteOrder>(self, state: &'s PcapNgState) -> PcapResult> { + Block::try_from_raw_block::(state, self) } } @@ -166,38 +167,38 @@ pub enum Block<'a> { } impl<'a> Block<'a> { - /// Parses a [`Block`] from a slice - pub fn from_slice(slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + /// Parses a [`Block`] from a slice, using a [`PcapNgState`]. + pub fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { let (rem, raw_block) = RawBlock::from_slice::(slice)?; - let block = Self::try_from_raw_block::(raw_block)?; + let block = Self::try_from_raw_block::(state, raw_block)?; Ok((rem, block)) } - /// Writes a [`Block`] to a writer. - pub fn write_to(&self, writer: &mut W) -> IoResult { + /// Writes a [`Block`] to a writer, using a [`PcapNgState`]. + pub fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { return match self { - Self::SectionHeader(b) => inner_write_to::(b, SECTION_HEADER_BLOCK, writer), - Self::InterfaceDescription(b) => inner_write_to::(b, INTERFACE_DESCRIPTION_BLOCK, writer), - Self::Packet(b) => inner_write_to::(b, PACKET_BLOCK, writer), - Self::SimplePacket(b) => inner_write_to::(b, SIMPLE_PACKET_BLOCK, writer), - Self::NameResolution(b) => inner_write_to::(b, NAME_RESOLUTION_BLOCK, writer), - Self::InterfaceStatistics(b) => inner_write_to::(b, INTERFACE_STATISTIC_BLOCK, writer), - Self::EnhancedPacket(b) => inner_write_to::(b, ENHANCED_PACKET_BLOCK, writer), - Self::SystemdJournalExport(b) => inner_write_to::(b, SYSTEMD_JOURNAL_EXPORT_BLOCK, writer), - Self::Unknown(b) => inner_write_to::(b, b.type_, writer), + Self::SectionHeader(b) => inner_write_to::(state, b, SECTION_HEADER_BLOCK, writer), + Self::InterfaceDescription(b) => inner_write_to::(state, b, INTERFACE_DESCRIPTION_BLOCK, writer), + Self::Packet(b) => inner_write_to::(state, b, PACKET_BLOCK, writer), + Self::SimplePacket(b) => inner_write_to::(state, b, SIMPLE_PACKET_BLOCK, writer), + Self::NameResolution(b) => inner_write_to::(state, b, NAME_RESOLUTION_BLOCK, writer), + Self::InterfaceStatistics(b) => inner_write_to::(state, b, INTERFACE_STATISTIC_BLOCK, writer), + Self::EnhancedPacket(b) => inner_write_to::(state, b, ENHANCED_PACKET_BLOCK, writer), + Self::SystemdJournalExport(b) => inner_write_to::(state, b, SYSTEMD_JOURNAL_EXPORT_BLOCK, writer), + Self::Unknown(b) => inner_write_to::(state, b, b.type_, writer), }; - fn inner_write_to<'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(block: &BL, block_code: u32, writer: &mut W) -> IoResult { + fn inner_write_to<'s, 'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(state: &'s PcapNgState, block: &BL, block_code: u32, writer: &mut W) -> IoResult { // Fake write to compute the data length - let data_len = block.write_to::(&mut std::io::sink()).unwrap(); + let data_len = block.write_to::(state, &mut std::io::sink()).unwrap(); let pad_len = (4 - (data_len % 4)) % 4; let block_len = data_len + pad_len + 12; writer.write_u32::(block_code)?; writer.write_u32::(block_len as u32)?; - block.write_to::(writer)?; + block.write_to::(state, writer)?; writer.write_all(&[0_u8; 3][..pad_len])?; writer.write_u32::(block_len as u32)?; @@ -205,10 +206,10 @@ impl<'a> Block<'a> { } } - /// Tries to create a [`Block`] from a [`RawBlock`]. + /// Tries to create a [`Block`] from a [`RawBlock`], given a [`PcapNgState`]. /// /// The RawBlock must be Borrowed. - pub fn try_from_raw_block(raw_block: RawBlock<'a>) -> Result, PcapError> { + pub fn try_from_raw_block<'s, B: ByteOrder>(state: &'s PcapNgState, raw_block: RawBlock<'a>) -> Result, PcapError> { let body = match raw_block.body { Cow::Borrowed(b) => b, _ => panic!("The raw block is not borrowed"), @@ -216,35 +217,35 @@ impl<'a> Block<'a> { match raw_block.type_ { SECTION_HEADER_BLOCK => { - let (_, block) = SectionHeaderBlock::from_slice::(body)?; + let (_, block) = SectionHeaderBlock::from_slice::(state, body)?; Ok(Block::SectionHeader(block)) }, INTERFACE_DESCRIPTION_BLOCK => { - let (_, block) = InterfaceDescriptionBlock::from_slice::(body)?; + let (_, block) = InterfaceDescriptionBlock::from_slice::(state, body)?; Ok(Block::InterfaceDescription(block)) }, PACKET_BLOCK => { - let (_, block) = PacketBlock::from_slice::(body)?; + let (_, block) = PacketBlock::from_slice::(state, body)?; Ok(Block::Packet(block)) }, SIMPLE_PACKET_BLOCK => { - let (_, block) = SimplePacketBlock::from_slice::(body)?; + let (_, block) = SimplePacketBlock::from_slice::(state, body)?; Ok(Block::SimplePacket(block)) }, NAME_RESOLUTION_BLOCK => { - let (_, block) = NameResolutionBlock::from_slice::(body)?; + let (_, block) = NameResolutionBlock::from_slice::(state, body)?; Ok(Block::NameResolution(block)) }, INTERFACE_STATISTIC_BLOCK => { - let (_, block) = InterfaceStatisticsBlock::from_slice::(body)?; + let (_, block) = InterfaceStatisticsBlock::from_slice::(state, body)?; Ok(Block::InterfaceStatistics(block)) }, ENHANCED_PACKET_BLOCK => { - let (_, block) = EnhancedPacketBlock::from_slice::(body)?; + let (_, block) = EnhancedPacketBlock::from_slice::(state, body)?; Ok(Block::EnhancedPacket(block)) }, SYSTEMD_JOURNAL_EXPORT_BLOCK => { - let (_, block) = SystemdJournalExportBlock::from_slice::(body)?; + let (_, block) = SystemdJournalExportBlock::from_slice::(state, body)?; Ok(Block::SystemdJournalExport(block)) }, type_ => Ok(Block::Unknown(UnknownBlock::new(type_, raw_block.initial_len, body))), @@ -383,13 +384,13 @@ impl<'a> Block<'a> { /// Common interface for the PcapNg blocks pub trait PcapNgBlock<'a> { - /// Parse a new block from a slice - fn from_slice(slice: &'a [u8]) -> Result<(&[u8], Self), PcapError> + /// Parse a new block from a slice, using a [`PcapNgState`]. + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> where Self: std::marker::Sized; - /// Write the content of a block into a writer - fn write_to(&self, writer: &mut W) -> IoResult; + /// Write the content of a block into a writer, using a [`PcapNgState`]. + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult; /// Convert a block into the [`Block`] enumeration fn into_block(self) -> Block<'a>; diff --git a/src/pcapng/blocks/enhanced_packet.rs b/src/pcapng/blocks/enhanced_packet.rs index 5be3107..03e5790 100644 --- a/src/pcapng/blocks/enhanced_packet.rs +++ b/src/pcapng/blocks/enhanced_packet.rs @@ -1,7 +1,6 @@ //! Enhanced Packet Block (EPB). use std::borrow::Cow; -use std::cell::Cell; use std::io::{Result as IoResult, Write}; use std::time::Duration; @@ -11,13 +10,13 @@ use byteorder_slice::ByteOrder; use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; -use super::interface_description::TsResolution; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// An Enhanced Packet Block (EPB) is the standard container for storing the packets coming from the network. -#[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] +#[derive(Clone, Debug, Default, IntoOwned, Eq, PartialEq)] pub struct EnhancedPacketBlock<'a> { /// It specifies the interface this packet comes from. /// @@ -25,9 +24,7 @@ pub struct EnhancedPacketBlock<'a> { /// (within the current Section of the file) is identified by the same number of this field. pub interface_id: u32, - /// Number of units of time that have elapsed since 1970-01-01 00:00:00 UTC. - /// By default the timestamp read from the format is considered by have a nano_second resolution. - /// If it is not the case, call [`self.correct_ts_with_ts_resolution()`]. + /// Time elapsed since 1970-01-01 00:00:00 UTC. pub timestamp: Duration, /// Actual length of the packet when it was transmitted on the network. @@ -38,26 +35,10 @@ pub struct EnhancedPacketBlock<'a> { /// Options pub options: Vec>, - - /// TsResolution to use when writing the block - write_ts_resolution: Cell, -} - -impl<'a> EnhancedPacketBlock<'a> { - /// Set the [`TsResolution`] to use for writing this block. - pub fn set_write_ts_resolution(&self, ts_resolution: TsResolution) { - self.write_ts_resolution.set(ts_resolution) - } - - /// Ajust the parsed timestamp field with the right [`TsResolution`]. - /// Must be called only once. - pub(crate) fn adjust_parsed_timestamp(&mut self, ts_resolution: TsResolution) { - self.timestamp *= ts_resolution.to_nano_secs(); - } } impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 20 { return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20")); } @@ -67,7 +48,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { let timestamp_high = slice.read_u32::().unwrap() as u64; let timestamp_low = slice.read_u32::().unwrap() as u64; let ts_raw = (timestamp_high << 32) + timestamp_low; - let timestamp = Duration::from_nanos(ts_raw); + let timestamp = state.decode_timestamp(interface_id, ts_raw)?; let captured_len = slice.read_u32::().unwrap(); let original_len = slice.read_u32::().unwrap(); @@ -89,21 +70,17 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { original_len, data: Cow::Borrowed(data), options, - write_ts_resolution: Cell::new(TsResolution::NANO), }; Ok((slice, block)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { let pad_len = (4 - (&self.data.len() % 4)) % 4; writer.write_u32::(self.interface_id)?; - let ts_raw = self.timestamp.as_nanos() / self.write_ts_resolution.get().to_nano_secs() as u128; - let ts_raw: u64 = ts_raw - .try_into() - .map_err(|_| std::io::Error::other("Timestamp too big, please use a bigger timestamp resolution"))?; + let ts_raw = state.encode_timestamp(self.interface_id, self.timestamp)?; let timestamp_high = (ts_raw >> 32) as u32; let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; @@ -125,19 +102,6 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { } } -impl Default for EnhancedPacketBlock<'_> { - fn default() -> Self { - Self { - interface_id: Default::default(), - timestamp: Duration::ZERO, - original_len: Default::default(), - data: Default::default(), - options: Default::default(), - write_ts_resolution: Default::default(), - } - } -} - /* ----- */ diff --git a/src/pcapng/blocks/interface_description.rs b/src/pcapng/blocks/interface_description.rs index fd82870..d85cc2d 100644 --- a/src/pcapng/blocks/interface_description.rs +++ b/src/pcapng/blocks/interface_description.rs @@ -14,6 +14,7 @@ use once_cell::sync::Lazy; use super::block_common::{Block, PcapNgBlock}; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; use crate::DataLink; @@ -38,7 +39,7 @@ pub struct InterfaceDescriptionBlock<'a> { } impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 8 { return Err(PcapError::InvalidField("InterfaceDescriptionBlock: block length < 8")); } @@ -58,7 +59,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { Ok((slice, block)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u16::(u32::from(self.linktype) as u16)?; writer.write_u16::(0)?; writer.write_u32::(self.snaplen)?; diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index 4bcdb49..0fe4e67 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -11,6 +11,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// The Interface Statistics Block contains the capture statistics for a given interface and it is optional. @@ -33,7 +34,7 @@ pub struct InterfaceStatisticsBlock<'a> { } impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&[u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 12 { return Err(PcapError::InvalidField("InterfaceStatisticsBlock: block length < 12")); } @@ -47,7 +48,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { Ok((slice, block)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u32::(self.interface_id)?; writer.write_u64::(self.timestamp)?; diff --git a/src/pcapng/blocks/name_resolution.rs b/src/pcapng/blocks/name_resolution.rs index 377dd50..a2f8ea4 100644 --- a/src/pcapng/blocks/name_resolution.rs +++ b/src/pcapng/blocks/name_resolution.rs @@ -11,6 +11,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// The Name Resolution Block (NRB) is used to support the correlation of numeric addresses @@ -24,7 +25,7 @@ pub struct NameResolutionBlock<'a> { } impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { let mut records = Vec::new(); loop { @@ -44,7 +45,7 @@ impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { Ok((rem, block)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { let mut len = 0; for record in &self.records { diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index 4ec8527..bc837d2 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -11,6 +11,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// The Packet Block is obsolete, and MUST NOT be used in new files. /// Use the Enhanced Packet Block or Simple Packet Block instead. @@ -43,7 +44,7 @@ pub struct PacketBlock<'a> { } impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 20 { return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20")); } @@ -78,7 +79,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { Ok((slice, block)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u16::(self.interface_id)?; writer.write_u16::(self.drop_count)?; writer.write_u64::(self.timestamp)?; diff --git a/src/pcapng/blocks/section_header.rs b/src/pcapng/blocks/section_header.rs index fff2d31..df2fe1a 100644 --- a/src/pcapng/blocks/section_header.rs +++ b/src/pcapng/blocks/section_header.rs @@ -11,6 +11,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; use crate::Endianness; @@ -39,7 +40,7 @@ pub struct SectionHeaderBlock<'a> { } impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 16 { return Err(PcapError::InvalidField("SectionHeaderBlock: block length < 16")); } @@ -71,7 +72,7 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { } } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { match self.endianness { Endianness::Big => writer.write_u32::(0x1A2B3C4D)?, Endianness::Little => writer.write_u32::(0x1A2B3C4D)?, diff --git a/src/pcapng/blocks/simple_packet.rs b/src/pcapng/blocks/simple_packet.rs index 29fd35e..7467762 100644 --- a/src/pcapng/blocks/simple_packet.rs +++ b/src/pcapng/blocks/simple_packet.rs @@ -10,6 +10,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// The Simple Packet Block (SPB) is a lightweight container for storing the packets coming from the network. @@ -25,7 +26,7 @@ pub struct SimplePacketBlock<'a> { } impl<'a> PcapNgBlock<'a> for SimplePacketBlock<'a> { - fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 4 { return Err(PcapError::InvalidField("SimplePacketBlock: block length < 4")); } @@ -36,7 +37,7 @@ impl<'a> PcapNgBlock<'a> for SimplePacketBlock<'a> { Ok((&[], packet)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u32::(self.original_len)?; writer.write_all(&self.data)?; diff --git a/src/pcapng/blocks/systemd_journal_export.rs b/src/pcapng/blocks/systemd_journal_export.rs index f1428e0..290de33 100644 --- a/src/pcapng/blocks/systemd_journal_export.rs +++ b/src/pcapng/blocks/systemd_journal_export.rs @@ -8,6 +8,7 @@ use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// The Systemd Journal Export Block is a lightweight containter for systemd Journal Export Format entry data. @@ -18,12 +19,12 @@ pub struct SystemdJournalExportBlock<'a> { } impl<'a> PcapNgBlock<'a> for SystemdJournalExportBlock<'a> { - fn from_slice(slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { let packet = SystemdJournalExportBlock { journal_entry: Cow::Borrowed(slice) }; Ok((&[], packet)) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_all(&self.journal_entry)?; let pad_len = (4 - (self.journal_entry.len() % 4)) % 4; diff --git a/src/pcapng/blocks/unknown.rs b/src/pcapng/blocks/unknown.rs index 706ca0f..ec97b57 100644 --- a/src/pcapng/blocks/unknown.rs +++ b/src/pcapng/blocks/unknown.rs @@ -7,6 +7,7 @@ use byteorder_slice::ByteOrder; use derive_into_owned::IntoOwned; use super::block_common::{Block, PcapNgBlock}; +use crate::pcapng::PcapNgState; use crate::PcapError; @@ -29,14 +30,14 @@ impl<'a> UnknownBlock<'a> { } impl<'a> PcapNgBlock<'a> for UnknownBlock<'a> { - fn from_slice(_slice: &'a [u8]) -> Result<(&[u8], Self), PcapError> + fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, _slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> where Self: Sized, { unimplemented!("UnkknownBlock::::From_slice shouldn't be called") } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_all(&self.value)?; Ok(self.value.len()) } diff --git a/src/pcapng/parser.rs b/src/pcapng/parser.rs index 94f55d7..7879dbb 100644 --- a/src/pcapng/parser.rs +++ b/src/pcapng/parser.rs @@ -54,17 +54,12 @@ impl PcapNgParser { /// Parses the first block which must be a valid SectionHeaderBlock. pub fn new(src: &[u8]) -> Result<(&[u8], Self), PcapError> { // Always use BigEndian here because we can't know the SectionHeaderBlock endianness - let (rem, section) = Block::from_slice::(src)?; - let section = match section { - Block::SectionHeader(section) => section.into_owned(), - _ => return Err(PcapError::InvalidField("PcapNg: SectionHeader invalid or missing")), - }; - - let state = PcapNgState { - section, - interfaces: Vec::new(), - ts_resolutions: Vec::new(), + let mut state = PcapNgState::default(); + let (rem, block) = Block::from_slice::(&state, src)?; + if !matches!(&block, Block::SectionHeader(_)) { + return Err(PcapError::InvalidField("PcapNg: SectionHeader invalid or missing")); }; + state.update_for_block(&block)?; let parser = PcapNgParser { state }; @@ -74,31 +69,18 @@ impl PcapNgParser { /// Returns the remainder and the next [`Block`]. pub fn next_block<'a>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], Block<'a>), PcapError> { // Read next Block - let mut res = match self.state.section.endianness { + match self.state.section.endianness { Endianness::Big => { let (rem, raw_block) = self.next_raw_block_inner::(src)?; - let block = raw_block.try_into_block::()?; + let block = raw_block.try_into_block::(&self.state)?; Ok((rem, block)) }, Endianness::Little => { let (rem, raw_block) = self.next_raw_block_inner::(src)?; - let block = raw_block.try_into_block::()?; + let block = raw_block.try_into_block::(&self.state)?; Ok((rem, block)) }, - }; - - // If the block is an EnhancedPacketBlock, adjust its timestamp with the correct TsResolution - if let Ok((_, Block::EnhancedPacket(ref mut blk))) = &mut res { - let ts_resol = self - .state - .ts_resolutions - .get(blk.interface_id as usize) - .ok_or(PcapError::InvalidInterfaceId(blk.interface_id))?; - - blk.adjust_parsed_timestamp(*ts_resol); } - - res } /// Returns the remainder and the next [`RawBlock`]. diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs index 37bf10b..c12cd79 100644 --- a/src/pcapng/state.rs +++ b/src/pcapng/state.rs @@ -18,14 +18,14 @@ use { /// State that must be maintained whilst reading or writing a PcapNg stream. /// /// This state is necessary because the encoding of blocks depends on -/// information seen earlier in the stream, such as the [Endianness] of the -/// [SectionHeaderBlock] and the [InterfaceDescriptionOption::IfTsResol] -/// option on each [InterfaceDescriptionBlock]. +/// information seen earlier in the stream, such as the [`Endianness`] of the +/// [`SectionHeaderBlock`] and the [`TsResolution`] of each +/// [`InterfaceDescriptionBlock`]. /// -/// Normally this state is maintained internally by a [PcapNgReader] or -/// [PcapNgWriter], but it's also possible to create a [PcapNgState] with -/// [PcapNgState::default], and then update it by calling -/// [PcapNgState::update_for_block] or [PcapNgState::update_for_raw_block]. +/// Normally this state is maintained internally by a [`PcapNgReader`] or +/// [`PcapNgWriter`], but it's also possible to create a new [`PcapNgState`] +/// with [`PcapNgState::default`], and then update it by calling +/// [`PcapNgState::update_for_block`] or [`PcapNgState::update_for_raw_block`]. /// #[derive(Default)] pub struct PcapNgState<'s> { @@ -61,7 +61,7 @@ impl<'s> PcapNgState<'s> { pub fn update_for_raw_block(&mut self, raw_block: &RawBlock) -> Result<(), PcapError> { match raw_block.type_ { SECTION_HEADER_BLOCK | INTERFACE_DESCRIPTION_BLOCK => { - let block = raw_block.clone().try_into_block::()?; + let block = raw_block.clone().try_into_block::(self)?; self.update_for_block(&block) }, _ => Ok(()) diff --git a/src/pcapng/writer.rs b/src/pcapng/writer.rs index 4c7db8c..39d508c 100644 --- a/src/pcapng/writer.rs +++ b/src/pcapng/writer.rs @@ -72,15 +72,21 @@ impl PcapNgWriter { /// Create a new [`PcapNgWriter`] from an existing writer with the given section header. pub fn with_section_header(mut writer: W, section: SectionHeaderBlock<'static>) -> PcapResult { - match section.endianness { - Endianness::Big => section.clone().into_block().write_to::(&mut writer).map_err(PcapError::IoError)?, - Endianness::Little => section.clone().into_block().write_to::(&mut writer).map_err(PcapError::IoError)?, - }; + let mut state = PcapNgState::default(); + + let block = section + .clone() + .into_block(); - let state = PcapNgState { - section, - interfaces: Vec::new(), - ts_resolutions: Vec::new() + state.update_for_block(&block)?; + + match section.endianness { + Endianness::Big => block + .write_to::(&state, &mut writer) + .map_err(PcapError::IoError)?, + Endianness::Little => block + .write_to::(&state, &mut writer) + .map_err(PcapError::IoError)?, }; Ok(Self { state, writer }) @@ -120,27 +126,9 @@ impl PcapNgWriter { pub fn write_block(&mut self, block: &Block) -> PcapResult { self.state.update_for_block(block)?; - match block { - Block::InterfaceStatistics(blk) => { - if blk.interface_id as usize >= self.state.interfaces.len() { - return Err(PcapError::InvalidInterfaceId(blk.interface_id)); - } - }, - Block::EnhancedPacket(blk) => { - if blk.interface_id as usize >= self.state.interfaces.len() { - return Err(PcapError::InvalidInterfaceId(blk.interface_id)); - } - - let ts_resol = self.state.ts_resolutions[blk.interface_id as usize]; - blk.set_write_ts_resolution(ts_resol); - }, - - _ => (), - } - match self.state.section.endianness { - Endianness::Big => block.write_to::(&mut self.writer).map_err(PcapError::IoError), - Endianness::Little => block.write_to::(&mut self.writer).map_err(PcapError::IoError), + Endianness::Big => block.write_to::(&self.state, &mut self.writer).map_err(PcapError::IoError), + Endianness::Little => block.write_to::(&self.state, &mut self.writer).map_err(PcapError::IoError), } } From 1205140b9543f44b208ed9d39e98f55e05b41581 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 21:34:18 +0000 Subject: [PATCH 04/13] Change InterfaceStatisticsBlock timestamp to a Duration. This field follows the same rules as the EPB timestamp. --- src/pcapng/blocks/interface_statistics.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index 0fe4e67..effc27d 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::io::{Result as IoResult, Write}; +use std::time::Duration; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -24,23 +25,23 @@ pub struct InterfaceStatisticsBlock<'a> { pub interface_id: u32, /// Time this statistics refers to. - /// - /// The format of the timestamp is the same already defined in the Enhanced Packet Block. - /// The length of a unit of time is specified by the 'if_tsresol' option of the Interface Description Block referenced by this packet. - pub timestamp: u64, + pub timestamp: Duration, /// Options pub options: Vec>, } impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { - fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 12 { return Err(PcapError::InvalidField("InterfaceStatisticsBlock: block length < 12")); } let interface_id = slice.read_u32::().unwrap(); - let timestamp = slice.read_u64::().unwrap(); + let timestamp_high = slice.read_u32::().unwrap() as u64; + let timestamp_low = slice.read_u32::().unwrap() as u64; + let ts_raw = (timestamp_high << 32) + timestamp_low; + let timestamp = state.decode_timestamp(interface_id, ts_raw)?; let (slice, options) = InterfaceStatisticsOption::opts_from_slice::(slice)?; let block = InterfaceStatisticsBlock { interface_id, timestamp, options }; @@ -48,9 +49,13 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { Ok((slice, block)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u32::(self.interface_id)?; - writer.write_u64::(self.timestamp)?; + let ts_raw = state.encode_timestamp(self.interface_id, self.timestamp)?; + let timestamp_high = (ts_raw >> 32) as u32; + let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; + writer.write_u32::(timestamp_high)?; + writer.write_u32::(timestamp_low)?; let opt_len = InterfaceStatisticsOption::write_opts_to::(&self.options, writer)?; Ok(12 + opt_len) From 7a547676df464b5c738642fa06f73e0002f1d4fb Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 21:49:23 +0000 Subject: [PATCH 05/13] Change PacketBlock timestamp to a Duration. This field follows the same rules as the EPB timestamp. --- src/pcapng/blocks/packet.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index bc837d2..3d953c7 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use std::io::{Result as IoResult, Write}; +use std::time::Duration; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -26,9 +27,8 @@ pub struct PacketBlock<'a> { /// between this packet and the preceding one. pub drop_count: u16, - /// The timestamp is a single 64-bit unsigned integer that represents the number of units of time - /// that have elapsed since 1970-01-01 00:00:00 UTC. - pub timestamp: u64, + /// Time elapsed since 1970-01-01 00:00:00 UTC. + pub timestamp: Duration, /// Number of octets captured from the packet (i.e. the length of the Packet Data field). pub captured_len: u32, @@ -44,14 +44,17 @@ pub struct PacketBlock<'a> { } impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { - fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 20 { return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20")); } let interface_id = slice.read_u16::().unwrap(); let drop_count = slice.read_u16::().unwrap(); - let timestamp = slice.read_u64::().unwrap(); + let timestamp_high = slice.read_u32::().unwrap() as u64; + let timestamp_low = slice.read_u32::().unwrap() as u64; + let ts_raw = (timestamp_high << 32) + timestamp_low; + let timestamp = state.decode_timestamp(interface_id as u32, ts_raw)?; let captured_len = slice.read_u32::().unwrap(); let original_len = slice.read_u32::().unwrap(); @@ -79,10 +82,14 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { Ok((slice, block)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u16::(self.interface_id)?; writer.write_u16::(self.drop_count)?; - writer.write_u64::(self.timestamp)?; + let ts_raw = state.encode_timestamp(self.interface_id as u32, self.timestamp)?; + let timestamp_high = (ts_raw >> 32) as u32; + let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; + writer.write_u32::(timestamp_high)?; + writer.write_u32::(timestamp_low)?; writer.write_u32::(self.captured_len)?; writer.write_u32::(self.original_len)?; writer.write_all(&self.data)?; From 021759e8e304be6ae01bd255d4e010bb873f2cdc Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 22:34:29 +0000 Subject: [PATCH 06/13] Move more work into encode_timestamp and decode_timestamp helpers. --- src/pcapng/blocks/enhanced_packet.rs | 12 ++---------- src/pcapng/blocks/interface_statistics.rs | 11 ++--------- src/pcapng/blocks/packet.rs | 11 ++--------- src/pcapng/state.rs | 23 +++++++++++++++++++---- 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/pcapng/blocks/enhanced_packet.rs b/src/pcapng/blocks/enhanced_packet.rs index 03e5790..d080050 100644 --- a/src/pcapng/blocks/enhanced_packet.rs +++ b/src/pcapng/blocks/enhanced_packet.rs @@ -45,10 +45,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { let interface_id = slice.read_u32::().unwrap(); - let timestamp_high = slice.read_u32::().unwrap() as u64; - let timestamp_low = slice.read_u32::().unwrap() as u64; - let ts_raw = (timestamp_high << 32) + timestamp_low; - let timestamp = state.decode_timestamp(interface_id, ts_raw)?; + let timestamp = state.decode_timestamp::(interface_id, &mut slice)?; let captured_len = slice.read_u32::().unwrap(); let original_len = slice.read_u32::().unwrap(); @@ -80,12 +77,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { writer.write_u32::(self.interface_id)?; - let ts_raw = state.encode_timestamp(self.interface_id, self.timestamp)?; - - let timestamp_high = (ts_raw >> 32) as u32; - let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; - writer.write_u32::(timestamp_high)?; - writer.write_u32::(timestamp_low)?; + state.encode_timestamp::(self.interface_id, self.timestamp, writer)?; writer.write_u32::(self.data.len() as u32)?; writer.write_u32::(self.original_len)?; diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index effc27d..fb968a2 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -38,10 +38,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { } let interface_id = slice.read_u32::().unwrap(); - let timestamp_high = slice.read_u32::().unwrap() as u64; - let timestamp_low = slice.read_u32::().unwrap() as u64; - let ts_raw = (timestamp_high << 32) + timestamp_low; - let timestamp = state.decode_timestamp(interface_id, ts_raw)?; + let timestamp = state.decode_timestamp::(interface_id, &mut slice)?; let (slice, options) = InterfaceStatisticsOption::opts_from_slice::(slice)?; let block = InterfaceStatisticsBlock { interface_id, timestamp, options }; @@ -51,11 +48,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u32::(self.interface_id)?; - let ts_raw = state.encode_timestamp(self.interface_id, self.timestamp)?; - let timestamp_high = (ts_raw >> 32) as u32; - let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; - writer.write_u32::(timestamp_high)?; - writer.write_u32::(timestamp_low)?; + state.encode_timestamp::(self.interface_id, self.timestamp, writer)?; let opt_len = InterfaceStatisticsOption::write_opts_to::(&self.options, writer)?; Ok(12 + opt_len) diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index 3d953c7..be0c754 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -51,10 +51,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { let interface_id = slice.read_u16::().unwrap(); let drop_count = slice.read_u16::().unwrap(); - let timestamp_high = slice.read_u32::().unwrap() as u64; - let timestamp_low = slice.read_u32::().unwrap() as u64; - let ts_raw = (timestamp_high << 32) + timestamp_low; - let timestamp = state.decode_timestamp(interface_id as u32, ts_raw)?; + let timestamp = state.decode_timestamp::(interface_id as u32, &mut slice)?; let captured_len = slice.read_u32::().unwrap(); let original_len = slice.read_u32::().unwrap(); @@ -85,11 +82,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u16::(self.interface_id)?; writer.write_u16::(self.drop_count)?; - let ts_raw = state.encode_timestamp(self.interface_id as u32, self.timestamp)?; - let timestamp_high = (ts_raw >> 32) as u32; - let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; - writer.write_u32::(timestamp_high)?; - writer.write_u32::(timestamp_low)?; + state.encode_timestamp::(self.interface_id as u32, self.timestamp, writer)?; writer.write_u32::(self.captured_len)?; writer.write_u32::(self.original_len)?; writer.write_all(&self.data)?; diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs index c12cd79..4830bb8 100644 --- a/src/pcapng/state.rs +++ b/src/pcapng/state.rs @@ -1,6 +1,9 @@ +use std::io::Write; use std::time::Duration; use byteorder_slice::ByteOrder; +use byteorder_slice::byteorder::WriteBytesExt; +use byteorder_slice::result::ReadSlice; use super::blocks::block_common::{Block, RawBlock}; use super::blocks::interface_description::{InterfaceDescriptionBlock, TsResolution}; @@ -68,7 +71,14 @@ impl<'s> PcapNgState<'s> { } } - pub(crate) fn decode_timestamp(&self, interface_id: u32, ts_raw: u64) -> Result { + pub(crate) fn decode_timestamp(&self, interface_id: u32, slice: &mut &[u8]) -> Result { + let timestamp_high = slice + .read_u32::() + .map_err(|_| PcapError::IncompleteBuffer)? as u64; + let timestamp_low = slice + .read_u32::() + .map_err(|_| PcapError::IncompleteBuffer)? as u64; + let ts_raw = (timestamp_high << 32) + timestamp_low; let ts_resolution = self .ts_resolutions .get(interface_id as usize) @@ -77,15 +87,20 @@ impl<'s> PcapNgState<'s> { Ok(Duration::from_nanos(ts_nanos)) } - pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration) -> std::io::Result { + pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration, writer: &mut W) -> std::io::Result<()> { let ts_resolution = self .ts_resolutions .get(interface_id as usize) .ok_or(std::io::Error::other("Invalid interface ID"))?; let ts_raw = timestamp.as_nanos() / ts_resolution.to_nano_secs() as u128; - ts_raw + let ts_raw: u64 = ts_raw .try_into() .map_err(|_| std::io::Error::other( - "Timestamp too big, please use a bigger timestamp resolution")) + "Timestamp too big, please use a bigger timestamp resolution"))?; + let timestamp_high = (ts_raw >> 32) as u32; + let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; + writer.write_u32::(timestamp_high)?; + writer.write_u32::(timestamp_low)?; + Ok(()) } } From 381093ee7917a088090f1b52e592c7886280b505 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 23:09:37 +0000 Subject: [PATCH 07/13] Pass state and interface ID to PcapNgOption::from_slice(). --- src/pcapng/blocks/enhanced_packet.rs | 4 ++-- src/pcapng/blocks/interface_description.rs | 6 +++--- src/pcapng/blocks/interface_statistics.rs | 4 ++-- src/pcapng/blocks/name_resolution.rs | 6 +++--- src/pcapng/blocks/opt_common.rs | 7 ++++--- src/pcapng/blocks/packet.rs | 4 ++-- src/pcapng/blocks/section_header.rs | 12 ++++++------ 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/pcapng/blocks/enhanced_packet.rs b/src/pcapng/blocks/enhanced_packet.rs index d080050..8b0b700 100644 --- a/src/pcapng/blocks/enhanced_packet.rs +++ b/src/pcapng/blocks/enhanced_packet.rs @@ -60,7 +60,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { let data = &slice[..captured_len as usize]; slice = &slice[tot_len..]; - let (slice, options) = EnhancedPacketOption::opts_from_slice::(slice)?; + let (slice, options) = EnhancedPacketOption::opts_from_slice::(state, Some(interface_id), slice)?; let block = EnhancedPacketBlock { interface_id, timestamp, @@ -126,7 +126,7 @@ pub enum EnhancedPacketOption<'a> { } impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> { - fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { let opt = match code { 1 => EnhancedPacketOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => { diff --git a/src/pcapng/blocks/interface_description.rs b/src/pcapng/blocks/interface_description.rs index d85cc2d..864d89c 100644 --- a/src/pcapng/blocks/interface_description.rs +++ b/src/pcapng/blocks/interface_description.rs @@ -39,7 +39,7 @@ pub struct InterfaceDescriptionBlock<'a> { } impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { - fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 8 { return Err(PcapError::InvalidField("InterfaceDescriptionBlock: block length < 8")); } @@ -52,7 +52,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { } let snaplen = slice.read_u32::().unwrap(); - let (slice, options) = InterfaceDescriptionOption::opts_from_slice::(slice)?; + let (slice, options) = InterfaceDescriptionOption::opts_from_slice::(state, None, slice)?; let block = InterfaceDescriptionBlock { linktype, snaplen, options }; @@ -161,7 +161,7 @@ pub enum InterfaceDescriptionOption<'a> { } impl<'a> PcapNgOption<'a> for InterfaceDescriptionOption<'a> { - fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { let opt = match code { 1 => InterfaceDescriptionOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => InterfaceDescriptionOption::IfName(Cow::Borrowed(std::str::from_utf8(slice)?)), diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index fb968a2..b87ef9e 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -39,7 +39,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { let interface_id = slice.read_u32::().unwrap(); let timestamp = state.decode_timestamp::(interface_id, &mut slice)?; - let (slice, options) = InterfaceStatisticsOption::opts_from_slice::(slice)?; + let (slice, options) = InterfaceStatisticsOption::opts_from_slice::(state, Some(interface_id), slice)?; let block = InterfaceStatisticsBlock { interface_id, timestamp, options }; @@ -104,7 +104,7 @@ pub enum InterfaceStatisticsOption<'a> { } impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { - fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { let opt = match code { 1 => InterfaceStatisticsOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => InterfaceStatisticsOption::IsbStartTime(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), diff --git a/src/pcapng/blocks/name_resolution.rs b/src/pcapng/blocks/name_resolution.rs index a2f8ea4..503982e 100644 --- a/src/pcapng/blocks/name_resolution.rs +++ b/src/pcapng/blocks/name_resolution.rs @@ -25,7 +25,7 @@ pub struct NameResolutionBlock<'a> { } impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { - fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { let mut records = Vec::new(); loop { @@ -38,7 +38,7 @@ impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { } } - let (rem, options) = NameResolutionOption::opts_from_slice::(slice)?; + let (rem, options) = NameResolutionOption::opts_from_slice::(state, None, slice)?; let block = NameResolutionBlock { records, options }; @@ -320,7 +320,7 @@ pub enum NameResolutionOption<'a> { } impl<'a> PcapNgOption<'a> for NameResolutionOption<'a> { - fn from_slice(code: u16, length: u16, slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, slice: &'a [u8]) -> Result { let opt = match code { 1 => NameResolutionOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => NameResolutionOption::NsDnsName(Cow::Borrowed(std::str::from_utf8(slice)?)), diff --git a/src/pcapng/blocks/opt_common.rs b/src/pcapng/blocks/opt_common.rs index 33f5537..273e095 100644 --- a/src/pcapng/blocks/opt_common.rs +++ b/src/pcapng/blocks/opt_common.rs @@ -7,17 +7,18 @@ use byteorder_slice::ByteOrder; use derive_into_owned::IntoOwned; use crate::errors::PcapError; +use crate::pcapng::PcapNgState; /// Common fonctions of the PcapNg options pub(crate) trait PcapNgOption<'a> { /// Parse an option from a slice - fn from_slice(code: u16, length: u16, slice: &'a [u8]) -> Result + fn from_slice(state: &PcapNgState, interface_id: Option, code: u16, length: u16, slice: &'a [u8]) -> Result where Self: std::marker::Sized; /// Parse all options in a block - fn opts_from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Vec), PcapError> + fn opts_from_slice(state: &PcapNgState, interface_id: Option, mut slice: &'a [u8]) -> Result<(&'a [u8], Vec), PcapError> where Self: std::marker::Sized, { @@ -46,7 +47,7 @@ pub(crate) trait PcapNgOption<'a> { } let tmp_slice = &slice[..length]; - let opt = Self::from_slice::(code, length as u16, tmp_slice)?; + let opt = Self::from_slice::(state, interface_id, code, length as u16, tmp_slice)?; // Jump over the padding slice = &slice[length + pad_len..]; diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index be0c754..23ad932 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -65,7 +65,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { let data = &slice[..captured_len as usize]; slice = &slice[tot_len..]; - let (slice, options) = PacketOption::opts_from_slice::(slice)?; + let (slice, options) = PacketOption::opts_from_slice::(state, Some(interface_id as u32), slice)?; let block = PacketBlock { interface_id, drop_count, @@ -123,7 +123,7 @@ pub enum PacketOption<'a> { } impl<'a> PcapNgOption<'a> for PacketOption<'a> { - fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { let opt = match code { 1 => PacketOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => { diff --git a/src/pcapng/blocks/section_header.rs b/src/pcapng/blocks/section_header.rs index df2fe1a..211bb57 100644 --- a/src/pcapng/blocks/section_header.rs +++ b/src/pcapng/blocks/section_header.rs @@ -40,7 +40,7 @@ pub struct SectionHeaderBlock<'a> { } impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { - fn from_slice<'s, B: ByteOrder>(_state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { + fn from_slice<'s, B: ByteOrder>(state: &'s PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { if slice.len() < 16 { return Err(PcapError::InvalidField("SectionHeaderBlock: block length < 16")); } @@ -53,8 +53,8 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { }; let (rem, major_version, minor_version, section_length, options) = match endianness { - Endianness::Big => parse_inner::(slice)?, - Endianness::Little => parse_inner::(slice)?, + Endianness::Big => parse_inner::(state, slice)?, + Endianness::Little => parse_inner::(state, slice)?, }; let block = SectionHeaderBlock { endianness, major_version, minor_version, section_length, options }; @@ -62,11 +62,11 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { return Ok((rem, block)); #[allow(clippy::type_complexity)] - fn parse_inner(mut slice: &[u8]) -> Result<(&[u8], u16, u16, i64, Vec), PcapError> { + fn parse_inner<'a, B: ByteOrder>(state: &PcapNgState, mut slice: &'a [u8]) -> Result<(&'a [u8], u16, u16, i64, Vec>), PcapError> { let maj_ver = slice.read_u16::().unwrap(); let min_ver = slice.read_u16::().unwrap(); let sec_len = slice.read_i64::().unwrap(); - let (rem, opts) = SectionHeaderOption::opts_from_slice::(slice)?; + let (rem, opts) = SectionHeaderOption::opts_from_slice::(state, None, slice)?; Ok((rem, maj_ver, min_ver, sec_len, opts)) } @@ -131,7 +131,7 @@ pub enum SectionHeaderOption<'a> { } impl<'a> PcapNgOption<'a> for SectionHeaderOption<'a> { - fn from_slice(code: u16, length: u16, slice: &'a [u8]) -> Result { + fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, slice: &'a [u8]) -> Result { let opt = match code { 1 => SectionHeaderOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 2 => SectionHeaderOption::Hardware(Cow::Borrowed(std::str::from_utf8(slice)?)), From db0be261dfedaba9016dadaaaadf2eed0be4da24 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 23:23:03 +0000 Subject: [PATCH 08/13] Pass state and interface ID to PcapNgOption::write_to. --- src/pcapng/blocks/enhanced_packet.rs | 4 ++-- src/pcapng/blocks/interface_description.rs | 6 +++--- src/pcapng/blocks/interface_statistics.rs | 4 ++-- src/pcapng/blocks/name_resolution.rs | 6 +++--- src/pcapng/blocks/opt_common.rs | 6 +++--- src/pcapng/blocks/packet.rs | 4 ++-- src/pcapng/blocks/section_header.rs | 6 +++--- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pcapng/blocks/enhanced_packet.rs b/src/pcapng/blocks/enhanced_packet.rs index 8b0b700..85f384f 100644 --- a/src/pcapng/blocks/enhanced_packet.rs +++ b/src/pcapng/blocks/enhanced_packet.rs @@ -84,7 +84,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { writer.write_all(&self.data)?; writer.write_all(&[0_u8; 3][..pad_len])?; - let opt_len = EnhancedPacketOption::write_opts_to::(&self.options, writer)?; + let opt_len = EnhancedPacketOption::write_opts_to::(&self.options, state, Some(self.interface_id), writer)?; Ok(20 + &self.data.len() + pad_len + opt_len) } @@ -152,7 +152,7 @@ impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { EnhancedPacketOption::Comment(a) => a.write_opt_to::(1, writer), EnhancedPacketOption::Flags(a) => a.write_opt_to::(2, writer), diff --git a/src/pcapng/blocks/interface_description.rs b/src/pcapng/blocks/interface_description.rs index 864d89c..7cecf54 100644 --- a/src/pcapng/blocks/interface_description.rs +++ b/src/pcapng/blocks/interface_description.rs @@ -59,12 +59,12 @@ impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { Ok((slice, block)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { writer.write_u16::(u32::from(self.linktype) as u16)?; writer.write_u16::(0)?; writer.write_u32::(self.snaplen)?; - let opt_len = InterfaceDescriptionOption::write_opts_to::(&self.options, writer)?; + let opt_len = InterfaceDescriptionOption::write_opts_to::(&self.options, state, None, writer)?; Ok(8 + opt_len) } @@ -238,7 +238,7 @@ impl<'a> PcapNgOption<'a> for InterfaceDescriptionOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { InterfaceDescriptionOption::Comment(a) => a.write_opt_to::(1, writer), InterfaceDescriptionOption::IfName(a) => a.write_opt_to::(2, writer), diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index b87ef9e..9fb7ca2 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -50,7 +50,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { writer.write_u32::(self.interface_id)?; state.encode_timestamp::(self.interface_id, self.timestamp, writer)?; - let opt_len = InterfaceStatisticsOption::write_opts_to::(&self.options, writer)?; + let opt_len = InterfaceStatisticsOption::write_opts_to::(&self.options, state, Some(self.interface_id), writer)?; Ok(12 + opt_len) } @@ -124,7 +124,7 @@ impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer), InterfaceStatisticsOption::IsbStartTime(a) => a.write_opt_to::(2, writer), diff --git a/src/pcapng/blocks/name_resolution.rs b/src/pcapng/blocks/name_resolution.rs index 503982e..85a673e 100644 --- a/src/pcapng/blocks/name_resolution.rs +++ b/src/pcapng/blocks/name_resolution.rs @@ -45,7 +45,7 @@ impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { Ok((rem, block)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { let mut len = 0; for record in &self.records { @@ -53,7 +53,7 @@ impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { } len += Record::End.write_to::(writer)?; - len += NameResolutionOption::write_opts_to::(&self.options, writer)?; + len += NameResolutionOption::write_opts_to::(&self.options, state, None, writer)?; Ok(len) } @@ -346,7 +346,7 @@ impl<'a> PcapNgOption<'a> for NameResolutionOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { NameResolutionOption::Comment(a) => a.write_opt_to::(1, writer), NameResolutionOption::NsDnsName(a) => a.write_opt_to::(2, writer), diff --git a/src/pcapng/blocks/opt_common.rs b/src/pcapng/blocks/opt_common.rs index 273e095..28c265a 100644 --- a/src/pcapng/blocks/opt_common.rs +++ b/src/pcapng/blocks/opt_common.rs @@ -59,17 +59,17 @@ pub(crate) trait PcapNgOption<'a> { } /// Write the option to a writer - fn write_to(&self, writer: &mut W) -> IoResult; + fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult; /// Write all options in a block - fn write_opts_to(opts: &[Self], writer: &mut W) -> IoResult + fn write_opts_to(opts: &[Self], state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult where Self: std::marker::Sized, { let mut have_opt = false; let mut written = 0; for opt in opts { - written += opt.write_to::(writer)?; + written += opt.write_to::(state, interface_id, writer)?; have_opt = true; } diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index 23ad932..0968542 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -90,7 +90,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { let pad_len = (4 - (self.captured_len as usize % 4)) % 4; writer.write_all(&[0_u8; 3][..pad_len])?; - let opt_len = PacketOption::write_opts_to::(&self.options, writer)?; + let opt_len = PacketOption::write_opts_to::(&self.options, state, Some(self.interface_id as u32), writer)?; Ok(20 + self.data.len() + pad_len + opt_len) } @@ -143,7 +143,7 @@ impl<'a> PcapNgOption<'a> for PacketOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { PacketOption::Comment(a) => a.write_opt_to::(1, writer), PacketOption::Flags(a) => a.write_opt_to::(2, writer), diff --git a/src/pcapng/blocks/section_header.rs b/src/pcapng/blocks/section_header.rs index 211bb57..e18633c 100644 --- a/src/pcapng/blocks/section_header.rs +++ b/src/pcapng/blocks/section_header.rs @@ -72,7 +72,7 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { } } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { match self.endianness { Endianness::Big => writer.write_u32::(0x1A2B3C4D)?, Endianness::Little => writer.write_u32::(0x1A2B3C4D)?, @@ -82,7 +82,7 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { writer.write_u16::(self.minor_version)?; writer.write_i64::(self.section_length)?; - let opt_len = SectionHeaderOption::write_opts_to::(&self.options, writer)?; + let opt_len = SectionHeaderOption::write_opts_to::(&self.options, state, None, writer)?; Ok(16 + opt_len) } @@ -147,7 +147,7 @@ impl<'a> PcapNgOption<'a> for SectionHeaderOption<'a> { Ok(opt) } - fn write_to(&self, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { match self { SectionHeaderOption::Comment(a) => a.write_opt_to::(1, writer), SectionHeaderOption::Hardware(a) => a.write_opt_to::(2, writer), From c18fd657b7907b36d13aeb21c82e4535470a05fa Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Thu, 7 Nov 2024 23:35:27 +0000 Subject: [PATCH 09/13] Change IsbStartTime and IsbEndTime fields to Duration. --- src/pcapng/blocks/interface_statistics.rs | 30 +++++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index 9fb7ca2..c6c2d8c 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -68,10 +68,14 @@ pub enum InterfaceStatisticsOption<'a> { Comment(Cow<'a, str>), /// The isb_starttime option specifies the time the capture started. - IsbStartTime(u64), + /// + /// The time is relative to 1970-01-01 00:00:00 UTC. + IsbStartTime(Duration), /// The isb_endtime option specifies the time the capture ended. - IsbEndTime(u64), + /// + /// The time is relative to 1970-01-01 00:00:00 UTC. + IsbEndTime(Duration), /// The isb_ifrecv option specifies the 64-bit unsigned integer number of packets received from the physical interface /// starting from the beginning of the capture. @@ -104,11 +108,11 @@ pub enum InterfaceStatisticsOption<'a> { } impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { - fn from_slice(_state: &PcapNgState, _interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { + fn from_slice(state: &PcapNgState, interface_id: Option, code: u16, length: u16, mut slice: &'a [u8]) -> Result { let opt = match code { 1 => InterfaceStatisticsOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), - 2 => InterfaceStatisticsOption::IsbStartTime(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), - 3 => InterfaceStatisticsOption::IsbEndTime(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), + 2 => InterfaceStatisticsOption::IsbStartTime(state.decode_timestamp::(interface_id.unwrap(), &mut slice)?), + 3 => InterfaceStatisticsOption::IsbEndTime(state.decode_timestamp::(interface_id.unwrap(), &mut slice)?), 4 => InterfaceStatisticsOption::IsbIfRecv(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 5 => InterfaceStatisticsOption::IsbIfDrop(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 6 => InterfaceStatisticsOption::IsbFilterAccept(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), @@ -124,11 +128,21 @@ impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult { match self { InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer), - InterfaceStatisticsOption::IsbStartTime(a) => a.write_opt_to::(2, writer), - InterfaceStatisticsOption::IsbEndTime(a) => a.write_opt_to::(3, writer), + InterfaceStatisticsOption::IsbStartTime(timestamp) => { + writer.write_u16::(2)?; + writer.write_u16::(8)?; + state.encode_timestamp::(interface_id.unwrap(), *timestamp, writer)?; + Ok(12) + }, + InterfaceStatisticsOption::IsbEndTime(timestamp) => { + writer.write_u16::(3)?; + writer.write_u16::(8)?; + state.encode_timestamp::(interface_id.unwrap(), *timestamp, writer)?; + Ok(12) + }, InterfaceStatisticsOption::IsbIfRecv(a) => a.write_opt_to::(4, writer), InterfaceStatisticsOption::IsbIfDrop(a) => a.write_opt_to::(5, writer), InterfaceStatisticsOption::IsbFilterAccept(a) => a.write_opt_to::(6, writer), From 1d291c4797fce8138f8c880b9676923ede45b948 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 8 Nov 2024 00:00:10 +0000 Subject: [PATCH 10/13] Allow returning PcapError from methods that write block data. This is necessary to return InvalidInterfaceId and TimestampTooBig. --- src/errors.rs | 6 ++++++ src/pcapng/blocks/block_common.rs | 10 +++++----- src/pcapng/blocks/enhanced_packet.rs | 10 +++++----- src/pcapng/blocks/interface_description.rs | 10 +++++----- src/pcapng/blocks/interface_statistics.rs | 10 +++++----- src/pcapng/blocks/name_resolution.rs | 8 ++++---- src/pcapng/blocks/opt_common.rs | 4 ++-- src/pcapng/blocks/packet.rs | 10 +++++----- src/pcapng/blocks/section_header.rs | 10 +++++----- src/pcapng/blocks/simple_packet.rs | 4 ++-- src/pcapng/blocks/systemd_journal_export.rs | 4 ++-- src/pcapng/blocks/unknown.rs | 4 ++-- src/pcapng/writer.rs | 20 +++++++------------- 13 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index f6d1268..7a1d488 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -50,3 +50,9 @@ impl From for PcapError { PcapError::FromUtf8Error(err) } } + +impl From for PcapError { + fn from(err: std::io::Error) -> Self { + PcapError::IoError(err) + } +} diff --git a/src/pcapng/blocks/block_common.rs b/src/pcapng/blocks/block_common.rs index 3c5de05..61f59e2 100644 --- a/src/pcapng/blocks/block_common.rs +++ b/src/pcapng/blocks/block_common.rs @@ -1,7 +1,7 @@ //! Common block types. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -128,7 +128,7 @@ impl<'a> RawBlock<'a> { /// Writes a [`RawBlock`] to a writer. /// /// Uses the endianness of the header. - pub fn write_to(&self, writer: &mut W) -> IoResult { + pub fn write_to(&self, writer: &mut W) -> Result { writer.write_u32::(self.type_)?; writer.write_u32::(self.initial_len)?; writer.write_all(&self.body[..])?; @@ -176,7 +176,7 @@ impl<'a> Block<'a> { } /// Writes a [`Block`] to a writer, using a [`PcapNgState`]. - pub fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + pub fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { return match self { Self::SectionHeader(b) => inner_write_to::(state, b, SECTION_HEADER_BLOCK, writer), Self::InterfaceDescription(b) => inner_write_to::(state, b, INTERFACE_DESCRIPTION_BLOCK, writer), @@ -189,7 +189,7 @@ impl<'a> Block<'a> { Self::Unknown(b) => inner_write_to::(state, b, b.type_, writer), }; - fn inner_write_to<'s, 'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(state: &'s PcapNgState, block: &BL, block_code: u32, writer: &mut W) -> IoResult { + fn inner_write_to<'s, 'a, B: ByteOrder, BL: PcapNgBlock<'a>, W: Write>(state: &'s PcapNgState, block: &BL, block_code: u32, writer: &mut W) -> Result { // Fake write to compute the data length let data_len = block.write_to::(state, &mut std::io::sink()).unwrap(); let pad_len = (4 - (data_len % 4)) % 4; @@ -390,7 +390,7 @@ pub trait PcapNgBlock<'a> { Self: std::marker::Sized; /// Write the content of a block into a writer, using a [`PcapNgState`]. - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult; + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result; /// Convert a block into the [`Block`] enumeration fn into_block(self) -> Block<'a>; diff --git a/src/pcapng/blocks/enhanced_packet.rs b/src/pcapng/blocks/enhanced_packet.rs index 85f384f..4e782ce 100644 --- a/src/pcapng/blocks/enhanced_packet.rs +++ b/src/pcapng/blocks/enhanced_packet.rs @@ -1,7 +1,7 @@ //! Enhanced Packet Block (EPB). use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use std::time::Duration; use byteorder_slice::byteorder::WriteBytesExt; @@ -72,7 +72,7 @@ impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { Ok((slice, block)) } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { let pad_len = (4 - (&self.data.len() % 4)) % 4; writer.write_u32::(self.interface_id)?; @@ -152,8 +152,8 @@ impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> Result { + Ok(match self { EnhancedPacketOption::Comment(a) => a.write_opt_to::(1, writer), EnhancedPacketOption::Flags(a) => a.write_opt_to::(2, writer), EnhancedPacketOption::Hash(a) => a.write_opt_to::(3, writer), @@ -161,6 +161,6 @@ impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> { EnhancedPacketOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), EnhancedPacketOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), EnhancedPacketOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/interface_description.rs b/src/pcapng/blocks/interface_description.rs index 7cecf54..2c5542d 100644 --- a/src/pcapng/blocks/interface_description.rs +++ b/src/pcapng/blocks/interface_description.rs @@ -3,7 +3,7 @@ //! Interface Description Block (IDB). use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -59,7 +59,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> { Ok((slice, block)) } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { writer.write_u16::(u32::from(self.linktype) as u16)?; writer.write_u16::(0)?; writer.write_u32::(self.snaplen)?; @@ -238,8 +238,8 @@ impl<'a> PcapNgOption<'a> for InterfaceDescriptionOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> Result { + Ok(match self { InterfaceDescriptionOption::Comment(a) => a.write_opt_to::(1, writer), InterfaceDescriptionOption::IfName(a) => a.write_opt_to::(2, writer), InterfaceDescriptionOption::IfDescription(a) => a.write_opt_to::(3, writer), @@ -258,7 +258,7 @@ impl<'a> PcapNgOption<'a> for InterfaceDescriptionOption<'a> { InterfaceDescriptionOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), InterfaceDescriptionOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), InterfaceDescriptionOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index c6c2d8c..20be649 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -1,7 +1,7 @@ //! Interface Statistics Block. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use std::time::Duration; use byteorder_slice::byteorder::WriteBytesExt; @@ -46,7 +46,7 @@ impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { Ok((slice, block)) } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { writer.write_u32::(self.interface_id)?; state.encode_timestamp::(self.interface_id, self.timestamp, writer)?; @@ -128,8 +128,8 @@ impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { Ok(opt) } - fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> Result { + Ok(match self { InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer), InterfaceStatisticsOption::IsbStartTime(timestamp) => { writer.write_u16::(2)?; @@ -151,6 +151,6 @@ impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { InterfaceStatisticsOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), InterfaceStatisticsOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), InterfaceStatisticsOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/name_resolution.rs b/src/pcapng/blocks/name_resolution.rs index 85a673e..549087f 100644 --- a/src/pcapng/blocks/name_resolution.rs +++ b/src/pcapng/blocks/name_resolution.rs @@ -45,7 +45,7 @@ impl<'a> PcapNgBlock<'a> for NameResolutionBlock<'a> { Ok((rem, block)) } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { let mut len = 0; for record in &self.records { @@ -346,8 +346,8 @@ impl<'a> PcapNgOption<'a> for NameResolutionOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> Result { + Ok(match self { NameResolutionOption::Comment(a) => a.write_opt_to::(1, writer), NameResolutionOption::NsDnsName(a) => a.write_opt_to::(2, writer), NameResolutionOption::NsDnsIpv4Addr(a) => a.write_opt_to::(3, writer), @@ -355,6 +355,6 @@ impl<'a> PcapNgOption<'a> for NameResolutionOption<'a> { NameResolutionOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), NameResolutionOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), NameResolutionOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/opt_common.rs b/src/pcapng/blocks/opt_common.rs index 28c265a..ff39382 100644 --- a/src/pcapng/blocks/opt_common.rs +++ b/src/pcapng/blocks/opt_common.rs @@ -59,10 +59,10 @@ pub(crate) trait PcapNgOption<'a> { } /// Write the option to a writer - fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult; + fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> Result; /// Write all options in a block - fn write_opts_to(opts: &[Self], state: &PcapNgState, interface_id: Option, writer: &mut W) -> IoResult + fn write_opts_to(opts: &[Self], state: &PcapNgState, interface_id: Option, writer: &mut W) -> Result where Self: std::marker::Sized, { diff --git a/src/pcapng/blocks/packet.rs b/src/pcapng/blocks/packet.rs index 0968542..e1a4716 100644 --- a/src/pcapng/blocks/packet.rs +++ b/src/pcapng/blocks/packet.rs @@ -1,7 +1,7 @@ //! Packet Block. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use std::time::Duration; use byteorder_slice::byteorder::WriteBytesExt; @@ -79,7 +79,7 @@ impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { Ok((slice, block)) } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { writer.write_u16::(self.interface_id)?; writer.write_u16::(self.drop_count)?; state.encode_timestamp::(self.interface_id as u32, self.timestamp, writer)?; @@ -143,14 +143,14 @@ impl<'a> PcapNgOption<'a> for PacketOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> Result { + Ok(match self { PacketOption::Comment(a) => a.write_opt_to::(1, writer), PacketOption::Flags(a) => a.write_opt_to::(2, writer), PacketOption::Hash(a) => a.write_opt_to::(3, writer), PacketOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), PacketOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), PacketOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/section_header.rs b/src/pcapng/blocks/section_header.rs index e18633c..da7d48c 100644 --- a/src/pcapng/blocks/section_header.rs +++ b/src/pcapng/blocks/section_header.rs @@ -1,7 +1,7 @@ //! Section Header Block. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -72,7 +72,7 @@ impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { } } - fn write_to(&self, state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, state: &PcapNgState, writer: &mut W) -> Result { match self.endianness { Endianness::Big => writer.write_u32::(0x1A2B3C4D)?, Endianness::Little => writer.write_u32::(0x1A2B3C4D)?, @@ -147,8 +147,8 @@ impl<'a> PcapNgOption<'a> for SectionHeaderOption<'a> { Ok(opt) } - fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> IoResult { - match self { + fn write_to(&self, _state: &PcapNgState, _interface_id: Option, writer: &mut W) -> Result { + Ok(match self { SectionHeaderOption::Comment(a) => a.write_opt_to::(1, writer), SectionHeaderOption::Hardware(a) => a.write_opt_to::(2, writer), SectionHeaderOption::OS(a) => a.write_opt_to::(3, writer), @@ -156,6 +156,6 @@ impl<'a> PcapNgOption<'a> for SectionHeaderOption<'a> { SectionHeaderOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), SectionHeaderOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), SectionHeaderOption::Unknown(a) => a.write_opt_to::(a.code, writer), - } + }?) } } diff --git a/src/pcapng/blocks/simple_packet.rs b/src/pcapng/blocks/simple_packet.rs index 7467762..bbb5397 100644 --- a/src/pcapng/blocks/simple_packet.rs +++ b/src/pcapng/blocks/simple_packet.rs @@ -1,7 +1,7 @@ //! Simple Packet Block (SPB). use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::byteorder::WriteBytesExt; use byteorder_slice::result::ReadSlice; @@ -37,7 +37,7 @@ impl<'a> PcapNgBlock<'a> for SimplePacketBlock<'a> { Ok((&[], packet)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> Result { writer.write_u32::(self.original_len)?; writer.write_all(&self.data)?; diff --git a/src/pcapng/blocks/systemd_journal_export.rs b/src/pcapng/blocks/systemd_journal_export.rs index 290de33..ed3737e 100644 --- a/src/pcapng/blocks/systemd_journal_export.rs +++ b/src/pcapng/blocks/systemd_journal_export.rs @@ -1,7 +1,7 @@ //! Systemd Journal Export Block. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::ByteOrder; use derive_into_owned::IntoOwned; @@ -24,7 +24,7 @@ impl<'a> PcapNgBlock<'a> for SystemdJournalExportBlock<'a> { Ok((&[], packet)) } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> Result { writer.write_all(&self.journal_entry)?; let pad_len = (4 - (self.journal_entry.len() % 4)) % 4; diff --git a/src/pcapng/blocks/unknown.rs b/src/pcapng/blocks/unknown.rs index ec97b57..66afe39 100644 --- a/src/pcapng/blocks/unknown.rs +++ b/src/pcapng/blocks/unknown.rs @@ -1,7 +1,7 @@ //! Unknown Block. use std::borrow::Cow; -use std::io::{Result as IoResult, Write}; +use std::io::Write; use byteorder_slice::ByteOrder; use derive_into_owned::IntoOwned; @@ -37,7 +37,7 @@ impl<'a> PcapNgBlock<'a> for UnknownBlock<'a> { unimplemented!("UnkknownBlock::::From_slice shouldn't be called") } - fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> IoResult { + fn write_to(&self, _state: &PcapNgState, writer: &mut W) -> Result { writer.write_all(&self.value)?; Ok(self.value.len()) } diff --git a/src/pcapng/writer.rs b/src/pcapng/writer.rs index 39d508c..5a9d7b1 100644 --- a/src/pcapng/writer.rs +++ b/src/pcapng/writer.rs @@ -6,7 +6,7 @@ use super::blocks::block_common::{Block, PcapNgBlock}; use super::blocks::interface_description::InterfaceDescriptionBlock; use super::blocks::section_header::SectionHeaderBlock; use super::{PcapNgState, RawBlock}; -use crate::{Endianness, PcapError, PcapResult}; +use crate::{Endianness, PcapResult}; /// Write a PcapNg to a writer. @@ -81,12 +81,8 @@ impl PcapNgWriter { state.update_for_block(&block)?; match section.endianness { - Endianness::Big => block - .write_to::(&state, &mut writer) - .map_err(PcapError::IoError)?, - Endianness::Little => block - .write_to::(&state, &mut writer) - .map_err(PcapError::IoError)?, + Endianness::Big => block.write_to::(&state, &mut writer)?, + Endianness::Little => block.write_to::(&state, &mut writer)?, }; Ok(Self { state, writer }) @@ -127,8 +123,8 @@ impl PcapNgWriter { self.state.update_for_block(block)?; match self.state.section.endianness { - Endianness::Big => block.write_to::(&self.state, &mut self.writer).map_err(PcapError::IoError), - Endianness::Little => block.write_to::(&self.state, &mut self.writer).map_err(PcapError::IoError), + Endianness::Big => block.write_to::(&self.state, &mut self.writer), + Endianness::Little => block.write_to::(&self.state, &mut self.writer), } } @@ -173,14 +169,12 @@ impl PcapNgWriter { pub fn write_raw_block(&mut self, block: &RawBlock) -> PcapResult { match self.state.section.endianness { Endianness::Big => { - let written = block.write_to::(&mut self.writer) - .map_err(PcapError::IoError)?; + let written = block.write_to::(&mut self.writer)?; self.state.update_for_raw_block::(block)?; Ok(written) }, Endianness::Little => { - let written = block.write_to::(&mut self.writer) - .map_err(PcapError::IoError)?; + let written = block.write_to::(&mut self.writer)?; self.state.update_for_raw_block::(block)?; Ok(written) } From 9a0cc050b2750dfd6004bb543b41733eda07aba9 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 8 Nov 2024 00:05:12 +0000 Subject: [PATCH 11/13] Return correct PcapError variants if encoding timestamps fails. --- src/pcapng/state.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs index 4830bb8..989790a 100644 --- a/src/pcapng/state.rs +++ b/src/pcapng/state.rs @@ -87,16 +87,15 @@ impl<'s> PcapNgState<'s> { Ok(Duration::from_nanos(ts_nanos)) } - pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration, writer: &mut W) -> std::io::Result<()> { + pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration, writer: &mut W) -> Result<(), PcapError> { let ts_resolution = self .ts_resolutions .get(interface_id as usize) - .ok_or(std::io::Error::other("Invalid interface ID"))?; + .ok_or(PcapError::InvalidInterfaceId(interface_id))?; let ts_raw = timestamp.as_nanos() / ts_resolution.to_nano_secs() as u128; let ts_raw: u64 = ts_raw .try_into() - .map_err(|_| std::io::Error::other( - "Timestamp too big, please use a bigger timestamp resolution"))?; + .or(Err(PcapError::TimestampTooBig))?; let timestamp_high = (ts_raw >> 32) as u32; let timestamp_low = (ts_raw & 0xFFFFFFFF) as u32; writer.write_u32::(timestamp_high)?; From 46e70090f5ae945e98799c38f209b9f0b8523449 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 8 Nov 2024 11:23:58 +0000 Subject: [PATCH 12/13] Handle the IfTsOffset option and encode/decode timestamps accordingly. --- src/pcapng/blocks/interface_description.rs | 12 +++++++++++ src/pcapng/state.rs | 23 +++++++++++----------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/pcapng/blocks/interface_description.rs b/src/pcapng/blocks/interface_description.rs index 2c5542d..f0d2c8c 100644 --- a/src/pcapng/blocks/interface_description.rs +++ b/src/pcapng/blocks/interface_description.rs @@ -93,6 +93,18 @@ impl<'a> InterfaceDescriptionBlock<'a> { ts_resol } + + /// Returns the timestamp offset of the interface. + /// If no ts_resolution is set, defaults to 0. + pub fn ts_offset(&self) -> Result { + for opt in &self.options { + if let InterfaceDescriptionOption::IfTsOffset(offset) = opt { + return Ok(*offset) + } + } + + Ok(0) + } } diff --git a/src/pcapng/state.rs b/src/pcapng/state.rs index 989790a..9fbc79d 100644 --- a/src/pcapng/state.rs +++ b/src/pcapng/state.rs @@ -36,8 +36,8 @@ pub struct PcapNgState<'s> { pub(crate) section: SectionHeaderBlock<'s>, /// List of the interfaces of the current section of the pcapng pub(crate) interfaces: Vec>, - /// Timestamp resolutions corresponding to the interfaces - pub(crate) ts_resolutions: Vec, + /// Timestamp resolutions and offsets corresponding to the interfaces + pub(crate) ts_parameters: Vec<(TsResolution, Duration)>, } impl<'s> PcapNgState<'s> { @@ -47,12 +47,12 @@ impl<'s> PcapNgState<'s> { Block::SectionHeader(blk) => { self.section = blk.clone().into_owned(); self.interfaces.clear(); - self.ts_resolutions.clear(); + self.ts_parameters.clear(); }, Block::InterfaceDescription(blk) => { let ts_resolution = blk.ts_resolution()?; - self.ts_resolutions.push(ts_resolution); - + let ts_offset = Duration::from_secs(blk.ts_offset()?); + self.ts_parameters.push((ts_resolution, ts_offset)); self.interfaces.push(blk.clone().into_owned()); }, _ => {}, @@ -79,20 +79,21 @@ impl<'s> PcapNgState<'s> { .read_u32::() .map_err(|_| PcapError::IncompleteBuffer)? as u64; let ts_raw = (timestamp_high << 32) + timestamp_low; - let ts_resolution = self - .ts_resolutions + let (ts_resolution, ts_offset) = self + .ts_parameters .get(interface_id as usize) .ok_or(PcapError::InvalidInterfaceId(interface_id))?; let ts_nanos = ts_raw * ts_resolution.to_nano_secs() as u64; - Ok(Duration::from_nanos(ts_nanos)) + Ok(Duration::from_nanos(ts_nanos) + *ts_offset) } pub(crate) fn encode_timestamp(&self, interface_id: u32, timestamp: Duration, writer: &mut W) -> Result<(), PcapError> { - let ts_resolution = self - .ts_resolutions + let (ts_resolution, ts_offset) = self + .ts_parameters .get(interface_id as usize) .ok_or(PcapError::InvalidInterfaceId(interface_id))?; - let ts_raw = timestamp.as_nanos() / ts_resolution.to_nano_secs() as u128; + let ts_relative = timestamp - *ts_offset; + let ts_raw = ts_relative.as_nanos() / ts_resolution.to_nano_secs() as u128; let ts_raw: u64 = ts_raw .try_into() .or(Err(PcapError::TimestampTooBig))?; From b0dd66ae67cd55fa3f5944e2a5955e1c419fdfa1 Mon Sep 17 00:00:00 2001 From: Martin Ling Date: Fri, 15 Nov 2024 14:36:14 +0000 Subject: [PATCH 13/13] Add a helper method to simplify InterfaceStatisticsOption::write_to(). --- src/pcapng/blocks/interface_statistics.rs | 50 +++++++++++++---------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/src/pcapng/blocks/interface_statistics.rs b/src/pcapng/blocks/interface_statistics.rs index 20be649..91adae6 100644 --- a/src/pcapng/blocks/interface_statistics.rs +++ b/src/pcapng/blocks/interface_statistics.rs @@ -130,27 +130,33 @@ impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { fn write_to(&self, state: &PcapNgState, interface_id: Option, writer: &mut W) -> Result { Ok(match self { - InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer), - InterfaceStatisticsOption::IsbStartTime(timestamp) => { - writer.write_u16::(2)?; - writer.write_u16::(8)?; - state.encode_timestamp::(interface_id.unwrap(), *timestamp, writer)?; - Ok(12) - }, - InterfaceStatisticsOption::IsbEndTime(timestamp) => { - writer.write_u16::(3)?; - writer.write_u16::(8)?; - state.encode_timestamp::(interface_id.unwrap(), *timestamp, writer)?; - Ok(12) - }, - InterfaceStatisticsOption::IsbIfRecv(a) => a.write_opt_to::(4, writer), - InterfaceStatisticsOption::IsbIfDrop(a) => a.write_opt_to::(5, writer), - InterfaceStatisticsOption::IsbFilterAccept(a) => a.write_opt_to::(6, writer), - InterfaceStatisticsOption::IsbOsDrop(a) => a.write_opt_to::(7, writer), - InterfaceStatisticsOption::IsbUsrDeliv(a) => a.write_opt_to::(8, writer), - InterfaceStatisticsOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), - InterfaceStatisticsOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), - InterfaceStatisticsOption::Unknown(a) => a.write_opt_to::(a.code, writer), - }?) + InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer)?, + InterfaceStatisticsOption::IsbStartTime(a) => write_timestamp::(2, a, state, interface_id, writer)?, + InterfaceStatisticsOption::IsbEndTime(a) => write_timestamp::(3, a, state, interface_id, writer)?, + InterfaceStatisticsOption::IsbIfRecv(a) => a.write_opt_to::(4, writer)?, + InterfaceStatisticsOption::IsbIfDrop(a) => a.write_opt_to::(5, writer)?, + InterfaceStatisticsOption::IsbFilterAccept(a) => a.write_opt_to::(6, writer)?, + InterfaceStatisticsOption::IsbOsDrop(a) => a.write_opt_to::(7, writer)?, + InterfaceStatisticsOption::IsbUsrDeliv(a) => a.write_opt_to::(8, writer)?, + InterfaceStatisticsOption::CustomBinary(a) => a.write_opt_to::(a.code, writer)?, + InterfaceStatisticsOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer)?, + InterfaceStatisticsOption::Unknown(a) => a.write_opt_to::(a.code, writer)?, + }) } } + +/// Helper for writing options that contain timestamps. +fn write_timestamp( + code: u16, + timestamp: &Duration, + state: &PcapNgState, + interface_id: Option, + writer: &mut W +) -> Result { + const TIMESTAMP_LENGTH: u16 = 8; + const OPTION_LENGTH: usize = 12; + writer.write_u16::(code)?; + writer.write_u16::(TIMESTAMP_LENGTH)?; + state.encode_timestamp::(interface_id.unwrap(), *timestamp, writer)?; + Ok(OPTION_LENGTH) +}