blob: 65116bbcd6bb0842f13d61774c8c01e910955545 [file] [log] [blame]
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use dbus::channel::MatchingReceiver;
use dbus::message::MatchRule;
use dbus::nonblock::SyncConnection;
use dbus_crossroads::Crossroads;
use tokio::sync::mpsc;
use crate::callbacks::{BtCallback, BtConnectionCallback, BtManagerCallback};
use crate::command_handler::CommandHandler;
use crate::dbus_iface::{BluetoothDBus, BluetoothGattDBus, BluetoothManagerDBus};
use crate::editor::AsyncEditor;
use bt_topshim::topstack;
use btstack::bluetooth::{BluetoothDevice, IBluetooth};
use manager_service::iface_bluetooth_manager::IBluetoothManager;
mod callbacks;
mod command_handler;
mod console;
mod dbus_arg;
mod dbus_iface;
mod editor;
/// Context structure for the client. Used to keep track details about the active adapter and its
/// state.
pub(crate) struct ClientContext {
/// List of adapters and whether they are enabled.
pub(crate) adapters: HashMap<i32, bool>,
// TODO(abps) - Change once we have multi-adapter support.
/// The default adapter is also the active adapter. Defaults to 0.
pub(crate) default_adapter: i32,
/// Current adapter is enabled?
pub(crate) enabled: bool,
/// Current adapter is ready to be used?
pub(crate) adapter_ready: bool,
/// Current adapter address if known.
pub(crate) adapter_address: Option<String>,
/// Currently active bonding attempt. If it is not none, we are currently attempting to bond
/// this device.
pub(crate) bonding_attempt: Option<BluetoothDevice>,
/// Is adapter discovering?
pub(crate) discovering_state: bool,
/// Devices found in current discovery session. List should be cleared when a new discovery
/// session starts so that previous results don't pollute current search.
pub(crate) found_devices: HashMap<String, BluetoothDevice>,
/// If set, the registered GATT client id. None otherwise.
pub(crate) gatt_client_id: Option<i32>,
/// Proxy for manager interface.
pub(crate) manager_dbus: BluetoothManagerDBus,
/// Proxy for adapter interface. Only exists when the default adapter is enabled.
pub(crate) adapter_dbus: Option<BluetoothDBus>,
/// Proxy for GATT interface.
pub(crate) gatt_dbus: Option<BluetoothGattDBus>,
/// Channel to send actions to take in the foreground
fg: mpsc::Sender<ForegroundActions>,
/// Internal DBus connection object.
dbus_connection: Arc<SyncConnection>,
/// Internal DBus crossroads object.
dbus_crossroads: Arc<Mutex<Crossroads>>,
}
impl ClientContext {
pub fn new(
dbus_connection: Arc<SyncConnection>,
dbus_crossroads: Arc<Mutex<Crossroads>>,
tx: mpsc::Sender<ForegroundActions>,
) -> ClientContext {
// Manager interface is almost always available but adapter interface
// requires that the specific adapter is enabled.
let manager_dbus =
BluetoothManagerDBus::new(dbus_connection.clone(), dbus_crossroads.clone());
ClientContext {
adapters: HashMap::new(),
default_adapter: 0,
enabled: false,
adapter_ready: false,
adapter_address: None,
bonding_attempt: None,
discovering_state: false,
found_devices: HashMap::new(),
gatt_client_id: None,
manager_dbus,
adapter_dbus: None,
gatt_dbus: None,
fg: tx,
dbus_connection,
dbus_crossroads,
}
}
// Sets required values for the adapter when enabling or disabling
fn set_adapter_enabled(&mut self, hci_interface: i32, enabled: bool) {
print_info!("hci{} enabled = {}", hci_interface, enabled);
self.adapters.entry(hci_interface).and_modify(|v| *v = enabled).or_insert(enabled);
// When the default adapter's state is updated, we need to modify a few more things.
// Only do this if we're not repeating the previous state.
let prev_enabled = self.enabled;
let default_adapter = self.default_adapter;
if hci_interface == default_adapter && prev_enabled != enabled {
self.enabled = enabled;
self.adapter_ready = false;
if enabled {
self.create_adapter_proxy(hci_interface);
} else {
self.adapter_dbus = None;
}
}
}
// Creates adapter proxy, registers callbacks and initializes address.
fn create_adapter_proxy(&mut self, idx: i32) {
let conn = self.dbus_connection.clone();
let cr = self.dbus_crossroads.clone();
let dbus = BluetoothDBus::new(conn.clone(), cr.clone(), idx);
self.adapter_dbus = Some(dbus);
let gatt_dbus = BluetoothGattDBus::new(conn.clone(), cr.clone(), idx);
self.gatt_dbus = Some(gatt_dbus);
// Trigger callback registration in the foreground
let fg = self.fg.clone();
tokio::spawn(async move {
let adapter = String::from(format!("adapter{}", idx));
let _ = fg.send(ForegroundActions::RegisterAdapterCallback(adapter)).await;
});
}
// Foreground-only: Updates the adapter address.
fn update_adapter_address(&mut self) -> String {
let address = self.adapter_dbus.as_ref().unwrap().get_address();
self.adapter_address = Some(address.clone());
address
}
fn connect_all_enabled_profiles(&mut self, device: BluetoothDevice) {
let fg = self.fg.clone();
tokio::spawn(async move {
let _ = fg.send(ForegroundActions::ConnectAllEnabledProfiles(device)).await;
});
}
fn run_callback(&mut self, callback: Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>) {
let fg = self.fg.clone();
tokio::spawn(async move {
let _ = fg.send(ForegroundActions::RunCallback(callback)).await;
});
}
}
/// Actions to take on the foreground loop. This allows us to queue actions in
/// callbacks that get run in the foreground context.
enum ForegroundActions {
ConnectAllEnabledProfiles(BluetoothDevice), // Connect all enabled profiles for this device
RunCallback(Box<dyn Fn(Arc<Mutex<ClientContext>>) + Send>), // Run callback in foreground
RegisterAdapterCallback(String), // Register callbacks for this adapter
Readline(rustyline::Result<String>), // Readline result from rustyline
}
/// Runs a command line program that interacts with a Bluetooth stack.
fn main() -> Result<(), Box<dyn std::error::Error>> {
// TODO: Process command line arguments.
topstack::get_runtime().block_on(async move {
// Connect to D-Bus system bus.
let (resource, conn) = dbus_tokio::connection::new_system_sync()?;
// The `resource` is a task that should be spawned onto a tokio compatible
// reactor ASAP. If the resource ever finishes, we lost connection to D-Bus.
tokio::spawn(async {
let err = resource.await;
panic!("Lost connection to D-Bus: {}", err);
});
// Sets up Crossroads for receiving callbacks.
let cr = Arc::new(Mutex::new(Crossroads::new()));
cr.lock().unwrap().set_async_support(Some((
conn.clone(),
Box::new(|x| {
tokio::spawn(x);
}),
)));
let cr_clone = cr.clone();
conn.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
cr_clone.lock().unwrap().handle_message(msg, conn).unwrap();
true
}),
);
// Accept foreground actions with mpsc
let (tx, rx) = mpsc::channel::<ForegroundActions>(10);
// Create the context needed for handling commands
let context = Arc::new(Mutex::new(ClientContext::new(conn, cr, tx.clone())));
// Check if manager interface is valid. We only print some help text before failing on the
// first actual access to the interface (so we can also capture the actual reason the
// interface isn't valid).
if !context.lock().unwrap().manager_dbus.is_valid() {
println!("Bluetooth manager doesn't seem to be working correctly.");
println!("Check if service is running.");
println!("...");
}
// TODO: Registering the callback should be done when btmanagerd is ready (detect with
// ObjectManager).
context.lock().unwrap().manager_dbus.register_callback(Box::new(BtManagerCallback::new(
String::from("/org/chromium/bluetooth/client/bluetooth_manager_callback"),
context.clone(),
)));
// Check if the default adapter is enabled. If yes, we should create the adapter proxy
// right away.
let default_adapter = context.lock().unwrap().default_adapter;
if context.lock().unwrap().manager_dbus.get_adapter_enabled(default_adapter) {
context.lock().unwrap().set_adapter_enabled(default_adapter, true);
}
let mut handler = CommandHandler::new(context.clone());
let args: Vec<String> = std::env::args().collect();
// Allow command line arguments to be read
if args.len() > 1 {
handler.process_cmd_line(&args[1], &args[2..].to_vec());
} else {
start_interactive_shell(handler, tx, rx, context).await;
}
return Result::Ok(());
})
}
async fn start_interactive_shell(
mut handler: CommandHandler,
tx: mpsc::Sender<ForegroundActions>,
mut rx: mpsc::Receiver<ForegroundActions>,
context: Arc<Mutex<ClientContext>>,
) {
let command_list = handler.get_command_list().clone();
let semaphore_fg = Arc::new(tokio::sync::Semaphore::new(1));
// Async task to keep reading new lines from user
let semaphore = semaphore_fg.clone();
tokio::spawn(async move {
let editor = AsyncEditor::new(command_list);
loop {
// Wait until ForegroundAction::Readline finishes its task.
let permit = semaphore.acquire().await;
if permit.is_err() {
break;
};
// Let ForegroundAction::Readline decide when it's done.
permit.unwrap().forget();
// It's good to do readline now.
let result = editor.readline().await;
let _ = tx.send(ForegroundActions::Readline(result)).await;
}
});
loop {
let m = rx.recv().await;
if m.is_none() {
break;
}
match m.unwrap() {
ForegroundActions::ConnectAllEnabledProfiles(device) => {
if context.lock().unwrap().adapter_ready {
context
.lock()
.unwrap()
.adapter_dbus
.as_mut()
.unwrap()
.connect_all_enabled_profiles(device);
} else {
println!("Adapter isn't ready to connect profiles.");
}
}
ForegroundActions::RunCallback(callback) => {
callback(context.clone());
}
// Once adapter is ready, register callbacks, get the address and mark it as ready
ForegroundActions::RegisterAdapterCallback(adapter) => {
let cb_objpath: String =
format!("/org/chromium/bluetooth/client/{}/bluetooth_callback", adapter);
let conn_cb_objpath: String =
format!("/org/chromium/bluetooth/client/{}/bluetooth_conn_callback", adapter);
context
.lock()
.unwrap()
.adapter_dbus
.as_mut()
.unwrap()
.register_callback(Box::new(BtCallback::new(cb_objpath, context.clone())));
context
.lock()
.unwrap()
.adapter_dbus
.as_mut()
.unwrap()
.register_connection_callback(Box::new(BtConnectionCallback::new(
conn_cb_objpath,
context.clone(),
)));
context.lock().unwrap().adapter_ready = true;
let adapter_address = context.lock().unwrap().update_adapter_address();
print_info!("Adapter {} is ready", adapter_address);
}
ForegroundActions::Readline(result) => match result {
Err(_err) => {
break;
}
Ok(line) => {
let command_vec =
line.split(" ").map(|s| String::from(s)).collect::<Vec<String>>();
let cmd = &command_vec[0];
if cmd.eq("quit") {
break;
}
handler.process_cmd_line(
&String::from(cmd),
&command_vec[1..command_vec.len()].to_vec(),
);
// Ready to do readline again.
semaphore_fg.add_permits(1);
}
},
}
}
semaphore_fg.close();
print_info!("Client exiting");
}