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