pcap_processor/tshark/
runner.rs1use 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 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 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 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 let output = Command::new(&self.config.path)
114 .arg("-Y")
115 .arg(filter)
116 .arg("-r")
117 .arg("/dev/null")
118 .output()?;
119
120 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}