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