r_extcap/controls/synchronous/
mod.rs

1//! Tools for handling the Control Pipe synchrnously.
2//!
3//! There are three main classes provided in this module:
4//!
5//! * [`ExtcapControlSender`] – Implements the sender side for sending control
6//!   packets from the extcap program you are implementing to Wireshark.
7//! * [`ExtcapControlReader`] – Implements the reader side that receives control
8//!   packets sent from Wireshark.
9//! * [`ChannelExtcapControlReader`] – A wrapper around `ExtcapControlReader`
10//!   that provides simpler, but less flexible, handling of the communication
11//!   using a mspc channel.
12//!
13//! See Wireshark's [Adding Capture Interfaces And Log Sources Using
14//! Extcap](https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html#_messages)
15//! section 8.2.3.2.1 for a description of the protocol format.
16
17use log::debug;
18use nom_derive::Parse;
19use std::{
20    fs::File,
21    io::{Read, Write},
22    path::{Path, PathBuf},
23    sync::{
24        mpsc::{self, SendError},
25        Mutex,
26    },
27    thread::JoinHandle,
28};
29use thiserror::Error;
30
31pub mod util;
32use util::ReadExt as _;
33
34use crate::controls::{ControlCommand, ControlPacket};
35
36/// Error type returned for control packet read operations.
37#[derive(Debug, Error)]
38pub enum ReadControlError {
39    /// Error reading the incoming control pipe.
40    #[error(transparent)]
41    IoError(#[from] std::io::Error),
42
43    /// Error parsing the incoming data into the [`ControlPacket`] format.
44    #[error("Error parsing control packet: {0}")]
45    ParseError(String),
46}
47
48/// Error associated with [`ChannelExtcapControlReader`].
49#[derive(Debug, Error)]
50pub enum ControlChannelError {
51    /// Error returned when the control packet cannot be read. See
52    /// the docs on [`ReadControlError`].
53    #[error(transparent)]
54    ReadControl(#[from] ReadControlError),
55
56    /// Error returned when the control packet cannot be sent on the channel.
57    /// This is caused by an underlying [`SendError`].
58    #[error("Cannot send control packet to channel")]
59    CannotSend,
60}
61
62impl<T> From<SendError<T>> for ControlChannelError {
63    fn from(_: SendError<T>) -> Self {
64        ControlChannelError::CannotSend
65    }
66}
67
68/// A reader for an Extcap Control using a [`Channel`][mpsc::channel]. This is
69/// the easier to use, but higher overhead way to read control packets. When the
70/// reader is spawned, a thread is spawned to continuously read messages and
71/// writes them into a bounded `sync_channel`. This allows the user to read the
72/// control messages without worrying about threading, by calling
73/// [`try_read_packet`][Self::try_read_packet] every once in a while.
74///
75/// Assuming the extcap `capture` implementation uses a loop to read or generate
76/// the packets, it can repeatedly call `try_read_packet` to read and handle the
77/// control packets until there are no more buffered messages before starting
78/// the main capturing logic.
79///
80/// For example:
81/// ```ignore
82/// fn capture(reader: &ChannelExtcapControlReader) -> Result<()> {
83///     let pcap_header = ...;
84///     let mut pcap_writer = PcapWriter::with_header(fifo, pcap_header)?;
85///     loop {
86///         while let Some(packet) = reader.try_read_packet() {
87///             // Handle the control packet
88///         }
89///         pcap_writer.write_packet(...)?;
90///     }
91///     Ok(())
92/// }
93pub struct ChannelExtcapControlReader {
94    /// The join handle for the spawned thread. In most cases there is no need
95    /// to use this, as the control fifo is expected to run for the whole
96    /// duration of the capture.
97    pub join_handle: JoinHandle<Result<(), ControlChannelError>>,
98    /// The channel to receive control packets from.
99    pub read_channel: mpsc::Receiver<ControlPacket<'static>>,
100}
101
102impl ChannelExtcapControlReader {
103    /// Create a `ChannelExtcapControlReader` and spawns the underlying thread
104    /// it uses to start reading the control packets from the pipe given in
105    /// `in_path`.
106    pub fn spawn(in_path: PathBuf) -> Self {
107        let (tx, rx) = mpsc::sync_channel::<ControlPacket<'static>>(10);
108        let join_handle = std::thread::spawn(move || {
109            let reader = ExtcapControlReader::new(&in_path);
110            loop {
111                tx.send(reader.read_control_packet()?)?;
112            }
113        });
114        Self {
115            join_handle,
116            read_channel: rx,
117        }
118    }
119
120    /// Try to read a buffered control packet, or return `None` if there are no
121    /// incoming control packets.
122    pub fn try_read_packet(&self) -> Option<ControlPacket<'static>> {
123        self.read_channel.try_recv().ok()
124    }
125
126    /// Reads a control packet. If the incoming channel is empty, this will
127    /// block and wait until an incoming packet comes in. This is typically used
128    /// when the extcap capture starts to wait for the `Initialized` packet from
129    /// the control channel.
130    ///
131    /// If you are only using this method and not using `try_read_packet`,
132    /// consider whether you can use [`ExtcapControlReader`] directly for lower
133    /// overhead.
134    pub fn read_packet(&self) -> Result<ControlPacket<'static>, mpsc::RecvError> {
135        self.read_channel.recv()
136    }
137}
138
139/// A reader for the Extcap control pipe.
140pub struct ExtcapControlReader {
141    /// The file to read the control packets from. This is the fifo passed with
142    /// the `--extcap-control-in` flag.
143    in_file: File,
144}
145
146impl ExtcapControlReader {
147    /// Creates a new instance of [`ExtcapControlReader`].
148    ///
149    /// * `in_path`: The path of the extcap control pipe passed with
150    ///   `--extcap-control-in`.
151    pub fn new(in_path: &Path) -> Self {
152        Self {
153            in_file: File::open(in_path).unwrap(),
154        }
155    }
156
157    /// Read one control packet, blocking until the packet arrives. Since the
158    /// control packet pipe is expected to stay open for the entire duration of
159    /// the extcap program, if the pipe is closed prematurely in this function
160    /// here, `UnexpectedEof` will be returned.
161    pub fn read_control_packet(&self) -> Result<ControlPacket<'static>, ReadControlError> {
162        let mut in_file = &self.in_file;
163        let header_bytes = in_file
164            .try_read_exact::<6>()?
165            .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::UnexpectedEof))?;
166        debug!(
167            "Read header bytes from incoming control message, now parsing... {:?}",
168            header_bytes
169        );
170        let (_rem, packet) = match ControlPacket::parse(&header_bytes) {
171            Ok((rem, packet)) => (rem, packet.into_owned()),
172            Err(nom::Err::Incomplete(nom::Needed::Size(size))) => {
173                let mut payload_bytes = vec![0_u8; size.get()];
174                in_file.read_exact(&mut payload_bytes)?;
175                let all_bytes = [header_bytes.as_slice(), payload_bytes.as_slice()].concat();
176                ControlPacket::parse(&all_bytes)
177                    .map(|(_, packet)| (&[][..], packet.into_owned()))
178                    .unwrap_or_else(|e| panic!("Unable to parse header packet: {e}"))
179            }
180            Err(e) => Err(ReadControlError::ParseError(e.to_string()))?,
181        };
182        debug!("Parsed incoming control message: {packet:?}");
183        Ok(packet)
184    }
185}
186
187const UNUSED_CONTROL_NUMBER: u8 = 255;
188
189/// Sender for extcap control packets. These control packets controls the UI
190/// generated by Wireshark. This trait also provides convenience functions for
191/// sending control packets formatted for particular usages like `info_message`
192/// and `status_message`. For other functions controlling various toolbar
193/// controls, see the methods in the [`control`][crate::controls] module instead.
194pub trait ExtcapControlSenderTrait: Sized {
195    /// Sends the given `packet` by writing it to the given output file (or
196    /// fifo).
197    fn send(self, packet: ControlPacket<'_>) -> std::io::Result<()>;
198
199    /// Shows a message in an information dialog popup. The message will show on
200    /// the screen until the user dismisses the popup.
201    fn info_message(self, message: &str) -> std::io::Result<()> {
202        self.send(ControlPacket::new_with_payload(
203            UNUSED_CONTROL_NUMBER,
204            ControlCommand::InformationMessage,
205            message.as_bytes(),
206        ))
207    }
208
209    /// Shows a message in a warning dialog popup. The message will show on the
210    /// screen until the user dismisses the popup.
211    fn warning_message(self, message: &str) -> std::io::Result<()> {
212        self.send(ControlPacket::new_with_payload(
213            UNUSED_CONTROL_NUMBER,
214            ControlCommand::WarningMessage,
215            message.as_bytes(),
216        ))
217    }
218
219    /// Shows a message in an error dialog popup. The message will show on the
220    /// screen until the user dismisses the popup.
221    fn error_message(self, message: &str) -> std::io::Result<()> {
222        self.send(ControlPacket::new_with_payload(
223            UNUSED_CONTROL_NUMBER,
224            ControlCommand::ErrorMessage,
225            message.as_bytes(),
226        ))
227    }
228
229    /// Shows a message in the status bar at the bottom of the Wireshark window.
230    /// When the message is shown, the status bar will also flash yellow to
231    /// bring it to the user's attention. The message will stay on the status
232    /// bar for a few seconds, or until another message overwrites it.
233    fn status_message(self, message: &str) -> std::io::Result<()> {
234        self.send(ControlPacket::new_with_payload(
235            UNUSED_CONTROL_NUMBER,
236            ControlCommand::StatusbarMessage,
237            message.as_bytes(),
238        ))
239    }
240}
241
242/// A sender for the extcap control packets. `out_file` should be the file given
243/// by the `--extcap-control-out` flag.
244pub struct ExtcapControlSender {
245    out_file: File,
246}
247
248impl ExtcapControlSender {
249    /// Creates a new instance of [`ExtcapControlSender`].
250    ///
251    /// * `out_path`: The path specified by the `--extcap-control-out` flag.
252    pub fn new(out_path: &Path) -> Self {
253        Self {
254            out_file: File::create(out_path).unwrap(),
255        }
256    }
257}
258
259impl ExtcapControlSenderTrait for &mut ExtcapControlSender {
260    fn send(self, packet: ControlPacket<'_>) -> std::io::Result<()> {
261        self.out_file.write_all(&packet.to_header_bytes())?;
262        self.out_file.write_all(&packet.payload)?;
263        self.out_file.flush().unwrap();
264        Ok(())
265    }
266}
267
268/// An implementation of ExtcapControlSenderTrait that is no-op when the
269/// `Option` is `None`. Since Wireshark may not include the
270/// `--extcap-control-out` flag (e.g. when no controls are returned during
271/// `--extcap-interfaces`, or when running in tshark), this allows an easier but
272/// less efficient way to say `option_extcap_sender.status_message(...)` without
273/// constantly checking for the option.
274impl<T> ExtcapControlSenderTrait for &mut Option<T>
275where
276    for<'a> &'a mut T: ExtcapControlSenderTrait,
277{
278    fn send(self, packet: ControlPacket<'_>) -> Result<(), tokio::io::Error> {
279        if let Some(s) = self {
280            s.send(packet)
281        } else {
282            Ok(())
283        }
284    }
285}
286
287/// Just for syntactic niceness when working with a control sender behind a
288/// mutex. This usage allows the sender to be locked only for the duration of
289/// that one control packet, without holding the lock longer than it needs to.
290impl<T> ExtcapControlSenderTrait for &Mutex<T>
291where
292    for<'a> &'a mut T: ExtcapControlSenderTrait,
293{
294    /// Sends a control message to Wireshark.
295    fn send(self, packet: ControlPacket<'_>) -> Result<(), tokio::io::Error> {
296        self.lock().unwrap().send(packet)
297    }
298}