1 | //! D-Bus standard interfaces. |
2 | //! |
3 | //! The D-Bus specification defines the message bus messages and some standard interfaces that may |
4 | //! be useful across various D-Bus applications. This module provides their proxy. |
5 | |
6 | use enumflags2::{bitflags , BitFlags}; |
7 | use serde::{Deserialize, Serialize}; |
8 | use serde_repr::{Deserialize_repr, Serialize_repr}; |
9 | use static_assertions::assert_impl_all; |
10 | use std::collections::HashMap; |
11 | use zbus_names::{ |
12 | BusName, InterfaceName, OwnedBusName, OwnedInterfaceName, OwnedUniqueName, UniqueName, |
13 | WellKnownName, |
14 | }; |
15 | use zvariant::{ |
16 | DeserializeDict, ObjectPath, Optional, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value, |
17 | }; |
18 | |
19 | use crate::{ |
20 | dbus_interface , dbus_proxy , DBusError, Guid, MessageHeader, ObjectServer, SignalContext, |
21 | }; |
22 | |
23 | #[rustfmt::skip] |
24 | macro_rules! gen_introspectable_proxy { |
25 | ($gen_async:literal, $gen_blocking:literal) => { |
26 | /// Proxy for the `org.freedesktop.DBus.Introspectable` interface. |
27 | #[dbus_proxy( |
28 | interface = "org.freedesktop.DBus.Introspectable" , |
29 | default_path = "/" , |
30 | gen_async = $gen_async, |
31 | gen_blocking = $gen_blocking, |
32 | )] |
33 | trait Introspectable { |
34 | /// Returns an XML description of the object, including its interfaces (with signals and |
35 | /// methods), objects below it in the object path tree, and its properties. |
36 | fn introspect(&self) -> Result<String>; |
37 | } |
38 | }; |
39 | } |
40 | |
41 | gen_introspectable_proxy!(true, false); |
42 | assert_impl_all!(IntrospectableProxy<'_>: Send, Sync, Unpin); |
43 | |
44 | /// Server-side implementation for the `org.freedesktop.DBus.Introspectable` interface. |
45 | /// This interface is implemented automatically for any object registered to the |
46 | /// [ObjectServer](crate::ObjectServer). |
47 | pub(crate) struct Introspectable; |
48 | |
49 | #[dbus_interface (name = "org.freedesktop.DBus.Introspectable" )] |
50 | impl Introspectable { |
51 | async fn introspect( |
52 | &self, |
53 | #[zbus(object_server)] server: &ObjectServer, |
54 | #[zbus(header)] header: MessageHeader<'_>, |
55 | ) -> Result<String> { |
56 | let path: &ObjectPath<'_> = header.path()?.ok_or(err:crate::Error::MissingField)?; |
57 | let root: RwLockReadGuard<'_, Node> = server.root().read().await; |
58 | let node: &Node = root |
59 | .get_child(path) |
60 | .ok_or_else(|| Error::UnknownObject(format!("Unknown object ' {path}'" )))?; |
61 | |
62 | Ok(node.introspect().await) |
63 | } |
64 | } |
65 | |
66 | #[rustfmt::skip] |
67 | macro_rules! gen_properties_proxy { |
68 | ($gen_async:literal, $gen_blocking:literal) => { |
69 | /// Proxy for the `org.freedesktop.DBus.Properties` interface. |
70 | #[dbus_proxy( |
71 | interface = "org.freedesktop.DBus.Properties" , |
72 | assume_defaults = true, |
73 | gen_async = $gen_async, |
74 | gen_blocking = $gen_blocking, |
75 | )] |
76 | trait Properties { |
77 | /// Get a property value. |
78 | async fn get( |
79 | &self, |
80 | interface_name: InterfaceName<'_>, |
81 | property_name: &str, |
82 | ) -> Result<OwnedValue>; |
83 | |
84 | /// Set a property value. |
85 | async fn set( |
86 | &self, |
87 | interface_name: InterfaceName<'_>, |
88 | property_name: &str, |
89 | value: &Value<'_>, |
90 | ) -> Result<()>; |
91 | |
92 | /// Get all properties. |
93 | async fn get_all( |
94 | &self, |
95 | interface_name: InterfaceName<'_>, |
96 | ) -> Result<HashMap<String, OwnedValue>>; |
97 | |
98 | #[dbus_proxy(signal)] |
99 | async fn properties_changed( |
100 | &self, |
101 | interface_name: InterfaceName<'_>, |
102 | changed_properties: HashMap<&str, Value<'_>>, |
103 | invalidated_properties: Vec<&str>, |
104 | ) -> Result<()>; |
105 | } |
106 | }; |
107 | } |
108 | |
109 | gen_properties_proxy!(true, false); |
110 | assert_impl_all!(PropertiesProxy<'_>: Send, Sync, Unpin); |
111 | |
112 | /// Server-side implementation for the `org.freedesktop.DBus.Properties` interface. |
113 | /// This interface is implemented automatically for any object registered to the |
114 | /// [ObjectServer](crate::ObjectServer). |
115 | pub struct Properties; |
116 | |
117 | assert_impl_all!(Properties: Send, Sync, Unpin); |
118 | |
119 | #[dbus_interface (name = "org.freedesktop.DBus.Properties" )] |
120 | impl Properties { |
121 | async fn get( |
122 | &self, |
123 | interface_name: InterfaceName<'_>, |
124 | property_name: &str, |
125 | #[zbus(object_server)] server: &ObjectServer, |
126 | #[zbus(header)] header: MessageHeader<'_>, |
127 | ) -> Result<OwnedValue> { |
128 | let path = header.path()?.ok_or(crate::Error::MissingField)?; |
129 | let root = server.root().read().await; |
130 | let iface = root |
131 | .get_child(path) |
132 | .and_then(|node| node.interface_lock(interface_name.as_ref())) |
133 | .ok_or_else(|| { |
134 | Error::UnknownInterface(format!("Unknown interface ' {interface_name}'" )) |
135 | })?; |
136 | |
137 | let res = iface.read().await.get(property_name).await; |
138 | res.unwrap_or_else(|| { |
139 | Err(Error::UnknownProperty(format!( |
140 | "Unknown property ' {property_name}'" |
141 | ))) |
142 | }) |
143 | } |
144 | |
145 | async fn set( |
146 | &self, |
147 | interface_name: InterfaceName<'_>, |
148 | property_name: &str, |
149 | value: Value<'_>, |
150 | #[zbus(object_server)] server: &ObjectServer, |
151 | #[zbus(header)] header: MessageHeader<'_>, |
152 | #[zbus(signal_context)] ctxt: SignalContext<'_>, |
153 | ) -> Result<()> { |
154 | let path = header.path()?.ok_or(crate::Error::MissingField)?; |
155 | let root = server.root().read().await; |
156 | let iface = root |
157 | .get_child(path) |
158 | .and_then(|node| node.interface_lock(interface_name.as_ref())) |
159 | .ok_or_else(|| { |
160 | Error::UnknownInterface(format!("Unknown interface ' {interface_name}'" )) |
161 | })?; |
162 | |
163 | match iface.read().await.set(property_name, &value, &ctxt) { |
164 | zbus::DispatchResult::RequiresMut => {} |
165 | zbus::DispatchResult::NotFound => { |
166 | return Err(Error::UnknownProperty(format!( |
167 | "Unknown property ' {property_name}'" |
168 | ))); |
169 | } |
170 | zbus::DispatchResult::Async(f) => { |
171 | return f.await.map_err(Into::into); |
172 | } |
173 | } |
174 | let res = iface |
175 | .write() |
176 | .await |
177 | .set_mut(property_name, &value, &ctxt) |
178 | .await; |
179 | res.unwrap_or_else(|| { |
180 | Err(Error::UnknownProperty(format!( |
181 | "Unknown property ' {property_name}'" |
182 | ))) |
183 | }) |
184 | } |
185 | |
186 | async fn get_all( |
187 | &self, |
188 | interface_name: InterfaceName<'_>, |
189 | #[zbus(object_server)] server: &ObjectServer, |
190 | #[zbus(header)] header: MessageHeader<'_>, |
191 | ) -> Result<HashMap<String, OwnedValue>> { |
192 | let path = header.path()?.ok_or(crate::Error::MissingField)?; |
193 | let root = server.root().read().await; |
194 | let iface = root |
195 | .get_child(path) |
196 | .and_then(|node| node.interface_lock(interface_name.as_ref())) |
197 | .ok_or_else(|| { |
198 | Error::UnknownInterface(format!("Unknown interface ' {interface_name}'" )) |
199 | })?; |
200 | |
201 | let res = iface.read().await.get_all().await; |
202 | Ok(res) |
203 | } |
204 | |
205 | /// Emits the `org.freedesktop.DBus.Properties.PropertiesChanged` signal. |
206 | #[dbus_interface (signal)] |
207 | #[rustfmt::skip] |
208 | pub async fn properties_changed( |
209 | ctxt: &SignalContext<'_>, |
210 | interface_name: InterfaceName<'_>, |
211 | changed_properties: &HashMap<&str, &Value<'_>>, |
212 | invalidated_properties: &[&str], |
213 | ) -> zbus::Result<()>; |
214 | } |
215 | |
216 | /// The type returned by the [`ObjectManagerProxy::get_managed_objects`] method. |
217 | pub type ManagedObjects = |
218 | HashMap<OwnedObjectPath, HashMap<OwnedInterfaceName, HashMap<String, OwnedValue>>>; |
219 | |
220 | #[rustfmt::skip] |
221 | macro_rules! gen_object_manager_proxy { |
222 | ($gen_async:literal, $gen_blocking:literal) => { |
223 | /// Proxy for the `org.freedesktop.DBus.ObjectManager` interface. |
224 | /// |
225 | /// **NB:** Changes to properties on existing interfaces are not reported using this interface. |
226 | /// Please use [`PropertiesProxy::receive_properties_changed`] to monitor changes to properties on |
227 | /// objects. |
228 | #[dbus_proxy( |
229 | interface = "org.freedesktop.DBus.ObjectManager" , |
230 | assume_defaults = true, |
231 | gen_async = $gen_async, |
232 | gen_blocking = $gen_blocking, |
233 | )] |
234 | trait ObjectManager { |
235 | /// The return value of this method is a dict whose keys are object paths. All returned object |
236 | /// paths are children of the object path implementing this interface, i.e. their object paths |
237 | /// start with the ObjectManager's object path plus '/'. |
238 | /// |
239 | /// Each value is a dict whose keys are interfaces names. Each value in this inner dict is the |
240 | /// same dict that would be returned by the org.freedesktop.DBus.Properties.GetAll() method for |
241 | /// that combination of object path and interface. If an interface has no properties, the empty |
242 | /// dict is returned. |
243 | fn get_managed_objects(&self) -> Result<ManagedObjects>; |
244 | |
245 | /// This signal is emitted when either a new object is added or when an existing object gains |
246 | /// one or more interfaces. The `interfaces_and_properties` argument contains a map with the |
247 | /// interfaces and properties (if any) that have been added to the given object path. |
248 | #[dbus_proxy(signal)] |
249 | fn interfaces_added( |
250 | &self, |
251 | object_path: ObjectPath<'_>, |
252 | interfaces_and_properties: HashMap<&str, HashMap<&str, Value<'_>>>, |
253 | ) -> Result<()>; |
254 | |
255 | /// This signal is emitted whenever an object is removed or it loses one or more interfaces. |
256 | /// The `interfaces` parameters contains a list of the interfaces that were removed. |
257 | #[dbus_proxy(signal)] |
258 | fn interfaces_removed( |
259 | &self, |
260 | object_path: ObjectPath<'_>, |
261 | interfaces: Vec<&str>, |
262 | ) -> Result<()>; |
263 | } |
264 | }; |
265 | } |
266 | |
267 | gen_object_manager_proxy!(true, false); |
268 | assert_impl_all!(ObjectManagerProxy<'_>: Send, Sync, Unpin); |
269 | |
270 | /// Service-side [Object Manager][om] interface implementation. |
271 | /// |
272 | /// The recommended path to add this interface at is the path form of the well-known name of a D-Bus |
273 | /// service, or below. For example, if a D-Bus service is available at the well-known name |
274 | /// `net.example.ExampleService1`, this interface should typically be registered at |
275 | /// `/net/example/ExampleService1`, or below (to allow for multiple object managers in a service). |
276 | /// |
277 | /// It is supported, but not recommended, to add this interface at the root path, `/`. |
278 | /// |
279 | /// When added to an `ObjectServer`, `InterfacesAdded` signal is emitted for all the objects under |
280 | /// the `path` its added at. You can use this fact to minimize the signal emissions by populating |
281 | /// the entire (sub)tree under `path` before registering an object manager. |
282 | /// |
283 | /// [om]: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager |
284 | #[derive (Debug, Clone)] |
285 | pub struct ObjectManager; |
286 | |
287 | #[dbus_interface (name = "org.freedesktop.DBus.ObjectManager" )] |
288 | impl ObjectManager { |
289 | async fn get_managed_objects( |
290 | &self, |
291 | #[zbus(object_server)] server: &ObjectServer, |
292 | #[zbus(header)] header: MessageHeader<'_>, |
293 | ) -> Result<ManagedObjects> { |
294 | let path = header.path()?.ok_or(crate::Error::MissingField)?; |
295 | let root = server.root().read().await; |
296 | let node = root |
297 | .get_child(path) |
298 | .ok_or_else(|| Error::UnknownObject(format!("Unknown object ' {path}'" )))?; |
299 | |
300 | Ok(node.get_managed_objects().await) |
301 | } |
302 | |
303 | /// This signal is emitted when either a new object is added or when an existing object gains |
304 | /// one or more interfaces. The `interfaces_and_properties` argument contains a map with the |
305 | /// interfaces and properties (if any) that have been added to the given object path. |
306 | #[dbus_interface (signal)] |
307 | pub async fn interfaces_added( |
308 | ctxt: &SignalContext<'_>, |
309 | object_path: &ObjectPath<'_>, |
310 | interfaces_and_properties: &HashMap<InterfaceName<'_>, HashMap<&str, Value<'_>>>, |
311 | ) -> zbus::Result<()>; |
312 | |
313 | /// This signal is emitted whenever an object is removed or it loses one or more interfaces. |
314 | /// The `interfaces` parameters contains a list of the interfaces that were removed. |
315 | #[dbus_interface (signal)] |
316 | pub async fn interfaces_removed( |
317 | ctxt: &SignalContext<'_>, |
318 | object_path: &ObjectPath<'_>, |
319 | interfaces: &[InterfaceName<'_>], |
320 | ) -> zbus::Result<()>; |
321 | } |
322 | |
323 | #[rustfmt::skip] |
324 | macro_rules! gen_peer_proxy { |
325 | ($gen_async:literal, $gen_blocking:literal) => { |
326 | /// Proxy for the `org.freedesktop.DBus.Peer` interface. |
327 | #[dbus_proxy( |
328 | interface = "org.freedesktop.DBus.Peer" , |
329 | assume_defaults = true, |
330 | gen_async = $gen_async, |
331 | gen_blocking = $gen_blocking, |
332 | )] |
333 | trait Peer { |
334 | /// On receipt, an application should do nothing other than reply as usual. It does not matter |
335 | /// which object path a ping is sent to. |
336 | fn ping(&self) -> Result<()>; |
337 | |
338 | /// An application should reply the containing a hex-encoded UUID representing the identity of |
339 | /// the machine the process is running on. This UUID must be the same for all processes on a |
340 | /// single system at least until that system next reboots. It should be the same across reboots |
341 | /// if possible, but this is not always possible to implement and is not guaranteed. It does not |
342 | /// matter which object path a GetMachineId is sent to. |
343 | fn get_machine_id(&self) -> Result<String>; |
344 | } |
345 | }; |
346 | } |
347 | |
348 | gen_peer_proxy!(true, false); |
349 | assert_impl_all!(PeerProxy<'_>: Send, Sync, Unpin); |
350 | |
351 | pub(crate) struct Peer; |
352 | |
353 | /// Server-side implementation for the `org.freedesktop.DBus.Peer` interface. |
354 | /// This interface is implemented automatically for any object registered to the |
355 | /// [ObjectServer](crate::ObjectServer). |
356 | #[dbus_interface (name = "org.freedesktop.DBus.Peer" )] |
357 | impl Peer { |
358 | fn ping(&self) {} |
359 | |
360 | fn get_machine_id(&self) -> Result<String> { |
361 | let mut id: String = match std::fs::read_to_string(path:"/var/lib/dbus/machine-id" ) { |
362 | Ok(id: String) => id, |
363 | Err(e: Error) => { |
364 | if let Ok(id: String) = std::fs::read_to_string(path:"/etc/machine-id" ) { |
365 | id |
366 | } else { |
367 | return Err(Error::IOError(format!( |
368 | "Failed to read from /var/lib/dbus/machine-id or /etc/machine-id: {e}" |
369 | ))); |
370 | } |
371 | } |
372 | }; |
373 | |
374 | let len: usize = id.trim_end().len(); |
375 | id.truncate(new_len:len); |
376 | Ok(id) |
377 | } |
378 | } |
379 | |
380 | #[rustfmt::skip] |
381 | macro_rules! gen_monitoring_proxy { |
382 | ($gen_async:literal, $gen_blocking:literal) => { |
383 | /// Proxy for the `org.freedesktop.DBus.Monitoring` interface. |
384 | #[dbus_proxy( |
385 | interface = "org.freedesktop.DBus.Monitoring" , |
386 | default_service = "org.freedesktop.DBus" , |
387 | default_path = "/org/freedesktop/DBus" , |
388 | assume_defaults = true, |
389 | gen_async = $gen_async, |
390 | gen_blocking = $gen_blocking, |
391 | )] |
392 | trait Monitoring { |
393 | /// Converts the connection into a monitor connection which can be used as a |
394 | /// debugging/monitoring tool. |
395 | fn become_monitor(&self, n1: &[&str], n2: u32) -> Result<()>; |
396 | } |
397 | }; |
398 | } |
399 | |
400 | gen_monitoring_proxy!(true, false); |
401 | assert_impl_all!(MonitoringProxy<'_>: Send, Sync, Unpin); |
402 | |
403 | #[rustfmt::skip] |
404 | macro_rules! gen_stats_proxy { |
405 | ($gen_async:literal, $gen_blocking:literal) => { |
406 | /// Proxy for the `org.freedesktop.DBus.Debug.Stats` interface. |
407 | #[dbus_proxy( |
408 | interface = "org.freedesktop.DBus.Debug.Stats" , |
409 | default_service = "org.freedesktop.DBus" , |
410 | default_path = "/org/freedesktop/DBus" , |
411 | assume_defaults = true, |
412 | gen_async = $gen_async, |
413 | gen_blocking = $gen_blocking, |
414 | )] |
415 | trait Stats { |
416 | /// GetStats (undocumented) |
417 | fn get_stats(&self) -> Result<Vec<HashMap<String, OwnedValue>>>; |
418 | |
419 | /// GetConnectionStats (undocumented) |
420 | fn get_connection_stats(&self, n1: &str) -> Result<Vec<HashMap<String, OwnedValue>>>; |
421 | |
422 | /// GetAllMatchRules (undocumented) |
423 | fn get_all_match_rules(&self) -> Result<Vec<HashMap<String, Vec<String>>>>; |
424 | } |
425 | }; |
426 | } |
427 | |
428 | gen_stats_proxy!(true, false); |
429 | assert_impl_all!(StatsProxy<'_>: Send, Sync, Unpin); |
430 | |
431 | /// The flags used by the bus [`request_name`] method. |
432 | /// |
433 | /// [`request_name`]: struct.DBusProxy.html#method.request_name |
434 | #[bitflags ] |
435 | #[repr (u32)] |
436 | #[derive (Type, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)] |
437 | pub enum RequestNameFlags { |
438 | /// If an application A specifies this flag and succeeds in becoming the owner of the name, and |
439 | /// another application B later calls [`request_name`] with the [`ReplaceExisting`] flag, then |
440 | /// application A will lose ownership and receive a `org.freedesktop.DBus.NameLost` signal, and |
441 | /// application B will become the new owner. If [`AllowReplacement`] is not specified by |
442 | /// application A, or [`ReplaceExisting`] is not specified by application B, then application B |
443 | /// will not replace application A as the owner. |
444 | /// |
445 | /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting |
446 | /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement |
447 | /// [`request_name`]: struct.DBusProxy.html#method.request_name |
448 | AllowReplacement = 0x01, |
449 | /// Try to replace the current owner if there is one. If this flag is not set the application |
450 | /// will only become the owner of the name if there is no current owner. If this flag is set, |
451 | /// the application will replace the current owner if the current owner specified |
452 | /// [`AllowReplacement`]. |
453 | /// |
454 | /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement |
455 | ReplaceExisting = 0x02, |
456 | /// Without this flag, if an application requests a name that is already owned, the |
457 | /// application will be placed in a queue to own the name when the current owner gives it |
458 | /// up. If this flag is given, the application will not be placed in the queue, the |
459 | /// request for the name will simply fail. This flag also affects behavior when an |
460 | /// application is replaced as name owner; by default the application moves back into the |
461 | /// waiting queue, unless this flag was provided when the application became the name |
462 | /// owner. |
463 | DoNotQueue = 0x04, |
464 | } |
465 | |
466 | assert_impl_all!(RequestNameFlags: Send, Sync, Unpin); |
467 | |
468 | /// The return code of the [`request_name`] method. |
469 | /// |
470 | /// [`request_name`]: struct.DBusProxy.html#method.request_name |
471 | #[repr (u32)] |
472 | #[derive (Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)] |
473 | pub enum RequestNameReply { |
474 | /// The caller is now the primary owner of the name, replacing any previous owner. Either the |
475 | /// name had no owner before, or the caller specified [`ReplaceExisting`] and the current owner |
476 | /// specified [`AllowReplacement`]. |
477 | /// |
478 | /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting |
479 | /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement |
480 | PrimaryOwner = 0x01, |
481 | /// The name already had an owner, [`DoNotQueue`] was not specified, and either the current |
482 | /// owner did not specify [`AllowReplacement`] or the requesting application did not specify |
483 | /// [`ReplaceExisting`]. |
484 | /// |
485 | /// [`DoNotQueue`]: enum.RequestNameFlags.html#variant.DoNotQueue |
486 | /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting |
487 | /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement |
488 | InQueue = 0x02, |
489 | /// The name already has an owner, [`DoNotQueue`] was specified, and either |
490 | /// [`AllowReplacement`] was not specified by the current owner, or [`ReplaceExisting`] was |
491 | /// not specified by the requesting application. |
492 | /// |
493 | /// [`DoNotQueue`]: enum.RequestNameFlags.html#variant.DoNotQueue |
494 | /// [`ReplaceExisting`]: enum.RequestNameFlags.html#variant.ReplaceExisting |
495 | /// [`AllowReplacement`]: enum.RequestNameFlags.html#variant.AllowReplacement |
496 | Exists = 0x03, |
497 | /// The application trying to request ownership of a name is already the owner of it. |
498 | AlreadyOwner = 0x04, |
499 | } |
500 | |
501 | assert_impl_all!(RequestNameReply: Send, Sync, Unpin); |
502 | |
503 | /// The return code of the [`release_name`] method. |
504 | /// |
505 | /// [`release_name`]: struct.DBusProxy.html#method.release_name |
506 | #[repr (u32)] |
507 | #[derive (Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)] |
508 | pub enum ReleaseNameReply { |
509 | /// The caller has released their claim on the given name. Either the caller was the primary |
510 | /// owner of the name, and the name is now unused or taken by somebody waiting in the queue for |
511 | /// the name, or the caller was waiting in the queue for the name and has now been removed from |
512 | /// the queue. |
513 | Released = 0x01, |
514 | /// The given name does not exist on this bus. |
515 | NonExistent = 0x02, |
516 | /// The caller was not the primary owner of this name, and was also not waiting in the queue to |
517 | /// own this name. |
518 | NotOwner = 0x03, |
519 | } |
520 | |
521 | assert_impl_all!(ReleaseNameReply: Send, Sync, Unpin); |
522 | |
523 | /// Credentials of a process connected to a bus server. |
524 | /// |
525 | /// If unable to determine certain credentials (for instance, because the process is not on the same |
526 | /// machine as the bus daemon, or because this version of the bus daemon does not support a |
527 | /// particular security framework), or if the values of those credentials cannot be represented as |
528 | /// documented here, then those credentials are omitted. |
529 | /// |
530 | /// **Note**: unknown keys, in particular those with "." that are not from the specification, will |
531 | /// be ignored. Use your own implementation or contribute your keys here, or in the specification. |
532 | #[derive (Debug, Default, DeserializeDict, PartialEq, Eq, SerializeDict, Type)] |
533 | #[zvariant(signature = "a{sv}" )] |
534 | pub struct ConnectionCredentials { |
535 | #[zvariant(rename = "UnixUserID" )] |
536 | #[deprecated (since = "3.13.0" , note = "Use `unix_user_id` method" )] |
537 | pub unix_user_id: Option<u32>, |
538 | |
539 | #[zvariant(rename = "UnixGroupIDs" )] |
540 | #[deprecated (since = "3.13.0" , note = "Use `unix_group_ids` method" )] |
541 | pub unix_group_ids: Option<Vec<u32>>, |
542 | |
543 | #[zvariant(rename = "ProcessID" )] |
544 | #[deprecated (since = "3.13.0" , note = "Use `process_id` method" )] |
545 | pub process_id: Option<u32>, |
546 | |
547 | #[zvariant(rename = "WindowsSID" )] |
548 | #[deprecated (since = "3.13.0" , note = "Use `windows_sid` method" )] |
549 | pub windows_sid: Option<String>, |
550 | |
551 | #[zvariant(rename = "LinuxSecurityLabel" )] |
552 | #[deprecated (since = "3.13.0" , note = "Use `linux_security_label` method" )] |
553 | pub linux_security_label: Option<Vec<u8>>, |
554 | } |
555 | |
556 | #[allow (deprecated)] |
557 | impl ConnectionCredentials { |
558 | /// The numeric Unix user ID, as defined by POSIX. |
559 | pub fn unix_user_id(&self) -> Option<u32> { |
560 | self.unix_user_id |
561 | } |
562 | |
563 | /// The numeric Unix group IDs (including both the primary group and the supplementary groups), |
564 | /// as defined by POSIX, in numerically sorted order. This array is either complete or absent: |
565 | /// if the message bus is able to determine some but not all of the caller's groups, or if one |
566 | /// of the groups is not representable in a UINT32, it must not add this credential to the |
567 | /// dictionary. |
568 | pub fn unix_group_ids(&self) -> Option<&Vec<u32>> { |
569 | self.unix_group_ids.as_ref() |
570 | } |
571 | |
572 | /// Same as [`ConnectionCredentials::unix_group_ids`], but consumes `self` and returns the group |
573 | /// IDs Vec. |
574 | pub fn into_unix_group_ids(self) -> Option<Vec<u32>> { |
575 | self.unix_group_ids |
576 | } |
577 | |
578 | /// The numeric process ID, on platforms that have this concept. On Unix, this is the process ID |
579 | /// defined by POSIX. |
580 | pub fn process_id(&self) -> Option<u32> { |
581 | self.process_id |
582 | } |
583 | |
584 | /// The Windows security identifier in its string form, e.g. |
585 | /// `S-1-5-21-3623811015-3361044348-30300820-1013` for a domain or local computer user or |
586 | /// "S-1-5-18` for the LOCAL_SYSTEM user. |
587 | pub fn windows_sid(&self) -> Option<&String> { |
588 | self.windows_sid.as_ref() |
589 | } |
590 | |
591 | /// Same as [`ConnectionCredentials::windows_sid`], but consumes `self` and returns the SID |
592 | /// string. |
593 | pub fn into_windows_sid(self) -> Option<String> { |
594 | self.windows_sid |
595 | } |
596 | |
597 | /// On Linux systems, the security label that would result from the SO_PEERSEC getsockopt call. |
598 | /// The array contains the non-zero bytes of the security label in an unspecified |
599 | /// ASCII-compatible encoding, followed by a single zero byte. |
600 | /// |
601 | /// For example, the SELinux context `system_u:system_r:init_t:s0` (a string of length 27) would |
602 | /// be encoded as 28 bytes ending with `':', 's', '0', '\x00'` |
603 | /// |
604 | /// On SELinux systems this is the SELinux context, as output by `ps -Z` or `ls -Z`. Typical |
605 | /// values might include `system_u:system_r:init_t:s0`, |
606 | /// `unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023`, or |
607 | /// `unconfined_u:unconfined_r:chrome_sandbox_t:s0-s0:c0.c1023`. |
608 | /// |
609 | /// On Smack systems, this is the Smack label. Typical values might include `_`, `*`, `User`, |
610 | /// `System` or `System::Shared`. |
611 | /// |
612 | /// On AppArmor systems, this is the AppArmor context, a composite string encoding the AppArmor |
613 | /// label (one or more profiles) and the enforcement mode. Typical values might include |
614 | /// `unconfined`, `/usr/bin/firefox (enforce)` or `user1 (complain)`. |
615 | pub fn linux_security_label(&self) -> Option<&Vec<u8>> { |
616 | self.linux_security_label.as_ref() |
617 | } |
618 | |
619 | /// Same as [`ConnectionCredentials::linux_security_label`], but consumes `self` and returns |
620 | /// the security label bytes. |
621 | pub fn into_linux_security_label(self) -> Option<Vec<u8>> { |
622 | self.linux_security_label |
623 | } |
624 | |
625 | /// Set the numeric Unix user ID, as defined by POSIX. |
626 | pub fn set_unix_user_id(mut self, unix_user_id: u32) -> Self { |
627 | self.unix_user_id = Some(unix_user_id); |
628 | |
629 | self |
630 | } |
631 | |
632 | /// Add a numeric Unix group ID. |
633 | /// |
634 | /// See [`ConnectionCredentials::unix_group_ids`] for more information. |
635 | pub fn add_unix_group_id(mut self, unix_group_id: u32) -> Self { |
636 | self.unix_group_ids |
637 | .get_or_insert_with(Vec::new) |
638 | .push(unix_group_id); |
639 | |
640 | self |
641 | } |
642 | |
643 | /// Set the numeric process ID, on platforms that have this concept. |
644 | /// |
645 | /// See [`ConnectionCredentials::process_id`] for more information. |
646 | pub fn set_process_id(mut self, process_id: u32) -> Self { |
647 | self.process_id = Some(process_id); |
648 | |
649 | self |
650 | } |
651 | |
652 | /// Set the Windows security identifier in its string form. |
653 | pub fn set_windows_sid(mut self, windows_sid: String) -> Self { |
654 | self.windows_sid = Some(windows_sid); |
655 | |
656 | self |
657 | } |
658 | |
659 | /// Set the Linux security label. |
660 | /// |
661 | /// See [`ConnectionCredentials::linux_security_label`] for more information. |
662 | pub fn set_linux_security_label(mut self, linux_security_label: Vec<u8>) -> Self { |
663 | self.linux_security_label = Some(linux_security_label); |
664 | |
665 | self |
666 | } |
667 | } |
668 | |
669 | #[rustfmt::skip] |
670 | macro_rules! gen_dbus_proxy { |
671 | ($gen_async:literal, $gen_blocking:literal) => { |
672 | /// Proxy for the `org.freedesktop.DBus` interface. |
673 | #[dbus_proxy( |
674 | assume_defaults = true, |
675 | interface = "org.freedesktop.DBus" , |
676 | gen_async = $gen_async, |
677 | gen_blocking = $gen_blocking, |
678 | )] |
679 | trait DBus { |
680 | /// Adds a match rule to match messages going through the message bus |
681 | #[deprecated(since = "3.5.0" , note = "Use `add_match_rule` instead" )] |
682 | fn add_match(&self, rule: &str) -> Result<()>; |
683 | |
684 | /// Adds a match rule to match messages going through the message bus |
685 | #[dbus_proxy(name = "AddMatch" )] |
686 | fn add_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>; |
687 | |
688 | /// Returns auditing data used by Solaris ADT, in an unspecified binary format. |
689 | fn get_adt_audit_session_data(&self, bus_name: BusName<'_>) -> Result<Vec<u8>>; |
690 | |
691 | /// Returns as many credentials as possible for the process connected to the server. |
692 | fn get_connection_credentials( |
693 | &self, |
694 | bus_name: BusName<'_>, |
695 | ) -> Result<ConnectionCredentials>; |
696 | |
697 | /// Returns the security context used by SELinux, in an unspecified format. |
698 | #[dbus_proxy(name = "GetConnectionSELinuxSecurityContext" )] |
699 | fn get_connection_selinux_security_context( |
700 | &self, |
701 | bus_name: BusName<'_>, |
702 | ) -> Result<Vec<u8>>; |
703 | |
704 | /// Returns the Unix process ID of the process connected to the server. |
705 | #[dbus_proxy(name = "GetConnectionUnixProcessID" )] |
706 | fn get_connection_unix_process_id(&self, bus_name: BusName<'_>) -> Result<u32>; |
707 | |
708 | /// Returns the Unix user ID of the process connected to the server. |
709 | fn get_connection_unix_user(&self, bus_name: BusName<'_>) -> Result<u32>; |
710 | |
711 | /// Gets the unique ID of the bus. |
712 | fn get_id(&self) -> Result<Guid>; |
713 | |
714 | /// Returns the unique connection name of the primary owner of the name given. |
715 | fn get_name_owner(&self, name: BusName<'_>) -> Result<OwnedUniqueName>; |
716 | |
717 | /// Returns the unique name assigned to the connection. |
718 | fn hello(&self) -> Result<OwnedUniqueName>; |
719 | |
720 | /// Returns a list of all names that can be activated on the bus. |
721 | fn list_activatable_names(&self) -> Result<Vec<OwnedBusName>>; |
722 | |
723 | /// Returns a list of all currently-owned names on the bus. |
724 | fn list_names(&self) -> Result<Vec<OwnedBusName>>; |
725 | |
726 | /// List the connections currently queued for a bus name. |
727 | fn list_queued_owners(&self, name: WellKnownName<'_>) -> Result<Vec<OwnedUniqueName>>; |
728 | |
729 | /// Checks if the specified name exists (currently has an owner). |
730 | fn name_has_owner(&self, name: BusName<'_>) -> Result<bool>; |
731 | |
732 | /// Ask the message bus to release the method caller's claim to the given name. |
733 | fn release_name(&self, name: WellKnownName<'_>) -> Result<ReleaseNameReply>; |
734 | |
735 | /// Reload server configuration. |
736 | fn reload_config(&self) -> Result<()>; |
737 | |
738 | /// Removes the first rule that matches. |
739 | #[deprecated(since = "3.5.0" , note = "Use `remove_match_rule` instead" )] |
740 | fn remove_match(&self, rule: &str) -> Result<()>; |
741 | |
742 | /// Removes the first rule that matches. |
743 | #[dbus_proxy(name = "RemoveMatch" )] |
744 | fn remove_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>; |
745 | |
746 | /// Ask the message bus to assign the given name to the method caller. |
747 | fn request_name( |
748 | &self, |
749 | name: WellKnownName<'_>, |
750 | flags: BitFlags<RequestNameFlags>, |
751 | ) -> Result<RequestNameReply>; |
752 | |
753 | /// Tries to launch the executable associated with a name (service |
754 | /// activation), as an explicit request. |
755 | fn start_service_by_name(&self, name: WellKnownName<'_>, flags: u32) -> Result<u32>; |
756 | |
757 | /// This method adds to or modifies that environment when activating services. |
758 | fn update_activation_environment(&self, environment: HashMap<&str, &str>) |
759 | -> Result<()>; |
760 | |
761 | /// This signal indicates that the owner of a name has |
762 | /// changed. It's also the signal to use to detect the appearance |
763 | /// of new names on the bus. |
764 | #[dbus_proxy(signal)] |
765 | fn name_owner_changed( |
766 | &self, |
767 | name: BusName<'_>, |
768 | old_owner: Optional<UniqueName<'_>>, |
769 | new_owner: Optional<UniqueName<'_>>, |
770 | ); |
771 | |
772 | /// This signal is sent to a specific application when it loses ownership of a name. |
773 | #[dbus_proxy(signal)] |
774 | fn name_lost(&self, name: BusName<'_>); |
775 | |
776 | /// This signal is sent to a specific application when it gains ownership of a name. |
777 | #[dbus_proxy(signal)] |
778 | fn name_acquired(&self, name: BusName<'_>); |
779 | |
780 | /// This property lists abstract “features” provided by the message bus, and can be used by |
781 | /// clients to detect the capabilities of the message bus with which they are communicating. |
782 | #[dbus_proxy(property)] |
783 | fn features(&self) -> Result<Vec<String>>; |
784 | |
785 | /// This property lists interfaces provided by the `/org/freedesktop/DBus` object, and can be |
786 | /// used by clients to detect the capabilities of the message bus with which they are |
787 | /// communicating. Unlike the standard Introspectable interface, querying this property does not |
788 | /// require parsing XML. This property was added in version 1.11.x of the reference |
789 | /// implementation of the message bus. |
790 | /// |
791 | /// The standard `org.freedesktop.DBus` and `org.freedesktop.DBus.Properties` interfaces are not |
792 | /// included in the value of this property, because their presence can be inferred from the fact |
793 | /// that a method call on `org.freedesktop.DBus.Properties` asking for properties of |
794 | /// `org.freedesktop.DBus` was successful. The standard `org.freedesktop.DBus.Peer` and |
795 | /// `org.freedesktop.DBus.Introspectable` interfaces are not included in the value of this |
796 | /// property either, because they do not indicate features of the message bus implementation. |
797 | #[dbus_proxy(property)] |
798 | fn interfaces(&self) -> Result<Vec<OwnedInterfaceName>>; |
799 | } |
800 | }; |
801 | } |
802 | |
803 | gen_dbus_proxy!(true, false); |
804 | assert_impl_all!(DBusProxy<'_>: Send, Sync, Unpin); |
805 | |
806 | /// Errors from <https://gitlab.freedesktop.org/dbus/dbus/-/blob/master/dbus/dbus-protocol.h> |
807 | #[derive (Clone, Debug, DBusError, PartialEq)] |
808 | #[dbus_error(prefix = "org.freedesktop.DBus.Error" , impl_display = true)] |
809 | #[allow (clippy::upper_case_acronyms)] |
810 | pub enum Error { |
811 | /// Unknown or fall-through ZBus error. |
812 | #[dbus_error(zbus_error)] |
813 | ZBus(zbus::Error), |
814 | |
815 | /// A generic error; "something went wrong" - see the error message for more. |
816 | Failed(String), |
817 | |
818 | /// There was not enough memory to complete an operation. |
819 | NoMemory(String), |
820 | |
821 | /// The bus doesn't know how to launch a service to supply the bus name you wanted. |
822 | ServiceUnknown(String), |
823 | |
824 | /// The bus name you referenced doesn't exist (i.e. no application owns it). |
825 | NameHasNoOwner(String), |
826 | |
827 | /// No reply to a message expecting one, usually means a timeout occurred. |
828 | NoReply(String), |
829 | |
830 | /// Something went wrong reading or writing to a socket, for example. |
831 | IOError(String), |
832 | |
833 | /// A D-Bus bus address was malformed. |
834 | BadAddress(String), |
835 | |
836 | /// Requested operation isn't supported (like ENOSYS on UNIX). |
837 | NotSupported(String), |
838 | |
839 | /// Some limited resource is exhausted. |
840 | LimitsExceeded(String), |
841 | |
842 | /// Security restrictions don't allow doing what you're trying to do. |
843 | AccessDenied(String), |
844 | |
845 | /// Authentication didn't work. |
846 | AuthFailed(String), |
847 | |
848 | /// Unable to connect to server (probably caused by ECONNREFUSED on a socket). |
849 | NoServer(String), |
850 | |
851 | /// Certain timeout errors, possibly ETIMEDOUT on a socket. |
852 | /// Note that `TimedOut` is used for message reply timeouts. |
853 | Timeout(String), |
854 | |
855 | /// No network access (probably ENETUNREACH on a socket). |
856 | NoNetwork(String), |
857 | |
858 | /// Can't bind a socket since its address is in use (i.e. EADDRINUSE). |
859 | AddressInUse(String), |
860 | |
861 | /// The connection is disconnected and you're trying to use it. |
862 | Disconnected(String), |
863 | |
864 | /// Invalid arguments passed to a method call. |
865 | InvalidArgs(String), |
866 | |
867 | /// Missing file. |
868 | FileNotFound(String), |
869 | |
870 | /// Existing file and the operation you're using does not silently overwrite. |
871 | FileExists(String), |
872 | |
873 | /// Method name you invoked isn't known by the object you invoked it on. |
874 | UnknownMethod(String), |
875 | |
876 | /// Object you invoked a method on isn't known. |
877 | UnknownObject(String), |
878 | |
879 | /// Interface you invoked a method on isn't known by the object. |
880 | UnknownInterface(String), |
881 | |
882 | /// Property you tried to access isn't known by the object. |
883 | UnknownProperty(String), |
884 | |
885 | /// Property you tried to set is read-only. |
886 | PropertyReadOnly(String), |
887 | |
888 | /// Certain timeout errors, e.g. while starting a service. |
889 | TimedOut(String), |
890 | |
891 | /// Tried to remove or modify a match rule that didn't exist. |
892 | MatchRuleNotFound(String), |
893 | |
894 | /// The match rule isn't syntactically valid. |
895 | MatchRuleInvalid(String), |
896 | |
897 | /// While starting a new process, the exec() call failed. |
898 | #[dbus_error(name = "Spawn.ExecFailed" )] |
899 | SpawnExecFailed(String), |
900 | |
901 | /// While starting a new process, the fork() call failed. |
902 | #[dbus_error(name = "Spawn.ForkFailed" )] |
903 | SpawnForkFailed(String), |
904 | |
905 | /// While starting a new process, the child exited with a status code. |
906 | #[dbus_error(name = "Spawn.ChildExited" )] |
907 | SpawnChildExited(String), |
908 | |
909 | /// While starting a new process, the child exited on a signal. |
910 | #[dbus_error(name = "Spawn.ChildSignaled" )] |
911 | SpawnChildSignaled(String), |
912 | |
913 | /// While starting a new process, something went wrong. |
914 | #[dbus_error(name = "Spawn.Failed" )] |
915 | SpawnFailed(String), |
916 | |
917 | /// We failed to setup the environment correctly. |
918 | #[dbus_error(name = "Spawn.FailedToSetup" )] |
919 | SpawnFailedToSetup(String), |
920 | |
921 | /// We failed to setup the config parser correctly. |
922 | #[dbus_error(name = "Spawn.ConfigInvalid" )] |
923 | SpawnConfigInvalid(String), |
924 | |
925 | /// Bus name was not valid. |
926 | #[dbus_error(name = "Spawn.ServiceNotValid" )] |
927 | SpawnServiceNotValid(String), |
928 | |
929 | /// Service file not found in system-services directory. |
930 | #[dbus_error(name = "Spawn.ServiceNotFound" )] |
931 | SpawnServiceNotFound(String), |
932 | |
933 | /// Permissions are incorrect on the setuid helper. |
934 | #[dbus_error(name = "Spawn.PermissionsInvalid" )] |
935 | SpawnPermissionsInvalid(String), |
936 | |
937 | /// Service file invalid (Name, User or Exec missing). |
938 | #[dbus_error(name = "Spawn.FileInvalid" )] |
939 | SpawnFileInvalid(String), |
940 | |
941 | /// There was not enough memory to complete the operation. |
942 | #[dbus_error(name = "Spawn.NoMemory" )] |
943 | SpawnNoMemory(String), |
944 | |
945 | /// Tried to get a UNIX process ID and it wasn't available. |
946 | UnixProcessIdUnknown(String), |
947 | |
948 | /// A type signature is not valid. |
949 | InvalidSignature(String), |
950 | |
951 | /// A file contains invalid syntax or is otherwise broken. |
952 | InvalidFileContent(String), |
953 | |
954 | /// Asked for SELinux security context and it wasn't available. |
955 | SELinuxSecurityContextUnknown(String), |
956 | |
957 | /// Asked for ADT audit data and it wasn't available. |
958 | AdtAuditDataUnknown(String), |
959 | |
960 | /// There's already an object with the requested object path. |
961 | ObjectPathInUse(String), |
962 | |
963 | /// The message meta data does not match the payload. e.g. expected number of file descriptors |
964 | /// were not sent over the socket this message was received on. |
965 | InconsistentMessage(String), |
966 | |
967 | /// The message is not allowed without performing interactive authorization, but could have |
968 | /// succeeded if an interactive authorization step was allowed. |
969 | InteractiveAuthorizationRequired(String), |
970 | |
971 | /// The connection is not from a container, or the specified container instance does not exist. |
972 | NotContainer(String), |
973 | } |
974 | |
975 | assert_impl_all!(Error: Send, Sync, Unpin); |
976 | |
977 | /// Alias for a `Result` with the error type [`zbus::fdo::Error`]. |
978 | /// |
979 | /// [`zbus::fdo::Error`]: enum.Error.html |
980 | pub type Result<T> = std::result::Result<T, Error>; |
981 | |
982 | #[cfg (test)] |
983 | mod tests { |
984 | use crate::{fdo, DBusError, Error, Message}; |
985 | use futures_util::StreamExt; |
986 | use ntest::timeout; |
987 | use std::convert::TryInto; |
988 | use test_log::test; |
989 | use tokio::runtime; |
990 | use zbus_names::WellKnownName; |
991 | |
992 | #[test ] |
993 | fn error_from_zerror() { |
994 | let m = Message::method(Some(":1.2" ), None::<()>, "/" , None::<()>, "foo" , &()).unwrap(); |
995 | let m = Message::method_error( |
996 | None::<()>, |
997 | &m, |
998 | "org.freedesktop.DBus.Error.TimedOut" , |
999 | &("so long" ), |
1000 | ) |
1001 | .unwrap(); |
1002 | let e: Error = m.into(); |
1003 | let e: fdo::Error = e.into(); |
1004 | assert_eq!(e, fdo::Error::TimedOut("so long" .to_string()),); |
1005 | assert_eq!(e.name(), "org.freedesktop.DBus.Error.TimedOut" ); |
1006 | assert_eq!(e.description(), Some("so long" )); |
1007 | } |
1008 | |
1009 | #[test ] |
1010 | #[timeout(15000)] |
1011 | fn signal() { |
1012 | // Multi-threaded scheduler. |
1013 | runtime::Runtime::new().unwrap().block_on(test_signal()); |
1014 | |
1015 | // single-threaded scheduler. |
1016 | runtime::Builder::new_current_thread() |
1017 | .enable_io() |
1018 | .build() |
1019 | .unwrap() |
1020 | .block_on(test_signal()); |
1021 | } |
1022 | |
1023 | async fn test_signal() { |
1024 | let conn = crate::Connection::session().await.unwrap(); |
1025 | let proxy = fdo::DBusProxy::new(&conn).await.unwrap(); |
1026 | |
1027 | // Register a well-known name with the session bus and ensure we get the appropriate |
1028 | // signals called for that. |
1029 | let well_known = "org.freedesktop.zbus.FdoSignalStreamTest" ; |
1030 | let unique_name = conn.unique_name().unwrap(); |
1031 | let owner_change_stream = proxy |
1032 | .receive_name_owner_changed_with_args(&[(0, well_known), (2, unique_name.as_str())]) |
1033 | .await |
1034 | .unwrap(); |
1035 | |
1036 | let name_acquired_stream = proxy |
1037 | .receive_name_acquired_with_args(&[(0, well_known)]) |
1038 | .await |
1039 | .unwrap(); |
1040 | let mut stream = owner_change_stream.zip(name_acquired_stream); |
1041 | |
1042 | let well_known: WellKnownName<'static> = well_known.try_into().unwrap(); |
1043 | proxy |
1044 | .request_name( |
1045 | well_known.as_ref(), |
1046 | fdo::RequestNameFlags::ReplaceExisting.into(), |
1047 | ) |
1048 | .await |
1049 | .unwrap(); |
1050 | |
1051 | let (name_owner_changed, name_acquired) = stream.next().await.unwrap(); |
1052 | assert_eq!(name_owner_changed.args().unwrap().name(), &well_known); |
1053 | assert_eq!( |
1054 | *name_owner_changed |
1055 | .args() |
1056 | .unwrap() |
1057 | .new_owner() |
1058 | .as_ref() |
1059 | .unwrap(), |
1060 | *unique_name |
1061 | ); |
1062 | assert_eq!(name_acquired.args().unwrap().name(), &well_known); |
1063 | |
1064 | let result = proxy.release_name(well_known.as_ref()).await.unwrap(); |
1065 | assert_eq!(result, fdo::ReleaseNameReply::Released); |
1066 | |
1067 | let result = proxy.release_name(well_known).await.unwrap(); |
1068 | assert_eq!(result, fdo::ReleaseNameReply::NonExistent); |
1069 | |
1070 | let _stream = proxy |
1071 | .receive_features_changed() |
1072 | .await |
1073 | .filter_map(|changed| async move { |
1074 | let v = changed.get().await.ok(); |
1075 | dbg!(v) |
1076 | }); |
1077 | } |
1078 | } |
1079 | |