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