1 //! This crate provides tools to automatically project generic API to D-Bus RPC.
2 //!
3 //! For D-Bus projection to work automatically, the API needs to follow certain restrictions:
4 //!
5 //! * API does not use D-Bus specific features: Signals, Properties, ObjectManager.
6 //! * Interfaces (contain Methods) are hosted on statically allocated D-Bus objects.
7 //! * When the service needs to notify the client about changes, callback objects are used. The
8 //!   client can pass a callback object obeying a specified Interface by passing the D-Bus object
9 //!   path.
10 //!
11 //! A good example is in
12 //! [`manager_service`](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt)
13 //! crate:
14 //!
15 //! * Define RPCProxy like in
16 //! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/lib.rs)
17 //! (TODO: We should remove this requirement in the future).
18 //! * Generate `DBusArg` trait like in
19 //! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_arg.rs).
20 //! This trait is generated by a macro and cannot simply be imported because of Rust's
21 //! [Orphan Rule](https://github.com/Ixrec/rust-orphan-rules).
22 //! * Define D-Bus-agnostic traits like in
23 //! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/iface_bluetooth_manager.rs).
24 //! These traits can be projected into D-Bus Interfaces on D-Bus objects.  A method parameter can
25 //! be of a Rust primitive type, structure, enum, or a callback specially typed as
26 //! `Box<dyn SomeCallbackTrait + Send>`. Callback traits implement `RPCProxy`.
27 //! * Implement the traits like in
28 //! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/bin/btmanagerd/bluetooth_manager.rs),
29 //! also D-Bus-agnostic.
30 //! * Define D-Bus projection mappings like in
31 //! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/bin/btmanagerd/bluetooth_manager_dbus.rs).
32 //!   * Add [`generate_dbus_exporter`](dbus_macros::generate_dbus_exporter) macro to an `impl` of a
33 //!     trait.
34 //!   * Define a method name of each method with [`dbus_method`](dbus_macros::dbus_method) macro.
35 //!   * Similarly, for callbacks use [`dbus_proxy_obj`](dbus_macros::dbus_proxy_obj) macro to define
36 //!     the method mappings.
37 //!   * Rust primitive types can be converted automatically to and from D-Bus types.
38 //!   * Rust structures require implementations of `DBusArg` for the conversion. This is made easy
39 //!     with the [`dbus_propmap`](dbus_macros::dbus_propmap) macro.
40 //!   * Rust enums require implementations of `DBusArg` for the conversion. This is made easy with
41 //!     the [`impl_dbus_arg_enum`](impl_dbus_arg_enum) macro.
42 //! * To project a Rust object to a D-Bus, call the function generated by
43 //!   [`generate_dbus_exporter`](dbus_macros::generate_dbus_exporter) like in
44 //!   [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs)
45 //!   passing in the object path, D-Bus connection, Crossroads object, the Rust object to be
46 //!   projected, and a [`DisconnectWatcher`](DisconnectWatcher) object.
47 
48 use dbus::arg::AppendAll;
49 use dbus::channel::MatchingReceiver;
50 use dbus::message::MatchRule;
51 use dbus::nonblock::SyncConnection;
52 use dbus::strings::BusName;
53 
54 use std::collections::HashMap;
55 use std::sync::{Arc, Mutex};
56 
57 pub mod prelude {
58     pub use super::{
59         dbus_generated, impl_dbus_arg_enum, impl_dbus_arg_from_into, ClientDBusProxy, DBusLog,
60         DBusLogOptions, DBusLogVerbosity, DisconnectWatcher,
61     };
62 }
63 
64 /// A D-Bus "NameOwnerChanged" handler that continuously monitors client disconnects.
65 ///
66 /// When the watched bus address disconnects, all the callbacks associated with it are called with
67 /// their associated ids.
68 pub struct DisconnectWatcher {
69     /// Global counter to provide a unique id every time `get_next_id` is called.
70     next_id: u32,
71 
72     /// Map of disconnect callbacks by bus address and callback id.
73     callbacks: Arc<Mutex<HashMap<BusName<'static>, HashMap<u32, Box<dyn Fn(u32) + Send>>>>>,
74 }
75 
76 impl DisconnectWatcher {
77     /// Creates a new DisconnectWatcher with empty callbacks.
new() -> DisconnectWatcher78     pub fn new() -> DisconnectWatcher {
79         DisconnectWatcher { next_id: 0, callbacks: Arc::new(Mutex::new(HashMap::new())) }
80     }
81 
82     /// Get the next unique id for this watcher.
get_next_id(&mut self) -> u3283     fn get_next_id(&mut self) -> u32 {
84         self.next_id = self.next_id + 1;
85         self.next_id
86     }
87 }
88 
89 impl DisconnectWatcher {
90     /// Adds a client address to be monitored for disconnect events.
add(&mut self, address: BusName<'static>, callback: Box<dyn Fn(u32) + Send>) -> u3291     pub fn add(&mut self, address: BusName<'static>, callback: Box<dyn Fn(u32) + Send>) -> u32 {
92         if !self.callbacks.lock().unwrap().contains_key(&address) {
93             self.callbacks.lock().unwrap().insert(address.clone(), HashMap::new());
94         }
95 
96         let id = self.get_next_id();
97         (*self.callbacks.lock().unwrap().get_mut(&address).unwrap()).insert(id, callback);
98 
99         return id;
100     }
101 
102     /// Sets up the D-Bus handler that monitors client disconnects.
setup_watch(&mut self, conn: Arc<SyncConnection>)103     pub async fn setup_watch(&mut self, conn: Arc<SyncConnection>) {
104         let mr = MatchRule::new_signal("org.freedesktop.DBus", "NameOwnerChanged");
105 
106         conn.add_match_no_cb(&mr.match_str())
107             .await
108             .expect("Unable to add match to D-Bus for monitoring client disconnects");
109         let callbacks_map = self.callbacks.clone();
110         conn.start_receive(
111             mr,
112             Box::new(move |msg, _conn| {
113                 // The args are "address", "old address", "new address".
114                 // https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
115                 let (addr, old, new) = msg.get3::<String, String, String>();
116 
117                 if addr.is_none() || old.is_none() || new.is_none() {
118                     return true;
119                 }
120 
121                 if old.unwrap().eq("") || !new.unwrap().eq("") {
122                     return true;
123                 }
124 
125                 // If old address exists but new address is empty, that means that client is
126                 // disconnected. So call the registered callbacks to be notified of this client
127                 // disconnect.
128                 let addr = BusName::new(addr.unwrap()).unwrap().into_static();
129                 if !callbacks_map.lock().unwrap().contains_key(&addr) {
130                     return true;
131                 }
132 
133                 for (id, callback) in callbacks_map.lock().unwrap()[&addr].iter() {
134                     callback(*id);
135                 }
136 
137                 callbacks_map.lock().unwrap().remove(&addr);
138 
139                 true
140             }),
141         );
142     }
143 
144     /// Removes callback by id if owned by the specific busname.
145     ///
146     /// If the callback can be removed, the callback will be called before being removed.
remove(&mut self, address: BusName<'static>, target_id: u32) -> bool147     pub fn remove(&mut self, address: BusName<'static>, target_id: u32) -> bool {
148         if !self.callbacks.lock().unwrap().contains_key(&address) {
149             return false;
150         }
151 
152         let mut callbacks = self.callbacks.lock().unwrap();
153         match callbacks.get(&address).and_then(|m| m.get(&target_id)) {
154             Some(cb) => {
155                 cb(target_id);
156                 let _ = callbacks.get_mut(&address).and_then(|m| m.remove(&target_id));
157                 true
158             }
159             None => false,
160         }
161     }
162 }
163 
164 /// A client proxy to conveniently call API methods generated with the
165 /// [`generate_dbus_interface_client`](dbus_macros::generate_dbus_interface_client) macro.
166 #[derive(Clone)]
167 pub struct ClientDBusProxy {
168     conn: Arc<SyncConnection>,
169     bus_name: String,
170     objpath: dbus::Path<'static>,
171     interface: String,
172 }
173 
174 impl ClientDBusProxy {
new( conn: Arc<SyncConnection>, bus_name: String, objpath: dbus::Path<'static>, interface: String, ) -> Self175     pub fn new(
176         conn: Arc<SyncConnection>,
177         bus_name: String,
178         objpath: dbus::Path<'static>,
179         interface: String,
180     ) -> Self {
181         Self { conn, bus_name, objpath, interface }
182     }
183 
create_proxy(&self) -> dbus::nonblock::Proxy<Arc<SyncConnection>>184     fn create_proxy(&self) -> dbus::nonblock::Proxy<Arc<SyncConnection>> {
185         let conn = self.conn.clone();
186         dbus::nonblock::Proxy::new(
187             self.bus_name.clone(),
188             self.objpath.clone(),
189             std::time::Duration::from_secs(2),
190             conn,
191         )
192     }
193 
194     /// Asynchronously calls the method and returns the D-Bus result and lets the caller unwrap.
async_method< A: AppendAll, T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>, >( &self, member: &str, args: A, ) -> Result<(T,), dbus::Error>195     pub async fn async_method<
196         A: AppendAll,
197         T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>,
198     >(
199         &self,
200         member: &str,
201         args: A,
202     ) -> Result<(T,), dbus::Error> {
203         let proxy = self.create_proxy();
204         proxy.method_call(self.interface.clone(), member, args).await
205     }
206 
207     /// Asynchronously calls the method and returns the D-Bus result with empty return data.
async_method_noreturn<A: AppendAll>( &self, member: &str, args: A, ) -> Result<(), dbus::Error>208     pub async fn async_method_noreturn<A: AppendAll>(
209         &self,
210         member: &str,
211         args: A,
212     ) -> Result<(), dbus::Error> {
213         let proxy = self.create_proxy();
214         proxy.method_call(self.interface.clone(), member, args).await
215     }
216 
217     /// Calls the method and returns the D-Bus result and lets the caller unwrap.
method_withresult< A: AppendAll, T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>, >( &self, member: &str, args: A, ) -> Result<(T,), dbus::Error>218     pub fn method_withresult<
219         A: AppendAll,
220         T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>,
221     >(
222         &self,
223         member: &str,
224         args: A,
225     ) -> Result<(T,), dbus::Error> {
226         let proxy = self.create_proxy();
227         // We know that all APIs return immediately, so we can block on it for simplicity.
228         return futures::executor::block_on(async {
229             proxy.method_call(self.interface.clone(), member, args).await
230         });
231     }
232 
233     /// Calls the method and unwrap the returned D-Bus result.
method<A: AppendAll, T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>>( &self, member: &str, args: A, ) -> T234     pub fn method<A: AppendAll, T: 'static + dbus::arg::Arg + for<'z> dbus::arg::Get<'z>>(
235         &self,
236         member: &str,
237         args: A,
238     ) -> T {
239         let (ret,): (T,) = self.method_withresult(member, args).unwrap();
240         return ret;
241     }
242 
243     /// Calls the void method and does not need to unwrap the result.
method_noreturn<A: AppendAll>(&self, member: &str, args: A)244     pub fn method_noreturn<A: AppendAll>(&self, member: &str, args: A) {
245         // The real type should be Result<((),), _> since there is no return value. However, to
246         // meet trait constraints, we just use bool and never unwrap the result. This calls the
247         // method, waits for the response but doesn't actually attempt to parse the result (on
248         // unwrap).
249         let _: Result<(bool,), _> = self.method_withresult(member, args);
250     }
251 }
252 
253 /// Implements `DBusArg` for an enum.
254 ///
255 /// A Rust enum is converted to D-Bus UINT32 type.
256 #[macro_export]
257 macro_rules! impl_dbus_arg_enum {
258     ($enum_type:ty) => {
259         impl DBusArg for $enum_type {
260             type DBusType = u32;
261             fn from_dbus(
262                 data: u32,
263                 _conn: Option<Arc<SyncConnection>>,
264                 _remote: Option<dbus::strings::BusName<'static>>,
265                 _disconnect_watcher: Option<
266                     Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
267                 >,
268             ) -> Result<$enum_type, Box<dyn std::error::Error>> {
269                 match <$enum_type>::from_u32(data) {
270                     Some(x) => Ok(x),
271                     None => Err(Box::new(DBusArgError::new(format!(
272                         "error converting {} to {}",
273                         data,
274                         stringify!($enum_type)
275                     )))),
276                 }
277             }
278 
279             fn to_dbus(data: $enum_type) -> Result<u32, Box<dyn std::error::Error>> {
280                 return Ok(data.to_u32().unwrap());
281             }
282 
283             fn log(data: &$enum_type) -> String {
284                 format!("{:?}", data)
285             }
286         }
287     };
288 }
289 
290 /// Implements `DBusArg` for a type which implements TryFrom and TryInto.
291 #[macro_export]
292 macro_rules! impl_dbus_arg_from_into {
293     ($rust_type:ty, $dbus_type:ty) => {
294         impl DBusArg for $rust_type {
295             type DBusType = $dbus_type;
296             fn from_dbus(
297                 data: $dbus_type,
298                 _conn: Option<Arc<SyncConnection>>,
299                 _remote: Option<dbus::strings::BusName<'static>>,
300                 _disconnect_watcher: Option<
301                     Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
302                 >,
303             ) -> Result<$rust_type, Box<dyn std::error::Error>> {
304                 match <$rust_type>::try_from(data.clone()) {
305                     Err(e) => Err(Box::new(DBusArgError::new(format!(
306                         "error converting {:?} to {:?}",
307                         data,
308                         stringify!($rust_type),
309                     )))),
310                     Ok(result) => Ok(result),
311                 }
312             }
313 
314             fn to_dbus(data: $rust_type) -> Result<$dbus_type, Box<dyn std::error::Error>> {
315                 match data.clone().try_into() {
316                     Err(e) => Err(Box::new(DBusArgError::new(format!(
317                         "error converting {:?} to {:?}",
318                         data,
319                         stringify!($dbus_type)
320                     )))),
321                     Ok(result) => Ok(result),
322                 }
323             }
324 
325             fn log(data: &$rust_type) -> String {
326                 format!("{:?}", data)
327             }
328         }
329     };
330 }
331 /// Marks a function to be implemented by dbus_projection macros.
332 #[macro_export]
333 macro_rules! dbus_generated {
334     () => {
335         // The implementation is not used but replaced by generated code.
336         // This uses panic! so that the compiler can accept it for any function
337         // return type.
338         panic!("To be implemented by dbus_projection macros");
339     };
340 }
341 
342 pub enum DBusLogOptions {
343     LogAll,
344     LogMethodNameOnly,
345 }
346 
347 pub enum DBusLogVerbosity {
348     Error,
349     Warn,
350     Info,
351     Verbose,
352 }
353 
354 pub enum DBusLog {
355     Enable(DBusLogOptions, DBusLogVerbosity),
356     Disable,
357 }
358 
359 impl DBusLog {
log(logging: DBusLog, prefix: &str, iface_name: &str, func_name: &str, param: &str)360     pub fn log(logging: DBusLog, prefix: &str, iface_name: &str, func_name: &str, param: &str) {
361         match logging {
362             Self::Enable(option, verbosity) => {
363                 let part_before_param = format!("{}: {}: {}", prefix, iface_name, func_name);
364                 let output = match option {
365                     DBusLogOptions::LogAll => format!("{}: {}", part_before_param, param),
366                     DBusLogOptions::LogMethodNameOnly => part_before_param,
367                 };
368 
369                 match verbosity {
370                     DBusLogVerbosity::Error => log::error!("{}", output),
371                     DBusLogVerbosity::Warn => log::warn!("{}", output),
372                     DBusLogVerbosity::Info => log::info!("{}", output),
373                     DBusLogVerbosity::Verbose => log::debug!("{}", output),
374                 }
375             }
376             Self::Disable => {}
377         }
378     }
379 }
380