• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! This module converts a GattDatastore to an AttDatabase,
2 //! by converting a registry of services into a list of attributes, and proxying
3 //! ATT read/write requests into characteristic reads/writes
4 
5 use pdl_runtime::Packet;
6 use std::cell::RefCell;
7 use std::collections::BTreeMap;
8 use std::ops::RangeInclusive;
9 use std::rc::Rc;
10 
11 use anyhow::{bail, Result};
12 use async_trait::async_trait;
13 use log::{error, warn};
14 
15 use crate::core::shared_box::{SharedBox, WeakBox, WeakBoxRef};
16 use crate::core::uuid::Uuid;
17 use crate::gatt::callbacks::{GattWriteRequestType, RawGattDatastore};
18 use crate::gatt::ffi::AttributeBackingType;
19 use crate::gatt::ids::{AttHandle, TransportIndex};
20 use crate::packets::att::{self, AttErrorCode};
21 
22 use super::att_database::{AttAttribute, AttDatabase};
23 use super::att_server_bearer::AttServerBearer;
24 
25 pub use super::att_database::AttPermissions;
26 
27 /// Primary Service Declaration from Bluetooth Assigned Numbers 3.5 Declarations
28 pub const PRIMARY_SERVICE_DECLARATION_UUID: Uuid = Uuid::new(0x2800);
29 /// Secondary Service Declaration from Bluetooth Assigned Numbers 3.5 Declarations
30 pub const SECONDARY_SERVICE_DECLARATION_UUID: Uuid = Uuid::new(0x2801);
31 /// Characteristic Declaration from Bluetooth Assigned Numbers 3.5 Declarations
32 pub const CHARACTERISTIC_UUID: Uuid = Uuid::new(0x2803);
33 
34 /// A GattService (currently, only primary services are supported) has an
35 /// identifying UUID and a list of contained characteristics, as well as a
36 /// handle (indicating the attribute where the service declaration will live)
37 #[derive(Debug, Clone)]
38 pub struct GattServiceWithHandle {
39     /// The handle of the service declaration
40     pub handle: AttHandle,
41     /// The type of the service
42     pub type_: Uuid,
43     /// A list of contained characteristics (that must have handles between the
44     /// service declaration handle, and that of the next service)
45     pub characteristics: Vec<GattCharacteristicWithHandle>,
46 }
47 
48 /// A GattCharacteristic consists of a handle (where the value attribute lives),
49 /// a UUID identifying its type, and permissions indicating what operations can
50 /// be performed
51 #[derive(Debug, Clone)]
52 pub struct GattCharacteristicWithHandle {
53     /// The handle of the characteristic value attribute. The characteristic
54     /// declaration is one before this handle.
55     pub handle: AttHandle,
56     /// The UUID representing the type of the characteristic value.
57     pub type_: Uuid,
58     /// The permissions (read/write) indicate what operations can be performed.
59     pub permissions: AttPermissions,
60     /// The descriptors associated with this characteristic
61     pub descriptors: Vec<GattDescriptorWithHandle>,
62 }
63 
64 /// A GattDescriptor consists of a handle, type_, and permissions (similar to a
65 /// GattCharacteristic) It is guaranteed that the handle of the GattDescriptor
66 /// is after the handle of the characteristic value attribute, and before the
67 /// next characteristic/service declaration
68 #[derive(Debug, Clone)]
69 pub struct GattDescriptorWithHandle {
70     /// The handle of the descriptor.
71     pub handle: AttHandle,
72     /// The UUID representing the type of the descriptor.
73     pub type_: Uuid,
74     /// The permissions (read/write) indicate what operations can be performed.
75     pub permissions: AttPermissions,
76 }
77 
78 /// The GattDatabase implements AttDatabase, and converts attribute reads/writes
79 /// into GATT operations to be sent to the upper layers
80 #[derive(Default)]
81 pub struct GattDatabase {
82     schema: RefCell<GattDatabaseSchema>,
83     listeners: RefCell<Vec<Rc<dyn GattDatabaseCallbacks>>>,
84 }
85 
86 #[derive(Default)]
87 struct GattDatabaseSchema {
88     attributes: BTreeMap<AttHandle, AttAttributeWithBackingValue>,
89 }
90 
91 #[derive(Clone)]
92 enum AttAttributeBackingValue {
93     Static(Vec<u8>),
94     DynamicCharacteristic(Rc<dyn RawGattDatastore>),
95     DynamicDescriptor(Rc<dyn RawGattDatastore>),
96 }
97 
98 #[derive(Clone)]
99 struct AttAttributeWithBackingValue {
100     attribute: AttAttribute,
101     value: AttAttributeBackingValue,
102 }
103 
104 /// Callbacks that can be registered on the GattDatabase to watch for
105 /// events of interest.
106 ///
107 /// Note: if the GattDatabase is dropped (e.g. due to unregistration), these
108 /// callbacks will not be invoked, even if the relevant event occurs later.
109 /// e.g. if we open the db, connect, close the db, then disconnect, then on_le_disconnect()
110 /// will NEVER be invoked.
111 pub trait GattDatabaseCallbacks {
112     /// A peer device on the given bearer has connected to this database (and can see its attributes)
on_le_connect( &self, tcb_idx: TransportIndex, bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>, )113     fn on_le_connect(
114         &self,
115         tcb_idx: TransportIndex,
116         bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>,
117     );
118     /// A peer device has disconnected from this database
on_le_disconnect(&self, tcb_idx: TransportIndex)119     fn on_le_disconnect(&self, tcb_idx: TransportIndex);
120     /// The attributes in the specified range have changed
on_service_change(&self, range: RangeInclusive<AttHandle>)121     fn on_service_change(&self, range: RangeInclusive<AttHandle>);
122 }
123 
124 impl GattDatabase {
125     /// Constructor, wrapping a GattDatastore
new() -> Self126     pub fn new() -> Self {
127         Default::default()
128     }
129 
130     /// Register an event listener
register_listener(&self, callbacks: Rc<dyn GattDatabaseCallbacks>)131     pub fn register_listener(&self, callbacks: Rc<dyn GattDatabaseCallbacks>) {
132         self.listeners.borrow_mut().push(callbacks);
133     }
134 
135     /// When a connection has been made with access to this database.
136     /// The supplied bearer is guaranteed to be ready for use.
on_bearer_ready( &self, tcb_idx: TransportIndex, bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>, )137     pub fn on_bearer_ready(
138         &self,
139         tcb_idx: TransportIndex,
140         bearer: WeakBoxRef<AttServerBearer<AttDatabaseImpl>>,
141     ) {
142         for listener in self.listeners.borrow().iter() {
143             listener.on_le_connect(tcb_idx, bearer.clone());
144         }
145     }
146 
147     /// When the connection has dropped.
on_bearer_dropped(&self, tcb_idx: TransportIndex)148     pub fn on_bearer_dropped(&self, tcb_idx: TransportIndex) {
149         for listener in self.listeners.borrow().iter() {
150             listener.on_le_disconnect(tcb_idx);
151         }
152     }
153 
154     /// Add a service with pre-allocated handles (for co-existence with C++) backed by the supplied datastore
155     /// Assumes that the characteristic DECLARATION handles are one less than
156     /// the characteristic handles.
157     /// Returns failure if handles overlap with ones already allocated
add_service_with_handles( &self, service: GattServiceWithHandle, datastore: Rc<dyn RawGattDatastore>, ) -> Result<()>158     pub fn add_service_with_handles(
159         &self,
160         service: GattServiceWithHandle,
161         datastore: Rc<dyn RawGattDatastore>,
162     ) -> Result<()> {
163         let mut attributes = BTreeMap::new();
164         let mut attribute_cnt = 0;
165 
166         let mut add_attribute = |attribute: AttAttribute, value: AttAttributeBackingValue| {
167             attribute_cnt += 1;
168             attributes.insert(attribute.handle, AttAttributeWithBackingValue { attribute, value })
169         };
170 
171         let mut characteristics = vec![];
172 
173         // service definition
174         add_attribute(
175             AttAttribute {
176                 handle: service.handle,
177                 type_: PRIMARY_SERVICE_DECLARATION_UUID,
178                 permissions: AttPermissions::READABLE,
179             },
180             AttAttributeBackingValue::Static(
181                 att::GattServiceDeclarationValue { uuid: service.type_.into() }
182                     .encode_to_vec()
183                     .map_err(|e| {
184                         anyhow::anyhow!("failed to encode primary service declaration: {e:?}")
185                     })?,
186             ),
187         );
188 
189         // characteristics
190         for characteristic in service.characteristics {
191             characteristics.push(characteristic.clone());
192 
193             // declaration
194             // Recall that we assume the declaration handle is one less than the value
195             // handle
196             let declaration_handle = AttHandle(characteristic.handle.0 - 1);
197 
198             add_attribute(
199                 AttAttribute {
200                     handle: declaration_handle,
201                     type_: CHARACTERISTIC_UUID,
202                     permissions: AttPermissions::READABLE,
203                 },
204                 AttAttributeBackingValue::Static(
205                     att::GattCharacteristicDeclarationValue {
206                         properties: att::GattCharacteristicProperties {
207                             broadcast: 0,
208                             read: characteristic.permissions.readable().into(),
209                             write_without_response: characteristic
210                                 .permissions
211                                 .writable_without_response()
212                                 .into(),
213                             write: characteristic.permissions.writable_with_response().into(),
214                             notify: 0,
215                             indicate: characteristic.permissions.indicate().into(),
216                             authenticated_signed_writes: 0,
217                             extended_properties: 0,
218                         },
219                         handle: characteristic.handle.into(),
220                         uuid: characteristic.type_.into(),
221                     }
222                     .encode_to_vec()
223                     .map_err(|e| {
224                         anyhow::anyhow!("failed to encode characteristic declaration: {e:?}")
225                     })?,
226                 ),
227             );
228 
229             // value
230             add_attribute(
231                 AttAttribute {
232                     handle: characteristic.handle,
233                     type_: characteristic.type_,
234                     permissions: characteristic.permissions,
235                 },
236                 AttAttributeBackingValue::DynamicCharacteristic(datastore.clone()),
237             );
238 
239             // descriptors
240             for descriptor in characteristic.descriptors {
241                 add_attribute(
242                     AttAttribute {
243                         handle: descriptor.handle,
244                         type_: descriptor.type_,
245                         permissions: descriptor.permissions,
246                     },
247                     AttAttributeBackingValue::DynamicDescriptor(datastore.clone()),
248                 );
249             }
250         }
251 
252         // validate attributes for overlap
253         let mut static_data = self.schema.borrow_mut();
254 
255         for handle in attributes.keys() {
256             if static_data.attributes.contains_key(handle) {
257                 bail!("duplicate handle detected");
258             }
259         }
260         if attributes.len() != attribute_cnt {
261             bail!("duplicate handle detected");
262         }
263 
264         // if we made it here, we successfully loaded the new service
265         static_data.attributes.extend(attributes.clone());
266 
267         // re-entrancy via the listeners is possible, so we prevent it by dropping here
268         drop(static_data);
269 
270         // notify listeners if any attribute changed
271         let added_handles = attributes.into_iter().map(|attr| attr.0).collect::<Vec<_>>();
272         if !added_handles.is_empty() {
273             for listener in self.listeners.borrow().iter() {
274                 listener.on_service_change(
275                     *added_handles.iter().min().unwrap()..=*added_handles.iter().max().unwrap(),
276                 );
277             }
278         }
279 
280         Ok(())
281     }
282 
283     /// Remove a previously-added service by service handle
remove_service_at_handle(&self, service_handle: AttHandle) -> Result<()>284     pub fn remove_service_at_handle(&self, service_handle: AttHandle) -> Result<()> {
285         let mut static_data = self.schema.borrow_mut();
286 
287         // find next service
288         let next_service_handle = static_data
289             .attributes
290             .values()
291             .find(|attribute| {
292                 attribute.attribute.handle > service_handle
293                     && attribute.attribute.type_ == PRIMARY_SERVICE_DECLARATION_UUID
294             })
295             .map(|service| service.attribute.handle);
296 
297         // predicate matching all handles in our service
298         let in_service_pred = |handle: AttHandle| {
299             service_handle <= handle && next_service_handle.map(|x| handle < x).unwrap_or(true)
300         };
301 
302         // record largest attribute matching predicate
303         let largest_service_handle =
304             static_data.attributes.keys().filter(|handle| in_service_pred(**handle)).max().cloned();
305 
306         // clear out attributes
307         static_data.attributes.retain(|curr_handle, _| !in_service_pred(*curr_handle));
308 
309         // re-entrancy via the listeners is possible, so we prevent it by dropping here
310         drop(static_data);
311 
312         // notify listeners if any attribute changed
313         if let Some(largest_service_handle) = largest_service_handle {
314             for listener in self.listeners.borrow().iter() {
315                 listener.on_service_change(service_handle..=largest_service_handle);
316             }
317         }
318 
319         Ok(())
320     }
321 }
322 
323 impl SharedBox<GattDatabase> {
324     /// Generate an impl AttDatabase from a backing GattDatabase, associated
325     /// with a given connection.
326     ///
327     /// Note: After the AttDatabaseImpl is constructed, we MUST call on_bearer_ready() with
328     /// the resultant bearer, so that the listeners get the correct sequence of callbacks.
get_att_database(&self, tcb_idx: TransportIndex) -> AttDatabaseImpl329     pub fn get_att_database(&self, tcb_idx: TransportIndex) -> AttDatabaseImpl {
330         AttDatabaseImpl { gatt_db: self.downgrade(), tcb_idx }
331     }
332 }
333 
334 /// An implementation of AttDatabase wrapping an underlying GattDatabase
335 pub struct AttDatabaseImpl {
336     gatt_db: WeakBox<GattDatabase>,
337     tcb_idx: TransportIndex,
338 }
339 
340 #[async_trait(?Send)]
341 impl AttDatabase for AttDatabaseImpl {
read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode>342     async fn read_attribute(&self, handle: AttHandle) -> Result<Vec<u8>, AttErrorCode> {
343         let value = self.gatt_db.with(|gatt_db| {
344             let Some(gatt_db) = gatt_db else {
345                 // db must have been closed
346                 return Err(AttErrorCode::InvalidHandle);
347             };
348             let services = gatt_db.schema.borrow();
349             let Some(attr) = services.attributes.get(&handle) else {
350                 return Err(AttErrorCode::InvalidHandle);
351             };
352             if !attr.attribute.permissions.readable() {
353                 return Err(AttErrorCode::ReadNotPermitted);
354             }
355             Ok(attr.value.clone())
356         })?;
357 
358         match value {
359             AttAttributeBackingValue::Static(val) => return Ok(val),
360             AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
361                 datastore
362                     .read(
363                         self.tcb_idx,
364                         handle,
365                         /* offset */ 0,
366                         AttributeBackingType::Characteristic,
367                     )
368                     .await
369             }
370             AttAttributeBackingValue::DynamicDescriptor(datastore) => {
371                 datastore
372                     .read(
373                         self.tcb_idx,
374                         handle,
375                         /* offset */ 0,
376                         AttributeBackingType::Descriptor,
377                     )
378                     .await
379             }
380         }
381     }
382 
write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode>383     async fn write_attribute(&self, handle: AttHandle, data: &[u8]) -> Result<(), AttErrorCode> {
384         let value = self.gatt_db.with(|gatt_db| {
385             let Some(gatt_db) = gatt_db else {
386                 // db must have been closed
387                 return Err(AttErrorCode::InvalidHandle);
388             };
389             let services = gatt_db.schema.borrow();
390             let Some(attr) = services.attributes.get(&handle) else {
391                 return Err(AttErrorCode::InvalidHandle);
392             };
393             if !attr.attribute.permissions.writable_with_response() {
394                 return Err(AttErrorCode::WriteNotPermitted);
395             }
396             Ok(attr.value.clone())
397         })?;
398 
399         match value {
400             AttAttributeBackingValue::Static(val) => {
401                 error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
402                 return Err(AttErrorCode::WriteNotPermitted);
403             }
404             AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
405                 datastore
406                     .write(
407                         self.tcb_idx,
408                         handle,
409                         AttributeBackingType::Characteristic,
410                         GattWriteRequestType::Request,
411                         data,
412                     )
413                     .await
414             }
415             AttAttributeBackingValue::DynamicDescriptor(datastore) => {
416                 datastore
417                     .write(
418                         self.tcb_idx,
419                         handle,
420                         AttributeBackingType::Descriptor,
421                         GattWriteRequestType::Request,
422                         data,
423                     )
424                     .await
425             }
426         }
427     }
428 
write_no_response_attribute(&self, handle: AttHandle, data: &[u8])429     fn write_no_response_attribute(&self, handle: AttHandle, data: &[u8]) {
430         let value = self.gatt_db.with(|gatt_db| {
431             let Some(gatt_db) = gatt_db else {
432                 // db must have been closed
433                 return None;
434             };
435             let services = gatt_db.schema.borrow();
436             let Some(attr) = services.attributes.get(&handle) else {
437                 warn!("cannot find handle {handle:?}");
438                 return None;
439             };
440             if !attr.attribute.permissions.writable_without_response() {
441                 warn!("trying to write without response to {handle:?}, which doesn't support it");
442                 return None;
443             }
444             Some(attr.value.clone())
445         });
446 
447         let Some(value) = value else {
448             return;
449         };
450 
451         match value {
452             AttAttributeBackingValue::Static(val) => {
453                 error!("A static attribute {val:?} is marked as writable - ignoring it and rejecting the write...");
454             }
455             AttAttributeBackingValue::DynamicCharacteristic(datastore) => {
456                 datastore.write_no_response(
457                     self.tcb_idx,
458                     handle,
459                     AttributeBackingType::Characteristic,
460                     data,
461                 );
462             }
463             AttAttributeBackingValue::DynamicDescriptor(datastore) => {
464                 datastore.write_no_response(
465                     self.tcb_idx,
466                     handle,
467                     AttributeBackingType::Descriptor,
468                     data,
469                 );
470             }
471         };
472     }
473 
list_attributes(&self) -> Vec<AttAttribute>474     fn list_attributes(&self) -> Vec<AttAttribute> {
475         self.gatt_db.with(|db| {
476             db.map(|db| db.schema.borrow().attributes.values().map(|attr| attr.attribute).collect())
477                 .unwrap_or_default()
478         })
479     }
480 }
481 
482 impl Clone for AttDatabaseImpl {
clone(&self) -> Self483     fn clone(&self) -> Self {
484         Self { gatt_db: self.gatt_db.clone(), tcb_idx: self.tcb_idx }
485     }
486 }
487 
488 impl AttDatabaseImpl {
489     /// When the bearer owning this AttDatabase is invalidated,
490     /// we must notify the listeners tied to our GattDatabase.
491     ///
492     /// Note: AttDatabases referring to the backing GattDatabase
493     /// may still exist after bearer invalidation, but the bearer will
494     /// no longer exist (so packets can no longer be sent/received).
on_bearer_dropped(&self)495     pub fn on_bearer_dropped(&self) {
496         self.gatt_db.with(|db| {
497             db.map(|db| {
498                 for listener in db.listeners.borrow().iter() {
499                     listener.on_le_disconnect(self.tcb_idx)
500                 }
501             })
502         });
503     }
504 }
505 
506 #[cfg(test)]
507 mod test {
508     use tokio::join;
509     use tokio::sync::mpsc::error::TryRecvError;
510     use tokio::task::spawn_local;
511 
512     use crate::gatt::mocks::mock_database_callbacks::{MockCallbackEvents, MockCallbacks};
513     use crate::gatt::mocks::mock_datastore::{MockDatastore, MockDatastoreEvents};
514     use crate::gatt::mocks::mock_raw_datastore::{MockRawDatastore, MockRawDatastoreEvents};
515     use crate::packets::att;
516     use crate::utils::task::block_on_locally;
517 
518     use super::*;
519 
520     const SERVICE_HANDLE: AttHandle = AttHandle(1);
521     const SERVICE_TYPE: Uuid = Uuid::new(0x1234);
522 
523     const CHARACTERISTIC_DECLARATION_HANDLE: AttHandle = AttHandle(2);
524     const CHARACTERISTIC_VALUE_HANDLE: AttHandle = AttHandle(3);
525     const CHARACTERISTIC_TYPE: Uuid = Uuid::new(0x5678);
526 
527     const DESCRIPTOR_HANDLE: AttHandle = AttHandle(4);
528     const DESCRIPTOR_TYPE: Uuid = Uuid::new(0x9ABC);
529 
530     const TCB_IDX: TransportIndex = TransportIndex(1);
531 
532     #[test]
test_read_empty_db()533     fn test_read_empty_db() {
534         let gatt_db = SharedBox::new(GattDatabase::new());
535         let att_db = gatt_db.get_att_database(TCB_IDX);
536 
537         let resp = tokio_test::block_on(att_db.read_attribute(AttHandle(1)));
538 
539         assert_eq!(resp, Err(AttErrorCode::InvalidHandle))
540     }
541 
542     #[test]
test_single_service()543     fn test_single_service() {
544         let (gatt_datastore, _) = MockDatastore::new();
545         let gatt_db = SharedBox::new(GattDatabase::new());
546         gatt_db
547             .add_service_with_handles(
548                 GattServiceWithHandle {
549                     handle: SERVICE_HANDLE,
550                     type_: SERVICE_TYPE,
551                     characteristics: vec![],
552                 },
553                 Rc::new(gatt_datastore),
554             )
555             .unwrap();
556         let att_db = gatt_db.get_att_database(TCB_IDX);
557 
558         let attrs = att_db.list_attributes();
559         let service_value = tokio_test::block_on(att_db.read_attribute(SERVICE_HANDLE));
560 
561         assert_eq!(
562             attrs,
563             vec![AttAttribute {
564                 handle: SERVICE_HANDLE,
565                 type_: PRIMARY_SERVICE_DECLARATION_UUID,
566                 permissions: AttPermissions::READABLE
567             }]
568         );
569         assert_eq!(
570             service_value,
571             att::GattServiceDeclarationValue { uuid: SERVICE_TYPE.into() }
572                 .encode_to_vec()
573                 .map_err(|_| AttErrorCode::UnlikelyError)
574         );
575     }
576 
577     #[test]
test_service_removal()578     fn test_service_removal() {
579         // arrange three services, each with a single characteristic
580         let (gatt_datastore, _) = MockDatastore::new();
581         let gatt_datastore = Rc::new(gatt_datastore);
582         let gatt_db = SharedBox::new(GattDatabase::new());
583 
584         gatt_db
585             .add_service_with_handles(
586                 GattServiceWithHandle {
587                     handle: AttHandle(1),
588                     type_: SERVICE_TYPE,
589                     characteristics: vec![GattCharacteristicWithHandle {
590                         handle: AttHandle(3),
591                         type_: CHARACTERISTIC_TYPE,
592                         permissions: AttPermissions::READABLE,
593                         descriptors: vec![],
594                     }],
595                 },
596                 gatt_datastore.clone(),
597             )
598             .unwrap();
599         gatt_db
600             .add_service_with_handles(
601                 GattServiceWithHandle {
602                     handle: AttHandle(4),
603                     type_: SERVICE_TYPE,
604                     characteristics: vec![GattCharacteristicWithHandle {
605                         handle: AttHandle(6),
606                         type_: CHARACTERISTIC_TYPE,
607                         permissions: AttPermissions::READABLE,
608                         descriptors: vec![],
609                     }],
610                 },
611                 gatt_datastore.clone(),
612             )
613             .unwrap();
614         gatt_db
615             .add_service_with_handles(
616                 GattServiceWithHandle {
617                     handle: AttHandle(7),
618                     type_: SERVICE_TYPE,
619                     characteristics: vec![GattCharacteristicWithHandle {
620                         handle: AttHandle(9),
621                         type_: CHARACTERISTIC_TYPE,
622                         permissions: AttPermissions::READABLE,
623                         descriptors: vec![],
624                     }],
625                 },
626                 gatt_datastore,
627             )
628             .unwrap();
629         let att_db = gatt_db.get_att_database(TCB_IDX);
630         assert_eq!(att_db.list_attributes().len(), 9);
631 
632         // act: remove the middle service
633         gatt_db.remove_service_at_handle(AttHandle(4)).unwrap();
634         let attrs = att_db.list_attributes();
635 
636         // assert that the middle service is gone
637         assert_eq!(attrs.len(), 6, "{attrs:?}");
638 
639         // assert the other two old services are still there
640         assert_eq!(
641             attrs[0],
642             AttAttribute {
643                 handle: AttHandle(1),
644                 type_: PRIMARY_SERVICE_DECLARATION_UUID,
645                 permissions: AttPermissions::READABLE
646             }
647         );
648         assert_eq!(
649             attrs[3],
650             AttAttribute {
651                 handle: AttHandle(7),
652                 type_: PRIMARY_SERVICE_DECLARATION_UUID,
653                 permissions: AttPermissions::READABLE
654             }
655         );
656     }
657 
658     #[test]
test_single_characteristic_declaration()659     fn test_single_characteristic_declaration() {
660         let (gatt_datastore, _) = MockDatastore::new();
661         let gatt_db = SharedBox::new(GattDatabase::new());
662         gatt_db
663             .add_service_with_handles(
664                 GattServiceWithHandle {
665                     handle: SERVICE_HANDLE,
666                     type_: SERVICE_TYPE,
667                     characteristics: vec![GattCharacteristicWithHandle {
668                         handle: CHARACTERISTIC_VALUE_HANDLE,
669                         type_: CHARACTERISTIC_TYPE,
670                         permissions: AttPermissions::READABLE
671                             | AttPermissions::WRITABLE_WITH_RESPONSE
672                             | AttPermissions::INDICATE,
673                         descriptors: vec![],
674                     }],
675                 },
676                 Rc::new(gatt_datastore),
677             )
678             .unwrap();
679         let att_db = gatt_db.get_att_database(TCB_IDX);
680 
681         let attrs = att_db.list_attributes();
682         let characteristic_decl =
683             tokio_test::block_on(att_db.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
684 
685         assert_eq!(attrs.len(), 3, "{attrs:?}");
686         assert_eq!(attrs[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
687         assert_eq!(
688             attrs[1],
689             AttAttribute {
690                 handle: CHARACTERISTIC_DECLARATION_HANDLE,
691                 type_: CHARACTERISTIC_UUID,
692                 permissions: AttPermissions::READABLE
693             }
694         );
695         assert_eq!(
696             attrs[2],
697             AttAttribute {
698                 handle: CHARACTERISTIC_VALUE_HANDLE,
699                 type_: CHARACTERISTIC_TYPE,
700                 permissions: AttPermissions::READABLE
701                     | AttPermissions::WRITABLE_WITH_RESPONSE
702                     | AttPermissions::INDICATE
703             }
704         );
705 
706         assert_eq!(
707             characteristic_decl,
708             att::GattCharacteristicDeclarationValue {
709                 properties: att::GattCharacteristicProperties {
710                     read: 1,
711                     broadcast: 0,
712                     write_without_response: 0,
713                     write: 1,
714                     notify: 0,
715                     indicate: 1,
716                     authenticated_signed_writes: 0,
717                     extended_properties: 0,
718                 },
719                 handle: CHARACTERISTIC_VALUE_HANDLE.into(),
720                 uuid: CHARACTERISTIC_TYPE.into()
721             }
722             .encode_to_vec()
723             .map_err(|_| AttErrorCode::UnlikelyError)
724         );
725     }
726 
727     #[test]
test_all_characteristic_permissions()728     fn test_all_characteristic_permissions() {
729         // arrange
730         let (gatt_datastore, _) = MockDatastore::new();
731         let gatt_db = SharedBox::new(GattDatabase::new());
732         let att_db = gatt_db.get_att_database(TCB_IDX);
733 
734         // act: add a characteristic with all permission bits set
735         gatt_db
736             .add_service_with_handles(
737                 GattServiceWithHandle {
738                     handle: SERVICE_HANDLE,
739                     type_: SERVICE_TYPE,
740                     characteristics: vec![GattCharacteristicWithHandle {
741                         handle: CHARACTERISTIC_VALUE_HANDLE,
742                         type_: CHARACTERISTIC_TYPE,
743                         permissions: AttPermissions::all(),
744                         descriptors: vec![],
745                     }],
746                 },
747                 Rc::new(gatt_datastore),
748             )
749             .unwrap();
750 
751         // assert: the characteristic declaration has all the bits we support set
752         let characteristic_decl =
753             tokio_test::block_on(att_db.read_attribute(CHARACTERISTIC_DECLARATION_HANDLE));
754         assert_eq!(
755             characteristic_decl,
756             att::GattCharacteristicDeclarationValue {
757                 properties: att::GattCharacteristicProperties {
758                     read: 1,
759                     broadcast: 0,
760                     write_without_response: 1,
761                     write: 1,
762                     notify: 0,
763                     indicate: 1,
764                     authenticated_signed_writes: 0,
765                     extended_properties: 0,
766                 },
767                 handle: CHARACTERISTIC_VALUE_HANDLE.into(),
768                 uuid: CHARACTERISTIC_TYPE.into()
769             }
770             .encode_to_vec()
771             .map_err(|_| AttErrorCode::UnlikelyError)
772         );
773     }
774 
775     #[test]
test_single_characteristic_value()776     fn test_single_characteristic_value() {
777         // arrange: create a database with a single characteristic
778         let (gatt_datastore, mut data_evts) = MockDatastore::new();
779         let gatt_db = SharedBox::new(GattDatabase::new());
780         gatt_db
781             .add_service_with_handles(
782                 GattServiceWithHandle {
783                     handle: SERVICE_HANDLE,
784                     type_: SERVICE_TYPE,
785                     characteristics: vec![GattCharacteristicWithHandle {
786                         handle: CHARACTERISTIC_VALUE_HANDLE,
787                         type_: CHARACTERISTIC_TYPE,
788                         permissions: AttPermissions::READABLE,
789                         descriptors: vec![],
790                     }],
791                 },
792                 Rc::new(gatt_datastore),
793             )
794             .unwrap();
795         let att_db = gatt_db.get_att_database(TCB_IDX);
796         let data = [1, 2];
797 
798         // act: read from the database, and supply a value from the backing datastore
799         let characteristic_value = tokio_test::block_on(async {
800             join!(
801                 async {
802                     let MockDatastoreEvents::Read(
803                         TCB_IDX,
804                         CHARACTERISTIC_VALUE_HANDLE,
805                         AttributeBackingType::Characteristic,
806                         reply,
807                     ) = data_evts.recv().await.unwrap()
808                     else {
809                         unreachable!()
810                     };
811                     reply.send(Ok(data.to_vec())).unwrap();
812                 },
813                 att_db.read_attribute(CHARACTERISTIC_VALUE_HANDLE)
814             )
815             .1
816         });
817 
818         // assert: the supplied value matches what the att datastore returned
819         assert_eq!(characteristic_value, Ok(data.to_vec()));
820     }
821 
822     #[test]
test_unreadable_characteristic()823     fn test_unreadable_characteristic() {
824         let (gatt_datastore, _) = MockDatastore::new();
825         let gatt_db = SharedBox::new(GattDatabase::new());
826         gatt_db
827             .add_service_with_handles(
828                 GattServiceWithHandle {
829                     handle: SERVICE_HANDLE,
830                     type_: SERVICE_TYPE,
831                     characteristics: vec![GattCharacteristicWithHandle {
832                         handle: CHARACTERISTIC_VALUE_HANDLE,
833                         type_: CHARACTERISTIC_TYPE,
834                         permissions: AttPermissions::empty(),
835                         descriptors: vec![],
836                     }],
837                 },
838                 Rc::new(gatt_datastore),
839             )
840             .unwrap();
841 
842         let characteristic_value = tokio_test::block_on(
843             gatt_db.get_att_database(TCB_IDX).read_attribute(CHARACTERISTIC_VALUE_HANDLE),
844         );
845 
846         assert_eq!(characteristic_value, Err(AttErrorCode::ReadNotPermitted));
847     }
848 
849     #[test]
test_handle_clash()850     fn test_handle_clash() {
851         let (gatt_datastore, _) = MockDatastore::new();
852         let gatt_db = SharedBox::new(GattDatabase::new());
853 
854         let result = gatt_db.add_service_with_handles(
855             GattServiceWithHandle {
856                 handle: SERVICE_HANDLE,
857                 type_: SERVICE_TYPE,
858                 characteristics: vec![GattCharacteristicWithHandle {
859                     handle: SERVICE_HANDLE,
860                     type_: CHARACTERISTIC_TYPE,
861                     permissions: AttPermissions::WRITABLE_WITH_RESPONSE,
862                     descriptors: vec![],
863                 }],
864             },
865             Rc::new(gatt_datastore),
866         );
867 
868         assert!(result.is_err());
869     }
870 
871     #[test]
test_handle_clash_with_existing()872     fn test_handle_clash_with_existing() {
873         let (gatt_datastore, _) = MockDatastore::new();
874         let gatt_datastore = Rc::new(gatt_datastore);
875         let gatt_db = Rc::new(GattDatabase::new());
876 
877         gatt_db
878             .add_service_with_handles(
879                 GattServiceWithHandle {
880                     handle: SERVICE_HANDLE,
881                     type_: SERVICE_TYPE,
882                     characteristics: vec![],
883                 },
884                 gatt_datastore.clone(),
885             )
886             .unwrap();
887 
888         let result = gatt_db.add_service_with_handles(
889             GattServiceWithHandle {
890                 handle: SERVICE_HANDLE,
891                 type_: SERVICE_TYPE,
892                 characteristics: vec![],
893             },
894             gatt_datastore,
895         );
896 
897         assert!(result.is_err());
898     }
899 
900     #[test]
test_write_single_characteristic_callback_invoked()901     fn test_write_single_characteristic_callback_invoked() {
902         // arrange: create a database with a single characteristic
903         let (gatt_datastore, mut data_evts) = MockDatastore::new();
904         let gatt_db = SharedBox::new(GattDatabase::new());
905         gatt_db
906             .add_service_with_handles(
907                 GattServiceWithHandle {
908                     handle: SERVICE_HANDLE,
909                     type_: SERVICE_TYPE,
910                     characteristics: vec![GattCharacteristicWithHandle {
911                         handle: CHARACTERISTIC_VALUE_HANDLE,
912                         type_: CHARACTERISTIC_TYPE,
913                         permissions: AttPermissions::WRITABLE_WITH_RESPONSE,
914                         descriptors: vec![],
915                     }],
916                 },
917                 Rc::new(gatt_datastore),
918             )
919             .unwrap();
920         let att_db = gatt_db.get_att_database(TCB_IDX);
921         let data = [1, 2];
922 
923         // act: write to the database
924         let recv_data = block_on_locally(async {
925             // start write task
926             spawn_local(async move {
927                 att_db.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data).await.unwrap();
928             });
929 
930             let MockDatastoreEvents::Write(
931                 TCB_IDX,
932                 CHARACTERISTIC_VALUE_HANDLE,
933                 AttributeBackingType::Characteristic,
934                 recv_data,
935                 _,
936             ) = data_evts.recv().await.unwrap()
937             else {
938                 unreachable!();
939             };
940             recv_data
941         });
942 
943         // assert: the received value matches what we supplied
944         assert_eq!(recv_data, data);
945     }
946 
947     #[test]
test_write_single_characteristic_recv_response()948     fn test_write_single_characteristic_recv_response() {
949         // arrange: create a database with a single characteristic
950         let (gatt_datastore, mut data_evts) = MockDatastore::new();
951         let gatt_db = SharedBox::new(GattDatabase::new());
952         gatt_db
953             .add_service_with_handles(
954                 GattServiceWithHandle {
955                     handle: SERVICE_HANDLE,
956                     type_: SERVICE_TYPE,
957                     characteristics: vec![GattCharacteristicWithHandle {
958                         handle: CHARACTERISTIC_VALUE_HANDLE,
959                         type_: CHARACTERISTIC_TYPE,
960                         permissions: AttPermissions::WRITABLE_WITH_RESPONSE,
961                         descriptors: vec![],
962                     }],
963                 },
964                 Rc::new(gatt_datastore),
965             )
966             .unwrap();
967         let att_db = gatt_db.get_att_database(TCB_IDX);
968         let data = [1, 2];
969 
970         // act: write to the database
971         let res = tokio_test::block_on(async {
972             join!(
973                 async {
974                     let MockDatastoreEvents::Write(_, _, _, _, reply) =
975                         data_evts.recv().await.unwrap()
976                     else {
977                         unreachable!();
978                     };
979                     reply.send(Err(AttErrorCode::UnlikelyError)).unwrap();
980                 },
981                 att_db.write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data)
982             )
983             .1
984         });
985 
986         // assert: the supplied value matches what the att datastore returned
987         assert_eq!(res, Err(AttErrorCode::UnlikelyError));
988     }
989 
990     #[test]
test_unwriteable_characteristic()991     fn test_unwriteable_characteristic() {
992         let (gatt_datastore, _) = MockDatastore::new();
993         let gatt_db = SharedBox::new(GattDatabase::new());
994         gatt_db
995             .add_service_with_handles(
996                 GattServiceWithHandle {
997                     handle: SERVICE_HANDLE,
998                     type_: SERVICE_TYPE,
999                     characteristics: vec![GattCharacteristicWithHandle {
1000                         handle: CHARACTERISTIC_VALUE_HANDLE,
1001                         type_: CHARACTERISTIC_TYPE,
1002                         permissions: AttPermissions::READABLE,
1003                         descriptors: vec![],
1004                     }],
1005                 },
1006                 Rc::new(gatt_datastore),
1007             )
1008             .unwrap();
1009         let data = [1, 2];
1010 
1011         let characteristic_value = tokio_test::block_on(
1012             gatt_db.get_att_database(TCB_IDX).write_attribute(CHARACTERISTIC_VALUE_HANDLE, &data),
1013         );
1014 
1015         assert_eq!(characteristic_value, Err(AttErrorCode::WriteNotPermitted));
1016     }
1017 
1018     #[test]
test_single_descriptor_declaration()1019     fn test_single_descriptor_declaration() {
1020         let (gatt_datastore, mut data_evts) = MockDatastore::new();
1021         let gatt_db = SharedBox::new(GattDatabase::new());
1022         gatt_db
1023             .add_service_with_handles(
1024                 GattServiceWithHandle {
1025                     handle: SERVICE_HANDLE,
1026                     type_: SERVICE_TYPE,
1027                     characteristics: vec![GattCharacteristicWithHandle {
1028                         handle: CHARACTERISTIC_VALUE_HANDLE,
1029                         type_: CHARACTERISTIC_TYPE,
1030                         permissions: AttPermissions::READABLE,
1031                         descriptors: vec![GattDescriptorWithHandle {
1032                             handle: DESCRIPTOR_HANDLE,
1033                             type_: DESCRIPTOR_TYPE,
1034                             permissions: AttPermissions::READABLE,
1035                         }],
1036                     }],
1037                 },
1038                 Rc::new(gatt_datastore),
1039             )
1040             .unwrap();
1041         let att_db = gatt_db.get_att_database(TCB_IDX);
1042         let data = [1, 2];
1043 
1044         let descriptor_value = block_on_locally(async {
1045             // start write task
1046             let pending_read =
1047                 spawn_local(async move { att_db.read_attribute(DESCRIPTOR_HANDLE).await.unwrap() });
1048 
1049             let MockDatastoreEvents::Read(
1050                 TCB_IDX,
1051                 DESCRIPTOR_HANDLE,
1052                 AttributeBackingType::Descriptor,
1053                 reply,
1054             ) = data_evts.recv().await.unwrap()
1055             else {
1056                 unreachable!();
1057             };
1058 
1059             reply.send(Ok(data.to_vec())).unwrap();
1060 
1061             pending_read.await.unwrap()
1062         });
1063 
1064         assert_eq!(descriptor_value, data);
1065     }
1066 
1067     #[test]
test_write_descriptor()1068     fn test_write_descriptor() {
1069         // arrange: db with a writable descriptor
1070         let (gatt_datastore, mut data_evts) = MockDatastore::new();
1071         let gatt_db = SharedBox::new(GattDatabase::new());
1072         gatt_db
1073             .add_service_with_handles(
1074                 GattServiceWithHandle {
1075                     handle: SERVICE_HANDLE,
1076                     type_: SERVICE_TYPE,
1077                     characteristics: vec![GattCharacteristicWithHandle {
1078                         handle: CHARACTERISTIC_VALUE_HANDLE,
1079                         type_: CHARACTERISTIC_TYPE,
1080                         permissions: AttPermissions::READABLE,
1081                         descriptors: vec![GattDescriptorWithHandle {
1082                             handle: DESCRIPTOR_HANDLE,
1083                             type_: DESCRIPTOR_TYPE,
1084                             permissions: AttPermissions::WRITABLE_WITH_RESPONSE,
1085                         }],
1086                     }],
1087                 },
1088                 Rc::new(gatt_datastore),
1089             )
1090             .unwrap();
1091         let att_db = gatt_db.get_att_database(TCB_IDX);
1092         let data = [1, 2];
1093 
1094         // act: write, and wait for the callback to be invoked
1095         block_on_locally(async {
1096             // start write task
1097             spawn_local(
1098                 async move { att_db.write_attribute(DESCRIPTOR_HANDLE, &data).await.unwrap() },
1099             );
1100 
1101             let MockDatastoreEvents::Write(
1102                 TCB_IDX,
1103                 DESCRIPTOR_HANDLE,
1104                 AttributeBackingType::Descriptor,
1105                 _,
1106                 _,
1107             ) = data_evts.recv().await.unwrap()
1108             else {
1109                 unreachable!();
1110             };
1111         });
1112 
1113         // assert: nothing, if we reach this far we are OK
1114     }
1115 
1116     #[test]
test_multiple_descriptors()1117     fn test_multiple_descriptors() {
1118         // arrange: a database with some characteristics and descriptors
1119         let (gatt_datastore, _) = MockDatastore::new();
1120         let gatt_db = SharedBox::new(GattDatabase::new());
1121         gatt_db
1122             .add_service_with_handles(
1123                 GattServiceWithHandle {
1124                     handle: AttHandle(1),
1125                     type_: SERVICE_TYPE,
1126                     characteristics: vec![
1127                         GattCharacteristicWithHandle {
1128                             handle: AttHandle(3),
1129                             type_: CHARACTERISTIC_TYPE,
1130                             permissions: AttPermissions::READABLE,
1131                             descriptors: vec![GattDescriptorWithHandle {
1132                                 handle: AttHandle(4),
1133                                 type_: DESCRIPTOR_TYPE,
1134                                 permissions: AttPermissions::READABLE,
1135                             }],
1136                         },
1137                         GattCharacteristicWithHandle {
1138                             handle: AttHandle(6),
1139                             type_: CHARACTERISTIC_TYPE,
1140                             permissions: AttPermissions::READABLE,
1141                             descriptors: vec![
1142                                 GattDescriptorWithHandle {
1143                                     handle: AttHandle(7),
1144                                     type_: DESCRIPTOR_TYPE,
1145                                     permissions: AttPermissions::WRITABLE_WITH_RESPONSE,
1146                                 },
1147                                 GattDescriptorWithHandle {
1148                                     handle: AttHandle(8),
1149                                     type_: DESCRIPTOR_TYPE,
1150                                     permissions: AttPermissions::READABLE
1151                                         | AttPermissions::WRITABLE_WITH_RESPONSE,
1152                                 },
1153                             ],
1154                         },
1155                     ],
1156                 },
1157                 Rc::new(gatt_datastore),
1158             )
1159             .unwrap();
1160 
1161         // act: get the attributes
1162         let attributes = gatt_db.get_att_database(TCB_IDX).list_attributes();
1163 
1164         // assert: check the attributes are in the correct order
1165         assert_eq!(attributes.len(), 8);
1166         assert_eq!(attributes[0].type_, PRIMARY_SERVICE_DECLARATION_UUID);
1167         assert_eq!(attributes[1].type_, CHARACTERISTIC_UUID);
1168         assert_eq!(attributes[2].type_, CHARACTERISTIC_TYPE);
1169         assert_eq!(attributes[3].type_, DESCRIPTOR_TYPE);
1170         assert_eq!(attributes[4].type_, CHARACTERISTIC_UUID);
1171         assert_eq!(attributes[5].type_, CHARACTERISTIC_TYPE);
1172         assert_eq!(attributes[6].type_, DESCRIPTOR_TYPE);
1173         assert_eq!(attributes[7].type_, DESCRIPTOR_TYPE);
1174         // assert: check the handles of the descriptors are correct
1175         assert_eq!(attributes[3].handle, AttHandle(4));
1176         assert_eq!(attributes[6].handle, AttHandle(7));
1177         assert_eq!(attributes[7].handle, AttHandle(8));
1178         // assert: check the permissions of the descriptors are correct
1179         assert_eq!(attributes[3].permissions, AttPermissions::READABLE);
1180         assert_eq!(attributes[6].permissions, AttPermissions::WRITABLE_WITH_RESPONSE);
1181         assert_eq!(
1182             attributes[7].permissions,
1183             AttPermissions::READABLE | AttPermissions::WRITABLE_WITH_RESPONSE
1184         );
1185     }
1186 
1187     #[test]
test_multiple_datastores()1188     fn test_multiple_datastores() {
1189         // arrange: create a database with two services backed by different datastores
1190         let gatt_db = SharedBox::new(GattDatabase::new());
1191 
1192         let (gatt_datastore_1, mut data_evts_1) = MockDatastore::new();
1193         gatt_db
1194             .add_service_with_handles(
1195                 GattServiceWithHandle {
1196                     handle: AttHandle(1),
1197                     type_: SERVICE_TYPE,
1198                     characteristics: vec![GattCharacteristicWithHandle {
1199                         handle: AttHandle(3),
1200                         type_: CHARACTERISTIC_TYPE,
1201                         permissions: AttPermissions::READABLE,
1202                         descriptors: vec![],
1203                     }],
1204                 },
1205                 Rc::new(gatt_datastore_1),
1206             )
1207             .unwrap();
1208 
1209         let (gatt_datastore_2, mut data_evts_2) = MockDatastore::new();
1210         gatt_db
1211             .add_service_with_handles(
1212                 GattServiceWithHandle {
1213                     handle: AttHandle(4),
1214                     type_: SERVICE_TYPE,
1215                     characteristics: vec![GattCharacteristicWithHandle {
1216                         handle: AttHandle(6),
1217                         type_: CHARACTERISTIC_TYPE,
1218                         permissions: AttPermissions::READABLE,
1219                         descriptors: vec![],
1220                     }],
1221                 },
1222                 Rc::new(gatt_datastore_2),
1223             )
1224             .unwrap();
1225 
1226         let att_db = gatt_db.get_att_database(TCB_IDX);
1227         let data = [1, 2];
1228 
1229         // act: read from the second characteristic and supply a response from the second datastore
1230         let characteristic_value = tokio_test::block_on(async {
1231             join!(
1232                 async {
1233                     let MockDatastoreEvents::Read(
1234                         TCB_IDX,
1235                         AttHandle(6),
1236                         AttributeBackingType::Characteristic,
1237                         reply,
1238                     ) = data_evts_2.recv().await.unwrap()
1239                     else {
1240                         unreachable!()
1241                     };
1242                     reply.send(Ok(data.to_vec())).unwrap();
1243                 },
1244                 att_db.read_attribute(AttHandle(6))
1245             )
1246             .1
1247         });
1248 
1249         // assert: the supplied value matches what the att datastore returned
1250         assert_eq!(characteristic_value, Ok(data.to_vec()));
1251         // the first datastore received no events
1252         assert_eq!(data_evts_1.try_recv().unwrap_err(), TryRecvError::Empty);
1253         // the second datastore has no remaining events
1254         assert_eq!(data_evts_2.try_recv().unwrap_err(), TryRecvError::Empty);
1255     }
1256 
make_bearer( gatt_db: &SharedBox<GattDatabase>, ) -> SharedBox<AttServerBearer<AttDatabaseImpl>>1257     fn make_bearer(
1258         gatt_db: &SharedBox<GattDatabase>,
1259     ) -> SharedBox<AttServerBearer<AttDatabaseImpl>> {
1260         SharedBox::new(AttServerBearer::new(gatt_db.get_att_database(TCB_IDX), |_| {
1261             unreachable!();
1262         }))
1263     }
1264 
1265     #[test]
test_connection_listener()1266     fn test_connection_listener() {
1267         // arrange: db with a listener
1268         let gatt_db = SharedBox::new(GattDatabase::new());
1269         let (callbacks, mut rx) = MockCallbacks::new();
1270         gatt_db.register_listener(Rc::new(callbacks));
1271         let bearer = make_bearer(&gatt_db);
1272 
1273         // act: open a connection
1274         gatt_db.on_bearer_ready(TCB_IDX, bearer.as_ref());
1275 
1276         // assert: we got the callback
1277         let event = rx.blocking_recv().unwrap();
1278         assert!(matches!(event, MockCallbackEvents::OnLeConnect(TCB_IDX, _)));
1279     }
1280 
1281     #[test]
test_disconnection_listener()1282     fn test_disconnection_listener() {
1283         // arrange: db with a listener
1284         let gatt_db = SharedBox::new(GattDatabase::new());
1285         let (callbacks, mut rx) = MockCallbacks::new();
1286         gatt_db.register_listener(Rc::new(callbacks));
1287 
1288         // act: disconnect
1289         gatt_db.on_bearer_dropped(TCB_IDX);
1290 
1291         // assert: we got the callback
1292         let event = rx.blocking_recv().unwrap();
1293         assert!(matches!(event, MockCallbackEvents::OnLeDisconnect(TCB_IDX)));
1294     }
1295 
1296     #[test]
test_multiple_listeners()1297     fn test_multiple_listeners() {
1298         // arrange: db with two listeners
1299         let gatt_db = SharedBox::new(GattDatabase::new());
1300         let (callbacks1, mut rx1) = MockCallbacks::new();
1301         gatt_db.register_listener(Rc::new(callbacks1));
1302         let (callbacks2, mut rx2) = MockCallbacks::new();
1303         gatt_db.register_listener(Rc::new(callbacks2));
1304 
1305         // act: disconnect
1306         gatt_db.on_bearer_dropped(TCB_IDX);
1307 
1308         // assert: we got the callback on both listeners
1309         let event = rx1.blocking_recv().unwrap();
1310         assert!(matches!(event, MockCallbackEvents::OnLeDisconnect(TCB_IDX)));
1311         let event = rx2.blocking_recv().unwrap();
1312         assert!(matches!(event, MockCallbackEvents::OnLeDisconnect(TCB_IDX)));
1313     }
1314 
1315     #[test]
test_add_service_changed_listener()1316     fn test_add_service_changed_listener() {
1317         // arrange: db with a listener
1318         let gatt_db = SharedBox::new(GattDatabase::new());
1319         let (callbacks, mut rx) = MockCallbacks::new();
1320         let (datastore, _) = MockDatastore::new();
1321 
1322         // act: start listening and add a new service
1323         gatt_db.register_listener(Rc::new(callbacks));
1324         gatt_db
1325             .add_service_with_handles(
1326                 GattServiceWithHandle {
1327                     handle: AttHandle(4),
1328                     type_: SERVICE_TYPE,
1329                     characteristics: vec![GattCharacteristicWithHandle {
1330                         handle: AttHandle(6),
1331                         type_: CHARACTERISTIC_TYPE,
1332                         permissions: AttPermissions::empty(),
1333                         descriptors: vec![],
1334                     }],
1335                 },
1336                 Rc::new(datastore),
1337             )
1338             .unwrap();
1339 
1340         // assert: we got the callback
1341         let event = rx.blocking_recv().unwrap();
1342         let MockCallbackEvents::OnServiceChange(range) = event else {
1343             unreachable!();
1344         };
1345         assert_eq!(*range.start(), AttHandle(4));
1346         assert_eq!(*range.end(), AttHandle(6));
1347     }
1348 
1349     #[test]
test_partial_remove_service_changed_listener()1350     fn test_partial_remove_service_changed_listener() {
1351         // arrange: db with two services and a listener
1352         let gatt_db = SharedBox::new(GattDatabase::new());
1353         let (callbacks, mut rx) = MockCallbacks::new();
1354         let (datastore, _) = MockDatastore::new();
1355         let datastore = Rc::new(datastore);
1356         gatt_db
1357             .add_service_with_handles(
1358                 GattServiceWithHandle {
1359                     handle: AttHandle(4),
1360                     type_: SERVICE_TYPE,
1361                     characteristics: vec![GattCharacteristicWithHandle {
1362                         handle: AttHandle(6),
1363                         type_: CHARACTERISTIC_TYPE,
1364                         permissions: AttPermissions::empty(),
1365                         descriptors: vec![],
1366                     }],
1367                 },
1368                 datastore.clone(),
1369             )
1370             .unwrap();
1371         gatt_db
1372             .add_service_with_handles(
1373                 GattServiceWithHandle {
1374                     handle: AttHandle(8),
1375                     type_: SERVICE_TYPE,
1376                     characteristics: vec![GattCharacteristicWithHandle {
1377                         handle: AttHandle(10),
1378                         type_: CHARACTERISTIC_TYPE,
1379                         permissions: AttPermissions::empty(),
1380                         descriptors: vec![],
1381                     }],
1382                 },
1383                 datastore,
1384             )
1385             .unwrap();
1386 
1387         // act: start listening and remove the first service
1388         gatt_db.register_listener(Rc::new(callbacks));
1389         gatt_db.remove_service_at_handle(AttHandle(4)).unwrap();
1390 
1391         // assert: we got the callback
1392         let event = rx.blocking_recv().unwrap();
1393         let MockCallbackEvents::OnServiceChange(range) = event else {
1394             unreachable!();
1395         };
1396         assert_eq!(*range.start(), AttHandle(4));
1397         assert_eq!(*range.end(), AttHandle(6));
1398     }
1399 
1400     #[test]
test_full_remove_service_changed_listener()1401     fn test_full_remove_service_changed_listener() {
1402         // arrange: db with a listener and a service
1403         let gatt_db = SharedBox::new(GattDatabase::new());
1404         let (callbacks, mut rx) = MockCallbacks::new();
1405         let (datastore, _) = MockDatastore::new();
1406         gatt_db
1407             .add_service_with_handles(
1408                 GattServiceWithHandle {
1409                     handle: AttHandle(4),
1410                     type_: SERVICE_TYPE,
1411                     characteristics: vec![GattCharacteristicWithHandle {
1412                         handle: AttHandle(6),
1413                         type_: CHARACTERISTIC_TYPE,
1414                         permissions: AttPermissions::empty(),
1415                         descriptors: vec![],
1416                     }],
1417                 },
1418                 Rc::new(datastore),
1419             )
1420             .unwrap();
1421 
1422         // act: start listening and remove the service
1423         gatt_db.register_listener(Rc::new(callbacks));
1424         gatt_db.remove_service_at_handle(AttHandle(4)).unwrap();
1425 
1426         // assert: we got the callback
1427         let event = rx.blocking_recv().unwrap();
1428         let MockCallbackEvents::OnServiceChange(range) = event else {
1429             unreachable!();
1430         };
1431         assert_eq!(*range.start(), AttHandle(4));
1432         assert_eq!(*range.end(), AttHandle(6));
1433     }
1434 
1435     #[test]
test_trivial_remove_service_changed_listener()1436     fn test_trivial_remove_service_changed_listener() {
1437         // arrange: db with a listener and a trivial service
1438         let gatt_db = SharedBox::new(GattDatabase::new());
1439         let (callbacks, mut rx) = MockCallbacks::new();
1440         let (datastore, _) = MockDatastore::new();
1441         gatt_db
1442             .add_service_with_handles(
1443                 GattServiceWithHandle {
1444                     handle: AttHandle(4),
1445                     type_: SERVICE_TYPE,
1446                     characteristics: vec![],
1447                 },
1448                 Rc::new(datastore),
1449             )
1450             .unwrap();
1451 
1452         // act: start listening and remove the service
1453         gatt_db.register_listener(Rc::new(callbacks));
1454         gatt_db.remove_service_at_handle(AttHandle(4)).unwrap();
1455 
1456         // assert: we got the callback
1457         let event = rx.blocking_recv().unwrap();
1458         let MockCallbackEvents::OnServiceChange(range) = event else {
1459             unreachable!();
1460         };
1461         assert_eq!(*range.start(), AttHandle(4));
1462         assert_eq!(*range.end(), AttHandle(4));
1463     }
1464 
1465     #[test]
test_write_no_response_single_characteristic()1466     fn test_write_no_response_single_characteristic() {
1467         // arrange: create a database with a single characteristic
1468         let (gatt_datastore, mut data_evts) = MockRawDatastore::new();
1469         let gatt_db = SharedBox::new(GattDatabase::new());
1470         gatt_db
1471             .add_service_with_handles(
1472                 GattServiceWithHandle {
1473                     handle: SERVICE_HANDLE,
1474                     type_: SERVICE_TYPE,
1475                     characteristics: vec![GattCharacteristicWithHandle {
1476                         handle: CHARACTERISTIC_VALUE_HANDLE,
1477                         type_: CHARACTERISTIC_TYPE,
1478                         permissions: AttPermissions::WRITABLE_WITHOUT_RESPONSE,
1479                         descriptors: vec![],
1480                     }],
1481                 },
1482                 Rc::new(gatt_datastore),
1483             )
1484             .unwrap();
1485         let att_db = gatt_db.get_att_database(TCB_IDX);
1486         let data = [1, 2];
1487 
1488         // act: write without response to the database
1489         att_db.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
1490 
1491         // assert: we got a callback
1492         let event = data_evts.blocking_recv().unwrap();
1493         let MockRawDatastoreEvents::WriteNoResponse(
1494             TCB_IDX,
1495             CHARACTERISTIC_VALUE_HANDLE,
1496             AttributeBackingType::Characteristic,
1497             recv_data,
1498         ) = event
1499         else {
1500             unreachable!("{event:?}");
1501         };
1502         assert_eq!(recv_data, data);
1503     }
1504 
1505     #[test]
test_unwriteable_without_response_characteristic()1506     fn test_unwriteable_without_response_characteristic() {
1507         // arrange: db with a characteristic that is writable, but not writable-without-response
1508         let (gatt_datastore, mut data_events) = MockRawDatastore::new();
1509         let gatt_db = SharedBox::new(GattDatabase::new());
1510         gatt_db
1511             .add_service_with_handles(
1512                 GattServiceWithHandle {
1513                     handle: SERVICE_HANDLE,
1514                     type_: SERVICE_TYPE,
1515                     characteristics: vec![GattCharacteristicWithHandle {
1516                         handle: CHARACTERISTIC_VALUE_HANDLE,
1517                         type_: CHARACTERISTIC_TYPE,
1518                         permissions: AttPermissions::READABLE
1519                             | AttPermissions::WRITABLE_WITH_RESPONSE,
1520                         descriptors: vec![],
1521                     }],
1522                 },
1523                 Rc::new(gatt_datastore),
1524             )
1525             .unwrap();
1526         let att_db = gatt_db.get_att_database(TCB_IDX);
1527         let data = [1, 2];
1528 
1529         // act: try writing without response to this characteristic
1530         att_db.write_no_response_attribute(CHARACTERISTIC_VALUE_HANDLE, &data);
1531 
1532         // assert: no callback was sent
1533         assert_eq!(data_events.try_recv().unwrap_err(), TryRecvError::Empty);
1534     }
1535 }
1536