blob: 9fbf4fd7d315209e6f4e5e182de06f7dcfa485de [file] [log] [blame]
// Copyright 2021 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},
at_commands::{self as at, SerDe},
bitflags::bitflags,
bt_profile_test_server_client::{MockPeer, ProfileTestHarness},
fidl::{
encoding::Decodable,
endpoints::{ServerEnd, ServiceMarker},
},
fidl_fuchsia_bluetooth_bredr as bredr,
fidl_fuchsia_bluetooth_hfp::HfpMarker,
fidl_fuchsia_sys::EnvironmentOptions,
fuchsia_async::{self as fasync, DurationExt, TimeoutExt},
fuchsia_bluetooth::types::{Channel, PeerId, Uuid},
fuchsia_component::server::{NestedEnvironment, ServiceFs, ServiceObj},
fuchsia_zircon::Duration,
futures::{stream::StreamExt, TryFutureExt},
matches::assert_matches,
std::convert::TryInto,
test_call_manager::{TestCallManager, HFP_AG_URL},
};
/// SDP Attribute ID for the Supported Features of HFP.
/// Defined in Assigned Numbers for SDP
/// https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
const ATTR_ID_HFP_SUPPORTED_FEATURES: u16 = 0x0311;
/// Timeout for data received over a Channel.
///
/// This time is expected to be:
/// a) sufficient to avoid flakes due to infra or resource contention
/// b) short enough to still provide useful feedback in those cases where asynchronous operations
/// fail
/// c) short enough to fail before the overall infra-imposed test timeout (currently 5 minutes)
const CHANNEL_TIMEOUT: Duration = Duration::from_seconds(2 * 60);
bitflags! {
/// Defined in HFP v1.8, Table 5.2
struct HandsFreeFeaturesSdpAttribute: u16 {
const ECHO_CANCELATION_AND_NOISE_REDUCTION = 0b0000_0001;
const THREE_WAY_CALLING = 0b0000_0010;
const CLI_PRESENTATION = 0b0000_0100;
const VOICE_RECOGNITION = 0b0000_1000;
const REMOTE_VOLUME_CONTROL = 0b0001_0000;
const WIDEBAND_SPEECH = 0b0010_0000;
const ENHANCED_VOICE_RECOGNITION = 0b0100_0000;
const ENHANCED_VOICE_RECOGNITION_TEXT = 0b1000_0000;
}
}
fn hfp_launch_info() -> bredr::LaunchInfo {
let url = Some(HFP_AG_URL.to_string());
bredr::LaunchInfo { component_url: url.clone(), ..bredr::LaunchInfo::EMPTY }
}
/// Make the SDP definition for an HFP Hands Free service.
fn hfp_hf_service_definition() -> bredr::ServiceDefinition {
let supported_features = HandsFreeFeaturesSdpAttribute::empty().bits();
bredr::ServiceDefinition {
service_class_uuids: Some(vec![
Uuid::new16(bredr::ServiceClassProfileIdentifier::Handsfree as u16).into(),
Uuid::new16(bredr::ServiceClassProfileIdentifier::GenericAudio as u16).into(),
]),
protocol_descriptor_list: Some(vec![
bredr::ProtocolDescriptor {
protocol: bredr::ProtocolIdentifier::L2Cap,
params: vec![],
},
bredr::ProtocolDescriptor {
protocol: bredr::ProtocolIdentifier::Rfcomm,
params: vec![],
},
]),
profile_descriptors: Some(vec![bredr::ProfileDescriptor {
profile_id: bredr::ServiceClassProfileIdentifier::Handsfree,
major_version: 1,
minor_version: 8,
}]),
additional_attributes: Some(vec![bredr::Attribute {
id: ATTR_ID_HFP_SUPPORTED_FEATURES,
element: bredr::DataElement::Uint16(supported_features),
}]),
..bredr::ServiceDefinition::new_empty()
}
}
/// Tests that HFP correctly advertises it's services and can be
/// discovered by another peer in the mock piconet.
#[fasync::run_singlethreaded(test)]
async fn test_hfp_ag_service_advertisement() {
let test_harness = ProfileTestHarness::new().expect("Failed to create profile test harness");
// Create MockPeer #1 to be driven by the test.
let id1 = PeerId(1);
let remote_peer = test_harness.register_peer(id1).await.unwrap();
// Peer #1 adds a search for HFP AG in the piconet.
let mut results_requests = remote_peer
.register_service_search(
bredr::ServiceClassProfileIdentifier::HandsfreeAudioGateway,
vec![],
)
.await
.unwrap();
// MockPeer #2 is the profile-under-test: HFP.
let id2 = PeerId(2);
let hfp_under_test = test_harness.register_peer(id2).await.unwrap();
hfp_under_test.launch_profile(hfp_launch_info()).await.expect("launch profile should be ok");
// We expect Peer #1 to discover HFP's service advertisement.
let service_found_fut = results_requests.select_next_some().map_err(|e| format_err!("{:?}", e));
let bredr::SearchResultsRequest::ServiceFound { peer_id, responder, .. } =
service_found_fut.await.unwrap();
assert_eq!(id2, peer_id.into());
responder.send().unwrap();
}
fn launch_hfp(
url: &str,
) -> (ServiceFs<ServiceObj<'_, fidl::Channel>>, NestedEnvironment, fuchsia_component::client::App) {
let mut fs = ServiceFs::new();
fs.add_service_at(bredr::ProfileMarker::NAME, |chan| Some(chan));
let options = EnvironmentOptions {
inherit_parent_services: true,
use_parent_runners: false,
kill_on_oom: false,
delete_storage_on_death: false,
};
let env = fs.create_salted_nested_environment_with_options(&"hfp", options).unwrap();
let app = fuchsia_component::client::launch(env.launcher(), url.to_string(), None).unwrap();
(fs, env, app)
}
async fn connect_profile_to_peer(
peer: &mut MockPeer,
fs: &mut ServiceFs<ServiceObj<'_, fidl::Channel>>,
) -> Result<(), Error> {
let channel = fs.next().await.ok_or(format_err!("Connection expected from hfp component"))?;
let server_end = ServerEnd::<bredr::ProfileMarker>::new(channel);
peer.connect_proxy(server_end).await?;
Ok(())
}
/// Expects a connection request on the `connect_requests` stream from the `other` peer.
/// Returns the Channel.
#[track_caller]
async fn expect_connection(
connect_requests: &mut bredr::ConnectionReceiverRequestStream,
other: PeerId,
) -> Channel {
match connect_requests.select_next_some().await.unwrap() {
bredr::ConnectionReceiverRequest::Connected { peer_id, channel, .. } => {
assert_eq!(other, peer_id.into());
channel.try_into().unwrap()
}
}
}
/// Tests that HFP correctly searches for Handsfree, discovers a mock peer
/// providing it, and attempts to connect to the mock peer.
#[fasync::run_singlethreaded(test)]
async fn test_hfp_search_and_connect() {
let test_harness = ProfileTestHarness::new().expect("Failed to create profile test harness");
// MockPeer #1 is driven by the test.
let id1 = PeerId(0x1111);
let mut remote_peer = test_harness.register_peer(id1).await.unwrap();
// Peer #1 advertises an HFP HF service.
let service_defs = vec![hfp_hf_service_definition()];
let mut connect_requests =
remote_peer.register_service_advertisement(service_defs).await.unwrap();
// MockPeer #2 is the profile-under-test: HFP.
let id2 = PeerId(0x2222);
let mut hfp_under_test = test_harness.register_peer(id2).await.unwrap();
// Launch hfp component and wire it up to MockPeer #2
let (mut fs, _env, app) = launch_hfp(HFP_AG_URL);
connect_profile_to_peer(&mut hfp_under_test, &mut fs).await.unwrap();
let proxy = app.connect_to_service::<HfpMarker>().unwrap();
let facade = TestCallManager::new();
facade.register_manager(proxy).await.unwrap();
// We expect HFP to discover Peer #1's service advertisement.
assert_matches!(hfp_under_test.expect_observer_service_found_request(id1).await, Ok(()));
// We then expect HFP to attempt to connect to Peer #1.
let _channel = expect_connection(&mut connect_requests, id2).await;
// The observer of Peer #1 should be relayed of the connection attempt.
assert_matches!(remote_peer.expect_observer_connection_request(id2).await, Ok(()));
}
/// Expects data on the provided `channel` and verifies the contents with the `expected` AT
/// messages.
#[track_caller]
async fn expect_data(channel: &mut Channel, expected: Vec<at::Response>) {
let _expected_bytes: Vec<u8> = expected
.into_iter()
.map(|exp| {
let mut bytes = Vec::new();
exp.serialize(&mut bytes).expect("serialization should succeed");
bytes
})
.flatten()
.collect();
let mut actual_bytes = Vec::new();
let read_result = channel
.read_datagram(&mut actual_bytes)
.on_timeout(CHANNEL_TIMEOUT.after_now(), move || Err(fidl::Status::TIMED_OUT))
.await;
assert_matches!(read_result, Ok(_));
// TODO(fxbug.dev/73568): Uncomment and update this assert when AT library deserialization
// supports parsing multiple AT commands in the same buffer. We should change the assert
// to parse `actual_bytes` into at::Responses and compare them to `expected`. This will allow
// us to use assert_matches! to validate the _type_ of the response, but not necessarily the
// contents since that is implementation specific.
// assert_eq!(actual_bytes, expected_bytes);
}
/// Serializes and sends the provided AT `command` using the `channel` and then
/// expects the `expected` response.
#[track_caller]
async fn send_command_and_expect_response(
channel: &mut Channel,
command: at::Command,
expected: Vec<at::Response>,
) {
// Serialize and send.
let mut bytes = Vec::new();
command.serialize(&mut bytes).expect("serialization should succeed");
let _ = channel.as_ref().write(&bytes);
// Expect the `expected` data as a response.
expect_data(channel, expected).await;
}
/// Tests that HFP correctly responds to the SLC Initialization procedure after the
/// RFCOMM channel has been connected.
/// Note: This integration test validates that the expected responses are received, but
/// does not validate individual field values (e.g the exact features or exact indicators) as
/// this is implementation specific.
#[fasync::run_singlethreaded(test)]
async fn test_hfp_full_slc_init_procedure() {
let test_harness = ProfileTestHarness::new().expect("Failed to create profile test harness");
// MockPeer #1 is driven by the test.
let id1 = PeerId(0x55);
let mut remote_peer = test_harness.register_peer(id1).await.unwrap();
// Peer #1 advertises an HFP HF service.
let service_defs = vec![hfp_hf_service_definition()];
let mut connect_requests =
remote_peer.register_service_advertisement(service_defs).await.unwrap();
// MockPeer #2 is the profile-under-test: HFP.
let id2 = PeerId(0x66);
let mut hfp_under_test = test_harness.register_peer(id2).await.unwrap();
// Launch hfp component and wire it up to MockPeer #2
let (mut fs, _env, app) = launch_hfp(HFP_AG_URL);
connect_profile_to_peer(&mut hfp_under_test, &mut fs).await.unwrap();
let proxy = app.connect_to_service::<HfpMarker>().unwrap();
let facade = TestCallManager::new();
facade.register_manager(proxy).await.unwrap();
// We expect HFP to discover Peer #1's service advertisement.
assert_matches!(hfp_under_test.expect_observer_service_found_request(id1).await, Ok(()));
// We then expect HFP to attempt to connect to Peer #1.
let mut remote = expect_connection(&mut connect_requests, id2).await;
// The observer of Peer #1 should be relayed of the connection attempt.
assert_matches!(remote_peer.expect_observer_connection_request(id2).await, Ok(()));
// Peer sends its HF features to the HFP component (AG) - we expect HFP to send
// its AG features back.
// TODO: We shouldn't need to assert on the specific features in the response.
let hf_features_cmd = at::Command::Brsf { features: 0b11_1111_1111_1111 };
send_command_and_expect_response(
&mut remote,
hf_features_cmd,
vec![at::success(at::Success::Brsf { features: 3955i64 }), at::Response::Ok],
)
.await;
// Peer sends its supported codecs - expect OK back.
let peer_supported_codecs_cmd = at::Command::Bac { codecs: vec![] };
send_command_and_expect_response(
&mut remote,
peer_supported_codecs_cmd,
vec![at::Response::Ok],
)
.await;
let indicator_test_cmd = at::Command::CindTest {};
let expected3 = at::success(at::Success::Cind {
service: true,
call: true,
callsetup: 0,
callheld: 0,
roam: true,
signal: 0,
battchg: 0,
});
send_command_and_expect_response(
&mut remote,
indicator_test_cmd,
vec![expected3, at::Response::Ok],
)
.await;
let indicator_read_cmd = at::Command::CindRead {};
let expected4 = at::success(at::Success::Cind {
service: false,
call: false,
callsetup: 0,
callheld: 0,
roam: false,
signal: 0,
battchg: 5,
});
send_command_and_expect_response(
&mut remote,
indicator_read_cmd,
vec![expected4, at::Response::Ok],
)
.await;
// Peer enables indicator reporting (ind = 1).
let peer_enable_ind_reporting_cmd = at::Command::Cmer { mode: 3, keyp: 0, disp: 0, ind: 1 };
send_command_and_expect_response(
&mut remote,
peer_enable_ind_reporting_cmd,
vec![at::Response::Ok],
)
.await;
let call_hold_info_cmd = at::Command::ChldTest {};
let expected6 = at::success(at::Success::Chld { commands: vec![] });
send_command_and_expect_response(
&mut remote,
call_hold_info_cmd,
vec![expected6, at::Response::Ok],
)
.await;
let peer_supported_indicators_cmd =
at::Command::Bind { indicators: vec![at::BluetoothHFIndicator::BatteryLevel] };
send_command_and_expect_response(
&mut remote,
peer_supported_indicators_cmd,
vec![at::Response::Ok],
)
.await;
let peer_request_indicators_cmd = at::Command::BindTest {};
let expected8 = at::success(at::Success::BindList { indicators: vec![] });
send_command_and_expect_response(
&mut remote,
peer_request_indicators_cmd,
vec![expected8, at::Response::Ok],
)
.await;
let peer_request_ind_status_cmd = at::Command::BindRead {};
let expected9 = at::success(at::Success::BindStatus {
anum: at::BluetoothHFIndicator::BatteryLevel,
state: true,
});
send_command_and_expect_response(
&mut remote,
peer_request_ind_status_cmd,
vec![expected9, at::Response::Ok],
)
.await;
}