[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),
}