blob: df069222af798804c975a0d3e215c163bd0f938e [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, Error},
bt_test_harness::{
access::{expectation, AccessHarness},
profile::ProfileHarness,
},
fidl::encoding::Decodable,
fidl::endpoints::create_request_stream,
fidl_fuchsia_bluetooth::{DeviceClass, MAJOR_DEVICE_CLASS_MISCELLANEOUS},
fidl_fuchsia_bluetooth_bredr::{
ChannelParameters, ConnectParameters, ConnectionReceiverRequestStream, DataElement,
L2capParameters, ProfileDescriptor, ProtocolDescriptor, ProtocolIdentifier,
SearchResultsRequest, SearchResultsRequestStream, ServiceClassProfileIdentifier,
ServiceDefinition, PSM_AVDTP,
},
fidl_fuchsia_bluetooth_sys::ProcedureTokenProxy,
fidl_fuchsia_bluetooth_test::{BredrPeerParameters, HciEmulatorProxy, PeerProxy},
fuchsia_async::{DurationExt, TimeoutExt},
fuchsia_bluetooth::{
expectation::asynchronous::{ExpectableExt, ExpectableStateExt},
types::{Address, PeerId, Uuid},
},
futures::{FutureExt, StreamExt, TryFutureExt},
test_harness::run_suite,
};
use crate::tests::timeout_duration;
/// This makes a custom BR/EDR service definition that runs over L2CAP.
fn service_definition_for_testing() -> ServiceDefinition {
let test_uuid: Uuid = "f0c451a0-7e57-1111-2222-123456789ABC".parse().expect("UUID to parse");
ServiceDefinition {
service_class_uuids: Some(vec![test_uuid.into()]),
protocol_descriptor_list: Some(vec![ProtocolDescriptor {
protocol: ProtocolIdentifier::L2Cap,
params: vec![DataElement::Uint16(0x100f)], // In the "dynamically-assigned" range
}]),
..ServiceDefinition::new_empty()
}
}
pub fn a2dp_sink_service_definition() -> ServiceDefinition {
ServiceDefinition {
service_class_uuids: Some(vec![Uuid::new16(0x110B).into()]), // Audio Sink UUID
protocol_descriptor_list: Some(vec![
ProtocolDescriptor {
protocol: ProtocolIdentifier::L2Cap,
params: vec![DataElement::Uint16(PSM_AVDTP)],
},
ProtocolDescriptor {
protocol: ProtocolIdentifier::Avdtp,
params: vec![DataElement::Uint16(0x0103)], // Indicate v1.3
},
]),
profile_descriptors: Some(vec![ProfileDescriptor {
profile_id: ServiceClassProfileIdentifier::AdvancedAudioDistribution,
major_version: 1,
minor_version: 2,
}]),
..ServiceDefinition::new_empty()
}
}
fn add_service(profile: &ProfileHarness) -> Result<ConnectionReceiverRequestStream, anyhow::Error> {
let service_defs = vec![service_definition_for_testing()];
let (connect_client, connect_requests) =
create_request_stream().context("ConnectionReceiver creation")?;
let _ = profile.aux().profile.advertise(
&mut service_defs.into_iter(),
ChannelParameters::new_empty(),
connect_client,
);
Ok(connect_requests)
}
async fn create_bredr_peer(proxy: &HciEmulatorProxy, address: Address) -> Result<PeerProxy, Error> {
let peer_params = BredrPeerParameters {
address: Some(address.into()),
connectable: Some(true),
device_class: Some(DeviceClass { value: MAJOR_DEVICE_CLASS_MISCELLANEOUS + 0 }),
service_definition: Some(vec![a2dp_sink_service_definition()]),
..BredrPeerParameters::EMPTY
};
let (peer, remote) = fidl::endpoints::create_proxy()?;
let _ = proxy
.add_bredr_peer(peer_params, remote)
.await?
.map_err(|e| format_err!("Failed to register fake peer: {:#?}", e))?;
Ok(peer)
}
async fn start_discovery(access: &AccessHarness) -> Result<ProcedureTokenProxy, Error> {
// We create a capability to capture the discovery token, and pass it to the profile provider
// Discovery will stop once we drop this token
let (token, token_server) = fidl::endpoints::create_proxy()?;
let fidl_response = access.aux().start_discovery(token_server);
fidl_response
.await?
.map_err(|sys_err| format_err!("Error calling StartDiscovery(): {:?}", sys_err))?;
Ok(token)
}
async fn add_search(
profile: &ProfileHarness,
profileid: ServiceClassProfileIdentifier,
) -> Result<SearchResultsRequestStream, Error> {
let (results_client, results_stream) =
create_request_stream().context("SearchResults creation")?;
profile.aux().profile.search(profileid, &[], results_client)?;
Ok(results_stream)
}
fn default_address() -> Address {
Address::Public([1, 0, 0, 0, 0, 0])
}
async fn test_add_profile(profile: ProfileHarness) -> Result<(), Error> {
let _ = add_service(&profile)?;
Ok(())
}
async fn test_same_psm_twice_fails(profile: ProfileHarness) -> Result<(), Error> {
let _request_stream = add_service(&profile)?;
let mut second_request_stream = add_service(&profile)?;
// Second request should have a closed stream
match second_request_stream
.next()
.on_timeout(timeout_duration().after_now(), || {
Some(Err(fidl::Error::UnexpectedSyncResponse))
})
.await
{
None => Ok(()),
x => Err(format_err!("Expected client to close, but instead got {:?}", x)),
}
}
async fn test_add_and_remove_profile(profile: ProfileHarness) -> Result<(), Error> {
let request_stream = add_service(&profile)?;
drop(request_stream);
// Adding the profile a second time after removing it should succeed.
let mut request_stream = add_service(&profile)?;
// Request stream should be pending (otherwise it is an error)
if request_stream.next().now_or_never().is_some() {
return Err(format_err!("Should not have an error on re-adding the service"));
}
Ok(())
}
async fn test_connect_unknown_peer(profile: ProfileHarness) -> Result<(), Error> {
let fut = profile.aux().profile.connect(
&mut PeerId(0xDEAD).into(),
&mut ConnectParameters::L2cap(L2capParameters {
psm: Some(PSM_AVDTP),
..L2capParameters::new_empty()
}),
);
match fut.await {
Ok(Err(_)) => Ok(()),
x => Err(format_err!("Expected error from connecting to an unknown peer, got {:?}", x)),
}
}
async fn test_add_search((access, profile): (AccessHarness, ProfileHarness)) -> Result<(), Error> {
let emulator = profile.aux().emulator.clone();
let peer_address = default_address();
let profile_id = ServiceClassProfileIdentifier::AudioSink;
let mut search_result = add_search(&profile, profile_id).await?;
let _test_peer = create_bredr_peer(&emulator, peer_address).await?;
let _discovery_result = start_discovery(&access).await?;
let state = access
.when_satisfied(expectation::peer_with_address(peer_address), timeout_duration())
.await?;
let connected_peer_id = state.peers.values().find(|p| p.address == peer_address).unwrap().id;
let fidl_response = access.aux().connect(&mut connected_peer_id.into());
fidl_response
.await?
.map_err(|sys_err| format_err!("Error calling Connect(): {:?}", sys_err))?;
access
.when_satisfied(expectation::peer_connected(connected_peer_id, true), timeout_duration())
.await?;
// The SDP search result conducted following connection should contain the
// peer ID of the created peer.
let service_found_fut = search_result.select_next_some().map_err(|e| format_err!("{:?}", e));
let SearchResultsRequest::ServiceFound { peer_id, .. } = service_found_fut.await?;
assert_eq!(connected_peer_id, peer_id.into());
// Peer should be updated with discovered service.
access
.when_satisfied(
expectation::peer_bredr_service_discovered(
connected_peer_id,
Uuid::new16(profile_id.into_primitive()),
),
timeout_duration(),
)
.await?;
Ok(())
}
// TODO(fxbug.dev/1252): the rest of connect_l2cap tests (that actually succeed)
/// Run all test cases.
pub fn run_all() -> Result<(), Error> {
run_suite!(
"bredr.Profile",
[
test_add_profile,
test_same_psm_twice_fails,
test_add_and_remove_profile,
test_connect_unknown_peer,
test_add_search
]
)
}