• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::channel::MatchingReceiver;
49 use dbus::message::MatchRule;
50 use dbus::nonblock::SyncConnection;
51 use dbus::strings::BusName;
52 
53 use std::collections::HashMap;
54 use std::sync::{Arc, Mutex};
55 
56 /// A D-Bus "NameOwnerChanged" handler that continuously monitors client disconnects.
57 ///
58 /// When the watched bus address disconnects, all the callbacks associated with it are called with
59 /// their associated ids.
60 pub struct DisconnectWatcher {
61     /// Global counter to provide a unique id every time `get_next_id` is called.
62     next_id: u32,
63 
64     /// Map of disconnect callbacks by bus address and callback id.
65     callbacks: Arc<Mutex<HashMap<BusName<'static>, HashMap<u32, Box<dyn Fn(u32) + Send>>>>>,
66 }
67 
68 impl DisconnectWatcher {
69     /// Creates a new DisconnectWatcher with empty callbacks.
new() -> DisconnectWatcher70     pub fn new() -> DisconnectWatcher {
71         DisconnectWatcher { next_id: 0, callbacks: Arc::new(Mutex::new(HashMap::new())) }
72     }
73 
74     /// Get the next unique id for this watcher.
get_next_id(&mut self) -> u3275     fn get_next_id(&mut self) -> u32 {
76         self.next_id = self.next_id + 1;
77         self.next_id
78     }
79 }
80 
81 impl DisconnectWatcher {
82     /// Adds a client address to be monitored for disconnect events.
add(&mut self, address: BusName<'static>, callback: Box<dyn Fn(u32) + Send>) -> u3283     pub fn add(&mut self, address: BusName<'static>, callback: Box<dyn Fn(u32) + Send>) -> u32 {
84         if !self.callbacks.lock().unwrap().contains_key(&address) {
85             self.callbacks.lock().unwrap().insert(address.clone(), HashMap::new());
86         }
87 
88         let id = self.get_next_id();
89         (*self.callbacks.lock().unwrap().get_mut(&address).unwrap()).insert(id, callback);
90 
91         return id;
92     }
93 
94     /// Sets up the D-Bus handler that monitors client disconnects.
setup_watch(&mut self, conn: Arc<SyncConnection>)95     pub async fn setup_watch(&mut self, conn: Arc<SyncConnection>) {
96         let mr = MatchRule::new_signal("org.freedesktop.DBus", "NameOwnerChanged");
97 
98         conn.add_match_no_cb(&mr.match_str()).await.unwrap();
99         let callbacks_map = self.callbacks.clone();
100         conn.start_receive(
101             mr,
102             Box::new(move |msg, _conn| {
103                 // The args are "address", "old address", "new address".
104                 // https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
105                 let (addr, old, new) = msg.get3::<String, String, String>();
106 
107                 if addr.is_none() || old.is_none() || new.is_none() {
108                     return true;
109                 }
110 
111                 if old.unwrap().eq("") || !new.unwrap().eq("") {
112                     return true;
113                 }
114 
115                 // If old address exists but new address is empty, that means that client is
116                 // disconnected. So call the registered callbacks to be notified of this client
117                 // disconnect.
118                 let addr = BusName::new(addr.unwrap()).unwrap().into_static();
119                 if !callbacks_map.lock().unwrap().contains_key(&addr) {
120                     return true;
121                 }
122 
123                 for (id, callback) in callbacks_map.lock().unwrap()[&addr].iter() {
124                     callback(*id);
125                 }
126 
127                 callbacks_map.lock().unwrap().remove(&addr);
128 
129                 true
130             }),
131         );
132     }
133 
134     /// Removes callback by id if owned by the specific busname.
135     ///
136     /// If the callback can be removed, the callback will be called before being removed.
remove(&mut self, address: BusName<'static>, target_id: u32) -> bool137     pub fn remove(&mut self, address: BusName<'static>, target_id: u32) -> bool {
138         if !self.callbacks.lock().unwrap().contains_key(&address) {
139             return false;
140         }
141 
142         match self.callbacks.lock().unwrap().get(&address).and_then(|m| m.get(&target_id)) {
143             Some(cb) => {
144                 cb(target_id);
145                 let _ = self
146                     .callbacks
147                     .lock()
148                     .unwrap()
149                     .get_mut(&address)
150                     .and_then(|m| m.remove(&target_id));
151                 true
152             }
153             None => false,
154         }
155     }
156 }
157 
158 /// Implements `DBusArg` for an enum.
159 ///
160 /// A Rust enum is converted to D-Bus INT32 type.
161 #[macro_export]
162 macro_rules! impl_dbus_arg_enum {
163     ($enum_type:ty) => {
164         impl DBusArg for $enum_type {
165             type DBusType = u32;
166             fn from_dbus(
167                 data: u32,
168                 _conn: Option<Arc<SyncConnection>>,
169                 _remote: Option<dbus::strings::BusName<'static>>,
170                 _disconnect_watcher: Option<
171                     Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
172                 >,
173             ) -> Result<$enum_type, Box<dyn std::error::Error>> {
174                 match <$enum_type>::from_u32(data) {
175                     Some(x) => Ok(x),
176                     None => Err(Box::new(DBusArgError::new(String::from(format!(
177                         "error converting {} to {}",
178                         data,
179                         stringify!($enum_type)
180                     ))))),
181                 }
182             }
183 
184             fn to_dbus(data: $enum_type) -> Result<u32, Box<dyn std::error::Error>> {
185                 return Ok(data.to_u32().unwrap());
186             }
187         }
188     };
189 }
190 
191 /// Implements `DBusArg` for a type which implements TryFrom and TryInto.
192 #[macro_export]
193 macro_rules! impl_dbus_arg_from_into {
194     ($rust_type:ty, $dbus_type:ty) => {
195         impl DBusArg for $rust_type {
196             type DBusType = $dbus_type;
197             fn from_dbus(
198                 data: $dbus_type,
199                 _conn: Option<Arc<SyncConnection>>,
200                 _remote: Option<dbus::strings::BusName<'static>>,
201                 _disconnect_watcher: Option<
202                     Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
203                 >,
204             ) -> Result<$rust_type, Box<dyn std::error::Error>> {
205                 match <$rust_type>::try_from(data) {
206                     Err(e) => Err(Box::new(DBusArgError::new(String::from(format!(
207                         "error converting {} to {}",
208                         data,
209                         stringify!($rust_type),
210                     ))))),
211                     Ok(result) => Ok(result),
212                 }
213             }
214 
215             fn to_dbus(data: $rust_type) -> Result<$dbus_type, Box<dyn std::error::Error>> {
216                 match data.try_into() {
217                     Err(e) => Err(Box::new(DBusArgError::new(String::from(format!(
218                         "error converting {:?} to {}",
219                         data,
220                         stringify!($dbus_type)
221                     ))))),
222                     Ok(result) => Ok(result),
223                 }
224             }
225         }
226     };
227 }
228 /// Marks a function to be implemented by dbus_projection macros.
229 #[macro_export]
230 macro_rules! dbus_generated {
231     () => {
232         // The implementation is not used but replaced by generated code.
233         // This uses panic! so that the compiler can accept it for any function
234         // return type.
235         panic!("To be implemented by dbus_projection macros");
236     };
237 }
238