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

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

[Private GATT] Add support for descriptors

Bug: 255880936
Test: unit
Change-Id: I62a0f715cbf27e5a895dcdb39cd9f06233fb19ea
parent 26c1bc3f
Loading
Loading
Loading
Loading
+13 −8
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ use async_trait::async_trait;
use crate::packets::{AttAttributeDataChild, AttAttributeDataView, AttErrorCode};

use super::{
    ffi::AttributeBackingType,
    ids::{AttHandle, ConnectionId, TransactionId},
    server::IndicationError,
};
@@ -18,25 +19,27 @@ use super::{
/// These callbacks are expected to be made available to the GattModule from
/// JNI.
pub trait GattCallbacks {
    /// Invoked when a client tries to read a characteristic. Expects a response
    /// using bluetooth::gatt::send_response();
    fn on_server_read_characteristic(
    /// Invoked when a client tries to read a characteristic/descriptor. Expects
    /// a response using bluetooth::gatt::send_response();
    fn on_server_read(
        &self,
        conn_id: ConnectionId,
        trans_id: TransactionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        offset: u32,
        is_long: bool,
    );

    /// Invoked when a client tries to write a characteristic. Expects a
    /// response using bluetooth::gatt::send_response();
    /// Invoked when a client tries to write a characteristic/descriptor.
    /// Expects a response using bluetooth::gatt::send_response();
    #[allow(clippy::too_many_arguments)] // needed to match the C++ interface
    fn on_server_write_characteristic(
    fn on_server_write(
        &self,
        conn_id: ConnectionId,
        trans_id: TransactionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        offset: u32,
        need_response: bool,
        is_prepare: bool,
@@ -64,17 +67,19 @@ pub trait GattDatastore {
    fn remove_connection(&self, conn_id: ConnectionId);

    /// Read a characteristic from the specified connection at the given handle.
    async fn read_characteristic(
    async fn read(
        &self,
        conn_id: ConnectionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
    ) -> Result<AttAttributeDataChild, AttErrorCode>;

    /// Write data to a given characteristic on the specified connection.
    async fn write_characteristic(
    async fn write(
        &self,
        conn_id: ConnectionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        data: AttAttributeDataView<'_>,
    ) -> Result<(), AttErrorCode>;
}
+7 −6
Original line number Diff line number Diff line
@@ -12,7 +12,7 @@ use crate::{
    packets::{AttAttributeDataChild, AttAttributeDataView, AttErrorCode},
};

use super::GattDatastore;
use super::{AttributeBackingType, GattDatastore};

struct PendingTransaction {
    response: oneshot::Sender<Result<AttAttributeDataChild, AttErrorCode>>,
@@ -121,15 +121,16 @@ impl GattDatastore for CallbackTransactionManager {
        assert!(old_conn.is_some(), "Received unexpected connection ID, something has gone wrong")
    }

    async fn read_characteristic(
    async fn read(
        &self,
        conn_id: ConnectionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
    ) -> Result<AttAttributeDataChild, AttErrorCode> {
        let (trans_id, rx) =
            self.pending_transactions.borrow_mut().start_new_transaction(conn_id)?;

        self.callbacks.on_server_read_characteristic(conn_id, trans_id, handle, 0, false);
        self.callbacks.on_server_read(conn_id, trans_id, handle, attr_type, 0, false);

        if let Ok(value) = rx.await {
            value
@@ -139,17 +140,17 @@ impl GattDatastore for CallbackTransactionManager {
        }
    }

    async fn write_characteristic(
    async fn write(
        &self,
        conn_id: ConnectionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        data: AttAttributeDataView<'_>,
    ) -> Result<(), AttErrorCode> {
        let (trans_id, rx) =
            self.pending_transactions.borrow_mut().start_new_transaction(conn_id)?;

        self.callbacks
            .on_server_write_characteristic(conn_id, trans_id, handle, 0, true, false, data);
        self.callbacks.on_server_write(conn_id, trans_id, handle, attr_type, 0, true, false, data);

        if let Ok(value) = rx.await {
            value.map(|_| ()) // the data passed back is irrelevant for write
+148 −20
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 std::iter::Peekable;

use anyhow::{bail, Result};
use bt_common::init_flags::{
    always_use_private_gatt_for_debugging_is_enabled, rust_event_loop_is_enabled,
@@ -23,8 +25,10 @@ use super::{
    channel::AttTransport,
    ids::{AdvertiserId, AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
    server::{
        att_server_bearer::AttServerBearer,
        gatt_database::{AttPermissions, GattCharacteristicWithHandle, GattServiceWithHandle},
        gatt_database::{
            AttPermissions, GattCharacteristicWithHandle, GattDescriptorWithHandle,
            GattServiceWithHandle,
        },
        IndicationError,
    },
    GattCallbacks,
@@ -44,33 +48,49 @@ mod inner {
        type Uuid = crate::core::uuid::Uuid;
    }

    /// The GATT entity backing the value of a user-controlled
    /// attribute
    #[derive(Debug)]
    #[namespace = "bluetooth::gatt"]
    enum AttributeBackingType {
        /// A GATT characteristic
        #[cxx_name = "CHARACTERISTIC"]
        Characteristic = 0u32,
        /// A GATT descriptor
        #[cxx_name = "DESCRIPTOR"]
        Descriptor = 1u32,
    }

    #[namespace = "bluetooth::gatt"]
    unsafe extern "C++" {
        include!("src/gatt/ffi/gatt_shim.h");
        type AttributeBackingType;

        /// This contains the callbacks from Rust into C++ JNI needed for GATT
        type GattServerCallbacks;

        /// This callback is invoked when reading a characteristic - the client
        /// This callback is invoked when reading - the client
        /// must reply using SendResponse
        #[cxx_name = "OnServerReadCharacteristic"]
        fn on_server_read_characteristic(
        #[cxx_name = "OnServerRead"]
        fn on_server_read(
            self: &GattServerCallbacks,
            conn_id: u16,
            trans_id: u32,
            attr_handle: u16,
            attr_type: AttributeBackingType,
            offset: u32,
            is_long: bool,
        );

        /// This callback is invoked when writing a characteristic - the client
        /// This callback is invoked when writing - the client
        /// must reply using SendResponse
        #[cxx_name = "OnServerWriteCharacteristic"]
        fn on_server_write_characteristic(
        #[cxx_name = "OnServerWrite"]
        fn on_server_write(
            self: &GattServerCallbacks,
            conn_id: u16,
            trans_id: u32,
            attr_handle: u16,
            attr_type: AttributeBackingType,
            offset: u32,
            need_response: bool,
            is_prepare: bool,
@@ -80,7 +100,7 @@ mod inner {
        /// This callback is invoked when an indication has been sent and the
        /// peer device has confirmed it, or if some error occurred.
        #[cxx_name = "OnIndicationSentConfirmation"]
        fn on_indication_sent_confirmation(&self, conn_id: u16, status: i32);
        fn on_indication_sent_confirmation(self: &GattServerCallbacks, conn_id: u16, status: i32);
    }

    /// What action the arbiter should take in response to an incoming packet
@@ -160,34 +180,37 @@ mod inner {
pub struct GattCallbacksImpl(pub UniquePtr<GattServerCallbacks>);

impl GattCallbacks for GattCallbacksImpl {
    fn on_server_read_characteristic(
    fn on_server_read(
        &self,
        conn_id: ConnectionId,
        trans_id: TransactionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        offset: u32,
        is_long: bool,
    ) {
        self.0
            .as_ref()
            .unwrap()
            .on_server_read_characteristic(conn_id.0, trans_id.0, handle.0, offset, is_long);
            .on_server_read(conn_id.0, trans_id.0, handle.0, attr_type, offset, is_long);
    }

    fn on_server_write_characteristic(
    fn on_server_write(
        &self,
        conn_id: ConnectionId,
        trans_id: TransactionId,
        handle: AttHandle,
        attr_type: AttributeBackingType,
        offset: u32,
        need_response: bool,
        is_prepare: bool,
        value: AttAttributeDataView,
    ) {
        self.0.as_ref().unwrap().on_server_write_characteristic(
        self.0.as_ref().unwrap().on_server_write(
            conn_id.0,
            trans_id.0,
            handle.0,
            attr_type,
            offset,
            need_response,
            is_prepare,
@@ -262,11 +285,33 @@ fn close_server(server_id: u8) {
    })
}

fn consume_descriptors<'a>(
    records: &mut Peekable<impl Iterator<Item = &'a GattRecord>>,
) -> Vec<GattDescriptorWithHandle> {
    let mut out = vec![];
    while let Some(GattRecord { uuid, attribute_handle, permissions, .. }) =
        records.next_if(|record| record.record_type == GattRecordType::Descriptor)
    {
        let mut att_permissions = AttPermissions::empty();
        att_permissions.set(AttPermissions::READABLE, permissions & 0x01 != 0);
        att_permissions.set(AttPermissions::WRITABLE, permissions & 0x10 != 0);

        out.push(GattDescriptorWithHandle {
            handle: AttHandle(*attribute_handle),
            type_: *uuid,
            permissions: att_permissions,
        })
    }
    out
}

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

    for record in service_records {
    let mut service_records = service_records.iter().peekable();

    while let Some(record) = service_records.next() {
        match record.record_type {
            GattRecordType::PrimaryService => {
                if service_handle_uuid.is_some() {
@@ -274,11 +319,17 @@ fn records_to_service(service_records: &[GattRecord]) -> Result<GattServiceWithH
                }
                service_handle_uuid = Some((record.attribute_handle, record.uuid));
            }
            GattRecordType::Characteristic => characteristics.push(GattCharacteristicWithHandle {
            GattRecordType::Characteristic => {
                characteristics.push(GattCharacteristicWithHandle {
                    handle: AttHandle(record.attribute_handle),
                    type_: record.uuid,
                    permissions: AttPermissions::from_bits_truncate(record.properties),
            }),
                    descriptors: consume_descriptors(&mut service_records),
                });
            }
            GattRecordType::Descriptor => {
                bail!("Got unexpected descriptor outside of characteristic declaration")
            }
            _ => {
                warn!("ignoring unsupported database entry of type {:?}", record.record_type)
            }
@@ -424,7 +475,10 @@ mod test {
    const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(2);
    const CHARACTERISTIC_UUID: Uuid = Uuid::new(0x5678);

    const ANOTHER_CHARACTERISTIC_HANDLE: AttHandle = AttHandle(3);
    const DESCRIPTOR_UUID: Uuid = Uuid::new(0x4321);
    const ANOTHER_DESCRIPTOR_UUID: Uuid = Uuid::new(0x5432);

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

    fn make_service_record(uuid: Uuid, handle: AttHandle) -> GattRecord {
@@ -449,6 +503,17 @@ mod test {
        }
    }

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

    #[test]
    fn test_empty_records() {
        let res = records_to_service(&[]);
@@ -546,4 +611,67 @@ mod test {
            AttPermissions::READABLE | AttPermissions::WRITABLE
        );
    }

    #[test]
    fn test_multiple_descriptors() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, AttHandle(1)),
            make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
            make_descriptor_record(ANOTHER_DESCRIPTOR_UUID, AttHandle(4), 0),
        ])
        .unwrap();

        assert_eq!(service.characteristics[0].descriptors.len(), 2);
        assert_eq!(service.characteristics[0].descriptors[0].handle, AttHandle(3));
        assert_eq!(service.characteristics[0].descriptors[0].type_, DESCRIPTOR_UUID);
        assert_eq!(service.characteristics[0].descriptors[1].handle, AttHandle(4));
        assert_eq!(service.characteristics[0].descriptors[1].type_, ANOTHER_DESCRIPTOR_UUID);
    }

    #[test]
    fn test_descriptor_permissions() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, AttHandle(1)),
            make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0x01),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(4), 0x10),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(5), 0x11),
        ])
        .unwrap();

        assert_eq!(service.characteristics[0].descriptors[0].permissions, AttPermissions::READABLE);
        assert_eq!(service.characteristics[0].descriptors[1].permissions, AttPermissions::WRITABLE);
        assert_eq!(
            service.characteristics[0].descriptors[2].permissions,
            AttPermissions::READABLE | AttPermissions::WRITABLE
        );
    }

    #[test]
    fn test_descriptors_multiple_characteristics() {
        let service = records_to_service(&[
            make_service_record(SERVICE_UUID, AttHandle(1)),
            make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(2), 0),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
            make_characteristic_record(CHARACTERISTIC_UUID, AttHandle(4), 0),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(5), 0),
        ])
        .unwrap();

        assert_eq!(service.characteristics[0].descriptors.len(), 1);
        assert_eq!(service.characteristics[0].descriptors[0].handle, AttHandle(3));
        assert_eq!(service.characteristics[1].descriptors.len(), 1);
        assert_eq!(service.characteristics[1].descriptors[0].handle, AttHandle(5));
    }

    #[test]
    fn test_unexpected_descriptor() {
        let res = records_to_service(&[
            make_service_record(SERVICE_UUID, AttHandle(1)),
            make_descriptor_record(DESCRIPTOR_UUID, AttHandle(3), 0),
        ]);

        assert!(res.is_err());
    }
}
+42 −18
Original line number Diff line number Diff line
@@ -50,11 +50,10 @@ std::optional<RawAddress> AddressOfConnection(uint16_t conn_id) {
namespace bluetooth {
namespace gatt {

void GattServerCallbacks::OnServerReadCharacteristic(uint16_t conn_id,
                                                     uint32_t trans_id,
void GattServerCallbacks::OnServerRead(uint16_t conn_id, uint32_t trans_id,
                                       uint16_t attr_handle,
                                                     uint32_t offset,
                                                     bool is_long) const {
                                       AttributeBackingType attr_type,
                                       uint32_t offset, bool is_long) const {
  auto addr = AddressOfConnection(conn_id);
  if (!addr.has_value()) {
    LOG_WARN(
@@ -63,16 +62,28 @@ void GattServerCallbacks::OnServerReadCharacteristic(uint16_t conn_id,
    return;
  }

  switch (attr_type) {
    case AttributeBackingType::CHARACTERISTIC:
      do_in_jni_thread(
          FROM_HERE,
      base::Bind(callbacks.request_read_characteristic_cb, conn_id, trans_id,
          base::Bind(callbacks.request_read_characteristic_cb, conn_id,
                     trans_id, addr.value(), attr_handle, offset, is_long));
      break;
    case AttributeBackingType::DESCRIPTOR:
      do_in_jni_thread(
          FROM_HERE,
          base::Bind(callbacks.request_read_descriptor_cb, conn_id, trans_id,
                     addr.value(), attr_handle, offset, is_long));
      break;
    default:
      LOG_ALWAYS_FATAL("Unexpected backing type %d", attr_type);
  }
}

void GattServerCallbacks::OnServerWriteCharacteristic(
    uint16_t conn_id, uint32_t trans_id, uint16_t attr_handle, uint32_t offset,
    bool need_response, bool is_prepare,
    ::rust::Slice<const uint8_t> value) const {
void GattServerCallbacks::OnServerWrite(
    uint16_t conn_id, uint32_t trans_id, uint16_t attr_handle,
    AttributeBackingType attr_type, uint32_t offset, bool need_response,
    bool is_prepare, ::rust::Slice<const uint8_t> value) const {
  auto addr = AddressOfConnection(conn_id);
  if (!addr.has_value()) {
    LOG_WARN(
@@ -84,11 +95,24 @@ void GattServerCallbacks::OnServerWriteCharacteristic(
  auto buf = new uint8_t[value.size()];
  std::copy(value.begin(), value.end(), buf);

  switch (attr_type) {
    case AttributeBackingType::CHARACTERISTIC:
      do_in_jni_thread(
          FROM_HERE,
      base::Bind(callbacks.request_write_characteristic_cb, conn_id, trans_id,
                 addr.value(), attr_handle, offset, need_response, is_prepare,
                 base::Owned(buf), value.size()));
          base::Bind(callbacks.request_write_characteristic_cb, conn_id,
                     trans_id, addr.value(), attr_handle, offset, need_response,
                     is_prepare, base::Owned(buf), value.size()));
      break;
    case AttributeBackingType::DESCRIPTOR:
      do_in_jni_thread(
          FROM_HERE,
          base::Bind(callbacks.request_write_descriptor_cb, conn_id, trans_id,
                     addr.value(), attr_handle, offset, need_response,
                     is_prepare, base::Owned(buf), value.size()));
      break;
    default:
      LOG_ALWAYS_FATAL("Unexpected backing type %hhu", attr_type);
  }
}

void GattServerCallbacks::OnIndicationSentConfirmation(uint16_t conn_id,
+16 −7
Original line number Diff line number Diff line
@@ -25,17 +25,26 @@
namespace bluetooth {
namespace gatt {

/// The GATT entity backing the value of a user-controlled
/// attribute
enum class AttributeBackingType {
  /// A GATT characteristic
  CHARACTERISTIC,
  /// A GATT descriptor
  DESCRIPTOR,
};

class GattServerCallbacks {
 public:
  GattServerCallbacks(const btgatt_server_callbacks_t& callbacks)
      : callbacks(callbacks){};

  void OnServerReadCharacteristic(uint16_t conn_id, uint32_t trans_id,
                                  uint16_t attr_handle, uint32_t offset,
  void OnServerRead(uint16_t conn_id, uint32_t trans_id, uint16_t attr_handle,
                    AttributeBackingType attr_type, uint32_t offset,
                    bool is_long) const;

  void OnServerWriteCharacteristic(uint16_t conn_id, uint32_t trans_id,
                                   uint16_t attr_handle, uint32_t offset,
  void OnServerWrite(uint16_t conn_id, uint32_t trans_id, uint16_t attr_handle,
                     AttributeBackingType attr_type, uint32_t offset,
                     bool need_response, bool is_prepare,
                     ::rust::Slice<const uint8_t> value) const;

Loading