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}