blob: 143db6152c1c1f172e1cb445cd12ef5e9c8c4a40 [file] [log] [blame]
// Copyright 2019 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::{Context as _, Error},
fidl_fuchsia_bluetooth_control::{
AdapterInfo, ControlEvent, ControlMarker, ControlProxy, RemoteDevice,
},
fidl_fuchsia_bluetooth_test::HciEmulatorProxy,
fuchsia_async as fasync,
fuchsia_bluetooth::{
expectation::asynchronous::{ExpectableState, ExpectableStateExt, ExpectationHarness},
expectation::Predicate,
hci_emulator::Emulator,
util::{clone_host_info, clone_remote_device},
},
fuchsia_zircon::{Duration, DurationNum},
futures::{
future::{self, BoxFuture},
FutureExt, TryFutureExt, TryStreamExt,
},
std::collections::HashMap,
};
use crate::harness::TestHarness;
/// Timeout for updating Bluetooth through fuchsia.bluetooth.control.Control actions
pub fn control_timeout() -> Duration {
10.seconds()
}
#[derive(Default)]
pub struct ControlState {
/// Current hosts
pub hosts: HashMap<String, AdapterInfo>,
/// Active host identifier
pub active_host: Option<String>,
/// Remote Peers seen
pub peers: HashMap<String, RemoteDevice>,
}
impl Clone for ControlState {
fn clone(&self) -> ControlState {
let hosts: HashMap<String, AdapterInfo> =
self.hosts.iter().map(|(k, v)| (k.clone(), clone_host_info(v))).collect();
let active_host = self.active_host.clone();
let peers: HashMap<String, RemoteDevice> =
self.peers.iter().map(|(k, v)| (k.clone(), clone_remote_device(v))).collect();
ControlState { hosts, active_host, peers }
}
}
pub type ControlHarness = ExpectationHarness<ControlState, ControlProxy>;
pub async fn handle_control_events(harness: ControlHarness) -> Result<(), Error> {
let mut events = harness.aux().take_event_stream();
while let Some(e) = events.try_next().await? {
match e {
ControlEvent::OnActiveAdapterChanged { adapter } => {
harness.write_state().active_host = adapter.map(|host| host.identifier);
}
ControlEvent::OnAdapterUpdated { adapter } => {
harness.write_state().hosts.insert(adapter.identifier.clone(), adapter);
}
ControlEvent::OnAdapterRemoved { identifier } => {
harness.write_state().hosts.remove(&identifier);
}
ControlEvent::OnDeviceUpdated { device } => {
harness.write_state().peers.insert(device.identifier.clone(), device);
}
ControlEvent::OnDeviceRemoved { identifier } => {
harness.write_state().peers.remove(&identifier);
}
};
harness.notify_state_changed();
}
Ok(())
}
pub async fn new_control_harness() -> Result<ControlHarness, Error> {
let proxy = fuchsia_component::client::connect_to_service::<ControlMarker>()
.context("Failed to connect to Control service")?;
let control_harness = ControlHarness::new(proxy);
// Store existing hosts in our state, as we won't get notified about them
let fut = control_harness.aux().get_adapters();
let hosts = fut.await?;
if let Some(hosts) = hosts {
for host in hosts {
control_harness.write_state().hosts.insert(host.identifier.clone(), host);
}
}
Ok(control_harness)
}
impl TestHarness for ControlHarness {
type Env = ();
type Runner = BoxFuture<'static, Result<(), Error>>;
fn init() -> BoxFuture<'static, Result<(Self, Self::Env, Self::Runner), Error>> {
async {
let harness = new_control_harness().await?;
let run_control = handle_control_events(harness.clone()).boxed();
Ok((harness, (), run_control))
}
.boxed()
}
fn terminate(_env: Self::Env) -> BoxFuture<'static, Result<(), Error>> {
future::ok(()).boxed()
}
}
pub mod expectation {
use crate::harness::control::ControlState;
use fidl_fuchsia_bluetooth_control::RemoteDevice;
use fuchsia_bluetooth::expectation::Predicate;
pub fn active_host_is(id: String) -> Predicate<ControlState> {
let msg = format!("active bt-host is {}", id);
let expected_host = Some(id);
Predicate::new(
move |state: &ControlState| -> bool { state.active_host == expected_host },
Some(&msg),
)
}
pub fn host_not_present(id: String) -> Predicate<ControlState> {
let msg = format!("bt-host {} is no longer present", id);
Predicate::new(move |state: &ControlState| !state.hosts.contains_key(&id), Some(&msg))
}
mod peer {
use super::*;
pub(crate) fn exists(p: Predicate<RemoteDevice>) -> Predicate<ControlState> {
let msg = format!("peer exists satisfying {}", p.describe());
Predicate::new(
move |state: &ControlState| state.peers.iter().any(|(_, d)| p.satisfied(d)),
Some(&msg),
)
}
pub(crate) fn with_identifier(id: &str) -> Predicate<RemoteDevice> {
let id_owned = id.to_string();
Predicate::<RemoteDevice>::new(
move |d| d.identifier == id_owned,
Some(&format!("identifier == {}", id)),
)
}
pub(crate) fn with_address(address: &str) -> Predicate<RemoteDevice> {
let addr_owned = address.to_string();
Predicate::<RemoteDevice>::new(
move |d| d.address == addr_owned,
Some(&format!("address == {}", address)),
)
}
pub(crate) fn connected(connected: bool) -> Predicate<RemoteDevice> {
Predicate::<RemoteDevice>::new(
move |d| d.connected == connected,
Some(&format!("connected == {}", connected)),
)
}
}
pub fn peer_connected(id: &str, connected: bool) -> Predicate<ControlState> {
peer::exists(peer::with_identifier(id).and(peer::connected(connected)))
}
pub fn peer_with_address(address: &str) -> Predicate<ControlState> {
peer::exists(peer::with_address(address))
}
}
/// An activated fake host.
/// Must be released with `host.release().await` before drop.
pub struct ActivatedFakeHost {
control: ControlHarness,
host: String,
hci: Option<Emulator>,
}
// All Fake HCI Devices have this address
pub const FAKE_HCI_ADDRESS: &'static str = "00:00:00:00:00:00";
/// Create and publish an emulated HCI device, and wait until the host is bound and registered to
/// bt-gap
pub async fn activate_fake_host(
control: ControlHarness,
name: &str,
) -> Result<(String, Emulator), Error> {
let initial_hosts: Vec<String> = control.read().hosts.keys().cloned().collect();
let initial_hosts_ = initial_hosts.clone();
let hci = Emulator::create_and_publish(name).await?;
let state = control
.when_satisfied(
Predicate::<ControlState>::new(
move |control| {
let added_fake_hosts = control.hosts.iter().filter(|(id, host)| {
host.address == FAKE_HCI_ADDRESS && !initial_hosts_.contains(id)
});
added_fake_hosts.count() > 0
},
Some("Fake Host Added"),
),
control_timeout(),
)
.await?;
let host = state
.hosts
.iter()
.find(|(id, host)| host.address == FAKE_HCI_ADDRESS && !initial_hosts.contains(id))
.unwrap()
.1
.identifier
.to_string(); // We can safely unwrap here as this is guarded by the previous expectation
let fut = control.aux().set_active_adapter(&host);
fut.await?;
control.when_satisfied(expectation::active_host_is(host.clone()), control_timeout()).await?;
Ok((host, hci))
}
impl ActivatedFakeHost {
pub async fn new(name: &str) -> Result<ActivatedFakeHost, Error> {
let control = new_control_harness().await?;
fasync::spawn(
handle_control_events(control.clone())
.unwrap_or_else(|e| eprintln!("Error handling control events: {:?}", e)),
);
let (host, hci) = activate_fake_host(control.clone(), name).await?;
Ok(ActivatedFakeHost { control, host, hci: Some(hci) })
}
pub async fn release(mut self) -> Result<(), Error> {
// Wait for the test device to be destroyed.
if let Some(hci) = &mut self.hci {
hci.destroy_and_wait().await?;
}
self.hci = None;
// Wait for BT-GAP to unregister the associated fake host
self.control
.when_satisfied(expectation::host_not_present(self.host.clone()), control_timeout())
.await?;
Ok(())
}
pub fn emulator(&self) -> &HciEmulatorProxy {
self.hci.as_ref().expect("emulator proxy requested after shut down").emulator()
}
}
impl Drop for ActivatedFakeHost {
fn drop(&mut self) {
assert!(self.hci.is_none());
}
}