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