blob: f5f1049e9102555ca6bc186b4e6f9e97ce347fdf [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, Context};
use fidl_fuchsia_bluetooth::{self as fbt, DeviceClass};
use fidl_fuchsia_bluetooth_host::{
DiscoverySessionMarker, DiscoverySessionProxy, HostEvent, HostProxy, HostStartDiscoveryRequest,
};
use fidl_fuchsia_bluetooth_sys as sys;
use fuchsia_async as fasync;
use fuchsia_bluetooth::inspect::Inspectable;
use fuchsia_bluetooth::types::{Address, BondingData, HostData, HostId, HostInfo, Peer, PeerId};
use fuchsia_sync::RwLock;
use futures::{future::try_join_all, Future, FutureExt, StreamExt, TryFutureExt};
use std::pin::pin;
use std::sync::{Arc, Weak};
use tracing::{debug, error, info, trace, warn};
#[cfg(test)]
use fidl_fuchsia_bluetooth_sys::TechnologyType;
use crate::{
build_config,
types::{self, from_fidl_result, Error},
};
/// When the host dispatcher requests being discoverable on a host device, the host device enables
/// discoverable and returns a HostDiscoverableSession. The discoverable state on the host device
/// persists until this session is dropped.
pub struct HostDiscoverableSession {
host: Weak<HostDeviceState>,
}
impl Drop for HostDiscoverableSession {
fn drop(&mut self) {
trace!("HostDiscoverableSession ended");
if let Some(host) = self.host.upgrade() {
let await_response = host.proxy.set_discoverable(false);
fasync::Task::spawn(async move {
if let Err(err) = await_response.await {
// TODO(https://fxbug.dev/42121837) - we should close the host channel if an error is returned
warn!("Unexpected error response when disabling discoverable: {:?}", err);
}
})
.detach();
}
}
}
#[derive(Clone)]
pub struct HostDevice(Arc<HostDeviceState>);
pub struct HostDeviceState {
// TODO(https://fxbug.dev/324954255): Remove device_path
device_path: String,
proxy: HostProxy,
info: RwLock<Inspectable<HostInfo>>,
}
/// A type for easy debug printing of the main identifiers for the host device, namely: The device
/// path, the host address and the host id.
#[derive(Clone, Debug)]
pub struct HostDebugIdentifiers {
// TODO(https://fxbug.dev/42165549)
#[allow(unused)]
id: HostId,
// TODO(https://fxbug.dev/42165549)
#[allow(unused)]
address: Address,
// TODO(https://fxbug.dev/42165549)
#[allow(unused)]
path: String,
}
// 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(device_path: String, proxy: HostProxy, info: Inspectable<HostInfo>) -> Self {
HostDevice(Arc::new(HostDeviceState { device_path, proxy, info: RwLock::new(info) }))
}
pub fn proxy(&self) -> &HostProxy {
&self.0.proxy
}
pub fn info(&self) -> HostInfo {
self.0.info.read().clone()
}
pub fn id(&self) -> HostId {
self.0.info.read().id.into()
}
pub fn public_address(&self) -> Address {
// The list of known Host Addresses is always nonempty. The Public address is guaranteed to
// be listed first.
self.0.info.read().addresses[0]
}
/// Convenience method to produce a type for easy debug printing of the main identifiers for the
/// host device, namely: The device path, the host address and the host id.
pub fn debug_identifiers(&self) -> HostDebugIdentifiers {
HostDebugIdentifiers {
id: self.id(),
address: self.public_address(),
path: self.path().to_string(),
}
}
pub fn path(&self) -> &str {
&self.0.device_path
}
pub fn set_name(&self, name: String) -> impl Future<Output = types::Result<()>> {
self.0.proxy.set_local_name(&name).map(from_fidl_result)
}
pub fn set_device_class(&self, dc: DeviceClass) -> impl Future<Output = types::Result<()>> {
self.0.proxy.set_device_class(&dc).map(from_fidl_result)
}
pub fn start_discovery(&self) -> types::Result<DiscoverySessionProxy> {
let (proxy, server) = fidl::endpoints::create_proxy::<DiscoverySessionMarker>().unwrap();
let result = self.0.proxy.start_discovery(HostStartDiscoveryRequest {
token: Some(server),
..Default::default()
});
result.map_err(Error::from).map(|_| proxy)
}
pub fn connect(&self, id: PeerId) -> impl Future<Output = types::Result<()>> {
let id: fbt::PeerId = id.into();
self.0.proxy.connect(&id).map(from_fidl_result)
}
pub fn disconnect(&self, id: PeerId) -> impl Future<Output = types::Result<()>> {
let id: fbt::PeerId = id.into();
self.0.proxy.disconnect(&id).map(from_fidl_result)
}
pub fn pair(
&self,
id: PeerId,
options: sys::PairingOptions,
) -> impl Future<Output = types::Result<()>> {
let id: fbt::PeerId = id.into();
self.0.proxy.pair(&id, &options).map(from_fidl_result)
}
pub fn forget(&self, id: PeerId) -> impl Future<Output = types::Result<()>> {
let id: fbt::PeerId = id.into();
self.0.proxy.forget(&id).map(from_fidl_result)
}
pub fn shutdown(&self) -> types::Result<()> {
self.0.proxy.shutdown().map_err(|e| e.into())
}
pub fn restore_bonds(
&self,
bonds: Vec<BondingData>,
) -> impl Future<Output = types::Result<Vec<sys::BondingData>>> {
// TODO(https://fxbug.dev/42160922): Due to the maximum message size, the RestoreBonds call has an
// upper limit on the number of bonds that may be restored at once. However, this limit is
// based on the complexity of fields packed into sys::BondingData, which can be measured
// dynamically with measure-tape as the vector is built. Maximizing the number of bonds per
// call may improve performance. Instead, a conservative fixed maximum is chosen here (at
// this time, the maximum message size is 64 KiB and a fully-populated BondingData without
// peer service records is close to 700 B).
const MAX_BONDS_PER_RESTORE_BONDS: usize = 16;
let bonds: Vec<_> = bonds.into_iter().map(sys::BondingData::from).collect();
let bonds_chunks = bonds.chunks(MAX_BONDS_PER_RESTORE_BONDS);
// Bonds that can't be restored are sent back in Ok(Vec<_>), which would not cause
// try_join_all to bail early
try_join_all(bonds_chunks.map(|c| self.0.proxy.restore_bonds(c).map_err(|e| e.into())))
.map_ok(|v| v.into_iter().flatten().collect())
}
pub fn set_connectable(&self, value: bool) -> impl Future<Output = types::Result<()>> {
self.0.proxy.set_connectable(value).map(from_fidl_result)
}
pub fn establish_discoverable_session(
&self,
) -> impl Future<Output = types::Result<HostDiscoverableSession>> {
let host = Arc::downgrade(&self.0);
self.0
.proxy
.set_discoverable(true)
.map(from_fidl_result)
.map_ok(move |_| HostDiscoverableSession { host })
}
pub fn set_local_data(&self, data: HostData) -> types::Result<()> {
self.0.proxy.set_local_data(&data.into()).map_err(|e| e.into())
}
pub fn enable_privacy(&self, enable: bool) -> types::Result<()> {
self.0.proxy.enable_privacy(enable).map_err(Error::from)
}
pub fn enable_background_scan(&self, enable: bool) -> types::Result<()> {
self.0.proxy.enable_background_scan(enable).map_err(Error::from)
}
pub fn set_bredr_security_mode(&self, mode: sys::BrEdrSecurityMode) -> types::Result<()> {
self.0.proxy.set_br_edr_security_mode(mode).map_err(Error::from)
}
pub fn set_le_security_mode(&self, mode: sys::LeSecurityMode) -> types::Result<()> {
self.0.proxy.set_le_security_mode(mode).map_err(Error::from)
}
pub fn apply_config(
&self,
config: build_config::Config,
) -> impl Future<Output = types::Result<()>> {
let equivalent_settings = config.into();
self.apply_sys_settings(&equivalent_settings)
}
/// `apply_sys_settings` applies each field present in `settings` to the host device, leaving
/// omitted parameters unchanged. If present, the `Err` arm of the returned future's output
/// is the error associated with the first failure to apply a setting to the host device.
pub fn apply_sys_settings(
&self,
settings: &sys::Settings,
) -> impl Future<Output = types::Result<()>> {
let mut error_occurred = settings.le_privacy.map(|en| self.enable_privacy(en)).transpose();
if let Ok(_) = error_occurred {
error_occurred =
settings.le_background_scan.map(|en| self.enable_background_scan(en)).transpose()
}
if let Ok(_) = error_occurred {
error_occurred =
settings.bredr_security_mode.map(|m| self.set_bredr_security_mode(m)).transpose();
}
if let Ok(_) = error_occurred {
error_occurred =
settings.le_security_mode.map(|m| self.set_le_security_mode(m)).transpose();
}
let connectable_fut = error_occurred
.map(|_| settings.bredr_connectable_mode.map(|c| self.set_connectable(c)));
async move {
match connectable_fut {
Ok(Some(fut)) => fut.await,
res => res.map(|_| ()),
}
}
}
/// 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>(self, listener: H) -> anyhow::Result<()> {
let handle_fidl = self.handle_fidl_events(listener.clone());
let watch_peers = self.watch_peers(listener.clone());
let watch_state = self.watch_state(listener);
let handle_fidl = pin!(handle_fidl);
let watch_peers = pin!(watch_peers);
let watch_state = pin!(watch_state);
futures::select! {
res1 = handle_fidl.fuse() => res1.context("failed to handle fuchsia.bluetooth.Host event"),
res2 = watch_peers.fuse() => res2.context("failed to relay peer watcher from Host"),
res3 = watch_state.fuse() => res3.context("failed to watch Host for HostInfo"),
}
}
fn watch_peers<H: HostListener>(
&self,
mut listener: H,
) -> impl Future<Output = types::Result<()>> {
let proxy = self.0.proxy.clone();
async move {
loop {
let (updated, removed) = proxy.watch_peers().await?;
for peer in updated.into_iter() {
listener.on_peer_updated(peer.try_into()?).await;
}
for id in removed.into_iter() {
listener.on_peer_removed(id.into()).await;
}
}
}
}
async fn watch_state<H: HostListener>(self, mut listener: H) -> types::Result<()> {
loop {
let info = self.clone().refresh_host_info().await?;
listener.on_host_updated(info).await?;
}
}
fn handle_fidl_events<H: HostListener>(
&self,
mut listener: H,
) -> impl Future<Output = types::Result<()>> {
let mut stream = self.0.proxy.take_event_stream();
async move {
while let Some(event) = stream.next().await {
match event? {
HostEvent::OnNewBondingData { data } => {
info!("Received bonding data");
let data: BondingData = match data.try_into() {
Err(e) => {
error!("Invalid bonding data, ignoring: {:#?}", e);
continue;
}
Ok(data) => data,
};
if let Err(e) = listener.on_new_host_bond(data.into()).await {
error!("Failed to persist bonding data: {:#?}", e);
}
}
HostEvent::_UnknownEvent { ordinal, .. } => {
warn!("Received unknown event with ordinal {ordinal}");
}
};
}
Err(types::Error::InternalError(format_err!("Host FIDL event stream terminated")))
}
}
async fn refresh_host_info(self) -> types::Result<HostInfo> {
let proxy = self.0.proxy.clone();
let info = proxy.watch_state().await?;
let info: HostInfo = info.try_into()?;
debug!("HostDevice::refresh_host_info: {:?}", info);
self.0.info.write().update(info.clone());
Ok(info)
}
#[cfg(test)]
pub(crate) async fn refresh_test_host_info(self) -> types::Result<HostInfo> {
self.refresh_host_info().await
}
#[cfg(test)]
pub(crate) fn mock_from_id(
id: HostId,
) -> (fidl_fuchsia_bluetooth_host::HostRequestStream, HostDevice) {
let (host_proxy, host_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_fuchsia_bluetooth_host::HostMarker>()
.unwrap();
let id_val = id.0 as u8;
let address = Address::Public([id_val; 6]);
let path = format!("/dev/host{}", id_val);
let host_device = HostDevice::mock(id, address, path, host_proxy);
(host_stream, host_device)
}
#[cfg(test)]
pub(crate) fn mock(id: HostId, address: Address, path: String, proxy: HostProxy) -> HostDevice {
let info = HostInfo {
id,
technology: TechnologyType::DualMode,
addresses: vec![address],
active: false,
local_name: None,
discoverable: false,
discovering: false,
};
HostDevice::new(path, proxy, Inspectable::new(info, fuchsia_inspect::Node::default()))
}
}
/// A type that can be notified when a Host or the peers it knows about change
///
/// Each of these trait methods returns a future that should be polled to completion. Once that
/// returned future is complete, the target type can be considered to have been notified of the
/// update event. This allows asynchronous notifications such as via an asynchronous msg channel.
///
/// The host takes care to serialize updates so that subsequent notifications are not triggered
/// until the previous future has been completed. This allows a HostListener type to ensure they
/// maintain ordering. If required, the implementation of these methods should ensure that
/// completing the future before sending a new update is sufficient to ensure ordering.
///
/// Since the notifying Host will wait on the completion of the returned futures, HostListeners
/// should not perform heavy work that may block or take an unnecessary length of time. If the
/// implementor needs to perform potentially-blocking work in response to these notifications, that
/// should be done in a separate task or thread that does not block the returned future.
pub trait HostListener {
/// The return Future type of `on_peer_updated`
type PeerUpdatedFut: Future<Output = ()>;
/// The return Future type of `on_peer_removed`
type PeerRemovedFut: Future<Output = ()>;
/// The return Future type of `on_new_host_bond`
type HostBondFut: Future<Output = Result<(), anyhow::Error>>;
/// The return Future type of `on_host_updated`
type HostInfoFut: Future<Output = Result<(), anyhow::Error>>;
/// Indicate that a Peer `Peer` has been added or updated
fn on_peer_updated(&mut self, peer: Peer) -> Self::PeerUpdatedFut;
/// Indicate that a Peer identified by `id` has been removed
fn on_peer_removed(&mut self, id: PeerId) -> Self::PeerRemovedFut;
/// Indicate that a new bond described by `data` has been made
fn on_new_host_bond(&mut self, data: BondingData) -> Self::HostBondFut;
/// Indicate that the Host now has properties described by `info`
fn on_host_updated(&mut self, info: HostInfo) -> Self::HostInfoFut;
}
#[cfg(test)]
pub(crate) mod test {
use super::*;
use {
async_helpers::maybe_stream::MaybeStream,
fidl::endpoints::Responder,
fidl_fuchsia_bluetooth_host::{
DiscoverySessionRequest, DiscoverySessionRequestStream, HostRequest, HostRequestStream,
},
fidl_fuchsia_bluetooth_sys::HostInfo as FidlHostInfo,
futures::select,
};
struct FakeHostServer {
host_stream: HostRequestStream,
host_info: Arc<RwLock<HostInfo>>,
discovery_stream: MaybeStream<DiscoverySessionRequestStream>,
}
impl FakeHostServer {
async fn run(&mut self) -> Result<(), anyhow::Error> {
loop {
select! {
req = self.discovery_stream.next() => {
info!("FakeHostServer: discovery_stream: {:?}", req);
match req {
Some(Ok(DiscoverySessionRequest::Stop { .. })) | None => {
assert!(self.host_info.read().discovering);
self.host_info.write().discovering = false;
self.discovery_stream = MaybeStream::default();
}
x => panic!("Unexpected request in fake host server: {:?}", x),
}
}
req = self.host_stream.next() => {
info!("FakeHostServer: {:?}", req);
match req {
Some(Ok(HostRequest::StartDiscovery { payload, .. })) => {
assert!(!self.host_info.read().discovering);
self.host_info.write().discovering = true;
self.discovery_stream.set(payload.token.unwrap().into_stream().unwrap());
}
Some(Ok(HostRequest::WatchState { responder })) => {
assert_matches::assert_matches!(
responder.send(
&FidlHostInfo::from(self.host_info.read().clone())
),
Ok(())
);
}
Some(Ok(HostRequest::WatchPeers { responder, .. })) => {
info!("FakeHostServer: Got watch peers, never responding..");
responder.drop_without_shutdown();
}
None => {
return Ok(());
}
x => panic!("Unexpected request in fake host server: {:?}", x),
}
}
}
}
}
}
/// Runs a HostRequestStream that handles StartDiscovery & WatchState requests.
pub(crate) async fn run_discovery_host_server(
server: HostRequestStream,
host_info: Arc<RwLock<HostInfo>>,
) -> Result<(), anyhow::Error> {
let mut host_server = FakeHostServer {
host_stream: server,
host_info,
discovery_stream: MaybeStream::default(),
};
host_server.run().await
}
}