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