summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Rahul Arya <aryarahul@google.com> 2023-03-22 00:07:21 +0000
committer Rahul Arya <aryarahul@google.com> 2023-03-22 00:07:21 +0000
commit62fa90934c6a0c2f7992f8eec89bbd972e35e29b (patch)
tree0a0851f5f3cf7b24db96fe8fa34667dba2112a34
parent5d745396c2f021a48a670f1371a7ec4c3e9c4677 (diff)
[Private GATT] Built-in services
GATT/GAP. Implementation of GATT srv_chg indication to follow in the next CL. Bug: 255880936 Test: unit Change-Id: If344709656c3d7e46e94f2644f63f8c221bae08d
-rw-r--r--system/rust/src/gatt/server.rs6
-rw-r--r--system/rust/src/gatt/server/request_handler.rs1
-rw-r--r--system/rust/src/gatt/server/services.rs18
-rw-r--r--system/rust/src/gatt/server/services/gap.rs170
-rw-r--r--system/rust/src/gatt/server/services/gatt.rs261
-rw-r--r--system/rust/src/packets.pdl9
-rw-r--r--system/rust/tests/gatt_server_test.rs41
7 files changed, 499 insertions, 7 deletions
diff --git a/system/rust/src/gatt/server.rs b/system/rust/src/gatt/server.rs
index ec0f01b907..7b5639669c 100644
--- a/system/rust/src/gatt/server.rs
+++ b/system/rust/src/gatt/server.rs
@@ -6,6 +6,7 @@ pub mod att_server_bearer;
pub mod gatt_database;
mod indication_handler;
mod request_handler;
+pub mod services;
mod transactions;
#[cfg(test)]
@@ -22,6 +23,7 @@ use self::{
super::ids::ServerId,
att_server_bearer::AttServerBearer,
gatt_database::{AttDatabaseImpl, GattServiceWithHandle},
+ services::register_builtin_services,
};
use super::{callbacks::GattDatastore, channel::AttTransport, ids::AttHandle};
@@ -97,7 +99,9 @@ impl GattModule {
/// Open a GATT server
pub fn open_gatt_server(&mut self, server_id: ServerId) -> Result<()> {
- let old = self.databases.insert(server_id, GattDatabase::new().into());
+ let mut db = GattDatabase::new();
+ register_builtin_services(&mut db)?;
+ let old = self.databases.insert(server_id, db.into());
if old.is_some() {
bail!("GATT server {server_id:?} already exists but was re-opened, clobbering old value...")
}
diff --git a/system/rust/src/gatt/server/request_handler.rs b/system/rust/src/gatt/server/request_handler.rs
index 8040fd3aec..e6890803d0 100644
--- a/system/rust/src/gatt/server/request_handler.rs
+++ b/system/rust/src/gatt/server/request_handler.rs
@@ -40,6 +40,7 @@ impl<Db: AttDatabase> AttRequestHandler<Db> {
Ok(result) => result,
Err(_) => {
// parse error, assume it's an unsupported request
+ // TODO(aryarahul): distinguish between REQUEST_NOT_SUPPORTED and INVALID_PDU
AttErrorResponseBuilder {
opcode_in_error: packet.get_opcode(),
handle_in_error: AttHandle(0).into(),
diff --git a/system/rust/src/gatt/server/services.rs b/system/rust/src/gatt/server/services.rs
new file mode 100644
index 0000000000..a97db3a441
--- /dev/null
+++ b/system/rust/src/gatt/server/services.rs
@@ -0,0 +1,18 @@
+//! This module initializes the built-in services included in every
+//! GATT server.
+
+pub mod gap;
+pub mod gatt;
+
+use anyhow::Result;
+
+use self::{gap::register_gap_service, gatt::register_gatt_service};
+
+use super::gatt_database::GattDatabase;
+
+/// Register all built-in services with the provided database
+pub fn register_builtin_services(database: &mut GattDatabase) -> Result<()> {
+ register_gap_service(database)?;
+ register_gatt_service(database)?;
+ Ok(())
+}
diff --git a/system/rust/src/gatt/server/services/gap.rs b/system/rust/src/gatt/server/services/gap.rs
new file mode 100644
index 0000000000..7538e45c8c
--- /dev/null
+++ b/system/rust/src/gatt/server/services/gap.rs
@@ -0,0 +1,170 @@
+//! The GAP service as defined in Core Spec 5.3 Vol 3C Section 12
+
+use std::rc::Rc;
+
+use anyhow::Result;
+use async_trait::async_trait;
+
+use crate::{
+ core::uuid::Uuid,
+ gatt::{
+ callbacks::GattDatastore,
+ ffi::AttributeBackingType,
+ ids::{AttHandle, ConnectionId},
+ server::gatt_database::{
+ AttPermissions, GattCharacteristicWithHandle, GattDatabase, GattServiceWithHandle,
+ },
+ },
+ packets::{AttAttributeDataChild, AttAttributeDataView, AttErrorCode},
+};
+
+struct GapService;
+
+// Must lie in the range specified by GATT_GAP_START_HANDLE from legacy stack
+const GAP_SERVICE_HANDLE: AttHandle = AttHandle(20);
+const DEVICE_NAME_HANDLE: AttHandle = AttHandle(22);
+const DEVICE_APPEARANCE_HANDLE: AttHandle = AttHandle(24);
+
+/// The UUID used for the GAP service (Assigned Numbers 3.4.1 Services by Name)
+pub const GAP_SERVICE_UUID: Uuid = Uuid::new(0x1800);
+/// The UUID used for the Device Name characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
+pub const DEVICE_NAME_UUID: Uuid = Uuid::new(0x2A00);
+/// The UUID used for the Device Appearance characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
+pub const DEVICE_APPEARANCE_UUID: Uuid = Uuid::new(0x2A01);
+
+#[async_trait(?Send)]
+impl GattDatastore for GapService {
+ async fn read(
+ &self,
+ _: ConnectionId,
+ handle: AttHandle,
+ _: AttributeBackingType,
+ ) -> Result<AttAttributeDataChild, AttErrorCode> {
+ match handle {
+ DEVICE_NAME_HANDLE => {
+ // for non-bonded peers, don't let them read the device name
+ // TODO(aryarahul): support discoverability, when we make this the main GATT server
+ Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION)
+ }
+ // 0x0000 from AssignedNumbers => "Unknown"
+ DEVICE_APPEARANCE_HANDLE => Ok(AttAttributeDataChild::RawData([0x00, 0x00].into())),
+ _ => unreachable!("unexpected handle read"),
+ }
+ }
+
+ async fn write(
+ &self,
+ _: ConnectionId,
+ _: AttHandle,
+ _: AttributeBackingType,
+ _: AttAttributeDataView<'_>,
+ ) -> Result<(), AttErrorCode> {
+ unreachable!("no GAP data should be writable")
+ }
+}
+
+/// Register the GAP service in the provided GATT database.
+pub fn register_gap_service(database: &mut GattDatabase) -> Result<()> {
+ database.add_service_with_handles(
+ // GAP Service
+ GattServiceWithHandle {
+ handle: GAP_SERVICE_HANDLE,
+ type_: GAP_SERVICE_UUID,
+ // Device Name
+ characteristics: vec![
+ GattCharacteristicWithHandle {
+ handle: DEVICE_NAME_HANDLE,
+ type_: DEVICE_NAME_UUID,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![],
+ },
+ // Appearance
+ GattCharacteristicWithHandle {
+ handle: DEVICE_APPEARANCE_HANDLE,
+ type_: DEVICE_APPEARANCE_UUID,
+ permissions: AttPermissions::READABLE,
+ descriptors: vec![],
+ },
+ ],
+ },
+ Rc::new(GapService),
+ )
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use crate::{
+ core::shared_box::SharedBox,
+ gatt::{
+ ids::ConnectionId,
+ server::{
+ att_database::{
+ AttDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
+ },
+ gatt_database::GattDatabase,
+ },
+ },
+ utils::task::block_on_locally,
+ };
+
+ const CONN_ID: ConnectionId = ConnectionId(1);
+
+ fn init_dbs() -> (SharedBox<GattDatabase>, impl AttDatabase) {
+ let mut gatt_database = GattDatabase::new();
+ register_gap_service(&mut gatt_database).unwrap();
+ let gatt_database = SharedBox::new(gatt_database);
+ let att_database = gatt_database.get_att_database(CONN_ID);
+ (gatt_database, att_database)
+ }
+
+ #[test]
+ fn test_gap_service_discovery() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: discover all services
+ let attrs = att_db.list_attributes();
+
+ // assert: 1 service + (2 characteristics) * (declaration + value attrs) = 5 attrs
+ assert_eq!(attrs.len(), 5);
+ // assert: value handles are correct
+ assert_eq!(attrs[0].handle, GAP_SERVICE_HANDLE);
+ assert_eq!(attrs[2].handle, DEVICE_NAME_HANDLE);
+ assert_eq!(attrs[4].handle, DEVICE_APPEARANCE_HANDLE);
+ // assert: types are correct
+ assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
+ assert_eq!(attrs[1].type_, CHARACTERISTIC_UUID);
+ assert_eq!(attrs[2].type_, DEVICE_NAME_UUID);
+ assert_eq!(attrs[3].type_, CHARACTERISTIC_UUID);
+ assert_eq!(attrs[4].type_, DEVICE_APPEARANCE_UUID);
+ // assert: permissions of value attrs are correct
+ assert_eq!(attrs[2].permissions, AttPermissions::READABLE);
+ assert_eq!(attrs[4].permissions, AttPermissions::READABLE);
+ }
+
+ #[test]
+ fn test_read_device_name_not_discoverable() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: try to read the device name
+ let name = block_on_locally(att_db.read_attribute(DEVICE_NAME_HANDLE));
+
+ // assert: the name is not readable
+ assert_eq!(name, Err(AttErrorCode::INSUFFICIENT_AUTHENTICATION));
+ }
+
+ #[test]
+ fn test_read_device_appearance() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: try to read the device name
+ let name = block_on_locally(att_db.read_attribute(DEVICE_APPEARANCE_HANDLE));
+
+ // assert: the name is not readable
+ assert_eq!(name, Ok(AttAttributeDataChild::RawData([0x00, 0x00].into())));
+ }
+}
diff --git a/system/rust/src/gatt/server/services/gatt.rs b/system/rust/src/gatt/server/services/gatt.rs
new file mode 100644
index 0000000000..dd9db3225a
--- /dev/null
+++ b/system/rust/src/gatt/server/services/gatt.rs
@@ -0,0 +1,261 @@
+//! The GATT service as defined in Core Spec 5.3 Vol 3G Section 7
+
+use std::{cell::RefCell, collections::HashSet, rc::Rc};
+
+use anyhow::Result;
+use async_trait::async_trait;
+use log::warn;
+
+use crate::{
+ core::uuid::Uuid,
+ gatt::{
+ callbacks::GattDatastore,
+ ffi::AttributeBackingType,
+ ids::{AttHandle, ConnectionId},
+ server::gatt_database::{
+ AttPermissions, GattCharacteristicWithHandle, GattDatabase, GattDescriptorWithHandle,
+ GattServiceWithHandle,
+ },
+ },
+ packets::{
+ AttAttributeDataChild, AttAttributeDataView, AttClientCharacteristicConfigurationBuilder,
+ AttClientCharacteristicConfigurationView, AttErrorCode, Packet,
+ },
+};
+
+#[derive(Default)]
+struct GattService {
+ // TODO(aryarahul): clear this on disconnect/reconnect
+ // TODO(aryarahul): do NOT clear this for bonded devices
+ // TODO(aryarahul): actually send this on service change
+ clients_watching_for_service_change_indication: RefCell<HashSet<ConnectionId>>,
+}
+
+// Must lie in the range specified by GATT_GATT_START_HANDLE from legacy stack
+const GATT_SERVICE_HANDLE: AttHandle = AttHandle(1);
+const SERVICE_CHANGE_HANDLE: AttHandle = AttHandle(3);
+const SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE: AttHandle = AttHandle(4);
+
+/// The UUID used for the GATT service (Assigned Numbers 3.4.1 Services by Name)
+pub const GATT_SERVICE_UUID: Uuid = Uuid::new(0x1801);
+/// The UUID used for the Service Changed characteristic (Assigned Numbers 3.8.1 Characteristics by Name)
+pub const SERVICE_CHANGE_UUID: Uuid = Uuid::new(0x2A05);
+/// The UUID used for the Client Characteristic Configuration descriptor (Assigned Numbers 3.7 Descriptors)
+pub const CLIENT_CHARACTERISTIC_CONFIGURATION_UUID: Uuid = Uuid::new(0x2902);
+
+#[async_trait(?Send)]
+impl GattDatastore for GattService {
+ async fn read(
+ &self,
+ conn_id: ConnectionId,
+ handle: AttHandle,
+ _: AttributeBackingType,
+ ) -> Result<AttAttributeDataChild, AttErrorCode> {
+ if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
+ Ok(AttClientCharacteristicConfigurationBuilder {
+ notification: 0,
+ indication: self
+ .clients_watching_for_service_change_indication
+ .borrow()
+ .contains(&conn_id)
+ .into(),
+ }
+ .into())
+ } else {
+ unreachable!()
+ }
+ }
+
+ async fn write(
+ &self,
+ conn_id: ConnectionId,
+ handle: AttHandle,
+ _: AttributeBackingType,
+ data: AttAttributeDataView<'_>,
+ ) -> Result<(), AttErrorCode> {
+ if handle == SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE {
+ let ccc = AttClientCharacteristicConfigurationView::try_parse(data).map_err(|err| {
+ warn!("failed to parse CCC descriptor, got: {err:?}");
+ AttErrorCode::APPLICATION_ERROR
+ })?;
+ if ccc.get_indication() != 0 {
+ self.clients_watching_for_service_change_indication.borrow_mut().insert(conn_id);
+ } else {
+ self.clients_watching_for_service_change_indication.borrow_mut().remove(&conn_id);
+ }
+ Ok(())
+ } else {
+ unreachable!()
+ }
+ }
+}
+
+/// Register the GATT service in the provided GATT database.
+pub fn register_gatt_service(database: &mut GattDatabase) -> Result<()> {
+ database.add_service_with_handles(
+ // GATT Service
+ GattServiceWithHandle {
+ handle: GATT_SERVICE_HANDLE,
+ type_: GATT_SERVICE_UUID,
+ // Service Changed Characteristic
+ characteristics: vec![GattCharacteristicWithHandle {
+ handle: SERVICE_CHANGE_HANDLE,
+ type_: SERVICE_CHANGE_UUID,
+ permissions: AttPermissions::INDICATE,
+ descriptors: vec![GattDescriptorWithHandle {
+ handle: SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
+ type_: CLIENT_CHARACTERISTIC_CONFIGURATION_UUID,
+ permissions: AttPermissions::READABLE | AttPermissions::WRITABLE,
+ }],
+ }],
+ },
+ Rc::new(GattService::default()),
+ )
+}
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use crate::{
+ core::shared_box::SharedBox,
+ gatt::{
+ ids::ConnectionId,
+ server::{
+ att_database::{
+ AttDatabase, CHARACTERISTIC_UUID, PRIMARY_SERVICE_DECLARATION_UUID,
+ },
+ gatt_database::GattDatabase,
+ },
+ },
+ utils::{
+ packet::{build_att_data, build_view_or_crash},
+ task::block_on_locally,
+ },
+ };
+
+ const CONN_ID: ConnectionId = ConnectionId(1);
+
+ fn init_dbs() -> (SharedBox<GattDatabase>, impl AttDatabase) {
+ let mut gatt_database = GattDatabase::new();
+ register_gatt_service(&mut gatt_database).unwrap();
+ let gatt_database = SharedBox::new(gatt_database);
+ let att_database = gatt_database.get_att_database(CONN_ID);
+ (gatt_database, att_database)
+ }
+
+ #[test]
+ fn test_gatt_service_discovery() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: discover all services
+ let attrs = att_db.list_attributes();
+
+ // assert: 1 service + 1 char decl + 1 char value + 1 char descriptor = 4 attrs
+ assert_eq!(attrs.len(), 4);
+ // assert: value handles are correct
+ assert_eq!(attrs[0].handle, GATT_SERVICE_HANDLE);
+ assert_eq!(attrs[2].handle, SERVICE_CHANGE_HANDLE);
+ // assert: types are correct
+ assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
+ assert_eq!(attrs[1].type_, CHARACTERISTIC_UUID);
+ assert_eq!(attrs[2].type_, SERVICE_CHANGE_UUID);
+ assert_eq!(attrs[3].type_, CLIENT_CHARACTERISTIC_CONFIGURATION_UUID);
+ // assert: permissions of value attrs are correct
+ assert_eq!(attrs[2].permissions, AttPermissions::INDICATE);
+ assert_eq!(attrs[3].permissions, AttPermissions::READABLE | AttPermissions::WRITABLE);
+ }
+
+ #[test]
+ fn test_default_indication_subscription() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: try to read the CCC descriptor
+ let resp =
+ block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+
+ // assert: we are not registered for either indications/notifications
+ let AttAttributeDataChild::AttClientCharacteristicConfiguration(configuration) = resp else {
+ unreachable!()
+ };
+ assert_eq!(
+ configuration,
+ AttClientCharacteristicConfigurationBuilder { notification: 0, indication: 0 }
+ );
+ }
+
+ #[test]
+ fn test_subscribe_to_indication() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: register for service change indication
+ block_on_locally(
+ att_db.write_attribute(
+ SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
+ build_view_or_crash(build_att_data(AttClientCharacteristicConfigurationBuilder {
+ notification: 0,
+ indication: 1,
+ }))
+ .view(),
+ ),
+ )
+ .unwrap();
+ // read our registration status
+ let resp =
+ block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+
+ // assert: we are registered for indications
+ let AttAttributeDataChild::AttClientCharacteristicConfiguration(configuration) = resp else {
+ unreachable!()
+ };
+ assert_eq!(
+ configuration,
+ AttClientCharacteristicConfigurationBuilder { notification: 0, indication: 1 }
+ );
+ }
+
+ #[test]
+ fn test_unsubscribe_to_indication() {
+ // arrange
+ let (_gatt_db, att_db) = init_dbs();
+
+ // act: register for service change indication
+ block_on_locally(
+ att_db.write_attribute(
+ SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
+ build_view_or_crash(build_att_data(AttClientCharacteristicConfigurationBuilder {
+ notification: 0,
+ indication: 1,
+ }))
+ .view(),
+ ),
+ )
+ .unwrap();
+ // act: next, unregister from this indication
+ block_on_locally(
+ att_db.write_attribute(
+ SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE,
+ build_view_or_crash(build_att_data(AttClientCharacteristicConfigurationBuilder {
+ notification: 0,
+ indication: 0,
+ }))
+ .view(),
+ ),
+ )
+ .unwrap();
+ // read our registration status
+ let resp =
+ block_on_locally(att_db.read_attribute(SERVICE_CHANGE_CCC_DESCRIPTOR_HANDLE)).unwrap();
+
+ // assert: we are not registered for indications
+ let AttAttributeDataChild::AttClientCharacteristicConfiguration(configuration) = resp else {
+ unreachable!()
+ };
+ assert_eq!(
+ configuration,
+ AttClientCharacteristicConfigurationBuilder { notification: 0, indication: 0 }
+ );
+ }
+}
diff --git a/system/rust/src/packets.pdl b/system/rust/src/packets.pdl
index 85ea01998a..3a1122ae06 100644
--- a/system/rust/src/packets.pdl
+++ b/system/rust/src/packets.pdl
@@ -59,9 +59,12 @@ enum AttErrorCode : 8 {
INVALID_HANDLE = 0x01,
READ_NOT_PERMITTED = 0x02,
WRITE_NOT_PERMITTED = 0x03,
+ INVALID_PDU = 0x04,
+ INSUFFICIENT_AUTHENTICATION = 0x05,
REQUEST_NOT_SUPPORTED = 0x06,
ATTRIBUTE_NOT_FOUND = 0x0A,
UNSUPPORTED_GROUP_TYPE = 0x10,
+ APPLICATION_ERROR = 0x80,
UNLIKELY_ERROR = 0x0E
}
@@ -135,6 +138,12 @@ struct GattServiceDeclarationValue : AttAttributeData {
uuid: Uuid,
}
+struct AttClientCharacteristicConfiguration : AttAttributeData {
+ notification: 1,
+ indication: 1,
+ _reserved_: 14,
+}
+
struct AttAttributeData {
_payload_
}
diff --git a/system/rust/tests/gatt_server_test.rs b/system/rust/tests/gatt_server_test.rs
index b40edc3a14..807616f42e 100644
--- a/system/rust/tests/gatt_server_test.rs
+++ b/system/rust/tests/gatt_server_test.rs
@@ -15,14 +15,16 @@ use bluetooth_core::{
AttPermissions, GattCharacteristicWithHandle, GattDescriptorWithHandle,
GattServiceWithHandle,
},
+ services::gap::DEVICE_NAME_UUID,
GattModule, IndicationError,
},
},
packets::{
- AttAttributeDataChild, AttBuilder, AttErrorCode, AttErrorResponseBuilder,
+ AttAttributeDataChild, AttBuilder, AttChild, AttErrorCode, AttErrorResponseBuilder,
AttHandleValueConfirmationBuilder, AttHandleValueIndicationBuilder, AttOpcode,
- AttReadRequestBuilder, AttReadResponseBuilder, AttWriteRequestBuilder,
- AttWriteResponseBuilder, GattServiceDeclarationValueBuilder, Serializable,
+ AttReadByTypeRequestBuilder, AttReadRequestBuilder, AttReadResponseBuilder,
+ AttWriteRequestBuilder, AttWriteResponseBuilder, GattServiceDeclarationValueBuilder,
+ Serializable,
},
utils::packet::{build_att_data, build_att_view_or_crash},
};
@@ -40,9 +42,9 @@ const ANOTHER_TCB_IDX: TransportIndex = TransportIndex(2);
const ANOTHER_SERVER_ID: ServerId = ServerId(3);
const ANOTHER_CONN_ID: ConnectionId = ConnectionId::new(ANOTHER_TCB_IDX, ANOTHER_SERVER_ID);
-const SERVICE_HANDLE: AttHandle = AttHandle(3);
-const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(5);
-const DESCRIPTOR_HANDLE: AttHandle = AttHandle(6);
+const SERVICE_HANDLE: AttHandle = AttHandle(6);
+const CHARACTERISTIC_HANDLE: AttHandle = AttHandle(8);
+const DESCRIPTOR_HANDLE: AttHandle = AttHandle(9);
const SERVICE_TYPE: Uuid = Uuid::new(0x0102);
const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x0103);
@@ -431,3 +433,30 @@ fn test_multiple_servers() {
assert_eq!(resp_2._child_.to_vec().unwrap(), ANOTHER_DATA);
})
}
+
+#[test]
+fn test_read_device_name() {
+ start_test(async move {
+ // arrange
+ let (mut gatt, mut transport_rx) = start_gatt_module();
+ create_server_and_open_connection(&mut gatt);
+
+ // act: try to read the device name
+ gatt.get_bearer(CONN_ID).unwrap().handle_packet(
+ build_att_view_or_crash(AttReadByTypeRequestBuilder {
+ starting_handle: AttHandle(1).into(),
+ ending_handle: AttHandle(0xFFFF).into(),
+ attribute_type: DEVICE_NAME_UUID.into(),
+ })
+ .view(),
+ );
+ let (tcb_idx, resp) = transport_rx.recv().await.unwrap();
+
+ // assert: the name should not be readable
+ assert_eq!(tcb_idx, TCB_IDX);
+ let AttChild::AttErrorResponse(resp) = resp._child_ else {
+ unreachable!("{resp:?}");
+ };
+ assert_eq!(resp.error_code, AttErrorCode::INSUFFICIENT_AUTHENTICATION);
+ });
+}