Skip to main content

rusty_pcap/
lib.rs

1#![forbid(unsafe_code)]
2//! rusty-pcap is a pcap library for Rust
3//!
4//! 100% Rust implementation of a pcap reader
5use std::{cmp, fmt::Display, io::Write};
6
7use crate::{
8    byte_order::{ByteOrder, WriteExt},
9    pcap::file_header::MagicNumberAndEndianness,
10    pcap_ng::PCAP_NG_MAGIC,
11};
12pub mod any_reader;
13pub mod byte_order;
14pub mod link_type;
15pub mod pcap;
16pub mod pcap_ng;
17pub(crate) mod utils;
18/// PcapFileType is the type of the pcap file, either Pcap or PcapNg
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PcapFileType {
21    /// Pcap file format
22    ///
23    /// Based on the libpcap format
24    Pcap,
25    /// PcapNg file format
26    PcapNg,
27}
28impl Display for PcapFileType {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            PcapFileType::Pcap => write!(f, "PCAP"),
32            PcapFileType::PcapNg => write!(f, "PCAP-NG"),
33        }
34    }
35}
36impl PcapFileType {
37    /// Returns the PcapFileType from the magic number
38    pub fn from_magic(magic: [u8; 4]) -> Option<Self> {
39        if MagicNumberAndEndianness::try_from(magic).is_ok() {
40            Some(PcapFileType::Pcap)
41        } else if magic == PCAP_NG_MAGIC {
42            Some(PcapFileType::PcapNg)
43        } else {
44            None
45        }
46    }
47}
48
49/// Represents the version of the pcap file format
50///
51/// Also used in pcap-ng files for the section header block
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct Version {
54    /// Major version
55    pub major: u16,
56    /// Minor version
57    pub minor: u16,
58}
59impl PartialOrd for Version {
60    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
61        Some(self.cmp(other))
62    }
63}
64impl Ord for Version {
65    fn cmp(&self, other: &Self) -> cmp::Ordering {
66        self.major
67            .cmp(&other.major)
68            .then_with(|| self.minor.cmp(&other.minor))
69    }
70}
71impl Version {
72    /// The current version of libpcap is 2.4
73    pub const PCAP_VERSION_2_4: Version = Version { major: 2, minor: 4 };
74    /// The libpcap version 2.3
75    pub const PCAP_VERSION_2_3: Version = Version { major: 2, minor: 3 };
76    /// Parses the version from the bytes
77    #[inline(always)]
78    pub(crate) fn parse(bytes: &[u8], byte_order: impl ByteOrder) -> Self {
79        let major = byte_order.u16_from_bytes([bytes[0], bytes[1]]);
80        let minor = byte_order.u16_from_bytes([bytes[2], bytes[3]]);
81        Self { major, minor }
82    }
83    pub(crate) fn write<W: Write>(
84        &self,
85        target: &mut W,
86        byte_order: impl ByteOrder,
87    ) -> Result<(), std::io::Error> {
88        target.write_u16(self.major, byte_order)?;
89        target.write_u16(self.minor, byte_order)?;
90        Ok(())
91    }
92}
93
94#[cfg(test)]
95pub(crate) mod test_helpers {
96    use std::{
97        fs::{File, create_dir_all},
98        io::Read,
99        path::{Path, PathBuf},
100    };
101
102    use anyhow::{Result, anyhow};
103
104    use crate::Version;
105
106    pub fn test_target_dir() -> Result<PathBuf> {
107        let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
108
109        let target_dir = base_dir.join("test_data/actual");
110
111        if !target_dir.exists() {
112            create_dir_all(&target_dir)?;
113        }
114        Ok(target_dir)
115    }
116    pub fn test_expected_dir() -> Result<PathBuf> {
117        let base_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
118
119        let target_dir = base_dir.join("test_data/expected");
120
121        if !target_dir.exists() {
122            create_dir_all(&target_dir)?;
123        }
124        Ok(target_dir)
125    }
126
127    pub fn test_files(test_file_name: &str) -> Result<(PathBuf, PathBuf)> {
128        let test_actual = test_target_dir()?.join(test_file_name);
129        let test_expected = test_expected_dir()?.join(test_file_name);
130        Ok((test_actual, test_expected))
131    }
132    #[track_caller]
133    pub fn do_files_match(actual: impl AsRef<Path>, expected: impl AsRef<Path>) -> Result<()> {
134        if !expected.as_ref().exists() {
135            println!("Expected Does not exist coppying actual over");
136            std::fs::copy(actual, expected)?;
137            return Err(anyhow!(
138                "No Expected File existed. But this is just to signal that you a new file was generated"
139            ));
140        }
141        let mut actual_file = File::open(actual)?;
142        let mut expected_file = File::open(expected)?;
143        let mut actual_bytes = Vec::with_capacity(actual_file.metadata()?.len() as usize);
144        let mut expected_bytes = Vec::with_capacity(actual_file.metadata()?.len() as usize);
145
146        actual_file.read_to_end(&mut actual_bytes)?;
147        expected_file.read_to_end(&mut expected_bytes)?;
148
149        assert_eq!(actual_bytes, expected_bytes);
150
151        Ok(())
152    }
153
154    #[test]
155    fn test_version_cmp() {
156        let v2_4 = Version { major: 2, minor: 4 };
157        let v2_3 = Version { major: 2, minor: 3 };
158        let v2_1 = Version { major: 1, minor: 1 };
159
160        assert!(v2_4 > v2_3);
161        assert!(v2_3 > v2_1);
162        assert!(v2_4 > v2_1);
163    }
164}