1 use clap::{value_t, App, Arg};
2
3 use std::collections::{HashMap, HashSet};
4 use std::sync::{Arc, Mutex};
5
6 use dbus::channel::MatchingReceiver;
7 use dbus::message::MatchRule;
8 use dbus::nonblock::SyncConnection;
9 use dbus_crossroads::Crossroads;
10 use tokio::sync::mpsc;
11
12 use crate::bt_adv::AdvSet;
13 use crate::bt_gatt::GattClientContext;
14 use crate::callbacks::{
15 AdminCallback, AdvertisingSetCallback, BtCallback, BtConnectionCallback, BtManagerCallback,
16 BtSocketManagerCallback, ScannerCallback, SuspendCallback,
17 };
18 use crate::command_handler::{CommandHandler, SocketSchedule};
19 use crate::dbus_iface::{
20 BluetoothAdminDBus, BluetoothDBus, BluetoothGattDBus, BluetoothManagerDBus, BluetoothQADBus,
21 BluetoothQALegacyDBus, BluetoothSocketManagerDBus, BluetoothTelephonyDBus, SuspendDBus,
22 };
23 use crate::editor::AsyncEditor;
24 use bt_topshim::topstack;
25 use btstack::bluetooth::{BluetoothDevice, IBluetooth};
26 use btstack::suspend::ISuspend;
27 use manager_service::iface_bluetooth_manager::IBluetoothManager;
28
29 mod bt_adv;
30 mod bt_gatt;
31 mod callbacks;
32 mod command_handler;
33 mod console;
34 mod dbus_arg;
35 mod dbus_iface;
36 mod editor;
37
38 /// Context structure for the client. Used to keep track details about the active adapter and its
39 /// state.
40 pub(crate) struct ClientContext {
41 /// List of adapters and whether they are enabled.
42 pub(crate) adapters: HashMap<i32, bool>,
43
44 // TODO(abps) - Change once we have multi-adapter support.
45 /// The default adapter is also the active adapter. Defaults to 0.
46 pub(crate) default_adapter: i32,
47
48 /// Current adapter is enabled?
49 pub(crate) enabled: bool,
50
51 /// Current adapter is ready to be used?
52 pub(crate) adapter_ready: bool,
53
54 /// Current adapter address if known.
55 pub(crate) adapter_address: Option<String>,
56
57 /// Currently active bonding attempt. If it is not none, we are currently attempting to bond
58 /// this device.
59 pub(crate) bonding_attempt: Option<BluetoothDevice>,
60
61 /// Is adapter discovering?
62 pub(crate) discovering_state: bool,
63
64 /// Devices found in current discovery session. List should be cleared when a new discovery
65 /// session starts so that previous results don't pollute current search.
66 pub(crate) found_devices: HashMap<String, BluetoothDevice>,
67
68 /// List of bonded devices.
69 pub(crate) bonded_devices: HashMap<String, BluetoothDevice>,
70
71 /// Proxy for manager interface.
72 pub(crate) manager_dbus: BluetoothManagerDBus,
73
74 /// Proxy for adapter interface. Only exists when the default adapter is enabled.
75 pub(crate) adapter_dbus: Option<BluetoothDBus>,
76
77 /// Proxy for adapter QA Legacy interface. Only exists when the default adapter is enabled.
78 pub(crate) qa_legacy_dbus: Option<BluetoothQALegacyDBus>,
79
80 /// Proxy for adapter QA interface.
81 pub(crate) qa_dbus: Option<BluetoothQADBus>,
82
83 /// Proxy for GATT interface.
84 pub(crate) gatt_dbus: Option<BluetoothGattDBus>,
85
86 /// Proxy for Admin interface.
87 pub(crate) admin_dbus: Option<BluetoothAdminDBus>,
88
89 /// Proxy for suspend interface.
90 pub(crate) suspend_dbus: Option<SuspendDBus>,
91
92 /// Proxy for socket manager interface.
93 pub(crate) socket_manager_dbus: Option<BluetoothSocketManagerDBus>,
94
95 /// Proxy for Telephony interface.
96 pub(crate) telephony_dbus: Option<BluetoothTelephonyDBus>,
97
98 /// Channel to send actions to take in the foreground
99 fg: mpsc::Sender<ForegroundActions>,
100
101 /// Internal DBus connection object.
102 dbus_connection: Arc<SyncConnection>,
103
104 /// Internal DBus crossroads object.
105 dbus_crossroads: Arc<Mutex<Crossroads>>,
106
107 /// Identifies the callback to receive IScannerCallback method calls.
108 scanner_callback_id: Option<u32>,
109
110 /// Identifies the callback to receive IAdvertisingSetCallback method calls.
111 advertiser_callback_id: Option<u32>,
112
113 /// Identifies the callback to receive IBluetoothAdminPolicyCallback method calls.
114 admin_callback_id: Option<u32>,
115
116 /// Keeps track of active LE scanners.
117 active_scanner_ids: HashSet<u8>,
118
119 /// Keeps track of advertising sets registered. Map from reg_id to AdvSet.
120 adv_sets: HashMap<i32, AdvSet>,
121
122 /// Identifies the callback to receive IBluetoothSocketManagerCallback method calls.
123 socket_manager_callback_id: Option<u32>,
124
125 /// Is btclient running in restricted mode?
126 is_restricted: bool,
127
128 /// Data of GATT client preference.
129 gatt_client_context: GattClientContext,
130
131 /// The schedule when a socket is connected.
132 socket_test_schedule: Option<SocketSchedule>,
133
134 /// The handle of the SDP record for MPS (Multi-Profile Specification).
135 mps_sdp_handle: Option<i32>,
136 }
137
138 impl ClientContext {
new( dbus_connection: Arc<SyncConnection>, dbus_crossroads: Arc<Mutex<Crossroads>>, tx: mpsc::Sender<ForegroundActions>, is_restricted: bool, ) -> ClientContext139 pub fn new(
140 dbus_connection: Arc<SyncConnection>,
141 dbus_crossroads: Arc<Mutex<Crossroads>>,
142 tx: mpsc::Sender<ForegroundActions>,
143 is_restricted: bool,
144 ) -> ClientContext {
145 // Manager interface is almost always available but adapter interface
146 // requires that the specific adapter is enabled.
147 let manager_dbus = BluetoothManagerDBus::new(dbus_connection.clone());
148
149 ClientContext {
150 adapters: HashMap::new(),
151 default_adapter: 0,
152 enabled: false,
153 adapter_ready: false,
154 adapter_address: None,
155 bonding_attempt: None,
156 discovering_state: false,
157 found_devices: HashMap::new(),
158 bonded_devices: HashMap::new(),
159 manager_dbus,
160 adapter_dbus: None,
161 qa_legacy_dbus: None,
162 qa_dbus: None,
163 gatt_dbus: None,
164 admin_dbus: None,
165 suspend_dbus: None,
166 socket_manager_dbus: None,
167 telephony_dbus: None,
168 fg: tx,
169 dbus_connection,
170 dbus_crossroads,
171 scanner_callback_id: None,
172 advertiser_callback_id: None,
173 admin_callback_id: None,
174 active_scanner_ids: HashSet::new(),
175 adv_sets: HashMap::new(),
176 socket_manager_callback_id: None,
177 is_restricted,
178 gatt_client_context: GattClientContext::new(),
179 socket_test_schedule: None,
180 mps_sdp_handle: None,
181 }
182 }
183
184 // Sets required values for the adapter when enabling or disabling
set_adapter_enabled(&mut self, hci_interface: i32, enabled: bool)185 fn set_adapter_enabled(&mut self, hci_interface: i32, enabled: bool) {
186 print_info!("hci{} enabled = {}", hci_interface, enabled);
187
188 self.adapters.entry(hci_interface).and_modify(|v| *v = enabled).or_insert(enabled);
189
190 // When the default adapter's state is updated, we need to modify a few more things.
191 // Only do this if we're not repeating the previous state.
192 let prev_enabled = self.enabled;
193 let default_adapter = self.default_adapter;
194 if hci_interface == default_adapter && prev_enabled != enabled {
195 self.enabled = enabled;
196 self.adapter_ready = false;
197 if enabled {
198 self.create_adapter_proxy(hci_interface);
199 } else {
200 self.adapter_dbus = None;
201 }
202 }
203 }
204
205 // Creates adapter proxy, registers callbacks and initializes address.
create_adapter_proxy(&mut self, idx: i32)206 fn create_adapter_proxy(&mut self, idx: i32) {
207 let conn = self.dbus_connection.clone();
208
209 let dbus = BluetoothDBus::new(conn.clone(), idx);
210 self.adapter_dbus = Some(dbus);
211 self.qa_legacy_dbus = Some(BluetoothQALegacyDBus::new(conn.clone(), idx));
212 self.qa_dbus = Some(BluetoothQADBus::new(conn.clone(), idx));
213
214 let gatt_dbus = BluetoothGattDBus::new(conn.clone(), idx);
215 self.gatt_dbus = Some(gatt_dbus);
216
217 let admin_dbus = BluetoothAdminDBus::new(conn.clone(), idx);
218 self.admin_dbus = Some(admin_dbus);
219
220 let socket_manager_dbus = BluetoothSocketManagerDBus::new(conn.clone(), idx);
221 self.socket_manager_dbus = Some(socket_manager_dbus);
222
223 self.suspend_dbus = Some(SuspendDBus::new(conn.clone(), idx));
224
225 self.telephony_dbus = Some(BluetoothTelephonyDBus::new(conn.clone(), idx));
226
227 // Trigger callback registration in the foreground
228 let fg = self.fg.clone();
229 tokio::spawn(async move {
230 let adapter = String::from(format!("adapter{}", idx));
231 let _ = fg.send(ForegroundActions::RegisterAdapterCallback(adapter)).await;
232 });
233 }
234
235 // Foreground-only: Updates the adapter address.
update_adapter_address(&mut self) -> String236 fn update_adapter_address(&mut self) -> String {
237 let address = self.adapter_dbus.as_ref().unwrap().get_address();
238 self.adapter_address = Some(address.clone());
239
240 address
241 }
242
243 // Foreground-only: Updates bonded devices.
update_bonded_devices(&mut self)244 fn update_bonded_devices(&mut self) {
245 let bonded_devices = self.adapter_dbus.as_ref().unwrap().get_bonded_devices();
246
247 for device in bonded_devices {
248 self.bonded_devices.insert(device.address.clone(), device.clone());
249 }
250 }
251
connect_all_enabled_profiles(&mut self, device: BluetoothDevice)252 fn connect_all_enabled_profiles(&mut self, device: BluetoothDevice) {
253 let fg = self.fg.clone();
254 tokio::spawn(async move {
255 let _ = fg.send(ForegroundActions::ConnectAllEnabledProfiles(device)).await;
256 });
257 }
258
run_callback(&mut self, callback: Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>)259 fn run_callback(&mut self, callback: Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>) {
260 let fg = self.fg.clone();
261 tokio::spawn(async move {
262 let _ = fg.send(ForegroundActions::RunCallback(callback)).await;
263 });
264 }
265
get_devices(&self) -> Vec<String>266 fn get_devices(&self) -> Vec<String> {
267 let mut result: Vec<String> = vec![];
268
269 result.extend(
270 self.found_devices.keys().map(|key| String::from(key)).collect::<Vec<String>>(),
271 );
272 result.extend(
273 self.bonded_devices
274 .keys()
275 .filter(|key| !self.found_devices.contains_key(&String::from(*key)))
276 .map(|key| String::from(key))
277 .collect::<Vec<String>>(),
278 );
279
280 result
281 }
282 }
283
284 /// Actions to take on the foreground loop. This allows us to queue actions in
285 /// callbacks that get run in the foreground context.
286 enum ForegroundActions {
287 ConnectAllEnabledProfiles(BluetoothDevice), // Connect all enabled profiles for this device
288 RunCallback(Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>), // Run callback in foreground
289 RegisterAdapterCallback(String), // Register callbacks for this adapter
290 Readline(rustyline::Result<String>), // Readline result from rustyline
291 }
292
293 /// Runs a command line program that interacts with a Bluetooth stack.
main() -> Result<(), Box<dyn std::error::Error>>294 fn main() -> Result<(), Box<dyn std::error::Error>> {
295 let matches = App::new("btclient")
296 .arg(Arg::with_name("restricted").long("restricted").takes_value(false))
297 .arg(Arg::with_name("command").short("c").long("command").takes_value(true))
298 .get_matches();
299 let command = value_t!(matches, "command", String);
300 let is_restricted = matches.is_present("restricted");
301
302 topstack::get_runtime().block_on(async move {
303 // Connect to D-Bus system bus.
304 let (resource, conn) = dbus_tokio::connection::new_system_sync()?;
305
306 // The `resource` is a task that should be spawned onto a tokio compatible
307 // reactor ASAP. If the resource ever finishes, we lost connection to D-Bus.
308 tokio::spawn(async {
309 let err = resource.await;
310 panic!("Lost connection to D-Bus: {}", err);
311 });
312
313 // Sets up Crossroads for receiving callbacks.
314 let cr = Arc::new(Mutex::new(Crossroads::new()));
315 cr.lock().unwrap().set_async_support(Some((
316 conn.clone(),
317 Box::new(|x| {
318 tokio::spawn(x);
319 }),
320 )));
321 let cr_clone = cr.clone();
322 conn.start_receive(
323 MatchRule::new_method_call(),
324 Box::new(move |msg, conn| {
325 cr_clone.lock().unwrap().handle_message(msg, conn).unwrap();
326 true
327 }),
328 );
329
330 // Accept foreground actions with mpsc
331 let (tx, rx) = mpsc::channel::<ForegroundActions>(10);
332
333 // Create the context needed for handling commands
334 let context = Arc::new(Mutex::new(ClientContext::new(
335 conn.clone(),
336 cr.clone(),
337 tx.clone(),
338 is_restricted,
339 )));
340
341 // Check if manager interface is valid. We only print some help text before failing on the
342 // first actual access to the interface (so we can also capture the actual reason the
343 // interface isn't valid).
344 if !context.lock().unwrap().manager_dbus.is_valid() {
345 println!("Bluetooth manager doesn't seem to be working correctly.");
346 println!("Check if service is running.");
347 return Ok(());
348 }
349
350 // TODO: Registering the callback should be done when btmanagerd is ready (detect with
351 // ObjectManager).
352 context.lock().unwrap().manager_dbus.register_callback(Box::new(BtManagerCallback::new(
353 String::from("/org/chromium/bluetooth/client/bluetooth_manager_callback"),
354 context.clone(),
355 conn.clone(),
356 cr.clone(),
357 )));
358
359 // Check if the default adapter is enabled. If yes, we should create the adapter proxy
360 // right away.
361 let default_adapter = context.lock().unwrap().default_adapter;
362
363 {
364 let mut context_locked = context.lock().unwrap();
365 match context_locked.manager_dbus.rpc.get_adapter_enabled(default_adapter).await {
366 Ok(ret) => {
367 if ret {
368 context_locked.set_adapter_enabled(default_adapter, true);
369 }
370 }
371 Err(e) => {
372 panic!("Bluetooth Manager is not available. Exiting. D-Bus error: {}", e);
373 }
374 }
375 }
376
377 let mut handler = CommandHandler::new(context.clone());
378
379 // Allow command line arguments to be read
380 match command {
381 Ok(command) => {
382 let mut iter = command.split(' ').map(String::from);
383 handler.process_cmd_line(
384 &iter.next().unwrap_or(String::from("")),
385 &iter.collect::<Vec<String>>(),
386 );
387 }
388 _ => {
389 start_interactive_shell(handler, tx, rx, context).await?;
390 }
391 };
392 return Result::Ok(());
393 })
394 }
395
start_interactive_shell( mut handler: CommandHandler, tx: mpsc::Sender<ForegroundActions>, mut rx: mpsc::Receiver<ForegroundActions>, context: Arc<Mutex<ClientContext>>, ) -> Result<(), Box<dyn std::error::Error>>396 async fn start_interactive_shell(
397 mut handler: CommandHandler,
398 tx: mpsc::Sender<ForegroundActions>,
399 mut rx: mpsc::Receiver<ForegroundActions>,
400 context: Arc<Mutex<ClientContext>>,
401 ) -> Result<(), Box<dyn std::error::Error>> {
402 let command_rule_list = handler.get_command_rule_list().clone();
403 let context_for_closure = context.clone();
404
405 let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1));
406
407 // Async task to keep reading new lines from user
408 let semaphore = semaphore_fg.clone();
409 let editor = AsyncEditor::new(command_rule_list, context_for_closure)
410 .map_err(|x| format!("creating async editor failed: {x}"))?;
411 tokio::spawn(async move {
412 loop {
413 // Wait until ForegroundAction::Readline finishes its task.
414 let permit = semaphore.acquire().await;
415 if permit.is_err() {
416 break;
417 };
418 // Let ForegroundAction::Readline decide when it's done.
419 permit.unwrap().forget();
420
421 // It's good to do readline now.
422 let result = editor.readline().await;
423 let _ = tx.send(ForegroundActions::Readline(result)).await;
424 }
425 });
426
427 'readline: loop {
428 let m = rx.recv().await;
429
430 if m.is_none() {
431 break;
432 }
433
434 match m.unwrap() {
435 ForegroundActions::ConnectAllEnabledProfiles(device) => {
436 if context.lock().unwrap().adapter_ready {
437 context
438 .lock()
439 .unwrap()
440 .adapter_dbus
441 .as_mut()
442 .unwrap()
443 .connect_all_enabled_profiles(device);
444 } else {
445 println!("Adapter isn't ready to connect profiles.");
446 }
447 }
448 ForegroundActions::RunCallback(callback) => {
449 callback(context.clone());
450 }
451 // Once adapter is ready, register callbacks, get the address and mark it as ready
452 ForegroundActions::RegisterAdapterCallback(adapter) => {
453 let cb_objpath: String =
454 format!("/org/chromium/bluetooth/client/{}/bluetooth_callback", adapter);
455 let conn_cb_objpath: String =
456 format!("/org/chromium/bluetooth/client/{}/bluetooth_conn_callback", adapter);
457 let suspend_cb_objpath: String =
458 format!("/org/chromium/bluetooth/client/{}/suspend_callback", adapter);
459 let scanner_cb_objpath: String =
460 format!("/org/chromium/bluetooth/client/{}/scanner_callback", adapter);
461 let advertiser_cb_objpath: String =
462 format!("/org/chromium/bluetooth/client/{}/advertising_set_callback", adapter);
463 let admin_cb_objpath: String =
464 format!("/org/chromium/bluetooth/client/{}/admin_callback", adapter);
465 let socket_manager_cb_objpath: String =
466 format!("/org/chromium/bluetooth/client/{}/socket_manager_callback", adapter);
467
468 let dbus_connection = context.lock().unwrap().dbus_connection.clone();
469 let dbus_crossroads = context.lock().unwrap().dbus_crossroads.clone();
470
471 context
472 .lock()
473 .unwrap()
474 .adapter_dbus
475 .as_mut()
476 .unwrap()
477 .rpc
478 .register_callback(Box::new(BtCallback::new(
479 cb_objpath.clone(),
480 context.clone(),
481 dbus_connection.clone(),
482 dbus_crossroads.clone(),
483 )))
484 .await
485 .expect("D-Bus error on IBluetooth::RegisterCallback");
486 context
487 .lock()
488 .unwrap()
489 .adapter_dbus
490 .as_mut()
491 .unwrap()
492 .rpc
493 .register_connection_callback(Box::new(BtConnectionCallback::new(
494 conn_cb_objpath,
495 context.clone(),
496 dbus_connection.clone(),
497 dbus_crossroads.clone(),
498 )))
499 .await
500 .expect("D-Bus error on IBluetooth::RegisterConnectionCallback");
501
502 // Register callback listener for le-scan`commands.
503 let scanner_callback_id = context
504 .lock()
505 .unwrap()
506 .gatt_dbus
507 .as_mut()
508 .unwrap()
509 .rpc
510 .register_scanner_callback(Box::new(ScannerCallback::new(
511 scanner_cb_objpath.clone(),
512 context.clone(),
513 dbus_connection.clone(),
514 dbus_crossroads.clone(),
515 )))
516 .await
517 .expect("D-Bus error on IBluetoothGatt::RegisterScannerCallback");
518 context.lock().unwrap().scanner_callback_id = Some(scanner_callback_id);
519
520 let advertiser_callback_id = context
521 .lock()
522 .unwrap()
523 .gatt_dbus
524 .as_mut()
525 .unwrap()
526 .rpc
527 .register_advertiser_callback(Box::new(AdvertisingSetCallback::new(
528 advertiser_cb_objpath.clone(),
529 context.clone(),
530 dbus_connection.clone(),
531 dbus_crossroads.clone(),
532 )))
533 .await
534 .expect("D-Bus error on IBluetoothGatt::RegisterAdvertiserCallback");
535 context.lock().unwrap().advertiser_callback_id = Some(advertiser_callback_id);
536
537 let admin_callback_id = context
538 .lock()
539 .unwrap()
540 .admin_dbus
541 .as_mut()
542 .unwrap()
543 .rpc
544 .register_admin_policy_callback(Box::new(AdminCallback::new(
545 admin_cb_objpath.clone(),
546 dbus_connection.clone(),
547 dbus_crossroads.clone(),
548 )))
549 .await
550 .expect("D-Bus error on IBluetoothAdmin::RegisterAdminCallback");
551 context.lock().unwrap().admin_callback_id = Some(admin_callback_id);
552
553 let socket_manager_callback_id = context
554 .lock()
555 .unwrap()
556 .socket_manager_dbus
557 .as_mut()
558 .unwrap()
559 .rpc
560 .register_callback(Box::new(BtSocketManagerCallback::new(
561 socket_manager_cb_objpath.clone(),
562 context.clone(),
563 dbus_connection.clone(),
564 dbus_crossroads.clone(),
565 )))
566 .await
567 .expect("D-Bus error on IBluetoothSocketManager::RegisterCallback");
568 context.lock().unwrap().socket_manager_callback_id =
569 Some(socket_manager_callback_id);
570
571 // When adapter is ready, Suspend API is also ready. Register as an observer.
572 // TODO(b/224606285): Implement suspend debug utils in btclient.
573 context.lock().unwrap().suspend_dbus.as_mut().unwrap().register_callback(Box::new(
574 SuspendCallback::new(
575 suspend_cb_objpath,
576 dbus_connection.clone(),
577 dbus_crossroads.clone(),
578 ),
579 ));
580
581 context.lock().unwrap().adapter_ready = true;
582 let adapter_address = context.lock().unwrap().update_adapter_address();
583 context.lock().unwrap().update_bonded_devices();
584
585 print_info!("Adapter {} is ready", adapter_address);
586 }
587 ForegroundActions::Readline(result) => match result {
588 Err(rustyline::error::ReadlineError::Interrupted) => {
589 // Ctrl-C cancels the currently typed line, do nothing and ready to do next
590 // readline again.
591 semaphore_fg.add_permits(1);
592 }
593 Err(_err) => {
594 break;
595 }
596 Ok(line) => {
597 // Currently Chrome OS uses Rust 1.60 so use the 1-time loop block to
598 // workaround this.
599 // With Rust 1.65 onwards we can convert this loop hack into a named block:
600 // https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#break-from-labeled-blocks
601 // TODO: Use named block when Android and Chrome OS Rust upgrade Rust to 1.65.
602 loop {
603 let args = match shell_words::split(line.as_str()) {
604 Ok(words) => words,
605 Err(e) => {
606 print_error!("Error parsing arguments: {}", e);
607 break;
608 }
609 };
610
611 let (cmd, rest) = match args.split_first() {
612 Some(pair) => pair,
613 None => break,
614 };
615
616 if cmd == "quit" {
617 break 'readline;
618 }
619
620 handler.process_cmd_line(cmd, &rest.to_vec());
621 break;
622 }
623
624 // Ready to do readline again.
625 semaphore_fg.add_permits(1);
626 }
627 },
628 }
629 }
630
631 semaphore_fg.close();
632
633 print_info!("Client exiting");
634 Ok(())
635 }
636