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