From 59514493157018d7850e5ee8b27f4b0339f453d0 Mon Sep 17 00:00:00 2001 From: Sonny Sasaka Date: Tue, 27 Apr 2021 00:31:33 -0700 Subject: [PATCH] Add the `btstack` crate The `btstack` crate contains the floss API implementations independent of any RPC projection. This patch adds the basic skeleton that implements the first simplest methods. Bug: 186492781 Tag: #floss Test: manual - tested with D-Bus integration in the next patch Change-Id: I10ff16d7e6cfcb599a3b356c58d7af15bcaa61d3 --- gd/rust/linux/stack/Cargo.toml | 20 +++ gd/rust/linux/stack/btif_macros/Cargo.toml | 12 ++ gd/rust/linux/stack/btif_macros/src/lib.rs | 136 ++++++++++++++++++++ gd/rust/linux/stack/src/bluetooth.rs | 192 +++++++++++++++++++++++++++++ gd/rust/linux/stack/src/bluetooth_gatt.rs | 86 +++++++++++++ gd/rust/linux/stack/src/lib.rs | 110 +++++++++++++++++ 6 files changed, 556 insertions(+) create mode 100644 gd/rust/linux/stack/Cargo.toml create mode 100644 gd/rust/linux/stack/btif_macros/Cargo.toml create mode 100644 gd/rust/linux/stack/btif_macros/src/lib.rs create mode 100644 gd/rust/linux/stack/src/bluetooth.rs create mode 100644 gd/rust/linux/stack/src/bluetooth_gatt.rs create mode 100644 gd/rust/linux/stack/src/lib.rs diff --git a/gd/rust/linux/stack/Cargo.toml b/gd/rust/linux/stack/Cargo.toml new file mode 100644 index 000000000..65987e8e1 --- /dev/null +++ b/gd/rust/linux/stack/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "btstack" +version = "0.1.0" +edition = "2018" + +[dependencies] +bt_topshim = { path = "../../topshim" } +bt_shim = { path = "../../shim" } + +btif_macros = { path = "btif_macros" } + +dbus = "0.9.2" + +num-traits = "*" +num-derive = "*" + +tokio = { version = "1", features = ['bytes', 'fs', 'io-util', 'libc', 'macros', 'memchr', 'mio', 'net', 'num_cpus', 'rt', 'rt-multi-thread', 'sync', 'time', 'tokio-macros'] } + +[lib] +path = "src/lib.rs" diff --git a/gd/rust/linux/stack/btif_macros/Cargo.toml b/gd/rust/linux/stack/btif_macros/Cargo.toml new file mode 100644 index 000000000..1810867c0 --- /dev/null +++ b/gd/rust/linux/stack/btif_macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "btif_macros" +version = "0.1.0" +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" +proc-macro2 = "1.0" diff --git a/gd/rust/linux/stack/btif_macros/src/lib.rs b/gd/rust/linux/stack/btif_macros/src/lib.rs new file mode 100644 index 000000000..bf9cc4383 --- /dev/null +++ b/gd/rust/linux/stack/btif_macros/src/lib.rs @@ -0,0 +1,136 @@ +extern crate proc_macro; + +use quote::quote; + +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use syn::parse::Parser; +use syn::punctuated::Punctuated; +use syn::token::Comma; +use syn::{Expr, FnArg, ItemTrait, Meta, Pat, TraitItem}; + +use crate::proc_macro::TokenStream; + +fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) { + let path = Path::new(filename.as_str()); + let mut file = File::create(&path).unwrap(); + file.write_all(gen.to_string().as_bytes()).unwrap(); +} + +/// Specifies the `Stack::Message` associated with a topshim callback. +#[proc_macro_attribute] +pub fn stack_message(_attr: TokenStream, item: TokenStream) -> TokenStream { + let ori_item: proc_macro2::TokenStream = item.clone().into(); + let gen = quote! { + #[allow(unused_variables)] + #ori_item + }; + gen.into() +} + +/// Generates a topshim callback object that contains closures. +/// +/// The closures are generated to be calls to the corresponding `Stack::Message`. +#[proc_macro_attribute] +pub fn btif_callbacks_generator(attr: TokenStream, item: TokenStream) -> TokenStream { + let args = Punctuated::::parse_separated_nonempty.parse(attr.clone()).unwrap(); + + let fn_ident = if let Expr::Path(p) = &args[0] { + p.path.get_ident().unwrap() + } else { + panic!("function name must be specified"); + }; + + let callbacks_struct_ident = if let Expr::Path(p) = &args[1] { + p.path.get_ident().unwrap() + } else { + panic!("callbacks struct ident must be specified"); + }; + + let ast: ItemTrait = syn::parse(item.clone()).unwrap(); + + let mut fn_names = quote! {}; + let mut closure_defs = quote! {}; + for attr in ast.items { + if let TraitItem::Method(m) = attr { + if m.attrs.len() != 1 { + continue; + } + + let attr = &m.attrs[0]; + if !attr.path.get_ident().unwrap().to_string().eq("stack_message") { + continue; + } + + let attr_args = attr.parse_meta().unwrap(); + let stack_message = if let Meta::List(meta_list) = attr_args { + Some(meta_list.nested[0].clone()) + } else { + None + }; + + if stack_message.is_none() { + continue; + } + + let mut arg_names = quote! {}; + for input in m.sig.inputs { + if let FnArg::Typed(t) = input { + if let Pat::Ident(i) = *t.pat { + let attr_name = i.ident; + arg_names = quote! { #arg_names #attr_name, }; + } + } + } + let method_ident = m.sig.ident; + + fn_names = quote! { + #fn_names + #method_ident, + }; + + closure_defs = quote! { + #closure_defs + let tx_clone = tx.clone(); + let #method_ident = Box::new(move |#arg_names| { + let tx = tx_clone.clone(); + topstack::get_runtime().spawn(async move { + let result = tx.send(Message::#stack_message(#arg_names)).await; + if let Err(e) = result { + eprintln!("Error in sending message: {}", e); + } + }); + }); + }; + } + } + + let ori_item = proc_macro2::TokenStream::from(item.clone()); + + let gen = quote! { + #ori_item + + /// Returns a callback object to be passed to topshim. + pub fn #fn_ident(tx: tokio::sync::mpsc::Sender) -> #callbacks_struct_ident { + #closure_defs + #callbacks_struct_ident { + #fn_names + // TODO: Handle these in main loop. + acl_state_changed: Box::new(|_, _, _, _| {}), + bond_state_changed: Box::new(|_, _, _| {}), + device_found: Box::new(|_, _| {}), + discovery_state_changed: Box::new(|_| {}), + pin_request: Box::new(|_, _, _, _| {}), + remote_device_properties_changed: Box::new(|_, _, _, _| {}), + ssp_request: Box::new(|_, _, _, _, _| {}), + } + } + }; + + // TODO: Have a simple framework to turn on/off macro-generated code debug. + debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string())); + + gen.into() +} diff --git a/gd/rust/linux/stack/src/bluetooth.rs b/gd/rust/linux/stack/src/bluetooth.rs new file mode 100644 index 000000000..a45f37bcb --- /dev/null +++ b/gd/rust/linux/stack/src/bluetooth.rs @@ -0,0 +1,192 @@ +//! Anything related to the adapter API (IBluetooth). + +use bt_topshim::btif::ffi; +use bt_topshim::btif::{BluetoothCallbacks, BluetoothInterface, BtState}; +use bt_topshim::topstack; + +use btif_macros::btif_callbacks_generator; +use btif_macros::stack_message; + +use num_traits::cast::ToPrimitive; +use num_traits::FromPrimitive; + +use std::fmt::Debug; +use std::sync::Arc; +use std::sync::Mutex; + +use tokio::sync::mpsc::Sender; + +use crate::{BDAddr, Message, RPCProxy}; + +/// Defines the adapter API. +pub trait IBluetooth { + /// Adds a callback from a client who wishes to observe adapter events. + fn register_callback(&mut self, callback: Box); + + /// Enables the adapter. + /// + /// Returns true if the request is accepted. + fn enable(&mut self) -> bool; + + /// Disables the adapter. + /// + /// Returns true if the request is accepted. + fn disable(&mut self) -> bool; + + /// Returns the Bluetooth address of the local adapter. + fn get_address(&self) -> String; +} + +/// The interface for adapter callbacks registered through `IBluetooth::register_callback`. +pub trait IBluetoothCallback: RPCProxy { + /// When any of the adapter states is changed. + fn on_bluetooth_state_changed(&self, prev_state: u32, new_state: u32); + + /// When any of the adapter local address is changed. + fn on_bluetooth_address_changed(&self, addr: String); +} + +/// Implementation of the adapter API. +pub struct Bluetooth { + intf: Arc>, + state: BtState, + callbacks: Vec<(u32, Box)>, + callbacks_last_id: u32, + tx: Sender, + local_address: Option, +} + +impl Bluetooth { + /// Constructs the IBluetooth implementation. + pub fn new(tx: Sender, intf: Arc>) -> Bluetooth { + Bluetooth { + tx, + intf, + state: BtState::Off, + callbacks: vec![], + callbacks_last_id: 0, + local_address: None, + } + } + + fn update_local_address(&mut self, raw: &Vec) { + self.local_address = Some(BDAddr::from_byte_vec(raw)); + + for callback in &self.callbacks { + callback.1.on_bluetooth_address_changed(self.local_address.unwrap().to_string()); + } + } + + pub(crate) fn callback_disconnected(&mut self, id: u32) { + self.callbacks.retain(|x| x.0 != id); + } +} + +#[btif_callbacks_generator(btif_bluetooth_callbacks, BluetoothCallbacks)] +pub(crate) trait BtifBluetoothCallbacks { + #[stack_message(BluetoothAdapterStateChanged)] + fn adapter_state_changed(&mut self, state: BtState); + + #[stack_message(BluetoothAdapterPropertiesChanged)] + fn adapter_properties_changed( + &mut self, + status: i32, + num_properties: i32, + properties: Vec, + ); +} + +#[derive(FromPrimitive, ToPrimitive, PartialEq, PartialOrd)] +#[repr(i32)] +#[derive(Debug)] +enum PropertyType { + BDName = 0x01, + BDAddr, + Uuids, + ClassOfDevice, + TypeOfDevice, + ServiceRecord, + AdapterScanMode, + AdapterBondedDevices, + AdapterDiscoverableTimeout, + RemoteFriendlyName, + RemoteRssi, + RemoteVersionInfo, + RemoteLocalLeFeatures, + RemoteDynamicAudioBuffer = 0x10, + Unknown = 0x100, +} + +impl BtifBluetoothCallbacks for Bluetooth { + fn adapter_state_changed(&mut self, state: BtState) { + for callback in &self.callbacks { + callback + .1 + .on_bluetooth_state_changed(self.state.to_u32().unwrap(), state.to_u32().unwrap()); + } + + self.state = state; + } + + #[allow(unused_variables)] + fn adapter_properties_changed( + &mut self, + status: i32, + num_properties: i32, + properties: Vec, + ) { + if status != 0 { + return; + } + + for prop in properties { + let prop_type = PropertyType::from_i32(prop.prop_type); + + if prop_type.is_none() { + continue; + } + + match prop_type.unwrap() { + PropertyType::BDAddr => { + self.update_local_address(&prop.val); + } + _ => {} + } + } + } +} + +// TODO: Add unit tests for this implementation +impl IBluetooth for Bluetooth { + fn register_callback(&mut self, mut callback: Box) { + let tx = self.tx.clone(); + + // TODO: Refactor into a separate wrap-around id generator. + self.callbacks_last_id += 1; + let id = self.callbacks_last_id; + + callback.register_disconnect(Box::new(move || { + let tx = tx.clone(); + topstack::get_runtime().spawn(async move { + let _result = tx.send(Message::BluetoothCallbackDisconnected(id)).await; + }); + })); + + self.callbacks.push((id, callback)) + } + + fn enable(&mut self) -> bool { + self.intf.lock().unwrap().enable() == 0 + } + + fn disable(&mut self) -> bool { + self.intf.lock().unwrap().disable() == 0 + } + + fn get_address(&self) -> String { + match self.local_address { + None => String::from(""), + Some(addr) => addr.to_string(), + } + } +} diff --git a/gd/rust/linux/stack/src/bluetooth_gatt.rs b/gd/rust/linux/stack/src/bluetooth_gatt.rs new file mode 100644 index 000000000..425276818 --- /dev/null +++ b/gd/rust/linux/stack/src/bluetooth_gatt.rs @@ -0,0 +1,86 @@ +//! Anything related to the GATT API (IBluetoothGatt). + +use bt_topshim::btif::BluetoothInterface; + +use std::sync::{Arc, Mutex}; + +/// Defines the GATT API. +pub trait IBluetoothGatt { + fn register_scanner(&self, callback: Box); + + fn unregister_scanner(&self, scanner_id: i32); + + fn start_scan(&self, scanner_id: i32, settings: ScanSettings, filters: Vec); + fn stop_scan(&self, scanner_id: i32); +} + +/// Interface for scanner callbacks to clients, passed to `IBluetoothGatt::register_scanner`. +pub trait IScannerCallback { + /// When the `register_scanner` request is done. + fn on_scanner_registered(&self, status: i32, scanner_id: i32); +} + +#[derive(Debug, FromPrimitive, ToPrimitive)] +#[repr(i32)] +/// Scan type configuration. +pub enum ScanType { + Active = 0, + Passive = 1, +} + +impl Default for ScanType { + fn default() -> Self { + ScanType::Active + } +} + +/// Represents RSSI configurations for hardware offloaded scanning. +// TODO: This is still a placeholder struct, not yet complete. +#[derive(Debug, Default)] +pub struct RSSISettings { + pub low_threshold: i32, + pub high_threshold: i32, +} + +/// Represents scanning configurations to be passed to `IBluetoothGatt::start_scan`. +#[derive(Debug, Default)] +pub struct ScanSettings { + pub interval: i32, + pub window: i32, + pub scan_type: ScanType, + pub rssi_settings: RSSISettings, +} + +/// Represents a scan filter to be passed to `IBluetoothGatt::start_scan`. +#[derive(Debug, Default)] +pub struct ScanFilter {} + +/// Implementation of the GATT API (IBluetoothGatt). +pub struct BluetoothGatt { + _intf: Arc>, +} + +impl BluetoothGatt { + /// Constructs a new IBluetoothGatt implementation. + pub fn new(intf: Arc>) -> BluetoothGatt { + BluetoothGatt { _intf: intf } + } +} + +impl IBluetoothGatt for BluetoothGatt { + fn register_scanner(&self, _callback: Box) { + // TODO: implement + } + + fn unregister_scanner(&self, _scanner_id: i32) { + // TODO: implement + } + + fn start_scan(&self, _scanner_id: i32, _settings: ScanSettings, _filters: Vec) { + // TODO: implement + } + + fn stop_scan(&self, _scanner_id: i32) { + // TODO: implement + } +} diff --git a/gd/rust/linux/stack/src/lib.rs b/gd/rust/linux/stack/src/lib.rs new file mode 100644 index 000000000..fc4f39ef3 --- /dev/null +++ b/gd/rust/linux/stack/src/lib.rs @@ -0,0 +1,110 @@ +//! Fluoride/GD Bluetooth stack. +//! +//! This crate provides the API implementation of the Fluoride/GD Bluetooth stack, independent of +//! any RPC projection. + +#[macro_use] +extern crate num_derive; + +pub mod bluetooth; +pub mod bluetooth_gatt; + +use bt_topshim::btif::ffi; +use bt_topshim::btif::BtState; + +use std::convert::TryInto; +use std::fmt::{Debug, Formatter, Result}; +use std::sync::{Arc, Mutex}; + +use tokio::sync::mpsc::channel; +use tokio::sync::mpsc::{Receiver, Sender}; + +use crate::bluetooth::{Bluetooth, BtifBluetoothCallbacks}; + +/// Represents a Bluetooth address. +// TODO: Add support for LE random addresses. +#[derive(Copy, Clone)] +pub struct BDAddr { + val: [u8; 6], +} + +impl Debug for BDAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + f.write_fmt(format_args!( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.val[0], self.val[1], self.val[2], self.val[3], self.val[4], self.val[5] + )) + } +} + +impl ToString for BDAddr { + fn to_string(&self) -> String { + String::from(format!( + "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", + self.val[0], self.val[1], self.val[2], self.val[3], self.val[4], self.val[5] + )) + } +} + +impl BDAddr { + /// Constructs a BDAddr from a vector of 6 bytes. + fn from_byte_vec(raw_addr: &Vec) -> BDAddr { + BDAddr { val: raw_addr.clone().try_into().unwrap() } + } +} + +/// Message types that are sent to the stack main dispatch loop. +pub enum Message { + BluetoothAdapterStateChanged(BtState), + BluetoothAdapterPropertiesChanged(i32, i32, Vec), + BluetoothCallbackDisconnected(u32), +} + +/// Umbrella class for the Bluetooth stack. +pub struct Stack {} + +impl Stack { + /// Creates an mpsc channel for passing messages to the main dispatch loop. + pub fn create_channel() -> (Sender, Receiver) { + channel::(1) + } + + /// Runs the main dispatch loop. + pub async fn dispatch(mut rx: Receiver, bluetooth: Arc>) { + loop { + let m = rx.recv().await; + + if m.is_none() { + eprintln!("Message dispatch loop quit"); + break; + } + + match m.unwrap() { + Message::BluetoothAdapterStateChanged(state) => { + bluetooth.lock().unwrap().adapter_state_changed(state); + } + + Message::BluetoothAdapterPropertiesChanged(status, num_properties, properties) => { + bluetooth.lock().unwrap().adapter_properties_changed( + status, + num_properties, + properties, + ); + } + + Message::BluetoothCallbackDisconnected(id) => { + bluetooth.lock().unwrap().callback_disconnected(id); + } + } + } + } +} + +/// Signifies that the object may be a proxy to a remote RPC object. +/// +/// An object that implements RPCProxy trait signifies that the object may be a proxy to a remote +/// RPC object. Therefore the object may be disconnected and thus should implement +/// `register_disconnect` to let others observe the disconnection event. +pub trait RPCProxy { + fn register_disconnect(&mut self, f: Box); +} -- 2.11.0