1use super::config::Ops;
2use crate::error::{Error, Result};
3use crate::{
4 PcapCancelToken, PcapConfig, PcapEngine, PcapParseResult, PcapResult,
5 DEFAULT_PCAP_CAPTURE_SECONDS, MAX_PCAP_CAPTURE_PACKETS, MAX_PCAP_CAPTURE_SECONDS,
6};
7use std::path::PathBuf;
8
9impl Ops {
10 pub fn pcap_check_support(&self) -> Result<Vec<String>> {
11 PcapEngine::check_support()
12 }
13
14 pub fn capture_pcap(
15 &self,
16 interface: String,
17 filter: Option<String>,
18 duration: Option<u64>,
19 output_file: Option<String>,
20 max_packets: Option<usize>,
21 ) -> Result<PcapResult> {
22 let (duration, max_packets) = normalize_capture_limits(duration, max_packets)?;
23 let cfg = PcapConfig {
24 interface,
25 filter,
26 output_file: normalize_capture_output(output_file)?,
27 duration,
28 max_packets,
29 };
30 PcapEngine::capture(cfg)
31 }
32
33 pub fn parse_pcap_file(
34 &self,
35 input_file: String,
36 max_packets: Option<usize>,
37 ) -> Result<PcapParseResult> {
38 PcapEngine::parse_file(input_file.into(), max_packets)
39 }
40
41 pub async fn capture_pcap_async(
47 &self,
48 interface: String,
49 filter: Option<String>,
50 duration: Option<u64>,
51 output_file: Option<String>,
52 max_packets: Option<usize>,
53 ) -> Result<PcapResult> {
54 self.capture_pcap_async_with_cancel(
55 interface,
56 filter,
57 duration,
58 output_file,
59 max_packets,
60 None,
61 )
62 .await
63 }
64
65 pub async fn capture_pcap_async_with_cancel(
66 &self,
67 interface: String,
68 filter: Option<String>,
69 duration: Option<u64>,
70 output_file: Option<String>,
71 max_packets: Option<usize>,
72 cancel: Option<PcapCancelToken>,
73 ) -> Result<PcapResult> {
74 let (duration, max_packets) = normalize_capture_limits(duration, max_packets)?;
75 let cfg = PcapConfig {
76 interface,
77 filter,
78 output_file: normalize_capture_output(output_file)?,
79 duration,
80 max_packets,
81 };
82
83 let task =
84 tokio::task::spawn_blocking(move || PcapEngine::capture_with_cancel(cfg, cancel));
85 match task.await {
86 Ok(res) => Ok(res?),
87 Err(e) => Err(Error::Other(format!("pcap capture task failed: {e}"))),
88 }
89 }
90}
91
92fn normalize_capture_limits(
93 duration: Option<u64>,
94 max_packets: Option<usize>,
95) -> Result<(Option<std::time::Duration>, Option<usize>)> {
96 let max_packets = match max_packets {
97 Some(0) => return Err(Error::invalid_input("max packets must be greater than 0")),
98 Some(n) if n > MAX_PCAP_CAPTURE_PACKETS => {
99 return Err(Error::invalid_input(format!(
100 "max packets too large: {n} (max {MAX_PCAP_CAPTURE_PACKETS})"
101 )));
102 }
103 Some(n) => Some(n),
104 None => None,
105 };
106
107 let duration = match duration {
108 Some(0) => return Err(Error::invalid_input("duration must be greater than 0")),
109 Some(seconds) if seconds > MAX_PCAP_CAPTURE_SECONDS => {
110 return Err(Error::invalid_input(format!(
111 "duration too long: {seconds}s (max {MAX_PCAP_CAPTURE_SECONDS}s)"
112 )));
113 }
114 Some(seconds) => Some(std::time::Duration::from_secs(seconds)),
115 None if max_packets.is_none() => {
116 Some(std::time::Duration::from_secs(DEFAULT_PCAP_CAPTURE_SECONDS))
117 }
118 None => Some(std::time::Duration::from_secs(MAX_PCAP_CAPTURE_SECONDS)),
124 };
125
126 Ok((duration, max_packets))
127}
128
129fn normalize_capture_output(output_file: Option<String>) -> Result<PathBuf> {
130 let path = PathBuf::from(output_file.unwrap_or_else(|| "capture.pcap".to_string()));
131 let is_pcap = path
132 .extension()
133 .and_then(|ext| ext.to_str())
134 .is_some_and(|ext| ext.eq_ignore_ascii_case("pcap"));
135 if !is_pcap {
136 return Err(Error::invalid_input("pcap output file must end in .pcap"));
137 }
138 Ok(path)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn capture_limits_default_to_bounded_duration() {
147 let (duration, max_packets) = normalize_capture_limits(None, None).unwrap();
148 assert_eq!(
149 duration,
150 Some(std::time::Duration::from_secs(DEFAULT_PCAP_CAPTURE_SECONDS))
151 );
152 assert_eq!(max_packets, None);
153 }
154
155 #[test]
156 fn capture_limits_reject_zero_and_too_large_values() {
157 assert!(normalize_capture_limits(Some(0), None).is_err());
158 assert!(normalize_capture_limits(None, Some(0)).is_err());
159 assert!(normalize_capture_limits(Some(MAX_PCAP_CAPTURE_SECONDS + 1), None).is_err());
160 assert!(normalize_capture_limits(None, Some(MAX_PCAP_CAPTURE_PACKETS + 1)).is_err());
161 }
162
163 #[test]
164 fn capture_output_requires_pcap_extension() {
165 assert!(normalize_capture_output(Some("capture.pcap".to_string())).is_ok());
166 assert!(normalize_capture_output(Some("capture.txt".to_string())).is_err());
167 }
168}