Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e7c3fe23 authored by Sonny Sasaka's avatar Sonny Sasaka
Browse files

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
parent d61f2117
Loading
Loading
Loading
Loading
+20 −0
Original line number Diff line number Diff line
[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"
+12 −0
Original line number Diff line number Diff line
[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"
+136 −0
Original line number Diff line number Diff line
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::<Expr, Comma>::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<Message>) -> #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()
}
+192 −0
Original line number Diff line number Diff line
//! 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<dyn IBluetoothCallback + Send>);

    /// 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<Mutex<BluetoothInterface>>,
    state: BtState,
    callbacks: Vec<(u32, Box<dyn IBluetoothCallback + Send>)>,
    callbacks_last_id: u32,
    tx: Sender<Message>,
    local_address: Option<BDAddr>,
}

impl Bluetooth {
    /// Constructs the IBluetooth implementation.
    pub fn new(tx: Sender<Message>, intf: Arc<Mutex<BluetoothInterface>>) -> Bluetooth {
        Bluetooth {
            tx,
            intf,
            state: BtState::Off,
            callbacks: vec![],
            callbacks_last_id: 0,
            local_address: None,
        }
    }

    fn update_local_address(&mut self, raw: &Vec<u8>) {
        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<ffi::BtProperty>,
    );
}

#[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<ffi::BtProperty>,
    ) {
        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<dyn IBluetoothCallback + Send>) {
        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(),
        }
    }
}
+86 −0
Original line number Diff line number Diff line
//! 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<dyn IScannerCallback + Send>);

    fn unregister_scanner(&self, scanner_id: i32);

    fn start_scan(&self, scanner_id: i32, settings: ScanSettings, filters: Vec<ScanFilter>);
    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<Mutex<BluetoothInterface>>,
}

impl BluetoothGatt {
    /// Constructs a new IBluetoothGatt implementation.
    pub fn new(intf: Arc<Mutex<BluetoothInterface>>) -> BluetoothGatt {
        BluetoothGatt { _intf: intf }
    }
}

impl IBluetoothGatt for BluetoothGatt {
    fn register_scanner(&self, _callback: Box<dyn IScannerCallback + Send>) {
        // TODO: implement
    }

    fn unregister_scanner(&self, _scanner_id: i32) {
        // TODO: implement
    }

    fn start_scan(&self, _scanner_id: i32, _settings: ScanSettings, _filters: Vec<ScanFilter>) {
        // TODO: implement
    }

    fn stop_scan(&self, _scanner_id: i32) {
        // TODO: implement
    }
}
Loading