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}