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, RemoteServiceMarker,
12    RemoteServiceProxy, RemoteServiceRequest, RemoteServiceRequestStream, ServiceHandle,
13};
14use fuchsia_bluetooth::types::Uuid;
15use zx::MonotonicDuration;
16
17/// Provides a simple mock implementation of `fuchsia.bluetooth.gatt2.RemoteService`.
18pub struct RemoteServiceMock {
19    stream: RemoteServiceRequestStream,
20    timeout: MonotonicDuration,
21}
22
23impl RemoteServiceMock {
24    pub fn new(
25        timeout: MonotonicDuration,
26    ) -> Result<(RemoteServiceProxy, RemoteServiceMock), Error> {
27        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<RemoteServiceMarker>();
28        Ok((proxy, RemoteServiceMock { stream, timeout }))
29    }
30
31    pub fn from_stream(
32        stream: RemoteServiceRequestStream,
33        timeout: MonotonicDuration,
34    ) -> RemoteServiceMock {
35        RemoteServiceMock { stream, timeout }
36    }
37
38    pub async fn expect_discover_characteristics(
39        &mut self,
40        characteristics: &Vec<Characteristic>,
41    ) -> Result<(), Error> {
42        expect_call(&mut self.stream, self.timeout, move |req| match req {
43            RemoteServiceRequest::DiscoverCharacteristics { responder } => {
44                match responder.send(characteristics) {
45                    Ok(_) => Ok(Status::Satisfied(())),
46                    Err(e) => Err(e.into()),
47                }
48            }
49            _ => Ok(Status::Pending),
50        })
51        .await
52    }
53
54    /// Wait until a Read By Type message is received with the given `uuid`. `result` will be sent
55    /// in response to the matching FIDL request.
56    pub async fn expect_read_by_type(
57        &mut self,
58        expected_uuid: Uuid,
59        result: Result<&[ReadByTypeResult], gatt2::Error>,
60    ) -> Result<(), Error> {
61        let expected_uuid: FidlUuid = expected_uuid.into();
62        expect_call(&mut self.stream, self.timeout, move |req| {
63            if let RemoteServiceRequest::ReadByType { uuid, responder } = req {
64                if uuid == expected_uuid {
65                    responder.send(result)?;
66                    Ok(Status::Satisfied(()))
67                } else {
68                    // Send error to unexpected request.
69                    responder.send(Err(fidl_fuchsia_bluetooth_gatt2::Error::UnlikelyError))?;
70                    Ok(Status::Pending)
71                }
72            } else {
73                Ok(Status::Pending)
74            }
75        })
76        .await
77    }
78
79    pub async fn expect_register_characteristic_notifier(
80        &mut self,
81        handle: Handle,
82    ) -> Result<ClientEnd<CharacteristicNotifierMarker>, Error> {
83        expect_call(&mut self.stream, self.timeout, move |req| match req {
84            RemoteServiceRequest::RegisterCharacteristicNotifier {
85                handle: h,
86                notifier,
87                responder,
88            } => {
89                if h == handle {
90                    responder.send(Ok(()))?;
91                    Ok(Status::Satisfied(notifier))
92                } else {
93                    responder.send(Err(gatt2::Error::InvalidHandle))?;
94                    Ok(Status::Pending)
95                }
96            }
97            _ => Ok(Status::Pending),
98        })
99        .await
100    }
101}
102
103/// Mock for the fuchsia.bluetooth.gatt2/Client server. Can be used to expect and intercept requests
104/// to connect to GATT services.
105pub struct ClientMock {
106    stream: ClientRequestStream,
107    timeout: MonotonicDuration,
108}
109
110impl ClientMock {
111    pub fn new(timeout: MonotonicDuration) -> Result<(ClientProxy, ClientMock), Error> {
112        let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<ClientMarker>();
113        Ok((proxy, ClientMock { stream, timeout }))
114    }
115
116    pub async fn expect_connect_to_service(
117        &mut self,
118        handle: ServiceHandle,
119    ) -> Result<(ClientControlHandle, ServerEnd<RemoteServiceMarker>), Error> {
120        expect_call(&mut self.stream, self.timeout, move |req| match req {
121            ClientRequest::ConnectToService { handle: h, service, control_handle }
122                if h == handle =>
123            {
124                Ok(Status::Satisfied((control_handle, service)))
125            }
126            _ => Ok(Status::Pending),
127        })
128        .await
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use crate::timeout_duration;
136    use futures::join;
137
138    #[fuchsia_async::run_until_stalled(test)]
139    async fn test_expect_read_by_type() {
140        let (proxy, mut mock) =
141            RemoteServiceMock::new(timeout_duration()).expect("failed to create mock");
142        let uuid = Uuid::new16(0x180d);
143        let result = Ok(&[][..]);
144
145        let fidl_uuid: FidlUuid = uuid.clone().into();
146        let read_by_type_fut = proxy.read_by_type(&fidl_uuid);
147        let expect_fut = mock.expect_read_by_type(uuid, result);
148
149        let (read_by_type_result, expect_result) = join!(read_by_type_fut, expect_fut);
150        let _ = read_by_type_result.expect("read by type request failed");
151        let _ = expect_result.expect("expectation not satisfied");
152    }
153}