rusty_pcap/pcap_ng/
options.rs

1//! Block Options for pcap-ng files
2//!
3//! See [3.5 Options](https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-options) for more details
4use crate::{
5    byte_order::{ByteOrder, ReadExt, WriteExt},
6    pcap_ng::pad_length_to_32_bytes,
7};
8use std::io::{Read, Write};
9use thiserror::Error;
10
11macro_rules! define_options_enum {
12    (
13        $(#[$docs:meta])*
14        enum $name:ident {
15            $(
16                $(#[$variant_docs:meta])*
17                $variant:ident = $value:literal,
18            )*
19        }
20    ) => {
21        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22        $(#[$docs])*
23        pub enum $name {
24            $(
25                $(#[$variant_docs])*
26                $variant = $value,
27            )*
28        }
29
30        impl TryFrom<u16> for $name {
31            type Error = ();
32
33            fn try_from(value: u16) -> Result<Self, Self::Error> {
34                match value {
35                    $(
36                        $value => Ok(Self::$variant),
37                    )*
38                    _ => return Err(()),
39                }
40            }
41        }
42
43    };
44}
45pub(crate) use define_options_enum;
46define_options_enum! {
47    /// Standard options for pcap-ng blocks
48    ///
49    /// [3.5 Options](https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#figure-7)
50    enum StandardOptions{
51        /// The opt_endofopt option is used to indicate the end of the options for a block.
52        ///
53        /// This option is not actually present in the options list, but is used to indicate the end of the options.
54        EndOfOpt = 0,
55        /// The opt_comment option is a UTF-8 string containing human-readable comment text that is associated to the current block.
56        /// Line separators SHOULD be a carriage-return + linefeed ('\r\n') or just linefeed ('\n'); either form may appear and be considered a line separator.
57        /// The string is not zero-terminated
58        Comment = 1,
59        /// This option code identifies a Custom Option containing a UTF-8 string in the Custom Data portion.
60        ///  The string is not zero-terminated. This Custom Option can be safely copied to a new file if the pcapng file is manipulated by an application;
61        /// otherwise 19372 should be used instead.
62        CustomUTF8Copied = 2988,
63        ///This option code identifies a Custom Option containing binary octets in the Custom Data portion.
64        /// This Custom Option can be safely copied to a new file if the pcapng file is manipulated by an application; otherwise 19372 should be used instead.
65        CustomBinaryCopied = 2989,
66        /// This option code identifies a Custom Option containing a UTF-8 string in the Custom Data portion.
67        /// The string is not zero-terminated.
68        /// This Custom Option should not be copied to a new file if the pcapng file is manipulated by an application.
69        CustomUTF8NotCopied = 19372,
70        /// This option code identifies a Custom Option containing binary octets in the Custom Data portion.
71        /// This Custom Option should not be copied to a new file if the pcapng file is manipulated by an application.
72        CustomBinaryNotCopied = 19373,
73    }
74}
75impl StandardOptions {
76    /// Returns true if the option is a custom option
77    pub fn is_custom(&self) -> bool {
78        matches!(
79            self,
80            Self::CustomBinaryCopied
81                | Self::CustomBinaryNotCopied
82                | Self::CustomUTF8Copied
83                | Self::CustomUTF8NotCopied
84        )
85    }
86}
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct BlockOption {
89    /// Option code
90    pub code: u16,
91    /// Length of the option value in bytes
92    pub length: u16,
93    /// Private Enterprise Number (PEN)
94    ///
95    /// Only present if the option is a custom option
96    pub pen: Option<u32>,
97    /// The value of the option
98    pub value: Vec<u8>,
99}
100#[derive(Debug, Clone, PartialEq, Eq, Error)]
101pub enum InvalidOption {
102    #[error("Custom option requires a Private Enterprise Number (PEN)")]
103    CustomRequiresPen,
104    #[error(
105        "Option code {0} is not a custom option, but a Private Enterprise Number (PEN) was provided"
106    )]
107    UnexpectedPen(u16),
108}
109impl BlockOption {
110    /// Creates a new BlockOption
111    ///
112    /// Pen can only be set if the option code is a custom option.
113    pub fn new(
114        option_code: u16,
115        pen: Option<u32>,
116        option_value: impl Into<Vec<u8>>,
117    ) -> Result<Self, InvalidOption> {
118        if let Ok(option_code) = StandardOptions::try_from(option_code) {
119            if option_code.is_custom() && pen.is_none() {
120                return Err(InvalidOption::CustomRequiresPen);
121            } else if !option_code.is_custom() && pen.is_some() {
122                return Err(InvalidOption::UnexpectedPen(option_code as u16));
123            }
124        } else if pen.is_some() {
125            return Err(InvalidOption::UnexpectedPen(option_code));
126        }
127        let option_value = option_value.into();
128        let option_length = option_value.len() as u16;
129        let result = Self {
130            code: option_code,
131            length: option_length,
132            pen,
133            value: option_value,
134        };
135        Ok(result)
136    }
137    pub fn padding_length(&self) -> usize {
138        pad_length_to_32_bytes(self.length as usize) - self.length as usize
139    }
140}
141#[derive(Debug, Error)]
142pub enum OptionParseError {
143    #[error(transparent)]
144    IO(#[from] std::io::Error),
145    #[error(transparent)]
146    UnexpectedSize(#[from] crate::byte_order::UnexpectedSize),
147}
148/// Represents a collection of options for a block
149///
150/// [3.5 Options](https://www.ietf.org/archive/id/draft-tuexen-opsawg-pcapng-03.html#name-options)
151#[derive(Debug, Clone, PartialEq, Eq, Default)]
152pub struct BlockOptions(pub Vec<BlockOption>);
153impl BlockOptions {
154    fn read_option_header<R: Read, B: ByteOrder>(
155        reader: &mut R,
156        byte_order: B,
157    ) -> Result<Option<(u16, u16, Option<u32>)>, OptionParseError> {
158        let option_code = reader.read_u16(byte_order)?;
159        let option_length = reader.read_u16(byte_order)?;
160
161        if option_code == 0 && option_length == 0 {
162            return Ok(None); // No more options to read
163        }
164
165        let pen = match StandardOptions::try_from(option_code) {
166            Ok(option) if option.is_custom() => Some(reader.read_u32(byte_order)?),
167            _ => None, // Skip unknown options
168        };
169        Ok(Some((option_code, option_length, pen)))
170    }
171
172    pub fn read_in<R: Read, B: ByteOrder>(
173        &mut self,
174        reader: &mut R,
175        byte_order: B,
176    ) -> Result<(), OptionParseError> {
177        loop {
178            let Some((option_code, option_length, pen)) =
179                Self::read_option_header(reader, byte_order)?
180            else {
181                break; // No more options to read
182            };
183
184            let padded_length = pad_length_to_32_bytes(option_length as usize);
185            let mut option_value = vec![0u8; padded_length];
186            reader.read_exact(&mut option_value)?;
187            option_value.truncate(option_length as usize);
188
189            self.0.push(BlockOption {
190                code: option_code,
191                length: option_length,
192                pen,
193                value: option_value,
194            });
195        }
196        Ok(())
197    }
198    pub fn read<R: Read, B: ByteOrder>(
199        reader: &mut R,
200        byte_order: B,
201    ) -> Result<Self, OptionParseError> {
202        let mut options = Self::default();
203        options.read_in(reader, byte_order)?;
204        Ok(options)
205    }
206
207    pub fn read_option<R: Read, B: ByteOrder>(
208        reader: &mut R,
209        byte_order: B,
210    ) -> Result<Option<Self>, OptionParseError> {
211        let mut options = Self::default();
212        options.read_in(reader, byte_order)?;
213        if options.0.is_empty() {
214            return Ok(None);
215        }
216        Ok(Some(options))
217    }
218
219    pub fn write<W: Write>(
220        &self,
221        writer: &mut W,
222        byte_order: impl ByteOrder,
223    ) -> Result<(), std::io::Error> {
224        for option in &self.0 {
225            writer.write_u16(option.code, byte_order)?;
226            writer.write_u16(option.length, byte_order)?;
227
228            if let Some(pen) = option.pen {
229                writer.write_u32(pen, byte_order)?;
230            }
231            writer.write_all(&option.value)?;
232            // Pad to 32 bytes
233            let padding = option.padding_length();
234            if padding > 0 {
235                writer.write_all(&vec![0; padding])?;
236            }
237        }
238        writer.write_u16(0, byte_order)?; // End of options
239        writer.write_u16(0, byte_order)?; // End of options length
240        Ok(())
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247    use crate::byte_order::LittleEndian;
248
249    #[test]
250    fn test_block_options_read_write() {
251        let option_one = BlockOption::new(1, None, b"Test comment").unwrap();
252        assert_eq!(option_one.code, 1);
253        assert_eq!(option_one.length, 12);
254        assert_eq!(option_one.value, b"Test comment");
255        assert!(option_one.pen.is_none());
256        assert_eq!(option_one.padding_length(), 0);
257
258        let option_two = BlockOption::new(2, None, b"Custom data").unwrap();
259        assert_eq!(option_two.code, 2);
260        assert_eq!(option_two.length, 11);
261        assert_eq!(option_two.value, b"Custom data");
262        assert_eq!(option_two.pen, None);
263        assert_eq!(option_two.padding_length(), 1);
264
265        let options = BlockOptions(vec![option_one, option_two]);
266
267        let mut buffer = Vec::new();
268        options.write(&mut buffer, LittleEndian).unwrap();
269        let expected_result = [
270            1, 0, 12, 0, 84, 101, 115, 116, 32, 99, 111, 109, 109, 101, 110, 116, 2, 0, 11, 0, 67,
271            117, 115, 116, 111, 109, 32, 100, 97, 116, 97, 0, 0, 0, 0, 0,
272        ];
273        assert_eq!(buffer, expected_result);
274
275        let read_options = BlockOptions::read(&mut buffer.as_slice(), LittleEndian).unwrap();
276        assert_eq!(options, read_options);
277    }
278}