zbus/match_rule/
mod.rs

1//! Bus match rule API.
2
3use std::{
4    borrow::Borrow,
5    fmt::{Display, Write},
6    ops::Deref,
7};
8
9use serde::{de, Deserialize, Serialize};
10use zvariant::Structure;
11
12use crate::{
13    message::Type,
14    names::{BusName, InterfaceName, MemberName, UniqueName},
15    zvariant::{ObjectPath, Str, Type as VariantType},
16    Error, Result,
17};
18
19mod builder;
20pub use builder::Builder;
21
22/// A bus match rule for subscribing to specific messages.
23///
24/// This is mainly used to subscribe to specific signals as by default the bus will not send out
25/// most broadcasted signals. This API is intended to make it easy to create and parse match rules.
26/// See the [match rules section of the D-Bus specification][mrs] for a description of each
27/// possible element of a match rule.
28///
29/// # Examples
30///
31/// ```
32/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
33/// # use zbus::MatchRule;
34///
35/// // Let's take the most typical example of match rule to subscribe to properties' changes:
36/// let rule = MatchRule::builder()
37///     .msg_type(zbus::message::Type::Signal)
38///     .sender("org.freedesktop.DBus")?
39///     .interface("org.freedesktop.DBus.Properties")?
40///     .member("PropertiesChanged")?
41///     .add_arg("org.zbus")?
42///     // Sometimes it's useful to match empty strings (null check).
43///     .add_arg("")?
44///     .build();
45/// let rule_str = rule.to_string();
46/// assert_eq!(
47///     rule_str,
48///     "type='signal',\
49///      sender='org.freedesktop.DBus',\
50///      interface='org.freedesktop.DBus.Properties',\
51///      member='PropertiesChanged',\
52///      arg0='org.zbus',\
53///      arg1=''",
54/// );
55///
56/// // Let's parse it back.
57/// let parsed_rule = MatchRule::try_from(rule_str.as_str())?;
58/// assert_eq!(rule, parsed_rule);
59///
60/// // Now for the `ObjectManager::InterfacesAdded` signal.
61/// let rule = MatchRule::builder()
62///     .msg_type(zbus::message::Type::Signal)
63///     .sender("org.zbus")?
64///     .interface("org.freedesktop.DBus.ObjectManager")?
65///     .member("InterfacesAdded")?
66///     .arg_path(0, "/org/zbus/NewPath")?
67///     .build();
68/// let rule_str = rule.to_string();
69/// assert_eq!(
70///     rule_str,
71///     "type='signal',\
72///      sender='org.zbus',\
73///      interface='org.freedesktop.DBus.ObjectManager',\
74///      member='InterfacesAdded',\
75///      arg0path='/org/zbus/NewPath'",
76/// );
77///
78/// // Let's parse it back.
79/// let parsed_rule = MatchRule::try_from(rule_str.as_str())?;
80/// assert_eq!(rule, parsed_rule);
81///
82/// # Ok(())
83/// # }
84/// ```
85///
86/// # Caveats
87///
88/// The `PartialEq` implementation assumes arguments in both rules are in the same order.
89///
90/// [mrs]: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
91#[derive(Clone, Debug, PartialEq, Eq, Hash, VariantType)]
92#[zvariant(signature = "s")]
93pub struct MatchRule<'m> {
94    pub(crate) msg_type: Option<Type>,
95    pub(crate) sender: Option<BusName<'m>>,
96    pub(crate) interface: Option<InterfaceName<'m>>,
97    pub(crate) member: Option<MemberName<'m>>,
98    pub(crate) path_spec: Option<PathSpec<'m>>,
99    pub(crate) destination: Option<UniqueName<'m>>,
100    pub(crate) args: Vec<(u8, Str<'m>)>,
101    pub(crate) arg_paths: Vec<(u8, ObjectPath<'m>)>,
102    pub(crate) arg0ns: Option<Str<'m>>,
103}
104
105impl<'m> MatchRule<'m> {
106    /// Create a builder for `MatchRule`.
107    pub fn builder() -> Builder<'m> {
108        Builder::new()
109    }
110
111    /// The sender, if set.
112    pub fn sender(&self) -> Option<&BusName<'_>> {
113        self.sender.as_ref()
114    }
115
116    /// The message type, if set.
117    pub fn msg_type(&self) -> Option<Type> {
118        self.msg_type
119    }
120
121    /// The interface, if set.
122    pub fn interface(&self) -> Option<&InterfaceName<'_>> {
123        self.interface.as_ref()
124    }
125
126    /// The member name, if set.
127    pub fn member(&self) -> Option<&MemberName<'_>> {
128        self.member.as_ref()
129    }
130
131    /// The path or path namespace, if set.
132    pub fn path_spec(&self) -> Option<&PathSpec<'_>> {
133        self.path_spec.as_ref()
134    }
135
136    /// The destination, if set.
137    pub fn destination(&self) -> Option<&UniqueName<'_>> {
138        self.destination.as_ref()
139    }
140
141    /// The arguments.
142    pub fn args(&self) -> &[(u8, Str<'_>)] {
143        self.args.as_ref()
144    }
145
146    /// The argument paths.
147    pub fn arg_paths(&self) -> &[(u8, ObjectPath<'_>)] {
148        self.arg_paths.as_ref()
149    }
150
151    /// Match messages whose first argument is within the specified namespace.
152    pub fn arg0ns(&self) -> Option<&Str<'m>> {
153        self.arg0ns.as_ref()
154    }
155
156    /// Create an owned clone of `self`.
157    pub fn to_owned(&self) -> MatchRule<'static> {
158        MatchRule {
159            msg_type: self.msg_type,
160            sender: self.sender.as_ref().map(|s| s.to_owned()),
161            interface: self.interface.as_ref().map(|i| i.to_owned()),
162            member: self.member.as_ref().map(|m| m.to_owned()),
163            path_spec: self.path_spec.as_ref().map(|p| p.to_owned()),
164            destination: self.destination.as_ref().map(|d| d.to_owned()),
165            args: self.args.iter().map(|(i, s)| (*i, s.to_owned())).collect(),
166            arg_paths: self
167                .arg_paths
168                .iter()
169                .map(|(i, p)| (*i, p.to_owned()))
170                .collect(),
171            arg0ns: self.arg0ns.as_ref().map(|a| a.to_owned()),
172        }
173    }
174
175    /// Convert into an owned clone of `self`.
176    pub fn into_owned(self) -> MatchRule<'static> {
177        MatchRule {
178            msg_type: self.msg_type,
179            sender: self.sender.map(|s| s.into_owned()),
180            interface: self.interface.map(|i| i.into_owned()),
181            member: self.member.map(|m| m.into_owned()),
182            path_spec: self.path_spec.map(|p| p.into_owned()),
183            destination: self.destination.map(|d| d.into_owned()),
184            args: self
185                .args
186                .into_iter()
187                .map(|(i, s)| (i, s.into_owned()))
188                .collect(),
189            arg_paths: self
190                .arg_paths
191                .into_iter()
192                .map(|(i, p)| (i, p.into_owned()))
193                .collect(),
194            arg0ns: self.arg0ns.map(|a| a.into_owned()),
195        }
196    }
197
198    /// Match the given message against this rule.
199    ///
200    /// # Caveats
201    ///
202    /// Since this method doesn't have any knowledge of names on the bus (or even connection to a
203    /// bus) matching always succeeds for:
204    ///
205    /// * `sender` in the rule (if set) that is a well-known name. The `sender` on a message is
206    ///   always a unique name.
207    /// * `destination` in the rule when `destination` on the `msg` is a well-known name. The
208    ///   `destination` on a match rule is always a unique name.
209    pub fn matches(&self, msg: &zbus::message::Message) -> Result<bool> {
210        let hdr = msg.header();
211
212        // Start with message type.
213        if let Some(msg_type) = self.msg_type() {
214            if msg_type != msg.message_type() {
215                return Ok(false);
216            }
217        }
218
219        // Then check sender.
220        if let Some(sender) = self.sender() {
221            match sender {
222                BusName::Unique(name) if Some(name) != hdr.sender() => {
223                    return Ok(false);
224                }
225                BusName::Unique(_) => (),
226                // We can't match against a well-known name.
227                BusName::WellKnown(_) => (),
228            }
229        }
230
231        // The interface.
232        if let Some(interface) = self.interface() {
233            match hdr.interface() {
234                Some(msg_interface) if interface != msg_interface => return Ok(false),
235                Some(_) => (),
236                None => return Ok(false),
237            }
238        }
239
240        // The member.
241        if let Some(member) = self.member() {
242            match hdr.member() {
243                Some(msg_member) if member != msg_member => return Ok(false),
244                Some(_) => (),
245                None => return Ok(false),
246            }
247        }
248
249        // The destination.
250        if let Some(destination) = self.destination() {
251            match hdr.destination() {
252                Some(BusName::Unique(name)) if destination != name => {
253                    return Ok(false);
254                }
255                Some(BusName::Unique(_)) | None => (),
256                // We can't match against a well-known name.
257                Some(BusName::WellKnown(_)) => (),
258            };
259        }
260
261        // The path.
262        if let Some(path_spec) = self.path_spec() {
263            let msg_path = match hdr.path() {
264                Some(p) => p,
265                None => return Ok(false),
266            };
267            match path_spec {
268                PathSpec::Path(path) if path != msg_path => return Ok(false),
269                PathSpec::PathNamespace(path_ns) if !msg_path.starts_with(path_ns.as_str()) => {
270                    return Ok(false);
271                }
272                PathSpec::Path(_) | PathSpec::PathNamespace(_) => (),
273            }
274        }
275
276        // The arg0 namespace.
277        if let Some(arg0_ns) = self.arg0ns() {
278            if let Ok(arg0) = msg.body().deserialize_unchecked::<BusName<'_>>() {
279                match arg0.strip_prefix(arg0_ns.as_str()) {
280                    None => return Ok(false),
281                    Some(s) if !s.is_empty() && !s.starts_with('.') => return Ok(false),
282                    _ => (),
283                }
284            } else {
285                return Ok(false);
286            }
287        }
288
289        // Args
290        if self.args().is_empty() && self.arg_paths().is_empty() {
291            return Ok(true);
292        }
293        let body = msg.body();
294        let structure = match body.deserialize::<Structure<'_>>() {
295            Ok(s) => s,
296            Err(_) => return Ok(false),
297        };
298        let args = structure.fields();
299
300        for (i, arg) in self.args() {
301            match args.get(*i as usize) {
302                Some(msg_arg) => match <&str>::try_from(msg_arg) {
303                    Ok(msg_arg) if arg != msg_arg => return Ok(false),
304                    Ok(_) => (),
305                    Err(_) => return Ok(false),
306                },
307                None => return Ok(false),
308            }
309        }
310
311        // Path args
312        for (i, path) in self.arg_paths() {
313            match args.get(*i as usize) {
314                Some(msg_arg) => match <ObjectPath<'_>>::try_from(msg_arg) {
315                    Ok(msg_arg) if *path != msg_arg => return Ok(false),
316                    Ok(_) => (),
317                    Err(_) => return Ok(false),
318                },
319                None => return Ok(false),
320            }
321        }
322
323        Ok(true)
324    }
325
326    pub(crate) fn fdo_signal_builder<S>(signal_name: S) -> Builder<'m>
327    where
328        S: TryInto<MemberName<'m>>,
329        S::Error: Into<Error>,
330    {
331        Builder::new()
332            .msg_type(Type::Signal)
333            .sender("org.freedesktop.DBus")
334            .unwrap()
335            .interface("org.freedesktop.DBus")
336            .unwrap()
337            .member(signal_name)
338            .unwrap()
339    }
340}
341
342impl Display for MatchRule<'_> {
343    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344        let mut first_component = true;
345        if let Some(msg_type) = self.msg_type() {
346            let type_str = match msg_type {
347                Type::Error => "error",
348                Type::MethodCall => "method_call",
349                Type::MethodReturn => "method_return",
350                Type::Signal => "signal",
351            };
352            write_match_rule_string_component(f, "type", type_str, &mut first_component)?;
353        }
354        if let Some(sender) = self.sender() {
355            write_match_rule_string_component(f, "sender", sender, &mut first_component)?;
356        }
357        if let Some(interface) = self.interface() {
358            write_match_rule_string_component(f, "interface", interface, &mut first_component)?;
359        }
360        if let Some(member) = self.member() {
361            write_match_rule_string_component(f, "member", member, &mut first_component)?;
362        }
363        if let Some(destination) = self.destination() {
364            write_match_rule_string_component(f, "destination", destination, &mut first_component)?;
365        }
366        if let Some(path_spec) = self.path_spec() {
367            let (key, value) = match path_spec {
368                PathSpec::Path(path) => ("path", path),
369                PathSpec::PathNamespace(ns) => ("path_namespace", ns),
370            };
371            write_match_rule_string_component(f, key, value, &mut first_component)?;
372        }
373        for (i, arg) in self.args() {
374            write_comma(f, &mut first_component)?;
375            write!(f, "arg{i}='{arg}'")?;
376        }
377        for (i, arg_path) in self.arg_paths() {
378            write_comma(f, &mut first_component)?;
379            write!(f, "arg{i}path='{arg_path}'")?;
380        }
381        if let Some(arg0namespace) = self.arg0ns() {
382            write_comma(f, &mut first_component)?;
383            write!(f, "arg0namespace='{arg0namespace}'")?;
384        }
385
386        Ok(())
387    }
388}
389
390fn write_match_rule_string_component(
391    f: &mut std::fmt::Formatter<'_>,
392    key: &str,
393    value: &str,
394    first_component: &mut bool,
395) -> std::fmt::Result {
396    write_comma(f, first_component)?;
397    f.write_str(key)?;
398    f.write_str("='")?;
399    f.write_str(value)?;
400    f.write_char('\'')?;
401
402    Ok(())
403}
404
405fn write_comma(f: &mut std::fmt::Formatter<'_>, first_component: &mut bool) -> std::fmt::Result {
406    if *first_component {
407        *first_component = false;
408    } else {
409        f.write_char(',')?;
410    }
411
412    Ok(())
413}
414
415impl<'m> TryFrom<&'m str> for MatchRule<'m> {
416    type Error = Error;
417
418    fn try_from(s: &'m str) -> Result<Self> {
419        let components = s.split(',');
420        if components.clone().peekable().peek().is_none() {
421            return Err(Error::InvalidMatchRule);
422        }
423        let mut builder = MatchRule::builder();
424        for component in components {
425            let (key, value) = component.split_once('=').ok_or(Error::InvalidMatchRule)?;
426            if key.is_empty()
427                || value.len() < 2
428                || !value.starts_with('\'')
429                || !value.ends_with('\'')
430            {
431                return Err(Error::InvalidMatchRule);
432            }
433            let value = &value[1..value.len() - 1];
434            builder = match key {
435                "type" => {
436                    let msg_type = match value {
437                        "error" => Type::Error,
438                        "method_call" => Type::MethodCall,
439                        "method_return" => Type::MethodReturn,
440                        "signal" => Type::Signal,
441                        _ => return Err(Error::InvalidMatchRule),
442                    };
443                    builder.msg_type(msg_type)
444                }
445                "sender" => builder.sender(value)?,
446                "interface" => builder.interface(value)?,
447                "member" => builder.member(value)?,
448                "path" => builder.path(value)?,
449                "path_namespace" => builder.path_namespace(value)?,
450                "destination" => builder.destination(value)?,
451                "arg0namespace" => builder.arg0ns(value)?,
452                key if key.starts_with("arg") => {
453                    if let Some(trailing_idx) = key.find("path") {
454                        let idx = key[3..trailing_idx]
455                            .parse::<u8>()
456                            .map_err(|_| Error::InvalidMatchRule)?;
457                        builder.arg_path(idx, value)?
458                    } else {
459                        let idx = key[3..]
460                            .parse::<u8>()
461                            .map_err(|_| Error::InvalidMatchRule)?;
462                        builder.arg(idx, value)?
463                    }
464                }
465                _ => return Err(Error::InvalidMatchRule),
466            };
467        }
468
469        Ok(builder.build())
470    }
471}
472
473impl<'de: 'm, 'm> Deserialize<'de> for MatchRule<'m> {
474    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
475    where
476        D: serde::Deserializer<'de>,
477    {
478        let name = <&str>::deserialize(deserializer)?;
479
480        Self::try_from(name).map_err(|e| de::Error::custom(e.to_string()))
481    }
482}
483
484impl Serialize for MatchRule<'_> {
485    fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
486    where
487        S: serde::Serializer,
488    {
489        serializer.serialize_str(&self.to_string())
490    }
491}
492
493/// The path or path namespace.
494#[derive(Clone, Debug, PartialEq, Eq, Hash)]
495pub enum PathSpec<'m> {
496    Path(ObjectPath<'m>),
497    PathNamespace(ObjectPath<'m>),
498}
499
500impl PathSpec<'_> {
501    /// Create an owned clone of `self`.
502    fn to_owned(&self) -> PathSpec<'static> {
503        match self {
504            PathSpec::Path(path) => PathSpec::Path(path.to_owned()),
505            PathSpec::PathNamespace(ns) => PathSpec::PathNamespace(ns.to_owned()),
506        }
507    }
508
509    /// Convert into an owned clone of `self`.
510    pub fn into_owned(self) -> PathSpec<'static> {
511        match self {
512            PathSpec::Path(path) => PathSpec::Path(path.into_owned()),
513            PathSpec::PathNamespace(ns) => PathSpec::PathNamespace(ns.into_owned()),
514        }
515    }
516}
517
518/// Owned sibling of [`MatchRule`].
519#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, VariantType)]
520pub struct OwnedMatchRule(#[serde(borrow)] MatchRule<'static>);
521
522impl OwnedMatchRule {
523    /// Convert to the inner `MatchRule`, consuming `self`.
524    pub fn into_inner(self) -> MatchRule<'static> {
525        self.0
526    }
527
528    /// Get a reference to the inner `MatchRule`.
529    pub fn inner(&self) -> &MatchRule<'static> {
530        &self.0
531    }
532}
533
534impl Deref for OwnedMatchRule {
535    type Target = MatchRule<'static>;
536
537    fn deref(&self) -> &Self::Target {
538        &self.0
539    }
540}
541
542impl<'a> Borrow<MatchRule<'a>> for OwnedMatchRule {
543    fn borrow(&self) -> &MatchRule<'a> {
544        &self.0
545    }
546}
547
548impl From<OwnedMatchRule> for MatchRule<'_> {
549    fn from(o: OwnedMatchRule) -> Self {
550        o.into_inner()
551    }
552}
553
554impl<'unowned, 'owned: 'unowned> From<&'owned OwnedMatchRule> for MatchRule<'unowned> {
555    fn from(rule: &'owned OwnedMatchRule) -> Self {
556        rule.inner().clone()
557    }
558}
559
560impl From<MatchRule<'_>> for OwnedMatchRule {
561    fn from(rule: MatchRule<'_>) -> Self {
562        OwnedMatchRule(rule.into_owned())
563    }
564}
565
566impl TryFrom<&'_ str> for OwnedMatchRule {
567    type Error = Error;
568
569    fn try_from(value: &str) -> Result<Self> {
570        Ok(Self::from(MatchRule::try_from(value)?))
571    }
572}
573
574impl<'de> Deserialize<'de> for OwnedMatchRule {
575    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
576    where
577        D: de::Deserializer<'de>,
578    {
579        String::deserialize(deserializer)
580            .and_then(|r| {
581                MatchRule::try_from(r.as_str())
582                    .map(|r| r.to_owned())
583                    .map_err(|e| de::Error::custom(e.to_string()))
584            })
585            .map(Self)
586    }
587}
588
589impl PartialEq<MatchRule<'_>> for OwnedMatchRule {
590    fn eq(&self, other: &MatchRule<'_>) -> bool {
591        self.0 == *other
592    }
593}