| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| anyhow::format_err, |
| fidl::endpoints::ClientEnd, |
| fidl_fuchsia_bluetooth::{self as fbt, DeviceClass}, |
| fidl_fuchsia_bluetooth_control::{ |
| self as control, HostData, InputCapabilityType, OutputCapabilityType, |
| PairingDelegateMarker, PairingOptions, |
| }, |
| fidl_fuchsia_bluetooth_host::{HostEvent, HostProxy}, |
| fuchsia_bluetooth::{ |
| inspect::Inspectable, |
| types::{BondingData, HostInfo, Peer, PeerId}, |
| }, |
| fuchsia_syslog::{fx_log_err, fx_log_info}, |
| futures::{Future, FutureExt, StreamExt}, |
| parking_lot::RwLock, |
| pin_utils::pin_mut, |
| std::{convert::TryInto, path::PathBuf, sync::Arc}, |
| }; |
| |
| use crate::types::{self, from_fidl_result, from_fidl_status, Error}; |
| |
| pub struct HostDevice { |
| pub path: PathBuf, |
| host: HostProxy, |
| info: Inspectable<HostInfo>, |
| } |
| |
| // Many HostDevice methods return impl Future rather than being implemented as `async`. This has an |
| // important behavioral difference in that the function body is triggered immediately when called. |
| // |
| // If they were instead declared async, the function body would not be executed until the first time |
| // the future was polled. |
| impl HostDevice { |
| pub fn new(path: PathBuf, host: HostProxy, info: Inspectable<HostInfo>) -> Self { |
| HostDevice { path, host, info } |
| } |
| |
| pub fn get_host(&self) -> &HostProxy { |
| &self.host |
| } |
| |
| pub fn set_host_pairing_delegate( |
| &self, |
| input: InputCapabilityType, |
| output: OutputCapabilityType, |
| delegate: ClientEnd<PairingDelegateMarker>, |
| ) { |
| let _ = self.host.set_pairing_delegate(input, output, Some(delegate)); |
| } |
| |
| pub fn get_info(&self) -> &HostInfo { |
| &self.info |
| } |
| |
| pub fn set_name(&self, mut name: String) -> impl Future<Output = types::Result<()>> { |
| self.host.set_local_name(&mut name).map(from_fidl_result) |
| } |
| |
| pub fn set_device_class( |
| &self, |
| mut cod: DeviceClass, |
| ) -> impl Future<Output = types::Result<()>> { |
| self.host.set_device_class(&mut cod).map(from_fidl_result) |
| } |
| |
| pub fn start_discovery(&mut self) -> impl Future<Output = types::Result<()>> { |
| self.host.start_discovery().map(from_fidl_result) |
| } |
| |
| pub fn connect(&mut self, id: PeerId) -> impl Future<Output = types::Result<()>> { |
| let mut id: fbt::PeerId = id.into(); |
| self.host.connect(&mut id).map(from_fidl_result) |
| } |
| |
| pub fn disconnect(&mut self, id: PeerId) -> impl Future<Output = types::Result<()>> { |
| let mut id: fbt::PeerId = id.into(); |
| self.host.disconnect(&mut id).map(from_fidl_result) |
| } |
| |
| pub fn pair( |
| &mut self, |
| id: PeerId, |
| options: PairingOptions, |
| ) -> impl Future<Output = types::Result<()>> { |
| let mut id: fbt::PeerId = id.into(); |
| self.host.pair(&mut id, options).map(from_fidl_result) |
| } |
| |
| pub fn forget(&mut self, id: PeerId) -> impl Future<Output = types::Result<()>> { |
| let mut id: fbt::PeerId = id.into(); |
| self.host.forget(&mut id).map(from_fidl_result) |
| } |
| |
| pub fn close(&self) -> types::Result<()> { |
| self.host.close().map_err(|e| e.into()) |
| } |
| |
| pub fn restore_bonds( |
| &self, |
| bonds: Vec<BondingData>, |
| ) -> impl Future<Output = types::Result<()>> { |
| let mut bonds: Vec<_> = bonds.into_iter().map(control::BondingData::from).collect(); |
| self.host.add_bonded_devices(&mut bonds.iter_mut()).map(from_fidl_status) |
| } |
| |
| pub fn set_connectable(&self, value: bool) -> impl Future<Output = types::Result<()>> { |
| self.host.set_connectable(value).map(from_fidl_result) |
| } |
| |
| pub fn stop_discovery(&self) -> types::Result<()> { |
| self.host.stop_discovery().map_err(|e| e.into()) |
| } |
| |
| pub fn set_discoverable(&self, discoverable: bool) -> impl Future<Output = types::Result<()>> { |
| self.host.set_discoverable(discoverable).map(from_fidl_result) |
| } |
| |
| pub fn set_local_data(&self, mut data: HostData) -> types::Result<()> { |
| self.host.set_local_data(&mut data).map_err(|e| e.into()) |
| } |
| |
| pub fn enable_privacy(&self, enable: bool) -> types::Result<()> { |
| self.host.enable_privacy(enable).map_err(Error::from) |
| } |
| |
| pub fn enable_background_scan(&self, enable: bool) -> types::Result<()> { |
| self.host.enable_background_scan(enable).map_err(Error::from) |
| } |
| } |
| |
| pub trait HostListener { |
| fn on_peer_updated(&mut self, peer: Peer); |
| fn on_peer_removed(&mut self, id: PeerId); |
| |
| type HostBondFut: Future<Output = Result<(), anyhow::Error>>; |
| fn on_new_host_bond(&mut self, data: BondingData) -> Self::HostBondFut; |
| } |
| |
| // TODO(armansito): It feels odd to expose it only so it is available to test/host_device.rs. It |
| // might be better to move the host_device tests into this module. |
| pub async fn refresh_host_info(host: Arc<RwLock<HostDevice>>) -> types::Result<()> { |
| let proxy = host.read().host.clone(); |
| let info = proxy.watch_state().await?; |
| host.write().info.update(info.try_into()?); |
| Ok(()) |
| } |
| |
| /// Monitors updates from a bt-host device and notifies `listener`. The returned Future represents |
| /// a task that never ends in successful operation and only returns in case of a failure to |
| /// communicate with the bt-host device. |
| pub async fn watch_events<H: HostListener + Clone>( |
| listener: H, |
| host: Arc<RwLock<HostDevice>>, |
| ) -> types::Result<()> { |
| let handle_fidl = handle_fidl_events(listener.clone(), host.clone()); |
| let watch_peers = watch_peers(listener, host.clone()); |
| let watch_state = watch_state(host); |
| pin_mut!(handle_fidl); |
| pin_mut!(watch_peers); |
| pin_mut!(watch_state); |
| futures::select! { |
| res1 = handle_fidl.fuse() => res1, |
| res2 = watch_peers.fuse() => res2, |
| res3 = watch_state.fuse() => res3, |
| } |
| } |
| |
| async fn handle_fidl_events<H: HostListener>( |
| mut listener: H, |
| host: Arc<RwLock<HostDevice>>, |
| ) -> types::Result<()> { |
| let mut stream = host.read().host.take_event_stream(); |
| while let Some(event) = stream.next().await { |
| match event? { |
| HostEvent::OnNewBondingData { data } => { |
| fx_log_info!("Received bonding data"); |
| let data: BondingData = match data.try_into() { |
| Err(e) => { |
| fx_log_err!("Invalid bonding data, ignoring: {:#?}", e); |
| continue; |
| } |
| Ok(data) => data, |
| }; |
| if let Err(e) = listener.on_new_host_bond(data.into()).await { |
| fx_log_err!("Failed to persist bonding data: {:#?}", e); |
| } |
| } |
| }; |
| } |
| Err(types::Error::InternalError(format_err!("Host FIDL event stream terminated"))) |
| } |
| |
| async fn watch_peers<H: HostListener + Clone>( |
| mut listener: H, |
| host: Arc<RwLock<HostDevice>>, |
| ) -> types::Result<()> { |
| let proxy = host.read().host.clone(); |
| loop { |
| let (updated, removed) = proxy.watch_peers().await?; |
| for peer in updated.into_iter() { |
| listener.on_peer_updated(peer.try_into()?); |
| } |
| for id in removed.into_iter() { |
| listener.on_peer_removed(id.into()); |
| } |
| } |
| } |
| |
| async fn watch_state(host: Arc<RwLock<HostDevice>>) -> types::Result<()> { |
| loop { |
| refresh_host_info(host.clone()).await?; |
| } |
| } |