blob: bde918ac79844698e67d94f18fc6bc66ca534ffb [file] [log] [blame]
// Copyright 2020 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};
use bt_avctp::{AvcPeer, AvcResponseType};
use fidl::endpoints::{create_endpoints, create_proxy, create_request_stream};
use fidl_fuchsia_bluetooth_avrcp::{
AbsoluteVolumeHandlerMarker, AbsoluteVolumeHandlerRequest, AbsoluteVolumeHandlerRequestStream,
ControllerMarker, PeerManagerMarker, TargetHandlerMarker,
};
use fixture::fixture;
use fuchsia_bluetooth::profile::{l2cap_connect_parameters, Psm};
use fuchsia_bluetooth::types::{Channel, Uuid};
use fuchsia_component_test::Capability;
use futures::stream::StreamExt;
use futures::{join, TryFutureExt};
use mock_piconet_client::{BtProfileComponent, PiconetHarness, PiconetMember};
use {
fidl_fuchsia_bluetooth_bredr as bredr, fidl_fuchsia_bluetooth_bredr_test as bredr_test,
fuchsia_async as fasync, fuchsia_zircon as zx,
};
/// AVRCP component URL.
const AVRCP_URL: &str = "fuchsia-pkg://fuchsia.com/bt-avrcp-integration-tests#meta/bt-avrcp.cm";
/// Attribute ID for SDP Support Features.
const SDP_SUPPORTED_FEATURES: u16 = 0x0311;
/// Name prefix for mock peers
const MOCK_PEER_NAME: &str = "mock-peer";
struct AvrcpIntegrationTest {
avrcp_observer: BtProfileComponent,
mock_peer: PiconetMember,
test_realm: fuchsia_component_test::RealmInstance,
}
impl AvrcpIntegrationTest {
async fn new() -> Result<Self, anyhow::Error> {
let mut piconet = PiconetHarness::new().await;
let spec = piconet.add_mock_piconet_member(MOCK_PEER_NAME.to_string(), None).await.unwrap();
let observer = piconet
.add_profile_with_capabilities(
"bt-avrcp-profile".to_string(),
AVRCP_URL.to_string(),
None,
vec![],
vec![Capability::protocol::<PeerManagerMarker>().into()],
)
.await
.unwrap();
let test_realm = piconet.build().await.unwrap();
let mock_peer = PiconetMember::new_from_spec(spec, &test_realm).unwrap();
Ok(Self { avrcp_observer: observer, mock_peer: mock_peer, test_realm: test_realm })
}
}
async fn setup_piconet<F, Fut>(_name: &str, test: F)
where
F: FnOnce(AvrcpIntegrationTest) -> Fut,
Fut: futures::Future<Output = ()>,
{
let test_fixture = AvrcpIntegrationTest::new().await.unwrap();
test(test_fixture).await
}
/// Make the SDP definition for an AVRCP Controller service.
fn avrcp_controller_service_definition() -> bredr::ServiceDefinition {
bredr::ServiceDefinition {
service_class_uuids: Some(vec![Uuid::new16(0x110E).into(), Uuid::new16(0x110F).into()]),
protocol_descriptor_list: Some(vec![
bredr::ProtocolDescriptor {
protocol: Some(bredr::ProtocolIdentifier::L2Cap),
params: Some(vec![bredr::DataElement::Uint16(bredr::PSM_AVCTP as u16)]),
..Default::default()
},
bredr::ProtocolDescriptor {
protocol: Some(bredr::ProtocolIdentifier::Avctp),
params: Some(vec![bredr::DataElement::Uint16(0x0103)]), // Indicate v1.3
..Default::default()
},
]),
profile_descriptors: Some(vec![bredr::ProfileDescriptor {
profile_id: Some(bredr::ServiceClassProfileIdentifier::AvRemoteControl),
major_version: Some(1),
minor_version: Some(6),
..Default::default()
}]),
additional_attributes: Some(vec![bredr::Attribute {
id: Some(SDP_SUPPORTED_FEATURES), // SDP Attribute "SUPPORTED FEATURES"
element: Some(bredr::DataElement::Uint16(0x0003)), // CATEGORY 1 and 2.
..Default::default()
}]),
..Default::default()
}
}
/// Stub absolute volume handler which accepts any requested volume
async fn run_absolute_volume_handler(stream: AbsoluteVolumeHandlerRequestStream) {
stream
.for_each(|request| async move {
match request {
Ok(AbsoluteVolumeHandlerRequest::SetVolume { requested_volume, responder }) => {
responder
.send(requested_volume)
.expect("Error forwarding absolute volume request");
}
_ => {}
}
})
.await;
}
/// Tests that AVRCP correctly advertises it's TG-related services and can be
/// discovered by another peer in the mock piconet.
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn avrcp_target_service_advertisement(tf: AvrcpIntegrationTest) {
let _ = &tf;
let mut results_requests = tf
.mock_peer
.register_service_search(
bredr::ServiceClassProfileIdentifier::AvRemoteControlTarget,
vec![],
)
.unwrap();
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()
else {
panic!("unknown method")
};
assert_eq!(tf.avrcp_observer.peer_id(), peer_id.into());
responder.send().unwrap();
}
/// Tests that AVRCP correctly advertises it's CT-related services and can be
/// discovered by another peer in the mock piconet.
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn avrcp_controller_service_advertisement(tf: AvrcpIntegrationTest) {
let _ = &tf;
let mut results_requests = tf
.mock_peer
.register_service_search(bredr::ServiceClassProfileIdentifier::AvRemoteControl, vec![])
.unwrap();
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()
else {
panic!("unknown method")
};
assert_eq!(tf.avrcp_observer.peer_id(), peer_id.into());
responder.send().unwrap();
}
/// Tests that AVRCP correctly searches for AvRemoteControls, discovers a mock peer
/// providing it, and attempts to connect to the mock peer.
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn avrcp_search_and_connect(mut tf: AvrcpIntegrationTest) {
let _ = &tf;
// Mock peer advertises an AVRCP CT service.
let service_defs = vec![avrcp_controller_service_definition()];
let mut connect_requests = tf
.mock_peer
.register_service_advertisement(service_defs)
.expect("Mock peer failed to register service advertisement");
// We then expect AVRCP to discover the mock peer and attempt to connect to it
tf.avrcp_observer.expect_observer_service_found_request(tf.mock_peer.peer_id()).await.unwrap();
let _channel = match connect_requests.select_next_some().await.unwrap() {
bredr::ConnectionReceiverRequest::Connected { peer_id, channel, .. } => {
assert_eq!(tf.avrcp_observer.peer_id(), peer_id.into());
channel
}
bredr::ConnectionReceiverRequest::_UnknownMethod { .. } => {
panic!("unknown method")
}
};
}
/// Tests the case of a remote peer initiating the AVCTP connection to AVRCP.
/// AVRCP should accept the connection (which should be relayed on the observer), and
/// sending/receiving data should be OK.
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn remote_initiates_connection_to_avrcp(mut tf: AvrcpIntegrationTest) {
let _ = &tf;
let avrcp_profile_id = tf.avrcp_observer.peer_id();
// Mock peer advertises an AVRCP CT service.
let service_defs = vec![avrcp_controller_service_definition()];
let mut connect_requests = tf
.mock_peer
.register_service_advertisement(service_defs)
.expect("Mock peer failed to register service advertisement");
// We then expect AVRCP to discover the mock peer and attempt to connect to it
tf.avrcp_observer.expect_observer_service_found_request(tf.mock_peer.peer_id()).await.unwrap();
match connect_requests.select_next_some().await.unwrap() {
// The `channel` associated with this connection is dropped as only one connection can be
// active at a time.
bredr::ConnectionReceiverRequest::Connected { peer_id, .. } => {
assert_eq!(avrcp_profile_id, peer_id.into());
}
bredr::ConnectionReceiverRequest::_UnknownMethod { .. } => {
panic!("unknown method");
}
};
// We wait `MAX_AVRCP_CONNECTION_ESTABLISHMENT` millis before attempting to connect, as per
// AVRCP 1.6.2, Section 4.1.1, to avoid conflict.
const MAX_AVRCP_CONNECTION_ESTABLISHMENT: zx::Duration = zx::Duration::from_millis(1000);
fasync::Timer::new(fasync::Time::after(MAX_AVRCP_CONNECTION_ESTABLISHMENT)).await;
// Mock peer attempts to connect to AVRCP.
let params = l2cap_connect_parameters(Psm::AVCTP, bredr::ChannelMode::Basic);
let channel = tf.mock_peer.make_connection(avrcp_profile_id, params).await.unwrap();
let channel: Channel = channel.try_into().unwrap();
// The observer of bt-avrcp.cm should be notified of the connection attempt.
match tf.avrcp_observer.expect_observer_request().await.unwrap() {
bredr_test::PeerObserverRequest::PeerConnected { peer_id, responder, .. } => {
assert_eq!(tf.mock_peer.peer_id(), peer_id.into());
responder.send().unwrap();
}
x => panic!("Expected PeerConnected but got: {:?}", x),
}
// Sending a random non-AVRCP data packet should be OK.
let write_result = channel.as_ref().write(&[0x00, 0x00, 0x00]);
assert_eq!(write_result, Ok(3));
// `read_datagram` occasionally returns ready with Ok(0) when there are no bytes read from the
// socket. Instead of erroring, we retry the read operation until we actually receive a nonzero
// amount of data over the channel.
// We expect a general reject response, since the sent packet is malformed.
let expected_read_result_packet = &[
0x03, // Bit 0 indicates invalid profile id, Bit 1 indicates CommandType::Response.
0x00, // Profile ID
0x00, // Profile ID
];
loop {
let mut vec = Vec::new();
let read_result =
channel.read_datagram(&mut vec).await.expect("reading from channel is ok");
if read_result != 0 {
assert_eq!(read_result, 3);
assert_eq!(vec.as_slice(), expected_read_result_packet);
break;
}
}
}
/// Tests the case of a remote device attempting to connect over PSM_BROWSE
/// before PSM_AVCTP. AVRCP 1.6 states that the Browse channel may only be established
/// after the control channel. We expect AVRCP to drop the incoming channel.
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn remote_initiates_browse_channel_before_control(mut tf: AvrcpIntegrationTest) {
let _ = &tf;
let avrcp_profile_id = tf.avrcp_observer.peer_id();
// Mock peer advertises an AVRCP CT service.
let service_defs = vec![avrcp_controller_service_definition()];
let mut connect_requests = tf
.mock_peer
.register_service_advertisement(service_defs)
.expect("Mock peer failed to register service advertisement");
// We then expect AVRCP to discover the mock peer and attempt to connect to it
tf.avrcp_observer.expect_observer_service_found_request(tf.mock_peer.peer_id()).await.unwrap();
match connect_requests.select_next_some().await.unwrap() {
bredr::ConnectionReceiverRequest::Connected { peer_id, .. } => {
assert_eq!(avrcp_profile_id, peer_id.into());
}
bredr::ConnectionReceiverRequest::_UnknownMethod { .. } => {
panic!("unknown method");
}
};
// Mock peer tries to initiate a browse channel connection.
let params =
l2cap_connect_parameters(Psm::AVCTP_BROWSE, bredr::ChannelMode::EnhancedRetransmission);
let channel = tf.mock_peer.make_connection(avrcp_profile_id, params).await.unwrap();
let channel: Channel = channel.try_into().unwrap();
// The observer of bt-avrcp.cm should be notified of the connection attempt.
match tf.avrcp_observer.expect_observer_request().await.unwrap() {
bredr_test::PeerObserverRequest::PeerConnected { peer_id, responder, .. } => {
assert_eq!(tf.mock_peer.peer_id(), peer_id.into());
responder.send().unwrap();
}
x => panic!("Expected PeerConnected but got: {:?}", x),
}
// However, AVRCP should immediately close the `channel` because the Browse channel
// cannot be established before the Control channel.
let closed = channel.closed().await;
assert_eq!(closed, Ok(()));
}
/// Tests that AVRCP's absolute volume handler and target handler can each be set once and only once
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn avrcp_disallows_handler_double_sets(mut tf: AvrcpIntegrationTest) {
// Mock peer advertises service & bt-avrcp discovers it
let service_defs = vec![avrcp_controller_service_definition()];
let mut connect_requests = tf
.mock_peer
.register_service_advertisement(service_defs)
.expect("Mock peer failed to register service advertisement");
// We then expect AVRCP to discover the mock peer and attempt to connect to it
tf.avrcp_observer.expect_observer_service_found_request(tf.mock_peer.peer_id()).await.unwrap();
let _channel = match connect_requests.select_next_some().await.unwrap() {
bredr::ConnectionReceiverRequest::Connected { peer_id, channel, .. } => {
assert_eq!(tf.avrcp_observer.peer_id(), peer_id.into());
channel
}
bredr::ConnectionReceiverRequest::_UnknownMethod { .. } => {
panic!("unknown method");
}
};
// Connect to bt-avrcp.cm's controller service
let avrcp_svc = tf
.avrcp_observer
.connect_to_protocol::<PeerManagerMarker>(&tf.test_realm)
.context("Failed to connect to Bluetooth AVRCP interface")
.unwrap();
// Get controller for mock peer
let (_c_client, c_server) = create_endpoints::<ControllerMarker>();
let _ = avrcp_svc
.get_controller_for_target(&tf.mock_peer.peer_id().into(), c_server)
.await
.unwrap();
// Create absolute volume handler, register with AVRCP
let (avh_client, avh_stream) = create_request_stream::<AbsoluteVolumeHandlerMarker>().unwrap();
let avh_fut = run_absolute_volume_handler(avh_stream);
fasync::Task::spawn(avh_fut).detach();
avrcp_svc
.set_absolute_volume_handler(avh_client)
.await
.unwrap()
.expect("AVRCP should accept first absolute volume handler");
// Register with AVRCP again
// Request should be rejected, so no handler needs to be instantiated
let (avh_client, _avh_server) = create_endpoints::<AbsoluteVolumeHandlerMarker>();
let _ = avrcp_svc
.set_absolute_volume_handler(avh_client)
.await
.unwrap()
.expect_err("AVRCP should reject absolute volume handler double sets");
// Create target handler, register with AVRCP
let (t_client, t_stream) = create_request_stream::<TargetHandlerMarker>().unwrap();
let t_fut = t_stream.for_each(|_request| async move {});
fasync::Task::spawn(t_fut).detach();
avrcp_svc
.register_target_handler(t_client)
.await
.unwrap()
.expect("AVRCP should accept first target handler");
// Register with AVRCP again
// Request should be rejected, so no handler needs to be instantiated
let (t_client, _t_server) = create_endpoints::<TargetHandlerMarker>();
let _ = avrcp_svc
.register_target_handler(t_client)
.await
.unwrap()
.expect_err("AVRCP should reject target handler double sets");
}
/// Confirm that peers can have their absolute volume set
/// through the registered absolute volume handler
#[fixture(setup_piconet)]
#[fuchsia::test]
async fn avrcp_remote_receives_set_absolute_volume_request(mut tf: AvrcpIntegrationTest) {
// Mock peer advertises an AVRCP CT service.
let service_defs = vec![avrcp_controller_service_definition()];
let mut connect_requests = tf
.mock_peer
.register_service_advertisement(service_defs)
.expect("Mock peer failed to register service advertisement");
// We then expect AVRCP to discover the mock peer and attempt to connect to it
tf.avrcp_observer.expect_observer_service_found_request(tf.mock_peer.peer_id()).await.unwrap();
let channel: Channel = match connect_requests.select_next_some().await.unwrap() {
bredr::ConnectionReceiverRequest::Connected { peer_id, channel, .. } => {
assert_eq!(tf.avrcp_observer.peer_id(), peer_id.into());
channel.try_into().unwrap()
}
bredr::ConnectionReceiverRequest::_UnknownMethod { .. } => {
panic!("unknown method")
}
};
// Connect to avrcp controller service.
let avrcp_svc = tf
.avrcp_observer
.connect_to_protocol::<PeerManagerMarker>(&tf.test_realm)
.context("Failed to connect to Bluetooth AVRCP interface")
.unwrap();
// Create absolute volume handler server which simply forwards the desired volume unmodified
let (avh_client, avh_server) = create_request_stream::<AbsoluteVolumeHandlerMarker>().unwrap();
let avh_fut = run_absolute_volume_handler(avh_server);
avrcp_svc
.set_absolute_volume_handler(avh_client)
.await
.unwrap()
.expect("Error registering AVH");
let _avh_task = fasync::Task::spawn(avh_fut);
// Get controller for mock peer
let (c_proxy, c_server) = create_proxy::<ControllerMarker>().unwrap();
avrcp_svc
.get_controller_for_target(&tf.mock_peer.peer_id().into(), c_server)
.await
.unwrap()
.expect("Failed to get controller for mock peer");
// Mock peer receives SetAbsoluteVolume request and responds with the actual volume which was set
let desired_vol: u8 = 0x7F; // Valid volume range is 0x00 - 0x7F
let actual_vol: u8 = 0x7E; // Volume actually set on the target
const SET_ABSOLUTE_VOLUME_PDU: u8 = 0x050;
let mut avc_command_stream = AvcPeer::new(channel).take_command_stream();
async {
let set_av_fut = c_proxy.set_absolute_volume(desired_vol);
let receive_av_fut = async {
match avc_command_stream.next().await.expect("Mock peer did not receive a command") {
Ok(command) => {
// Command and response structure described in AVRCP spec 25.16
let body = command.body();
assert_eq!(body.len(), 5);
assert_eq!(body[0], SET_ABSOLUTE_VOLUME_PDU); // Confirm PDU is SetAbsoluteVolume
assert_eq!(body[4], desired_vol); // Confirm the requested volume was received unmodified
// Respond with the actual volume set on the target
command
.send_response(
AvcResponseType::Accepted,
&[0x50, 0x00, 0x00, 0x01, actual_vol],
)
.unwrap();
}
Err(e) => panic!("{}", e),
}
};
match join!(set_av_fut, receive_av_fut) {
(Ok(Ok(vol)), _) => assert_eq!(vol, actual_vol),
(Ok(Err(e)), _) => {
panic!("Received ControllerError from AVRCP: {}", e.into_primitive())
}
(Err(e), _) => panic!("SetAbsoluteVolume FIDL call failed: {}", e),
}
}
.await;
}