summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Rahul Arya <aryarahul@google.com> 2023-03-08 03:22:38 +0000
committer Rahul Arya <aryarahul@google.com> 2023-03-09 01:26:28 +0000
commite8b68ea94aeb440e63c255bab747c3dcc6565276 (patch)
tree385846a4b814aff86e1f93fbd28a1eb2cfb4ebd3
parent26c1bc3fa4def19ddaa87ae90756991dd7289fd3 (diff)
[Private GATT] Add support for descriptors
Bug: 255880936 Test: unit Change-Id: I62a0f715cbf27e5a895dcdb39cd9f06233fb19ea
-rw-r--r--system/rust/src/gatt/callbacks.rs21
-rw-r--r--system/rust/src/gatt/callbacks/callback_transaction_manager.rs13
-rw-r--r--system/rust/src/gatt/ffi.rs168
-rw-r--r--system/rust/src/gatt/ffi/gatt_shim.cc60
-rw-r--r--system/rust/src/gatt/ffi/gatt_shim.h23
-rw-r--r--system/rust/src/gatt/mocks/mock_callbacks.rs19
-rw-r--r--system/rust/src/gatt/mocks/mock_datastore.rs18
-rw-r--r--system/rust/src/gatt/server/att_server_bearer.rs5
-rw-r--r--system/rust/src/gatt/server/gatt_database.rs273
-rw-r--r--system/rust/src/gatt/server/transactions/helpers/payload_accumulator.rs7
-rw-r--r--system/rust/tests/gatt_callbacks_test.rs38
-rw-r--r--system/rust/tests/gatt_server_test.rs162
12 files changed, 635 insertions, 172 deletions
diff --git a/system/rust/src/gatt/callbacks.rs b/system/rust/src/gatt/callbacks.rs
index 3d969122a9..d60bd5a0c8 100644
--- a/system/rust/src/gatt/callbacks.rs
+++ b/system/rust/src/gatt/callbacks.rs
@@ -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>;
}
diff --git a/system/rust/src/gatt/callbacks/callback_transaction_manager.rs b/system/rust/src/gatt/callbacks/callback_transaction_manager.rs
index 0406a1e000..da50be9bd4 100644
--- a/system/rust/src/gatt/callbacks/callback_transaction_manager.rs
+++ b/system/rust/src/gatt/callbacks/callback_transaction_manager.rs
@@ -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
diff --git a/system/rust/src/gatt/ffi.rs b/system/rust/src/gatt/ffi.rs
index cb93597aec..32dbb6c8c2 100644
--- a/system/rust/src/gatt/ffi.rs
+++ b/system/rust/src/gatt/ffi.rs
@@ -1,6 +1,8 @@
//! 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 {
- handle: AttHandle(record.attribute_handle),
- type_: record.uuid,
- permissions: AttPermissions::from_bits_truncate(record.properties),
- }),
+ 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());
+ }
}
diff --git a/system/rust/src/gatt/ffi/gatt_shim.cc b/system/rust/src/gatt/ffi/gatt_shim.cc
index 7949195f04..3957188844 100644
--- a/system/rust/src/gatt/ffi/gatt_shim.cc
+++ b/system/rust/src/gatt/ffi/gatt_shim.cc
@@ -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,
- uint16_t attr_handle,
- uint32_t offset,
- bool is_long) const {
+void GattServerCallbacks::OnServerRead(uint16_t conn_id, uint32_t trans_id,
+ uint16_t attr_handle,
+ 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;
}
- do_in_jni_thread(
- FROM_HERE,
- base::Bind(callbacks.request_read_characteristic_cb, conn_id, trans_id,
- addr.value(), attr_handle, offset, is_long));
+ switch (attr_type) {
+ case AttributeBackingType::CHARACTERISTIC:
+ do_in_jni_thread(
+ FROM_HERE,
+ 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);
- 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()));
+ 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()));
+ 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,
diff --git a/system/rust/src/gatt/ffi/gatt_shim.h b/system/rust/src/gatt/ffi/gatt_shim.h
index 1457cc3a03..5c42ee2c02 100644
--- a/system/rust/src/gatt/ffi/gatt_shim.h
+++ b/system/rust/src/gatt/ffi/gatt_shim.h
@@ -25,19 +25,28 @@
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,
- bool is_long) const;
+ 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,
- bool need_response, bool is_prepare,
- ::rust::Slice<const uint8_t> value) const;
+ 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;
void OnIndicationSentConfirmation(uint16_t conn_id, int status) const;
diff --git a/system/rust/src/gatt/mocks/mock_callbacks.rs b/system/rust/src/gatt/mocks/mock_callbacks.rs
index efc44063c8..3c05ad2c9c 100644
--- a/system/rust/src/gatt/mocks/mock_callbacks.rs
+++ b/system/rust/src/gatt/mocks/mock_callbacks.rs
@@ -2,6 +2,7 @@
use crate::{
gatt::{
+ ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId, TransactionId},
server::IndicationError,
GattCallbacks,
@@ -25,12 +26,13 @@ impl MockCallbacks {
#[derive(Debug)]
pub enum MockCallbackEvents {
/// GattCallbacks#on_server_read_characteristic invoked
- OnServerReadCharacteristic(ConnectionId, TransactionId, AttHandle, u32, bool),
+ OnServerRead(ConnectionId, TransactionId, AttHandle, AttributeBackingType, u32, bool),
/// GattCallbacks#on_server_write_characteristic invoked
- OnServerWriteCharacteristic(
+ OnServerWrite(
ConnectionId,
TransactionId,
AttHandle,
+ AttributeBackingType,
u32,
bool,
bool,
@@ -41,36 +43,39 @@ pub enum MockCallbackEvents {
}
impl GattCallbacks for MockCallbacks {
- 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
- .send(MockCallbackEvents::OnServerReadCharacteristic(
- conn_id, trans_id, handle, offset, is_long,
+ .send(MockCallbackEvents::OnServerRead(
+ conn_id, trans_id, handle, attr_type, offset, is_long,
))
.unwrap();
}
- 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
- .send(MockCallbackEvents::OnServerWriteCharacteristic(
+ .send(MockCallbackEvents::OnServerWrite(
conn_id,
trans_id,
handle,
+ attr_type,
offset,
need_response,
is_prepare,
diff --git a/system/rust/src/gatt/mocks/mock_datastore.rs b/system/rust/src/gatt/mocks/mock_datastore.rs
index 7e7eac2465..6a1fc27dd5 100644
--- a/system/rust/src/gatt/mocks/mock_datastore.rs
+++ b/system/rust/src/gatt/mocks/mock_datastore.rs
@@ -3,6 +3,7 @@
use crate::{
gatt::{
callbacks::GattDatastore,
+ ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId},
},
packets::{
@@ -37,16 +38,18 @@ pub enum MockDatastoreEvents {
RemoveConnection(ConnectionId),
/// A characteristic was read on a given handle. The oneshot is used to
/// return the value read.
- ReadCharacteristic(
+ Read(
ConnectionId,
AttHandle,
+ AttributeBackingType,
oneshot::Sender<Result<AttAttributeDataChild, AttErrorCode>>,
),
/// A characteristic was written to on a given handle. The oneshot is used
/// to return whether the write succeeded.
- WriteCharacteristic(
+ Write(
ConnectionId,
AttHandle,
+ AttributeBackingType,
OwnedAttAttributeDataView,
oneshot::Sender<Result<(), AttErrorCode>>,
),
@@ -62,29 +65,32 @@ impl GattDatastore for MockDatastore {
self.0.send(MockDatastoreEvents::RemoveConnection(conn_id)).unwrap();
}
- async fn read_characteristic(
+ async fn read(
&self,
conn_id: ConnectionId,
handle: AttHandle,
+ attr_type: AttributeBackingType,
) -> Result<AttAttributeDataChild, AttErrorCode> {
let (tx, rx) = oneshot::channel();
- self.0.send(MockDatastoreEvents::ReadCharacteristic(conn_id, handle, tx)).unwrap();
+ self.0.send(MockDatastoreEvents::Read(conn_id, handle, attr_type, tx)).unwrap();
let resp = rx.await.unwrap();
info!("sending {resp:?} down from upper tester");
resp
}
- async fn write_characteristic(
+ async fn write(
&self,
conn_id: ConnectionId,
handle: AttHandle,
+ attr_type: AttributeBackingType,
data: AttAttributeDataView<'_>,
) -> Result<(), AttErrorCode> {
let (tx, rx) = oneshot::channel();
self.0
- .send(MockDatastoreEvents::WriteCharacteristic(
+ .send(MockDatastoreEvents::Write(
conn_id,
handle,
+ attr_type,
data.to_owned_packet(),
tx,
))
diff --git a/system/rust/src/gatt/server/att_server_bearer.rs b/system/rust/src/gatt/server/att_server_bearer.rs
index 02ade701a2..4534cc54a7 100644
--- a/system/rust/src/gatt/server/att_server_bearer.rs
+++ b/system/rust/src/gatt/server/att_server_bearer.rs
@@ -203,6 +203,7 @@ mod test {
core::{shared_box::SharedBox, uuid::Uuid},
gatt::{
callbacks::GattDatastore,
+ ffi::AttributeBackingType,
ids::ConnectionId,
mocks::mock_datastore::{MockDatastore, MockDatastoreEvents},
server::{
@@ -314,11 +315,13 @@ mod test {
handle: VALID_HANDLE,
type_: Uuid::new(2),
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
},
GattCharacteristicWithHandle {
handle: ANOTHER_VALID_HANDLE,
type_: Uuid::new(2),
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
},
],
})
@@ -344,7 +347,7 @@ mod test {
});
conn.as_ref().handle_packet(req2.view());
// handle first reply
- let MockDatastoreEvents::ReadCharacteristic(CONN_ID, VALID_HANDLE, data_resp) =
+ let MockDatastoreEvents::Read(CONN_ID, VALID_HANDLE, AttributeBackingType::Characteristic, data_resp) =
data_rx.recv().await.unwrap() else {
unreachable!();
};
diff --git a/system/rust/src/gatt/server/gatt_database.rs b/system/rust/src/gatt/server/gatt_database.rs
index 619672a20b..f1f448d12e 100644
--- a/system/rust/src/gatt/server/gatt_database.rs
+++ b/system/rust/src/gatt/server/gatt_database.rs
@@ -4,8 +4,9 @@
use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
-use anyhow::{anyhow, bail, Result};
+use anyhow::{bail, Result};
use async_trait::async_trait;
+use log::error;
use crate::{
core::{
@@ -14,6 +15,7 @@ use crate::{
},
gatt::{
callbacks::GattDatastore,
+ ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId},
},
packets::{
@@ -55,6 +57,22 @@ pub struct GattCharacteristicWithHandle {
pub type_: Uuid,
/// The permissions (read/write) indicate what operations can be performed.
pub permissions: AttPermissions,
+ /// The descriptors associated with this characteristic
+ pub descriptors: Vec<GattDescriptorWithHandle>,
+}
+
+/// A GattDescriptor consists of a handle, type_, and permissions (similar to a
+/// GattCharacteristic) It is guaranteed that the handle of the GattDescriptor
+/// is after the handle of the characteristic value attribute, and before the
+/// next characteristic/service declaration
+#[derive(Debug, Clone)]
+pub struct GattDescriptorWithHandle {
+ /// The handle of the descriptor.
+ pub handle: AttHandle,
+ /// The UUID representing the type of the descriptor.
+ pub type_: Uuid,
+ /// The permissions (read/write) indicate what operations can be performed.
+ pub permissions: AttPermissions,
}
/// The GattDatabase implements AttDatabase, and converts attribute reads/writes
@@ -67,14 +85,14 @@ pub struct GattDatabase<T: ?Sized> {
#[derive(Default)]
struct GattDatabaseSchema {
- services: Vec<GattServiceWithHandle>,
attributes: BTreeMap<AttHandle, AttAttributeWithBackingValue>,
}
#[derive(Clone)]
enum AttAttributeBackingValue {
Static(AttAttributeDataChild),
- Dynamic,
+ DynamicCharacteristic,
+ DynamicDescriptor,
}
#[derive(Clone)]
@@ -120,11 +138,7 @@ impl<T: GattDatastore + ?Sized> GattDatabase<T> {
// characteristics
for characteristic in service.characteristics {
- characteristics.push(GattCharacteristicWithHandle {
- handle: characteristic.handle,
- type_: characteristic.type_,
- permissions: characteristic.permissions,
- });
+ characteristics.push(characteristic.clone());
// declaration
// Recall that we assume the declaration handle is one less than the value
@@ -163,8 +177,20 @@ impl<T: GattDatastore + ?Sized> GattDatabase<T> {
type_: characteristic.type_,
permissions: characteristic.permissions,
},
- AttAttributeBackingValue::Dynamic,
+ AttAttributeBackingValue::DynamicCharacteristic,
);
+
+ // descriptors
+ for descriptor in characteristic.descriptors {
+ add_attribute(
+ AttAttribute {
+ handle: descriptor.handle,
+ type_: descriptor.type_,
+ permissions: descriptor.permissions,
+ },
+ AttAttributeBackingValue::DynamicDescriptor,
+ );
+ }
}
// validate attributes for overlap
@@ -180,9 +206,6 @@ impl<T: GattDatastore + ?Sized> GattDatabase<T> {
}
// if we made it here, we successfully loaded the new service
- let service =
- GattServiceWithHandle { handle: service.handle, type_: service.type_, characteristics };
- static_data.services.push(service);
static_data.attributes.extend(attributes.into_iter());
Ok(())
}
@@ -191,16 +214,6 @@ impl<T: GattDatastore + ?Sized> GattDatabase<T> {
pub fn remove_service_at_handle(&self, service_handle: AttHandle) -> Result<()> {
let mut static_data = self.schema.borrow_mut();
- // remove old service
- static_data
- .services
- .iter()
- .position(|service| service.handle == service_handle)
- .map(|index| static_data.services.remove(index))
- .ok_or_else(|| {
- anyhow!("service at handle {service_handle:?} not found, cannot remove")
- })?;
-
// find next service
let next_service_handle = static_data
.attributes
@@ -244,7 +257,7 @@ where
&self,
handle: AttHandle,
) -> Result<AttAttributeDataChild, AttErrorCode> {
- let value = self.gatt_db.with(|gatt_db| {
+ let (value, datastore) = self.gatt_db.with(|gatt_db| {
let Some(gatt_db) = gatt_db else {
// db must have been closed
return Err(AttErrorCode::INVALID_HANDLE);
@@ -256,21 +269,16 @@ where
if !attr.attribute.permissions.readable() {
return Err(AttErrorCode::READ_NOT_PERMITTED);
}
- Ok(attr.value.clone())
+ Ok((attr.value.clone(), gatt_db.datastore.clone()))
})?;
match value {
AttAttributeBackingValue::Static(val) => return Ok(val),
- AttAttributeBackingValue::Dynamic => {
- self.gatt_db
- .with(|gatt_db| {
- gatt_db
- .expect("no await points since last access, must be non-null")
- .datastore
- .clone()
- })
- .read_characteristic(self.conn_id, handle)
- .await
+ AttAttributeBackingValue::DynamicCharacteristic => {
+ datastore.read(self.conn_id, handle, AttributeBackingType::Characteristic).await
+ }
+ AttAttributeBackingValue::DynamicDescriptor => {
+ datastore.read(self.conn_id, handle, AttributeBackingType::Descriptor).await
}
}
}
@@ -280,7 +288,7 @@ where
handle: AttHandle,
data: AttAttributeDataView<'_>,
) -> Result<(), AttErrorCode> {
- let datastore = self.gatt_db.with(|gatt_db| {
+ let (value, datastore) = self.gatt_db.with(|gatt_db| {
let Some(gatt_db) = gatt_db else {
// db must have been closed
return Err(AttErrorCode::INVALID_HANDLE);
@@ -292,16 +300,29 @@ where
if !attr.attribute.permissions.writable() {
return Err(AttErrorCode::WRITE_NOT_PERMITTED);
}
- Ok(gatt_db.datastore.clone())
+ Ok((attr.value.clone(), gatt_db.datastore.clone()))
})?;
- datastore.write_characteristic(self.conn_id, handle, data).await
+ match value {
+ AttAttributeBackingValue::Static(val) => {
+ error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
+ return Err(AttErrorCode::WRITE_NOT_PERMITTED);
+ }
+ AttAttributeBackingValue::DynamicCharacteristic => {
+ datastore
+ .write(self.conn_id, handle, AttributeBackingType::Characteristic, data)
+ .await
+ }
+ AttAttributeBackingValue::DynamicDescriptor => {
+ datastore.write(self.conn_id, handle, AttributeBackingType::Descriptor, data).await
+ }
+ }
}
fn list_attributes(&self) -> Vec<AttAttribute> {
self.gatt_db.with(|db| {
db.map(|db| db.schema.borrow().attributes.values().map(|attr| attr.attribute).collect())
- .unwrap_or(vec![])
+ .unwrap_or_default()
})
}
}
@@ -334,6 +355,9 @@ mod test {
const CHARACTERISTIC_VALUE_HANDLE: AttHandle = AttHandle(3);
const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x5678);
+ const DESCRIPTOR_HANDLE: AttHandle = AttHandle(4);
+ const DESCRIPTOR_TYPE: Uuid = Uuid::new(0x9ABC);
+
const CONN_ID: ConnectionId = ConnectionId(1);
#[test]
@@ -393,6 +417,7 @@ mod test {
handle: AttHandle(3),
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -404,6 +429,7 @@ mod test {
handle: AttHandle(6),
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -415,6 +441,7 @@ mod test {
handle: AttHandle(9),
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -461,6 +488,7 @@ mod test {
permissions: AttPermissions::READABLE
| AttPermissions::WRITABLE
| AttPermissions::INDICATE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -525,6 +553,7 @@ mod test {
handle: CHARACTERISTIC_VALUE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -535,9 +564,10 @@ mod test {
let characteristic_value = tokio_test::block_on(async {
join!(
async {
- let MockDatastoreEvents::ReadCharacteristic(
+ let MockDatastoreEvents::Read(
CONN_ID,
CHARACTERISTIC_VALUE_HANDLE,
+ AttributeBackingType::Characteristic,
reply,
) = data_evts.recv().await.unwrap() else {
unreachable!()
@@ -565,6 +595,7 @@ mod test {
handle: CHARACTERISTIC_VALUE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::empty(),
+ descriptors: vec![],
}],
})
.unwrap();
@@ -588,6 +619,7 @@ mod test {
handle: SERVICE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::WRITABLE,
+ descriptors: vec![],
}],
});
@@ -629,6 +661,7 @@ mod test {
handle: CHARACTERISTIC_VALUE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::WRITABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -647,9 +680,10 @@ mod test {
.unwrap();
});
- let MockDatastoreEvents::WriteCharacteristic(
+ let MockDatastoreEvents::Write(
CONN_ID,
CHARACTERISTIC_VALUE_HANDLE,
+ AttributeBackingType::Characteristic,
recv_data,
_,
) = data_evts.recv().await.unwrap() else {
@@ -678,6 +712,7 @@ mod test {
handle: CHARACTERISTIC_VALUE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::WRITABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -689,7 +724,7 @@ mod test {
let res = tokio_test::block_on(async {
join!(
async {
- let MockDatastoreEvents::WriteCharacteristic(_,_,_,reply) = data_evts.recv().await.unwrap() else {
+ let MockDatastoreEvents::Write(_,_,_,_,reply) = data_evts.recv().await.unwrap() else {
unreachable!();
};
reply.send(Err(AttErrorCode::UNLIKELY_ERROR)).unwrap();
@@ -715,6 +750,7 @@ mod test {
handle: CHARACTERISTIC_VALUE_HANDLE,
type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE,
+ descriptors: vec![],
}],
})
.unwrap();
@@ -729,4 +765,159 @@ mod test {
assert_eq!(characteristic_value, Err(AttErrorCode::WRITE_NOT_PERMITTED));
}
+
+ #[test]
+ fn test_single_descriptor_declaration() {
+ let (gatt_datastore, mut data_evts) = MockDatastore::new();
+ let gatt_db = SharedBox::new(GattDatabase::new(gatt_datastore.into()));
+ gatt_db
+ .add_service_with_handles(GattServiceWithHandle {
+ handle: SERVICE_HANDLE,
+ type_: SERVICE_TYPE,
+ characteristics: vec![GattCharacteristicWithHandle {
+ handle: CHARACTERISTIC_VALUE_HANDLE,
+ type_: CHARACTERISTIC_TYPE,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![GattDescriptorWithHandle {
+ handle: DESCRIPTOR_HANDLE,
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::READABLE,
+ }],
+ }],
+ })
+ .unwrap();
+ let att_db = gatt_db.get_att_database(CONN_ID);
+ let data = AttAttributeDataChild::RawData(Box::new([1, 2]));
+
+ let descriptor_value = block_on_locally(async {
+ // start write task
+ let pending_read =
+ spawn_local(async move { att_db.read_attribute(DESCRIPTOR_HANDLE).await.unwrap() });
+
+ let MockDatastoreEvents::Read(
+ CONN_ID,
+ DESCRIPTOR_HANDLE,
+ AttributeBackingType::Descriptor,
+ reply,
+ ) = data_evts.recv().await.unwrap() else {
+ unreachable!();
+ };
+
+ reply.send(Ok(data.clone())).unwrap();
+
+ pending_read.await.unwrap()
+ });
+
+ assert_eq!(descriptor_value, data);
+ }
+
+ #[test]
+ fn test_write_descriptor() {
+ // arrange: db with a writable descriptor
+ let (gatt_datastore, mut data_evts) = MockDatastore::new();
+ let gatt_db = SharedBox::new(GattDatabase::new(gatt_datastore.into()));
+ gatt_db
+ .add_service_with_handles(GattServiceWithHandle {
+ handle: SERVICE_HANDLE,
+ type_: SERVICE_TYPE,
+ characteristics: vec![GattCharacteristicWithHandle {
+ handle: CHARACTERISTIC_VALUE_HANDLE,
+ type_: CHARACTERISTIC_TYPE,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![GattDescriptorWithHandle {
+ handle: DESCRIPTOR_HANDLE,
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::WRITABLE,
+ }],
+ }],
+ })
+ .unwrap();
+ let att_db = gatt_db.get_att_database(CONN_ID);
+ let data =
+ build_view_or_crash(build_att_data(AttAttributeDataChild::RawData(Box::new([1, 2]))));
+
+ // act: write, and wait for the callback to be invoked
+ block_on_locally(async {
+ // start write task
+ spawn_local(async move {
+ att_db.write_attribute(DESCRIPTOR_HANDLE, data.view()).await.unwrap()
+ });
+
+ let MockDatastoreEvents::Write(
+ CONN_ID,
+ DESCRIPTOR_HANDLE,
+ AttributeBackingType::Descriptor,
+ _,
+ _,
+ ) = data_evts.recv().await.unwrap() else {
+ unreachable!();
+ };
+ });
+
+ // assert: nothing, if we reach this far we are OK
+ }
+
+ #[test]
+ fn test_multiple_descriptors() {
+ // arrange: a database with some characteristics and descriptors
+ let (gatt_datastore, _) = MockDatastore::new();
+ let gatt_db = SharedBox::new(GattDatabase::new(gatt_datastore.into()));
+ gatt_db
+ .add_service_with_handles(GattServiceWithHandle {
+ handle: AttHandle(1),
+ type_: SERVICE_TYPE,
+ characteristics: vec![
+ GattCharacteristicWithHandle {
+ handle: AttHandle(3),
+ type_: CHARACTERISTIC_TYPE,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![GattDescriptorWithHandle {
+ handle: AttHandle(4),
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::READABLE,
+ }],
+ },
+ GattCharacteristicWithHandle {
+ handle: AttHandle(6),
+ type_: CHARACTERISTIC_TYPE,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![
+ GattDescriptorWithHandle {
+ handle: AttHandle(7),
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::WRITABLE,
+ },
+ GattDescriptorWithHandle {
+ handle: AttHandle(8),
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE,
+ },
+ ],
+ },
+ ],
+ })
+ .unwrap();
+
+ // act: get the attributes
+ let attributes = gatt_db.get_att_database(CONN_ID).list_attributes();
+
+ // assert: check the attributes are in the correct order
+ assert_eq!(attributes.len(), 8);
+ assert_eq!(attributes[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
+ assert_eq!(attributes[1].type_, CHARACTERISTIC_UUID);
+ assert_eq!(attributes[2].type_, CHARACTERISTIC_TYPE);
+ assert_eq!(attributes[3].type_, DESCRIPTOR_TYPE);
+ assert_eq!(attributes[4].type_, CHARACTERISTIC_UUID);
+ assert_eq!(attributes[5].type_, CHARACTERISTIC_TYPE);
+ assert_eq!(attributes[6].type_, DESCRIPTOR_TYPE);
+ assert_eq!(attributes[7].type_, DESCRIPTOR_TYPE);
+ // assert: check the handles of the descriptors are correct
+ assert_eq!(attributes[3].handle, AttHandle(4));
+ assert_eq!(attributes[6].handle, AttHandle(7));
+ assert_eq!(attributes[7].handle, AttHandle(8));
+ // assert: check the permissions of the descriptors are correct
+ assert_eq!(attributes[3].permissions, AttPermissions::READABLE);
+ assert_eq!(attributes[6].permissions, AttPermissions::WRITABLE);
+ assert_eq!(attributes[7].permissions, AttPermissions::READABLE | AttPermissions::WRITABLE);
+ }
}
diff --git a/system/rust/src/gatt/server/transactions/helpers/payload_accumulator.rs b/system/rust/src/gatt/server/transactions/helpers/payload_accumulator.rs
index 40a30f0de5..72aa46b367 100644
--- a/system/rust/src/gatt/server/transactions/helpers/payload_accumulator.rs
+++ b/system/rust/src/gatt/server/transactions/helpers/payload_accumulator.rs
@@ -11,6 +11,7 @@ impl<T: Builder> PayloadAccumulator<T> {
Self { curr: 0, lim: size * 8, elems: vec![] }
}
+ #[must_use]
pub fn push(&mut self, builder: T) -> bool {
// if serialization fails we WANT to continue, to get a clean SerializeError at
// the end
@@ -47,11 +48,12 @@ mod test {
fn test_nonempty() {
let mut accumulator = PayloadAccumulator::new(128);
- accumulator.push(AttBuilder {
+ let ok = accumulator.push(AttBuilder {
opcode: AttOpcode::WRITE_RESPONSE,
_child_: AttChild::RawData([1, 2].into()),
});
+ assert!(ok);
assert!(!accumulator.is_empty())
}
@@ -59,11 +61,12 @@ mod test {
fn test_push_serialize() {
let mut accumulator = PayloadAccumulator::new(128);
- accumulator.push(AttBuilder {
+ let ok = accumulator.push(AttBuilder {
opcode: AttOpcode::WRITE_RESPONSE,
_child_: AttChild::RawData([1, 2].into()),
});
+ assert!(ok);
assert_eq!(
accumulator.into_boxed_slice().as_ref(),
[AttBuilder {
diff --git a/system/rust/tests/gatt_callbacks_test.rs b/system/rust/tests/gatt_callbacks_test.rs
index 0fde0db941..60e0b6c61b 100644
--- a/system/rust/tests/gatt_callbacks_test.rs
+++ b/system/rust/tests/gatt_callbacks_test.rs
@@ -5,6 +5,7 @@ use std::rc::Rc;
use bluetooth_core::{
gatt::{
callbacks::{CallbackResponseError, CallbackTransactionManager, GattDatastore},
+ ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId, ServerId, TransactionId, TransportIndex},
mocks::mock_callbacks::{MockCallbackEvents, MockCallbacks},
},
@@ -22,6 +23,7 @@ const CONN_ID: ConnectionId = ConnectionId::new(TCB_IDX, SERVER_ID);
const ANOTHER_CONN_ID: ConnectionId = ConnectionId(10);
const HANDLE_1: AttHandle = AttHandle(3);
+const BACKING_TYPE: AttributeBackingType = AttributeBackingType::Descriptor;
fn initialize_manager_with_connection(
) -> (Rc<CallbackTransactionManager>, UnboundedReceiver<MockCallbackEvents>) {
@@ -34,8 +36,8 @@ fn initialize_manager_with_connection(
async fn pull_trans_id(events_rx: &mut UnboundedReceiver<MockCallbackEvents>) -> TransactionId {
match events_rx.recv().await.unwrap() {
- MockCallbackEvents::OnServerReadCharacteristic(_, trans_id, _, _, _) => trans_id,
- MockCallbackEvents::OnServerWriteCharacteristic(_, trans_id, _, _, _, _, _) => trans_id,
+ MockCallbackEvents::OnServerRead(_, trans_id, _, _, _, _) => trans_id,
+ MockCallbackEvents::OnServerWrite(_, trans_id, _, _, _, _, _, _) => trans_id,
_ => unimplemented!(),
}
}
@@ -47,11 +49,11 @@ fn test_read_characteristic_callback() {
let (callback_manager, mut callbacks_rx) = initialize_manager_with_connection();
// act: start read operation
- spawn_local(async move { callback_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { callback_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// assert: verify the read callback is received
- let MockCallbackEvents::OnServerReadCharacteristic(
- CONN_ID, _, HANDLE_1, 0, false,
+ let MockCallbackEvents::OnServerRead(
+ CONN_ID, _, HANDLE_1, BACKING_TYPE, 0, false,
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
@@ -68,7 +70,7 @@ fn test_read_characteristic_response() {
// act: start read operation
let cloned_manager = callback_manager.clone();
let pending_read =
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// provide a response
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data.clone()).unwrap();
@@ -89,7 +91,7 @@ fn test_sequential_reads() {
// act: start read operation
let cloned_manager = callback_manager.clone();
let pending_read_1 =
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// respond to first
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data1.clone()).unwrap();
@@ -97,7 +99,7 @@ fn test_sequential_reads() {
// do a second read operation
let cloned_manager = callback_manager.clone();
let pending_read_2 =
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// respond to second
let trans_id = pull_trans_id(&mut callbacks_rx).await;
callback_manager.send_response(CONN_ID, trans_id, data2.clone()).unwrap();
@@ -119,12 +121,12 @@ fn test_concurrent_reads() {
// act: start read operation
let cloned_manager = callback_manager.clone();
let pending_read_1 =
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// do a second read operation
let cloned_manager = callback_manager.clone();
let pending_read_2 =
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// respond to first
let trans_id = pull_trans_id(&mut callbacks_rx).await;
@@ -148,9 +150,9 @@ fn test_distinct_transaction_ids() {
// act: start two read operations concurrently
let cloned_manager = callback_manager.clone();
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
let cloned_manager = callback_manager.clone();
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// pull both trans_ids
let trans_id_1 = pull_trans_id(&mut callbacks_rx).await;
@@ -170,7 +172,7 @@ fn test_invalid_conn_id() {
// act: start a read operation
let cloned_manager = callback_manager.clone();
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// respond with the correct trans_id but an invalid conn_id
let trans_id = pull_trans_id(&mut callbacks_rx).await;
let err = callback_manager.send_response(ANOTHER_CONN_ID, trans_id, data).unwrap_err();
@@ -189,7 +191,7 @@ fn test_invalid_trans_id() {
// act: start a read operation
let cloned_manager = callback_manager.clone();
- spawn_local(async move { cloned_manager.read_characteristic(CONN_ID, HANDLE_1).await });
+ spawn_local(async move { cloned_manager.read(CONN_ID, HANDLE_1, BACKING_TYPE).await });
// respond with the correct conn_id but an invalid trans_id
let trans_id = pull_trans_id(&mut callbacks_rx).await;
let invalid_trans_id = TransactionId(trans_id.0 + 1);
@@ -211,12 +213,12 @@ fn test_write_characteristic_callback() {
build_view_or_crash(build_att_data(AttAttributeDataChild::RawData([1, 2].into())));
let cloned_data = data.view().to_owned_packet();
spawn_local(async move {
- callback_manager.write_characteristic(CONN_ID, HANDLE_1, cloned_data.view()).await
+ callback_manager.write(CONN_ID, HANDLE_1, BACKING_TYPE, cloned_data.view()).await
});
// assert: verify the write callback is received
- let MockCallbackEvents::OnServerWriteCharacteristic(
- CONN_ID, _, HANDLE_1, 0, /* needs_response = */ true, false, recv_data
+ let MockCallbackEvents::OnServerWrite(
+ CONN_ID, _, HANDLE_1, BACKING_TYPE, 0, /* needs_response = */ true, false, recv_data
) = callbacks_rx.recv().await.unwrap() else {
unreachable!()
};
@@ -238,7 +240,7 @@ fn test_write_characteristic_response() {
build_view_or_crash(build_att_data(AttAttributeDataChild::RawData([1, 2].into())));
let cloned_manager = callback_manager.clone();
let pending_write = spawn_local(async move {
- cloned_manager.write_characteristic(CONN_ID, HANDLE_1, data.view()).await
+ cloned_manager.write(CONN_ID, HANDLE_1, BACKING_TYPE, data.view()).await
});
// provide a response with some error code
let trans_id = pull_trans_id(&mut callbacks_rx).await;
diff --git a/system/rust/tests/gatt_server_test.rs b/system/rust/tests/gatt_server_test.rs
index eafeec569e..2677e9d1de 100644
--- a/system/rust/tests/gatt_server_test.rs
+++ b/system/rust/tests/gatt_server_test.rs
@@ -4,13 +4,17 @@ use bluetooth_core::{
core::uuid::Uuid,
gatt::{
self,
+ ffi::AttributeBackingType,
ids::{AttHandle, ConnectionId, ServerId, TransportIndex},
mocks::{
mock_datastore::{MockDatastore, MockDatastoreEvents},
mock_transport::MockAttTransport,
},
server::{
- gatt_database::{AttPermissions, GattCharacteristicWithHandle, GattServiceWithHandle},
+ gatt_database::{
+ AttPermissions, GattCharacteristicWithHandle, GattDescriptorWithHandle,
+ GattServiceWithHandle,
+ },
GattModule, IndicationError,
},
},
@@ -31,10 +35,15 @@ mod utils;
const TCB_IDX: TransportIndex = TransportIndex(1);
const SERVER_ID: ServerId = ServerId(2);
const CONN_ID: ConnectionId = ConnectionId::new(TCB_IDX, SERVER_ID);
-const HANDLE_1: AttHandle = AttHandle(3);
-const HANDLE_2: AttHandle = AttHandle(5);
-const UUID_1: Uuid = Uuid::new(0x0102);
-const UUID_2: Uuid = Uuid::new(0x0103);
+
+const SERVICE_HANDLE: AttHandle = AttHandle(3);
+const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(5);
+const DESCRIPTOR_HANDLE: AttHandle = AttHandle(6);
+
+const SERVICE_TYPE: Uuid = Uuid::new(0x0102);
+const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x0103);
+const DESCRIPTOR_TYPE: Uuid = Uuid::new(0x0104);
+
const DATA: [u8; 4] = [1, 2, 3, 4];
fn start_gatt_module() -> (
@@ -54,14 +63,19 @@ fn create_server_and_open_connection(gatt: &mut GattModule) {
gatt.register_gatt_service(
SERVER_ID,
GattServiceWithHandle {
- handle: HANDLE_1,
- type_: UUID_1,
+ handle: SERVICE_HANDLE,
+ type_: SERVICE_TYPE,
characteristics: vec![GattCharacteristicWithHandle {
- handle: HANDLE_2,
- type_: UUID_2,
+ handle: CHARACTERISTIC_HANDLE,
+ type_: CHARACTERISTIC_TYPE,
permissions: AttPermissions::READABLE
| AttPermissions::WRITABLE
| AttPermissions::INDICATE,
+ descriptors: vec![GattDescriptorWithHandle {
+ handle: DESCRIPTOR_HANDLE,
+ type_: DESCRIPTOR_TYPE,
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE,
+ }],
}],
},
)
@@ -78,7 +92,11 @@ fn test_connection_creation() {
gatt.open_gatt_server(SERVER_ID).unwrap();
gatt.register_gatt_service(
SERVER_ID,
- GattServiceWithHandle { handle: HANDLE_1, type_: UUID_1, characteristics: vec![] },
+ GattServiceWithHandle {
+ handle: SERVICE_HANDLE,
+ type_: SERVICE_TYPE,
+ characteristics: vec![],
+ },
)
.unwrap();
@@ -101,7 +119,11 @@ fn test_disconnection() {
gatt.open_gatt_server(SERVER_ID).unwrap();
gatt.register_gatt_service(
SERVER_ID,
- GattServiceWithHandle { handle: HANDLE_1, type_: UUID_1, characteristics: vec![] },
+ GattServiceWithHandle {
+ handle: SERVICE_HANDLE,
+ type_: SERVICE_TYPE,
+ characteristics: vec![],
+ },
)
.unwrap();
gatt.on_le_connect(CONN_ID).unwrap();
@@ -129,8 +151,10 @@ fn test_service_read() {
// act
gatt.get_bearer(CONN_ID).unwrap().handle_packet(
- build_att_view_or_crash(AttReadRequestBuilder { attribute_handle: HANDLE_1.into() })
- .view(),
+ build_att_view_or_crash(AttReadRequestBuilder {
+ attribute_handle: SERVICE_HANDLE.into(),
+ })
+ .view(),
);
let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
@@ -142,7 +166,7 @@ fn test_service_read() {
opcode: AttOpcode::READ_RESPONSE,
_child_: AttReadResponseBuilder {
value: build_att_data(GattServiceDeclarationValueBuilder {
- uuid: UUID_1.into()
+ uuid: SERVICE_TYPE.into()
})
}
.into()
@@ -165,8 +189,10 @@ fn test_server_closed_while_connected() {
// act: read from the closed server
gatt.get_bearer(CONN_ID).unwrap().handle_packet(
- build_att_view_or_crash(AttReadRequestBuilder { attribute_handle: HANDLE_1.into() })
- .view(),
+ build_att_view_or_crash(AttReadRequestBuilder {
+ attribute_handle: SERVICE_HANDLE.into(),
+ })
+ .view(),
);
let (_, resp) = transport_rx.recv().await.unwrap();
@@ -176,7 +202,7 @@ fn test_server_closed_while_connected() {
resp._child_,
AttErrorResponseBuilder {
opcode_in_error: AttOpcode::READ_REQUEST,
- handle_in_error: HANDLE_1.into(),
+ handle_in_error: SERVICE_HANDLE.into(),
error_code: AttErrorCode::INVALID_HANDLE
}
.into()
@@ -197,11 +223,17 @@ fn test_characteristic_read() {
// act
gatt.get_bearer(CONN_ID).unwrap().handle_packet(
- build_att_view_or_crash(AttReadRequestBuilder { attribute_handle: HANDLE_2.into() })
- .view(),
+ build_att_view_or_crash(AttReadRequestBuilder {
+ attribute_handle: CHARACTERISTIC_HANDLE.into(),
+ })
+ .view(),
);
- let tx = if let MockDatastoreEvents::ReadCharacteristic(CONN_ID, HANDLE_2, tx) =
- data_rx.recv().await.unwrap()
+ let tx = if let MockDatastoreEvents::Read(
+ CONN_ID,
+ CHARACTERISTIC_HANDLE,
+ AttributeBackingType::Characteristic,
+ tx,
+ ) = data_rx.recv().await.unwrap()
{
tx
} else {
@@ -236,19 +268,23 @@ fn test_characteristic_write() {
// act
gatt.get_bearer(CONN_ID).unwrap().handle_packet(
build_att_view_or_crash(AttWriteRequestBuilder {
- handle: HANDLE_2.into(),
+ handle: CHARACTERISTIC_HANDLE.into(),
value: build_att_data(data.clone()),
})
.view(),
);
- let (tx, written_data) =
- if let MockDatastoreEvents::WriteCharacteristic(CONN_ID, HANDLE_2, written_data, tx) =
- data_rx.recv().await.unwrap()
- {
- (tx, written_data)
- } else {
- unreachable!()
- };
+ let (tx, written_data) = if let MockDatastoreEvents::Write(
+ CONN_ID,
+ CHARACTERISTIC_HANDLE,
+ AttributeBackingType::Characteristic,
+ written_data,
+ tx,
+ ) = data_rx.recv().await.unwrap()
+ {
+ (tx, written_data)
+ } else {
+ unreachable!()
+ };
tx.send(Ok(())).unwrap();
let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
@@ -280,8 +316,9 @@ fn test_send_indication() {
data_rx.recv().await.unwrap();
// act
- let pending_indication =
- spawn_local(gatt.get_bearer(CONN_ID).unwrap().send_indication(HANDLE_2, data.clone()));
+ let pending_indication = spawn_local(
+ gatt.get_bearer(CONN_ID).unwrap().send_indication(CHARACTERISTIC_HANDLE, data.clone()),
+ );
let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
@@ -297,7 +334,7 @@ fn test_send_indication() {
AttBuilder {
opcode: AttOpcode::HANDLE_VALUE_INDICATION,
_child_: AttHandleValueIndicationBuilder {
- handle: HANDLE_2.into(),
+ handle: CHARACTERISTIC_HANDLE.into(),
value: build_att_data(data),
}
.into()
@@ -316,11 +353,10 @@ fn test_send_indication_and_disconnect() {
data_rx.recv().await.unwrap();
// act: send an indication, then disconnect
- let pending_indication = spawn_local(
- gatt.get_bearer(CONN_ID)
- .unwrap()
- .send_indication(HANDLE_2, AttAttributeDataChild::RawData([1, 2, 3, 4].into())),
- );
+ let pending_indication = spawn_local(gatt.get_bearer(CONN_ID).unwrap().send_indication(
+ CHARACTERISTIC_HANDLE,
+ AttAttributeDataChild::RawData([1, 2, 3, 4].into()),
+ ));
transport_rx.recv().await.unwrap();
gatt.on_le_disconnect(CONN_ID);
@@ -331,3 +367,53 @@ fn test_send_indication_and_disconnect() {
));
})
}
+
+#[test]
+fn test_write_to_descriptor() {
+ start_test(async move {
+ // arrange
+ let (mut gatt, mut data_rx, mut transport_rx) = start_gatt_module();
+
+ let data = AttAttributeDataChild::RawData(DATA.into());
+
+ create_server_and_open_connection(&mut gatt);
+ data_rx.recv().await.unwrap();
+
+ // act
+ gatt.get_bearer(CONN_ID).unwrap().handle_packet(
+ build_att_view_or_crash(AttWriteRequestBuilder {
+ handle: DESCRIPTOR_HANDLE.into(),
+ value: build_att_data(data.clone()),
+ })
+ .view(),
+ );
+ let (tx, written_data) = if let MockDatastoreEvents::Write(
+ CONN_ID,
+ DESCRIPTOR_HANDLE,
+ AttributeBackingType::Descriptor,
+ written_data,
+ tx,
+ ) = data_rx.recv().await.unwrap()
+ {
+ (tx, written_data)
+ } else {
+ unreachable!()
+ };
+ tx.send(Ok(())).unwrap();
+ let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
+
+ // assert
+ assert_eq!(tcb_idx, TCB_IDX);
+ assert_eq!(
+ resp,
+ AttBuilder {
+ opcode: AttOpcode::WRITE_RESPONSE,
+ _child_: AttWriteResponseBuilder {}.into()
+ }
+ );
+ assert_eq!(
+ data.to_vec().unwrap(),
+ written_data.view().get_raw_payload().collect::<Vec<_>>()
+ )
+ })
+}