pcap_parser/pcapng/
option.rs

1use std::borrow::Cow;
2use std::convert::TryFrom;
3use std::fmt;
4
5use nom::combinator::{complete, map_parser};
6use nom::multi::many0;
7use nom::{bytes::streaming::take, error::ParseError};
8use nom::{IResult, Parser as _};
9use rusticata_macros::{align32, newtype_enum};
10
11use crate::endianness::{PcapBE, PcapEndianness, PcapLE};
12
13#[derive(Clone, Copy, Eq, PartialEq)]
14pub struct OptionCode(pub u16);
15
16newtype_enum! {
17impl debug OptionCode {
18    EndOfOpt = 0,
19    Comment = 1,
20    ShbHardware = 2,
21    IfName = 2,
22    IsbStartTime = 2,
23    ShbOs = 3,
24    IfDescription = 3,
25    IsbEndTime = 3,
26    ShbUserAppl = 4,
27    IfIpv4Addr = 4,
28    IsbIfRecv = 4,
29    IsbIfDrop = 5,
30    IfMacAddr = 6,
31    IsbFilterAccept = 6,
32    IfEuiAddr = 7,
33    IsbOsDrop = 7,
34    IfSpeed = 8,
35    IsbUsrDeliv = 8,
36    IfTsresol = 9,
37    IfFilter = 11,
38    IfOs = 12,
39    IfTsoffset = 14,
40    Custom2988 = 2988,
41    Custom2989 = 2989,
42    Custom19372 = 19372,
43    Custom19373 = 19373,
44}
45}
46
47/// The error type which is returned when calling functions on [PcapNGOption]
48#[derive(Debug, PartialEq)]
49pub enum PcapNGOptionError {
50    InvalidLength,
51    Utf8Error,
52}
53
54impl fmt::Display for PcapNGOptionError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            PcapNGOptionError::InvalidLength => write!(f, "Invalid length"),
58            PcapNGOptionError::Utf8Error => write!(f, "Invalid UTF-8 string"),
59        }
60    }
61}
62
63impl std::error::Error for PcapNGOptionError {}
64
65/// A PcapNG option
66#[derive(Debug)]
67pub struct PcapNGOption<'a> {
68    /// The numeric code for the option
69    ///
70    /// Note that codes are relative to the block type, and same codes are used for different
71    /// things (for ex 2 is `shb_hardware` if the block is a SHB, but 2 is `if_name` for an IDB)
72    pub code: OptionCode,
73    /// The declared length for the option
74    ///
75    /// Note that `value.len()` can be greater than `len`, because data is padded to a 32-bit boundary
76    pub len: u16,
77    /// The raw value (including padding) of the option
78    ///
79    /// See [PcapNGOption::as_bytes] to get the value truncated to `len`.
80    pub value: Cow<'a, [u8]>,
81}
82
83impl PcapNGOption<'_> {
84    /// Return a reference to the option value, as raw bytes (not related to the `len` field)
85    #[inline]
86    pub fn value(&self) -> &[u8] {
87        self.value.as_ref()
88    }
89
90    /// Return a reference to the option value, using the `len` field to limit it, or None if length is invalid
91    pub fn as_bytes(&self) -> Result<&[u8], PcapNGOptionError> {
92        let len = usize::from(self.len);
93        if len <= self.value.len() {
94            Ok(&self.value[..len])
95        } else {
96            Err(PcapNGOptionError::InvalidLength)
97        }
98    }
99
100    /// Return the option value interpreted as string
101    ///
102    /// Returns an error if the length of the option is invalid, or if the value is not valid UTF-8.
103    pub fn as_str(&self) -> Result<&str, PcapNGOptionError> {
104        self.as_bytes()
105            .and_then(|b| std::str::from_utf8(b).or(Err(PcapNGOptionError::Utf8Error)))
106    }
107
108    /// Return the option value interpreted as i32, or an error
109    ///
110    /// Option data length and declared must be exactly 4 bytes
111    pub fn as_i32_le(&self) -> Result<i32, PcapNGOptionError> {
112        if self.len != 4 {
113            return Err(PcapNGOptionError::InvalidLength);
114        }
115        <[u8; 4]>::try_from(self.value())
116            .map(i32::from_le_bytes)
117            .or(Err(PcapNGOptionError::InvalidLength))
118    }
119
120    /// Return the option value interpreted as u32, or an error
121    ///
122    /// Option data length and declared must be exactly 4 bytes
123    pub fn as_u32_le(&self) -> Result<u32, PcapNGOptionError> {
124        if self.len != 4 {
125            return Err(PcapNGOptionError::InvalidLength);
126        }
127        <[u8; 4]>::try_from(self.value())
128            .map(u32::from_le_bytes)
129            .or(Err(PcapNGOptionError::InvalidLength))
130    }
131
132    /// Return the option value interpreted as i64, or an error
133    ///
134    /// Option data length and declared must be exactly 8 bytes
135    pub fn as_i64_le(&self) -> Result<i64, PcapNGOptionError> {
136        if self.len != 8 {
137            return Err(PcapNGOptionError::InvalidLength);
138        }
139        <[u8; 8]>::try_from(self.value())
140            .map(i64::from_le_bytes)
141            .or(Err(PcapNGOptionError::InvalidLength))
142    }
143
144    /// Return the option value interpreted as u64, or an error
145    ///
146    /// Option data length and declared must be exactly 8 bytes
147    pub fn as_u64_le(&self) -> Result<u64, PcapNGOptionError> {
148        if self.len != 8 {
149            return Err(PcapNGOptionError::InvalidLength);
150        }
151        <[u8; 8]>::try_from(self.value())
152            .map(u64::from_le_bytes)
153            .or(Err(PcapNGOptionError::InvalidLength))
154    }
155}
156
157/// Parse a pcap-ng Option (little-endian)
158#[inline]
159pub fn parse_option_le<'i, E: ParseError<&'i [u8]>>(
160    i: &'i [u8],
161) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
162    parse_option::<PcapLE, E>(i)
163}
164
165/// Parse a pcap-ng Option (big-endian)
166#[inline]
167pub fn parse_option_be<'i, E: ParseError<&'i [u8]>>(
168    i: &'i [u8],
169) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
170    parse_option::<PcapBE, E>(i)
171}
172
173pub(crate) fn parse_option<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>(
174    i: &'i [u8],
175) -> IResult<&'i [u8], PcapNGOption<'i>, E> {
176    let (i, code) = En::parse_u16(i)?;
177    let (i, len) = En::parse_u16(i)?;
178    let (i, value) = take(align32!(len as u32))(i)?;
179    let option = PcapNGOption {
180        code: OptionCode(code),
181        len,
182        value: Cow::Borrowed(value),
183    };
184    Ok((i, option))
185}
186
187pub(crate) fn opt_parse_options<'i, En: PcapEndianness, E: ParseError<&'i [u8]>>(
188    i: &'i [u8],
189    len: usize,
190    opt_offset: usize,
191) -> IResult<&'i [u8], Vec<PcapNGOption<'i>>, E> {
192    if len > opt_offset {
193        map_parser(
194            take(len - opt_offset),
195            many0(complete(parse_option::<En, E>)),
196        )
197        .parse(i)
198    } else {
199        Ok((i, Vec::new()))
200    }
201}
202
203#[inline]
204pub(crate) fn options_find_map<'a, F, O>(
205    options: &'a [PcapNGOption],
206    code: OptionCode,
207    f: F,
208) -> Option<Result<O, PcapNGOptionError>>
209where
210    F: Fn(&'a PcapNGOption) -> Result<O, PcapNGOptionError>,
211{
212    options
213        .iter()
214        .find_map(|opt| if opt.code == code { Some(f(opt)) } else { None })
215}
216
217pub(crate) fn options_get_as_bytes<'a>(
218    options: &'a [PcapNGOption],
219    code: OptionCode,
220) -> Option<Result<&'a [u8], PcapNGOptionError>> {
221    options_find_map(options, code, |opt| opt.as_bytes())
222}
223
224pub(crate) fn options_get_as_str<'a>(
225    options: &'a [PcapNGOption],
226    code: OptionCode,
227) -> Option<Result<&'a str, PcapNGOptionError>> {
228    options_find_map(options, code, |opt| opt.as_str())
229}
230
231pub(crate) fn options_get_as_u8(
232    options: &[PcapNGOption],
233    code: OptionCode,
234) -> Option<Result<u8, PcapNGOptionError>> {
235    options_find_map(options, code, |opt| {
236        let value = opt.value();
237        if opt.len == 1 && !value.is_empty() {
238            Ok(value[0])
239        } else {
240            Err(PcapNGOptionError::InvalidLength)
241        }
242    })
243}
244
245pub(crate) fn options_get_as_i64_le(
246    options: &[PcapNGOption],
247    code: OptionCode,
248) -> Option<Result<i64, PcapNGOptionError>> {
249    options_find_map(options, code, |opt| opt.as_i64_le())
250}
251
252pub(crate) fn options_get_as_u64_le(
253    options: &[PcapNGOption],
254    code: OptionCode,
255) -> Option<Result<u64, PcapNGOptionError>> {
256    options_find_map(options, code, |opt| opt.as_u64_le())
257}
258
259pub(crate) fn options_get_as_ts(
260    options: &[PcapNGOption],
261    code: OptionCode,
262) -> Option<Result<(u32, u32), PcapNGOptionError>> {
263    options_find_map(options, code, |opt| {
264        let value = opt.value();
265        if opt.len == 8 && value.len() == 8 {
266            let bytes_ts_high =
267                <[u8; 4]>::try_from(&value[..4]).or(Err(PcapNGOptionError::InvalidLength))?;
268            let bytes_ts_low =
269                <[u8; 4]>::try_from(&value[4..8]).or(Err(PcapNGOptionError::InvalidLength))?;
270            let ts_high = u32::from_le_bytes(bytes_ts_high);
271            let ts_low = u32::from_le_bytes(bytes_ts_low);
272            Ok((ts_high, ts_low))
273        } else {
274            Err(PcapNGOptionError::InvalidLength)
275        }
276    })
277}