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