blob: 6c38940cbc6774aca843320cc27ff50b2810af37 [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, Error},
fidl::endpoints::{create_endpoints, ServerEnd},
fidl_fuchsia_bluetooth::{ConnectionRole, Uuid},
fidl_fuchsia_bluetooth_le::{
AdvertisingData, AdvertisingHandleMarker, AdvertisingModeHint, AdvertisingParameters,
PeripheralError, PeripheralStartAdvertisingResult as AdvertisingResult,
},
fidl_fuchsia_bluetooth_test::{
ConnectionState, HciEmulatorProxy, LegacyAdvertisingType, LowEnergyPeerParameters,
PeerProxy, MAX_LEGACY_ADVERTISING_DATA_LENGTH,
},
fuchsia_async::{self as fasync, DurationExt, TimeoutExt},
fuchsia_bluetooth::{
expectation::asynchronous::{ExpectableState, ExpectableStateExt},
types::Address,
},
fuchsia_zircon::{Duration, DurationNum},
futures::{Future, Stream, TryFutureExt, TryStream, TryStreamExt},
std::{iter::repeat, mem::drop},
};
use crate::harness::{
emulator,
expect::{expect_eq, expect_ok},
low_energy_peripheral::PeripheralHarness,
};
mod expectation {
use {
crate::harness::low_energy_peripheral::PeripheralState,
fuchsia_bluetooth::expectation::Predicate,
};
pub fn peripheral_received_connection() -> Predicate<PeripheralState> {
Predicate::new(
move |state: &PeripheralState| -> bool { !state.connections.is_empty() },
Some("le.Peripheral notified a connection"),
)
}
}
fn test_timeout() -> Duration {
10.seconds()
}
fn empty_advertising_data() -> AdvertisingData {
AdvertisingData {
name: None,
appearance: None,
tx_power_level: None,
service_uuids: None,
service_data: None,
manufacturer_data: None,
uris: None,
}
}
async fn start_advertising(
harness: &PeripheralHarness,
params: AdvertisingParameters,
handle: ServerEnd<AdvertisingHandleMarker>,
) -> Result<AdvertisingResult, Error> {
let fut = harness
.aux()
.proxy()
.start_advertising(params, handle)
.map_err(|e| e.into())
.on_timeout(test_timeout().after_now(), move || Err(format_err!("timed out")));
fut.await.map_err(|e| e.context("Could not start advertising").into())
}
fn default_parameters() -> AdvertisingParameters {
AdvertisingParameters {
data: None,
scan_response: None,
mode_hint: None,
connectable: None,
connection_options: None,
}
}
async fn test_enable_advertising(harness: PeripheralHarness) -> Result<(), Error> {
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, default_parameters(), handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(true), test_timeout())
.await?;
Ok(())
}
async fn test_enable_and_disable_advertising(harness: PeripheralHarness) -> Result<(), Error> {
let (handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, default_parameters(), handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(true), test_timeout())
.await?;
// Closing the advertising handle should stop advertising.
drop(handle);
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(false), test_timeout())
.await?;
Ok(())
}
async fn test_advertising_handle_closed_while_pending(
harness: PeripheralHarness,
) -> Result<(), Error> {
let (handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
// Drop the handle before getting a response to abort the procedure.
drop(handle);
let result = start_advertising(&harness, default_parameters(), handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
// Advertising should become disabled after getting enabled once.
let _ = harness
.when_satisfied(
emulator::expectation::advertising_was_enabled(true)
.and(emulator::expectation::advertising_is_enabled(false)),
test_timeout(),
)
.await?;
Ok(())
}
async fn test_advertising_data_too_long(harness: PeripheralHarness) -> Result<(), Error> {
const LENGTH: usize = (MAX_LEGACY_ADVERTISING_DATA_LENGTH + 1) as usize;
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
// Assign a very long name.
let mut params = default_parameters();
params.data = Some(AdvertisingData {
name: Some(repeat("x").take(LENGTH).collect::<String>()),
..empty_advertising_data()
});
let result = start_advertising(&harness, params, handle_remote).await?;
expect_eq!(Err(PeripheralError::AdvertisingDataTooLong), result)?;
Ok(())
}
async fn test_scan_response_data_too_long(harness: PeripheralHarness) -> Result<(), Error> {
const LENGTH: usize = (MAX_LEGACY_ADVERTISING_DATA_LENGTH + 1) as usize;
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
// Assign a very long name.
let mut params = default_parameters();
params.scan_response = Some(AdvertisingData {
name: Some(repeat("x").take(LENGTH).collect::<String>()),
..empty_advertising_data()
});
let result = start_advertising(&harness, params, handle_remote).await?;
expect_eq!(Err(PeripheralError::ScanResponseDataTooLong), result)?;
Ok(())
}
async fn test_update_advertising(harness: PeripheralHarness) -> Result<(), Error> {
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, default_parameters(), handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(true), test_timeout())
.await?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvNonconnInd),
test_timeout(),
)
.await?;
harness.write_state().reset();
// Call `start_advertising` again with new parameters.
let mut params = default_parameters();
params.connectable = Some(true);
let (_handle2, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
// Advertising should stop and start with the new parameters.
let _ = harness
.when_satisfied(
emulator::expectation::advertising_was_enabled(false)
.and(emulator::expectation::advertising_is_enabled(true)),
test_timeout(),
)
.await?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvInd),
test_timeout(),
)
.await?;
Ok(())
}
async fn test_advertising_types(harness: PeripheralHarness) -> Result<(), Error> {
// Non-connectable
let params = AdvertisingParameters { connectable: Some(false), ..default_parameters() };
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvNonconnInd),
test_timeout(),
)
.await?;
// Connectable
let params = AdvertisingParameters { connectable: Some(true), ..default_parameters() };
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvInd),
test_timeout(),
)
.await?;
// Scannable
let params = AdvertisingParameters {
connectable: Some(false),
scan_response: Some(AdvertisingData {
name: Some("hello".to_string()),
..empty_advertising_data()
}),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvScanInd),
test_timeout(),
)
.await?;
// Connectable and scannable
let params = AdvertisingParameters {
connectable: Some(true),
scan_response: Some(AdvertisingData {
name: Some("hello".to_string()),
..empty_advertising_data()
}),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(
emulator::expectation::advertising_type_is(LegacyAdvertisingType::AdvInd),
test_timeout(),
)
.await?;
Ok(())
}
async fn test_advertising_modes(harness: PeripheralHarness) -> Result<(), Error> {
// Very fast advertising interval (<= 60 ms), only supported for connectable advertising.
let params = AdvertisingParameters {
connectable: Some(true),
mode_hint: Some(AdvertisingModeHint::VeryFast),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_max_interval_is(60), test_timeout())
.await?;
// Very fast advertising interval (<= 60 ms) falls back to "fast" parameters for non-connectable
// advertising.
let params = AdvertisingParameters {
mode_hint: Some(AdvertisingModeHint::VeryFast),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_max_interval_is(150), test_timeout())
.await?;
// Fast advertising interval (<= 150 ms)
let params = AdvertisingParameters {
mode_hint: Some(AdvertisingModeHint::Fast),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_max_interval_is(150), test_timeout())
.await?;
// Slow advertising interval (<= 1.2 s)
let params = AdvertisingParameters {
mode_hint: Some(AdvertisingModeHint::Slow),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_max_interval_is(1200), test_timeout())
.await?;
Ok(())
}
async fn test_advertising_data(harness: PeripheralHarness) -> Result<(), Error> {
// Test that encoding one field works. The serialization of other fields is unit tested elsewhere.
let params = AdvertisingParameters {
data: Some(AdvertisingData { name: Some("hello".to_string()), ..empty_advertising_data() }),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let data = vec![
// Flags (General discoverable mode)
0x02,
0x01,
0x02,
// The local name, as above.
0x06,
0x09,
('h' as u8),
('e' as u8),
('l' as u8),
('l' as u8),
('o' as u8),
];
let _ = harness
.when_satisfied(
emulator::expectation::advertising_is_enabled(true)
.and(emulator::expectation::advertising_data_is(data)),
test_timeout(),
)
.await?;
Ok(())
}
async fn test_scan_response(harness: PeripheralHarness) -> Result<(), Error> {
// Test that encoding one field works. The serialization of other fields is unit tested elsewhere.
let params = AdvertisingParameters {
data: Some(AdvertisingData {
service_uuids: Some(vec![Uuid {
value: [
0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x0d,
0x18, 0x00, 0x00,
],
}]),
..empty_advertising_data()
}),
scan_response: Some(AdvertisingData {
name: Some("hello".to_string()),
..empty_advertising_data()
}),
..default_parameters()
};
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let data = vec![
0x02, 0x01, 0x02, // Flags (General discoverable mode)
0x03, 0x02, 0x0d, 0x18, // Incomplete list of service UUIDs
];
let scan_rsp = vec![
// The local name, as above.
0x06,
0x09,
('h' as u8),
('e' as u8),
('l' as u8),
('l' as u8),
('o' as u8),
];
let _ = harness
.when_satisfied(
emulator::expectation::advertising_is_enabled(true)
.and(emulator::expectation::advertising_data_is(data))
.and(emulator::expectation::scan_response_is(scan_rsp)),
test_timeout(),
)
.await?;
Ok(())
}
fn default_address() -> Address {
Address::Public([1, 0, 0, 0, 0, 0])
}
async fn add_fake_peer(proxy: &HciEmulatorProxy, address: &Address) -> Result<PeerProxy, Error> {
let (local, remote) = fidl::endpoints::create_proxy()?;
let params = LowEnergyPeerParameters {
address: Some(address.into()),
connectable: Some(true),
advertisement: None,
scan_response: None,
};
let _ = proxy
.add_low_energy_peer(params, remote)
.await?
.map_err(|e| format_err!("Failed to register fake peer: {:?}", e))?;
Ok(local)
}
// Returns success when the `events` stream returns a "None" or fails. (This is what FIDL handles do
// when their peer gets closed). Returns an error if the timeout expires before then.
fn watch_handle_closed<T, V>(mut events: T) -> impl Future<Output = Result<(), Error>>
where
T: Stream<Item = Result<V, fidl::Error>>
+ Send
+ std::marker::Unpin
+ TryStream<Error = fidl::Error>
+ TryStreamExt,
{
let f = async move {
while events.try_next().await?.is_some() {}
Ok(())
};
f.on_timeout(test_timeout().after_now(), || Err(format_err!("Timed out before handle closed")))
}
async fn test_receive_connection(harness: PeripheralHarness) -> Result<(), Error> {
let emulator = harness.aux().emulator().clone();
let address = default_address();
let peer = add_fake_peer(&emulator, &address).await?;
let (handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let mut params = default_parameters();
params.connectable = Some(true);
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(true), test_timeout())
.await?;
peer.emulate_le_connection_complete(ConnectionRole::Follower)?;
let _ = harness
.when_satisfied(expectation::peripheral_received_connection(), test_timeout())
.await?;
// Receiving a connection is expected to stop advertising. Verify that the emulator no longer
// advertises.
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(false), test_timeout())
.await?;
// Similarly our AdvertisingHandle should be closed by the system. Polling for events should
// result in an error. Keep a local copy to ensure that the handle doesn't get dropped
// locally.
let handle = handle.into_proxy()?;
watch_handle_closed(handle.clone().take_event_stream()).await
}
async fn test_connection_dropped_when_not_connectable(
harness: PeripheralHarness,
) -> Result<(), Error> {
let emulator = harness.aux().emulator().clone();
let address = default_address();
let peer = add_fake_peer(&emulator, &address).await?;
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
// `default_parameters()` are configured as non-connectable.
let result = start_advertising(&harness, default_parameters(), handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
let _ = harness
.when_satisfied(emulator::expectation::advertising_is_enabled(true), test_timeout())
.await?;
peer.emulate_le_connection_complete(ConnectionRole::Follower)?;
// Wait for the connection to get dropped by the stack as it should be rejected when we are not
// connectable. We assign our own PeerId here for tracking purposes (this is distinct from the
// PeerId that the Peripheral proxy would report).
fasync::spawn(
emulator::watch_peer_connection_states(harness.clone(), address, peer.clone())
.unwrap_or_else(|_| ()),
);
let _ = harness
.when_satisfied(
emulator::expectation::peer_connection_state_was(address, ConnectionState::Connected)
.and(emulator::expectation::peer_connection_state_is(
address,
ConnectionState::Disconnected,
)),
test_timeout(),
)
.await?;
// Make sure that we haven't received any connection events over the Peripheral protocol.
expect_true!(harness.read().connections.is_empty())?;
Ok(())
}
async fn test_drop_connection(harness: PeripheralHarness) -> Result<(), Error> {
let emulator = harness.aux().emulator().clone();
let address = default_address();
let peer = add_fake_peer(&emulator, &address).await?;
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let mut params = default_parameters();
params.connectable = Some(true);
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
peer.emulate_le_connection_complete(ConnectionRole::Follower)?;
fasync::spawn(
emulator::watch_peer_connection_states(harness.clone(), address, peer.clone())
.unwrap_or_else(|_| ()),
);
let _ = harness
.when_satisfied(expectation::peripheral_received_connection(), test_timeout())
.await?;
expect_true!(harness.read().connections.len() == 1)?;
let (_, conn) = harness.write_state().connections.remove(0);
// Explicitly drop the connection handle. This should tell the emulator to disconnect the peer.
drop(conn);
let _ = harness
.when_satisfied(
emulator::expectation::peer_connection_state_was(address, ConnectionState::Connected)
.and(emulator::expectation::peer_connection_state_is(
address,
ConnectionState::Disconnected,
)),
test_timeout(),
)
.await?;
Ok(())
}
async fn test_connection_handle_closes_on_disconnect(
harness: PeripheralHarness,
) -> Result<(), Error> {
let emulator = harness.aux().emulator().clone();
let address = default_address();
let peer = add_fake_peer(&emulator, &address).await?;
let (_handle, handle_remote) = create_endpoints::<AdvertisingHandleMarker>()?;
let mut params = default_parameters();
params.connectable = Some(true);
let result = start_advertising(&harness, params, handle_remote).await?;
expect_ok(result, "failed to start advertising")?;
peer.emulate_le_connection_complete(ConnectionRole::Follower)?;
fasync::spawn(
emulator::watch_peer_connection_states(harness.clone(), address, peer.clone())
.unwrap_or_else(|_| ()),
);
let _ = harness
.when_satisfied(expectation::peripheral_received_connection(), test_timeout())
.await?;
expect_true!(harness.read().connections.len() == 1)?;
let (_, conn) = harness.write_state().connections.remove(0);
// Tell the controller to disconnect the link. The harness should get notified of this.
peer.emulate_disconnection_complete()?;
let _ = harness
.when_satisfied(
emulator::expectation::peer_connection_state_was(address, ConnectionState::Connected)
.and(emulator::expectation::peer_connection_state_is(
address,
ConnectionState::Disconnected,
)),
test_timeout(),
)
.await?;
// Our connection handle should be closed by the system. Polling for events should
// result in an error. Keep a local copy to ensure that the handle doesn't get dropped
// locally.
watch_handle_closed(conn.clone().take_event_stream()).await
}
/// Run all test cases.
pub fn run_all() -> Result<(), Error> {
run_suite!(
"le.Peripheral",
[
test_enable_advertising,
test_enable_and_disable_advertising,
test_advertising_handle_closed_while_pending,
test_advertising_data_too_long,
test_scan_response_data_too_long,
test_update_advertising,
test_advertising_types,
test_advertising_modes,
test_advertising_data,
test_scan_response,
test_receive_connection,
test_connection_dropped_when_not_connectable,
test_drop_connection,
test_connection_handle_closes_on_disconnect
]
)
}