zbus/fdo/
mod.rs

1mod error;
2pub use error::{Error, Result};
3
4pub(crate) mod dbus;
5pub use dbus::{
6    ConnectionCredentials, DBusProxy, NameAcquired, NameAcquiredArgs, NameAcquiredStream, NameLost,
7    NameLostArgs, NameLostStream, NameOwnerChanged, NameOwnerChangedArgs, NameOwnerChangedStream,
8    ReleaseNameReply, RequestNameFlags, RequestNameReply, StartServiceReply,
9};
10
11pub(crate) mod introspectable;
12pub(crate) use introspectable::Introspectable;
13pub use introspectable::IntrospectableProxy;
14
15pub(crate) mod monitoring;
16pub use monitoring::MonitoringProxy;
17
18pub(crate) mod object_manager;
19pub use object_manager::{
20    InterfacesAdded, InterfacesAddedArgs, InterfacesAddedStream, InterfacesRemoved,
21    InterfacesRemovedArgs, InterfacesRemovedStream, ManagedObjects, ObjectManager,
22    ObjectManagerProxy,
23};
24
25pub(crate) mod peer;
26pub(crate) use peer::Peer;
27pub use peer::PeerProxy;
28
29pub(crate) mod properties;
30pub use properties::{
31    Properties, PropertiesChanged, PropertiesChangedArgs, PropertiesChangedStream, PropertiesProxy,
32};
33
34pub(crate) mod stats;
35pub use stats::StatsProxy;
36
37#[cfg(test)]
38mod tests {
39    use crate::{DBusError, Error, fdo, interface, message::Message};
40    use futures_util::StreamExt;
41    use ntest::timeout;
42    use test_log::test;
43    use tokio::runtime;
44    use zbus_names::WellKnownName;
45
46    use super::PeerProxy;
47
48    #[test]
49    fn error_from_zerror() {
50        let m = Message::method_call("/", "foo")
51            .unwrap()
52            .destination(":1.2")
53            .unwrap()
54            .build(&())
55            .unwrap();
56        let m = Message::error(&m.header(), "org.freedesktop.DBus.Error.TimedOut")
57            .unwrap()
58            .build(&("so long"))
59            .unwrap();
60        let e: Error = m.into();
61        let e: fdo::Error = e.into();
62        assert_eq!(e, fdo::Error::TimedOut("so long".to_string()),);
63        assert_eq!(e.name(), "org.freedesktop.DBus.Error.TimedOut");
64        assert_eq!(e.description(), Some("so long"));
65    }
66
67    #[test]
68    #[timeout(15000)]
69    fn signal() {
70        // Multi-threaded scheduler.
71        runtime::Runtime::new().unwrap().block_on(test_signal());
72
73        // single-threaded scheduler.
74        runtime::Builder::new_current_thread()
75            .enable_io()
76            .build()
77            .unwrap()
78            .block_on(test_signal());
79    }
80
81    async fn test_signal() {
82        let conn = crate::Connection::session().await.unwrap();
83        let proxy = fdo::DBusProxy::new(&conn).await.unwrap();
84
85        // Register a well-known name with the session bus and ensure we get the appropriate
86        // signals called for that.
87        let well_known = "org.freedesktop.zbus.FdoSignalStreamTest";
88        let unique_name = conn.unique_name().unwrap();
89        let owner_change_stream = proxy
90            .receive_name_owner_changed_with_args(&[(0, well_known), (2, unique_name.as_str())])
91            .await
92            .unwrap();
93
94        let name_acquired_stream = proxy
95            .receive_name_acquired_with_args(&[(0, well_known)])
96            .await
97            .unwrap();
98        let mut stream = owner_change_stream.zip(name_acquired_stream);
99
100        let well_known: WellKnownName<'static> = well_known.try_into().unwrap();
101        proxy
102            .request_name(
103                well_known.as_ref(),
104                fdo::RequestNameFlags::ReplaceExisting.into(),
105            )
106            .await
107            .unwrap();
108
109        let (name_owner_changed, name_acquired) = stream.next().await.unwrap();
110        assert_eq!(name_owner_changed.args().unwrap().name(), &well_known);
111        assert_eq!(
112            *name_owner_changed
113                .args()
114                .unwrap()
115                .new_owner()
116                .as_ref()
117                .unwrap(),
118            *unique_name
119        );
120        assert_eq!(name_acquired.args().unwrap().name(), &well_known);
121
122        let result = proxy.release_name(well_known.as_ref()).await.unwrap();
123        assert_eq!(result, fdo::ReleaseNameReply::Released);
124
125        let result = proxy.release_name(well_known).await.unwrap();
126        assert_eq!(result, fdo::ReleaseNameReply::NonExistent);
127
128        let _stream = proxy
129            .receive_features_changed()
130            .await
131            .filter_map(|changed| async move {
132                let v = changed.get().await.ok();
133                dbg!(v)
134            });
135    }
136
137    #[test]
138    #[timeout(15000)]
139    fn no_object_manager_signals_before_hello() {
140        crate::block_on(no_object_manager_signals_before_hello_async());
141    }
142
143    async fn no_object_manager_signals_before_hello_async() {
144        // We were emitting `InterfacesAdded` signals before `Hello` was called, which is wrong and
145        // results in us getting disconnected by the bus. This test case ensures we don't do that
146        // and also that the signals are eventually emitted.
147
148        // Let's first create an interator to get the signals (it has to be another connection).
149        let conn = zbus::Connection::session().await.unwrap();
150        let mut stream = zbus::MessageStream::for_match_rule(
151            zbus::MatchRule::builder()
152                .msg_type(zbus::message::Type::Signal)
153                .interface("org.freedesktop.DBus.ObjectManager")
154                .unwrap()
155                .path("/org/zbus/NoObjectManagerSignalsBeforeHello")
156                .unwrap()
157                .build(),
158            &conn,
159            None,
160        )
161        .await
162        .unwrap();
163
164        // Now create the service side.
165        struct TestObj;
166        #[interface(name = "org.zbus.TestObj")]
167        impl TestObj {
168            #[zbus(property)]
169            fn test(&self) -> String {
170                "test".into()
171            }
172        }
173        let _conn = zbus::conn::Builder::session()
174            .unwrap()
175            .name("org.zbus.NoObjectManagerSignalsBeforeHello")
176            .unwrap()
177            .serve_at("/org/zbus/NoObjectManagerSignalsBeforeHello/Obj", TestObj)
178            .unwrap()
179            .serve_at(
180                "/org/zbus/NoObjectManagerSignalsBeforeHello",
181                super::ObjectManager,
182            )
183            .unwrap()
184            .build()
185            .await
186            .unwrap();
187
188        // Let's see if the `InterfacesAdded` signal was emitted.
189        let msg = stream.next().await.unwrap().unwrap();
190        let signal = super::InterfacesAdded::from_message(msg).unwrap();
191        assert_eq!(
192            signal.args().unwrap().interfaces_and_properties,
193            vec![(
194                "org.zbus.TestObj".try_into().unwrap(),
195                vec![("Test", zvariant::Value::new("test"))]
196                    .into_iter()
197                    .collect()
198            )]
199            .into_iter()
200            .collect()
201        );
202    }
203
204    #[test]
205    #[timeout(15000)]
206    fn peer_on_arbitrary_path() {
207        crate::block_on(peer_on_arbitrary_path_async());
208    }
209
210    /// Test that org.freedesktop.DBus.Peer works on arbitrary paths.
211    ///
212    /// According to the D-Bus specification:
213    /// "On receipt of the METHOD_CALL message org.freedesktop.DBus.Peer.Ping, an application
214    /// should do nothing other than reply with a METHOD_RETURN as usual. It does not matter
215    /// which object path a ping is sent to."
216    ///
217    /// This test verifies that Ping and GetMachineId work on paths that haven't been
218    /// registered via ObjectServer::at.
219    async fn peer_on_arbitrary_path_async() {
220        // Create a service with only a registered path at /registered
221        struct TestObj;
222        #[interface(name = "org.zbus.TestObj")]
223        impl TestObj {}
224
225        let _service_conn = zbus::conn::Builder::session()
226            .unwrap()
227            .name("org.zbus.PeerArbitraryPathTest")
228            .unwrap()
229            .serve_at("/registered", TestObj)
230            .unwrap()
231            .build()
232            .await
233            .unwrap();
234
235        // Create a client connection
236        let client_conn = zbus::Connection::session().await.unwrap();
237
238        // Ping on an unregistered path should succeed
239        let proxy = PeerProxy::new(
240            &client_conn,
241            "org.zbus.PeerArbitraryPathTest",
242            "/this/path/does/not/exist",
243        )
244        .await
245        .expect("Failed to create PeerProxy");
246        let result = proxy.ping().await;
247        assert!(result.is_ok(), "Ping on unregistered path should succeed");
248
249        // GetMachineId on an unregistered path should succeed
250        let proxy = PeerProxy::new(
251            &client_conn,
252            "org.zbus.PeerArbitraryPathTest",
253            "/another/unregistered/path",
254        )
255        .await
256        .expect("Failed to create PeerProxy");
257        let result = proxy.get_machine_id().await;
258        // GetMachineId may fail on some platforms (like *BSD), but should not fail
259        // with "Unknown object" error
260        match &result {
261            Ok(id) => {
262                assert!(!id.is_empty(), "Machine ID should not be empty");
263            }
264            #[cfg(not(any(
265                target_os = "linux",
266                target_os = "macos",
267                target_os = "freebsd",
268                target_os = "dragonfly",
269                target_os = "openbsd",
270                target_os = "netbsd",
271                windows,
272            )))]
273            Err(fdo::Error::NotSupported(_)) => {
274                // NotSupported is acceptable on platforms where it's not implemented
275            }
276            Err(e) => {
277                panic!("GetMachineId failed unexpectedly: {:?}", e);
278            }
279        }
280
281        // Ping on registered path should still work
282        let proxy = PeerProxy::new(
283            &client_conn,
284            "org.zbus.PeerArbitraryPathTest",
285            "/registered",
286        )
287        .await
288        .expect("Failed to create PeerProxy");
289        let result = proxy.ping().await;
290        assert!(result.is_ok(), "Ping on registered path should succeed");
291
292        // Unknown method on Peer interface should fail properly
293        let result = client_conn
294            .call_method(
295                Some("org.zbus.PeerArbitraryPathTest"),
296                "/unregistered",
297                Some("org.freedesktop.DBus.Peer"),
298                "UnknownMethod",
299                &(),
300            )
301            .await;
302        assert!(result.is_err(), "Unknown method should fail");
303    }
304}