r_extcap/controls/
mod.rs

1//! Module to interact with the controls for the extcap program. There are two
2//! aspects of "controls" handled in this module:
3//!
4//! 1. The toolbar controls in the Wireshark interface, when the user selects
5//!    `View > Interface Toolbars`. This module helps define UI elements on that
6//!    toolbar and react to user interactions performed on them.
7//! 2. The control packets exchanged between this extcap program and Wireshark.
8//!    Besides the UI toolbar elements above, control packets are also used for
9//!    things like displaying status bar and dialog messages, as well as for
10//!    Wireshark to send events like `Initialized`.
11
12use std::borrow::Cow;
13
14use nom::number::streaming::be_u24;
15use nom_derive::Nom;
16use typed_builder::TypedBuilder;
17
18use crate::PrintSentence;
19
20#[cfg(feature = "async")]
21use asynchronous::ExtcapControlSenderTrait as _;
22#[cfg(feature = "sync")]
23use synchronous::ExtcapControlSenderTrait as _;
24
25#[cfg(feature = "async")]
26pub mod asynchronous;
27
28#[cfg(feature = "sync")]
29pub mod synchronous;
30
31/// A `ToolbarControl` that can be enabled or disabled.
32pub trait EnableableControl: ToolbarControl {
33    /// Sets whether the control is enabled or disabled.
34    ///
35    /// Returns a `ControlPacket` that can be sent using a
36    /// [`synchronous::ExtcapControlSender`] or
37    /// [`asynchronous::ExtcapControlSender`].
38    fn set_enabled(&self, enabled: bool) -> ControlPacket<'static> {
39        ControlPacket::new_with_payload(
40            self.control_number(),
41            if enabled {
42                ControlCommand::Enable
43            } else {
44                ControlCommand::Disable
45            },
46            &[][..],
47        )
48    }
49}
50
51/// A [`ToolbarControl`] that has a customizable label.
52pub trait ControlWithLabel: ToolbarControl {
53    /// Sets the label of this control.
54    ///
55    /// Returns a `ControlPacket` that can be sent using a
56    /// [`synchronous::ExtcapControlSender`] or
57    /// [`asynchronous::ExtcapControlSender`].
58    fn set_label<'a>(&self, label: &'a str) -> ControlPacket<'a> {
59        ControlPacket::new_with_payload(
60            self.control_number(),
61            ControlCommand::Set,
62            label.as_bytes(),
63        )
64    }
65}
66
67/// A checkbox which lets the user set a true / false value.
68///
69/// The extcap utility can set a default value at startup, change the value
70/// using [`set_checked`][Self::set_checked], and receive value changes from an
71/// [`ExtcapControlReader`][asynchronous::ExtcapControlReader]. When starting a
72/// capture Wireshark will send the value if different from the default value.
73#[derive(Debug, TypedBuilder)]
74pub struct BooleanControl {
75    /// The control number, a unique identifier for this control.
76    pub control_number: u8,
77    /// The user-visible label for the check box.
78    #[builder(setter(into))]
79    pub display: String,
80    /// Tooltip shown when hovering over the UI element.
81    #[builder(default, setter(strip_option, into))]
82    pub tooltip: Option<String>,
83    /// Whether the control should be checked or unchecked by default
84    #[builder(default = false)]
85    pub default_value: bool,
86}
87
88impl EnableableControl for BooleanControl {}
89impl ControlWithLabel for BooleanControl {}
90
91impl BooleanControl {
92    /// Set whether this checkbox is checked.
93    pub fn set_checked<'a>(&self, checked: bool) -> ControlPacket<'a> {
94        ControlPacket::new_with_payload(
95            self.control_number(),
96            ControlCommand::Set,
97            vec![checked as u8],
98        )
99    }
100}
101
102impl PrintSentence for BooleanControl {
103    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        write!(f, "control {{number={}}}", self.control_number())?;
105        write!(f, "{{type=boolean}}")?;
106        write!(f, "{{display={}}}", self.display)?;
107        write!(f, "{{default={}}}", self.default_value)?;
108        if let Some(tooltip) = &self.tooltip {
109            write!(f, "{{tooltip={}}}", tooltip)?;
110        }
111        writeln!(f)
112    }
113}
114
115impl ToolbarControl for BooleanControl {
116    fn control_number(&self) -> u8 {
117        self.control_number
118    }
119}
120
121/// Button that sends a signal when pressed. The button is only enabled when
122/// capturing.
123///
124/// The extcap utility can set the button text at startup using the `display`
125/// field, change the button text using
126/// [`set_label`][ControlWithLabel::set_label], and receive button press signals
127/// from an [`ExtcapControlReader`][asynchronous::ExtcapControlReader].
128///
129/// The button is disabled and the button text is restored to the default text
130/// when not capturing.
131#[derive(Debug, TypedBuilder)]
132pub struct ButtonControl {
133    /// The control number, a unique identifier for this control.
134    pub control_number: u8,
135    /// The user-visible label for the button.
136    #[builder(setter(into))]
137    pub display: String,
138    /// Tooltip shown when hovering over the UI element.
139    #[builder(default, setter(strip_option, into))]
140    pub tooltip: Option<String>,
141}
142
143impl EnableableControl for ButtonControl {}
144impl ControlWithLabel for ButtonControl {}
145
146impl ToolbarControl for ButtonControl {
147    fn control_number(&self) -> u8 {
148        self.control_number
149    }
150}
151
152impl PrintSentence for ButtonControl {
153    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        write!(f, "control {{number={}}}", self.control_number())?;
155        write!(f, "{{type=button}}")?;
156        write!(f, "{{display={}}}", self.display)?;
157        if let Some(tooltip) = &self.tooltip {
158            write!(f, "{{tooltip={}}}", tooltip)?;
159        }
160        writeln!(f)
161    }
162}
163
164/// A logger mechanism where the extcap utility can send log entries to be
165/// presented in a log window. This communication is unidirectional from this
166/// extcap program to Wireshark.
167///
168/// A button will be displayed in the toolbar which will open the log window
169/// when clicked.
170#[derive(Debug, TypedBuilder)]
171pub struct LoggerControl {
172    /// The control number, a unique identifier for this control.
173    pub control_number: u8,
174    /// Label of the button that opens the log window.
175    #[builder(setter(into))]
176    pub display: String,
177    /// Tooltip shown when hovering over the UI element.
178    #[builder(default, setter(strip_option, into))]
179    pub tooltip: Option<String>,
180}
181
182impl LoggerControl {
183    /// Clear the log and add the given log the entry to the window.
184    pub fn clear_and_add_log<'a>(&self, log: Cow<'a, str>) -> ControlPacket<'a> {
185        ControlPacket::new_with_payload(
186            self.control_number(),
187            ControlCommand::Set,
188            format!("{}\n", log).into_bytes(),
189        )
190    }
191
192    /// Add the log entry to the log window.
193    pub fn add_log<'a>(&self, log: Cow<'a, str>) -> ControlPacket<'a> {
194        ControlPacket::new_with_payload(
195            self.control_number(),
196            ControlCommand::Add,
197            format!("{}\n", log).into_bytes(),
198        )
199    }
200}
201
202impl ToolbarControl for LoggerControl {
203    fn control_number(&self) -> u8 {
204        self.control_number
205    }
206}
207
208impl PrintSentence for LoggerControl {
209    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210        write!(f, "control {{number={}}}", self.control_number())?;
211        write!(f, "{{type=button}}")?;
212        write!(f, "{{role=logger}}")?;
213        write!(f, "{{display={}}}", self.display)?;
214        if let Some(tooltip) = &self.tooltip {
215            write!(f, "{{tooltip={tooltip}}}")?;
216        }
217        writeln!(f)
218    }
219}
220
221/// A button in the toolbar that opens the help URL when clicked. The URL it
222/// opens is defined in [`Metadata::help_url`][crate::interface::Metadata::help_url].
223#[derive(Debug, TypedBuilder)]
224pub struct HelpButtonControl {
225    /// The control number, a unique identifier for this control.
226    pub control_number: u8,
227    /// Label of the button that opens the help URL.
228    #[builder(setter(into))]
229    pub display: String,
230    /// Tooltip shown when hovering over the UI element.
231    #[builder(default, setter(strip_option, into))]
232    pub tooltip: Option<String>,
233}
234
235impl ToolbarControl for HelpButtonControl {
236    fn control_number(&self) -> u8 {
237        self.control_number
238    }
239}
240
241impl PrintSentence for HelpButtonControl {
242    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        write!(f, "control {{number={}}}", self.control_number())?;
244        write!(f, "{{type=button}}")?;
245        write!(f, "{{role=help}}")?;
246        write!(f, "{{display={}}}", self.display)?;
247        if let Some(tooltip) = &self.tooltip {
248            write!(f, "{{tooltip={tooltip}}}")?;
249        }
250        writeln!(f)
251    }
252}
253
254/// This button will restore all control values to default. The button is only
255/// enabled when not capturing.
256#[derive(Debug, TypedBuilder)]
257pub struct RestoreButtonControl {
258    /// The control number, a unique identifier for this control.
259    pub control_number: u8,
260    /// Label of the button.
261    #[builder(setter(into))]
262    pub display: String,
263    /// Tooltip shown when hovering over the UI element.
264    #[builder(default, setter(strip_option, into))]
265    pub tooltip: Option<String>,
266}
267
268impl ToolbarControl for RestoreButtonControl {
269    fn control_number(&self) -> u8 {
270        self.control_number
271    }
272}
273
274impl PrintSentence for RestoreButtonControl {
275    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(f, "control {{number={}}}", self.control_number())?;
277        write!(f, "{{type=button}}")?;
278        write!(f, "{{role=restore}}")?;
279        write!(f, "{{display={}}}", self.display)?;
280        if let Some(tooltip) = &self.tooltip {
281            write!(f, "{{tooltip={tooltip}}}")?;
282        }
283        writeln!(f)
284    }
285}
286
287/// A dropdown selector with fixed values which can be selected.
288///
289/// Default values can be provided using the `options` field. When starting
290/// a capture, Wireshark will send the value as a command line flag if the
291/// selected value is different from the default value.
292#[derive(Debug, TypedBuilder)]
293pub struct SelectorControl {
294    /// The control number, a unique identifier for this control.
295    pub control_number: u8,
296    /// The user-visible label of this selector, displayed next to the drop down
297    /// box.
298    #[builder(setter(into))]
299    pub display: String,
300    /// Tooltip shown when hovering over the UI element.
301    #[builder(default, setter(strip_option, into))]
302    pub tooltip: Option<String>,
303    /// The list of options available for selection in this selector.
304    #[builder(default, setter(into))]
305    pub options: Vec<SelectorControlOption>,
306}
307
308impl SelectorControl {
309    /// Add an option to the selector dynamically.
310    pub fn set_value<'a>(&self, value: &'a str) -> ControlPacket<'a> {
311        ControlPacket::new_with_payload(
312            self.control_number(),
313            ControlCommand::Set,
314            value.as_bytes(),
315        )
316    }
317
318    /// Add an option to the selector dynamically.
319    pub fn add_value<'a>(&self, value: &'a str, display: Option<&'a str>) -> ControlPacket<'a> {
320        let payload_bytes: Cow<'a, [u8]> = match display {
321            Some(d) => Cow::Owned(format!("{}\0{}", value, d).as_bytes().to_vec()),
322            None => Cow::Borrowed(value.as_bytes()),
323        };
324        ControlPacket::new_with_payload(self.control_number(), ControlCommand::Add, payload_bytes)
325    }
326
327    /// Removes an option from the selector.
328    ///
329    /// Panics: If `value` is an empty string.
330    pub fn remove_value<'a>(&self, value: &'a str) -> ControlPacket<'a> {
331        assert!(
332            !value.is_empty(),
333            "Argument to remove_value must not be empty"
334        );
335        ControlPacket::new_with_payload(
336            self.control_number(),
337            ControlCommand::Remove,
338            value.as_bytes(),
339        )
340    }
341
342    /// Clears all options from the selector.
343    pub fn clear(&self) -> ControlPacket<'static> {
344        ControlPacket::new_with_payload(self.control_number(), ControlCommand::Remove, &[][..])
345    }
346}
347
348impl ToolbarControl for SelectorControl {
349    fn control_number(&self) -> u8 {
350        self.control_number
351    }
352}
353
354impl PrintSentence for SelectorControl {
355    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356        write!(
357            f,
358            "control {{number={}}}{{type=selector}}",
359            self.control_number()
360        )?;
361        write!(f, "{{display={}}}", self.display)?;
362        if let Some(tooltip) = &self.tooltip {
363            write!(f, "{{tooltip={}}}", tooltip)?;
364        }
365        writeln!(f)?;
366        for value in self.options.iter() {
367            value.format_sentence(f, self)?;
368        }
369        Ok(())
370    }
371}
372
373/// An option in a [`SelectorControl`].
374#[derive(Clone, Debug, TypedBuilder)]
375pub struct SelectorControlOption {
376    /// The value that is sent in the payload of the [`ControlPacket`] when this
377    /// option is selected.
378    #[builder(setter(into))]
379    pub value: String,
380    /// The user visible label for this option.
381    #[builder(setter(into))]
382    pub display: String,
383    /// Whether this option is selected as the default.
384    #[builder(default)]
385    pub default: bool,
386}
387
388impl SelectorControlOption {
389    /// Writes the extcap config sentence for this option to the formatter. See
390    /// the documentation for [`ExtcapFormatter`][crate::ExtcapFormatter] for
391    /// details.
392    pub fn format_sentence<C: ToolbarControl>(
393        &self,
394        f: &mut std::fmt::Formatter<'_>,
395        control: &C,
396    ) -> std::fmt::Result {
397        write!(
398            f,
399            "value {{control={}}}{{value={}}}{{display={}}}",
400            control.control_number(),
401            self.value,
402            self.display,
403        )?;
404        if self.default {
405            write!(f, "{{default=true}}")?;
406        }
407        writeln!(f)?;
408        Ok(())
409    }
410}
411
412/// A text field toolbar control element.
413///
414/// Maximum length is accepted by a `StringControl` is 32767 bytes.
415///
416/// The default string value can be set at startup, and the value can be changed
417/// dynamically while capturing. When the value changes or is different form the
418/// default, its value will be sent as a [`ControlPacket`] during capture.
419#[derive(Debug, Default, TypedBuilder)]
420pub struct StringControl {
421    /// The control number, a unique identifier for this control.
422    pub control_number: u8,
423    /// A user-visible label for this control.
424    #[builder(setter(into))]
425    pub display: String,
426    /// An optional tooltip that is shown when hovering on the UI element.
427    #[builder(setter(into, strip_option))]
428    pub tooltip: Option<String>,
429    /// An optional placeholder that is shown when this control is empty.
430    #[builder(setter(into, strip_option))]
431    pub placeholder: Option<String>,
432    /// An optional regular expression string that validates the value on the
433    /// field. If the value does not match the regular expression, the text
434    /// field will appear red and its value will not be sent in a
435    /// [`ControlPacket`].
436    ///
437    /// Despite what the Wireshark documentation says, back slashes in the the
438    /// regular expression string do not have to be escaped, just remember to
439    /// use a Rust raw string when defining them. (e.g. r"\d\d\d\d").
440    #[builder(setter(into, strip_option))]
441    pub validation: Option<String>,
442    /// The default value
443    #[builder(default, setter(into, strip_option))]
444    pub default_value: Option<String>,
445}
446
447impl StringControl {
448    /// Sets the value in the text field.
449    ///
450    /// Panics: If the string is longer than 32767 bytes.
451    pub fn set_value<'a>(&self, message: &'a str) -> ControlPacket<'a> {
452        assert!(
453            message.as_bytes().len() <= 32767,
454            "message must not be longer than 32767 bytes"
455        );
456        ControlPacket::new_with_payload(
457            self.control_number,
458            ControlCommand::Set,
459            message.as_bytes(),
460        )
461    }
462}
463
464impl ToolbarControl for StringControl {
465    fn control_number(&self) -> u8 {
466        self.control_number
467    }
468}
469
470impl PrintSentence for StringControl {
471    fn format_sentence(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
472        write!(
473            f,
474            "control {{number={}}}{{type=string}}",
475            self.control_number()
476        )?;
477        write!(f, "{{display={}}}", self.display)?;
478        if let Some(tooltip) = &self.tooltip {
479            write!(f, "{{tooltip={}}}", tooltip)?;
480        }
481        if let Some(placeholder) = &self.placeholder {
482            write!(f, "{{placeholder={}}}", placeholder)?;
483        }
484        if let Some(validation) = &self.validation {
485            write!(f, "{{validation={}}}", validation)?;
486        }
487        if let Some(default_value) = &self.default_value {
488            write!(f, "{{default={}}}", default_value)?;
489        }
490        writeln!(f)
491    }
492}
493
494/// Controls provided by this extcap utility to use in a toolbar in the UI.
495/// These controls are bidirectional and can be used to control the extcap
496/// utility while capturing.
497///
498/// This is useful in scenarios where configuration can be done based on
499/// findings in the capture process, setting temporary values or give other
500/// inputs without restarting the current capture.
501///
502/// Communication from the extcap program to Wireshark is done through methods
503/// on these controls like `set_enabled` or `set_value`, and the implementations
504/// will create a corresponding control packet that can be sent to Wireshark
505/// through [`ControlPacket::send`] or [`ControlPacket::send_async`].
506///
507/// All controls will be presented as GUI elements in a toolbar specific to the
508/// extcap utility. The extcap must not rely on using those controls (they are
509/// optional) because of other capturing tools not using GUI (e.g. tshark,
510/// tfshark).
511pub trait ToolbarControl: PrintSentence {
512    /// The control number, a unique identifier for this control.
513    fn control_number(&self) -> u8;
514}
515
516/// Control packets for the extcap interface. This is used for communication of
517/// control data between Wireshark and this extcap program.
518///
519/// Reference:
520/// <https://www.wireshark.org/docs/wsdg_html_chunked/ChCaptureExtcap.html#_messages>
521#[derive(Debug, Nom, Clone, PartialEq, Eq)]
522pub struct ControlPacket<'a> {
523    /// The common sync pipe indication. This protocol uses the value "T".
524    #[nom(Verify = "*sync_pipe_indication == b'T'")]
525    pub sync_pipe_indication: u8,
526    /// Length of `payload` + 2 bytes for `control_number` and `command`.
527    #[nom(Parse = "be_u24")]
528    pub message_length: u32,
529    /// Unique number to identify the control, as previously returned in the
530    /// `{control}` declarations returned in the
531    /// [`--extcap-interfaces`][crate::ExtcapArgs::extcap_interfaces] phase. This
532    /// number also gives the order of the controls in the interface toolbar.
533    /// The control number, a unique identifier for this control.
534    pub control_number: u8,
535    /// The command associated with this packet. See [`ControlCommand`] for
536    /// details.
537    pub command: ControlCommand,
538    /// Payload specific to the [`command`][Self::command]. For example, the
539    /// payload for [`StatusbarMessage`][ControlCommand::StatusbarMessage] is
540    /// the message string.
541    #[nom(Map = "Cow::from", Take = "(message_length - 2) as usize")]
542    pub payload: Cow<'a, [u8]>,
543}
544
545impl<'a> ControlPacket<'a> {
546    /// Creates a new control packet with a payload.
547    #[must_use]
548    pub fn new_with_payload<CowSlice: Into<Cow<'a, [u8]>>>(
549        control_number: u8,
550        command: ControlCommand,
551        payload: CowSlice,
552    ) -> Self {
553        let payload = payload.into();
554        ControlPacket {
555            sync_pipe_indication: b'T',
556            message_length: (payload.len() + 2) as u32,
557            control_number,
558            command,
559            payload,
560        }
561    }
562
563    /// Creates a new control packet with an empty payload.
564    #[must_use]
565    pub fn new(control_number: u8, command: ControlCommand) -> Self {
566        let empty_slice: &'static [u8] = &[];
567        Self::new_with_payload(control_number, command, empty_slice)
568    }
569
570    /// Outputs the serialzied bytes of the header to send back to wireshark.
571    pub fn to_header_bytes(&self) -> [u8; 6] {
572        let mut bytes = [0_u8; 6];
573        bytes[0] = self.sync_pipe_indication;
574        bytes[1..4].copy_from_slice(&self.message_length.to_be_bytes()[1..]);
575        bytes[4] = self.control_number;
576        bytes[5] = self.command as u8;
577        bytes
578    }
579
580    /// Turns the given ControlPacket into a ControlPacket with fully owned data
581    /// and 'static lifetime.
582    pub fn into_owned(self) -> ControlPacket<'static> {
583        ControlPacket {
584            payload: match self.payload {
585                Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
586                Cow::Owned(v) => Cow::Owned(v),
587            },
588            ..self
589        }
590    }
591
592    /// Sends this control packet to Wireshark using the given `sender`.
593    #[cfg(feature = "sync")]
594    pub fn send(self, sender: &mut synchronous::ExtcapControlSender) -> std::io::Result<()> {
595        sender.send(self)
596    }
597
598    /// Sends this control packet to Wireshark using the given `sender`.
599    #[cfg(feature = "async")]
600    pub async fn send_async(
601        self,
602        sender: &mut asynchronous::ExtcapControlSender,
603    ) -> tokio::io::Result<()> {
604        sender.send(self).await
605    }
606}
607
608/// The control command for the control packet. Note that a `ControlCommand` is
609/// not valid for all control types, for example, the `Remove` command is
610/// applicable only to [`SelectorControls`][SelectorControl], and `Initialized`
611/// is only sent by Wireshark to this extcap program.
612#[derive(Clone, Copy, Debug, PartialEq, Eq, Nom)]
613#[repr(u8)]
614pub enum ControlCommand {
615    /// Sent by Wireshark to indicate that this extcap has been initialized and
616    /// is ready to accept packets.
617    Initialized = 0,
618    /// Either sent by Wireshark to indicate that the user has interacted with
619    /// one of the controls, or sent by the extcap program to change the value
620    /// on a given control.
621    ///
622    /// Used by control types: [`BooleanControl`], [`ButtonControl`],
623    /// [`LoggerControl`], [`SelectorControl`], and [`StringControl`].
624    Set = 1,
625    /// Sent by the extcap program to add a value to the given logger or
626    /// selector.
627    ///
628    /// Used by control types: [`LoggerControl`] and [`SelectorControl`].
629    Add = 2,
630    /// Sent by the extcap program to remove a value from the given selector.
631    ///
632    /// Used by control types: [`SelectorControl`].
633    Remove = 3,
634    /// Sent by the extcap program to enable a given control.
635    ///
636    /// Used by control types: [`BooleanControl`], [`ButtonControl`],
637    /// [`SelectorControl`], and [`StringControl`].
638    Enable = 4,
639    /// Sent by the extcap program to disable a given control.
640    ///
641    /// Used by control types: [`BooleanControl`], [`ButtonControl`],
642    /// [`SelectorControl`], and [`StringControl`].
643    Disable = 5,
644    /// Sent by the extcap program to show a message in the status bar.
645    StatusbarMessage = 6,
646    /// Sent by the extcap program to show a message in an information dialog
647    /// popup.
648    InformationMessage = 7,
649    /// Sent by the extcap program to show a message in a warning dialog popup.
650    WarningMessage = 8,
651    /// Sent by the extcap program to show a message in an error dialog popup.
652    ErrorMessage = 9,
653}
654
655#[cfg(test)]
656mod test {
657    use nom_derive::Parse;
658
659    use super::ControlPacket;
660
661    #[test]
662    fn test_to_bytes() {
663        let packet = ControlPacket::new_with_payload(
664            123,
665            super::ControlCommand::InformationMessage,
666            &b"testing123"[..],
667        );
668        let full_bytes = [&packet.to_header_bytes(), packet.payload.as_ref()].concat();
669        let (rem, parsed_packet) = ControlPacket::parse(&full_bytes).unwrap();
670        assert_eq!(packet, parsed_packet);
671        assert!(rem.is_empty());
672    }
673}