bt_fidl_mocks/
gatt2.rs

1// Copyright 2022 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::expect::{expect_call, Status};
6use anyhow::Error;
7use fidl::endpoints::{ClientEnd, ServerEnd};
8use fidl_fuchsia_bluetooth::Uuid as FidlUuid;
9use fidl_fuchsia_bluetooth_gatt2::{
10    self as gatt2, Characteristic, CharacteristicNotifierMarker, ClientControlHandle, ClientMarker,
11    ClientProxy, ClientRequest, ClientRequestStream, Handle, ReadByTypeResult, ReadValue,
12    RemoteServiceMarker, RemoteServiceProxy, RemoteServiceRequest, RemoteServiceRequestStream,
13    ServiceHandle,
14};
15use fuchsia_bluetooth::types::Uuid;
16use log::info;
17use std::collections::HashSet;
18use zx::MonotonicDuration;
19
20/// Provides a simple mock implementation of `fuchsia.bluetooth.gatt2.RemoteService`.
21pub struct RemoteServiceMock {
22    stream: RemoteServiceRequestStream,
23    timeout: MonotonicDuration,
24}
25
26impl RemoteServiceMock {
27    pub fn new(
28        timeout: MonotonicDuration,
29    ) -> Result<(RemoteServiceProxy, RemoteServiceMock), Error> {
30        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<RemoteServiceMarker>();
31        Ok((proxy, RemoteServiceMock { stream, timeout }))
32    }
33
34    pub fn from_stream(
35        stream: RemoteServiceRequestStream,
36        timeout: MonotonicDuration,
37    ) -> RemoteServiceMock {
38        RemoteServiceMock { stream, timeout }
39    }
40
41    pub async fn expect_discover_characteristics(
42        &mut self,
43        characteristics: &Vec<Characteristic>,
44    ) -> Result<(), Error> {
45        expect_call(&mut self.stream, self.timeout, move |req| match req {
46            RemoteServiceRequest::DiscoverCharacteristics { responder } => {
47                match responder.send(characteristics) {
48                    Ok(_) => Ok(Status::Satisfied(())),
49                    Err(e) => Err(e.into()),
50                }
51            }
52            _ => Ok(Status::Pending),
53        })
54        .await
55    }
56
57    /// Wait until a Read By Type message is received with the given `uuid`. `result` will be sent
58    /// in response to the matching FIDL request.
59    pub async fn expect_read_by_type(
60        &mut self,
61        expected_uuid: Uuid,
62        result: Result<&[ReadByTypeResult], gatt2::Error>,
63    ) -> Result<(), Error> {
64        let expected_uuid: FidlUuid = expected_uuid.into();
65        expect_call(&mut self.stream, self.timeout, move |req| {
66            if let RemoteServiceRequest::ReadByType { uuid, responder } = req {
67                if uuid == expected_uuid {
68                    responder.send(result)?;
69                    Ok(Status::Satisfied(()))
70                } else {
71                    // Send error to unexpected request.
72                    responder.send(Err(gatt2::Error::UnlikelyError))?;
73                    Ok(Status::Pending)
74                }
75            } else {
76                Ok(Status::Pending)
77            }
78        })
79        .await
80    }
81
82    /// Wait until a Read Characteristic request is received with the given handle, then `result`
83    /// will be sent.
84    pub async fn expect_read_characteristic(
85        &mut self,
86        expected_handle: u64,
87        result: Result<&ReadValue, gatt2::Error>,
88    ) -> Result<(), Error> {
89        expect_call(&mut self.stream, self.timeout, move |req| match req {
90            RemoteServiceRequest::ReadCharacteristic { handle, options: _, responder } => {
91                if handle.value == expected_handle {
92                    responder.send(result)?;
93                    Ok(Status::Satisfied(()))
94                } else {
95                    responder.send(Err(gatt2::Error::UnlikelyError))?;
96                    Ok(Status::Pending)
97                }
98            }
99            x => {
100                info!("Received unexpected RemoteServiceRequest: {x:?}");
101                Ok(Status::Pending)
102            }
103        })
104        .await
105    }
106
107    pub async fn expect_register_characteristic_notifier(
108        &mut self,
109        handle: Handle,
110    ) -> Result<ClientEnd<CharacteristicNotifierMarker>, Error> {
111        expect_call(&mut self.stream, self.timeout, move |req| match req {
112            RemoteServiceRequest::RegisterCharacteristicNotifier {
113                handle: h,
114                notifier,
115                responder,
116            } => {
117                if h == handle {
118                    responder.send(Ok(()))?;
119                    Ok(Status::Satisfied(notifier))
120                } else {
121                    info!("Got RegisterCharacteristicNotifier for wrong handle: {h:?}, ignoring");
122                    responder.send(Err(gatt2::Error::InvalidHandle))?;
123                    Ok(Status::Pending)
124                }
125            }
126            x => {
127                info!("Received unexpected RemoteServiceRequest: {x:?}");
128                Ok(Status::Pending)
129            }
130        })
131        .await
132    }
133}
134
135/// Mock for the fuchsia.bluetooth.gatt2/Client server. Can be used to expect and intercept requests
136/// to connect to GATT services.
137pub struct ClientMock {
138    stream: ClientRequestStream,
139    timeout: MonotonicDuration,
140    services: Vec<gatt2::ServiceInfo>,
141    /// The handles of the services we have already returned.
142    returned_services: HashSet<u64>,
143    last_filter: Vec<fidl_fuchsia_bluetooth::Uuid>,
144}
145
146impl ClientMock {
147    pub fn new(timeout: MonotonicDuration) -> Result<(ClientProxy, ClientMock), Error> {
148        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ClientMarker>();
149        Ok((proxy, Self::from_stream(stream, timeout)))
150    }
151
152    pub fn from_stream(stream: ClientRequestStream, timeout: MonotonicDuration) -> Self {
153        Self {
154            stream,
155            timeout,
156            services: Vec::new(),
157            returned_services: HashSet::new(),
158            last_filter: vec![Uuid::new16(0xffff).into()],
159        }
160    }
161
162    pub fn add_service(&mut self, service: gatt2::ServiceInfo) {
163        self.services.push(service);
164    }
165
166    pub async fn expect_watch_services(&mut self) -> Result<(), Error> {
167        // The services we haven't returned before
168        let unseen_services: Vec<_> = self
169            .services
170            .iter()
171            .filter(|x| !self.returned_services.contains(&x.handle.unwrap().value))
172            .cloned()
173            .collect();
174        let all_services = self.services.clone();
175        let last_filter = self.last_filter.clone();
176        expect_call(&mut self.stream, self.timeout, |req| match req {
177            ClientRequest::WatchServices { uuids, responder } => {
178                let services = if uuids != last_filter {
179                    self.returned_services.clear();
180                    self.last_filter = uuids.clone();
181                    all_services.clone()
182                } else {
183                    unseen_services.clone()
184                };
185                if uuids.is_empty() {
186                    responder.send(services.as_slice(), &[])?;
187                    services.iter().for_each(|s| {
188                        let _ = self.returned_services.insert(s.handle.unwrap().value);
189                    });
190                } else {
191                    let matched: Vec<gatt2::ServiceInfo> = services
192                        .iter()
193                        .filter(|x| uuids.iter().find(|u| x.type_ == Some(**u)).is_some())
194                        .cloned()
195                        .collect();
196                    responder.send(matched.as_slice(), &[])?;
197                    matched.iter().for_each(|s| {
198                        let _ = self.returned_services.insert(s.handle.unwrap().value);
199                    });
200                }
201                Ok(Status::Satisfied(()))
202            }
203            x => {
204                info!("Received unexpected gatt2::Client Request: {x:?}");
205                Ok(Status::Pending)
206            }
207        })
208        .await
209    }
210
211    pub async fn expect_connect_to_service(
212        &mut self,
213        handle: ServiceHandle,
214    ) -> Result<(ClientControlHandle, ServerEnd<RemoteServiceMarker>), Error> {
215        expect_call(&mut self.stream, self.timeout, move |req| match req {
216            ClientRequest::ConnectToService { handle: h, service, control_handle }
217                if h == handle =>
218            {
219                Ok(Status::Satisfied((control_handle, service)))
220            }
221            _ => Ok(Status::Pending),
222        })
223        .await
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::timeout_duration;
231    use futures::join;
232
233    #[fuchsia::test]
234    async fn test_expect_read_by_type() {
235        let (proxy, mut mock) =
236            RemoteServiceMock::new(timeout_duration()).expect("failed to create mock");
237        let uuid = Uuid::new16(0x180d);
238        let result = Ok(&[][..]);
239
240        let fidl_uuid: FidlUuid = uuid.clone().into();
241        let read_by_type_fut = proxy.read_by_type(&fidl_uuid);
242        let expect_fut = mock.expect_read_by_type(uuid, result);
243
244        let (read_by_type_result, expect_result) = join!(read_by_type_fut, expect_fut);
245        let _ = read_by_type_result.expect("read by type request failed");
246        let _ = expect_result.expect("expectation not satisfied");
247    }
248
249    #[fuchsia::test]
250    async fn test_watch_services() {
251        let (proxy, mut mock) = ClientMock::new(timeout_duration()).expect("failed to create mock");
252
253        mock.add_service(gatt2::ServiceInfo {
254            handle: Some(gatt2::ServiceHandle { value: 1 }),
255            kind: Some(gatt2::ServiceKind::Primary),
256            type_: Some(Uuid::new16(0x100d).into()),
257            ..Default::default()
258        });
259
260        let expect_watch_services_fut = mock.expect_watch_services();
261        let watch_services_fut = proxy.watch_services(&[]);
262
263        let (expect_result, watch_result) = join!(expect_watch_services_fut, watch_services_fut);
264
265        let (services_updated, _services_removed) = watch_result.unwrap();
266        assert_eq!(services_updated.len(), 1);
267        assert!(expect_result.is_ok());
268
269        mock.add_service(gatt2::ServiceInfo {
270            handle: Some(gatt2::ServiceHandle { value: 2 }),
271            kind: Some(gatt2::ServiceKind::Primary),
272            type_: Some(Uuid::new16(0x100f).into()),
273            ..Default::default()
274        });
275
276        let expect_watch_services_fut = mock.expect_watch_services();
277        let watch_services_fut = proxy.watch_services(&[]);
278
279        let (expect_result, watch_result) = join!(expect_watch_services_fut, watch_services_fut);
280
281        let (services_updated, _services_removed) = watch_result.unwrap();
282        assert_eq!(services_updated.len(), 1);
283        assert!(services_updated[0].handle.is_some_and(|x| x.value == 2));
284        assert!(expect_result.is_ok());
285    }
286}