use std::borrow::Cow;
use std::convert::TryFrom;
use std::fmt;
use nom::combinator::{complete, map_parser};
use nom::multi::many0;
use nom::{bytes::streaming::take, error::ParseError};
use nom::{IResult, Parser as _};
use rusticata_macros::{align32, newtype_enum};
use crate::endianness::{PcapBE, PcapEndianness, PcapLE};
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct OptionCode(pub u16);
newtype_enum! {
impl debug OptionCode {
EndOfOpt = 0,
Comment = 1,
ShbHardware = 2,
IfName = 2,
IsbStartTime = 2,
ShbOs = 3,
IfDescription = 3,
IsbEndTime = 3,
ShbUserAppl = 4,
IfIpv4Addr = 4,
IsbIfRecv = 4,
IsbIfDrop = 5,
IfMacAddr = 6,
IsbFilterAccept = 6,
IfEuiAddr = 7,
IsbOsDrop = 7,
IfSpeed = 8,
IsbUsrDeliv = 8,
IfTsresol = 9,
IfFilter = 11,
IfOs = 12,
IfTsoffset = 14,
Custom2988 = 2988,
Custom2989 = 2989,
Custom19372 = 19372,
Custom19373 = 19373,
}
}
#[derive(Debug, PartialEq)]
pub enum PcapNGOptionError {
InvalidLength,
Utf8Error,
}
impl fmt::Display for PcapNGOptionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PcapNGOptionError::InvalidLength => write!(f, "Invalid length"),
PcapNGOptionError::Utf8Error => write!(f, "Invalid UTF-8 string"),
}
}
}
impl std::error::Error for PcapNGOptionError {}
#[derive(Debug)]
pub struct PcapNGOption<'a> {
pub code: OptionCode,
pub len: u16,
pub value: Cow<'a, [u8]>,
}
impl PcapNGOption<'_> {
#[inline]
pub fn value(&self) -> &[u8] {
self.value.as_ref()
}
pub fn as_bytes(&self) -> Result<&[u8], PcapNGOptionError> {
let len = usize::from(self.len);
if len <= self.value.len() {
Ok(&self.value[..len])
} else {
Err(PcapNGOptionError::InvalidLength)
}
}
pub fn as_str(&self) -> Result<&str, PcapNGOptionError> {
self.as_bytes()
.and_then(|b| std::str::from_utf8(b).or(Err(PcapNGOptionError::Utf8Error)))
}
pub fn as_i32_le(&self) -> Result<i32, PcapNGOptionError> {
if self.len != 4 {
return Err(PcapNGOptionError::InvalidLength);
}
<[u8; 4]>::try_from(self.value())
.map(i32::from_le_bytes)
.or(Err(PcapNGOptionError::InvalidLength))
}
pub fn as_u32_le(&self) -> Result<u32, PcapNGOptionError> {
if self.len != 4 {
return Err(PcapNGOptionError::InvalidLength);
}
<[u8; 4]>::try_from(self.value())
.map(u32::from_le_bytes)
.or(Err(PcapNGOptionError::InvalidLength))
}
pub fn as_i64_le(&self) -> Result<i64, PcapNGOptionError> {
if self.len != 8 {
return Err(PcapNGOptionError::InvalidLength);
}
<[u8; 8]>::try_from(self.value())
.map(i64::from_le_bytes)
.or(Err(PcapNGOptionError::InvalidLength))
}
pub fn as_u64_le(&self) -> Result<u64, PcapNGOptionError> {
if self.len != 8 {
return Err(PcapNGOptionError::InvalidLength);
}
<[u8; 8]>::try_from(self.value())
.map(u64::from_le_bytes)
.or(Err(PcapNGOptionError::InvalidLength))
}
}
#[inline]
pub fn parse_option_le<'i, E: ParseError<&'i [u8]>>(
i: &'i [u8],
) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
parse_option::<PcapLE, E>(i)
}
#[inline]
pub fn parse_option_be<'i, E: ParseError<&'i [u8]>>(
i: &'i [u8],
) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
parse_option::<PcapBE, E>(i)
}
pub(crate) fn parse_option<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>(
i: &'i [u8],
) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
let (i, code) = En::parse_u16(i)?;
let (i, len) = En::parse_u16(i)?;
let (i, value) = take(align32!(len as u32))(i)?;
let option = PcapNGOption {
code: OptionCode(code),
len,
value: Cow::Borrowed(value),
};
Ok((i, option))
}
pub(crate) fn opt_parse_options<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>(
i: &'i [u8],
len: usize,
opt_offset: usize,
) -> IResult<&'i [u8], Vec<PcapNGOption<'i>>, E> {
if len > opt_offset {
map_parser(
take(len - opt_offset),
many0(complete(parse_option::<En, E>)),
)
.parse(i)
} else {
Ok((i, Vec::new()))
}
}
#[inline]
pub(crate) fn options_find_map<'a, F, O>(
options: &'a [PcapNGOption],
code: OptionCode,
f: F,
) -> Option<Result<O, PcapNGOptionError>>
where
F: Fn(&'a PcapNGOption) -> Result<O, PcapNGOptionError>,
{
options
.iter()
.find_map(|opt| if opt.code == code { Some(f(opt)) } else { None })
}
pub(crate) fn options_get_as_bytes<'a>(
options: &'a [PcapNGOption],
code: OptionCode,
) -> Option<Result<&'a [u8], PcapNGOptionError>> {
options_find_map(options, code, |opt| opt.as_bytes())
}
pub(crate) fn options_get_as_str<'a>(
options: &'a [PcapNGOption],
code: OptionCode,
) -> Option<Result<&'a str, PcapNGOptionError>> {
options_find_map(options, code, |opt| opt.as_str())
}
pub(crate) fn options_get_as_u8(
options: &[PcapNGOption],
code: OptionCode,
) -> Option<Result<u8, PcapNGOptionError>> {
options_find_map(options, code, |opt| {
let value = opt.value();
if opt.len == 1 && !value.is_empty() {
Ok(value[0])
} else {
Err(PcapNGOptionError::InvalidLength)
}
})
}
pub(crate) fn options_get_as_i64_le(
options: &[PcapNGOption],
code: OptionCode,
) -> Option<Result<i64, PcapNGOptionError>> {
options_find_map(options, code, |opt| opt.as_i64_le())
}
pub(crate) fn options_get_as_u64_le(
options: &[PcapNGOption],
code: OptionCode,
) -> Option<Result<u64, PcapNGOptionError>> {
options_find_map(options, code, |opt| opt.as_u64_le())
}
pub(crate) fn options_get_as_ts(
options: &[PcapNGOption],
code: OptionCode,
) -> Option<Result<(u32, u32), PcapNGOptionError>> {
options_find_map(options, code, |opt| {
let value = opt.value();
if opt.len == 8 && value.len() == 8 {
let bytes_ts_high =
<[u8; 4]>::try_from(&value[..4]).or(Err(PcapNGOptionError::InvalidLength))?;
let bytes_ts_low =
<[u8; 4]>::try_from(&value[4..8]).or(Err(PcapNGOptionError::InvalidLength))?;
let ts_high = u32::from_le_bytes(bytes_ts_high);
let ts_low = u32::from_le_bytes(bytes_ts_low);
Ok((ts_high, ts_low))
} else {
Err(PcapNGOptionError::InvalidLength)
}
})
}