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