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

Commit c8a8d02e authored by Rahul Arya's avatar Rahul Arya
Browse files

[Private GATT] Mirror GATT server inside rust module

Since servers can be registered as "private" after creation,
we mirror their services in Rust by snooping on the JNI
calls/callbacks.

Bug: 255880936
Test: unit
Change-Id: If14d1a97a0f57a0a8bb8094fb48d7a0c7d5c8128
parent 4b3dea3b
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -624,6 +624,7 @@ static const btgatt_client_callbacks_t sGattClientCallbacks = {
 */

void btgatts_register_app_cb(int status, int server_if, const Uuid& uuid) {
  bluetooth::gatt::open_server(server_if);
  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mCallbacksObj) return;
@@ -646,6 +647,19 @@ void btgatts_connection_cb(int conn_id, int server_if, int connected,
void btgatts_service_added_cb(int status, int server_if,
                              const btgatt_db_element_t* service,
                              size_t service_count) {
  // mirror the database in rust, now that it's created.
  if (status == 0x00 /* SUCCESS */) {
    auto service_records = rust::Vec<bluetooth::gatt::GattRecord>();
    for (size_t i = 0; i != service_count; ++i) {
      auto& curr_service = service[i];
      service_records.push_back(bluetooth::gatt::GattRecord{
          curr_service.uuid, (bluetooth::gatt::GattRecordType)curr_service.type,
          curr_service.attribute_handle, curr_service.properties,
          curr_service.extended_properties, curr_service.permissions});
    }
    bluetooth::gatt::add_service(server_if, std::move(service_records));
  }

  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mCallbacksObj) return;
@@ -664,6 +678,8 @@ void btgatts_service_added_cb(int status, int server_if,
}

void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle) {
  bluetooth::gatt::remove_service(server_if, srvc_handle);

  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mCallbacksObj) return;
@@ -672,6 +688,8 @@ void btgatts_service_stopped_cb(int status, int server_if, int srvc_handle) {
}

void btgatts_service_deleted_cb(int status, int server_if, int srvc_handle) {
  bluetooth::gatt::remove_service(server_if, srvc_handle);

  std::shared_lock<std::shared_mutex> lock(callbacks_mutex);
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid() || !mCallbacksObj) return;
@@ -1899,6 +1917,7 @@ static void gattServerRegisterAppNative(JNIEnv* env, jobject object,
static void gattServerUnregisterAppNative(JNIEnv* env, jobject object,
                                          jint serverIf) {
  if (!sGattIf) return;
  bluetooth::gatt::close_server(serverIf);
  sGattIf->server->unregister_server(serverIf);
}

+12 −0
Original line number Diff line number Diff line
@@ -14,11 +14,23 @@

use crate::core::init;

use cxx::{type_id, ExternType};
pub use inner::*;

unsafe impl ExternType for Uuid {
    type Id = type_id!("bluetooth::Uuid");
    type Kind = cxx::kind::Trivial;
}

#[allow(dead_code, missing_docs)]
#[cxx::bridge]
mod inner {
    #[namespace = "bluetooth"]
    extern "C++" {
        include!("bluetooth/uuid.h");
        type Uuid = crate::core::uuid::Uuid;
    }

    #[namespace = "bluetooth::rust_shim"]
    extern "Rust" {
        fn init();
+302 −2
Original line number Diff line number Diff line
//! FFI interfaces for the GATT module. Some structs are exported so that
//! core::init can instantiate and pass them into the main loop.

use anyhow::{bail, Result};
use bt_common::init_flags::rust_event_loop_is_enabled;
pub use inner::*;
use log::{error, info, warn};

use crate::packets::{AttBuilder, Serializable, SerializeError};
use crate::{
    do_in_rust_thread,
    packets::{AttBuilder, Serializable, SerializeError},
};

use super::{channel::AttTransport, ids::TransportIndex};
use super::{
    channel::AttTransport,
    ids::{AttHandle, ServerId, TransportIndex},
    server::gatt_database::{AttPermissions, GattCharacteristicWithHandle, GattServiceWithHandle},
};

#[cxx::bridge]
#[allow(clippy::needless_lifetimes)]
#[allow(clippy::too_many_arguments)]
#[allow(missing_docs)]
mod inner {
    #[namespace = "bluetooth"]
    extern "C++" {
        /// A C++ UUID.
        type Uuid = crate::core::uuid::Uuid;
    }

    /// The type of GATT record supplied over FFI
    #[derive(Debug)]
    #[namespace = "bluetooth::gatt"]
    enum GattRecordType {
        PrimaryService,
        SecondaryService,
        IncludedService,
        Characteristic,
        Descriptor,
    }

    /// An entry in a service definition received from JNI. See GattRecordType
    /// for possible types.
    #[namespace = "bluetooth::gatt"]
    struct GattRecord {
        uuid: Uuid,
        record_type: GattRecordType,
        attribute_handle: u16,

        properties: u8,
        extended_properties: u16,

        permissions: u16,
    }

    #[namespace = "bluetooth::shim::arbiter"]
    unsafe extern "C++" {
        include!("stack/arbiter/acl_arbiter.h");
        /// Send an outgoing packet on the specified tcb_idx
        fn SendPacketToPeer(tcb_idx: u8, packet: Vec<u8>);
    }

    #[namespace = "bluetooth::gatt"]
    extern "Rust" {
        // service management
        fn open_server(server_id: u8);
        fn close_server(server_id: u8);
        fn add_service(server_id: u8, service_records: Vec<GattRecord>);
        fn remove_service(server_id: u8, service_handle: u16);
    }
}

/// Implementation of AttTransport wrapping the corresponding C++ method
@@ -33,3 +83,253 @@ impl AttTransport for AttTransportImpl {
        Ok(())
    }
}

fn open_server(server_id: u8) {
    if !rust_event_loop_is_enabled() {
        return;
    }

    let server_id = ServerId(server_id);

    do_in_rust_thread(move |modules| {
        if let Err(err) = modules.gatt_module.open_gatt_server(server_id) {
            error!("{err:?}")
        }
    })
}

fn close_server(server_id: u8) {
    if !rust_event_loop_is_enabled() {
        return;
    }

    let server_id = ServerId(server_id);

    do_in_rust_thread(move |modules| {
        if let Err(err) = modules.gatt_module.close_gatt_server(server_id) {
            error!("{err:?}")
        }
    })
}

fn records_to_service(service_records: &[GattRecord]) -> Result<GattServiceWithHandle> {
    let mut characteristics = vec![];
    let mut service_handle_uuid = None;

    for record in service_records {
        match record.record_type {
            GattRecordType::PrimaryService => {
                if service_handle_uuid.is_some() {
                    bail!("got service registration but with duplicate primary service! {service_records:?}".to_string());
                }
                service_handle_uuid = Some((record.attribute_handle, record.uuid));
            }
            GattRecordType::Characteristic => characteristics.push(GattCharacteristicWithHandle {
                handle: AttHandle(record.attribute_handle),
                type_: record.uuid,
                permissions: AttPermissions {
                    readable: record.properties & 0x02 != 0,
                    writable: record.properties & 0x08 != 0,
                },
            }),
            _ => {
                warn!("ignoring unsupported database entry of type {:?}", record.record_type)
            }
        }
    }

    let Some((handle, uuid)) = service_handle_uuid else {
        bail!("got service registration but with no primary service! {characteristics:?}".to_string())
    };

    Ok(GattServiceWithHandle { handle: AttHandle(handle), type_: uuid, characteristics })
}

fn add_service(server_id: u8, service_records: Vec<GattRecord>) {
    if !rust_event_loop_is_enabled() {
        return;
    }

    // marshal into the form expected by GattModule
    let server_id = ServerId(server_id);

    match records_to_service(&service_records) {
        Ok(service) => {
            let handle = service.handle;
            do_in_rust_thread(move |modules| {
                let ok = modules.gatt_module.register_gatt_service(server_id, service.clone());
                match ok {
                    Ok(_) => info!(
                        "successfully registered service for server {server_id:?} with handle {handle:?} (service={service:?})"
                    ),
                    Err(err) => error!(
                        "failed to register GATT service for server {server_id:?} with error: {err},  (service={service:?})"
                    ),
                }
            });
        }
        Err(err) => {
            error!("failed to register service for server {server_id:?}, err: {err:?}")
        }
    }
}

fn remove_service(server_id: u8, service_handle: u16) {
    if !rust_event_loop_is_enabled() {
        return;
    }

    let server_id = ServerId(server_id);
    let service_handle = AttHandle(service_handle);
    do_in_rust_thread(move |modules| {
        let ok = modules.gatt_module.unregister_gatt_service(server_id, service_handle);
        match ok {
            Ok(_) => info!(
                "successfully removed service {service_handle:?} for server {server_id:?}"
            ),
            Err(err) => error!(
                "failed to remove GATT service {service_handle:?} for server {server_id:?} with error: {err}"
            ),
        }
    })
}

#[cfg(test)]
mod test {
    use super::*;

    const SERVICE_HANDLE: AttHandle = AttHandle(1);
    const SERVICE_UUID: Uuid = Uuid::new(0x1234);

    const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(2);
    const CHARACTERISTIC_UUID: Uuid = Uuid::new(0x5678);

    const ANOTHER_CHARACTERISTIC_HANDLE: AttHandle = AttHandle(3);
    const ANOTHER_CHARACTERISTIC_UUID: Uuid = Uuid::new(0x9ABC);

    fn make_service_record(uuid: Uuid, handle: AttHandle) -> GattRecord {
        GattRecord {
            uuid,
            record_type: GattRecordType::PrimaryService,
            attribute_handle: handle.0,
            properties: 0,
            extended_properties: 0,
            permissions: 0,
        }
    }

    fn make_characteristic_record(uuid: Uuid, handle: AttHandle, properties: u8) -> GattRecord {
        GattRecord {
            uuid,
            record_type: GattRecordType::Characteristic,
            attribute_handle: handle.0,
            properties,
            extended_properties: 0,
            permissions: 0,
        }
    }

    #[test]
    fn test_empty_records() {
        let res = records_to_service(&[]);
        assert!(res.is_err());
    }

    #[test]
    fn test_primary_service() {
        let service =
            records_to_service(&[make_service_record(SERVICE_UUID, SERVICE_HANDLE)]).unwrap();

        assert_eq!(service.handle, SERVICE_HANDLE);
        assert_eq!(service.type_, SERVICE_UUID);
        assert_eq!(service.characteristics.len(), 0);
    }

    #[test]
    fn test_dupe_primary_service() {
        let res = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
        ]);

        assert!(res.is_err());
    }

    #[test]
    fn test_service_with_single_characteristic() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0),
        ])
        .unwrap();

        assert_eq!(service.handle, SERVICE_HANDLE);
        assert_eq!(service.type_, SERVICE_UUID);

        assert_eq!(service.characteristics.len(), 1);
        assert_eq!(service.characteristics[0].handle, CHARACTERISTIC_HANDLE);
        assert_eq!(service.characteristics[0].type_, CHARACTERISTIC_UUID);
    }

    #[test]
    fn test_multiple_characteristics() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0),
            make_characteristic_record(
                ANOTHER_CHARACTERISTIC_UUID,
                ANOTHER_CHARACTERISTIC_HANDLE,
                0,
            ),
        ])
        .unwrap();

        assert_eq!(service.characteristics.len(), 2);
        assert_eq!(service.characteristics[0].handle, CHARACTERISTIC_HANDLE);
        assert_eq!(service.characteristics[0].type_, CHARACTERISTIC_UUID);
        assert_eq!(service.characteristics[1].handle, ANOTHER_CHARACTERISTIC_HANDLE);
        assert_eq!(service.characteristics[1].type_, ANOTHER_CHARACTERISTIC_UUID);
    }

    #[test]
    fn test_characteristic_readable_property() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x02),
        ])
        .unwrap();

        assert_eq!(
            service.characteristics[0].permissions,
            AttPermissions { readable: true, writable: false }
        );
    }

    #[test]
    fn test_characteristic_writable_property() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x08),
        ])
        .unwrap();

        assert_eq!(
            service.characteristics[0].permissions,
            AttPermissions { readable: false, writable: true }
        );
    }

    #[test]
    fn test_characteristic_readable_and_writable_property() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, SERVICE_HANDLE),
            make_characteristic_record(CHARACTERISTIC_UUID, CHARACTERISTIC_HANDLE, 0x02 | 0x08),
        ])
        .unwrap();

        assert_eq!(
            service.characteristics[0].permissions,
            AttPermissions { readable: true, writable: true }
        );
    }
}