1
use std::path::Path;
2

            
3
#[cfg(not(windows))]
4
use std::os::unix::io::RawFd;
5

            
6
use crate::{
7
    Error,
8
    capture::{Capture, Offline},
9
    path_to_cstring, raw,
10
};
11

            
12
#[cfg(libpcap_1_5_0)]
13
use crate::capture::Precision;
14

            
15
#[cfg(not(windows))]
16
use crate::capture::activated::open_raw_fd;
17

            
18
impl Capture<Offline> {
19
    /// Opens an offline capture handle from a pcap dump file, given a path.
20
    ///
21
    /// On Windows a path outside ASCII needs `init(CharEncoding::Utf8)` first. The path always
22
    /// reaches libpcap as UTF-8, but until that call libpcap reads it in the local code page,
23
    /// which on most systems is not UTF-8: the name gets mangled and the file is not found.
24
21
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Capture<Offline>, Error> {
25
21
        let path = path_to_cstring(path.as_ref())?;
26
21
        Capture::new_raw(Some(path), |library, path, err| unsafe {
27
21
            raw::pcap_open_offline(library, path, err)
28
21
        })
29
21
    }
30

            
31
    /// Opens an offline capture handle from a pcap dump file, given a path.
32
    /// Takes an additional precision argument specifying the timestamp precision desired.
33
    ///
34
    /// On Windows a path outside ASCII needs `init(CharEncoding::Utf8)` first. The path always
35
    /// reaches libpcap as UTF-8, but until that call libpcap reads it in the local code page,
36
    /// which on most systems is not UTF-8: the name gets mangled and the file is not found.
37
    #[cfg(libpcap_1_5_0)]
38
1
    pub fn from_file_with_precision<P: AsRef<Path>>(
39
1
        path: P,
40
1
        precision: Precision,
41
1
    ) -> Result<Capture<Offline>, Error> {
42
        #[cfg(windows)]
43
        if !raw::has_offline_precision() {
44
            raw::require_library()?;
45
            return Err(Error::EntrypointNotFound(
46
                "pcap_open_offline_with_tstamp_precision",
47
            ));
48
        }
49

            
50
1
        let path = path_to_cstring(path.as_ref())?;
51
1
        Capture::new_raw(Some(path), |library, path, err| unsafe {
52
1
            raw::pcap_open_offline_with_tstamp_precision(library, path, precision as _, err)
53
1
        })
54
1
    }
55

            
56
    /// Opens an offline capture handle from a pcap dump file, given a file descriptor.
57
    ///
58
    /// # Safety
59
    ///
60
    /// Unsafe, because the returned Capture assumes it is the sole owner of the file descriptor.
61
    #[cfg(not(windows))]
62
6
    pub unsafe fn from_raw_fd(fd: RawFd) -> Result<Capture<Offline>, Error> {
63
6
        unsafe { open_raw_fd(fd, b'r') }.and_then(|file| {
64
3
            Capture::new_raw(None, |library, _, err| unsafe {
65
3
                raw::pcap_fopen_offline(library, file, err)
66
3
            })
67
3
        })
68
6
    }
69

            
70
    /// Opens an offline capture handle from a pcap dump file, given a file descriptor. Takes an
71
    /// additional precision argument specifying the timestamp precision desired.
72
    ///
73
    /// # Safety
74
    ///
75
    /// Unsafe, because the returned Capture assumes it is the sole owner of the file descriptor.
76
    #[cfg(all(not(windows), libpcap_1_5_0))]
77
6
    pub unsafe fn from_raw_fd_with_precision(
78
6
        fd: RawFd,
79
6
        precision: Precision,
80
6
    ) -> Result<Capture<Offline>, Error> {
81
6
        unsafe { open_raw_fd(fd, b'r') }.and_then(|file| {
82
3
            Capture::new_raw(None, |library, _, err| unsafe {
83
3
                raw::pcap_fopen_offline_with_tstamp_precision(library, file, precision as _, err)
84
3
            })
85
3
        })
86
6
    }
87

            
88
    /// Get the major version number of the pcap dump file format.
89
7
    pub fn major_version(&self) -> i32 {
90
7
        unsafe { raw::pcap_major_version(self.handle.as_ptr()) }
91
7
    }
92

            
93
    /// Get the minor version number of the pcap dump file format.
94
7
    pub fn minor_version(&self) -> i32 {
95
7
        unsafe { raw::pcap_minor_version(self.handle.as_ptr()) }
96
7
    }
97

            
98
    /// Get the (major, minor) version number of the pcap dump file format.
99
4
    pub fn version(&self) -> (i32, i32) {
100
4
        (self.major_version(), self.minor_version())
101
4
    }
102
}
103

            
104
#[cfg(test)]
105
mod tests {
106
    #[cfg(libpcap_1_5_0)]
107
    use mockall::predicate;
108

            
109
    use crate::{
110
        capture::testmod::test_capture,
111
        raw::testmod::{RAWMTX, as_pcap_t},
112
    };
113

            
114
    use super::*;
115

            
116
    #[test]
117
    fn test_from_file() {
118
        let _m = RAWMTX.lock();
119

            
120
        let mut dummy: isize = 777;
121
        let pcap = as_pcap_t(&mut dummy);
122

            
123
        let ctx = raw::pcap_open_offline_context();
124
        ctx.expect().return_once_st(move |_, _, _| pcap);
125

            
126
        let ctx = raw::pcap_close_context();
127
        ctx.expect()
128
            .withf_st(move |ptr| *ptr == pcap)
129
            .return_once(|_| {});
130

            
131
        let result = Capture::from_file("path/to/nowhere");
132
        assert!(result.is_ok());
133
    }
134

            
135
    #[test]
136
    #[cfg(libpcap_1_5_0)]
137
    fn test_from_file_with_precision() {
138
        let _m = RAWMTX.lock();
139

            
140
        let mut dummy: isize = 777;
141
        let pcap = as_pcap_t(&mut dummy);
142

            
143
        #[cfg(windows)]
144
        let ctx = raw::has_offline_precision_context();
145
        #[cfg(windows)]
146
        ctx.expect().return_once(|| true);
147

            
148
        let ctx = raw::pcap_open_offline_with_tstamp_precision_context();
149
        ctx.expect()
150
            .with(
151
                predicate::always(),
152
                predicate::always(),
153
                predicate::eq(1),
154
                predicate::always(),
155
            )
156
            .return_once_st(move |_, _, _, _| pcap);
157

            
158
        let ctx = raw::pcap_close_context();
159
        ctx.expect()
160
            .withf_st(move |ptr| *ptr == pcap)
161
            .return_once(|_| {});
162

            
163
        let result = Capture::from_file_with_precision("path/to/nowhere", Precision::Nano);
164
        assert!(result.is_ok());
165
    }
166

            
167
    #[test]
168
    #[cfg(all(windows, libpcap_1_5_0))]
169
    fn test_from_file_precision_missing() {
170
        let _m = RAWMTX.lock();
171

            
172
        let ctx = raw::has_offline_precision_context();
173
        ctx.expect().return_once(|| false);
174

            
175
        let result = Capture::from_file_with_precision("path/to/nowhere", Precision::Nano);
176
        assert!(matches!(result, Err(Error::EntrypointNotFound(_))));
177
    }
178

            
179
    #[test]
180
    fn test_version() {
181
        let _m = RAWMTX.lock();
182

            
183
        let mut dummy: isize = 777;
184
        let pcap = as_pcap_t(&mut dummy);
185

            
186
        let ctx = raw::pcap_major_version_context();
187
        ctx.expect()
188
            .withf_st(move |arg| *arg == pcap)
189
            .return_once(|_| 5);
190

            
191
        let ctx = raw::pcap_minor_version_context();
192
        ctx.expect()
193
            .withf_st(move |arg| *arg == pcap)
194
            .return_once(|_| 7);
195

            
196
        let test_capture = test_capture::<Offline>(pcap);
197
        let capture = test_capture.capture;
198

            
199
        assert_eq!(capture.version(), (5, 7));
200
    }
201
}