blob: dc3cfe8551d8924eddba2cf8e2319030e70dd9ab [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::{format_err, Context as _, Error},
fidl_fuchsia_bluetooth::{self as fbt, DeviceClass, MAJOR_DEVICE_CLASS_TOY},
fidl_fuchsia_bluetooth_host::HostProxy,
fidl_fuchsia_bluetooth_sys::{self as fsys, TechnologyType},
fidl_fuchsia_bluetooth_test::{EmulatorSettings, HciError, PeerProxy},
fuchsia_async as fasync,
fuchsia_bluetooth::{
constants::HOST_DEVICE_DIR,
device_watcher::{DeviceWatcher, WatchFilter},
expectation::{self, asynchronous::ExpectableStateExt, peer},
hci_emulator::Emulator,
host,
types::{Address, HostInfo, PeerId},
},
fuchsia_zircon as zx,
std::{convert::TryInto, path::PathBuf},
};
use crate::harness::{
emulator::{self, EmulatorHarness},
expect::expect_eq,
host_driver::{
expect_host_state, expect_no_peer, expect_peer, timeout_duration, HostDriverHarness,
},
};
// Tests that creating and destroying a fake HCI device binds and unbinds the bt-host driver.
async fn test_lifecycle(_: ()) -> Result<(), Error> {
let address = Address::Public([1, 2, 3, 4, 5, 6]);
let settings = EmulatorSettings {
address: Some(address.to_fidl()),
hci_config: None,
extended_advertising: None,
acl_buffer_settings: None,
le_acl_buffer_settings: None,
};
let mut emulator = Emulator::create("bt-hci-integration-lifecycle").await?;
let hci_topo = PathBuf::from(fdio::device_get_topo_path(emulator.file())?);
// Publish the bt-hci device and verify that a bt-host appears under its topology within a
// reasonable timeout.
let mut watcher = DeviceWatcher::new(HOST_DEVICE_DIR, zx::Duration::from_seconds(10)).await?;
let _ = emulator.publish(settings).await?;
let bthost = watcher.watch_new(&hci_topo, WatchFilter::AddedOnly).await?;
// Open a host channel using a fidl call and check the device is responsive
let handle = host::open_host_channel(bthost.file())?;
let host = HostProxy::new(fasync::Channel::from_channel(handle.into())?);
let info: HostInfo = host
.watch_state()
.await
.context("Is bt-gap running? If so, try stopping it and re-running these tests")?
.try_into()?;
// The bt-host should have been initialized with the address that we initially configured.
assert_eq!(address, info.address);
// Remove the bt-hci device and check that the test device is also destroyed.
emulator.destroy_and_wait().await?;
// Check that the bt-host device is also destroyed.
watcher.watch_removed(bthost.path()).await
}
// Tests that the bt-host driver assigns the local name to "fuchsia" when initialized.
async fn test_default_local_name(harness: HostDriverHarness) -> Result<(), Error> {
const NAME: &str = "fuchsia";
let _ = harness
.when_satisfied(emulator::expectation::local_name_is(NAME), timeout_duration())
.await?;
expect_host_state(&harness, expectation::host_driver::name(NAME)).await?;
Ok(())
}
// Tests that the local name assigned to a bt-host is reflected in `AdapterState` and propagated
// down to the controller.
async fn test_set_local_name(harness: HostDriverHarness) -> Result<(), Error> {
const NAME: &str = "test1234";
let proxy = harness.aux().proxy().clone();
let result = proxy.set_local_name(NAME).await?;
expect_eq!(Ok(()), result)?;
let _ = harness
.when_satisfied(emulator::expectation::local_name_is(NAME), timeout_duration())
.await?;
expect_host_state(&harness, expectation::host_driver::name(NAME)).await?;
Ok(())
}
// Tests that the device class assigned to a bt-host gets propagated down to the controller.
async fn test_set_device_class(harness: HostDriverHarness) -> Result<(), Error> {
let mut device_class = DeviceClass { value: MAJOR_DEVICE_CLASS_TOY + 4 };
let proxy = harness.aux().proxy().clone();
let result = proxy.set_device_class(&mut device_class).await?;
expect_eq!(Ok(()), result)?;
let _ = harness
.when_satisfied(emulator::expectation::device_class_is(device_class), timeout_duration())
.await?;
Ok(())
}
// Tests that host state updates when discoverable mode is turned on.
// TODO(armansito): Test for FakeHciDevice state changes.
async fn test_discoverable(harness: HostDriverHarness) -> Result<(), Error> {
let proxy = harness.aux().proxy().clone();
// Disabling discoverable mode when not discoverable should succeed.
let result = proxy.set_discoverable(false).await?;
expect_eq!(Ok(()), result)?;
// Enable discoverable mode.
let result = proxy.set_discoverable(true).await?;
expect_eq!(Ok(()), result)?;
expect_host_state(&harness, expectation::host_driver::discoverable(true)).await?;
// Disable discoverable mode
let result = proxy.set_discoverable(false).await?;
expect_eq!(Ok(()), result)?;
expect_host_state(&harness, expectation::host_driver::discoverable(false)).await?;
// Disabling discoverable mode when not discoverable should succeed.
let result = proxy.set_discoverable(false).await?;
expect_eq!(Ok(()), result)?;
Ok(())
}
// Tests that host state updates when discovery is started and stopped.
// TODO(armansito): Test for FakeHciDevice state changes.
async fn test_discovery(harness: HostDriverHarness) -> Result<(), Error> {
let proxy = harness.aux().proxy().clone();
// Start discovery. "discovering" should get set to true.
let result = proxy.start_discovery().await?;
expect_eq!(Ok(()), result)?;
expect_host_state(&harness, expectation::host_driver::discovering(true)).await?;
let address = Address::Random([1, 0, 0, 0, 0, 0]);
let fut = harness.aux().add_le_peer_default(&address);
let _peer = fut.await?;
// The host should discover a fake peer.
expect_peer(&harness, peer::name("Fake").and(peer::address(address))).await?;
// Stop discovery. "discovering" should get set to false.
let _ = proxy.stop_discovery()?;
expect_host_state(&harness, expectation::host_driver::discovering(false)).await?;
Ok(())
}
// Tests that "close" cancels all operations.
// TODO(armansito): Test for FakeHciDevice state changes.
async fn test_close(harness: HostDriverHarness) -> Result<(), Error> {
// Enable all procedures.
let proxy = harness.aux().proxy().clone();
let result = proxy.start_discovery().await?;
expect_eq!(Ok(()), result)?;
let result = proxy.set_discoverable(true).await?;
expect_eq!(Ok(()), result)?;
let active_state = expectation::host_driver::discoverable(true)
.and(expectation::host_driver::discovering(true));
expect_host_state(&harness, active_state).await?;
// Close should cancel these procedures.
proxy.close()?;
let closed_state_update = expectation::host_driver::discoverable(false)
.and(expectation::host_driver::discovering(false));
expect_host_state(&harness, closed_state_update).await?;
Ok(())
}
async fn test_watch_peers(harness: HostDriverHarness) -> Result<(), Error> {
// `HostDriverHarness` internally calls `Host.WatchPeers()` to monitor peers and satisfy peer
// expectations. `harness.peers()` represents the local cache monitored using this method.
// Peers should be initially empty.
expect_eq!(0, harness.state().peers().len())?;
// Calling `Host.WatchPeers()` directly will hang since the harness already calls this
// internally. We issue our own request and verify that it gets satisfied later.
// Add a LE and a BR/EDR peer with the given addresses.
let le_peer_address = Address::Random([1, 0, 0, 0, 0, 0]);
let bredr_peer_address = Address::Public([2, 0, 0, 0, 0, 0]);
let fut = harness.aux().add_le_peer_default(&le_peer_address);
let _le_peer = fut.await?;
let fut = harness.aux().add_bredr_peer_default(&bredr_peer_address);
let _bredr_peer = fut.await?;
// At this stage the fake peers are registered with the emulator but bt-host does not know about
// them yet. Check that `watch_fut` is still unsatisfied.
expect_eq!(0, harness.state().peers().len())?;
// Wait for all fake devices to be discovered.
let proxy = harness.aux().proxy().clone();
let result = proxy.start_discovery().await?;
expect_eq!(Ok(()), result)?;
let expected_le =
peer::address(le_peer_address).and(peer::technology(TechnologyType::LowEnergy));
let expected_bredr =
peer::address(bredr_peer_address).and(peer::technology(TechnologyType::Classic));
expect_peer(&harness, expected_le).await?;
expect_peer(&harness, expected_bredr).await?;
expect_eq!(2, harness.state().peers().len())?;
Ok(())
}
async fn test_connect(harness: HostDriverHarness) -> Result<(), Error> {
let address1 = Address::Random([1, 0, 0, 0, 0, 0]);
let address2 = Address::Random([2, 0, 0, 0, 0, 0]);
let fut = harness.aux().add_le_peer_default(&address1);
let _peer1 = fut.await?;
let fut = harness.aux().add_le_peer_default(&address2);
let peer2 = fut.await?;
// Configure `peer2` to return an error for the connection attempt.
let _ = peer2.assign_connection_status(HciError::ConnectionTimeout).await?;
let proxy = harness.aux().proxy().clone();
// Start discovery and let bt-host process the fake devices.
let result = proxy.start_discovery().await?;
expect_eq!(Ok(()), result)?;
expect_peer(&harness, peer::address(address1)).await?;
expect_peer(&harness, peer::address(address2)).await?;
let peers = harness.state().peers().clone();
expect_eq!(2, peers.len())?;
// Obtain bt-host assigned IDs of the devices.
let success_id = peers
.iter()
.find(|x| x.1.address == address1)
.ok_or(format_err!("success peer not found"))?
.0
.clone();
let failure_id = peers
.iter()
.find(|x| x.1.address == address2)
.ok_or(format_err!("error peer not found"))?
.0
.clone();
let mut success_id: fbt::PeerId = success_id.into();
let mut failure_id: fbt::PeerId = failure_id.into();
// Connecting to the failure peer should result in an error.
let status = proxy.connect(&mut failure_id).await?;
expect_eq!(Err(fsys::Error::Failed), status)?;
// Connecting to the success peer should return success and the peer should become connected.
let status = proxy.connect(&mut success_id).await?;
expect_eq!(Ok(()), status)?;
let connected = peer::identifier(success_id.into()).and(peer::connected(true));
expect_peer(&harness, connected).await?;
Ok(())
}
async fn wait_for_test_peer(
harness: HostDriverHarness,
address: &Address,
) -> Result<(PeerId, PeerProxy), Error> {
let fut = harness.aux().add_le_peer_default(&address);
let proxy = fut.await?;
// Start discovery and let bt-host process the fake LE peer.
let host = harness.aux().proxy().clone();
let result = host.start_discovery().await?;
expect_eq!(Ok(()), result)?;
let le_dev = expectation::peer::address(address.clone());
expect_peer(&harness, le_dev).await?;
let peer_id = harness
.state()
.peers()
.iter()
.find(|(_, p)| p.address == *address)
.ok_or(format_err!("could not find peer with address: {}", address))?
.0
.clone();
Ok((peer_id, proxy))
}
// TODO(BT-932) - Add a test for disconnect failure when a connection attempt is outgoing, provided
// that we can provide a manner of doing so that will not flake.
/// Disconnecting from an unknown device should succeed
async fn test_disconnect_unknown_device(harness: HostDriverHarness) -> Result<(), Error> {
let mut unknown_id = PeerId(0).into();
let fut = harness.aux().proxy().disconnect(&mut unknown_id);
let status = fut.await?;
expect_eq!(Ok(()), status)
}
/// Disconnecting from a known, unconnected device should succeed
async fn test_disconnect_unconnected_device(harness: HostDriverHarness) -> Result<(), Error> {
let address = Address::Random([1, 0, 0, 0, 0, 0]);
let (id, _proxy) = wait_for_test_peer(harness.clone(), &address).await?;
let mut id = id.into();
let fut = harness.aux().proxy().disconnect(&mut id);
let status = fut.await?;
expect_eq!(Ok(()), status)
}
/// Disconnecting from a connected device should succeed and result in the device being disconnected
async fn test_disconnect_connected_device(harness: HostDriverHarness) -> Result<(), Error> {
let address = Address::Random([1, 0, 0, 0, 0, 0]);
let (id, _proxy) = wait_for_test_peer(harness.clone(), &address).await?;
let mut id = id.into();
let proxy = harness.aux().proxy().clone();
let status = proxy.connect(&mut id).await?;
expect_eq!(Ok(()), status)?;
let connected = peer::address(address).and(peer::connected(true));
let disconnected = peer::address(address).and(peer::connected(false));
let _ = expect_peer(&harness, connected).await?;
let status = proxy.disconnect(&mut id).await?;
expect_eq!(Ok(()), status)?;
let _ = expect_peer(&harness, disconnected).await?;
Ok(())
}
async fn test_forget(harness: HostDriverHarness) -> Result<(), Error> {
let address = Address::Random([1, 0, 0, 0, 0, 0]);
let (id, _proxy) = wait_for_test_peer(harness.clone(), &address).await?;
let mut id = id.into();
let proxy = harness.aux().proxy().clone();
// Wait for fake peer to be discovered (`wait_for_test_peer` starts discovery).
let expected_peer = expectation::peer::address(address);
expect_peer(&harness, expected_peer.clone()).await?;
// Connecting to the peer should return success and the peer should become connected.
let status = proxy.connect(&mut id).await?;
expect_eq!(Ok(()), status)?;
expect_peer(&harness, expected_peer.and(expectation::peer::connected(true))).await?;
// Forgetting the peer should result in its removal.
let status = proxy.forget(&mut id).await?;
expect_eq!(Ok(()), status)?;
expect_no_peer(&harness, id.into()).await?;
// TODO(BT-879): Test that the link closes by querying fake HCI.
Ok(())
}
/// Run all test cases.
pub fn run_all() -> Result<(), Error> {
run_suite!(
"bt-host driver",
[
test_lifecycle,
test_default_local_name,
test_set_local_name,
test_set_device_class,
test_discoverable,
test_discovery,
test_close,
test_watch_peers,
test_connect,
test_forget,
test_disconnect_unknown_device,
test_disconnect_unconnected_device,
test_disconnect_connected_device
]
)
}