rusty_pcap/pcap/
file_header.rs

1//! PCAP file header representation and parsing
2use std::io::{Cursor, Read, Write};
3
4use crate::{
5    Version,
6    byte_order::{Endianness, ExtendedByteOrder, WriteExt},
7    link_type::LinkType,
8    pcap::PcapParseError,
9};
10
11/// The magic number used to identify pcap files and their endianness
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum MagicNumber {
14    /// Microsecond Resolution
15    #[default]
16    Microsecond,
17    /// Nanosecond Resolution
18    Nanosecond,
19}
20
21/// Represents the magic number and endianness of a pcap file
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub struct MagicNumberAndEndianness {
24    /// The magic number identifying the pcap file format and timestamp resolution
25    pub magic_number: MagicNumber,
26    /// The endianness of the file
27    pub endianness: Endianness,
28}
29
30impl TryFrom<[u8; 4]> for MagicNumberAndEndianness {
31    type Error = PcapParseError;
32
33    fn try_from(value: [u8; 4]) -> Result<Self, Self::Error> {
34        match value {
35            [0xa1, 0xb2, 0xc3, 0xd4] => Ok(Self {
36                magic_number: MagicNumber::Microsecond,
37                endianness: Endianness::BigEndian,
38            }),
39            [0xd4, 0xc3, 0xb2, 0xa1] => Ok(Self {
40                magic_number: MagicNumber::Microsecond,
41                endianness: Endianness::LittleEndian,
42            }),
43            [0xA1, 0xB2, 0x3C, 0x4D] => Ok(Self {
44                magic_number: MagicNumber::Nanosecond,
45                endianness: Endianness::BigEndian,
46            }),
47            [0x4d, 0x3c, 0xb2, 0xa1] => Ok(Self {
48                magic_number: MagicNumber::Nanosecond,
49                endianness: Endianness::LittleEndian,
50            }),
51            _ => Err(PcapParseError::InvalidMagicNumber(Some(value))),
52        }
53    }
54}
55impl From<MagicNumberAndEndianness> for [u8; 4] {
56    fn from(value: MagicNumberAndEndianness) -> Self {
57        match (value.magic_number, value.endianness) {
58            (MagicNumber::Microsecond, Endianness::LittleEndian) => [0xd4, 0xc3, 0xb2, 0xa1],
59            (MagicNumber::Microsecond, Endianness::BigEndian) => [0xa1, 0xb2, 0xc3, 0xd4],
60            (MagicNumber::Nanosecond, Endianness::LittleEndian) => [0xA1, 0xB2, 0x3C, 0x4D],
61            (MagicNumber::Nanosecond, Endianness::BigEndian) => [0x4d, 0x3c, 0xb2, 0xa1],
62        }
63    }
64}
65impl TryFrom<&[u8]> for MagicNumberAndEndianness {
66    type Error = PcapParseError;
67
68    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
69        if value.len() < 4 {
70            return Err(PcapParseError::InvalidMagicNumber(None));
71        }
72        let array: [u8; 4] = value[0..4].try_into()?;
73        Self::try_from(array)
74    }
75}
76/// Represents the file header of a pcap file
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct PcapFileHeader {
79    /// First 4 bytes are the magic number and endianness
80    pub magic_number_and_endianness: MagicNumberAndEndianness,
81    /// The version of the pcap file format
82    /// Bytes 4..8
83    pub version: Version,
84    /// The timezone offset
85    /// Bytes 8..12
86    pub timezone: u32,
87    /// The number of significant figures
88    /// Bytes 12..16
89    pub sig_figs: u32,
90    /// The maximum byte length of captured packets
91    ///
92    /// Bytes 16..20
93    pub snap_length: u32,
94    /// The link type of the captured packets
95    /// Bytes 20..24
96    pub link_type: LinkType,
97}
98impl Default for PcapFileHeader {
99    fn default() -> Self {
100        Self {
101            magic_number_and_endianness: MagicNumberAndEndianness::default(),
102            version: Version::PCAP_VERSION_2_4,
103            timezone: Default::default(),
104            sig_figs: Default::default(),
105            snap_length: Default::default(),
106            link_type: Default::default(),
107        }
108    }
109}
110impl PcapFileHeader {
111    /// Reads the file header from the reader
112    pub fn read<R: Read>(reader: &mut R) -> Result<Self, PcapParseError> {
113        let mut header = [0u8; 24];
114        reader.read_exact(&mut header)?;
115        Self::try_from(&header)
116    }
117    /// Writes the file header to the writer
118    pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
119        let as_bytes: [u8; 24] = self.into();
120        writer.write_all(&as_bytes)?;
121        Ok(())
122    }
123}
124impl TryFrom<&[u8; 24]> for PcapFileHeader {
125    type Error = PcapParseError;
126
127    fn try_from(bytes: &[u8; 24]) -> Result<Self, Self::Error> {
128        let magic_number_and_endianness = MagicNumberAndEndianness::try_from(&bytes[0..4])?;
129
130        let version = Version::parse(&bytes[4..8], magic_number_and_endianness.endianness);
131        let timezone = magic_number_and_endianness
132            .endianness
133            .try_u32_from_bytes(&bytes[8..12])?;
134        let sig_figs = magic_number_and_endianness
135            .endianness
136            .try_u32_from_bytes(&bytes[12..16])?;
137        let snap_length = magic_number_and_endianness
138            .endianness
139            .try_u32_from_bytes(&bytes[16..20])?;
140        let link_type = LinkType::try_from(
141            magic_number_and_endianness
142                .endianness
143                .try_u32_from_bytes(&bytes[20..24])?,
144        )?;
145        Ok(Self {
146            magic_number_and_endianness,
147            version,
148            timezone,
149            sig_figs,
150            snap_length,
151            link_type,
152        })
153    }
154}
155impl<'a> From<&'a PcapFileHeader> for [u8; 24] {
156    fn from(value: &'a PcapFileHeader) -> Self {
157        // It is impossible for these write calls to error out.
158        let mut header = Cursor::new([0u8; 24]);
159        let magic_number: [u8; 4] = value.magic_number_and_endianness.into();
160        let _ = header.write_all(&magic_number);
161        let endianness = value.magic_number_and_endianness.endianness;
162        let _ = value.version.write(&mut header, endianness);
163        let _ = header.write_u32(value.timezone, endianness);
164        let _ = header.write_u32(value.sig_figs, endianness);
165        let _ = header.write_u32(value.snap_length, endianness);
166        let _ = header.write_u32((value.link_type as u16).into(), endianness);
167        header.into_inner()
168    }
169}
170
171impl From<PcapFileHeader> for [u8; 24] {
172    fn from(value: PcapFileHeader) -> Self {
173        (&value).into()
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_magic_number_and_endianness() {
183        let magic_bytes = [0xa1, 0xb2, 0xc3, 0xd4];
184        let magic = MagicNumberAndEndianness::try_from(magic_bytes).unwrap();
185        assert_eq!(magic.magic_number, MagicNumber::Microsecond);
186        assert_eq!(magic.endianness, Endianness::BigEndian);
187    }
188
189    #[test]
190    fn test_pcap_file_header_read() {
191        let file = std::fs::File::open("test_data/test.pcap").expect("Failed to open test.pcap");
192        let mut reader = std::io::BufReader::new(file);
193        let header = PcapFileHeader::read(&mut reader).expect("Failed to read PCAP header");
194        assert_eq!(
195            header.magic_number_and_endianness.magic_number,
196            MagicNumber::Microsecond
197        );
198        assert_eq!(
199            header.magic_number_and_endianness.endianness,
200            Endianness::LittleEndian
201        );
202        println!("{:?}", header);
203    }
204
205    #[test]
206    fn test_header_write() {
207        let header = PcapFileHeader {
208            magic_number_and_endianness: MagicNumberAndEndianness {
209                magic_number: MagicNumber::Microsecond,
210                endianness: Endianness::BigEndian,
211            },
212            version: Version { major: 2, minor: 6 },
213            timezone: 1,
214            sig_figs: 2,
215            snap_length: 100,
216            link_type: LinkType::Ethernet,
217        };
218
219        let as_bytes: [u8; 24] = header.into();
220
221        let from_bytes =
222            PcapFileHeader::try_from(&as_bytes).expect("Unable to parse written header");
223
224        assert_eq!(header, from_bytes)
225    }
226}