| // 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 |
| ] |
| ) |
| } |