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

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

floss: Implement UnregisterClient and ClientConnect

This implements IBluetoothGatt's UnregisterClient and ClientConnect
according to the Binder implementation. Also introduces ContextMap
structure to store GATT API client info.

Bug: 193685325
Tag: #floss
Test: Manual - build floss on Linux and test with WIP btclient

Change-Id: Icf20fc6fc09ee283d0133f5a78bac7a5cce395d5
parent 68844755
Loading
Loading
Loading
Loading
+13 −1
Original line number Diff line number Diff line
@@ -84,5 +84,17 @@ impl IBluetoothGatt for IBluetoothGattDBus {
    }

    #[dbus_method("UnregisterClient")]
    fn unregister_client(&self, client_if: i32) {}
    fn unregister_client(&mut self, client_id: i32) {}

    #[dbus_method("ClientConnect")]
    fn client_connect(
        &self,
        client_id: i32,
        addr: String,
        is_direct: bool,
        transport: i32,
        opportunistic: bool,
        phy: i32,
    ) {
    }
}
+151 −19
Original line number Diff line number Diff line
@@ -9,13 +9,55 @@ use bt_topshim::profiles::gatt::{
};
use bt_topshim::topstack;

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

use tokio::sync::mpsc::Sender;

use crate::{Message, RPCProxy};

struct Client {
    id: Option<i32>,
    uuid: Uuid128Bit,
    callback: Box<dyn IBluetoothGattCallback + Send>,
}

struct ContextMap {
    // TODO(b/196635530): Consider using `multimap` for a more efficient implementation of get by
    // multiple keys.
    clients: Vec<Client>,
}

impl ContextMap {
    fn new() -> ContextMap {
        ContextMap { clients: vec![] }
    }

    fn get_by_uuid(&self, uuid: &Uuid128Bit) -> Option<&Client> {
        self.clients.iter().find(|client| client.uuid == *uuid)
    }

    fn add(&mut self, uuid: &Uuid128Bit, callback: Box<dyn IBluetoothGattCallback + Send>) {
        if self.get_by_uuid(uuid).is_some() {
            return;
        }

        self.clients.push(Client { id: None, uuid: uuid.clone(), callback });
    }

    fn remove(&mut self, id: i32) {
        self.clients.retain(|client| !(client.id.is_some() && client.id.unwrap() == id));
    }

    fn set_client_id(&mut self, uuid: &Uuid128Bit, id: i32) {
        let client = self.clients.iter_mut().find(|client| client.uuid == *uuid);
        if client.is_none() {
            return;
        }

        client.unwrap().id = Some(id);
    }
}

/// Defines the GATT API.
pub trait IBluetoothGatt {
    fn register_scanner(&self, callback: Box<dyn IScannerCallback + Send>);
@@ -34,13 +76,24 @@ pub trait IBluetoothGatt {
    );

    /// Unregisters a GATT Client.
    fn unregister_client(&self, client_if: i32);
    fn unregister_client(&mut self, client_id: i32);

    /// Initiates a GATT connection to a peer device.
    fn client_connect(
        &self,
        client_id: i32,
        addr: String,
        is_direct: bool,
        transport: i32,
        opportunistic: bool,
        phy: i32,
    );
}

/// Callback for GATT Client API.
pub trait IBluetoothGattCallback: RPCProxy {
    /// When the `register_client` request is done.
    fn on_client_registered(&self, status: i32, client_if: i32);
    fn on_client_registered(&self, status: i32, client_id: i32);
}

/// Interface for scanner callbacks to clients, passed to `IBluetoothGatt::register_scanner`.
@@ -91,13 +144,13 @@ pub struct BluetoothGatt {
    intf: Arc<Mutex<BluetoothInterface>>,
    gatt: Option<Gatt>,

    gatt_client_map: HashMap<Uuid128Bit, Box<dyn IBluetoothGattCallback + Send>>,
    context_map: ContextMap,
}

impl BluetoothGatt {
    /// Constructs a new IBluetoothGatt implementation.
    pub fn new(intf: Arc<Mutex<BluetoothInterface>>) -> BluetoothGatt {
        BluetoothGatt { intf: intf, gatt: None, gatt_client_map: HashMap::new() }
        BluetoothGatt { intf: intf, gatt: None, context_map: ContextMap::new() }
    }

