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