From 4528c1b20c04f4a4ec8626521c5b4d1cb07cd5f7 Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Sun, 25 Feb 2024 12:35:35 +0100 Subject: [PATCH] Add unit tests (#330) --- .github/workflows/ci-steps.yml | 9 +- CONTRIBUTING.md | 33 +- Cargo.toml | 2 + msrv.lock | 132 +++++- src/capture/activated/active.rs | 104 +++++ src/capture/activated/dead.rs | 107 ++--- src/capture/activated/iterator.rs | 206 +++++++++ src/capture/activated/mod.rs | 652 ++++++++++++++++++++++++++++- src/capture/activated/offline.rs | 77 ++++ src/capture/inactive.rs | 264 +++++++++++- src/capture/mod.rs | 106 ++++- src/capture/selectable.rs | 81 ++++ src/codec.rs | 28 ++ src/device.rs | 422 ++++++++++++++++++- src/lib.rs | 83 +++- src/linktype.rs | 57 ++- src/packet.rs | 24 +- src/raw.rs | 393 ++++++++++------- src/stream/mod.rs | 24 ++ src/stream/unix.rs | 46 +- src/stream/windows.rs | 3 +- tests/capture/activated/mod.rs | 138 ++++++ tests/capture/activated/offline.rs | 10 + tests/capture/mod.rs | 1 + tests/lib.rs | 210 +--------- 25 files changed, 2688 insertions(+), 524 deletions(-) create mode 100644 src/capture/selectable.rs create mode 100644 tests/capture/activated/mod.rs create mode 100644 tests/capture/activated/offline.rs create mode 100644 tests/capture/mod.rs diff --git a/.github/workflows/ci-steps.yml b/.github/workflows/ci-steps.yml index 38118016d..f27da7a9a 100644 --- a/.github/workflows/ci-steps.yml +++ b/.github/workflows/ci-steps.yml @@ -61,10 +61,13 @@ jobs: run: cp msrv.lock Cargo.lock - name: cargo build run: cargo build --all-targets ${{ inputs.profile }} ${{ inputs.features }} + # Since secrets are not passed to workflows that are triggered by a pull request from a fork + # the npcap dll won't be installed so we skip this step on pull requests. It will + # nevertheless be run once merged and the pull request will still verify the build steps. + - name: cargo test (windows pull-request special) + if: (inputs.os == 'windows-latest') && (github.event_name == 'pull_request') + run: cargo test --lib ${{ inputs.profile }} ${{ inputs.features }} - name: cargo test - # Since secrets are not passed to workflows that are triggered by a pull request from a fork - # the npcap dll won't be installed so we skip this step on pull requests. It will - # nevertheless be run once merged and the pull request will still verify the build steps. if: (inputs.os != 'windows-latest') || (github.event_name != 'pull_request') run: cargo test --all-targets ${{ inputs.profile }} ${{ inputs.features }} - name: cargo clippy diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b650c87fe..d19d6ec8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,16 +6,11 @@ The current code coverage for the `main` branch is automatically published on ev ### Pre-requisites -To obtain code coverage locally you will need the nightly compiler toolchain, [compatible LLVM coverage tools](https://doc.rust-lang.org/rustc/instrument-coverage.html#installing-llvm-coverage-tools), and [`grcov`](https://github.com/mozilla/grcov). - -Make sure you have the nightly toolchain installed with `rustup`: -``` -rustup install nightly -``` +To obtain code coverage locally you will need [compatible LLVM coverage tools](https://doc.rust-lang.org/rustc/instrument-coverage.html#installing-llvm-coverage-tools) and [`grcov`](https://github.com/mozilla/grcov). The easiest way to obtain compatible LLVM coverage tools is by adding the `llvm-tools-preview` `rustup` component (for nightly!): ``` -rustup +nightly component add llvm-tools-preview +rustup component add llvm-tools-preview ``` `grcov` can be installed through cargo: @@ -25,24 +20,32 @@ cargo install grcov ### Obtaining code coverage -First, switch to the nightly toolchain. Note that switching toolchains is necessary, as opposed to just using the `+nightly` option, because `grcov` does not have such an option and will not be able to find (or worse, will find the incorrect version of) `llvm-tools-preview`. -``` -rustup default nightly -``` - Clean any previously compiled objects and binaries. ``` -cargo clean +rm -rf ./target/debug/{coverage,profraw} +cargo clean -p pcap ``` Compile and run the tests. We set `RUSTFLAGS="-C instrument-coverage"` to enable source-based code coverage and `LLVM_PROFILE_FILE="target/debug/coverage/profraw/pcap-%p-%m.profraw"` to make sure each test gets its own profile information. ``` -RUSTFLAGS="-C instrument-coverage" LLVM_PROFILE_FILE="target/debug/profraw/pcap-%p-%m.profraw" cargo test --all-features +env RUSTFLAGS="-C instrument-coverage" \ + LLVM_PROFILE_FILE="target/debug/profraw/pcap-%p-%m.profraw" \ + cargo test --all-features --all-targets ``` And finally, run `grcov` to obtain a report. ``` -grcov target/debug/profraw -s src/ --binary-path ./target/debug/ -t html --branch --ignore-not-existing -o ./target/debug/coverage/ +grcov target/debug/profraw \ + --binary-path ./target/debug/ \ + --output-types html \ + --source-dir . \ + --ignore-not-existing \ + --ignore "build.rs" \ + --ignore "tests/*" \ + --ignore "examples/*" \ + --excl-start "GRCOV_EXCL_START|mod tests \{" \ + --excl-stop "GRCOV_EXCL_STOP" \ + --output-path ./target/debug/coverage/ ``` The code coverage report will be available in `target/debug/coverage/index.html` which you can explore in your browser. diff --git a/Cargo.toml b/Cargo.toml index 6031cd1a2..bfee3ffb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ windows-sys = { version = "0.36.1", features = ["Win32_Foundation", "Win32_Netwo tun-tap = { version = "0.1.3", optional = true } [dev-dependencies] +once_cell = "1.14.0" +mockall = "0.11.4" tempdir = "0.3" [target.'cfg(target_os = "windows")'.dev-dependencies] diff --git a/msrv.lock b/msrv.lock index f74ff0529..1055f1e17 100644 --- a/msrv.lock +++ b/msrv.lock @@ -1,7 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - [[package]] name = "aho-corasick" version = "0.7.18" @@ -120,6 +118,24 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + [[package]] name = "errno" version = "0.2.8" @@ -160,12 +176,27 @@ dependencies = [ "rustc-serialize", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -321,6 +352,15 @@ dependencies = [ "libc", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -457,6 +497,33 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "mockall" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "net2" version = "0.2.38" @@ -468,6 +535,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "ntapi" version = "0.3.7" @@ -477,6 +550,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.1" @@ -487,6 +569,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + [[package]] name = "parking_lot" version = "0.9.0" @@ -515,7 +603,7 @@ dependencies = [ [[package]] name = "pcap" -version = "1.1.0" +version = "1.2.0" dependencies = [ "bitflags", "errno", @@ -525,6 +613,8 @@ dependencies = [ "gat-std", "libc", "libloading", + "mockall", + "once_cell", "pkg-config", "regex", "tempdir", @@ -551,6 +641,36 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.37" @@ -726,6 +846,12 @@ dependencies = [ "remove_dir_all", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "tokio" version = "0.1.22" diff --git a/src/capture/activated/active.rs b/src/capture/activated/active.rs index 077ad867e..ede87a6ef 100644 --- a/src/capture/activated/active.rs +++ b/src/capture/activated/active.rs @@ -39,3 +39,107 @@ impl AsRawFd for Capture { fd } } + +#[cfg(test)] +mod tests { + use crate::{ + capture::testmod::test_capture, + raw::{ + mock_ffi::*, + testmod::{as_pcap_t, geterr_expect, RAWMTX}, + }, + }; + + use super::*; + + #[test] + fn test_sendpacket() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let buffer: [u8; 10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let ctx = pcap_sendpacket_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once(|_, _, _| 0); + + let result = capture.sendpacket(buffer); + assert!(result.is_ok()); + + let ctx = pcap_sendpacket_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once(|_, _, _| -1); + + let _err = geterr_expect(pcap); + + let result = capture.sendpacket(buffer); + assert!(result.is_err()); + } + + #[test] + fn test_setnonblock() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + assert!(!capture.is_nonblock()); + + let ctx = pcap_setnonblock_context(); + ctx.expect() + .withf_st(move |arg1, arg2, _| (*arg1 == pcap) && (*arg2 == 1)) + .return_once(|_, _, _| 0); + + let capture = capture.setnonblock().unwrap(); + assert!(capture.is_nonblock()); + } + + #[test] + fn test_setnonblock_error() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + assert!(!capture.nonblock); + + let ctx = pcap_setnonblock_context(); + ctx.expect() + .withf_st(move |arg1, arg2, _| (*arg1 == pcap) && (*arg2 == 1)) + .return_once(|_, _, _| -1); + + let result = capture.setnonblock(); + assert!(result.is_err()); + } + + #[test] + #[cfg(not(windows))] + fn test_as_raw_fd() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = pcap_fileno_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 7); + + assert_eq!(capture.as_raw_fd(), 7); + } +} diff --git a/src/capture/activated/dead.rs b/src/capture/activated/dead.rs index 63c7e10e3..5efbbf061 100644 --- a/src/capture/activated/dead.rs +++ b/src/capture/activated/dead.rs @@ -1,4 +1,4 @@ -use std::{ffi::CString, fmt, mem, ptr::NonNull, slice}; +use std::ptr::NonNull; use crate::{ capture::{Capture, Dead}, @@ -31,72 +31,55 @@ impl Capture { NonNull::::new(handle).ok_or(Error::InsufficientMemory)?, )) } - - /// Compiles the string into a filter program using `pcap_compile`. - pub fn compile(&self, program: &str, optimize: bool) -> Result { - let program = CString::new(program).unwrap(); - - unsafe { - let mut bpf_program: raw::bpf_program = mem::zeroed(); - if -1 - == raw::pcap_compile( - self.handle.as_ptr(), - &mut bpf_program, - program.as_ptr(), - optimize as libc::c_int, - 0, - ) - { - return Err(Error::new(raw::pcap_geterr(self.handle.as_ptr()))); - } - Ok(BpfProgram(bpf_program)) - } - } } -#[repr(transparent)] -pub struct BpfInstruction(raw::bpf_insn); -#[repr(transparent)] -pub struct BpfProgram(raw::bpf_program); - -impl BpfProgram { - /// checks whether a filter matches a packet - pub fn filter(&self, buf: &[u8]) -> bool { - let header: raw::pcap_pkthdr = raw::pcap_pkthdr { - ts: libc::timeval { - tv_sec: 0, - tv_usec: 0, - }, - caplen: buf.len() as u32, - len: buf.len() as u32, - }; - unsafe { raw::pcap_offline_filter(&self.0, &header, buf.as_ptr()) > 0 } - } +#[cfg(test)] +mod tests { + #[cfg(libpcap_1_5_0)] + use mockall::predicate; - pub fn get_instructions(&self) -> &[BpfInstruction] { - unsafe { - slice::from_raw_parts( - self.0.bf_insns as *const BpfInstruction, - self.0.bf_len as usize, - ) - } - } -} + use crate::raw::testmod::{as_pcap_t, RAWMTX}; + + use super::*; + + #[test] + fn test_dead() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); -impl Drop for BpfProgram { - fn drop(&mut self) { - unsafe { raw::pcap_freecode(&mut self.0) } + let ctx = raw::pcap_open_dead_context(); + ctx.expect().return_once_st(move |_, _| pcap); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let result = Capture::dead(Linktype::ETHERNET); + assert!(result.is_ok()); } -} -impl fmt::Display for BpfInstruction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} {} {} {}", - self.0.code, self.0.jt, self.0.jf, self.0.k - ) + #[test] + #[cfg(libpcap_1_5_0)] + fn test_dead_with_precision() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_open_dead_with_tstamp_precision_context(); + ctx.expect() + .with(predicate::always(), predicate::always(), predicate::eq(1)) + .return_once_st(move |_, _, _| pcap); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let result = Capture::dead_with_precision(Linktype::ETHERNET, Precision::Nano); + assert!(result.is_ok()); } } - -unsafe impl Send for BpfProgram {} diff --git a/src/capture/activated/iterator.rs b/src/capture/activated/iterator.rs index 9b309ac68..f7a5080f3 100644 --- a/src/capture/activated/iterator.rs +++ b/src/capture/activated/iterator.rs @@ -71,3 +71,209 @@ mod lending_iter { } } } + +#[cfg(test)] +mod tests { + use crate::{ + capture::{ + activated::testmod::{next_ex_expect, PACKET}, + testmod::test_capture, + Active, Offline, + }, + codec::testmod::Codec, + raw::{ + self, + testmod::{as_pcap_t, geterr_expect, RAWMTX}, + }, + }; + + #[cfg(feature = "lending-iter")] + use gat_std::iter::{IntoIterator, Iterator}; + + use super::*; + + #[cfg(feature = "lending-iter")] + use super::lending_iter::*; + + #[test] + fn test_iter_next() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + let mut packet_iter = capture.iter(Codec); + + let _nxt = next_ex_expect(pcap); + + let next = packet_iter.next().unwrap(); + let next_packet = next.unwrap(); + assert_eq!(next_packet.header, *PACKET.header); + assert_eq!(*next_packet.data, *PACKET.data); + + let _nxt = next_ex_expect(pcap); + + let next_packet = packet_iter.capture_mut().next_packet().unwrap(); + assert_eq!(next_packet, PACKET); + + let _nxt = next_ex_expect(pcap); + + let (mut capture, _) = packet_iter.into(); + + let next_packet = capture.next_packet().unwrap(); + assert_eq!(next_packet, PACKET); + } + + #[test] + fn test_iter_timeout() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let mut packet_iter = capture.iter(Codec); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| 0); + + let next = packet_iter.next().unwrap(); + let err = next.unwrap_err(); + assert_eq!(err, Error::TimeoutExpired); + } + + #[test] + fn test_next_packet_read_error() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let mut packet_iter = capture.iter(Codec); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -1); + + let _err = geterr_expect(pcap); + + let next = packet_iter.next().unwrap(); + assert!(next.is_err()); + } + + #[test] + fn test_next_packet_no_more_packets() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let mut packet_iter = capture.iter(Codec); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -2); + + let next = packet_iter.next(); + assert!(next.is_none()); + } + + #[test] + #[cfg(feature = "lending-iter")] + fn test_lending_iter() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + let mut packet_iter: PacketLendingIter = capture.into_iter(); + + let _nxt = next_ex_expect(pcap); + + let next = packet_iter.next().unwrap(); + let next_packet = next.unwrap(); + assert_eq!(next_packet, PACKET); + } + + #[test] + #[cfg(feature = "lending-iter")] + fn test_lending_iter_timeout() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + let mut packet_iter: PacketLendingIter = capture.into_iter(); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| 0); + + let next = packet_iter.next().unwrap(); + let err = next.unwrap_err(); + assert_eq!(err, Error::TimeoutExpired); + } + + #[test] + #[cfg(feature = "lending-iter")] + fn test_lending_iter_read_error() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + let mut packet_iter: PacketLendingIter = capture.into_iter(); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -1); + + let _err = geterr_expect(pcap); + + let next = packet_iter.next().unwrap(); + assert!(next.is_err()); + } + + #[test] + #[cfg(feature = "lending-iter")] + fn test_lending_iter_no_more_packets() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + let mut packet_iter: PacketLendingIter = capture.into_iter(); + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -2); + + let next = packet_iter.next(); + assert!(next.is_none()); + } +} diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 4b9e8d678..0b08bf88b 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -5,7 +5,7 @@ pub mod offline; use std::{ ffi::CString, - mem, + fmt, mem, path::Path, ptr::{self, NonNull}, slice, @@ -163,7 +163,6 @@ impl Capture { let mut header: *mut raw::pcap_pkthdr = ptr::null_mut(); let mut packet: *const libc::c_uchar = ptr::null(); let retcode = raw::pcap_next_ex(self.handle.as_ptr(), &mut header, &mut packet); - self.check_err(retcode != -1)?; // -1 => an error occured while reading the packet match retcode { i if i >= 1 => { // packet was read without issue @@ -177,15 +176,20 @@ impl Capture { // timeout expired Err(Error::TimeoutExpired) } + -1 => { + // an error occured while reading the packet + Err(self.get_err()) + } -2 => { // packets are being read from a "savefile" and there are no // more packets to read Err(Error::NoMorePackets) } + // GRCOV_EXCL_START _ => { // libpcap only defines codes >=1, 0, -1, and -2 unreachable!() - } + } // GRCOV_EXCL_STOP } } } @@ -195,13 +199,10 @@ impl Capture { PacketIter::new(self, codec) } - /// Sets the filter on the capture using the given BPF program string. Internally this is - /// compiled using `pcap_compile()`. `optimize` controls whether optimization on the resulting - /// code is performed - /// - /// See for more information about this syntax. - pub fn filter(&mut self, program: &str, optimize: bool) -> Result<(), Error> { + /// Compiles the string into a filter program using `pcap_compile`. + pub fn compile(&self, program: &str, optimize: bool) -> Result { let program = CString::new(program)?; + unsafe { let mut bpf_program: raw::bpf_program = mem::zeroed(); let ret = raw::pcap_compile( @@ -211,13 +212,21 @@ impl Capture { optimize as libc::c_int, 0, ); - self.check_err(ret != -1)?; - let ret = raw::pcap_setfilter(self.handle.as_ptr(), &mut bpf_program); - raw::pcap_freecode(&mut bpf_program); - self.check_err(ret != -1) + self.check_err(ret != -1).and(Ok(BpfProgram(bpf_program))) } } + /// Sets the filter on the capture using the given BPF program string. Internally this is + /// compiled using `pcap_compile()`. `optimize` controls whether optimization on the resulting + /// code is performed + /// + /// See for more information about this syntax. + pub fn filter(&mut self, program: &str, optimize: bool) -> Result<(), Error> { + let mut bpf_program = self.compile(program, optimize)?; + let ret = unsafe { raw::pcap_setfilter(self.handle.as_ptr(), &mut bpf_program.0) }; + self.check_err(ret != -1) + } + /// Get capture statistics about this capture. The values represent packet statistics from the /// start of the run to the time of the call. /// @@ -282,6 +291,53 @@ impl Drop for Savefile { } } +#[repr(transparent)] +pub struct BpfInstruction(raw::bpf_insn); +#[repr(transparent)] +pub struct BpfProgram(raw::bpf_program); + +impl BpfProgram { + /// checks whether a filter matches a packet + pub fn filter(&self, buf: &[u8]) -> bool { + let header: raw::pcap_pkthdr = raw::pcap_pkthdr { + ts: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + caplen: buf.len() as u32, + len: buf.len() as u32, + }; + unsafe { raw::pcap_offline_filter(&self.0, &header, buf.as_ptr()) > 0 } + } + + pub fn get_instructions(&self) -> &[BpfInstruction] { + unsafe { + slice::from_raw_parts( + self.0.bf_insns as *const BpfInstruction, + self.0.bf_len as usize, + ) + } + } +} + +impl Drop for BpfProgram { + fn drop(&mut self) { + unsafe { raw::pcap_freecode(&mut self.0) } + } +} + +impl fmt::Display for BpfInstruction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {} {} {}", + self.0.code, self.0.jt, self.0.jf, self.0.k + ) + } +} + +unsafe impl Send for BpfProgram {} + #[cfg(not(windows))] /// Open a raw file descriptor. /// @@ -295,3 +351,573 @@ pub unsafe fn open_raw_fd(fd: RawFd, mode: u8) -> Result<*mut libc::FILE, Error> .map(|f| f as _) .ok_or(Error::InvalidRawFd) } + +// GRCOV_EXCL_START +#[cfg(test)] +mod testmod { + use super::*; + + pub static TS: libc::timeval = libc::timeval { + tv_sec: 5, + tv_usec: 50, + }; + pub static LEN: u32 = DATA.len() as u32; + pub static CAPLEN: u32 = LEN; + + pub static mut PKTHDR: raw::pcap_pkthdr = raw::pcap_pkthdr { + ts: TS, + caplen: CAPLEN, + len: LEN, + }; + pub static PACKET_HEADER: PacketHeader = PacketHeader { + ts: TS, + caplen: CAPLEN, + len: LEN, + }; + + pub static DATA: [u8; 4] = [4, 5, 6, 7]; + pub static PACKET: Packet = Packet { + header: &PACKET_HEADER, + data: &DATA, + }; + + pub struct NextExContext(raw::__pcap_next_ex::Context); + pub fn next_ex_expect(pcap: *mut raw::pcap_t) -> NextExContext { + let data_ptr: *const libc::c_uchar = DATA.as_ptr(); + let pkthdr_ptr: *mut raw::pcap_pkthdr = unsafe { &mut PKTHDR }; + + let ctx = raw::pcap_next_ex_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, arg2, arg3| { + unsafe { + *arg2 = pkthdr_ptr; + *arg3 = data_ptr; + } + CAPLEN as i32 + }); + + NextExContext(ctx) + } +} +// GRCOV_EXCL_STOP + +#[cfg(test)] +mod tests { + use crate::{ + capture::{ + activated::testmod::{next_ex_expect, PACKET}, + testmod::test_capture, + Active, Capture, Offline, + }, + raw::testmod::{as_pcap_dumper_t, as_pcap_t, geterr_expect, RAWMTX}, + }; + + use super::*; + + #[test] + fn test_list_datalinks() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_list_datalinks_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(|_, _| 0); + + let ctx = raw::pcap_free_datalinks_context(); + ctx.expect().return_once(|_| {}); + + let _err = geterr_expect(pcap); + + let result = capture.list_datalinks(); + assert!(result.is_err()); + + let mut datalinks: [i32; 4] = [0, 1, 2, 3]; + let links: *mut i32 = datalinks.as_mut_ptr(); + let len = datalinks.len(); + + let ctx = raw::pcap_list_datalinks_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(move |_, arg2| { + unsafe { *arg2 = links }; + len as i32 + }); + + let ctx = raw::pcap_free_datalinks_context(); + ctx.checkpoint(); + ctx.expect().return_once(|_| {}); + + let pcap_datalinks = capture.list_datalinks().unwrap(); + assert_eq!( + pcap_datalinks, + datalinks.iter().cloned().map(Linktype).collect::>() + ); + } + + #[test] + fn test_set_datalink() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_set_datalink_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let result = capture.set_datalink(Linktype::ETHERNET); + assert!(result.is_ok()); + + let ctx = raw::pcap_set_datalink_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| -1); + + let _err = geterr_expect(pcap); + + let result = capture.set_datalink(Linktype::ETHERNET); + assert!(result.is_err()); + } + + #[test] + fn test_get_datalink() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_datalink_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 1); + + let linktype = capture.get_datalink(); + assert_eq!(linktype, Linktype::ETHERNET); + } + + #[test] + fn unify_activated() { + #![allow(dead_code)] + fn test1() -> Capture { + panic!(); + } + + fn test2() -> Capture { + panic!(); + } + + fn maybe(a: bool) -> Capture { + if a { + test1().into() + } else { + test2().into() + } + } + + fn also_maybe(a: &mut Capture) { + a.filter("whatever filter string, this won't be run anyway", false) + .unwrap(); + } + } + + #[test] + fn test_savefile() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let mut value: isize = 888; + let pcap_dumper = as_pcap_dumper_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_dump_open_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(move |_, _| pcap_dumper); + + let ctx = raw::pcap_dump_close_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| {}); + + let result = capture.savefile("path/to/nowhere"); + assert!(result.is_ok()); + } + + #[test] + #[cfg(libpcap_1_7_2)] + fn test_savefile_append() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let mut value: isize = 888; + let pcap_dumper = as_pcap_dumper_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_dump_open_append_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(move |_, _| pcap_dumper); + + let ctx = raw::pcap_dump_close_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| {}); + + let result = capture.savefile_append("path/to/nowhere"); + assert!(result.is_ok()); + } + + #[test] + fn test_savefile_error() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_dump_open_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| std::ptr::null_mut()); + + let _err = geterr_expect(pcap); + + let result = capture.savefile("path/to/nowhere"); + assert!(result.is_err()); + } + + #[test] + #[cfg(libpcap_1_7_2)] + fn test_savefile_append_error() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_dump_open_append_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| std::ptr::null_mut()); + + let _err = geterr_expect(pcap); + + let result = capture.savefile_append("path/to/nowhere"); + assert!(result.is_err()); + } + + #[test] + fn test_savefile_ops() { + let _m = RAWMTX.lock(); + + let mut value: isize = 888; + let pcap_dumper = as_pcap_dumper_t(&mut value); + + let ctx = raw::pcap_dump_close_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| {}); + + let mut savefile = Savefile { + handle: NonNull::new(pcap_dumper).unwrap(), + }; + + let ctx = raw::pcap_dump_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap_dumper as _) + .return_once(|_, _, _| {}); + + savefile.write(&PACKET); + + let ctx = raw::pcap_dump_flush_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| 0); + + let result = savefile.flush(); + assert!(result.is_ok()); + + let ctx = raw::pcap_dump_flush_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap_dumper) + .return_once(|_| -1); + + let result = savefile.flush(); + assert!(result.is_err()); + } + + #[test] + fn test_direction() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_setdirection_context(); + ctx.expect() + .withf_st(move |arg1, arg2| (*arg1 == pcap) && (*arg2 == raw::PCAP_D_OUT)) + .return_once(|_, _| 0); + + let result = capture.direction(Direction::Out); + assert!(result.is_ok()); + + let ctx = raw::pcap_setdirection_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, arg2| (*arg1 == pcap) && (*arg2 == raw::PCAP_D_OUT)) + .return_once(|_, _| -1); + + let _err = geterr_expect(pcap); + + let result = capture.direction(Direction::Out); + assert!(result.is_err()); + + // For code coverage of the derive line. + assert_ne!(Direction::In, Direction::InOut); + assert_ne!(Direction::In, Direction::Out); + assert_ne!(Direction::InOut, Direction::Out); + } + + #[test] + fn test_next_packet() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let _nxt = next_ex_expect(pcap); + + let next_packet = capture.next_packet().unwrap(); + assert_eq!(next_packet, PACKET); + } + + #[test] + fn test_next_packet_timeout() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| 0); + + let err = capture.next_packet().unwrap_err(); + assert_eq!(err, Error::TimeoutExpired); + } + + #[test] + fn test_next_packet_read_error() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -1); + + let _err = geterr_expect(pcap); + + let result = capture.next_packet(); + assert!(result.is_err()); + } + + #[test] + fn test_next_packet_no_more_packets() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let ctx = raw::pcap_next_ex_context(); + ctx.expect() + .withf_st(move |arg1, _, _| *arg1 == pcap) + .return_once_st(move |_, _, _| -2); + + let err = capture.next_packet().unwrap_err(); + assert_eq!(err, Error::NoMorePackets); + } + + #[test] + fn test_compile() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_compile_context(); + ctx.expect() + .withf_st(move |arg1, _, _, _, _| *arg1 == pcap) + .return_once(|_, _, _, _, _| -1); + + let _err = geterr_expect(pcap); + + let ctx = raw::pcap_freecode_context(); + ctx.expect().return_once(|_| {}); + + let result = capture.compile("some bpf program", false); + assert!(result.is_err()); + + let ctx = raw::pcap_compile_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _, _, _, _| *arg1 == pcap) + .return_once(|_, _, _, _, _| 0); + + let ctx = raw::pcap_freecode_context(); + ctx.checkpoint(); + ctx.expect().return_once(|_| {}); + + let result = capture.compile("some bpf program", false); + assert!(result.is_ok()); + } + + #[test] + fn test_filter() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let ctx = raw::pcap_compile_context(); + ctx.expect() + .withf_st(move |arg1, _, _, _, _| *arg1 == pcap) + .return_once(|_, _, _, _, _| 0); + + let ctx = raw::pcap_setfilter_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| -1); + + let _err = geterr_expect(pcap); + + let ctx = raw::pcap_freecode_context(); + ctx.expect().return_once(|_| {}); + + let result = capture.filter("some bpf program", false); + assert!(result.is_err()); + + let ctx = raw::pcap_compile_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _, _, _, _| *arg1 == pcap) + .return_once(|_, _, _, _, _| 0); + + let ctx = raw::pcap_setfilter_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let ctx = raw::pcap_freecode_context(); + ctx.checkpoint(); + ctx.expect().return_once(|_| {}); + + let result = capture.compile("some bpf program", false); + assert!(result.is_ok()); + } + + #[test] + fn test_stats() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture = test_capture.capture; + + let stat = raw::pcap_stat { + ps_recv: 1, + ps_drop: 2, + ps_ifdrop: 3, + }; + + let ctx = raw::pcap_stats_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(move |_, arg2| { + unsafe { *arg2 = stat }; + 0 + }); + + let stats = capture.stats().unwrap(); + assert_eq!(stats, Stat::new(stat.ps_recv, stat.ps_drop, stat.ps_ifdrop)); + + let ctx = raw::pcap_stats_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once_st(move |_, _| -1); + + let _err = geterr_expect(pcap); + + let result = capture.stats(); + assert!(result.is_err()); + } + + #[test] + fn test_bpf_instruction_display() { + let instr = BpfInstruction(raw::bpf_insn { + code: 1, + jt: 2, + jf: 3, + k: 4, + }); + assert_eq!(format!("{}", instr), "1 2 3 4"); + } +} diff --git a/src/capture/activated/offline.rs b/src/capture/activated/offline.rs index 713cb32c7..950d3894d 100644 --- a/src/capture/activated/offline.rs +++ b/src/capture/activated/offline.rs @@ -78,3 +78,80 @@ impl Capture { (self.major_version(), self.minor_version()) } } + +#[cfg(test)] +mod tests { + #[cfg(libpcap_1_5_0)] + use mockall::predicate; + + use crate::{ + capture::testmod::test_capture, + raw::testmod::{as_pcap_t, RAWMTX}, + }; + + use super::*; + + #[test] + fn test_from_file() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_open_offline_context(); + ctx.expect().return_once_st(move |_, _| pcap); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let result = Capture::from_file("path/to/nowhere"); + assert!(result.is_ok()); + } + + #[test] + #[cfg(libpcap_1_5_0)] + fn test_from_file_with_precision() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_open_offline_with_tstamp_precision_context(); + ctx.expect() + .with(predicate::always(), predicate::eq(1), predicate::always()) + .return_once_st(move |_, _, _| pcap); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let result = Capture::from_file_with_precision("path/to/nowhere", Precision::Nano); + assert!(result.is_ok()); + } + + #[test] + fn test_version() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_major_version_context(); + ctx.expect() + .withf_st(move |arg| *arg == pcap) + .return_once(|_| 5); + + let ctx = raw::pcap_minor_version_context(); + ctx.expect() + .withf_st(move |arg| *arg == pcap) + .return_once(|_| 7); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + assert_eq!(capture.version(), (5, 7)); + } +} diff --git a/src/capture/inactive.rs b/src/capture/inactive.rs index eac5586bf..bafd3de46 100644 --- a/src/capture/inactive.rs +++ b/src/capture/inactive.rs @@ -37,8 +37,7 @@ impl Capture { }) } - /// Activates an inactive capture created from `Capture::from_device()` or returns - /// an error. + /// Activates an inactive capture created from `Capture::from_device()` or returns an error. pub fn open(self) -> Result, Error> { unsafe { self.check_err(raw::pcap_activate(self.handle.as_ptr()) == 0)?; @@ -46,8 +45,7 @@ impl Capture { } } - /// Set the read timeout for the Capture. By default, this is 0, so it will block - /// indefinitely. + /// Set the read timeout for the Capture. By default, this is 0, so it will block indefinitely. pub fn timeout(self, ms: i32) -> Capture { unsafe { raw::pcap_set_timeout(self.handle.as_ptr(), ms) }; self @@ -174,3 +172,261 @@ pub enum TimestampType { /// The timestamp is not synchronized with the system clock. AdapterUnsynced = 4, } + +#[cfg(test)] +mod tests { + use crate::{ + capture::testmod::test_capture, + raw::testmod::{as_pcap_t, geterr_expect, RAWMTX}, + }; + + use super::*; + + #[test] + fn test_from_device() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_create_context(); + ctx.expect().return_once_st(move |_, _| pcap); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let result = Capture::from_device("some_device"); + assert!(result.is_ok()); + } + + #[test] + fn test_from_device_error() { + let _m = RAWMTX.lock(); + + let ctx = raw::pcap_create_context(); + ctx.expect().return_once_st(|_, _| std::ptr::null_mut()); + + let result = Capture::from_device("some_device"); + assert!(result.is_err()); + } + + #[test] + fn test_open() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_activate_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 0); + + let result = capture.open(); + assert!(result.is_ok()); + } + + #[test] + fn test_open_error() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_activate_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| -1); + + let _err = geterr_expect(pcap); + + let result = capture.open(); + assert!(result.is_err()); + } + + #[test] + fn test_timeout() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_timeout_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.timeout(5); + } + + #[test] + #[cfg(libpcap_1_2_1)] + fn test_timstamp_type() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_tstamp_type_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.tstamp_type(TimestampType::Host); + + // For code coverage of the derive line. + assert_ne!(TimestampType::Host, TimestampType::HostLowPrec); + assert_ne!(TimestampType::Host, TimestampType::HostHighPrec); + } + + #[test] + fn test_promisc() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_promisc_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.promisc(true); + } + + #[cfg(libpcap_1_5_0)] + struct ImmediateModeExpect(raw::__pcap_set_immediate_mode::Context); + + #[cfg(all(windows, not(libpcap_1_5_0)))] + struct ImmediateModeExpect(raw::__pcap_setmintocopy::Context); + + fn immediate_mode_expect(pcap: *mut raw::pcap_t) -> ImmediateModeExpect { + #[cfg(libpcap_1_5_0)] + { + let ctx = raw::pcap_set_immediate_mode_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + ImmediateModeExpect(ctx) + } + #[cfg(all(windows, not(libpcap_1_5_0)))] + { + let ctx = raw::pcap_setmintocopy_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + ImmediateModeExpect(ctx) + } + } + + #[test] + #[cfg(any(libpcap_1_5_0, windows))] + fn test_immediate_mode() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let _ctx = immediate_mode_expect(pcap); + let capture = capture.immediate_mode(true); + + let _ctx = immediate_mode_expect(pcap); + let _capture = capture.immediate_mode(false); + } + + #[test] + #[cfg(not(windows))] + fn test_rfmon() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_rfmon_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.rfmon(true); + } + + #[test] + fn test_buffer_size() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_buffer_size_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.buffer_size(10); + } + + #[test] + #[cfg(libpcap_1_5_0)] + fn test_precision() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_tstamp_precision_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.precision(Precision::Nano); + } + + #[test] + fn test_snaplen() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_set_snaplen_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.snaplen(10); + } +} diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 5c22d2113..1e67a4a3c 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -1,5 +1,7 @@ pub mod activated; pub mod inactive; +#[cfg(all(not(windows), feature = "capture-stream"))] +pub mod selectable; use std::{ ffi::CString, @@ -160,9 +162,13 @@ impl Capture { if success { Ok(()) } else { - Err(unsafe { Error::new(raw::pcap_geterr(self.handle.as_ptr())) }) + Err(self.get_err()) } } + + fn get_err(&self) -> Error { + unsafe { Error::new(raw::pcap_geterr(self.handle.as_ptr())) } + } } impl Drop for Capture { @@ -183,3 +189,101 @@ pub enum Precision { /// Use timestamps with nanosecond precision. Nano = 1, } + +// GRCOV_EXCL_START +#[cfg(test)] +pub mod testmod { + use raw::testmod::RAWMTX; + + use super::*; + + pub struct TestCapture { + pub capture: Capture, + _close_ctx: raw::__pcap_close::Context, + } + + pub fn test_capture(pcap: *mut raw::pcap_t) -> TestCapture { + // Lock must be acquired by caller. + assert!(RAWMTX.try_lock().is_err()); + + let ctx = raw::pcap_close_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + TestCapture { + capture: Capture::::from(NonNull::new(pcap).unwrap()), + _close_ctx: ctx, + } + } +} +// GRCOV_EXCL_STOP + +#[cfg(test)] +mod tests { + use crate::{ + capture::testmod::test_capture, + raw::testmod::{as_pcap_t, RAWMTX}, + }; + + use super::*; + + #[test] + fn test_capture_getters() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + assert!(!capture.is_nonblock()); + assert_eq!(capture.as_ptr(), capture.handle.as_ptr()); + } + + #[test] + #[cfg(windows)] + fn test_min_to_copy() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_setmintocopy_context(); + ctx.expect() + .withf_st(move |arg1, _| *arg1 == pcap) + .return_once(|_, _| 0); + + let _capture = capture.min_to_copy(5); + } + + #[test] + #[cfg(windows)] + fn test_get_event() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_getevent_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 5); + + let handle = unsafe { capture.get_event() }; + assert_eq!(handle, 5); + } + + #[test] + fn test_precision() { + assert_ne!(Precision::Micro, Precision::Nano); + } +} diff --git a/src/capture/selectable.rs b/src/capture/selectable.rs new file mode 100644 index 000000000..55b0fe9d2 --- /dev/null +++ b/src/capture/selectable.rs @@ -0,0 +1,81 @@ +use std::os::unix::io::{AsRawFd, RawFd}; + +use crate::{ + capture::{Activated, Capture, State}, + raw, Error, +}; + +/// Newtype [`Capture`] wrapper that exposes `pcap_get_selectable_fd()`. +pub struct SelectableCapture { + inner: Capture, + fd: RawFd, +} + +impl SelectableCapture { + pub fn new(capture: Capture) -> Result { + let fd = unsafe { raw::pcap_get_selectable_fd(capture.as_ptr()) }; + if fd == -1 { + return Err(Error::InvalidRawFd); + } + Ok(Self { inner: capture, fd }) + } + + pub fn get_inner_mut(&mut self) -> &mut Capture { + &mut self.inner + } +} + +impl AsRawFd for SelectableCapture { + fn as_raw_fd(&self) -> RawFd { + self.fd + } +} + +#[cfg(test)] +mod tests { + use crate::{ + capture::{testmod::test_capture, Active}, + raw::testmod::{as_pcap_t, RAWMTX}, + }; + + use super::*; + + #[test] + fn test_selectable_capture() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_get_selectable_fd_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 5); + + let mut selectable = SelectableCapture::new(capture).unwrap(); + assert!(!selectable.get_inner_mut().is_nonblock()); + assert_eq!(selectable.as_raw_fd(), 5); + } + + #[test] + fn test_selectable_capture_error() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + + let ctx = raw::pcap_get_selectable_fd_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| -1); + + let result = SelectableCapture::new(capture); + assert!(result.is_err()); + } +} diff --git a/src/codec.rs b/src/codec.rs index b687a9df0..c2acabbac 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -10,3 +10,31 @@ pub trait PacketCodec { fn decode(&mut self, packet: Packet<'_>) -> Self::Item; } + +// GRCOV_EXCL_START +#[cfg(test)] +pub mod testmod { + use crate::packet::PacketHeader; + + use super::*; + + pub struct Codec; + + #[derive(Debug, PartialEq, Eq)] + pub struct PacketOwned { + pub header: PacketHeader, + pub data: Box<[u8]>, + } + + impl PacketCodec for Codec { + type Item = PacketOwned; + + fn decode(&mut self, pkt: Packet) -> Self::Item { + PacketOwned { + header: *pkt.header, + data: pkt.data.into(), + } + } + } +} +// GRCOV_EXCL_STOP diff --git a/src/device.rs b/src/device.rs index 9c82e829b..48ca4db92 100644 --- a/src/device.rs +++ b/src/device.rs @@ -3,11 +3,11 @@ use std::{convert::TryFrom, net::IpAddr, ptr}; use bitflags::bitflags; #[cfg(target_os = "windows")] -use windows_sys::Win32::Networking::WinSock::{AF_INET, AF_INET6, SOCKADDR_IN, SOCKADDR_IN6}; +use windows_sys::Win32::Networking::WinSock; use crate::{ capture::{Active, Capture}, - raw, Error, + cstr_to_string, raw, Error, }; bitflags! { @@ -55,7 +55,9 @@ impl From for ConnectionStatus { raw::PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE => ConnectionStatus::NotApplicable, // DeviceFlags::CONNECTION_STATUS should be a 2-bit mask which means that the four // values should cover all the possibilities. + // GRCOV_EXCL_START _ => unreachable!(), + // GRCOV_EXCL_STOP } } } @@ -195,8 +197,8 @@ impl TryFrom<&raw::pcap_if_t> for Device { fn try_from(dev: &raw::pcap_if_t) -> Result { Ok(Device::new( - unsafe { Error::cstr_to_string(dev.name)?.ok_or(Error::InvalidString)? }, - unsafe { Error::cstr_to_string(dev.description)? }, + unsafe { cstr_to_string(dev.name)?.ok_or(Error::InvalidString)? }, + unsafe { cstr_to_string(dev.description)? }, unsafe { Address::new_vec(dev.addresses) }, DeviceFlags::from(dev.flags), )) @@ -237,7 +239,7 @@ impl Address { }) } - #[cfg(not(target_os = "windows"))] + #[cfg(not(windows))] unsafe fn convert_sockaddr(ptr: *const libc::sockaddr) -> Option { if ptr.is_null() { return None; @@ -258,20 +260,20 @@ impl Address { } } - #[cfg(target_os = "windows")] + #[cfg(windows)] unsafe fn convert_sockaddr(ptr: *const libc::sockaddr) -> Option { if ptr.is_null() { return None; } match (*ptr).sa_family as u32 { - AF_INET => { - let ptr: *const SOCKADDR_IN = std::mem::transmute(ptr); + WinSock::AF_INET => { + let ptr: *const WinSock::SOCKADDR_IN = std::mem::transmute(ptr); let addr: [u8; 4] = ((*ptr).sin_addr.S_un.S_addr).to_ne_bytes(); Some(IpAddr::from(addr)) } - AF_INET6 => { - let ptr: *const SOCKADDR_IN6 = std::mem::transmute(ptr); + WinSock::AF_INET6 => { + let ptr: *const WinSock::SOCKADDR_IN6 = std::mem::transmute(ptr); let addr = (*ptr).sin6_addr.u.Byte; Some(IpAddr::from(addr)) } @@ -280,3 +282,403 @@ impl Address { } } } + +#[cfg(test)] +mod tests { + use std::ffi::CString; + + use crate::raw::testmod::{as_pcap_t, RAWMTX}; + + use super::*; + + #[cfg(not(windows))] + enum Sockaddr { + SockaddrIn(libc::sockaddr_in), + SockaddrIn6(libc::sockaddr_in6), + } + + #[cfg(windows)] + enum Sockaddr { + SockaddrIn(WinSock::SOCKADDR_IN), + SockaddrIn6(WinSock::SOCKADDR_IN6), + } + + impl Sockaddr { + fn as_mut_ptr(&mut self) -> *mut libc::sockaddr { + match self { + Sockaddr::SockaddrIn(ref mut sin) => sin as *mut _ as _, + Sockaddr::SockaddrIn6(ref mut sin6) => sin6 as *mut _ as _, + } + } + + fn set_family(&mut self, family: u16) { + // Annoyingly this differs between Linux (u16) and Mac (u8). + #[cfg(not(windows))] + let family = family as libc::sa_family_t; + + match self { + Sockaddr::SockaddrIn(ref mut sin) => sin.sin_family = family, + Sockaddr::SockaddrIn6(ref mut sin6) => sin6.sin6_family = family, + } + } + } + + static IF1_NAME: &str = "if1"; + static IF2_NAME: &str = "if2"; + static IF1_DESC: &str = "if1 desc"; + static IF2_DESC: &str = "if2 desc"; + + fn devs() -> Vec { + let mut devs = vec![ + raw::pcap_if_t { + next: std::ptr::null_mut(), + name: CString::new(IF1_NAME).unwrap().into_raw(), + description: CString::new(IF1_DESC).unwrap().into_raw(), + addresses: std::ptr::null_mut(), + flags: (raw::PCAP_IF_LOOPBACK | raw::PCAP_IF_UP), + }, + raw::pcap_if_t { + next: std::ptr::null_mut(), + name: CString::new(IF2_NAME).unwrap().into_raw(), + description: CString::new(IF2_DESC).unwrap().into_raw(), + addresses: std::ptr::null_mut(), + flags: 0, + }, + ]; + devs[0].next = &mut devs[1]; + devs + } + + trait InetAddressV4 { + fn new() -> Self; + fn set_addr(&mut self, addr: u32); + } + + #[cfg(not(windows))] + impl InetAddressV4 for libc::sockaddr_in { + fn new() -> Self { + let mut addr: Self = unsafe { std::mem::zeroed() }; + addr.sin_family = libc::AF_INET as libc::sa_family_t; + addr + } + + fn set_addr(&mut self, addr: u32) { + self.sin_addr.s_addr = addr; + } + } + + #[cfg(windows)] + impl InetAddressV4 for WinSock::SOCKADDR_IN { + fn new() -> Self { + let mut addr: Self = unsafe { std::mem::zeroed() }; + // The cast is only necessary due to a bug in windows_sys@v0.36.1 + addr.sin_family = WinSock::AF_INET as u16; + addr + } + + fn set_addr(&mut self, addr: u32) { + self.sin_addr.S_un.S_addr = addr; + } + } + + fn sockaddr_ipv4() -> Sockaddr { + #[cfg(not(windows))] + let mut addr: libc::sockaddr_in = InetAddressV4::new(); + #[cfg(windows)] + let mut addr: WinSock::SOCKADDR_IN = InetAddressV4::new(); + + addr.sin_port = 1075; + addr.set_addr(0x4200000A); + + Sockaddr::SockaddrIn(addr) + } + + trait InetAddressV6 { + fn new() -> Self; + fn set_octet(&mut self, index: usize, octet: u8); + } + + #[cfg(not(windows))] + impl InetAddressV6 for libc::sockaddr_in6 { + fn new() -> Self { + let mut addr: Self = unsafe { std::mem::zeroed() }; + addr.sin6_family = libc::AF_INET6 as libc::sa_family_t; + addr.sin6_addr.s6_addr[0] = 0xFE; + addr.sin6_addr.s6_addr[1] = 0x80; + addr + } + + fn set_octet(&mut self, index: usize, octet: u8) { + self.sin6_addr.s6_addr[index] = octet; + } + } + + #[cfg(windows)] + impl InetAddressV6 for WinSock::SOCKADDR_IN6 { + fn new() -> Self { + let mut addr: Self = unsafe { std::mem::zeroed() }; + // The cast is only necessary due to a bug in windows_sys@v0.36.1 + addr.sin6_family = WinSock::AF_INET6 as u16; + unsafe { + addr.sin6_addr.u.Byte[0] = 0xFE; + addr.sin6_addr.u.Byte[1] = 0x80; + } + addr + } + + fn set_octet(&mut self, index: usize, octet: u8) { + unsafe { self.sin6_addr.u.Byte[index] = octet }; + } + } + + fn sockaddr_ipv6() -> Sockaddr { + #[cfg(not(windows))] + let mut addr: libc::sockaddr_in6 = InetAddressV6::new(); + #[cfg(windows)] + let mut addr: WinSock::SOCKADDR_IN6 = InetAddressV6::new(); + + addr.sin6_port = 1075; + addr.set_octet(15, 0x42); + + Sockaddr::SockaddrIn6(addr) + } + + impl From<&mut Sockaddr> for raw::pcap_addr_t { + fn from(value: &mut Sockaddr) -> Self { + raw::pcap_addr_t { + next: std::ptr::null_mut(), + addr: value.as_mut_ptr(), + netmask: std::ptr::null_mut(), + broadaddr: std::ptr::null_mut(), + dstaddr: std::ptr::null_mut(), + } + } + } + + #[test] + fn test_device_flags() { + let flags = DeviceFlags::from( + raw::PCAP_IF_LOOPBACK | raw::PCAP_IF_UP | raw::PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE, + ); + + assert!(flags.is_loopback()); + assert!(flags.is_up()); + assert!(flags.contains(IfFlags::LOOPBACK | IfFlags::UP)); + + assert!(!flags.is_running()); + assert!(!flags.is_wireless()); + + assert_ne!(flags.connection_status, ConnectionStatus::Unknown); + assert_ne!(flags.connection_status, ConnectionStatus::Connected); + assert_ne!(flags.connection_status, ConnectionStatus::Disconnected); + assert_eq!(flags.connection_status, ConnectionStatus::NotApplicable); + + assert!(!format!("{:?}", flags).is_empty()); + } + + #[test] + fn test_connection_status() { + let flags = raw::PCAP_IF_CONNECTION_STATUS_UNKNOWN; + assert_eq!(ConnectionStatus::from(flags), ConnectionStatus::Unknown); + + let flags = raw::PCAP_IF_CONNECTION_STATUS_CONNECTED; + assert_eq!(ConnectionStatus::from(flags), ConnectionStatus::Connected); + + let flags = raw::PCAP_IF_CONNECTION_STATUS_DISCONNECTED; + assert_eq!( + ConnectionStatus::from(flags), + ConnectionStatus::Disconnected + ); + + let flags = raw::PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE; + assert_eq!( + ConnectionStatus::from(flags), + ConnectionStatus::NotApplicable + ); + } + + #[test] + fn test_into_capture() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let ctx = raw::pcap_create_context(); + ctx.expect().return_once_st(move |_, _| pcap); + + let ctx = raw::pcap_activate_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once(|_| 0); + + let ctx = raw::pcap_close_context(); + ctx.expect() + .withf_st(move |ptr| *ptr == pcap) + .return_once(|_| {}); + + let device: Device = "device".into(); + let _capture: Capture = device.clone().open().unwrap(); + + assert!(!format!("{:?}", device).is_empty()); + } + + #[test] + fn test_lookup() { + let _m = RAWMTX.lock(); + + let ctx = raw::pcap_findalldevs_context(); + ctx.expect().return_once_st(move |arg1, _| { + unsafe { *arg1 = std::ptr::null_mut() }; + 0 + }); + + let ctx = raw::pcap_freealldevs_context(); + ctx.expect().return_once(move |_| {}); + + let device = Device::lookup().unwrap(); + assert!(device.is_none()); + + let mut devs = devs(); + let mut addrs = sockaddr_ipv4(); + let mut pcap_addr = (&mut addrs).into(); + devs[0].addresses = &mut pcap_addr; + let devs_ptr = devs.as_mut_ptr(); + + let ctx = raw::pcap_findalldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once_st(move |arg1, _| { + unsafe { *arg1 = devs_ptr }; + 0 + }); + + let ctx = raw::pcap_freealldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once(move |_| {}); + + let device = Device::lookup().unwrap().unwrap(); + assert_eq!(&device.name, IF1_NAME); + assert_eq!(&device.desc.unwrap(), IF1_DESC); + assert_eq!(device.addresses.len(), 1); + assert!(device.addresses[0].addr.is_ipv4()); + + let ctx = raw::pcap_findalldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once_st(move |_, _| -1); + + let ctx = raw::pcap_freealldevs_context(); + ctx.checkpoint(); + + let result = Device::lookup(); + assert!(result.is_err()); + } + + #[test] + fn test_list() { + let _m = RAWMTX.lock(); + + let ctx = raw::pcap_findalldevs_context(); + ctx.expect().return_once_st(move |arg1, _| { + unsafe { *arg1 = std::ptr::null_mut() }; + 0 + }); + + let ctx = raw::pcap_freealldevs_context(); + ctx.expect().return_once(move |_| {}); + + let devices = Device::list().unwrap(); + assert!(devices.is_empty()); + + let mut devs = devs(); + let mut ipv4s = sockaddr_ipv4(); + let mut ipv6s = sockaddr_ipv6(); + let mut pcap_addr: raw::pcap_addr_t = (&mut ipv4s).into(); + let mut pcap_addr6: raw::pcap_addr_t = (&mut ipv6s).into(); + pcap_addr.next = &mut pcap_addr6; + devs[1].addresses = &mut pcap_addr; + let devs_ptr = devs.as_mut_ptr(); + + let ctx = raw::pcap_findalldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once_st(move |arg1, _| { + unsafe { *arg1 = devs_ptr }; + 0 + }); + + let ctx = raw::pcap_freealldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once(move |_| {}); + + let devices = Device::list().unwrap(); + assert_eq!(devices.len(), devs.len()); + + assert_eq!(&devices[0].name, IF1_NAME); + assert_eq!(devices[0].desc.as_ref().unwrap(), IF1_DESC); + assert_eq!(devices[0].addresses.len(), 0); + + assert_eq!(&devices[1].name, IF2_NAME); + assert_eq!(devices[1].desc.as_ref().unwrap(), IF2_DESC); + assert_eq!(devices[1].addresses.len(), 2); + assert!(devices[1].addresses[0].addr.is_ipv4()); + assert!(devices[1].addresses[1].addr.is_ipv6()); + + let ctx = raw::pcap_findalldevs_context(); + ctx.checkpoint(); + ctx.expect().return_once_st(move |_, _| -1); + + let ctx = raw::pcap_freealldevs_context(); + ctx.checkpoint(); + + let result = Device::list(); + assert!(result.is_err()); + } + + #[test] + fn test_address_ipv4() { + let mut addr = sockaddr_ipv4(); + let pcap_addr: raw::pcap_addr_t = (&mut addr).into(); + + let address = unsafe { Address::new(&pcap_addr) }.unwrap(); + + assert!(address.addr.is_ipv4()); + assert_eq!(address.addr.to_string(), "10.0.0.66"); + + assert!(address.netmask.is_none()); + assert!(address.broadcast_addr.is_none()); + assert!(address.dst_addr.is_none()); + + assert!(!format!("{:?}", address).is_empty()); + } + + #[test] + fn test_address_family() { + let mut addr = sockaddr_ipv4(); + + #[cfg(not(windows))] + addr.set_family(libc::AF_IPX as u16); + #[cfg(windows)] + addr.set_family(WinSock::AF_IPX); + + let pcap_addr: raw::pcap_addr_t = (&mut addr).into(); + + let address = unsafe { Address::new(&pcap_addr) }; + assert!(address.is_none()); + } + + #[test] + fn test_address_ipv6() { + let mut addr = sockaddr_ipv6(); + let pcap_addr: raw::pcap_addr_t = (&mut addr).into(); + + let address = unsafe { Address::new(&pcap_addr) }.unwrap(); + + assert!(address.addr.is_ipv6()); + assert_eq!(address.addr.to_string(), "fe80::42"); + + assert!(address.netmask.is_none()); + assert!(address.broadcast_addr.is_none()); + assert!(address.dst_addr.is_none()); + + assert!(!format!("{:?}", address).is_empty()); + } +} diff --git a/src/lib.rs b/src/lib.rs index c1620a4df..39a2374ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,11 +72,7 @@ mod packet; #[cfg(not(windows))] pub use capture::activated::open_raw_fd; pub use capture::{ - activated::{ - dead::{BpfInstruction, BpfProgram}, - iterator::PacketIter, - Direction, Savefile, Stat, - }, + activated::{iterator::PacketIter, BpfInstruction, BpfProgram, Direction, Savefile, Stat}, inactive::TimestampType, {Activated, Active, Capture, Dead, Inactive, Offline, Precision, State}, }; @@ -133,21 +129,12 @@ pub enum Error { impl Error { unsafe fn new(ptr: *const libc::c_char) -> Error { - match Self::cstr_to_string(ptr) { + match cstr_to_string(ptr) { Err(e) => e as Error, Ok(string) => PcapError(string.unwrap_or_default()), } } - unsafe fn cstr_to_string(ptr: *const libc::c_char) -> Result, Error> { - let string = if ptr.is_null() { - None - } else { - Some(CStr::from_ptr(ptr as _).to_str()?.to_owned()) - }; - Ok(string) - } - fn with_errbuf(func: F) -> Result where F: FnOnce(*mut libc::c_char) -> Result, @@ -157,6 +144,15 @@ impl Error { } } +unsafe fn cstr_to_string(ptr: *const libc::c_char) -> Result, Error> { + let string = if ptr.is_null() { + None + } else { + Some(CStr::from_ptr(ptr as _).to_str()?.to_owned()) + }; + Ok(string) +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { @@ -178,6 +174,7 @@ impl fmt::Display for Error { } } +// Using description is deprecated. Remove in next version. impl std::error::Error for Error { fn description(&self) -> &str { match *self { @@ -220,7 +217,7 @@ impl From for Error { impl From for Error { fn from(obj: std::io::Error) -> Error { - IoError(obj.kind()) + obj.kind().into() } } @@ -229,3 +226,57 @@ impl From for Error { IoError(obj) } } + +#[cfg(test)] +mod tests { + use std::error::Error as StdError; + use std::{ffi::CString, io}; + + use super::*; + + #[test] + fn test_error_invalid_utf8() { + let bytes: [u8; 8] = [0x78, 0xfe, 0xe9, 0x89, 0x00, 0x00, 0xed, 0x4f]; + let error = unsafe { Error::new(&bytes as *const _ as _) }; + assert!(matches!(error, Error::MalformedError(_))); + } + + #[test] + fn test_error_null() { + let error = unsafe { Error::new(std::ptr::null()) }; + assert_eq!(error, Error::PcapError("".to_string())); + } + + #[test] + #[allow(deprecated)] + fn test_errors() { + let mut errors: Vec = vec![]; + + let bytes: [u8; 8] = [0x78, 0xfe, 0xe9, 0x89, 0x00, 0x00, 0xed, 0x4f]; + let cstr = unsafe { CStr::from_ptr(&bytes as *const _ as _) }; + + errors.push(cstr.to_str().unwrap_err().into()); + errors.push(Error::InvalidString); + errors.push(Error::PcapError("git rekt".to_string())); + errors.push(Error::InvalidLinktype); + errors.push(Error::TimeoutExpired); + errors.push(Error::NoMorePackets); + errors.push(Error::NonNonBlock); + errors.push(Error::InsufficientMemory); + errors.push(CString::new(b"f\0oo".to_vec()).unwrap_err().into()); + errors.push(io::Error::new(io::ErrorKind::Interrupted, "error").into()); + #[cfg(not(windows))] + errors.push(Error::InvalidRawFd); + errors.push(Error::ErrnoError(errno::Errno(125))); + errors.push(Error::BufferOverflow); + + for error in errors.iter() { + assert!(!error.to_string().is_empty()); + assert!(!error.description().is_empty()); + match error { + Error::MalformedError(_) => assert!(error.cause().is_some()), + _ => assert!(error.cause().is_none()), + } + } + } +} diff --git a/src/linktype.rs b/src/linktype.rs index 0f1c91966..26044e891 100644 --- a/src/linktype.rs +++ b/src/linktype.rs @@ -1,6 +1,6 @@ use std::ffi::CString; -use crate::{raw, Error}; +use crate::{cstr_to_string, raw, Error}; /// This is a datalink link type. /// @@ -18,13 +18,13 @@ pub struct Linktype(pub i32); impl Linktype { /// Gets the name of the link type, such as EN10MB pub fn get_name(&self) -> Result { - unsafe { Error::cstr_to_string(raw::pcap_datalink_val_to_name(self.0)) }? + unsafe { cstr_to_string(raw::pcap_datalink_val_to_name(self.0)) }? .ok_or(Error::InvalidLinktype) } /// Gets the description of a link type. pub fn get_description(&self) -> Result { - unsafe { Error::cstr_to_string(raw::pcap_datalink_val_to_description(self.0)) }? + unsafe { cstr_to_string(raw::pcap_datalink_val_to_description(self.0)) }? .ok_or(Error::InvalidLinktype) } @@ -168,3 +168,54 @@ impl Linktype { pub const USB_2_0: Self = Self(288); pub const ATSC_ALP: Self = Self(289); } + +#[cfg(test)] +mod tests { + use crate::raw::testmod::RAWMTX; + + use super::*; + + #[test] + fn test_get_name() { + let _m = RAWMTX.lock(); + let name = "datalink name"; + + let cstr = CString::new(name).unwrap(); + let ctx = raw::pcap_datalink_val_to_name_context(); + ctx.expect().return_once(|_| cstr.into_raw()); + + let linktype_name = Linktype::ARCNET_LINUX.get_name().unwrap(); + assert_eq!(&linktype_name, name); + } + + #[test] + fn test_get_description() { + let _m = RAWMTX.lock(); + let desc = "datalink description"; + + let cstr = CString::new(desc).unwrap(); + let ctx = raw::pcap_datalink_val_to_description_context(); + ctx.expect().return_once(|_| cstr.into_raw()); + + let linktype_name = Linktype::ARCNET_LINUX.get_description().unwrap(); + assert_eq!(&linktype_name, desc); + } + + #[test] + fn test_from_name() { + let _m = RAWMTX.lock(); + + let ctx = raw::pcap_datalink_name_to_val_context(); + ctx.expect().return_once(|_| 7); + + let linktype = Linktype::from_name("git rekt scrub").unwrap(); + assert_eq!(linktype, Linktype::ARCNET_BSD); + + let ctx = raw::pcap_datalink_name_to_val_context(); + ctx.checkpoint(); + ctx.expect().return_once(|_| -1); + + let err = Linktype::from_name("git rekt scrub").unwrap_err(); + assert_eq!(err, Error::InvalidLinktype); + } +} diff --git a/src/packet.rs b/src/packet.rs index ba37caa09..240ffdbca 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -66,9 +66,31 @@ mod tests { use super::*; + static HEADER: PacketHeader = PacketHeader { + ts: libc::timeval { + tv_sec: 5, + tv_usec: 50, + }, + caplen: 5, + len: 9, + }; + #[test] - fn test_struct_size() { + fn test_packet_header_size() { use std::mem::size_of; assert_eq!(size_of::(), size_of::()); } + + #[test] + fn test_packet_header_clone() { + // For code coverag purposes. + #[allow(clippy::clone_on_copy)] + let header_clone = HEADER.clone(); + assert_eq!(header_clone, HEADER); + } + + #[test] + fn test_packet_header_display() { + assert!(!format!("{:?}", HEADER).is_empty()); + } } diff --git a/src/raw.rs b/src/raw.rs index 6188184ae..0961ee0d3 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -1,10 +1,11 @@ +// GRCOV_EXCL_START #![allow(dead_code)] #![allow(non_camel_case_types)] use libc::{c_char, c_int, c_uchar, c_uint, c_ushort, sockaddr, timeval, FILE}; -#[cfg(windows)] -use windows_sys::Win32::Foundation::HANDLE; +#[cfg(test)] +use mockall::automock; pub const PCAP_IF_LOOPBACK: u32 = 0x00000001; pub const PCAP_IF_UP: u32 = 0x00000002; @@ -102,172 +103,250 @@ pub struct pcap_send_queue { pub type pcap_handler = Option ()>; -extern "C" { - // [OBSOLETE] pub fn pcap_lookupdev(arg1: *mut c_char) -> *mut c_char; - // pub fn pcap_lookupnet(arg1: *const c_char, arg2: *mut c_uint, arg3: *mut c_uint, - // arg4: *mut c_char) -> c_int; - pub fn pcap_create(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; - pub fn pcap_set_snaplen(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_set_promisc(arg1: *mut pcap_t, arg2: c_int) -> c_int; - // pub fn pcap_can_set_rfmon(arg1: *mut pcap_t) -> c_int; - pub fn pcap_set_timeout(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_set_buffer_size(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_activate(arg1: *mut pcap_t) -> c_int; - // pub fn pcap_open_live(arg1: *const c_char, arg2: c_int, arg3: c_int, arg4: c_int, - // arg5: *mut c_char) -> *mut pcap_t; - pub fn pcap_open_dead(arg1: c_int, arg2: c_int) -> *mut pcap_t; - pub fn pcap_open_offline(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; - pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t; - pub fn pcap_close(arg1: *mut pcap_t); - // pub fn pcap_loop(arg1: *mut pcap_t, arg2: c_int, - // arg3: pcap_handler, arg4: *mut c_uchar) -> c_int; - // pub fn pcap_dispatch(arg1: *mut pcap_t, arg2: c_int, arg3: pcap_handler, - // arg4: *mut c_uchar)-> c_int; - // pub fn pcap_next(arg1: *mut pcap_t, arg2: *mut pcap_pkthdr) -> *const c_uchar; - pub fn pcap_next_ex( - arg1: *mut pcap_t, - arg2: *mut *mut pcap_pkthdr, - arg3: *mut *const c_uchar, - ) -> c_int; - // pub fn pcap_breakloop(arg1: *mut pcap_t); - pub fn pcap_stats(arg1: *mut pcap_t, arg2: *mut pcap_stat) -> c_int; - pub fn pcap_setfilter(arg1: *mut pcap_t, arg2: *mut bpf_program) -> c_int; - pub fn pcap_setdirection(arg1: *mut pcap_t, arg2: pcap_direction_t) -> c_int; - // pub fn pcap_getnonblock(arg1: *mut pcap_t, arg2: *mut c_char) -> c_int; - pub fn pcap_setnonblock(arg1: *mut pcap_t, arg2: c_int, arg3: *mut c_char) -> c_int; - pub fn pcap_sendpacket(arg1: *mut pcap_t, arg2: *const c_uchar, arg3: c_int) -> c_int; - // pub fn pcap_statustostr(arg1: c_int) -> *const c_char; - // pub fn pcap_strerror(arg1: c_int) -> *const c_char; - pub fn pcap_geterr(arg1: *mut pcap_t) -> *mut c_char; - // pub fn pcap_perror(arg1: *mut pcap_t, arg2: *mut c_char); - pub fn pcap_compile( - arg1: *mut pcap_t, - arg2: *mut bpf_program, - arg3: *const c_char, - arg4: c_int, - arg5: c_uint, - ) -> c_int; - // pub fn pcap_compile_nopcap(arg1: c_int, arg2: c_int, arg3: *mut bpf_program, - // arg4: *const c_char, arg5: c_int, arg6: c_uint) -> c_int; - pub fn pcap_freecode(arg1: *mut bpf_program); - pub fn pcap_offline_filter( - arg1: *const bpf_program, - arg2: *const pcap_pkthdr, - arg3: *const c_uchar, - ) -> c_int; - pub fn pcap_datalink(arg1: *mut pcap_t) -> c_int; - // pub fn pcap_datalink_ext(arg1: *mut pcap_t) -> c_int; - pub fn pcap_list_datalinks(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; - pub fn pcap_set_datalink(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_free_datalinks(arg1: *mut c_int); - pub fn pcap_datalink_name_to_val(arg1: *const c_char) -> c_int; - pub fn pcap_datalink_val_to_name(arg1: c_int) -> *const c_char; - pub fn pcap_datalink_val_to_description(arg1: c_int) -> *const c_char; - // pub fn pcap_snapshot(arg1: *mut pcap_t) -> c_int; - // pub fn pcap_is_swapped(arg1: *mut pcap_t) -> c_int; - pub fn pcap_major_version(arg1: *mut pcap_t) -> c_int; - pub fn pcap_minor_version(arg1: *mut pcap_t) -> c_int; - // pub fn pcap_file(arg1: *mut pcap_t) -> *mut FILE; - pub fn pcap_fileno(arg1: *mut pcap_t) -> c_int; - pub fn pcap_dump_open(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; - pub fn pcap_dump_fopen(arg1: *mut pcap_t, fp: *mut FILE) -> *mut pcap_dumper_t; - // pub fn pcap_dump_file(arg1: *mut pcap_dumper_t) -> *mut FILE; - // pub fn pcap_dump_ftell(arg1: *mut pcap_dumper_t) -> c_long; - pub fn pcap_dump_flush(arg1: *mut pcap_dumper_t) -> c_int; - pub fn pcap_dump_close(arg1: *mut pcap_dumper_t); - pub fn pcap_dump(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar); - pub fn pcap_findalldevs(arg1: *mut *mut pcap_if_t, arg2: *mut c_char) -> c_int; - pub fn pcap_freealldevs(arg1: *mut pcap_if_t); - // pub fn pcap_lib_version() -> *const c_char; - // pub fn bpf_image(arg1: *const bpf_insn, arg2: c_int) -> *mut c_char; - // pub fn bpf_dump(arg1: *const bpf_program, arg2: c_int); - pub fn pcap_get_selectable_fd(arg1: *mut pcap_t) -> c_int; -} +#[cfg_attr(test, automock)] +pub mod ffi { + use super::*; -#[cfg(libpcap_1_2_1)] -extern "C" { - // pub fn pcap_free_tstamp_types(arg1: *mut c_int) -> (); - // pub fn pcap_list_tstamp_types(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; - // pub fn pcap_tstamp_type_name_to_val(arg1: *const c_char) -> c_int; - // pub fn pcap_tstamp_type_val_to_description(arg1: c_int) -> *const c_char; - // pub fn pcap_tstamp_type_val_to_name(arg1: c_int) -> *const c_char; - pub fn pcap_set_tstamp_type(arg1: *mut pcap_t, arg2: c_int) -> c_int; -} + extern "C" { + // [OBSOLETE] pub fn pcap_lookupdev(arg1: *mut c_char) -> *mut c_char; + // pub fn pcap_lookupnet(arg1: *const c_char, arg2: *mut c_uint, arg3: *mut c_uint, + // arg4: *mut c_char) -> c_int; + pub fn pcap_create(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_set_snaplen(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_promisc(arg1: *mut pcap_t, arg2: c_int) -> c_int; + // pub fn pcap_can_set_rfmon(arg1: *mut pcap_t) -> c_int; + pub fn pcap_set_timeout(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_buffer_size(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_activate(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_open_live(arg1: *const c_char, arg2: c_int, arg3: c_int, arg4: c_int, + // arg5: *mut c_char) -> *mut pcap_t; + pub fn pcap_open_dead(arg1: c_int, arg2: c_int) -> *mut pcap_t; + pub fn pcap_open_offline(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t; + pub fn pcap_close(arg1: *mut pcap_t); + // pub fn pcap_loop(arg1: *mut pcap_t, arg2: c_int, + // arg3: pcap_handler, arg4: *mut c_uchar) -> c_int; + // pub fn pcap_dispatch(arg1: *mut pcap_t, arg2: c_int, arg3: pcap_handler, + // arg4: *mut c_uchar)-> c_int; + // pub fn pcap_next(arg1: *mut pcap_t, arg2: *mut pcap_pkthdr) -> *const c_uchar; + pub fn pcap_next_ex( + arg1: *mut pcap_t, + arg2: *mut *mut pcap_pkthdr, + arg3: *mut *const c_uchar, + ) -> c_int; + // pub fn pcap_breakloop(arg1: *mut pcap_t); + pub fn pcap_stats(arg1: *mut pcap_t, arg2: *mut pcap_stat) -> c_int; + pub fn pcap_setfilter(arg1: *mut pcap_t, arg2: *mut bpf_program) -> c_int; + pub fn pcap_setdirection(arg1: *mut pcap_t, arg2: pcap_direction_t) -> c_int; + // pub fn pcap_getnonblock(arg1: *mut pcap_t, arg2: *mut c_char) -> c_int; + pub fn pcap_setnonblock(arg1: *mut pcap_t, arg2: c_int, arg3: *mut c_char) -> c_int; + pub fn pcap_sendpacket(arg1: *mut pcap_t, arg2: *const c_uchar, arg3: c_int) -> c_int; + // pub fn pcap_statustostr(arg1: c_int) -> *const c_char; + // pub fn pcap_strerror(arg1: c_int) -> *const c_char; + pub fn pcap_geterr(arg1: *mut pcap_t) -> *mut c_char; + // pub fn pcap_perror(arg1: *mut pcap_t, arg2: *mut c_char); + pub fn pcap_compile( + arg1: *mut pcap_t, + arg2: *mut bpf_program, + arg3: *const c_char, + arg4: c_int, + arg5: c_uint, + ) -> c_int; + // pub fn pcap_compile_nopcap(arg1: c_int, arg2: c_int, arg3: *mut bpf_program, + // arg4: *const c_char, arg5: c_int, arg6: c_uint) -> c_int; + pub fn pcap_freecode(arg1: *mut bpf_program); + pub fn pcap_offline_filter( + arg1: *const bpf_program, + arg2: *const pcap_pkthdr, + arg3: *const c_uchar, + ) -> c_int; + pub fn pcap_datalink(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_datalink_ext(arg1: *mut pcap_t) -> c_int; + pub fn pcap_list_datalinks(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; + pub fn pcap_set_datalink(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_free_datalinks(arg1: *mut c_int); + pub fn pcap_datalink_name_to_val(arg1: *const c_char) -> c_int; + pub fn pcap_datalink_val_to_name(arg1: c_int) -> *const c_char; + pub fn pcap_datalink_val_to_description(arg1: c_int) -> *const c_char; + // pub fn pcap_snapshot(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_is_swapped(arg1: *mut pcap_t) -> c_int; + pub fn pcap_major_version(arg1: *mut pcap_t) -> c_int; + pub fn pcap_minor_version(arg1: *mut pcap_t) -> c_int; + // pub fn pcap_file(arg1: *mut pcap_t) -> *mut FILE; + pub fn pcap_fileno(arg1: *mut pcap_t) -> c_int; + pub fn pcap_dump_open(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; + pub fn pcap_dump_fopen(arg1: *mut pcap_t, fp: *mut FILE) -> *mut pcap_dumper_t; + // pub fn pcap_dump_file(arg1: *mut pcap_dumper_t) -> *mut FILE; + // pub fn pcap_dump_ftell(arg1: *mut pcap_dumper_t) -> c_long; + pub fn pcap_dump_flush(arg1: *mut pcap_dumper_t) -> c_int; + pub fn pcap_dump_close(arg1: *mut pcap_dumper_t); + pub fn pcap_dump(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar); + pub fn pcap_findalldevs(arg1: *mut *mut pcap_if_t, arg2: *mut c_char) -> c_int; + pub fn pcap_freealldevs(arg1: *mut pcap_if_t); + // pub fn pcap_lib_version() -> *const c_char; + // pub fn bpf_image(arg1: *const bpf_insn, arg2: c_int) -> *mut c_char; + // pub fn bpf_dump(arg1: *const bpf_program, arg2: c_int); + pub fn pcap_get_selectable_fd(arg1: *mut pcap_t) -> c_int; + } -#[cfg(libpcap_1_5_0)] -extern "C" { - pub fn pcap_fopen_offline_with_tstamp_precision( - arg1: *mut FILE, - arg2: c_uint, - arg3: *mut c_char, - ) -> *mut pcap_t; - // pub fn pcap_get_tstamp_precision(arg1: *mut pcap_t) -> c_int; - pub fn pcap_open_dead_with_tstamp_precision( - arg1: c_int, - arg2: c_int, - arg3: c_uint, - ) -> *mut pcap_t; - pub fn pcap_open_offline_with_tstamp_precision( - arg1: *const c_char, - arg2: c_uint, - arg3: *mut c_char, - ) -> *mut pcap_t; - pub fn pcap_set_immediate_mode(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_set_tstamp_precision(arg1: *mut pcap_t, arg2: c_int) -> c_int; -} + #[cfg(libpcap_1_2_1)] + extern "C" { + // pub fn pcap_free_tstamp_types(arg1: *mut c_int) -> (); + // pub fn pcap_list_tstamp_types(arg1: *mut pcap_t, arg2: *mut *mut c_int) -> c_int; + // pub fn pcap_tstamp_type_name_to_val(arg1: *const c_char) -> c_int; + // pub fn pcap_tstamp_type_val_to_description(arg1: c_int) -> *const c_char; + // pub fn pcap_tstamp_type_val_to_name(arg1: c_int) -> *const c_char; + pub fn pcap_set_tstamp_type(arg1: *mut pcap_t, arg2: c_int) -> c_int; + } -#[cfg(libpcap_1_7_2)] -extern "C" { - pub fn pcap_dump_open_append(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; -} + #[cfg(libpcap_1_5_0)] + extern "C" { + pub fn pcap_fopen_offline_with_tstamp_precision( + arg1: *mut FILE, + arg2: c_uint, + arg3: *mut c_char, + ) -> *mut pcap_t; + // pub fn pcap_get_tstamp_precision(arg1: *mut pcap_t) -> c_int; + pub fn pcap_open_dead_with_tstamp_precision( + arg1: c_int, + arg2: c_int, + arg3: c_uint, + ) -> *mut pcap_t; + pub fn pcap_open_offline_with_tstamp_precision( + arg1: *const c_char, + arg2: c_uint, + arg3: *mut c_char, + ) -> *mut pcap_t; + pub fn pcap_set_immediate_mode(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_set_tstamp_precision(arg1: *mut pcap_t, arg2: c_int) -> c_int; + } + + #[cfg(libpcap_1_7_2)] + extern "C" { + pub fn pcap_dump_open_append(arg1: *mut pcap_t, arg2: *const c_char) -> *mut pcap_dumper_t; + } -#[cfg(libpcap_1_9_0)] -extern "C" { - // pcap_bufsize - // pcap_createsrcstr - // pcap_dump_ftell64 - // pcap_findalldevs_ex - // pcap_get_required_select_timeout - // pcap_open - // pcap_parsesrcstr - // pcap_remoteact_accept - // pcap_remoteact_cleanup - // pcap_remoteact_close - // pcap_remoteact_list - // pcap_set_protocol_linux - // pcap_setsampling + #[cfg(libpcap_1_9_0)] + extern "C" { + // pcap_bufsize + // pcap_createsrcstr + // pcap_dump_ftell64 + // pcap_findalldevs_ex + // pcap_get_required_select_timeout + // pcap_open + // pcap_parsesrcstr + // pcap_remoteact_accept + // pcap_remoteact_cleanup + // pcap_remoteact_close + // pcap_remoteact_list + // pcap_set_protocol_linux + // pcap_setsampling + } + + #[cfg(libpcap_1_9_1)] + extern "C" { + // pcap_datalink_val_to_description_or_dlt + } } -#[cfg(libpcap_1_9_1)] -extern "C" { - // pcap_datalink_val_to_description_or_dlt +#[cfg(not(windows))] +#[cfg_attr(test, automock)] +pub mod ffi_unix { + use super::*; + + #[link(name = "pcap")] + extern "C" { + // pub fn pcap_inject(arg1: *mut pcap_t, arg2: *const c_void, arg3: size_t) -> c_int; + pub fn pcap_set_rfmon(arg1: *mut pcap_t, arg2: c_int) -> c_int; + } } #[cfg(windows)] -pub const WINPCAP_MINTOCOPY_DEFAULT: c_int = 16000; +#[cfg_attr(test, automock)] +pub mod ffi_windows { + use windows_sys::Win32::Foundation::HANDLE; -#[cfg(windows)] -#[link(name = "wpcap")] -extern "C" { - pub fn pcap_setmintocopy(arg1: *mut pcap_t, arg2: c_int) -> c_int; - pub fn pcap_getevent(p: *mut pcap_t) -> HANDLE; - pub fn pcap_sendqueue_alloc(memsize: c_uint) -> *mut pcap_send_queue; - pub fn pcap_sendqueue_destroy(queue: *mut pcap_send_queue); - pub fn pcap_sendqueue_queue( - queue: *mut pcap_send_queue, - pkt_header: *const pcap_pkthdr, - pkt_data: *const c_uchar, - ) -> c_int; - pub fn pcap_sendqueue_transmit( - p: *mut pcap_t, - queue: *mut pcap_send_queue, - sync: c_int, - ) -> c_uint; + use super::*; + + pub const WINPCAP_MINTOCOPY_DEFAULT: c_int = 16000; + + #[link(name = "wpcap")] + extern "C" { + pub fn pcap_setmintocopy(arg1: *mut pcap_t, arg2: c_int) -> c_int; + pub fn pcap_getevent(p: *mut pcap_t) -> HANDLE; + pub fn pcap_sendqueue_alloc(memsize: c_uint) -> *mut pcap_send_queue; + pub fn pcap_sendqueue_destroy(queue: *mut pcap_send_queue); + pub fn pcap_sendqueue_queue( + queue: *mut pcap_send_queue, + pkt_header: *const pcap_pkthdr, + pkt_data: *const c_uchar, + ) -> c_int; + pub fn pcap_sendqueue_transmit( + p: *mut pcap_t, + queue: *mut pcap_send_queue, + sync: c_int, + ) -> c_uint; + } } +// The conventional solution is to use `mockall_double`. However, automock's requirement for an +// inner module would require changing the imports in all the files using this module. This approach +// allows all the other modules to keep using the `raw` module as before. +#[cfg(not(test))] +pub use ffi::*; + +#[cfg(not(test))] +#[cfg(not(windows))] +pub use ffi_unix::*; + +#[cfg(not(test))] +#[cfg(windows)] +pub use ffi_windows::*; + +#[cfg(test)] +pub use mock_ffi::*; + +#[cfg(test)] #[cfg(not(windows))] -#[link(name = "pcap")] -extern "C" { - // pub fn pcap_inject(arg1: *mut pcap_t, arg2: *const c_void, arg3: size_t) -> c_int; - pub fn pcap_set_rfmon(arg1: *mut pcap_t, arg2: c_int) -> c_int; +pub use mock_ffi_unix::*; + +#[cfg(test)] +#[cfg(windows)] +pub use mock_ffi_windows::*; + +#[cfg(test)] +pub mod testmod { + use std::{ffi::CString, sync::Mutex}; + + use once_cell::sync::Lazy; + + use super::*; + + pub struct GeterrContext(__pcap_geterr::Context); + + // Must be acquired by any test using mock FFI. + pub static RAWMTX: Lazy> = Lazy::new(|| Mutex::new(())); + + pub fn as_pcap_t(value: &mut T) -> *mut pcap_t { + value as *mut T as *mut pcap_t + } + + pub fn as_pcap_dumper_t(value: &mut T) -> *mut pcap_dumper_t { + value as *mut T as *mut pcap_dumper_t + } + + pub fn geterr_expect(pcap: *mut pcap_t) -> GeterrContext { + // Lock must be acquired by caller. + assert!(RAWMTX.try_lock().is_err()); + + let err = CString::new("oh oh").unwrap(); + let ctx = pcap_geterr_context(); + ctx.checkpoint(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once_st(|_| err.into_raw()); + + GeterrContext(ctx) + } } +// GRCOV_EXCL_STOP diff --git a/src/stream/mod.rs b/src/stream/mod.rs index ff4e730bd..2488baace 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -28,3 +28,27 @@ impl Capture { PacketStream::new(self, codec) } } + +#[cfg(test)] +mod tests { + use crate::{ + capture::{testmod::test_capture, Active}, + codec::testmod::Codec, + raw::testmod::{as_pcap_t, RAWMTX}, + }; + + #[test] + fn test_stream_error() { + let _m = RAWMTX.lock(); + + let mut dummy: isize = 777; + let pcap = as_pcap_t(&mut dummy); + + let test_capture = test_capture::(pcap); + let capture = test_capture.capture; + assert!(!capture.is_nonblock()); + + let result = capture.stream(Codec); + assert!(result.is_err()); + } +} diff --git a/src/stream/unix.rs b/src/stream/unix.rs index 905951cb3..538ae9de2 100644 --- a/src/stream/unix.rs +++ b/src/stream/unix.rs @@ -3,7 +3,6 @@ //! See [`Capture::stream`](super::Capture::stream). use std::io; use std::marker::Unpin; -use std::os::fd::{AsRawFd, RawFd}; use std::pin::Pin; use std::task::{self, Poll}; @@ -11,9 +10,9 @@ use futures::ready; use tokio::io::unix::AsyncFd; use crate::{ - capture::{Activated, Capture, State}, + capture::{selectable::SelectableCapture, Activated, Capture}, codec::PacketCodec, - raw, Error, + Error, }; /// Implement Stream for async use of pcap @@ -33,10 +32,9 @@ impl PacketStream { /// Returns a mutable reference to the inner [`Capture`]. /// - /// The caller must ensure the capture will not be set to be - /// blocking. + /// The caller must ensure the capture will not be set to be blocking. pub fn capture_mut(&mut self) -> &mut Capture { - &mut self.inner.get_mut().inner + self.inner.get_mut().get_inner_mut() } } @@ -51,11 +49,15 @@ impl futures::Stream for PacketStream Ok(Ok(codec.decode(p))), - Err(e @ Error::TimeoutExpired) => Err(io::Error::new(io::ErrorKind::WouldBlock, e)), - Err(e) => Ok(Err(e)), - }) { + match guard.try_io( + |inner| match inner.get_mut().get_inner_mut().next_packet() { + Ok(p) => Ok(Ok(codec.decode(p))), + Err(e @ Error::TimeoutExpired) => { + Err(io::Error::new(io::ErrorKind::WouldBlock, e)) + } + Err(e) => Ok(Err(e)), + }, + ) { Ok(result) => { return Poll::Ready(Some(result?)); } @@ -64,25 +66,3 @@ impl futures::Stream for PacketStream { - inner: Capture, - fd: RawFd, -} - -impl SelectableCapture { - fn new(capture: Capture) -> Result { - let fd = unsafe { raw::pcap_get_selectable_fd(capture.as_ptr()) }; - if fd == -1 { - return Err(Error::InvalidRawFd); - } - Ok(Self { inner: capture, fd }) - } -} - -impl AsRawFd for SelectableCapture { - fn as_raw_fd(&self) -> RawFd { - self.fd - } -} diff --git a/src/stream/windows.rs b/src/stream/windows.rs index cc0d52ec0..11656c8f6 100644 --- a/src/stream/windows.rs +++ b/src/stream/windows.rs @@ -33,8 +33,7 @@ impl PacketStream { /// Returns a mutable reference to the inner [`Capture`]. /// - /// The caller must ensure the capture will not be set to be - /// blocking. + /// The caller must ensure the capture will not be set to be blocking. pub fn capture_mut(&mut self) -> &mut Capture { &mut self.capture } diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs new file mode 100644 index 000000000..277795d68 --- /dev/null +++ b/tests/capture/activated/mod.rs @@ -0,0 +1,138 @@ +mod offline; + +use tempdir::TempDir; + +use pcap::{Capture, Linktype}; + +use crate::{capture_from_test_file, Packets}; + +#[test] +fn read_packet_with_full_data() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + assert_eq!(capture.next_packet().unwrap().len(), 98); +} + +#[test] +fn read_packet_with_truncated_data() { + let mut capture = capture_from_test_file("packet_snaplen_20.pcap"); + assert_eq!(capture.next_packet().unwrap().len(), 20); +} + +#[test] +fn capture_dead_savefile() { + let mut packets = Packets::new(); + packets.push(1460408319, 1234, 1, 1, &[1]); + packets.push(1460408320, 4321, 1, 1, &[2]); + + let dir = TempDir::new("pcap").unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets.foreach(|p| save.write(p)); + drop(save); + + let mut cap = Capture::from_file(&tmpfile).unwrap(); + packets.verify(&mut cap); +} + +#[test] +#[cfg(libpcap_1_7_2)] +fn capture_dead_savefile_append() { + let mut packets1 = Packets::new(); + packets1.push(1460408319, 1234, 1, 1, &[1]); + packets1.push(1460408320, 4321, 1, 1, &[2]); + let mut packets2 = Packets::new(); + packets2.push(1460408321, 2345, 1, 1, &[3]); + packets2.push(1460408322, 5432, 1, 1, &[4]); + let packets = &packets1 + &packets2; + + let dir = TempDir::new("pcap").unwrap(); + let tmpfile = dir.path().join("test.pcap"); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile(&tmpfile).unwrap(); + packets1.foreach(|p| save.write(p)); + drop(save); + + let cap = Capture::dead(Linktype(1)).unwrap(); + let mut save = cap.savefile_append(&tmpfile).unwrap(); + packets2.foreach(|p| save.write(p)); + drop(save); + + let mut cap = Capture::from_file(&tmpfile).unwrap(); + packets.verify(&mut cap); +} + +#[test] +fn test_linktype() { + let capture = capture_from_test_file("packet_snaplen_65535.pcap"); + let linktype = capture.get_datalink(); + + assert!(linktype.get_name().is_ok()); + assert_eq!(linktype.get_name().unwrap(), String::from("EN10MB")); + assert!(linktype.get_description().is_ok()); +} + +#[test] +fn test_error() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + // Trying to get stats from offline capture should error. + assert!(capture.stats().err().is_some()); +} + +#[test] +fn test_compile() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + let packet = capture.next_packet().unwrap(); + + let bpf_capture = Capture::dead(Linktype::ETHERNET).unwrap(); + + let program = bpf_capture.compile("dst host 8.8.8.8", false).unwrap(); + let instructions = program.get_instructions(); + + assert!(!instructions.is_empty()); + assert!(program.filter(packet.data)); + + let program = bpf_capture.compile("src host 8.8.8.8", false).unwrap(); + let instructions = program.get_instructions(); + + assert!(!instructions.is_empty()); + assert!(!program.filter(packet.data)); +} + +#[test] +fn test_compile_optimized() { + let bpf_capture = Capture::dead(Linktype::ETHERNET).unwrap(); + + let program_str = "ip and ip and tcp"; + let program_unopt = bpf_capture.compile(program_str, false).unwrap(); + let instr_unopt = program_unopt.get_instructions(); + + let program_opt = bpf_capture.compile(program_str, true).unwrap(); + let instr_opt = program_opt.get_instructions(); + + assert!(instr_opt.len() < instr_unopt.len()); +} + +#[test] +fn test_compile_error() { + let bpf_capture = Capture::dead(Linktype::ETHERNET).unwrap(); + + let program_str = "this is a terrible program"; + + let result = bpf_capture.compile(program_str, false); + assert!(result.is_err()); + + let result = bpf_capture.compile(program_str, true); + assert!(result.is_err()); +} + +#[test] +fn test_filter() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + capture.filter("dst host 8.8.8.8", false).unwrap(); + + let result = capture.next_packet(); + assert!(result.is_ok()); +} diff --git a/tests/capture/activated/offline.rs b/tests/capture/activated/offline.rs new file mode 100644 index 000000000..f8eb1acba --- /dev/null +++ b/tests/capture/activated/offline.rs @@ -0,0 +1,10 @@ +use crate::capture_from_test_file; + +#[test] +fn test_pcap_version() { + let capture = capture_from_test_file("packet_snaplen_65535.pcap"); + + assert_eq!(capture.version(), (2, 4)); + assert_eq!(capture.major_version(), 2); + assert_eq!(capture.minor_version(), 4); +} diff --git a/tests/capture/mod.rs b/tests/capture/mod.rs new file mode 100644 index 000000000..55ef5b12d --- /dev/null +++ b/tests/capture/mod.rs @@ -0,0 +1 @@ +mod activated; diff --git a/tests/lib.rs b/tests/lib.rs index c54b8e4b1..669218cb4 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,15 +1,9 @@ -#[cfg(not(windows))] -use std::io; +mod capture; + use std::ops::Add; use std::path::Path; -use tempdir::TempDir; -use pcap::{ - Activated, Active, Capture, ConnectionStatus, DeviceFlags, IfFlags, Linktype, Offline, Packet, - PacketHeader, -}; -#[cfg(not(windows))] -use pcap::{Error, Precision}; +use pcap::{Activated, Capture, Offline, Packet, PacketHeader}; #[cfg(not(windows))] #[allow(non_camel_case_types)] @@ -25,48 +19,11 @@ type suseconds_t = libc::suseconds_t; #[allow(non_camel_case_types)] type suseconds_t = libc::c_long; -#[test] -fn read_packet_with_full_data() { - let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); - assert_eq!(capture.next_packet().unwrap().len(), 98); -} - -#[test] -fn read_packet_with_truncated_data() { - let mut capture = capture_from_test_file("packet_snaplen_20.pcap"); - assert_eq!(capture.next_packet().unwrap().len(), 20); -} - fn capture_from_test_file(file_name: &str) -> Capture { let path = Path::new("tests/data/").join(file_name); Capture::from_file(path).unwrap() } -#[test] -fn unify_activated() { - #![allow(dead_code)] - fn test1() -> Capture { - panic!(); - } - - fn test2() -> Capture { - panic!(); - } - - fn maybe(a: bool) -> Capture { - if a { - test1().into() - } else { - test2().into() - } - } - - fn also_maybe(a: &mut Capture) { - a.filter("whatever filter string, this won't be run anyway", false) - .unwrap(); - } -} - #[derive(Clone)] pub struct Packets { headers: Vec, @@ -129,61 +86,20 @@ impl<'a> Add for &'a Packets { } } -#[test] -fn capture_dead_savefile() { - let mut packets = Packets::new(); - packets.push(1460408319, 1234, 1, 1, &[1]); - packets.push(1460408320, 4321, 1, 1, &[2]); - - let dir = TempDir::new("pcap").unwrap(); - let tmpfile = dir.path().join("test.pcap"); - - let cap = Capture::dead(Linktype(1)).unwrap(); - let mut save = cap.savefile(&tmpfile).unwrap(); - packets.foreach(|p| save.write(p)); - drop(save); - - let mut cap = Capture::from_file(&tmpfile).unwrap(); - packets.verify(&mut cap); -} - -#[test] -#[cfg(libpcap_1_7_2)] -fn capture_dead_savefile_append() { - let mut packets1 = Packets::new(); - packets1.push(1460408319, 1234, 1, 1, &[1]); - packets1.push(1460408320, 4321, 1, 1, &[2]); - let mut packets2 = Packets::new(); - packets2.push(1460408321, 2345, 1, 1, &[3]); - packets2.push(1460408322, 5432, 1, 1, &[4]); - let packets = &packets1 + &packets2; - - let dir = TempDir::new("pcap").unwrap(); - let tmpfile = dir.path().join("test.pcap"); - - let cap = Capture::dead(Linktype(1)).unwrap(); - let mut save = cap.savefile(&tmpfile).unwrap(); - packets1.foreach(|p| save.write(p)); - drop(save); - - let cap = Capture::dead(Linktype(1)).unwrap(); - let mut save = cap.savefile_append(&tmpfile).unwrap(); - packets2.foreach(|p| save.write(p)); - drop(save); - - let mut cap = Capture::from_file(&tmpfile).unwrap(); - packets.verify(&mut cap); -} - #[test] #[cfg(not(windows))] fn test_raw_fd_api() { use std::fs::File; + use std::io; use std::io::prelude::*; - #[cfg(not(windows))] use std::os::unix::io::{FromRawFd, RawFd}; use std::thread; + use tempdir::TempDir; + + use pcap::Linktype; + use pcap::{Error, Precision}; + // Create a total of more than 64K data (> max pipe buf size) const N_PACKETS: usize = 64; let data: Vec = (0..191).cycle().take(N_PACKETS * 1024).collect(); @@ -297,111 +213,3 @@ fn test_raw_fd_api() { pipe_thread.join().unwrap(); } } - -#[test] -fn test_linktype() { - let capture = capture_from_test_file("packet_snaplen_65535.pcap"); - let linktype = capture.get_datalink(); - - assert!(linktype.get_name().is_ok()); - assert_eq!(linktype.get_name().unwrap(), String::from("EN10MB")); - assert!(linktype.get_description().is_ok()); -} - -#[test] -fn test_error() { - let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); - // Trying to get stats from offline capture should error. - assert!(capture.stats().err().is_some()); -} - -#[test] -fn test_compile() { - let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); - let packet = capture.next_packet().unwrap(); - - let bpf_capture = Capture::dead(Linktype::ETHERNET).unwrap(); - - let program = bpf_capture.compile("dst host 8.8.8.8", false).unwrap(); - let instructions = program.get_instructions(); - - assert!(!instructions.is_empty()); - assert!(program.filter(packet.data)); - - let program = bpf_capture.compile("src host 8.8.8.8", false).unwrap(); - let instructions = program.get_instructions(); - - assert!(!instructions.is_empty()); - assert!(!program.filter(packet.data)); -} - -#[test] -fn test_compile_optimized() { - let bpf_capture = Capture::dead(Linktype::ETHERNET).unwrap(); - - let program_str = "ip and ip and tcp"; - let program_unopt = bpf_capture.compile(program_str, false).unwrap(); - let instr_unopt = program_unopt.get_instructions(); - - let program_opt = bpf_capture.compile(program_str, true).unwrap(); - let instr_opt = program_opt.get_instructions(); - - assert!(instr_opt.len() < instr_unopt.len()); -} - -#[test] -fn test_pcap_version() { - let capture = capture_from_test_file("packet_snaplen_65535.pcap"); - - assert_eq!(capture.version(), (2, 4)); - assert_eq!(capture.major_version(), 2); - assert_eq!(capture.minor_version(), 4); -} - -#[test] -fn test_device_flags() { - pub const PCAP_IF_LOOPBACK: u32 = 0x00000001; - pub const PCAP_IF_UP: u32 = 0x00000002; - pub const PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE: u32 = 0x00000030; - - let flags = - DeviceFlags::from(PCAP_IF_LOOPBACK | PCAP_IF_UP | PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE); - - assert!(flags.is_loopback()); - assert!(flags.is_up()); - assert!(flags.contains(IfFlags::LOOPBACK | IfFlags::UP)); - - assert!(!flags.is_running()); - assert!(!flags.is_wireless()); - - assert_ne!(flags.connection_status, ConnectionStatus::Unknown); - assert_ne!(flags.connection_status, ConnectionStatus::Connected); - assert_ne!(flags.connection_status, ConnectionStatus::Disconnected); - assert_eq!(flags.connection_status, ConnectionStatus::NotApplicable); -} - -#[test] -fn test_connection_status() { - pub const PCAP_IF_CONNECTION_STATUS_UNKNOWN: u32 = 0x00000000; - pub const PCAP_IF_CONNECTION_STATUS_CONNECTED: u32 = 0x00000010; - pub const PCAP_IF_CONNECTION_STATUS_DISCONNECTED: u32 = 0x00000020; - pub const PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE: u32 = 0x00000030; - - let flags = PCAP_IF_CONNECTION_STATUS_UNKNOWN; - assert_eq!(ConnectionStatus::from(flags), ConnectionStatus::Unknown); - - let flags = PCAP_IF_CONNECTION_STATUS_CONNECTED; - assert_eq!(ConnectionStatus::from(flags), ConnectionStatus::Connected); - - let flags = PCAP_IF_CONNECTION_STATUS_DISCONNECTED; - assert_eq!( - ConnectionStatus::from(flags), - ConnectionStatus::Disconnected - ); - - let flags = PCAP_IF_CONNECTION_STATUS_NOT_APPLICABLE; - assert_eq!( - ConnectionStatus::from(flags), - ConnectionStatus::NotApplicable - ); -}