Skip to main content

pcap_processor/tshark/
runner.rs

1use crate::error::{PcapError, Result};
2use std::path::{Path, PathBuf};
3use std::process::Command;
4
5#[derive(Clone)]
6pub struct TsharkConfig {
7    pub path: PathBuf,
8}
9
10impl TsharkConfig {
11    pub fn detect(custom_path: Option<PathBuf>) -> Result<Self> {
12        if let Some(path) = custom_path {
13            if path.exists() {
14                return Ok(Self { path });
15            }
16            return Err(PcapError::TsharkNotFound);
17        }
18
19        // Try common locations
20        let common_paths = [
21            "/usr/bin/tshark",
22            "/usr/local/bin/tshark",
23            "/opt/homebrew/bin/tshark",
24            "/Applications/Wireshark.app/Contents/MacOS/tshark",
25        ];
26
27        for path in common_paths {
28            let path = PathBuf::from(path);
29            if path.exists() {
30                return Ok(Self { path });
31            }
32        }
33
34        // Try which
35        if let Ok(path) = which::which("tshark") {
36            return Ok(Self { path });
37        }
38
39        Err(PcapError::TsharkNotFound)
40    }
41}
42
43pub struct TsharkRunner {
44    config: TsharkConfig,
45}
46
47impl TsharkRunner {
48    pub fn new(config: TsharkConfig) -> Self {
49        Self { config }
50    }
51
52    pub fn extract_field_values(
53        &self,
54        file: &Path,
55        field: &str,
56        filter: Option<&str>,
57        no_resolve: bool,
58    ) -> Result<Vec<String>> {
59        let mut cmd = Command::new(&self.config.path);
60        cmd.arg("-r").arg(file);
61        cmd.arg("-T").arg("fields");
62        cmd.arg("-e").arg(field);
63
64        if let Some(f) = filter {
65            cmd.arg("-Y").arg(f);
66        }
67
68        if no_resolve {
69            cmd.arg("-n");
70        }
71
72        let output = cmd.output()?;
73
74        if !output.status.success() {
75            let stderr = String::from_utf8_lossy(&output.stderr);
76            return Err(PcapError::TsharkFailed {
77                message: stderr.trim().to_string(),
78            });
79        }
80
81        let stdout = String::from_utf8_lossy(&output.stdout);
82        let values: Vec<String> = stdout
83            .lines()
84            .filter(|line| !line.is_empty())
85            .flat_map(|line| {
86                // Handle multi-value fields (comma-separated in tshark output)
87                line.split(',').map(|s| s.trim().to_string())
88            })
89            .filter(|s| !s.is_empty())
90            .collect();
91
92        Ok(values)
93    }
94
95    pub fn get_field_list(&self) -> Result<String> {
96        let output = Command::new(&self.config.path)
97            .arg("-G")
98            .arg("fields")
99            .output()?;
100
101        if !output.status.success() {
102            let stderr = String::from_utf8_lossy(&output.stderr);
103            return Err(PcapError::TsharkFailed {
104                message: stderr.trim().to_string(),
105            });
106        }
107
108        Ok(String::from_utf8_lossy(&output.stdout).to_string())
109    }
110
111    pub fn validate_filter(&self, filter: &str) -> Result<()> {
112        // Use /dev/null as empty input to validate filter syntax
113        let output = Command::new(&self.config.path)
114            .arg("-Y")
115            .arg(filter)
116            .arg("-r")
117            .arg("/dev/null")
118            .output()?;
119
120        // tshark returns non-zero for invalid filters but also for missing files
121        // Check stderr for filter-specific errors
122        let stderr = String::from_utf8_lossy(&output.stderr);
123
124        if stderr.contains("invalid")
125            || stderr.contains("neither combinator")
126            || stderr.contains("Syntax error")
127            || stderr.contains("unexpected")
128        {
129            return Err(PcapError::InvalidFilter {
130                filter: filter.to_string(),
131                message: stderr.trim().to_string(),
132            });
133        }
134
135        Ok(())
136    }
137}