    pub fn init_profiles(&mut self, tx: Sender<Message>) {
@@ -123,7 +176,9 @@ impl BluetoothGatt {

// Temporary util that covers only basic string conversion.
// TODO(b/193685325): Implement more UUID utils by using Uuid from gd/hci/uuid.h with cxx.
fn parse_uuid_string(uuid: String) -> Option<Uuid> {
fn parse_uuid_string<T: Into<String>>(uuid: T) -> Option<Uuid> {
    let uuid = uuid.into();

    if uuid.len() != 32 {
        return None;
    }
@@ -165,52 +220,102 @@ impl IBluetoothGatt for BluetoothGatt {
        eatt_support: bool,
    ) {
        let uuid = parse_uuid_string(app_uuid).unwrap();
        self.gatt_client_map.insert(uuid.uu, callback);
        self.context_map.add(&uuid.uu, callback);
        self.gatt.as_ref().unwrap().client.register_client(&uuid, eatt_support);
    }

    fn unregister_client(&self, _client_if: i32) {
        // TODO(b/193685325): implement
    fn unregister_client(&mut self, client_id: i32) {
        self.context_map.remove(client_id);
        self.gatt.as_ref().unwrap().client.unregister_client(client_id);
    }

    fn client_connect(
        &self,
        client_id: i32,
        addr: String,
        is_direct: bool,
        transport: i32,
        opportunistic: bool,
        phy: i32,
    ) {
        let address = match RawAddress::from_string(addr.clone()) {
            None => return,
            Some(addr) => addr,
        };

        self.gatt.as_ref().unwrap().client.connect(
            client_id,
            &address,
            is_direct,
            transport,
            opportunistic,
            phy,
        );
    }
}

#[btif_callbacks_dispatcher(BluetoothGatt, dispatch_gatt_client_callbacks, GattClientCallbacks)]
pub(crate) trait BtifGattClientCallbacks {
    #[btif_callback(RegisterClient)]
    fn register_client_cb(&mut self, status: i32, client_if: i32, app_uuid: Uuid);
    fn register_client_cb(&mut self, status: i32, client_id: i32, app_uuid: Uuid);

    #[btif_callback(Connect)]
    fn connect_cb(&mut self, conn_id: i32, status: i32, client_if: i32, addr: RawAddress);
    fn connect_cb(&mut self, conn_id: i32, status: i32, client_id: i32, addr: RawAddress);

    // TODO(b/193685325): Define all callbacks.
}

impl BtifGattClientCallbacks for BluetoothGatt {
    fn register_client_cb(&mut self, status: i32, client_if: i32, app_uuid: Uuid) {
        let callback = self.gatt_client_map.get(&app_uuid.uu);
        if callback.is_none() {
            println!("Warning: Callback not registered for UUID {:?}", app_uuid.uu);
    fn register_client_cb(&mut self, status: i32, client_id: i32, app_uuid: Uuid) {
        self.context_map.set_client_id(&app_uuid.uu, client_id);

        let client = self.context_map.get_by_uuid(&app_uuid.uu);
        if client.is_none() {
            println!("Warning: Client not registered for UUID {:?}", app_uuid.uu);
            return;
        }

        callback.unwrap().on_client_registered(status, client_if);
        let callback = &client.unwrap().callback;
        callback.on_client_registered(status, client_id);
    }

    fn connect_cb(&mut self, _conn_id: i32, _status: i32, _client_if: i32, _addr: RawAddress) {
    fn connect_cb(&mut self, _conn_id: i32, _status: i32, _client_id: i32, _addr: RawAddress) {
        // TODO(b/193685325): handle;
    }
}

#[cfg(test)]
mod tests {
    struct TestBluetoothGattCallback {
        id: String,
    }

    impl TestBluetoothGattCallback {
        fn new(id: String) -> TestBluetoothGattCallback {
            TestBluetoothGattCallback { id }
        }
    }

    impl IBluetoothGattCallback for TestBluetoothGattCallback {
        fn on_client_registered(&self, _status: i32, _client_id: i32) {}
    }

    impl RPCProxy for TestBluetoothGattCallback {
        fn register_disconnect(&mut self, _f: Box<dyn Fn() + Send>) {}

        fn get_object_id(&self) -> String {
            self.id.clone()
        }
    }

    use super::*;

    #[test]
    fn test_uuid_from_string() {
        let uuid = parse_uuid_string(String::from("abcdef"));
        let uuid = parse_uuid_string("abcdef");
        assert!(uuid.is_none());

        let uuid = parse_uuid_string(String::from("0123456789abcdef0123456789abcdef"));
        let uuid = parse_uuid_string("0123456789abcdef0123456789abcdef");
        assert!(uuid.is_some());
        let expected: [u8; 16] = [
            0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab,
@@ -218,4 +323,31 @@ mod tests {
        ];
        assert_eq!(Uuid { uu: expected }, uuid.unwrap());
    }

    #[test]
    fn test_context_map() {
        let mut map = ContextMap::new();

        // Add client 1.
        let callback1 = Box::new(TestBluetoothGattCallback::new(String::from("Callback 1")));
        let uuid1 = parse_uuid_string("00000000000000000000000000000001").unwrap().uu;
        map.add(&uuid1, callback1);
        let found = map.get_by_uuid(&uuid1);
        assert!(found.is_some());
        assert_eq!("Callback 1", found.unwrap().callback.get_object_id());

        // Add client 2.
        let callback2 = Box::new(TestBluetoothGattCallback::new(String::from("Callback 2")));
        let uuid2 = parse_uuid_string("00000000000000000000000000000002").unwrap().uu;
        map.add(&uuid2, callback2);
        let found = map.get_by_uuid(&uuid2);
        assert!(found.is_some());
        assert_eq!("Callback 2", found.unwrap().callback.get_object_id());

        // Remove client 1.
        map.set_client_id(&uuid1, 3);
        map.remove(3);
        let found = map.get_by_uuid(&uuid1);
        assert!(found.is_none());
    }
}