1use 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
20pub 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 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 responder.send(Err(gatt2::Error::UnlikelyError))?;
73 Ok(Status::Pending)
74 }
75 } else {
76 Ok(Status::Pending)
77 }
78 })
79 .await
80 }
81
82 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
135pub struct ClientMock {
138 stream: ClientRequestStream,
139 timeout: MonotonicDuration,
140 services: Vec<gatt2::ServiceInfo>,
141 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 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}