1use async_utils::hanging_get::client::HangingGetStream;
6use core::pin::Pin;
7use core::task::{Context, Poll};
8use fidl_fuchsia_bluetooth_sys as sys;
9use fuchsia_bluetooth::Error;
10use fuchsia_bluetooth::types::{Address, HostInfo};
11use futures::ready;
12use futures::stream::{FusedStream, Stream, StreamExt};
13use log::trace;
14
15#[derive(Clone, Copy, Debug, PartialEq)]
17pub enum HostEvent {
18 Discoverable(bool),
20 NewActiveHost { discoverable: bool },
22 NotAvailable,
24}
25
26#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)]
28enum Terminated {
29 Active,
30 ShuttingDown,
31 Done,
32}
33
34pub struct HostWatcher {
38 host_updates: HangingGetStream<sys::HostWatcherProxy, Vec<sys::HostInfo>>,
40 active_host: Option<HostInfo>,
42 terminated: Terminated,
44}
45
46impl HostWatcher {
47 pub fn new(host_watcher: sys::HostWatcherProxy) -> Self {
50 let host_updates =
51 HangingGetStream::new_with_fn_ptr(host_watcher, sys::HostWatcherProxy::watch);
52 Self { host_updates, active_host: None, terminated: Terminated::Active }
53 }
54
55 #[cfg(test)]
56 fn new_with_state(host_watcher: sys::HostWatcherProxy, active_host: Option<HostInfo>) -> Self {
57 let mut this = Self::new(host_watcher);
58 this.active_host = active_host;
59 this
60 }
61
62 pub fn set_active_host(&mut self, host: HostInfo) {
63 self.active_host = Some(host);
64 }
65
66 fn compare(&self, new: &Option<HostInfo>) -> Option<HostEvent> {
69 trace!("Current ({:?}) - New ({:?})", self.active_host, new);
70 match (&self.active_host, new) {
71 (None, Some(info)) => {
72 Some(HostEvent::NewActiveHost { discoverable: info.discoverable })
73 }
74 (Some(_), None) => Some(HostEvent::NotAvailable),
75 (Some(current_info), Some(new_info)) if current_info.id != new_info.id => {
76 Some(HostEvent::NewActiveHost { discoverable: new_info.discoverable })
77 }
78 (Some(current_info), Some(new_info))
79 if current_info.discoverable != new_info.discoverable =>
80 {
81 Some(HostEvent::Discoverable(new_info.discoverable))
83 }
84 _ => None, }
86 }
87
88 fn handle_host_watcher_update(
89 &mut self,
90 update: Vec<sys::HostInfo>,
91 ) -> Result<Option<HostEvent>, Error> {
92 let maybe_active = update
93 .iter()
94 .find(|info| info.active.unwrap_or_default())
95 .map(HostInfo::try_from)
96 .transpose()?;
97
98 let event = self.compare(&maybe_active);
99 self.active_host = maybe_active;
100 return Ok(event);
101 }
102
103 pub fn addresses(&self) -> Option<Vec<Address>> {
105 self.active_host.as_ref().map(|host| host.addresses.clone())
106 }
107
108 pub fn public_address(&self) -> Option<Address> {
110 self.active_host
111 .as_ref()
112 .map(|host| {
113 host.addresses.iter().find(|addr| matches!(addr, Address::Public(_))).copied()
114 })
115 .flatten()
116 }
117
118 pub fn ble_address(&self) -> Option<Address> {
120 self.addresses()
121 .map(|addrs| addrs.into_iter().find(|addr| matches!(addr, Address::Random(_))))
122 .flatten()
123 .or_else(|| self.public_address())
124 }
125
126 pub fn is_valid_address(&self, address_bytes: &[u8; 6]) -> bool {
129 self.active_host
130 .as_ref()
131 .is_some_and(|host| host.addresses.iter().any(|addr| addr.bytes() == address_bytes))
132 }
133
134 pub fn pairing_mode(&self) -> Option<bool> {
136 self.active_host.as_ref().map(|h| h.discoverable)
137 }
138
139 pub fn local_name(&self) -> Option<String> {
140 self.active_host.as_ref().map(|h| h.local_name.clone()).flatten()
141 }
142}
143
144impl Stream for HostWatcher {
145 type Item = Result<HostEvent, Error>;
146
147 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
148 match self.terminated {
149 Terminated::Active => {}
150 Terminated::ShuttingDown => {
151 self.terminated = Terminated::Done;
152 return Poll::Ready(None);
153 }
154 Terminated::Done => panic!("Cannot poll a terminated stream"),
155 }
156
157 loop {
160 let result = ready!(self.host_updates.poll_next_unpin(cx));
161
162 let result = match result {
163 Some(Ok(update)) => match self.handle_host_watcher_update(update) {
164 Ok(None) => continue,
165 Ok(Some(request)) => Some(Ok(request)),
166 Err(e) => Some(Err(e)),
167 },
168 Some(Err(e)) => {
169 self.terminated = Terminated::ShuttingDown;
172 Some(Err(e.into()))
173 }
174 None => None,
175 };
176 if result.is_none() {
177 trace!("HostWatcher hanging-get exhausted");
178 self.terminated = Terminated::Done;
179 }
180
181 return Poll::Ready(result);
182 }
183 }
184}
185
186impl FusedStream for HostWatcher {
187 fn is_terminated(&self) -> bool {
188 self.terminated == Terminated::Done
189 }
190}
191
192#[cfg(test)]
193pub(crate) mod tests {
194 use super::*;
195
196 use assert_matches::assert_matches;
197 use async_utils::PollExt;
198 use fuchsia_async as fasync;
199 use fuchsia_bluetooth::types::{HostId, example_host};
200 use std::pin::pin;
201
202 #[track_caller]
203 fn expect_watch_request(
204 exec: &mut fasync::TestExecutor,
205 stream: &mut sys::HostWatcherRequestStream,
206 ) -> sys::HostWatcherWatchResponder {
207 let expect_fut = stream.select_next_some();
208 let mut expect_fut = pin!(expect_fut);
209 exec.run_until_stalled(&mut expect_fut)
210 .expect("ready")
211 .expect("valid FIDL request")
212 .into_watch()
213 .expect("Watch request")
214 }
215
216 #[fuchsia::test]
217 fn is_valid_address_matches_active_host_addresses() {
218 let _exec = fasync::TestExecutor::new();
219
220 let (proxy, _server) = fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
221 let mut watcher = HostWatcher::new(proxy);
222
223 let public = Address::Public([1, 2, 3, 4, 5, 6]);
224 let random = Address::Random([2, 3, 2, 3, 2, 3]);
225 let unknown = [9, 9, 9, 9, 9, 9];
226
227 assert!(!watcher.is_valid_address(public.bytes()));
229
230 let mut host =
232 example_host(HostId(1), true, true);
233 host.addresses.as_mut().unwrap().push(random.into());
234 watcher.set_active_host(host.try_into().unwrap());
235
236 assert!(watcher.is_valid_address(public.bytes()));
237 assert!(watcher.is_valid_address(random.bytes()));
238 assert!(!watcher.is_valid_address(&unknown));
239 }
240
241 #[fuchsia::test]
242 fn update_with_no_hosts_stream_is_pending() {
243 let mut exec = fasync::TestExecutor::new();
244
245 let (proxy, mut server) =
246 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
247 let mut watcher = HostWatcher::new(proxy);
248
249 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
250
251 let watch_responder = expect_watch_request(&mut exec, &mut server);
252 let _ = watch_responder.send(&[]).unwrap();
254
255 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
258 }
259
260 #[fuchsia::test]
261 fn update_with_active_host_change_yields_items() {
262 let mut exec = fasync::TestExecutor::new();
263
264 let (proxy, mut server) =
265 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
266 let mut watcher = HostWatcher::new(proxy);
267
268 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
269
270 let watch_responder = expect_watch_request(&mut exec, &mut server);
271 let host1 =
273 example_host(HostId(1), true, false);
274 let _ = watch_responder.send(&[host1]).unwrap();
275
276 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update ready");
278 assert_matches!(item, Some(Ok(HostEvent::NewActiveHost { discoverable: false })));
279
280 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
282 let watch_responder = expect_watch_request(&mut exec, &mut server);
283 let _ = watch_responder.send(&[]).unwrap();
285
286 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update");
288 assert_matches!(item, Some(Ok(HostEvent::NotAvailable)));
289 }
290
291 #[fuchsia::test]
292 fn active_to_no_active_host_update_yields_event() {
293 let mut exec = fasync::TestExecutor::new();
294
295 let (proxy, mut server) =
296 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
297 let host = example_host(HostId(1), true, false);
298 let mut watcher = HostWatcher::new_with_state(proxy, host.try_into().ok());
300 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
301
302 let watch_responder = expect_watch_request(&mut exec, &mut server);
303 let host2 =
305 example_host(HostId(2), false, false);
306 let host3 =
307 example_host(HostId(3), false, false);
308 let _ = watch_responder.send(&[host2, host3]).unwrap();
309
310 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update");
313 assert_matches!(item, Some(Ok(HostEvent::NotAvailable)));
314 }
315
316 #[fuchsia::test]
317 fn update_with_no_active_host_changes_is_pending() {
318 let mut exec = fasync::TestExecutor::new();
319
320 let (proxy, mut server) =
321 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
322 let mut host1 =
324 example_host(HostId(1), true, false);
325 let mut watcher = HostWatcher::new_with_state(proxy, host1.clone().try_into().ok());
326 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
327
328 let watch_responder = expect_watch_request(&mut exec, &mut server);
329 let host2 =
331 example_host(HostId(2), false, false);
332 let _ = watch_responder.send(&[host1.clone(), host2]).unwrap();
333
334 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
336
337 let watch_responder = expect_watch_request(&mut exec, &mut server);
338 host1.discovering = Some(false);
340 host1.local_name = Some("123".to_string());
341 let _ = watch_responder.send(&[host1]).unwrap();
342
343 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
345 }
346
347 #[fuchsia::test]
348 fn update_with_active_host_discoverable_change_yields_item() {
349 let mut exec = fasync::TestExecutor::new();
350
351 let (proxy, mut server) =
352 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
353 let mut host1 =
354 example_host(HostId(1), true, false);
355 let mut watcher = HostWatcher::new_with_state(proxy, host1.clone().try_into().ok());
357 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
358
359 let watch_responder = expect_watch_request(&mut exec, &mut server);
360 host1.discoverable = Some(true);
362 let _ = watch_responder.send(&[host1.clone()]).unwrap();
363
364 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update");
365 assert_matches!(item, Some(Ok(HostEvent::Discoverable(true))));
366 }
367
368 #[fuchsia::test]
369 fn update_with_new_active_host_yields_item() {
370 let mut exec = fasync::TestExecutor::new();
371
372 let (proxy, mut server) =
373 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
374 let host1 =
375 example_host(HostId(1), true, false);
376 let mut watcher = HostWatcher::new_with_state(proxy, host1.clone().try_into().ok());
378 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
379
380 let watch_responder = expect_watch_request(&mut exec, &mut server);
382 let host2 =
383 example_host(HostId(2), true, false);
384 let _ = watch_responder.send(&[host2]).unwrap();
385
386 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update");
387 assert_matches!(item, Some(Ok(HostEvent::NewActiveHost { discoverable: false })));
388
389 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
391 let watch_responder = expect_watch_request(&mut exec, &mut server);
392 let host3 = example_host(HostId(3), true, true);
393 let _ = watch_responder.send(&[host3]).unwrap();
394
395 let item = exec.run_until_stalled(&mut watcher.next()).expect("host update");
396 assert_matches!(item, Some(Ok(HostEvent::NewActiveHost { discoverable: true })));
397 }
398
399 #[fuchsia::test]
400 fn invalidly_formatted_host_returns_error_stream_item() {
401 let mut exec = fasync::TestExecutor::new();
402
403 let (proxy, mut server) =
404 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
405 let mut watcher = HostWatcher::new(proxy);
406 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
407 assert!(!watcher.is_terminated());
408
409 let watch_responder = expect_watch_request(&mut exec, &mut server);
410 let invalid_host =
412 sys::HostInfo { id: Some(HostId(12).into()), active: Some(true), ..Default::default() };
413 let _ = watch_responder.send(&[invalid_host]).unwrap();
414
415 let item = exec.run_until_stalled(&mut watcher.next()).expect("host watcher update");
416 assert_matches!(item, Some(Err(_)));
417 assert!(!watcher.is_terminated());
418 }
419
420 #[fuchsia::test]
421 fn closing_fidl_server_terminates_host_watcher() {
422 let mut exec = fasync::TestExecutor::new();
423
424 let (proxy, mut server) =
425 fidl::endpoints::create_proxy_and_stream::<sys::HostWatcherMarker>();
426 let mut watcher = HostWatcher::new(proxy);
427 let _ = exec.run_until_stalled(&mut watcher.next()).expect_pending("No updates");
428 assert!(!watcher.is_terminated());
429
430 let watch_responder = expect_watch_request(&mut exec, &mut server);
431 let _ = watch_responder.send(&[]).unwrap();
432
433 drop(server);
437
438 let item = exec.run_until_stalled(&mut watcher.next()).expect("host watcher FIDL error");
439 assert_matches!(item, Some(Err(_)));
440 assert!(!watcher.is_terminated());
441
442 let item = exec.run_until_stalled(&mut watcher.next()).expect("host watcher termination");
443 assert_matches!(item, None);
444 assert!(watcher.is_terminated());
445 }
446}