#![allow(clippy::cast_lossless)]
use std::borrow::Cow;
use std::fmt::Display;
use std::io::Write;
use std::time::Duration;
use byteorder_slice::ByteOrder;
use byteorder_slice::byteorder::WriteBytesExt;
use byteorder_slice::result::ReadSlice;
use derive_into_owned::IntoOwned;
use once_cell::sync::Lazy;
use super::block_common::{Block, PcapNgBlock};
use super::opt_common::{CommonOption, PcapNgOption, WriteOpt};
use crate::DataLink;
use crate::pcapng::PcapNgState;
use crate::pcapng::errors::{BlockContentParseError, ContentValidationError, OptionEntryError, PcapNgWriteError};
#[derive(Clone, Debug, IntoOwned, Eq, PartialEq)]
pub struct InterfaceDescriptionBlock<'a> {
pub linktype: DataLink,
pub snaplen: u32,
pub options: Vec<InterfaceDescriptionOption<'a>>,
}
impl<'a> PcapNgBlock<'a> for InterfaceDescriptionBlock<'a> {
fn from_slice<B: ByteOrder>(
state: &PcapNgState,
mut slice: &'a [u8],
) -> Result<(&'a [u8], Self), BlockContentParseError> {
if slice.len() < 8 {
return Err(BlockContentParseError::BlockContentTooSmall {
needed: 8,
actual: slice.len(),
});
}
let linktype = (slice.read_u16::<B>().unwrap() as u32).into();
let reserved = slice.read_u16::<B>().unwrap();
if reserved != 0 {
return Err(ContentValidationError::InvalidReservedField(reserved).into());
}
let snaplen = slice.read_u32::<B>().unwrap();
let (slice, options) = InterfaceDescriptionOption::opts_from_slice::<B>(state, None, slice)?;
let block = InterfaceDescriptionBlock {
linktype,
snaplen,
options,
};
Ok((slice, block))
}
fn write_to<B: ByteOrder, W: Write>(&self, state: &PcapNgState, writer: &mut W) -> Result<usize, PcapNgWriteError> {
let datalink: u16 = u32::from(self.linktype).try_into().map_err(|_| {
PcapNgWriteError::validation_error(
"InterfaceDescriptionBlock.linktype",
ContentValidationError::InvalidLinktype(self.linktype),
)
})?;
writer.write_u16::<B>(datalink)?;
writer.write_u16::<B>(0)?;
writer.write_u32::<B>(self.snaplen)?;
let opt_len = InterfaceDescriptionOption::write_opts_to::<B, W>(&self.options, state, None, writer)?;
Ok(8 + opt_len)
}
fn into_block(self) -> Block<'a> {
Block::InterfaceDescription(self)
}
}
impl<'a> InterfaceDescriptionBlock<'a> {
pub fn new(linktype: DataLink, snaplen: u32) -> Self {
Self {
linktype,
snaplen,
options: vec![],
}
}
pub fn ts_resolution(&self) -> InterfaceTsResolution {
let mut ts_resol = InterfaceTsResolution::default();
for opt in &self.options {
if let InterfaceDescriptionOption::IfTsResol(resol) = opt {
ts_resol = *resol;
break;
}
}
ts_resol
}
pub fn ts_offset(&self) -> i64 {
for opt in &self.options {
if let InterfaceDescriptionOption::IfTsOffset(offset) = opt {
return *offset;
}
}
0
}
}
#[derive(Clone, Debug, IntoOwned, Eq, PartialEq)]
pub enum InterfaceDescriptionOption<'a> {
IfName(Cow<'a, str>),
IfDescription(Cow<'a, str>),
IfIpv4Addr(Cow<'a, [u8]>),
IfIpv6Addr(Cow<'a, [u8]>),
IfMacAddr(Cow<'a, [u8]>),
IfEuIAddr(u64),
IfSpeed(u64),
IfTsResol(InterfaceTsResolution),
IfTzone(u32),
IfFilter(Cow<'a, [u8]>),
IfOs(Cow<'a, str>),
IfFcsLen(u8),
IfTsOffset(i64),
IfHardware(Cow<'a, str>),
Common(CommonOption<'a>),
}
impl InterfaceDescriptionOption<'_> {
const IF_NAME: u16 = 2;
const IF_DESCRIPTION: u16 = 3;
const IF_IPV4_ADDR: u16 = 4;
const IF_IPV6_ADDR: u16 = 5;
const IF_MAC_ADDR: u16 = 6;
const IF_EU_ADDR: u16 = 7;
const IF_SPEED: u16 = 8;
const IF_TS_RESOL: u16 = 9;
const IF_T_ZONE: u16 = 10;
const IF_FILTER: u16 = 11;
const IF_OS: u16 = 12;
const IF_FCS_LEN: u16 = 13;
const IF_TS_OFFSET: u16 = 14;
const IF_HARDWARE: u16 = 15;
}
impl<'a> PcapNgOption<'a> for InterfaceDescriptionOption<'a> {
fn from_slice<B: ByteOrder>(
_state: &PcapNgState,
_interface_id: Option<u32>,
code: u16,
mut slice: &'a [u8],
) -> Result<Self, OptionEntryError> {
let opt = match code {
Self::IF_NAME => InterfaceDescriptionOption::IfName(Cow::Borrowed(std::str::from_utf8(slice)?)),
Self::IF_DESCRIPTION => {
InterfaceDescriptionOption::IfDescription(Cow::Borrowed(std::str::from_utf8(slice)?))
}
Self::IF_IPV4_ADDR => {
if slice.len() != 8 {
return Err(OptionEntryError::WrongSize {
expected: 8,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfIpv4Addr(Cow::Borrowed(slice))
}
Self::IF_IPV6_ADDR => {
if slice.len() != 17 {
return Err(OptionEntryError::WrongSize {
expected: 17,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfIpv6Addr(Cow::Borrowed(slice))
}
Self::IF_MAC_ADDR => {
if slice.len() != 6 {
return Err(OptionEntryError::WrongSize {
expected: 6,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfMacAddr(Cow::Borrowed(slice))
}
Self::IF_EU_ADDR => {
if slice.len() != 8 {
return Err(OptionEntryError::WrongSize {
expected: 8,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfEuIAddr(slice.read_u64::<B>().unwrap())
}
Self::IF_SPEED => {
if slice.len() != 8 {
return Err(OptionEntryError::WrongSize {
expected: 8,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfSpeed(slice.read_u64::<B>().unwrap())
}
Self::IF_TS_RESOL => {
if slice.len() != 1 {
return Err(OptionEntryError::WrongSize {
expected: 1,
actual: slice.len(),
});
}
let raw_resol = slice.read_u8().unwrap();
let resol = InterfaceTsResolution::from_u8(raw_resol)?;
InterfaceDescriptionOption::IfTsResol(resol)
}
Self::IF_T_ZONE => {
if slice.len() != 4 {
return Err(OptionEntryError::WrongSize {
expected: 4,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfTzone(slice.read_u32::<B>().unwrap())
}
Self::IF_FILTER => {
if slice.is_empty() {
return Err(OptionEntryError::WrongSize {
expected: 0,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfFilter(Cow::Borrowed(slice))
}
Self::IF_OS => InterfaceDescriptionOption::IfOs(Cow::Borrowed(std::str::from_utf8(slice)?)),
Self::IF_FCS_LEN => {
if slice.len() != 1 {
return Err(OptionEntryError::WrongSize {
expected: 1,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfFcsLen(slice.read_u8().unwrap())
}
Self::IF_TS_OFFSET => {
if slice.len() != 8 {
return Err(OptionEntryError::WrongSize {
expected: 8,
actual: slice.len(),
});
}
InterfaceDescriptionOption::IfTsOffset(slice.read_i64::<B>().unwrap())
}
Self::IF_HARDWARE => InterfaceDescriptionOption::IfHardware(Cow::Borrowed(std::str::from_utf8(slice)?)),
_ => InterfaceDescriptionOption::Common(CommonOption::new::<B>(code, slice)?),
};
Ok(opt)
}
fn write_to<B: ByteOrder, W: Write>(
&self,
_state: &PcapNgState,
_interface_id: Option<u32>,
writer: &mut W,
) -> Result<usize, PcapNgWriteError> {
match self {
InterfaceDescriptionOption::IfName(a) => a.write_opt::<B, W>(Self::IF_NAME, writer),
InterfaceDescriptionOption::IfDescription(a) => a.write_opt::<B, W>(Self::IF_DESCRIPTION, writer),
InterfaceDescriptionOption::IfIpv4Addr(a) => a.write_opt::<B, W>(Self::IF_IPV4_ADDR, writer),
InterfaceDescriptionOption::IfIpv6Addr(a) => a.write_opt::<B, W>(Self::IF_IPV6_ADDR, writer),
InterfaceDescriptionOption::IfMacAddr(a) => a.write_opt::<B, W>(Self::IF_MAC_ADDR, writer),
InterfaceDescriptionOption::IfEuIAddr(a) => a.write_opt::<B, W>(Self::IF_EU_ADDR, writer),
InterfaceDescriptionOption::IfSpeed(a) => a.write_opt::<B, W>(Self::IF_SPEED, writer),
InterfaceDescriptionOption::IfTsResol(a) => a.to_u8().write_opt::<B, W>(Self::IF_TS_RESOL, writer),
InterfaceDescriptionOption::IfTzone(a) => a.write_opt::<B, W>(Self::IF_T_ZONE, writer),
InterfaceDescriptionOption::IfFilter(a) => a.write_opt::<B, W>(Self::IF_FILTER, writer),
InterfaceDescriptionOption::IfOs(a) => a.write_opt::<B, W>(Self::IF_OS, writer),
InterfaceDescriptionOption::IfFcsLen(a) => a.write_opt::<B, W>(Self::IF_FCS_LEN, writer),
InterfaceDescriptionOption::IfTsOffset(a) => a.write_opt::<B, W>(Self::IF_TS_OFFSET, writer),
InterfaceDescriptionOption::IfHardware(a) => a.write_opt::<B, W>(Self::IF_HARDWARE, writer),
InterfaceDescriptionOption::Common(a) => a.write_opt::<B, W>(a.code(), writer),
}
}
fn code_name(code: u16) -> &'static str {
match code {
Self::IF_NAME => "IfName",
Self::IF_DESCRIPTION => "IfDescription",
Self::IF_IPV4_ADDR => "IfIpv4Addr",
Self::IF_IPV6_ADDR => "IfIpv6Addr",
Self::IF_MAC_ADDR => "IfMacAddr",
Self::IF_EU_ADDR => "IfEuIAddr",
Self::IF_SPEED => "IfSpeed",
Self::IF_TS_RESOL => "IfTsResol",
Self::IF_T_ZONE => "IfTzone",
Self::IF_FILTER => "IfFilter",
Self::IF_OS => "IfOs",
Self::IF_FCS_LEN => "IfFcsLen",
Self::IF_TS_OFFSET => "IfTsOffset",
Self::IF_HARDWARE => "IfHardware",
_ => CommonOption::code_name(code),
}
}
}
static TS_RESOL_DEC_TO_DURATION: Lazy<Vec<u128>> = Lazy::new(|| (0..10).map(|i| 10_u128.pow(9 - i)).collect());
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct InterfaceTsResolution {
is_bin: bool,
resol: u8,
}
impl InterfaceTsResolution {
pub const SEC: Self = InterfaceTsResolution {
is_bin: false,
resol: 0,
};
pub const MILLI: Self = InterfaceTsResolution {
is_bin: false,
resol: 3,
};
pub const MICRO: Self = InterfaceTsResolution {
is_bin: false,
resol: 6,
};
pub const NANO: Self = InterfaceTsResolution {
is_bin: false,
resol: 9,
};
pub fn new(is_bin: bool, resol: u8) -> Result<Self, ContentValidationError> {
if is_bin && resol > 29 {
let resol_enc = InterfaceTsResolution { is_bin, resol }.to_u8();
return Err(ContentValidationError::InvalidTsResolution(resol_enc, is_bin, resol));
}
if !is_bin && resol > 9 {
let resol_enc = InterfaceTsResolution { is_bin, resol }.to_u8();
return Err(ContentValidationError::InvalidTsResolution(resol_enc, is_bin, resol));
}
Ok(InterfaceTsResolution { is_bin, resol })
}
pub fn from_u8(ts_resol: u8) -> Result<Self, ContentValidationError> {
let is_bin = (ts_resol >> 7) & 0x1 == 1;
let resol = ts_resol & 0x7F;
Self::new(is_bin, resol)
}
pub fn to_u8(self) -> u8 {
(self.is_bin as u8) << 7 | self.resol
}
pub fn decode_timestamp(&self, ts_raw: u64) -> Duration {
let timestamp_ns = if self.is_bin {
(ts_raw as u128 * 1_000_000_000_u128) >> self.resol
} else {
ts_raw as u128 * TS_RESOL_DEC_TO_DURATION[self.resol as usize]
};
Duration::from_nanos_u128(timestamp_ns)
}
pub fn encode_timestamp(&self, timestamp: Duration) -> Result<u64, ContentValidationError> {
let timestamp_ns = timestamp.as_nanos();
let ts = if self.is_bin {
timestamp_ns
.checked_shl(self.resol.into())
.ok_or(ContentValidationError::FailedToEncodeTimestamp {
timestamp,
resolution: *self,
offset: 0,
})?
/ 1_000_000_000_u128
} else {
timestamp_ns / TS_RESOL_DEC_TO_DURATION[self.resol as usize]
};
ts.try_into()
.map_err(|_| ContentValidationError::FailedToEncodeTimestamp {
timestamp,
resolution: *self,
offset: 0,
})
}
pub fn is_bin(&self) -> bool {
self.is_bin
}
pub fn resolution(&self) -> u8 {
self.resol
}
}
impl Default for InterfaceTsResolution {
fn default() -> Self {
Self::MICRO
}
}
impl Display for InterfaceTsResolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_bin {
write!(f, "2^-{}s", self.resol)
} else {
write!(f, "10^-{}s", self.resol)
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{ContentValidationError, InterfaceTsResolution};
#[test]
fn binary_timestamp_roundtrip_loses_at_most_one_tick_min() {
let resolution = InterfaceTsResolution::new(true, 10).unwrap();
let mut raw = 1;
for _ in 0..100 {
let ts = resolution.decode_timestamp(raw);
raw = resolution.encode_timestamp(ts).unwrap();
}
assert_eq!(raw, 0);
}
#[test]
fn binary_timestamp_roundtrip_loses_at_most_one_tick_max() {
let resolution = InterfaceTsResolution::new(true, 10).unwrap();
let mut raw = u64::MAX;
for _ in 0..100 {
let ts = resolution.decode_timestamp(raw);
raw = resolution.encode_timestamp(ts).unwrap();
}
assert_eq!(raw, 18446744073709551614);
}
#[test]
fn binary_timestamp_encode_overflow_returns_invalid_timestamp() {
let resolution = InterfaceTsResolution::new(true, 29).unwrap();
let error = resolution.encode_timestamp(Duration::MAX).unwrap_err();
assert!(matches!(
error,
ContentValidationError::FailedToEncodeTimestamp {
timestamp,
resolution: error_resolution,
offset,
} if timestamp == Duration::MAX
&& error_resolution == resolution
&& offset == 0
));
}
}