blob: 6cc38126e3a71415b52a81855ce25765c7fe9dc5 [file] [log] [blame]
//! This crate provides tools to automatically project generic API to D-Bus RPC.
//!
//! For D-Bus projection to work automatically, the API needs to follow certain restrictions:
//!
//! * API does not use D-Bus specific features: Signals, Properties, ObjectManager.
//! * Interfaces (contain Methods) are hosted on statically allocated D-Bus objects.
//! * When the service needs to notify the client about changes, callback objects are used. The
//! client can pass a callback object obeying a specified Interface by passing the D-Bus object
//! path.
//!
//! A good example is in
//! [`manager_service`](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt)
//! crate:
//!
//! * Define RPCProxy like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/lib.rs)
//! (TODO: We should remove this requirement in the future).
//! * Generate `DBusArg` trait like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/bin/btmanagerd/dbus_arg.rs).
//! This trait is generated by a macro and cannot simply be imported because of Rust's
//! [Orphan Rule](https://github.com/Ixrec/rust-orphan-rules).
//! * Define D-Bus-agnostic traits like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/iface_bluetooth_manager.rs).
//! These traits can be projected into D-Bus Interfaces on D-Bus objects. A method parameter can
//! be of a Rust primitive type, structure, enum, or a callback specially typed as
//! `Box<dyn SomeCallbackTrait + Send>`. Callback traits implement `RPCProxy`.
//! * Implement the traits like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/bin/btmanagerd/bluetooth_manager.rs),
//! also D-Bus-agnostic.
//! * Define D-Bus projection mappings like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/bin/btmanagerd/bluetooth_manager_dbus.rs).
//! * Add [`generate_dbus_exporter`](dbus_macros::generate_dbus_exporter) macro to an `impl` of a
//! trait.
//! * Define a method name of each method with [`dbus_method`](dbus_macros::dbus_method) macro.
//! * Similarly, for callbacks use [`dbus_proxy_obj`](dbus_macros::dbus_proxy_obj) macro to define
//! the method mappings.
//! * Rust primitive types can be converted automatically to and from D-Bus types.
//! * Rust structures require implementations of `DBusArg` for the conversion. This is made easy
//! with the [`dbus_propmap`](dbus_macros::dbus_propmap) macro.
//! * Rust enums require implementations of `DBusArg` for the conversion. This is made easy with
//! the [`impl_dbus_arg_enum`](impl_dbus_arg_enum) macro.
//! * To project a Rust object to a D-Bus, call the function generated by
//! [`generate_dbus_exporter`](dbus_macros::generate_dbus_exporter) like in
//! [here](https://android.googlesource.com/platform/system/bt/+/refs/heads/master/gd/rust/linux/mgmt/src/bin/btmanagerd/main.rs)
//! passing in the object path, D-Bus connection, Crossroads object, the Rust object to be
//! projected, and a [`DisconnectWatcher`](DisconnectWatcher) object.
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::SyncConnection;
use dbus::strings::BusName;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
/// A D-Bus "NameOwnerChanged" handler that continuously monitors client disconnects.
///
/// When the watched bus address disconnects, all the callbacks associated with it are called with
/// their associated ids.
pub struct DisconnectWatcher {
/// Global counter to provide a unique id every time `get_next_id` is called.
next_id: u32,
/// Map of disconnect callbacks by bus address and callback id.
callbacks: Arc<Mutex<HashMap<BusName<'static>, HashMap<u32, Box<dyn Fn(u32) + Send>>>>>,
}
impl DisconnectWatcher {
/// Creates a new DisconnectWatcher with empty callbacks.
pub fn new() -> DisconnectWatcher {
DisconnectWatcher { next_id: 0, callbacks: Arc::new(Mutex::new(HashMap::new())) }
}
/// Get the next unique id for this watcher.
fn get_next_id(&mut self) -> u32 {
self.next_id = self.next_id + 1;
self.next_id
}
}
impl DisconnectWatcher {
/// Adds a client address to be monitored for disconnect events.
pub fn add(&mut self, address: BusName<'static>, callback: Box<dyn Fn(u32) + Send>) -> u32 {
if !self.callbacks.lock().unwrap().contains_key(&address) {
self.callbacks.lock().unwrap().insert(address.clone(), HashMap::new());
}
let id = self.get_next_id();
(*self.callbacks.lock().unwrap().get_mut(&address).unwrap()).insert(id, callback);
return id;
}
/// Sets up the D-Bus handler that monitors client disconnects.
pub async fn setup_watch(&mut self, conn: Arc<SyncConnection>) {
let mr = MatchRule::new_signal("org.freedesktop.DBus", "NameOwnerChanged");
conn.add_match_no_cb(&mr.match_str()).await.unwrap();
let callbacks_map = self.callbacks.clone();
conn.start_receive(
mr,
Box::new(move |msg, _conn| {
// The args are "address", "old address", "new address".
// https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed
let (addr, old, new) = msg.get3::<String, String, String>();
if addr.is_none() || old.is_none() || new.is_none() {
return true;
}
if old.unwrap().eq("") || !new.unwrap().eq("") {
return true;
}
// If old address exists but new address is empty, that means that client is
// disconnected. So call the registered callbacks to be notified of this client
// disconnect.
let addr = BusName::new(addr.unwrap()).unwrap().into_static();
if !callbacks_map.lock().unwrap().contains_key(&addr) {
return true;
}
for (id, callback) in callbacks_map.lock().unwrap()[&addr].iter() {
callback(*id);
}
callbacks_map.lock().unwrap().remove(&addr);
true
}),
);
}
/// Removes callback by id if owned by the specific busname.
///
/// If the callback can be removed, the callback will be called before being removed.
pub fn remove(&mut self, address: BusName<'static>, target_id: u32) -> bool {
if !self.callbacks.lock().unwrap().contains_key(&address) {
return false;
}
match self.callbacks.lock().unwrap().get(&address).and_then(|m| m.get(&target_id)) {
Some(cb) => {
cb(target_id);
let _ = self
.callbacks
.lock()
.unwrap()
.get_mut(&address)
.and_then(|m| m.remove(&target_id));
true
}
None => false,
}
}
}
/// Implements `DBusArg` for an enum.
///
/// A Rust enum is converted to D-Bus INT32 type.
#[macro_export]
macro_rules! impl_dbus_arg_enum {
($enum_type:ty) => {
impl DBusArg for $enum_type {
type DBusType = u32;
fn from_dbus(
data: u32,
_conn: Option<Arc<SyncConnection>>,
_remote: Option<dbus::strings::BusName<'static>>,
_disconnect_watcher: Option<
Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
>,
) -> Result<$enum_type, Box<dyn std::error::Error>> {
match <$enum_type>::from_u32(data) {
Some(x) => Ok(x),
None => Err(Box::new(DBusArgError::new(String::from(format!(
"error converting {} to {}",
data,
stringify!($enum_type)
))))),
}
}
fn to_dbus(data: $enum_type) -> Result<u32, Box<dyn std::error::Error>> {
return Ok(data.to_u32().unwrap());
}
}
};
}