1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
//! 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/packages/modules/Bluetooth/+/refs/heads/master/system/gd/rust/linux/mgmt)
//! crate:
//!
//! * Define RPCProxy like in
//! [here](https://android.googlesource.com/platform/packages/modules/Bluetooth/+/refs/heads/master/system/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/packages/modules/Bluetooth/+/refs/heads/master/system/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/packages/modules/Bluetooth/+/refs/heads/master/system/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/packages/modules/Bluetooth/+/refs/heads/master/system/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/packages/modules/Bluetooth/+/refs/heads/master/system/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/packages/modules/Bluetooth/+/refs/heads/master/system/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());
}
}
};
}
/// Implements `DBusArg` for a type which implements TryFrom and TryInto.
#[macro_export]
macro_rules! impl_dbus_arg_from_into {
($rust_type:ty, $dbus_type:ty) => {
impl DBusArg for $rust_type {
type DBusType = $dbus_type;
fn from_dbus(
data: $dbus_type,
_conn: Option<Arc<SyncConnection>>,
_remote: Option<dbus::strings::BusName<'static>>,
_disconnect_watcher: Option<
Arc<std::sync::Mutex<dbus_projection::DisconnectWatcher>>,
>,
) -> Result<$rust_type, Box<dyn std::error::Error>> {
match <$rust_type>::try_from(data) {
Err(e) => Err(Box::new(DBusArgError::new(String::from(format!(
"error converting {} to {}",
data,
stringify!($rust_type),
))))),
Ok(result) => Ok(result),
}
}
fn to_dbus(data: $rust_type) -> Result<$dbus_type, Box<dyn std::error::Error>> {
match data.try_into() {
Err(e) => Err(Box::new(DBusArgError::new(String::from(format!(
"error converting {:?} to {}",
data,
stringify!($dbus_type)
))))),
Ok(result) => Ok(result),
}
}
}
};
}
/// Marks a function to be implemented by dbus_projection macros.
#[macro_export]
macro_rules! dbus_generated {
() => {
// The implementation is not used but replaced by generated code.
// This uses panic! so that the compiler can accept it for any function
// return type.
panic!("To be implemented by dbus_projection macros");
};
}
|