blob: 30d6f87df331bb62775195dd479172560debdc83 [file] [log] [blame]
// 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?;
}
}