bt_gatt/
client.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::types::*;
6
7use bt_common::{PeerId, Uuid};
8
9/// GATT Client connected to a particular peer.
10/// Holding a struct that implements this should attempt to maintain a LE
11/// connection to the peer.
12pub trait Client<T: crate::GattTypes> {
13    /// The ID of the peer this is connected to.
14    fn peer_id(&self) -> PeerId;
15
16    /// Find services by UUID on the peer.
17    /// This may cause as much as a full discovery of all services on the peer
18    /// if the stack deems it appropriate.
19    /// Service information should be up to date at the time returned.
20    fn find_service(&self, uuid: Uuid) -> T::FindServicesFut;
21}
22
23pub trait PeerServiceHandle<T: crate::GattTypes> {
24    fn uuid(&self) -> Uuid;
25    fn is_primary(&self) -> bool;
26    fn connect(&self) -> T::ServiceConnectFut;
27}
28
29/// Implement when a type can be deserialized from a characteristic value.
30pub trait FromCharacteristic: Sized {
31    const UUID: Uuid;
32
33    /// Create this type from a Characteristic and an initial value.
34    fn from_chr(
35        characteristic: Characteristic,
36        value: &[u8],
37    ) -> ::core::result::Result<Self, bt_common::packet_encoding::Error>;
38
39    /// Attempt to update the type when supplied with the `new_value`, which may
40    /// or may not be the complete value.
41    fn update(
42        &mut self,
43        new_value: &[u8],
44    ) -> ::core::result::Result<&mut Self, bt_common::packet_encoding::Error>;
45
46    /// Attempt to read a characteristic if it matches the provided
47    /// characteristic UUID.
48    fn try_read<T: crate::GattTypes>(
49        characteristic: Characteristic,
50        service: &T::PeerService,
51    ) -> impl futures::Future<Output = ::core::result::Result<Self, Error>> {
52        async move {
53            if characteristic.uuid != Self::UUID {
54                return Err(Error::ScanFailed("Wrong UUID".to_owned()));
55            }
56            let mut buf = [0; 128];
57            let (bytes, mut truncated) =
58                service.read_characteristic(&characteristic.handle, 0, &mut buf).await?;
59            let mut vec;
60            let buf_ptr = if truncated {
61                vec = Vec::with_capacity(bytes);
62                vec.copy_from_slice(&buf[..bytes]);
63                while truncated {
64                    let (bytes, still_truncated) = service
65                        .read_characteristic(&characteristic.handle, vec.len() as u16, &mut buf)
66                        .await?;
67                    vec.extend_from_slice(&buf[..bytes]);
68                    truncated = still_truncated;
69                }
70                &vec[..]
71            } else {
72                &buf[..bytes]
73            };
74            Self::from_chr(characteristic, buf_ptr).map_err(Into::into)
75        }
76    }
77}
78
79#[derive(Debug, Clone)]
80pub struct CharacteristicNotification {
81    pub handle: Handle,
82    pub value: Vec<u8>,
83    pub maybe_truncated: bool,
84}
85
86/// A connection to a GATT Service on a Peer.
87/// All operations are done synchronously.
88pub trait PeerService<T: crate::GattTypes> {
89    /// Discover characteristics on this service.
90    /// If `uuid` is provided, only the characteristics matching `uuid` will be
91    /// returned. This operation may use either the Discovery All
92    /// Characteristics of a Service or Discovery Characteristic by UUID
93    /// procedures, regardless of `uuid`.
94    fn discover_characteristics(&self, uuid: Option<Uuid>) -> T::CharacteristicDiscoveryFut;
95
96    /// Read a characteristic into a buffer, given the handle within the
97    /// service. On success, returns the size read and whether the value may
98    /// have been truncated. By default this will try to use a long read if
99    /// the `buf` is larger than a normal read will allow (22 bytes) or if
100    /// the offset is non-zero.
101    fn read_characteristic<'a>(
102        &self,
103        handle: &Handle,
104        offset: u16,
105        buf: &'a mut [u8],
106    ) -> T::ReadFut<'a>;
107
108    fn write_characteristic<'a>(
109        &self,
110        handle: &Handle,
111        mode: WriteMode,
112        offset: u16,
113        buf: &'a [u8],
114    ) -> T::WriteFut<'a>;
115
116    fn read_descriptor<'a>(
117        &self,
118        handle: &Handle,
119        offset: u16,
120        buf: &'a mut [u8],
121    ) -> T::ReadFut<'a>;
122
123    fn write_descriptor<'a>(&self, handle: &Handle, offset: u16, buf: &'a [u8]) -> T::WriteFut<'a>;
124
125    /// Subscribe to updates on a Characteristic.
126    /// Either notifications or indications will be enabled depending on the
127    /// properties available, with indications preferred if they are
128    /// supported. Fails if the Characteristic doesn't support indications
129    /// or notifications. Errors are delivered through an Err item in the
130    /// stream. This will often write to the Client Characteristic
131    /// Configuration descriptor for the Characteristic subscribed to.
132    /// Updates sent from the peer will be delivered to the Stream returned.
133    fn subscribe(&self, handle: &Handle) -> T::NotificationStream;
134
135    // TODO: Find included services
136}
137
138/// Convenience class for communicating with characteristics on a remote peer.
139pub struct ServiceCharacteristic<'a, T: crate::GattTypes> {
140    service: &'a T::PeerService,
141    characteristic: Characteristic,
142    uuid: Uuid,
143}
144
145impl<'a, T: crate::GattTypes> ServiceCharacteristic<'a, T> {
146    pub async fn find(
147        service: &'a T::PeerService,
148        uuid: Uuid,
149    ) -> Result<Vec<ServiceCharacteristic<'a, T>>> {
150        let chrs = service.discover_characteristics(Some(uuid)).await?;
151        Ok(chrs.into_iter().map(|characteristic| Self { service, characteristic, uuid }).collect())
152    }
153
154    pub async fn read(&self, buf: &mut [u8]) -> Result<usize> {
155        self.service.read_characteristic(self.handle(), 0, buf).await.map(|(bytes, _)| bytes)
156    }
157
158    pub fn uuid(&self) -> Uuid {
159        self.uuid
160    }
161
162    pub fn handle(&self) -> &Handle {
163        &self.characteristic.handle
164    }
165
166    pub fn characteristic(&self) -> &Characteristic {
167        &self.characteristic
168    }
169}