[bt-gatt] Add GattTypes to simplify client/lib use Change-Id: I68e2d69e28d4f0a5a028d5035e9153f34e1bdd93 Reviewed-on: https://bluetooth-review.git.corp.google.com/c/bluetooth/+/1400 Reviewed-by: Dayeong Lee <dayeonglee@google.com> Reviewed-by: Ani Ramakrishnan <aniramakri@google.com>
diff --git a/rust/bt-bass/src/client.rs b/rust/bt-bass/src/client.rs index d10e6da..f9ba7fd 100644 --- a/rust/bt-bass/src/client.rs +++ b/rust/bt-bass/src/client.rs
@@ -5,15 +5,14 @@ pub mod error; use std::collections::HashMap; -use std::time::SystemTime; use std::sync::{Arc, RwLock}; +use std::time::SystemTime; -use futures::Stream; use tracing::warn; use bt_common::packet_encoding::Decodable; -use bt_gatt::types::{Handle, Result as GattResult}; -use bt_gatt::client::{CharacteristicNotification, PeerService, ServiceCharacteristic}; +use bt_gatt::client::{PeerService, ServiceCharacteristic}; +use bt_gatt::types::Handle; use crate::client::error::{Error, ServiceError}; use crate::types::*; @@ -26,21 +25,22 @@ /// Manages connection to the Broadcast Audio Scan Service at the /// remote Scan Delegator and writes/reads characteristics to/from it. #[allow(dead_code)] -pub struct BroadcastAudioScanServiceClient<PeerServiceT, GattNotificationStream> { - gatt_client: Box<PeerServiceT>, - /// Broadcast Audio Scan Service only has one Broadcast Audio Scan Control Point characteristic - /// according to BASS Section 3. There shall +pub struct BroadcastAudioScanServiceClient<T: bt_gatt::GattTypes> { + gatt_client: T::PeerService, + /// Broadcast Audio Scan Service only has one Broadcast Audio Scan Control + /// Point characteristic according to BASS Section 3. There shall /// be one or more Broadcast Receive State characteristics. audio_scan_control_point: Handle, - /// Broadcast Receive State characteristics can be used to determine the BASS status. + /// Broadcast Receive State characteristics can be used to determine the + /// BASS status. receive_states: Arc<RwLock<HashMap<Handle, Option<ReceiveStateReadValue>>>>, // List of characteristic notification streams we are listening to. - notification_streams: Vec<GattNotificationStream>, + notification_streams: Vec<T::NotificationStream>, } #[allow(dead_code)] -impl<GattNotificationStream: Stream<Item = GattResult<CharacteristicNotification>>, PeerServiceT: PeerService<NotificationStream = GattNotificationStream>> BroadcastAudioScanServiceClient<PeerServiceT, GattNotificationStream> { - async fn create(gatt_client: Box<PeerServiceT>) -> Result<Self, Error> { +impl<T: bt_gatt::GattTypes> BroadcastAudioScanServiceClient<T> { + async fn create(gatt_client: T::PeerService) -> Result<Self, Error> { let characteristics = Self::discover_all_characteristics(&gatt_client).await?; let mut client = Self { gatt_client, @@ -53,28 +53,43 @@ } // Discover all the characteristics that are required. - // On success, returns the tuple of (Broadcast Audio Scan Control Point Characteristic, HashMap of all Broadcast Received State Characteristics). - async fn discover_all_characteristics(gatt_client: &PeerServiceT) -> Result<(Handle, HashMap<Handle, Option<ReceiveStateReadValue>>), Error> { - let bascp = ServiceCharacteristic::find(gatt_client, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID).await.map_err(|e| Error::Gatt(e))?; - let brs = ServiceCharacteristic::find(gatt_client, BROADCAST_RECEIVE_STATE_UUID).await.map_err(|e| Error::Gatt(e))?; + // On success, returns the tuple of (Broadcast Audio Scan Control Point + // Characteristic, HashMap of all Broadcast Received State Characteristics). + async fn discover_all_characteristics( + gatt_client: &T::PeerService, + ) -> Result<(Handle, HashMap<Handle, Option<ReceiveStateReadValue>>), Error> { + let bascp = + ServiceCharacteristic::<T>::find(gatt_client, BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID) + .await + .map_err(|e| Error::Gatt(e))?; + let brs = ServiceCharacteristic::<T>::find(gatt_client, BROADCAST_RECEIVE_STATE_UUID) + .await + .map_err(|e| Error::Gatt(e))?; if bascp.len() == 0 || brs.len() == 0 { return Err(Error::Service(ServiceError::MissingCharacteristic)); } if bascp.len() > 1 { return Err(Error::Service(ServiceError::ExtraScanControlPointCharacteristic)); } - // Discover all characteristics at the BASS at the remote peer over the GATT connection. Service should have a single Broadcast Audio Scan Control Point Characteristic + // Discover all characteristics at the BASS at the remote peer over the GATT + // connection. Service should have a single Broadcast Audio Scan Control Point + // Characteristic let mut brs_map = HashMap::new(); for c in brs { - // Read the value of the Broadcast Recieve State at the time of discovery for record. + // Read the value of the Broadcast Recieve State at the time of discovery for + // record. let mut buf = vec![0; READ_CHARACTERISTIC_BUFFER_SIZE]; match c.read(&mut buf[..]).await { Ok(read_bytes) => match BroadcastReceiveState::decode(&buf[0..read_bytes]) { Ok(decoded) => { brs_map.insert(*c.handle(), Some((decoded.0, SystemTime::now()))); continue; - }, - Err(e) => warn!("Failed to decode characteristic ({:?}) to Broadcast Receive State value: {:?}", *c.handle(), e), + } + Err(e) => warn!( + "Failed to decode characteristic ({:?}) to Broadcast Receive State value: {:?}", + *c.handle(), + e + ), }, Err(e) => warn!("Failed to read characteristic ({:?}) value: {:?}", *c.handle(), e), } @@ -101,61 +116,88 @@ use std::task::Poll; - use futures::{FutureExt, pin_mut}; + use futures::{pin_mut, FutureExt}; use bt_common::Uuid; - use bt_gatt::Characteristic; - use bt_gatt::types::{AttributePermissions, CharacteristicProperty, CharacteristicProperties, Handle}; use bt_gatt::test_utils::*; + use bt_gatt::types::{ + AttributePermissions, CharacteristicProperties, CharacteristicProperty, Handle, + }; + use bt_gatt::Characteristic; #[test] fn create_client() { let mut fake_peer_service = FakePeerService::new(); - // Add 3 Broadcast Receive State Characteristics, 1 Broadcast Audio Scan Control Point Characteristic, - // and 1 random one. - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: BROADCAST_RECEIVE_STATE_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(2), - uuid: BROADCAST_RECEIVE_STATE_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(3), - uuid: BROADCAST_RECEIVE_STATE_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(4), - uuid: Uuid::from_u16(0x1234), - properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(5), - uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); + // Add 3 Broadcast Receive State Characteristics, 1 Broadcast Audio Scan Control + // Point Characteristic, and 1 random one. + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: BROADCAST_RECEIVE_STATE_UUID, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(2), + uuid: BROADCAST_RECEIVE_STATE_UUID, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(3), + uuid: BROADCAST_RECEIVE_STATE_UUID, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(4), + uuid: Uuid::from_u16(0x1234), + properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(5), + uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, + properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); - let create_result = BroadcastAudioScanServiceClient::create(Box::new(fake_peer_service.clone())); + let create_result = + BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); pin_mut!(create_result); let polled = create_result.poll_unpin(&mut noop_cx); let Poll::Ready(Ok(client)) = polled else { panic!("Expected BASSClient to be succesfully created"); -}; + }; // Check that all the characteristics have been discovered. assert_eq!(client.audio_scan_control_point, Handle(5)); @@ -164,7 +206,8 @@ assert!(broadcast_receive_states.contains_key(&Handle(2))); assert!(broadcast_receive_states.contains_key(&Handle(3))); - // Three notification one for each of broadcast receive state characteristic should have been set up. + // Three notification one for each of broadcast receive state characteristic + // should have been set up. assert_eq!(client.notification_streams.len(), 3); } @@ -172,16 +215,23 @@ fn create_client_fails_missing_characteristics() { // Missing scan control point characteristic. let mut fake_peer_service = FakePeerService::new(); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: BROADCAST_RECEIVE_STATE_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: BROADCAST_RECEIVE_STATE_UUID, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); - let create_result = BroadcastAudioScanServiceClient::create(Box::new(fake_peer_service.clone())); + let create_result = + BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); pin_mut!(create_result); let polled = create_result.poll_unpin(&mut noop_cx); let Poll::Ready(Err(_)) = polled else { @@ -190,16 +240,20 @@ // Missing receive state characteristic. let mut fake_peer_service: FakePeerService = FakePeerService::new(); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, + properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); - let create_result = BroadcastAudioScanServiceClient::create(Box::new(fake_peer_service.clone())); + let create_result = + BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); pin_mut!(create_result); let polled = create_result.poll_unpin(&mut noop_cx); let Poll::Ready(Err(_)) = polled else { @@ -211,30 +265,43 @@ fn create_client_fails_duplicate_characteristics() { // More than one scan control point characteristics. let mut fake_peer_service = FakePeerService::new(); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: BROADCAST_RECEIVE_STATE_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(2), - uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(3), - uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: BROADCAST_RECEIVE_STATE_UUID, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(2), + uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, + properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(3), + uuid: BROADCAST_AUDIO_SCAN_CONTROL_POINT_UUID, + properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); - let create_result = BroadcastAudioScanServiceClient::create(Box::new(fake_peer_service.clone())); + let create_result = + BroadcastAudioScanServiceClient::<FakeTypes>::create(fake_peer_service.clone()); pin_mut!(create_result); let polled = create_result.poll_unpin(&mut noop_cx); let Poll::Ready(Err(_)) = polled else {
diff --git a/rust/bt-gatt/src/central/mod.rs b/rust/bt-gatt/src/central/mod.rs index 5197556..7e4abcf 100644 --- a/rust/bt-gatt/src/central/mod.rs +++ b/rust/bt-gatt/src/central/mod.rs
@@ -2,18 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -//! Contains traits that are used to find and connect to Low Energy Peers, i.e. the GAP Central -//! and Observer roles role defined in the Bluetooth Core Specification -//! (5.4, Volume 3 Part C Section 2.2.2) +//! Contains traits that are used to find and connect to Low Energy Peers, i.e. +//! the GAP Central and Observer roles role defined in the Bluetooth Core +//! Specification (5.4, Volume 3 Part C Section 2.2.2) //! -//! These traits should be implemented outside this crate, conforming to the types and structs here -//! when necessary. - -use crate::client::Client; -use crate::types::*; +//! These traits should be implemented outside this crate, conforming to the +//! types and structs here when necessary. use bt_common::{PeerId, Uuid}; -use futures::{Future, Stream}; #[derive(Debug, Clone)] pub enum AdvertisingDatum { @@ -26,7 +22,8 @@ Uri(String), } -/// Matches a single advertised attribute or condition from a Bluetooth Low Energy peer. +/// Matches a single advertised attribute or condition from a Bluetooth Low +/// Energy peer. #[derive(Clone, Debug)] pub enum Filter { /// Advertised Service UUID @@ -39,12 +36,13 @@ IsConnectable, /// String provided is included in the peer's name MatchesName(String), - /// Path loss from the peer (RSSI - Advertised TX Power) is below the given dB value + /// Path loss from the peer (RSSI - Advertised TX Power) is below the given + /// dB value MaxPathLoss(i8), } -/// A ScanFilter must match all of its combined filters and conditions to provide a result. -/// Currently can only include zero or more Filters. +/// A ScanFilter must match all of its combined filters and conditions to +/// provide a result. Currently can only include zero or more Filters. /// The Default ScanFilter will match everything and should be avoided. #[derive(Default, Clone, Debug)] pub struct ScanFilter { @@ -79,15 +77,11 @@ pub advertised: Vec<AdvertisingDatum>, } -pub trait Central { - type ScanResultStream: Stream<Item = Result<ScanResult>> + 'static; - type Client: Client; - type ClientFut: Future<Output = Result<Self::Client>>; - +pub trait Central<T: crate::GattTypes> { /// Scan for peers. /// If any of the filters match, the results will be returned in the Stream. - fn scan(&self, filters: &[ScanFilter]) -> Self::ScanResultStream; + fn scan(&self, filters: &[ScanFilter]) -> T::ScanResultStream; /// Connect to a specific peer. - fn connect(&self, peer_id: PeerId) -> Self::ClientFut; + fn connect(&self, peer_id: PeerId) -> T::ConnectFuture; }
diff --git a/rust/bt-gatt/src/client/mod.rs b/rust/bt-gatt/src/client/mod.rs index e34c8ae..3684f9d 100644 --- a/rust/bt-gatt/src/client/mod.rs +++ b/rust/bt-gatt/src/client/mod.rs
@@ -5,7 +5,6 @@ use crate::types::*; use bt_common::{PeerId, Uuid}; -use futures::{Future, Stream}; pub enum ServiceKind { Primary, @@ -28,10 +27,7 @@ /// GATT Client connected to a particular peer. /// Holding a struct that implements this should attempt to maintain a LE /// connection to the peer. -pub trait Client { - type PeerServiceHandleT: PeerServiceHandle; - type ServiceResultFut: Future<Output = Result<Vec<Self::PeerServiceHandleT>>> + 'static; - +pub trait Client<T: crate::GattTypes> { /// The ID of the peer this is connected to. fn peer_id(&self) -> PeerId; @@ -39,16 +35,13 @@ /// This may cause as much as a full discovery of all services on the peer /// if the stack deems it appropriate. /// Service information should be up to date at the time returned. - fn find_service(&self, uuid: Uuid) -> Self::ServiceResultFut; + fn find_service(&self, uuid: Uuid) -> T::FindServicesFut; } -pub trait PeerServiceHandle { - type PeerServiceT: PeerService; - type ConnectFut: Future<Output = Result<Self::PeerServiceT>>; - +pub trait PeerServiceHandle<T: crate::GattTypes> { fn uuid(&self) -> Uuid; fn is_primary(&self) -> bool; - fn connect(&self) -> Self::ConnectFut; + fn connect(&self) -> T::ServiceConnectFut; } /// Implement when a type can be deserialized from a characteristic value. @@ -78,18 +71,13 @@ /// A connection to a GATT Service on a Peer. /// All operations are done synchronously. -pub trait PeerService { - type CharacteristicsFut: Future<Output = Result<Vec<Characteristic>>>; - type NotificationStream: Stream<Item = Result<CharacteristicNotification>>; - type ReadFut<'a>: Future<Output = Result<(usize, bool)>> + 'a; - type WriteFut<'a>: Future<Output = Result<()>> + 'a; - +pub trait PeerService<T: crate::GattTypes> { /// Discover characteristics on this service. /// If `uuid` is provided, only the characteristics matching `uuid` will be /// returned. This operation may use either the Discovery All /// Characteristics of a Service or Discovery Characteristic by UUID /// procedures, regardless of `uuid`. - fn discover_characteristics(&self, uuid: Option<Uuid>) -> Self::CharacteristicsFut; + fn discover_characteristics(&self, uuid: Option<Uuid>) -> T::CharacteristicDiscoveryFut; /// Read a characteristic into a buffer, given the handle within the /// service. On success, returns the size read and whether the value may @@ -101,7 +89,7 @@ handle: &Handle, offset: u16, buf: &'a mut [u8], - ) -> Self::ReadFut<'a>; + ) -> T::ReadFut<'a>; fn write_characteristic<'a>( &self, @@ -109,21 +97,16 @@ mode: WriteMode, offset: u16, buf: &'a [u8], - ) -> Self::WriteFut<'a>; + ) -> T::WriteFut<'a>; fn read_descriptor<'a>( &self, handle: &Handle, offset: u16, buf: &'a mut [u8], - ) -> Self::ReadFut<'a>; + ) -> T::ReadFut<'a>; - fn write_descriptor<'a>( - &self, - handle: &Handle, - offset: u16, - buf: &'a [u8], - ) -> Self::WriteFut<'a>; + fn write_descriptor<'a>(&self, handle: &Handle, offset: u16, buf: &'a [u8]) -> T::WriteFut<'a>; /// Subscribe to updates on a Characteristic. /// Either notifications or indications will be enabled depending on the @@ -133,27 +116,29 @@ /// stream. This will often write to the Client Characteristic /// Configuration descriptor for the Characteristic subscribed to. /// Updates sent from the peer wlil be delivered to the Stream returned. - fn subscribe(&self, handle: &Handle) -> Self::NotificationStream; + fn subscribe(&self, handle: &Handle) -> T::NotificationStream; } /// Convenience class for communicating with characteristics on a remote peer. -pub struct ServiceCharacteristic<'a, PeerServiceT> { - service: &'a PeerServiceT, +pub struct ServiceCharacteristic<'a, T: crate::GattTypes> { + service: &'a T::PeerService, characteristic: Characteristic, uuid: Uuid, } -impl<'a, PeerServiceT: PeerService> ServiceCharacteristic<'a, PeerServiceT> { +impl<'a, T: crate::GattTypes> ServiceCharacteristic<'a, T> { pub async fn find( - service: &'a PeerServiceT, + service: &'a T::PeerService, uuid: Uuid, - ) -> Result<Vec<ServiceCharacteristic<'a, PeerServiceT>>> { + ) -> Result<Vec<ServiceCharacteristic<'a, T>>> { let chrs = service.discover_characteristics(Some(uuid)).await?; Ok(chrs.into_iter().map(|characteristic| Self { service, characteristic, uuid }).collect()) } -} -impl<'a, PeerServiceT> ServiceCharacteristic<'a, PeerServiceT> { + pub async fn read(&self, buf: &mut [u8]) -> Result<usize> { + self.service.read_characteristic(self.handle(), 0, buf).await.map(|(bytes, _)| bytes) + } + pub fn uuid(&self) -> Uuid { self.uuid } @@ -166,9 +151,3 @@ &self.characteristic } } - -impl<'a, PeerServiceT: PeerService> ServiceCharacteristic<'a, PeerServiceT> { - pub async fn read(&self, buf: &mut [u8]) -> Result<usize> { - self.service.read_characteristic(self.handle(), 0, buf).await.map(|(bytes, _)| bytes) - } -}
diff --git a/rust/bt-gatt/src/lib.rs b/rust/bt-gatt/src/lib.rs index 6f7f263..c441821 100644 --- a/rust/bt-gatt/src/lib.rs +++ b/rust/bt-gatt/src/lib.rs
@@ -18,4 +18,37 @@ pub mod test_utils; #[cfg(test)] -pub mod tests; +mod tests; + +use futures::{Future, Stream}; + +/// Implementors implement traits with respect to GattTypes. +pub trait GattTypes: Sized { + // Types related to finding and connecting to peers + type Central: Central<Self>; + type ScanResultStream: Stream<Item = Result<central::ScanResult>> + 'static; + type Client: Client<Self>; + type ConnectFuture: Future<Output = Result<Self::Client>>; + + // Types related to finding and connecting to services + type PeerServiceHandle: client::PeerServiceHandle<Self>; + type FindServicesFut: Future<Output = Result<Vec<Self::PeerServiceHandle>>> + 'static; + type PeerService: client::PeerService<Self>; + type ServiceConnectFut: Future<Output = Result<Self::PeerService>>; + + // Types related to interacting with services + /// Future returned by PeerService::discover_characteristics, + /// delivering the set of characteristics discovered within a service + type CharacteristicDiscoveryFut: Future<Output = Result<Vec<Characteristic>>>; + /// A stream of notifications, delivering updates to a characteristic value + /// that has been subscribed to. See [`client::PeerService::subscribe`] + type NotificationStream: Stream<Item = Result<client::CharacteristicNotification>> + 'static; + /// Future resolving when a characteristic or descriptor has been read. + /// Resolves to a number of bytes read along with a boolean indicating if + /// the value was possibly truncated, or an Error if the value could not + /// be read. + type ReadFut<'a>: Future<Output = Result<(usize, bool)>> + 'a; + /// Future resolving when a characteristic or descriptor has been written. + /// Returns an error if the value could not be written. + type WriteFut<'a>: Future<Output = Result<()>> + 'a; +}
diff --git a/rust/bt-gatt/src/test_utils.rs b/rust/bt-gatt/src/test_utils.rs index 97393d2..c4189d7 100644 --- a/rust/bt-gatt/src/test_utils.rs +++ b/rust/bt-gatt/src/test_utils.rs
@@ -2,26 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use crate::{central::ScanResult, client::CharacteristicNotification, types::*}; +use crate::central::ScanResult; +use crate::client::{self, CharacteristicNotification}; +use crate::{types::*, GattTypes}; use bt_common::{PeerId, Uuid}; use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; +use futures::future::{ready, Ready}; use futures::{Future, Stream}; -use futures::future::{Ready, ready}; use parking_lot::Mutex; use std::collections::HashMap; -use std::task::Poll; use std::sync::Arc; +use std::task::Poll; #[derive(Default)] -pub(crate) struct FakeCentral {} +pub struct FakeCentral {} #[derive(Default)] struct FakePeerServiceInner { // Notifier that's used to send out notification. notifiers: HashMap<Handle, UnboundedSender<Result<CharacteristicNotification>>>, - // Characteristics to return when `read_characteristic` and `discover_characteristics` are called. + // Characteristics to return when `read_characteristic` and `discover_characteristics` are + // called. characteristics: HashMap<Handle, (Characteristic, Vec<u8>)>, } @@ -43,25 +46,29 @@ let handle = char.handle; lock.characteristics.insert(handle, (char, value.clone())); if let Some(notifier) = lock.notifiers.get_mut(&handle) { - notifier.unbounded_send(Ok(CharacteristicNotification { handle, value, maybe_truncated: false })).expect("should succeed"); + notifier + .unbounded_send(Ok(CharacteristicNotification { + handle, + value, + maybe_truncated: false, + })) + .expect("should succeed"); } } } -impl crate::client::PeerService for FakePeerService { - type CharacteristicsFut = Ready<Result<Vec<Characteristic>>>; - type NotificationStream = UnboundedReceiver<Result<CharacteristicNotification>>; - type ReadFut<'a> = Ready<Result<(usize, bool)>>; - type WriteFut<'a> = Ready<Result<()>>; - - fn discover_characteristics(&self, uuid: Option<Uuid>) -> Self::CharacteristicsFut { +impl crate::client::PeerService<FakeTypes> for FakePeerService { + fn discover_characteristics( + &self, + uuid: Option<Uuid>, + ) -> <FakeTypes as GattTypes>::CharacteristicDiscoveryFut { let lock = self.inner.lock(); let mut result = Vec::new(); - for (_handle, (char, _value))in &lock.characteristics { + for (_handle, (char, _value)) in &lock.characteristics { match uuid { Some(uuid) if uuid == char.uuid => result.push(char.clone()), None => result.push(char.clone()), - _ => {}, + _ => {} } } ready(Ok(result)) @@ -72,7 +79,7 @@ handle: &Handle, _offset: u16, buf: &'a mut [u8], - ) -> Self::ReadFut<'a> { + ) -> <FakeTypes as GattTypes>::ReadFut<'a> { let read_characteristics = &(*self.inner.lock()).characteristics; let Some((_, value)) = read_characteristics.get(handle) else { return ready(Err(Error::Gatt(GattError::InvalidHandle))); @@ -87,7 +94,7 @@ _mode: WriteMode, _offset: u16, _buf: &'a [u8], - ) -> Self::WriteFut<'a> { + ) -> <FakeTypes as GattTypes>::WriteFut<'a> { todo!() } @@ -96,7 +103,7 @@ _handle: &Handle, _offset: u16, _buf: &'a mut [u8], - ) -> Self::ReadFut<'a> { + ) -> <FakeTypes as GattTypes>::ReadFut<'a> { todo!() } @@ -105,23 +112,20 @@ _handle: &Handle, _offset: u16, _buf: &'a [u8], - ) -> Self::WriteFut<'a> { + ) -> <FakeTypes as GattTypes>::WriteFut<'a> { todo!() } - fn subscribe(&self, handle: &Handle) -> Self::NotificationStream { + fn subscribe(&self, handle: &Handle) -> <FakeTypes as GattTypes>::NotificationStream { let (sender, receiver) = unbounded(); (*self.inner.lock()).notifiers.insert(*handle, sender); receiver } } -pub(crate) struct FakeServiceHandle {} +pub struct FakeServiceHandle {} -impl crate::client::PeerServiceHandle for FakeServiceHandle { - type PeerServiceT = FakePeerService; - type ConnectFut = Ready<Result<Self::PeerServiceT>>; - +impl crate::client::PeerServiceHandle<FakeTypes> for FakeServiceHandle { fn uuid(&self) -> Uuid { todo!() } @@ -130,27 +134,24 @@ todo!() } - fn connect(&self) -> Self::ConnectFut { - todo!() + fn connect(&self) -> <FakeTypes as GattTypes>::ServiceConnectFut { + futures::future::ready(Ok(FakePeerService::new())) } } -pub(crate) struct FakeClient {} +pub struct FakeClient {} -impl crate::Client for FakeClient { - type PeerServiceHandleT = FakeServiceHandle; - type ServiceResultFut = Ready<Result<Vec<Self::PeerServiceHandleT>>>; - +impl crate::Client<FakeTypes> for FakeClient { fn peer_id(&self) -> PeerId { todo!() } - fn find_service(&self, _uuid: Uuid) -> Self::ServiceResultFut { - todo!() + fn find_service(&self, _uuid: Uuid) -> <FakeTypes as GattTypes>::FindServicesFut { + futures::future::ready(Ok::<Vec<FakeServiceHandle>, Error>(vec![FakeServiceHandle {}])) } } -pub(crate) struct SingleResultStream { +pub struct SingleResultStream { result: Option<Result<crate::central::ScanResult>>, } @@ -183,27 +184,38 @@ } } -impl crate::Central for FakeCentral { +pub struct FakeTypes {} + +impl GattTypes for FakeTypes { + type Central = FakeCentral; type ScanResultStream = SingleResultStream; - type Client = FakeClient; + type ConnectFuture = Ready<Result<FakeClient>>; + type PeerServiceHandle = FakeServiceHandle; + type FindServicesFut = Ready<Result<Vec<FakeServiceHandle>>>; + type PeerService = FakePeerService; + type ServiceConnectFut = Ready<Result<FakePeerService>>; + type CharacteristicDiscoveryFut = Ready<Result<Vec<Characteristic>>>; + type NotificationStream = UnboundedReceiver<Result<client::CharacteristicNotification>>; + type ReadFut<'a> = Ready<Result<(usize, bool)>>; + type WriteFut<'a> = Ready<Result<()>>; +} - type ClientFut = ClientConnectFut; - - fn scan(&self, _filters: &[crate::central::ScanFilter]) -> Self::ScanResultStream { +impl crate::Central<FakeTypes> for FakeCentral { + fn scan(&self, _filters: &[crate::central::ScanFilter]) -> SingleResultStream { SingleResultStream { result: Some(Ok(ScanResult { id: PeerId(1), connectable: true, name: crate::central::PeerName::CompleteName("Marie's Pixel 7 Pro".to_owned()), - advertised: vec![crate::central::AdvertisingDatum::Services(vec![ - Uuid::from_u16(0x1844), - ])], + advertised: vec![crate::central::AdvertisingDatum::Services(vec![Uuid::from_u16( + 0x1844, + )])], })), } } - fn connect(&self, _peer_id: PeerId) -> Self::ClientFut { - todo!() + fn connect(&self, _peer_id: PeerId) -> <FakeTypes as GattTypes>::ConnectFuture { + futures::future::ready(Ok(FakeClient {})) } }
diff --git a/rust/bt-gatt/src/tests.rs b/rust/bt-gatt/src/tests.rs index da13d2b..894b143 100644 --- a/rust/bt-gatt/src/tests.rs +++ b/rust/bt-gatt/src/tests.rs
@@ -5,13 +5,13 @@ use std::task::Poll; use assert_matches::assert_matches; -use futures::{FutureExt, StreamExt}; +use futures::{Future, FutureExt, StreamExt}; -use bt_common::Uuid; +use bt_common::{PeerId, Uuid}; use crate::test_utils::*; use crate::types::*; -use crate::{Central, central::Filter, client::PeerService}; +use crate::{central::Filter, client::PeerService, Central}; const TEST_UUID_1: Uuid = Uuid::from_u16(0x1234); const TEST_UUID_2: Uuid = Uuid::from_u16(0x2345); @@ -20,41 +20,65 @@ // Sets up a fake peer service with some characteristics. fn set_up() -> FakePeerService { let mut fake_peer_service = FakePeerService::new(); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: TEST_UUID_1, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(2), - uuid: TEST_UUID_1, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(3), - uuid: TEST_UUID_1, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(4), - uuid: TEST_UUID_2, - properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(5), - uuid: TEST_UUID_3, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: TEST_UUID_1, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(2), + uuid: TEST_UUID_1, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(3), + uuid: TEST_UUID_1, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(4), + uuid: TEST_UUID_2, + properties: CharacteristicProperties(vec![CharacteristicProperty::Notify]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(5), + uuid: TEST_UUID_3, + properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![], + ); fake_peer_service } @@ -72,7 +96,8 @@ let polled = discover_results.poll_unpin(&mut noop_cx); assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 1) }); - let mut discover_results = fake_peer_service.discover_characteristics(Some(Uuid::from_u16(0xFF22))); + let mut discover_results = + fake_peer_service.discover_characteristics(Some(Uuid::from_u16(0xFF22))); let polled = discover_results.poll_unpin(&mut noop_cx); assert_matches!(polled, Poll::Ready(Ok(chars)) => { assert_eq!(chars.len(), 0) }); @@ -90,7 +115,8 @@ // For characteristic that was added, value is returned let mut read_result = fake_peer_service.read_characteristic(&Handle(0x1), 0, &mut buf[..]); - let polled: Poll<std::prelude::v1::Result<(usize, bool), Error>> = read_result.poll_unpin(&mut noop_cx); + let polled: Poll<std::prelude::v1::Result<(usize, bool), Error>> = + read_result.poll_unpin(&mut noop_cx); assert_matches!(polled, Poll::Ready(Ok((len, _))) => { assert_eq!(len, 0) }); // For characteristic that doesn't exist, fails. @@ -99,13 +125,19 @@ assert_matches!(polled, Poll::Ready(Err(_))); // Change the value for characteristic with handle 1. - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: TEST_UUID_1, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![0,1,2,3]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: TEST_UUID_1, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![0, 1, 2, 3], + ); // Successfully reads the updated value. let mut read_result = fake_peer_service.read_characteristic(&Handle(0x1), 0, &mut buf[..]); @@ -123,17 +155,24 @@ let mut fake_peer_service = set_up(); let mut notification_stream = fake_peer_service.subscribe(&Handle(0x1)); - // Stream is empty unless we add an item through the FakeNotificationStream struct. + // Stream is empty unless we add an item through the FakeNotificationStream + // struct. assert!(notification_stream.poll_next_unpin(&mut noop_cx).is_pending()); // Update the characteristic value so that notification is sent. - fake_peer_service.add_characteristic(Characteristic { - handle: Handle(1), - uuid: TEST_UUID_1, - properties: CharacteristicProperties(vec![CharacteristicProperty::Broadcast, CharacteristicProperty::Notify]), - permissions: AttributePermissions::default(), - descriptors: vec![], - }, vec![0,1,2,3]); + fake_peer_service.add_characteristic( + Characteristic { + handle: Handle(1), + uuid: TEST_UUID_1, + properties: CharacteristicProperties(vec![ + CharacteristicProperty::Broadcast, + CharacteristicProperty::Notify, + ]), + permissions: AttributePermissions::default(), + descriptors: vec![], + }, + vec![0, 1, 2, 3], + ); // Stream should be ready. let polled = notification_stream.poll_next_unpin(&mut noop_cx); @@ -156,3 +195,43 @@ let polled = scan_results.poll_next_unpin(&mut noop_cx); assert_matches!(polled, Poll::Ready(Some(Ok(_)))); } + +fn boxed_generic_usage<T: crate::GattTypes>(central: Box<dyn crate::Central<T>>) { + use crate::client::PeerServiceHandle; + + let mut noop_cx = futures::task::Context::from_waker(futures::task::noop_waker_ref()); + let _stream = central.scan(&[]); + let connect_fut = central.connect(PeerId(1)); + + futures::pin_mut!(connect_fut); + let Poll::Ready(Ok(client)) = connect_fut.poll(&mut noop_cx) else { + panic!("Connect should be ready Ok"); + }; + + let client_boxed: Box<dyn crate::client::Client<T>> = Box::new(client); + + let find_serv_fut = client_boxed.find_service(Uuid::from_u16(0)); + + futures::pin_mut!(find_serv_fut); + let Poll::Ready(Ok(services)) = find_serv_fut.poll(&mut noop_cx) else { + panic!("Expected services future to resolve"); + }; + + assert_eq!(services.len(), 1); + + let connect_service_fut = services[0].connect(); + + futures::pin_mut!(connect_service_fut); + let Poll::Ready(Ok(service)) = connect_service_fut.poll(&mut noop_cx) else { + panic!("Expected service to connect"); + }; + + let _service_box: Box<dyn crate::client::PeerService<T>> = Box::new(service); +} + +#[test] +fn central_dynamic_usage() { + let central: Box<dyn crate::Central<FakeTypes>> = Box::new(FakeCentral::default()); + + boxed_generic_usage(central); +}
diff --git a/rust/bt-gatt/src/types/mod.rs b/rust/bt-gatt/src/types/mod.rs index d07c186..85c7a0a 100644 --- a/rust/bt-gatt/src/types/mod.rs +++ b/rust/bt-gatt/src/types/mod.rs
@@ -143,7 +143,7 @@ #[error("scan failed: {0}")] ScanFailed(String), #[error("another error: {0}")] - Other(#[from] Box<dyn std::error::Error + Send>), + Other(#[from] Box<dyn std::error::Error + Send + Sync>), #[error("GATT error: {0}")] Gatt(#[from] GattError), }