#![warn(missing_docs)]
use clap::Args;
use config::{ConfigTrait, SelectorConfig};
use controls::ToolbarControl;
use interface::{Interface, Metadata};
use std::{
fmt::Display,
path::{Path, PathBuf},
};
use thiserror::Error;
#[cfg(not(target_os = "windows"))]
use std::fs::File;
pub mod config;
pub mod controls;
pub mod interface;
#[derive(Debug, Args)]
pub struct ExtcapArgs {
#[arg(long, verbatim_doc_comment)]
pub extcap_interfaces: bool,
#[arg(long)]
pub extcap_version: Option<String>,
#[arg(long, verbatim_doc_comment)]
pub extcap_config: bool,
#[arg(long, requires = "extcap_interface", verbatim_doc_comment)]
pub extcap_dlts: bool,
#[arg(long, requires = "fifo", requires = "extcap_interface")]
pub capture: bool,
#[arg(long)]
pub extcap_interface: Option<String>,
#[arg(long, requires = "capture")]
pub fifo: Option<PathBuf>,
#[arg(long, requires = "capture")]
pub extcap_capture_filter: Option<String>,
#[arg(long, requires = "capture")]
pub extcap_control_in: Option<PathBuf>,
#[arg(long, requires = "capture")]
pub extcap_control_out: Option<PathBuf>,
#[arg(long, requires = "extcap_interface")]
pub extcap_reload_option: Option<String>,
}
#[derive(Debug, Error)]
pub enum CaptureError {
#[error("Missing `--extcap-interface` argument during `--capture` phase")]
MissingInterface,
#[error(
"--fifo argument is missing. This is expected to be included \
when invoked by Wireshark during the capture stage."
)]
MissingFifo,
#[error("IO error opening output FIFO for capture")]
Io(#[from] std::io::Error),
}
impl ExtcapArgs {
pub fn run(&self) -> Result<ExtcapStep, ExtcapError> {
if self.extcap_interfaces {
Ok(ExtcapStep::Interfaces(InterfacesStep))
} else if let Some(interface) = &self.extcap_interface {
if self.extcap_config {
if let Some(reload_config) = &self.extcap_reload_option {
Ok(ExtcapStep::ReloadConfig(ReloadConfigStep {
interface,
config: reload_config,
}))
} else {
Ok(ExtcapStep::Config(ConfigStep { interface }))
}
} else if self.extcap_dlts {
Ok(ExtcapStep::Dlts(DltsStep { interface }))
} else if self.capture {
let fifo_path = self.fifo.as_ref().ok_or(CaptureError::MissingFifo)?;
#[cfg(target_os = "windows")]
let fifo = {
use std::os::windows::prelude::OpenOptionsExt;
std::fs::OpenOptions::new()
.write(true)
.create(true)
.security_qos_flags(0x10000)
.open(fifo_path)
.map_err(CaptureError::Io)?
};
#[cfg(not(target_os = "windows"))]
let fifo = File::create(fifo_path).map_err(CaptureError::Io)?;
let interface = self
.extcap_interface
.as_ref()
.ok_or(CaptureError::MissingInterface)?;
Ok(ExtcapStep::Capture(CaptureStep {
interface,
fifo,
fifo_path,
extcap_control_in: &self.extcap_control_in,
extcap_control_out: &self.extcap_control_out,
}))
} else {
Err(ExtcapError::NotExtcapInput)
}
} else {
Err(ExtcapError::NotExtcapInput)
}
}
}
#[derive(Debug, Error)]
pub enum ExtcapError {
#[error("Missing input extcap command. {}", installation_instructions())]
NotExtcapInput,
#[error(transparent)]
CaptureError(#[from] CaptureError),
}
pub fn installation_instructions() -> String {
let install_cmd = std::env::current_exe()
.ok()
.and_then(|exe| {
let path = exe.to_string_lossy();
let name = exe.file_name()?.to_string_lossy();
Some(format!(concat!(
"\n\nFor Wireshark 4.0 or before:\n",
" mkdir -p \"$HOME/.config/wireshark/extcap/\" && ln -s \"{path}\" \"$HOME/.config/wireshark/extcap/{name}\"\n",
"For Wireshark 4.1 or later:\n",
" mkdir -p \"$HOME/.local/lib/wireshark/extcap/\" && ln -s \"{path}\" \"$HOME/.local/lib/wireshark/extcap/{name}\"",
), path = path, name = name))
})
.unwrap_or_default();
format!(
concat!(
"This is an extcap plugin meant to be used with Wireshark or tshark.\n",
"To install this plugin for use with Wireshark, symlink or copy this executable ",
"to your Wireshark extcap directory{}",
),
install_cmd
)
}
#[derive(Debug, Error)]
pub enum PrintDltError {
#[error("Cannot list DLT for unknown interface \"{0}\".")]
UnknownInterface(String),
}
#[derive(Debug, Error)]
pub enum ReloadConfigError {
#[error("Cannot reload options for unknown config \"{0}\".")]
UnknownConfig(String),
#[error("Cannot reload config options for \"{0}\", which is not of type \"selector\".")]
UnsupportedConfig(String),
}
#[derive(Debug, Error)]
pub enum ListConfigError {
#[error("Cannot reload config options for unknown interface \"{0}\".")]
UnknownInterface(String),
}
pub enum ExtcapStep<'a> {
Interfaces(InterfacesStep),
Dlts(DltsStep<'a>),
Config(ConfigStep<'a>),
ReloadConfig(ReloadConfigStep<'a>),
Capture(CaptureStep<'a>),
}
pub struct InterfacesStep;
impl InterfacesStep {
pub fn list_interfaces(
&self,
metadata: &Metadata,
interfaces: &[&Interface],
controls: &[&dyn ToolbarControl],
) {
metadata.print_sentence();
for interface in interfaces {
interface.print_sentence();
}
for control in controls {
control.print_sentence();
}
}
}
pub struct DltsStep<'a> {
pub interface: &'a str,
}
impl<'a> DltsStep<'a> {
pub fn print_dlt(&self, interface: &Interface) {
interface.dlt.print_sentence();
}
pub fn print_from_interfaces(&self, interfaces: &[&Interface]) -> Result<(), PrintDltError> {
interfaces
.iter()
.find(|i| i.value == self.interface)
.ok_or_else(|| PrintDltError::UnknownInterface(self.interface.to_owned()))?
.dlt
.print_sentence();
Ok(())
}
}
pub struct ConfigStep<'a> {
pub interface: &'a str,
}
impl<'a> ConfigStep<'a> {
pub fn list_configs(&self, configs: &[&dyn ConfigTrait]) {
for config in configs {
config.print_sentence();
}
}
}
pub struct ReloadConfigStep<'a> {
pub interface: &'a str,
pub config: &'a str,
}
impl<'a> ReloadConfigStep<'a> {
pub fn reload_options(&self, config: &SelectorConfig) -> Result<(), ReloadConfigError> {
let reload = config
.reload
.as_ref()
.ok_or_else(|| ReloadConfigError::UnsupportedConfig(config.call.clone()))?;
for value in (reload.reload_fn)() {
value.print_sentence(config.config_number);
}
Ok(())
}
pub fn reload_from_configs(
&self,
configs: &[&dyn ConfigTrait],
) -> Result<(), ReloadConfigError> {
let config = configs
.iter()
.find(|c| c.call() == self.config)
.ok_or_else(|| ReloadConfigError::UnknownConfig(self.config.to_owned()))?;
let selector = config
.as_any()
.downcast_ref::<SelectorConfig>()
.ok_or_else(|| ReloadConfigError::UnsupportedConfig(self.config.to_owned()))?;
self.reload_options(selector)
}
}
pub struct CaptureStep<'a> {
pub interface: &'a str,
pub fifo: std::fs::File,
fifo_path: &'a Path,
pub extcap_control_in: &'a Option<std::path::PathBuf>,
pub extcap_control_out: &'a Option<std::path::PathBuf>,
}
impl<'a> CaptureStep<'a> {
#[cfg(feature = "sync")]
pub fn new_control_sender(&self) -> Option<controls::synchronous::ExtcapControlSender> {
self.extcap_control_out
.as_ref()
.map(|p| controls::synchronous::ExtcapControlSender::new(p))
}
#[cfg(feature = "async")]
pub async fn new_control_sender_async(
&self,
) -> Option<controls::asynchronous::ExtcapControlSender> {
if let Some(p) = &self.extcap_control_out {
Some(controls::asynchronous::ExtcapControlSender::new(p).await)
} else {
None
}
}
#[cfg(feature = "sync")]
pub fn spawn_channel_control_reader(
&self,
) -> Option<controls::synchronous::ChannelExtcapControlReader> {
self.extcap_control_in
.as_ref()
.map(|p| controls::synchronous::ChannelExtcapControlReader::spawn(p.to_owned()))
}
#[cfg(feature = "async")]
pub fn spawn_channel_control_reader_async(
&self,
) -> Option<controls::asynchronous::ChannelExtcapControlReader> {
self.extcap_control_in
.as_ref()
.map(|p| controls::asynchronous::ChannelExtcapControlReader::spawn(p.to_owned()))
}
#[cfg(feature = "sync")]
pub fn new_control_reader(&self) -> Option<controls::synchronous::ExtcapControlReader> {
self.extcap_control_in
.as_ref()
.map(|p| controls::synchronous::ExtcapControlReader::new(p))
}
#[cfg(feature = "async")]
pub async fn new_control_reader_async(
&self,
) -> Option<controls::asynchronous::ExtcapControlReader> {
if let Some(p) = &self.extcap_control_in {
Some(controls::asynchronous::ExtcapControlReader::new(p).await)
} else {
None
}
}
#[cfg(feature = "async")]
pub async fn fifo_async(&self) -> tokio::io::Result<tokio::fs::File> {
tokio::fs::File::create(self.fifo_path).await
}
}
pub struct ExtcapFormatter<'a, T: ?Sized>(pub &'a T)
where
Self: Display;
pub trait PrintSentence {
fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
fn print_sentence(&self) {
print!("{}", ExtcapFormatter(self));
}
}
impl<'a, T: PrintSentence + ?Sized> Display for ExtcapFormatter<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.format_sentence(f)
}
}
#[macro_export]
macro_rules! cargo_metadata {
() => {
$crate::interface::Metadata {
version: env!("CARGO_PKG_VERSION").into(),
help_url: env!("CARGO_PKG_HOMEPAGE").into(),
display_description: env!("CARGO_PKG_DESCRIPTION").into(),
}
};
}
#[cfg(test)]
mod test {
use clap::Args;
use super::ExtcapArgs;
#[test]
fn assert_args() {
let cmd = clap::Command::new("test");
let augmented_cmd = ExtcapArgs::augment_args(cmd);
augmented_cmd.debug_assert();
}
}