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