blob: dd49022cc50107d0426f48abff93b422da64dbbc [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.
#include "profile_server.h"
#include <fuchsia/bluetooth/bredr/cpp/fidl_test_base.h>
#include <zircon/errors.h>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "fuchsia/bluetooth/bredr/cpp/fidl.h"
#include "fuchsia/bluetooth/cpp/fidl.h"
#include "lib/fidl/cpp/vector.h"
#include "lib/zx/socket.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/adapter_test_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/fake_adapter_test_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/host_error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_l2cap.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/data_element.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/sdp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
namespace bthost {
namespace {
namespace fidlbredr = fuchsia::bluetooth::bredr;
namespace android_hci = pw::bluetooth::vendor::android_hci;
using bt::l2cap::testing::FakeChannel;
using pw::bluetooth::AclPriority;
using FeaturesBits = pw::bluetooth::Controller::FeaturesBits;
void NopAdvertiseCallback(fidlbredr::Profile_Advertise_Result) {}
const bt::DeviceAddress kTestDevAddr(bt::DeviceAddress::Type::kBREDR, {1});
constexpr bt::l2cap::Psm kPsm = bt::l2cap::kAVDTP;
constexpr uint16_t kSynchronousDataPacketLength = 64;
constexpr uint8_t kTotalNumSynchronousDataPackets = 1;
fidlbredr::ScoConnectionParameters CreateScoConnectionParameters(
fidlbredr::HfpParameterSet param_set = fidlbredr::HfpParameterSet::T2) {
fidlbredr::ScoConnectionParameters params;
params.set_parameter_set(param_set);
params.set_air_coding_format(fidlbredr::CodingFormat::MSBC);
params.set_air_frame_size(8u);
params.set_io_bandwidth(32000);
params.set_io_coding_format(fidlbredr::CodingFormat::LINEAR_PCM);
params.set_io_frame_size(16u);
params.set_io_pcm_data_format(fuchsia::hardware::audio::SampleFormat::PCM_SIGNED);
params.set_io_pcm_sample_payload_msb_position(3u);
params.set_path(fidlbredr::DataPath::OFFLOAD);
return params;
}
fidlbredr::ServiceDefinition MakeFIDLServiceDefinition() {
fidlbredr::ServiceDefinition def;
def.mutable_service_class_uuids()->emplace_back(
fidl_helpers::UuidToFidl(bt::sdp::profile::kAudioSink));
fidlbredr::ProtocolDescriptor l2cap_proto;
l2cap_proto.protocol = fidlbredr::ProtocolIdentifier::L2CAP;
fidlbredr::DataElement l2cap_data_el;
l2cap_data_el.set_uint16(fidlbredr::PSM_AVDTP);
l2cap_proto.params.emplace_back(std::move(l2cap_data_el));
def.mutable_protocol_descriptor_list()->emplace_back(std::move(l2cap_proto));
fidlbredr::ProtocolDescriptor avdtp_proto;
avdtp_proto.protocol = fidlbredr::ProtocolIdentifier::AVDTP;
fidlbredr::DataElement avdtp_data_el;
avdtp_data_el.set_uint16(0x0103); // Version 1.3
avdtp_proto.params.emplace_back(std::move(avdtp_data_el));
def.mutable_protocol_descriptor_list()->emplace_back(std::move(avdtp_proto));
fidlbredr::ProfileDescriptor prof_desc;
prof_desc.profile_id = fidlbredr::ServiceClassProfileIdentifier::ADVANCED_AUDIO_DISTRIBUTION;
prof_desc.major_version = 1;
prof_desc.minor_version = 3;
def.mutable_profile_descriptors()->emplace_back(prof_desc);
// Additional attributes are also OK.
fidlbredr::Attribute addl_attr;
addl_attr.id = 0x000A; // Documentation URL ID
fidlbredr::DataElement doc_url_el;
doc_url_el.set_url("fuchsia.dev");
addl_attr.element = std::move(doc_url_el);
def.mutable_additional_attributes()->emplace_back(std::move(addl_attr));
return def;
}
// Returns a basic protocol list element with a protocol descriptor list that only contains an L2CAP
// descriptor.
bt::sdp::DataElement MakeL2capProtocolListElement() {
bt::sdp::DataElement l2cap_uuid_el;
l2cap_uuid_el.Set(bt::UUID(bt::sdp::protocol::kL2CAP));
std::vector<bt::sdp::DataElement> l2cap_descriptor_list;
l2cap_descriptor_list.emplace_back(std::move(l2cap_uuid_el));
std::vector<bt::sdp::DataElement> protocols;
protocols.emplace_back(std::move(l2cap_descriptor_list));
bt::sdp::DataElement protocol_list_el;
protocol_list_el.Set(std::move(protocols));
return protocol_list_el;
}
class FakeScoConnectionReceiver : public fidlbredr::testing::ScoConnectionReceiver_TestBase {
public:
FakeScoConnectionReceiver(fidl::InterfaceRequest<ScoConnectionReceiver> request,
async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher), connected_count_(0), error_count_(0) {}
void Connected(fidlbredr::ScoConnectionHandle connection,
fidlbredr::ScoConnectionParameters params) override {
connection_.Bind(std::move(connection));
parameters_ = std::move(params);
connected_count_++;
}
void Error(fidlbredr::ScoErrorCode error) override {
error_ = error;
error_count_++;
}
void Close() { binding_.Close(ZX_ERR_PEER_CLOSED); }
size_t connected_count() const { return connected_count_; }
const fidlbredr::ScoConnectionPtr& connection() const { return connection_; }
fidlbredr::ScoConnectionPtr take_connection() { return std::move(connection_); }
const std::optional<fidlbredr::ScoConnectionParameters>& parameters() const {
return parameters_;
}
size_t error_count() const { return error_count_; }
const std::optional<fidlbredr::ScoErrorCode>& error() const { return error_; }
private:
fidl::Binding<ScoConnectionReceiver> binding_;
size_t connected_count_;
fidlbredr::ScoConnectionPtr connection_;
std::optional<fidlbredr::ScoConnectionParameters> parameters_;
std::optional<uint16_t> max_tx_data_size_;
size_t error_count_;
std::optional<fidlbredr::ScoErrorCode> error_;
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
};
using TestingBase = bthost::testing::AdapterTestFixture;
class ProfileServerTest : public TestingBase {
public:
ProfileServerTest() = default;
~ProfileServerTest() override = default;
protected:
void SetUp(FeaturesBits features) {
bt::testing::FakeController::Settings settings;
settings.ApplyDualModeDefaults();
settings.synchronous_data_packet_length = kSynchronousDataPacketLength;
settings.total_num_synchronous_data_packets = kTotalNumSynchronousDataPackets;
TestingBase::SetUp(settings, features);
fidlbredr::ProfileHandle profile_handle;
client_.Bind(std::move(profile_handle));
server_ =
std::make_unique<ProfileServer>(adapter()->AsWeakPtr(), client_.NewRequest(dispatcher()));
}
void SetUp() override { SetUp(FeaturesBits{0}); }
void TearDown() override {
RunLoopUntilIdle();
client_ = nullptr;
server_ = nullptr;
TestingBase::TearDown();
}
ProfileServer* server() const { return server_.get(); }
fidlbredr::ProfilePtr& client() { return client_; }
bt::gap::PeerCache* peer_cache() const { return adapter()->peer_cache(); }
private:
std::unique_ptr<ProfileServer> server_;
fidlbredr::ProfilePtr client_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ProfileServerTest);
};
class FakeConnectionReceiver : public fidlbredr::testing::ConnectionReceiver_TestBase {
public:
FakeConnectionReceiver(fidl::InterfaceRequest<ConnectionReceiver> request,
async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher), connected_count_(0), closed_(false) {
binding_.set_error_handler([&](zx_status_t /*status*/) { closed_ = true; });
}
void Connected(fuchsia::bluetooth::PeerId peer_id, fidlbredr::Channel channel,
std::vector<fidlbredr::ProtocolDescriptor> protocol) override {
peer_id_ = peer_id;
channel_ = std::move(channel);
protocol_ = std::move(protocol);
connected_count_++;
}
void Revoke() { binding_.events().OnRevoke(); }
size_t connected_count() const { return connected_count_; }
const std::optional<fuchsia::bluetooth::PeerId>& peer_id() const { return peer_id_; }
const std::optional<fidlbredr::Channel>& channel() const { return channel_; }
const std::optional<std::vector<fidlbredr::ProtocolDescriptor>>& protocol() const {
return protocol_;
}
bool closed() { return closed_; }
std::optional<fidlbredr::AudioDirectionExtPtr> bind_ext_direction() {
if (!channel().has_value()) {
return std::nullopt;
}
fidlbredr::AudioDirectionExtPtr client = channel_.value().mutable_ext_direction()->Bind();
return client;
}
fidlbredr::Channel take_channel() {
fidlbredr::Channel channel = std::move(channel_.value());
channel_.reset();
return channel;
}
private:
fidl::Binding<ConnectionReceiver> binding_;
size_t connected_count_;
std::optional<fuchsia::bluetooth::PeerId> peer_id_;
std::optional<fidlbredr::Channel> channel_;
std::optional<std::vector<fidlbredr::ProtocolDescriptor>> protocol_;
bool closed_;
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
};
class FakeSearchResults : public fidlbredr::testing::SearchResults_TestBase {
public:
FakeSearchResults(fidl::InterfaceRequest<SearchResults> request, async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher), service_found_count_(0) {
binding_.set_error_handler([this](zx_status_t) { closed_ = true; });
}
void ServiceFound(fuchsia::bluetooth::PeerId peer_id,
fidl::VectorPtr<fidlbredr::ProtocolDescriptor> protocol,
std::vector<fidlbredr::Attribute> attributes,
ServiceFoundCallback callback) override {
peer_id_ = peer_id;
attributes_ = std::move(attributes);
callback(fidlbredr::SearchResults_ServiceFound_Result::WithResponse(
fidlbredr::SearchResults_ServiceFound_Response()));
service_found_count_++;
}
bool closed() const { return closed_; }
size_t service_found_count() const { return service_found_count_; }
const std::optional<fuchsia::bluetooth::PeerId>& peer_id() const { return peer_id_; }
const std::optional<std::vector<fidlbredr::Attribute>>& attributes() const { return attributes_; }
private:
bool closed_ = false;
fidl::Binding<SearchResults> binding_;
std::optional<fuchsia::bluetooth::PeerId> peer_id_;
std::optional<std::vector<fidlbredr::Attribute>> attributes_;
size_t service_found_count_;
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
};
TEST_F(ProfileServerTest, ErrorOnInvalidDefinition) {
fidlbredr::ConnectionReceiverHandle receiver_handle;
fidl::InterfaceRequest<fidlbredr::ConnectionReceiver> request = receiver_handle.NewRequest();
std::vector<fidlbredr::ServiceDefinition> services;
fidlbredr::ServiceDefinition def;
// Empty service definition is not allowed - it must contain at least a service UUID.
services.emplace_back(std::move(def));
auto cb = [](fidlbredr::Profile_Advertise_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_receiver(std::move(receiver_handle));
client()->Advertise(std::move(adv_request), std::move(cb));
RunLoopUntilIdle();
// Server should close because it's not a good definition.
zx_signals_t signals;
request.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(0), &signals);
EXPECT_TRUE(signals & ZX_CHANNEL_PEER_CLOSED);
}
TEST_F(ProfileServerTest, ErrorOnMultipleAdvertiseRequests) {
fidlbredr::ConnectionReceiverHandle receiver_handle1;
fidl::InterfaceRequest<fidlbredr::ConnectionReceiver> request1 = receiver_handle1.NewRequest();
std::vector<fidlbredr::ServiceDefinition> services1;
services1.emplace_back(MakeFIDLServiceDefinition());
// First callback should never be called since the first advertisement is valid.
size_t cb1_count = 0;
auto cb1 = [&](auto) { cb1_count++; };
fidlbredr::ProfileAdvertiseRequest adv_request1;
adv_request1.set_services(std::move(services1));
adv_request1.set_receiver(std::move(receiver_handle1));
client()->Advertise(std::move(adv_request1), std::move(cb1));
RunLoopUntilIdle();
ASSERT_EQ(cb1_count, 0u);
fidlbredr::ConnectionReceiverHandle receiver_handle2;
fidl::InterfaceRequest<fidlbredr::ConnectionReceiver> request2 = receiver_handle2.NewRequest();
std::vector<fidlbredr::ServiceDefinition> services2;
services2.emplace_back(MakeFIDLServiceDefinition());
// Second callback should error because the second advertisement is requesting a taken PSM.
size_t cb2_count = 0;
auto cb2 = [&](fidlbredr::Profile_Advertise_Result response) {
cb2_count++;
EXPECT_TRUE(response.is_err());
EXPECT_EQ(response.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
fidlbredr::ProfileAdvertiseRequest adv_request2;
adv_request2.set_services(std::move(services2));
adv_request2.set_receiver(std::move(receiver_handle2));
client()->Advertise(std::move(adv_request2), std::move(cb2));
RunLoopUntilIdle();
ASSERT_EQ(cb1_count, 0u);
ASSERT_EQ(cb2_count, 1u);
// Second channel should close.
zx_signals_t signals;
request2.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time(0), &signals);
EXPECT_TRUE(signals & ZX_CHANNEL_PEER_CLOSED);
// Unregister the first advertisement.
request1 = receiver_handle1.NewRequest();
RunLoopUntilIdle();
EXPECT_EQ(cb1_count, 1u);
}
TEST_F(ProfileServerTest, ErrorOnInvalidConnectParametersNoPsm) {
// Random peer, since we don't expect the connection.
fuchsia::bluetooth::PeerId peer_id{123};
// No PSM provided - this is invalid.
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
// Expect an error result.
auto sock_cb = [](fidlbredr::Profile_Connect_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Connect(peer_id, std::move(conn_params), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(ProfileServerTest, ErrorOnInvalidConnectParametersRfcomm) {
// Random peer, since we don't expect the connection.
fuchsia::bluetooth::PeerId peer_id{123};
// RFCOMM Parameters are provided - this is not supported.
fidlbredr::RfcommParameters rfcomm_params;
fidlbredr::ConnectParameters conn_params;
conn_params.set_rfcomm(std::move(rfcomm_params));
// Expect an error result.
auto sock_cb = [](fidlbredr::Profile_Connect_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Connect(peer_id, std::move(conn_params), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(ProfileServerTest, UnregisterAdvertisementTriggersCallback) {
fidlbredr::ConnectionReceiverHandle receiver_handle;
fidl::InterfaceRequest<fidlbredr::ConnectionReceiver> request = receiver_handle.NewRequest();
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
size_t cb_count = 0;
auto cb = [&](fidlbredr::Profile_Advertise_Result result) {
cb_count++;
EXPECT_TRUE(result.is_response());
};
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_receiver(std::move(receiver_handle));
client()->Advertise(std::move(adv_request), std::move(cb));
RunLoopUntilIdle();
// Advertisement is still active, callback shouldn't get triggered.
ASSERT_EQ(cb_count, 0u);
// Overwrite the server end of the ConnectionReceiver.
request = receiver_handle.NewRequest();
RunLoopUntilIdle();
// Profile server should drop the advertisement and notify the callback of termination.
ASSERT_EQ(cb_count, 1u);
}
TEST_F(ProfileServerTest, RevokeConnectionReceiverUnregistersAdvertisement) {
fidlbredr::ConnectionReceiverHandle receiver_handle;
FakeConnectionReceiver connect_receiver(receiver_handle.NewRequest(), dispatcher());
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
size_t cb_count = 0;
auto cb = [&](fidlbredr::Profile_Advertise_Result result) {
cb_count++;
EXPECT_TRUE(result.is_response());
};
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_receiver(std::move(receiver_handle));
client()->Advertise(std::move(adv_request), std::move(cb));
RunLoopUntilIdle();
// Advertisement is still active, callback shouldn't get triggered.
ASSERT_EQ(cb_count, 0u);
ASSERT_FALSE(connect_receiver.closed());
// Server end of `ConnectionReceiver` revokes the advertisement.
connect_receiver.Revoke();
RunLoopUntilIdle();
// Profile server should drop the advertisement and notify the callback of termination. The
// `connect_receiver` should be closed.
ASSERT_EQ(cb_count, 1u);
ASSERT_TRUE(connect_receiver.closed());
}
class ProfileServerTestConnectedPeer : public ProfileServerTest {
public:
ProfileServerTestConnectedPeer() = default;
~ProfileServerTestConnectedPeer() override = default;
protected:
void SetUp(FeaturesBits features) {
ProfileServerTest::SetUp(features);
peer_ = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
std::unique_ptr<bt::testing::FakePeer> fake_peer =
std::make_unique<bt::testing::FakePeer>(kTestDevAddr, pw_dispatcher());
test_device()->AddPeer(std::move(fake_peer));
std::optional<bt::hci::Result<>> status;
auto connect_cb = [this, &status](auto cb_status, auto cb_conn_ref) {
ASSERT_TRUE(cb_conn_ref);
status = cb_status;
connection_ = std::move(cb_conn_ref);
};
EXPECT_TRUE(adapter()->bredr()->Connect(peer_->identifier(), connect_cb));
EXPECT_EQ(bt::gap::Peer::ConnectionState::kInitializing, peer_->bredr()->connection_state());
RunLoopUntilIdle();
ASSERT_TRUE(status.has_value());
EXPECT_EQ(fit::ok(), status.value());
ASSERT_TRUE(connection_);
EXPECT_EQ(peer_->identifier(), connection_->peer_id());
EXPECT_NE(bt::gap::Peer::ConnectionState::kNotConnected, peer_->bredr()->connection_state());
}
void SetUp() override { SetUp(FeaturesBits::kHciSco); }
void TearDown() override {
connection_ = nullptr;
peer_ = nullptr;
ProfileServerTest::TearDown();
}
bt::gap::BrEdrConnection* connection() const { return connection_; }
bt::gap::Peer* peer() const { return peer_; }
private:
bt::gap::BrEdrConnection* connection_;
bt::gap::Peer* peer_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ProfileServerTestConnectedPeer);
};
class ProfileServerTestScoConnected : public ProfileServerTestConnectedPeer {
public:
void SetUp() override {
fidlbredr::ScoConnectionParameters params =
CreateScoConnectionParameters(fidlbredr::HfpParameterSet::D0);
params.set_path(fidlbredr::DataPath::HOST);
SetUp(std::move(params));
}
void SetUp(fidlbredr::ScoConnectionParameters conn_params) {
ProfileServerTestConnectedPeer::SetUp(FeaturesBits::kHciSco);
test_device()->set_configure_sco_cb([](auto, auto, auto, auto cb) { cb(PW_STATUS_OK); });
test_device()->set_reset_sco_cb([](auto cb) { cb(PW_STATUS_OK); });
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(conn_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{peer()->identifier().value()});
request.set_initiator(false);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
test_device()->SendConnectionRequest(peer()->address(), pw::bluetooth::emboss::LinkType::SCO);
RunLoopUntilIdle();
ASSERT_TRUE(receiver.connection().is_bound());
sco_connection_ = receiver.take_connection();
sco_connection_.set_error_handler([this](zx_status_t status) {
sco_connection_ = nullptr;
sco_conn_error_ = status;
});
// Find the link handle used for the SCO connection.
bt::testing::FakePeer* fake_peer = test_device()->FindPeer(peer()->address());
ASSERT_TRUE(fake_peer);
// There are 2 connections: BR/EDR, SCO
ASSERT_EQ(fake_peer->logical_links().size(), 2u);
bt::testing::FakePeer::HandleSet links = fake_peer->logical_links();
// The link that is not the BR/EDR connection link must be the SCO link.
links.erase(connection()->link().handle());
sco_conn_handle_ = *links.begin();
}
void TearDown() override { ProfileServerTestConnectedPeer::TearDown(); }
fidlbredr::ScoConnectionPtr& sco_connection() { return sco_connection_; }
std::optional<zx_status_t> sco_conn_error() const { return sco_conn_error_; }
bt::hci_spec::ConnectionHandle sco_handle() const { return sco_conn_handle_; }
private:
fidlbredr::ScoConnectionPtr sco_connection_;
bt::hci_spec::ConnectionHandle sco_conn_handle_;
std::optional<zx_status_t> sco_conn_error_;
};
class ProfileServerTestOffloadedScoConnected : public ProfileServerTestScoConnected {
public:
void SetUp() override {
fidlbredr::ScoConnectionParameters params =
CreateScoConnectionParameters(fidlbredr::HfpParameterSet::D0);
params.set_path(fidlbredr::DataPath::OFFLOAD);
ProfileServerTestScoConnected::SetUp(std::move(params));
}
};
TEST_F(ProfileServerTestConnectedPeer, ConnectL2capChannelParameters) {
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
// Approve pairing requests.
pairing_delegate->SetConfirmPairingCallback(
[](bt::PeerId, auto confirm_cb) { confirm_cb(true); });
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
bt::l2cap::ChannelParameters expected_params;
expected_params.mode = bt::l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission;
expected_params.max_rx_sdu_size = bt::l2cap::kMinACLMTU;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41,
expected_params);
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto chan_cb = [&channel](fidlbredr::Profile_Connect_Result result) {
EXPECT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
// Initiates pairing
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
// Set L2CAP channel parameters
fidlbredr::ChannelParameters chan_params;
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
chan_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
chan_params.set_max_rx_sdu_size(bt::l2cap::kMinACLMTU);
l2cap_params.set_psm(kPsm);
l2cap_params.set_parameters(std::move(chan_params));
conn_params.set_l2cap(std::move(l2cap_params));
client()->Connect(peer_id, std::move(conn_params), std::move(chan_cb));
RunLoopUntilIdle();
ASSERT_TRUE(channel.has_value());
EXPECT_TRUE(channel->has_socket());
EXPECT_FALSE(channel->IsEmpty());
EXPECT_EQ(channel->channel_mode(), chan_params.channel_mode());
// FakeL2cap returns channels with max tx sdu size of kDefaultMTU.
EXPECT_EQ(channel->max_tx_sdu_size(), bt::l2cap::kDefaultMTU);
EXPECT_FALSE(channel->has_ext_direction());
EXPECT_FALSE(channel->has_flush_timeout());
}
TEST_F(ProfileServerTestConnectedPeer,
ConnectWithAuthenticationRequiredButLinkKeyNotAuthenticatedFails) {
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kNoInputNoOutput);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
fidlbredr::SecurityRequirements security;
security.set_authentication_required(true);
// Set L2CAP channel parameters
fidlbredr::ChannelParameters chan_params;
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
chan_params.set_security_requirements(std::move(security));
l2cap_params.set_psm(kPsm);
l2cap_params.set_parameters(std::move(chan_params));
conn_params.set_l2cap(std::move(l2cap_params));
size_t sock_cb_count = 0;
auto sock_cb = [&](fidlbredr::Profile_Connect_Result result) {
sock_cb_count++;
ASSERT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::bluetooth::ErrorCode::FAILED, result.err());
};
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
// Initiates pairing.
// FakeController will create an unauthenticated key.
client()->Connect(peer_id, std::move(conn_params), std::move(sock_cb));
RunLoopUntilIdle();
EXPECT_EQ(1u, sock_cb_count);
}
// Tests receiving an empty Channel results in an error propagated through the callback.
TEST_F(ProfileServerTestConnectedPeer, ConnectEmptyChannelResponse) {
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
// Approve pairing requests.
pairing_delegate->SetConfirmPairingCallback(
[](bt::PeerId, auto confirm_cb) { confirm_cb(true); });
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Make the l2cap channel creation fail.
l2cap()->set_simulate_open_channel_failure(true);
bt::l2cap::ChannelParameters expected_params;
expected_params.mode = bt::l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission;
expected_params.max_rx_sdu_size = bt::l2cap::kMinACLMTU;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41,
expected_params);
fidlbredr::ChannelParameters chan_params;
chan_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
chan_params.set_max_rx_sdu_size(bt::l2cap::kMinACLMTU);
auto sock_cb = [](fidlbredr::Profile_Connect_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::bluetooth::ErrorCode::FAILED, result.err());
};
// Initiates pairing
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(kPsm);
l2cap_params.set_parameters(std::move(chan_params));
conn_params.set_l2cap(std::move(l2cap_params));
client()->Connect(peer_id, std::move(conn_params), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestConnectedPeer,
AdvertiseChannelParametersReceivedInOnChannelConnectedCallback) {
constexpr uint16_t kTxMtu = bt::l2cap::kMinACLMTU;
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
fidlbredr::ConnectionReceiverHandle connect_receiver_handle;
FakeConnectionReceiver connect_receiver(connect_receiver_handle.NewRequest(), dispatcher());
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
fidlbredr::ChannelParameters chan_params;
chan_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_parameters(std::move(chan_params));
adv_request.set_receiver(std::move(connect_receiver_handle));
client()->Advertise(std::move(adv_request), NopAdvertiseCallback);
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 0u);
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41, kTxMtu));
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 1u);
ASSERT_EQ(connect_receiver.peer_id().value().value, peer()->identifier().value());
ASSERT_TRUE(connect_receiver.channel().value().has_socket());
EXPECT_EQ(connect_receiver.channel().value().channel_mode(),
fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
EXPECT_EQ(connect_receiver.channel().value().max_tx_sdu_size(), kTxMtu);
EXPECT_FALSE(connect_receiver.channel().value().has_ext_direction());
EXPECT_FALSE(connect_receiver.channel().value().has_flush_timeout());
}
class AclPrioritySupportedTest : public ProfileServerTestConnectedPeer {
public:
void SetUp() override {
ProfileServerTestConnectedPeer::SetUp(FeaturesBits::kSetAclPriorityCommand);
}
};
class PriorityTest
: public AclPrioritySupportedTest,
public ::testing::WithParamInterface<std::pair<fidlbredr::A2dpDirectionPriority, bool>> {};
TEST_P(PriorityTest, OutboundConnectAndSetPriority) {
const fidlbredr::A2dpDirectionPriority kPriority = GetParam().first;
const bool kExpectSuccess = GetParam().second;
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
// Approve pairing requests.
pairing_delegate->SetConfirmPairingCallback(
[](bt::PeerId, auto confirm_cb) { confirm_cb(true); });
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41,
bt::l2cap::ChannelParameters());
FakeChannel::WeakPtr fake_channel;
l2cap()->set_channel_callback([&](FakeChannel::WeakPtr chan) { fake_channel = std::move(chan); });
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto chan_cb = [&channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(kPsm);
conn_params.set_l2cap(std::move(l2cap_params));
// Initiates pairing
client()->Connect(peer_id, std::move(conn_params), std::move(chan_cb));
RunLoopUntilIdle();
ASSERT_TRUE(fake_channel.is_alive());
ASSERT_TRUE(channel.has_value());
ASSERT_TRUE(channel->has_ext_direction());
fidlbredr::AudioDirectionExtPtr client = channel->mutable_ext_direction()->Bind();
size_t priority_cb_count = 0;
fake_channel->set_acl_priority_fails(!kExpectSuccess);
client->SetPriority(kPriority, [&](fidlbredr::AudioDirectionExt_SetPriority_Result result) {
EXPECT_EQ(result.is_response(), kExpectSuccess);
priority_cb_count++;
});
RunLoopUntilIdle();
EXPECT_EQ(priority_cb_count, 1u);
client = nullptr;
RunLoopUntilIdle();
if (kExpectSuccess) {
switch (kPriority) {
case fidlbredr::A2dpDirectionPriority::SOURCE:
EXPECT_EQ(fake_channel->requested_acl_priority(), AclPriority::kSource);
break;
case fidlbredr::A2dpDirectionPriority::SINK:
EXPECT_EQ(fake_channel->requested_acl_priority(), AclPriority::kSink);
break;
default:
EXPECT_EQ(fake_channel->requested_acl_priority(), AclPriority::kNormal);
}
} else {
EXPECT_EQ(fake_channel->requested_acl_priority(), AclPriority::kNormal);
}
}
const std::array<std::pair<fidlbredr::A2dpDirectionPriority, bool>, 4> kPriorityParams = {
{{fidlbredr::A2dpDirectionPriority::SOURCE, false},
{fidlbredr::A2dpDirectionPriority::SOURCE, true},
{fidlbredr::A2dpDirectionPriority::SINK, true},
{fidlbredr::A2dpDirectionPriority::NORMAL, true}}};
INSTANTIATE_TEST_SUITE_P(ProfileServerTestConnectedPeer, PriorityTest,
::testing::ValuesIn(kPriorityParams));
TEST_F(AclPrioritySupportedTest, InboundConnectAndSetPriority) {
constexpr uint16_t kTxMtu = bt::l2cap::kMinACLMTU;
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
FakeChannel::WeakPtr fake_channel;
l2cap()->set_channel_callback([&](FakeChannel::WeakPtr chan) { fake_channel = std::move(chan); });
fidlbredr::ConnectionReceiverHandle connect_receiver_handle;
FakeConnectionReceiver connect_receiver(connect_receiver_handle.NewRequest(), dispatcher());
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_receiver(std::move(connect_receiver_handle));
client()->Advertise(std::move(adv_request), NopAdvertiseCallback);
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 0u);
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41, kTxMtu));
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 1u);
ASSERT_TRUE(connect_receiver.channel().has_value());
ASSERT_TRUE(connect_receiver.channel().value().has_ext_direction());
// Taking value() is safe because of the has_ext_direction() check.
fidlbredr::AudioDirectionExtPtr client = connect_receiver.bind_ext_direction().value();
size_t priority_cb_count = 0;
client->SetPriority(fidlbredr::A2dpDirectionPriority::SINK,
[&](fidlbredr::AudioDirectionExt_SetPriority_Result result) {
EXPECT_TRUE(result.is_response());
priority_cb_count++;
});
RunLoopUntilIdle();
EXPECT_EQ(priority_cb_count, 1u);
ASSERT_TRUE(fake_channel.is_alive());
EXPECT_EQ(fake_channel->requested_acl_priority(), AclPriority::kSink);
}
// Verifies that a socket channel relay is correctly set up such that bytes written to the socket
// are sent to the channel.
TEST_F(ProfileServerTestConnectedPeer, ConnectReturnsValidSocket) {
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
// Approve pairing requests.
pairing_delegate->SetConfirmPairingCallback(
[](bt::PeerId, auto confirm_cb) { confirm_cb(true); });
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
bt::l2cap::ChannelParameters expected_params;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41,
expected_params);
std::optional<FakeChannel::WeakPtr> fake_chan;
l2cap()->set_channel_callback(
[&fake_chan](FakeChannel::WeakPtr chan) { fake_chan = std::move(chan); });
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto result_cb = [&channel](fidlbredr::Profile_Connect_Result result) {
EXPECT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(kPsm);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
// Initiates pairing
client()->Connect(peer_id, std::move(conn_params), std::move(result_cb));
RunLoopUntilIdle();
ASSERT_TRUE(channel.has_value());
ASSERT_TRUE(channel->has_socket());
auto& socket = channel->socket();
ASSERT_TRUE(fake_chan.has_value());
FakeChannel::WeakPtr fake_chan_ptr = fake_chan.value();
size_t send_count = 0;
fake_chan_ptr->SetSendCallback([&send_count](auto buffer) { send_count++; }, pw_dispatcher());
const char write_data[2] = "a";
size_t bytes_written = 0;
auto status = socket.write(0, write_data, sizeof(write_data) - 1, &bytes_written);
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1u, bytes_written);
RunLoopUntilIdle();
EXPECT_EQ(1u, send_count);
}
// Verifies that a socket channel relay is correctly set up such that bytes written to the socket
// are sent to the channel.
TEST_F(ProfileServerTestConnectedPeer, ConnectionReceiverReturnsValidSocket) {
std::unique_ptr<bt::gap::FakePairingDelegate> pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
fidlbredr::ConnectionReceiverHandle connect_receiver_handle;
FakeConnectionReceiver connect_receiver(connect_receiver_handle.NewRequest(), dispatcher());
std::optional<FakeChannel::WeakPtr> fake_chan;
l2cap()->set_channel_callback(
[&fake_chan](FakeChannel::WeakPtr chan) { fake_chan = std::move(chan); });
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_receiver(std::move(connect_receiver_handle));
client()->Advertise(std::move(adv_request), NopAdvertiseCallback);
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 0u);
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPsm, 0x40, 0x41));
RunLoopUntilIdle();
ASSERT_EQ(connect_receiver.connected_count(), 1u);
ASSERT_EQ(connect_receiver.peer_id().value().value, peer()->identifier().value());
ASSERT_TRUE(connect_receiver.channel().has_value());
ASSERT_TRUE(connect_receiver.channel().value().has_socket());
// Taking channel is safe because of the previous checks.
fidlbredr::Channel channel = connect_receiver.take_channel();
ASSERT_TRUE(fake_chan.has_value());
FakeChannel::WeakPtr fake_chan_ptr = fake_chan.value();
size_t send_count = 0;
fake_chan_ptr->SetSendCallback([&send_count](auto buffer) { send_count++; }, pw_dispatcher());
const char write_data[2] = "a";
size_t bytes_written = 0;
int status = channel.socket().write(0, write_data, sizeof(write_data) - 1, &bytes_written);
EXPECT_EQ(ZX_OK, status);
EXPECT_EQ(1u, bytes_written);
RunLoopUntilIdle();
EXPECT_EQ(1u, send_count);
}
TEST_F(ProfileServerTest, ConnectScoWithInvalidParameters) {
std::vector<fidlbredr::ScoConnectionParameters> bad_sco_params;
bad_sco_params.emplace_back();
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(true);
request.set_params(std::move(bad_sco_params));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTest, ConnectScoWithMissingPeerId) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTest, ConnectScoWithMissingReceiverDoesNotCrash) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
}
TEST_F(ProfileServerTest, ConnectScoWithEmptyParameters) {
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(true);
request.set_params({});
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTest, ConnectScoInitiatorWithTooManyParameters) {
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(CreateScoConnectionParameters());
sco_params_list.emplace_back(CreateScoConnectionParameters());
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTest, ConnectScoWithUnconnectedPeerReturnsError) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::FAILURE);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTestConnectedPeer, ConnectScoInitiatorSuccess) {
fidlbredr::ScoConnectionParameters sco_params =
CreateScoConnectionParameters(fidlbredr::HfpParameterSet::T1);
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{peer()->identifier().value()});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
ASSERT_TRUE(receiver.connection().is_bound());
ASSERT_TRUE(receiver.parameters().has_value());
ASSERT_TRUE(receiver.parameters()->has_parameter_set());
EXPECT_EQ(receiver.parameters()->parameter_set(), fidlbredr::HfpParameterSet::T1);
ASSERT_TRUE(receiver.parameters()->has_max_tx_data_size());
EXPECT_EQ(receiver.parameters()->max_tx_data_size(), kSynchronousDataPacketLength);
}
TEST_F(ProfileServerTestConnectedPeer, ConnectScoResponderSuccess) {
// Use 2 parameter sets to test that the profile server returns the second set when a SCO
// connection request is received (T2 is ESCO only and D0 is SCO only, so D0 will
// be used to accept the connection).
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(CreateScoConnectionParameters(fidlbredr::HfpParameterSet::T2));
sco_params_list.emplace_back(CreateScoConnectionParameters(fidlbredr::HfpParameterSet::D0));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{peer()->identifier().value()});
request.set_initiator(false);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
// Receive a SCO connection request. The D0 parameters will be used to accept the request.
test_device()->SendConnectionRequest(peer()->address(), pw::bluetooth::emboss::LinkType::SCO);
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
ASSERT_TRUE(receiver.connection().is_bound());
ASSERT_TRUE(receiver.parameters().has_value());
ASSERT_TRUE(receiver.parameters()->has_parameter_set());
EXPECT_EQ(receiver.parameters()->parameter_set(), fidlbredr::HfpParameterSet::D0);
}
TEST_F(ProfileServerTestConnectedPeer, ConnectScoResponderUnconnectedPeerReturnsError) {
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(CreateScoConnectionParameters());
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{1});
request.set_initiator(false);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::FAILURE);
EXPECT_FALSE(receiver.connection().is_bound());
}
TEST_F(ProfileServerTestConnectedPeer, ConnectScoInitiatorAndCloseReceiver) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{peer()->identifier().value()});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
receiver.Close();
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().is_bound());
}
// Verifies that the profile server gracefully ignores connection results after the receiver has
// closed.
TEST_F(ProfileServerTestConnectedPeer, ConnectScoInitiatorAndCloseReceiverBeforeCompleteEvent) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
std::vector<fidlbredr::ScoConnectionParameters> sco_params_list;
sco_params_list.emplace_back(std::move(sco_params));
fidlbredr::ScoConnectionReceiverHandle receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
test_device()->SetDefaultCommandStatus(bt::hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
fidlbredr::ProfileConnectScoRequest request;
request.set_peer_id(fuchsia::bluetooth::PeerId{peer()->identifier().value()});
request.set_initiator(true);
request.set_params(std::move(sco_params_list));
request.set_receiver(std::move(receiver_handle));
client()->ConnectSco(std::move(request));
receiver.Close();
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().is_bound());
test_device()->SendCommandChannelPacket(bt::testing::SynchronousConnectionCompletePacket(
0x00, peer()->address(), bt::hci_spec::LinkType::kSCO,
pw::bluetooth::emboss::StatusCode::CONNECTION_TIMEOUT));
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().is_bound());
}
class ProfileServerTestFakeAdapter : public bt::fidl::testing::FakeAdapterTestFixture {
public:
ProfileServerTestFakeAdapter() = default;
~ProfileServerTestFakeAdapter() override = default;
void SetUp() override {
FakeAdapterTestFixture::SetUp();
fidlbredr::ProfileHandle profile_handle;
client_.Bind(std::move(profile_handle));
server_ =
std::make_unique<ProfileServer>(adapter()->AsWeakPtr(), client_.NewRequest(dispatcher()));
}
void TearDown() override { FakeAdapterTestFixture::TearDown(); }
fidlbredr::ProfilePtr& client() { return client_; }
private:
std::unique_ptr<ProfileServer> server_;
fidlbredr::ProfilePtr client_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ProfileServerTestFakeAdapter);
};
TEST_F(ProfileServerTestFakeAdapter, ConnectChannelParametersContainsFlushTimeout) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const pw::chrono::SystemClock::duration kFlushTimeout(std::chrono::milliseconds(100));
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = std::move(chan); });
// Set L2CAP channel parameters
fidlbredr::ChannelParameters chan_params;
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
chan_params.set_flush_timeout(kFlushTimeout.count());
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(std::move(chan_params));
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
EXPECT_EQ(last_channel->info().flush_timeout, std::optional(kFlushTimeout));
ASSERT_TRUE(response_channel.has_value());
ASSERT_TRUE(response_channel->has_flush_timeout());
ASSERT_EQ(response_channel->flush_timeout(), kFlushTimeout.count());
}
TEST_F(ProfileServerTestFakeAdapter, AdvertiseChannelParametersContainsFlushTimeout) {
const pw::chrono::SystemClock::duration kFlushTimeout(std::chrono::milliseconds(100));
const bt::hci_spec::ConnectionHandle kHandle(1);
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
fidlbredr::ChannelParameters chan_params;
chan_params.set_flush_timeout(kFlushTimeout.count());
fidlbredr::ConnectionReceiverHandle connect_receiver_handle;
FakeConnectionReceiver connect_receiver(connect_receiver_handle.NewRequest(), dispatcher());
fidlbredr::ProfileAdvertiseRequest adv_request;
adv_request.set_services(std::move(services));
adv_request.set_parameters(std::move(chan_params));
adv_request.set_receiver(std::move(connect_receiver_handle));
client()->Advertise(std::move(adv_request), NopAdvertiseCallback);
RunLoopUntilIdle();
ASSERT_EQ(adapter()->fake_bredr()->registered_services().size(), 1u);
auto service_iter = adapter()->fake_bredr()->registered_services().begin();
EXPECT_EQ(service_iter->second.channel_params.flush_timeout, std::optional(kFlushTimeout));
bt::l2cap::ChannelInfo chan_info = bt::l2cap::ChannelInfo::MakeBasicMode(
bt::l2cap::kDefaultMTU, bt::l2cap::kDefaultMTU, bt::l2cap::kAVDTP, kFlushTimeout);
auto channel = std::make_unique<FakeChannel>(bt::l2cap::kFirstDynamicChannelId,
bt::l2cap::kFirstDynamicChannelId, kHandle,
bt::LinkType::kACL, chan_info);
service_iter->second.connect_callback(channel->GetWeakPtr(), MakeL2capProtocolListElement());
RunLoopUntilIdle();
ASSERT_TRUE(connect_receiver.channel().has_value());
fidlbredr::Channel fidl_channel = connect_receiver.take_channel();
ASSERT_TRUE(fidl_channel.has_flush_timeout());
EXPECT_EQ(fidl_channel.flush_timeout(), kFlushTimeout.count());
channel->Close();
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestFakeAdapter, AdvertiseWithMissingFields) {
auto adv_ok_cb = [](fidlbredr::Profile_Advertise_Result result) {
EXPECT_TRUE(result.is_response());
};
auto adv_err_cb = [](fidlbredr::Profile_Advertise_Result result) {
ASSERT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
fidlbredr::ProfileAdvertiseRequest adv_request_missing_receiver;
std::vector<fidlbredr::ServiceDefinition> services1;
services1.emplace_back(MakeFIDLServiceDefinition());
adv_request_missing_receiver.set_services(std::move(services1));
adv_request_missing_receiver.set_parameters(::fuchsia::bluetooth::bredr::ChannelParameters());
client()->Advertise(std::move(adv_request_missing_receiver), adv_err_cb);
RunLoopUntilIdle();
ASSERT_EQ(adapter()->fake_bredr()->registered_services().size(), 0u);
fidlbredr::ConnectionReceiverHandle connect_receiver_handle1;
FakeConnectionReceiver connect_receiver1(connect_receiver_handle1.NewRequest(), dispatcher());
fidlbredr::ProfileAdvertiseRequest adv_request_missing_services;
adv_request_missing_services.set_receiver(std::move(connect_receiver_handle1));
adv_request_missing_services.set_parameters(::fuchsia::bluetooth::bredr::ChannelParameters());
client()->Advertise(std::move(adv_request_missing_services), adv_err_cb);
RunLoopUntilIdle();
ASSERT_EQ(adapter()->fake_bredr()->registered_services().size(), 0u);
// Missing parameters is allowed.
fidlbredr::ProfileAdvertiseRequest adv_request_missing_parameters;
std::vector<fidlbredr::ServiceDefinition> services2;
services2.emplace_back(MakeFIDLServiceDefinition());
adv_request_missing_parameters.set_services(std::move(services2));
fidlbredr::ConnectionReceiverHandle connect_receiver_handle2;
FakeConnectionReceiver connect_receiver2(connect_receiver_handle2.NewRequest(), dispatcher());
adv_request_missing_parameters.set_receiver(std::move(connect_receiver_handle2));
client()->Advertise(std::move(adv_request_missing_parameters), adv_ok_cb);
RunLoopUntilIdle();
ASSERT_EQ(adapter()->fake_bredr()->registered_services().size(), 1u);
}
TEST_F(ProfileServerTestFakeAdapter, L2capParametersExtRequestParametersSucceeds) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const zx::duration kFlushTimeout(zx::msec(100));
const uint16_t kMaxRxSduSize(200);
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = chan; });
// Set L2CAP channel parameters
fidlbredr::ChannelParameters chan_params;
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
chan_params.set_channel_mode(fidlbredr::ChannelMode::BASIC);
chan_params.set_max_rx_sdu_size(kMaxRxSduSize);
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(std::move(chan_params));
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
EXPECT_FALSE(last_channel->info().flush_timeout.has_value());
ASSERT_TRUE(response_channel.has_value());
ASSERT_FALSE(response_channel->has_flush_timeout());
ASSERT_TRUE(response_channel->has_ext_l2cap());
fidlbredr::ChannelParameters request_chan_params;
request_chan_params.set_flush_timeout(kFlushTimeout.get());
std::optional<fidlbredr::ChannelParameters> result_chan_params;
fidlbredr::L2capParametersExtPtr l2cap_client = response_channel->mutable_ext_l2cap()->Bind();
l2cap_client->RequestParameters(
std::move(request_chan_params),
[&](fidlbredr::L2capParametersExt_RequestParameters_Result new_params) {
result_chan_params = new_params.response().ResultValue_();
});
RunLoopUntilIdle();
ASSERT_TRUE(result_chan_params.has_value());
ASSERT_TRUE(result_chan_params->has_channel_mode());
ASSERT_TRUE(result_chan_params->has_max_rx_sdu_size());
// TODO(https://fxbug.dev/42152567): set current security requirements in returned channel
// parameters
ASSERT_FALSE(result_chan_params->has_security_requirements());
ASSERT_TRUE(result_chan_params->has_flush_timeout());
EXPECT_EQ(result_chan_params->channel_mode(), fidlbredr::ChannelMode::BASIC);
EXPECT_EQ(result_chan_params->max_rx_sdu_size(), kMaxRxSduSize);
EXPECT_EQ(result_chan_params->flush_timeout(), kFlushTimeout.get());
l2cap_client.Unbind();
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestFakeAdapter, L2capParametersExtRequestParametersFails) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const zx::duration kFlushTimeout(zx::msec(100));
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = chan; });
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
EXPECT_FALSE(last_channel->info().flush_timeout.has_value());
ASSERT_TRUE(response_channel.has_value());
ASSERT_FALSE(response_channel->has_flush_timeout());
ASSERT_TRUE(response_channel->has_ext_l2cap());
last_channel->set_flush_timeout_succeeds(false);
fidlbredr::ChannelParameters request_chan_params;
request_chan_params.set_flush_timeout(kFlushTimeout.get());
std::optional<fidlbredr::ChannelParameters> result_chan_params;
fidlbredr::L2capParametersExtPtr l2cap_client = response_channel->mutable_ext_l2cap()->Bind();
l2cap_client->RequestParameters(
std::move(request_chan_params),
[&](fidlbredr::L2capParametersExt_RequestParameters_Result new_params) {
result_chan_params = new_params.response().ResultValue_();
});
RunLoopUntilIdle();
ASSERT_TRUE(result_chan_params.has_value());
EXPECT_FALSE(result_chan_params->has_flush_timeout());
l2cap_client.Unbind();
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestFakeAdapter, L2capParametersExtRequestParametersClosedOnChannelClosed) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = chan; });
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
ASSERT_TRUE(response_channel.has_value());
fidlbredr::L2capParametersExtPtr l2cap_client = response_channel->mutable_ext_l2cap()->Bind();
bool l2cap_client_closed = false;
l2cap_client.set_error_handler([&](zx_status_t /*status*/) { l2cap_client_closed = true; });
// Closing the channel should close l2cap_client (after running the loop).
last_channel->Close();
// Destroy the channel (like the real LogicalLink would) to verify that ProfileServer doesn't try
// to use channel pointers.
EXPECT_TRUE(adapter()->fake_bredr()->DestroyChannel(last_channel->id()));
// Any request for the closed channel should be ignored.
fidlbredr::ChannelParameters request_chan_params;
std::optional<fidlbredr::ChannelParameters> result_chan_params;
l2cap_client->RequestParameters(
std::move(request_chan_params),
[&](fidlbredr::L2capParametersExt_RequestParameters_Result new_params) {
result_chan_params = new_params.response().ResultValue_();
});
RunLoopUntilIdle();
EXPECT_TRUE(l2cap_client_closed);
EXPECT_FALSE(result_chan_params.has_value());
l2cap_client.Unbind();
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestFakeAdapter, AudioDirectionExtRequestParametersClosedOnChannelClosed) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = chan; });
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
ASSERT_TRUE(response_channel.has_value());
fidlbredr::AudioDirectionExtPtr audio_client = response_channel->mutable_ext_direction()->Bind();
bool audio_client_closed = false;
audio_client.set_error_handler([&](zx_status_t /*status*/) { audio_client_closed = true; });
// Closing the channel should close audio_client (after running the loop).
last_channel->Close();
// Destroy the channel (like the real LogicalLink would) to verify that ProfileServer doesn't try
// to use channel pointers.
EXPECT_TRUE(adapter()->fake_bredr()->DestroyChannel(last_channel->id()));
// Any request for the closed channel should be ignored.
size_t priority_cb_count = 0;
audio_client->SetPriority(
fidlbredr::A2dpDirectionPriority::NORMAL,
[&](fidlbredr::AudioDirectionExt_SetPriority_Result result) { priority_cb_count++; });
RunLoopUntilIdle();
EXPECT_TRUE(audio_client_closed);
EXPECT_EQ(priority_cb_count, 0u);
audio_client.Unbind();
RunLoopUntilIdle();
}
TEST_F(ProfileServerTestFakeAdapter, AudioOffloadExtRequestParametersClosedOnChannelClosed) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
FakeChannel::WeakPtr last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { last_channel = std::move(chan); });
// Support Android Vendor Extensions to enable Audio Offload Extension
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().aac().Write(true);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(kFidlPeerId, std::move(conn_params),
[&](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(last_channel.is_alive());
ASSERT_TRUE(response_channel.has_value());
ASSERT_TRUE(response_channel->has_ext_audio_offload());
fidlbredr::AudioOffloadExtPtr audio_client =
response_channel->mutable_ext_audio_offload()->Bind();
bool audio_client_closed = false;
audio_client.set_error_handler([&](zx_status_t /*status*/) { audio_client_closed = true; });
// Closing the channel should close |audio_client| (after running the loop).
last_channel->Close();
// Destroy the channel (like the real LogicalLink would) to verify that ProfileServer doesn't try
// to use channel pointers.
EXPECT_TRUE(adapter()->fake_bredr()->DestroyChannel(last_channel->id()));
// Any request for the closed channel should be ignored.
std::optional<fidlbredr::AudioOffloadExt_GetSupportedFeatures_Response> result_features;
audio_client->GetSupportedFeatures(
[&result_features](fidlbredr::AudioOffloadExt_GetSupportedFeatures_Result features) {
result_features = std::move(features.response());
});
RunLoopUntilIdle();
EXPECT_TRUE(audio_client_closed);
EXPECT_FALSE(result_features.has_value());
audio_client.Unbind();
RunLoopUntilIdle();
}
class ProfileServerInvalidSamplingFrequencyTest
: public ProfileServerTestFakeAdapter,
public ::testing::WithParamInterface<fidlbredr::AudioSamplingFrequency> {};
const std::vector<fidlbredr::AudioSamplingFrequency> kInvalidSamplingFrequencies = {
fidlbredr::AudioSamplingFrequency::HZ_88200,
fidlbredr::AudioSamplingFrequency::HZ_96000,
};
INSTANTIATE_TEST_SUITE_P(ProfileServerTestFakeAdapter, ProfileServerInvalidSamplingFrequencyTest,
::testing::ValuesIn(kInvalidSamplingFrequencies));
TEST_P(ProfileServerInvalidSamplingFrequencyTest, SbcInvalidSamplingFrequency) {
// enable a2dp offloading
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
// enable offloaded sbc encoding
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().sbc().Write(true);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
// set up a fake channel and connection
FakeChannel::WeakPtr fake_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](auto chan) { fake_channel = std::move(chan); });
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
fidlbredr::ChannelParameters chan_params;
l2cap_params.set_parameters(std::move(chan_params));
fidlbredr::ConnectParameters conn_params;
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
ASSERT_TRUE(response_channel->has_ext_audio_offload());
// set up the bad configuration
std::unique_ptr<fidlbredr::AudioOffloadFeatures> codec = fidlbredr::AudioOffloadFeatures::New();
std::unique_ptr<fidlbredr::AudioSbcSupport> codec_value = fidlbredr::AudioSbcSupport::New();
codec->set_sbc(std::move(*codec_value));
std::unique_ptr<fidlbredr::AudioEncoderSettings> encoder_settings =
std::make_unique<fidlbredr::AudioEncoderSettings>();
std::unique_ptr<fuchsia::media::SbcEncoderSettings> encoder_settings_value =
fuchsia::media::SbcEncoderSettings::New();
encoder_settings->set_sbc(*encoder_settings_value);
std::unique_ptr<fidlbredr::AudioOffloadConfiguration> config =
std::make_unique<fidlbredr::AudioOffloadConfiguration>();
config->set_codec(std::move(*codec));
config->set_max_latency(10);
config->set_scms_t_enable(true);
config->set_sampling_frequency(GetParam());
config->set_bits_per_sample(fidlbredr::AudioBitsPerSample::BPS_16);
config->set_channel_mode(fidlbredr::AudioChannelMode::MONO);
config->set_encoded_bit_rate(10);
config->set_encoder_settings(std::move(*encoder_settings));
// attempt to start the audio offload
fidl::InterfaceHandle<fidlbredr::AudioOffloadController> controller_handle;
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller_request =
controller_handle.NewRequest();
fidl::InterfacePtr<fidlbredr::AudioOffloadExt> audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
audio_offload_ext_client->StartAudioOffload(std::move(*config), std::move(controller_request));
fidl::InterfacePtr<fidlbredr::AudioOffloadController> audio_offload_controller_client;
audio_offload_controller_client.Bind(std::move(controller_handle));
std::optional<zx_status_t> audio_offload_controller_epitaph;
audio_offload_controller_client.set_error_handler(
[&](zx_status_t status) { audio_offload_controller_epitaph = status; });
RunLoopUntilIdle();
// Verify that |audio_offload_controller_client| was closed with |ZX_ERR_INTERNAL| epitaph
ASSERT_TRUE(audio_offload_controller_epitaph.has_value());
EXPECT_EQ(audio_offload_controller_epitaph.value(), ZX_ERR_NOT_SUPPORTED);
}
class AndroidSupportedFeaturesTest
: public ProfileServerTestFakeAdapter,
public ::testing::WithParamInterface<
std::pair<bool /*android_vendor_support*/, uint32_t /*android_vendor_capabilities*/>> {};
TEST_P(AndroidSupportedFeaturesTest, AudioOffloadExtGetSupportedFeatures) {
const bool android_vendor_ext_support = GetParam().first;
const uint32_t a2dp_offload_capabilities = GetParam().second;
if (android_vendor_ext_support) {
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().BackingStorage().UncheckedWriteUInt(
a2dp_offload_capabilities);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
}
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
if (!android_vendor_ext_support || !a2dp_offload_capabilities) {
EXPECT_FALSE(response_channel->has_ext_audio_offload());
return;
}
ASSERT_TRUE(response_channel->has_ext_audio_offload());
std::optional<fidlbredr::AudioOffloadExt_GetSupportedFeatures_Response> result_features;
fidlbredr::AudioOffloadExtPtr audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
audio_offload_ext_client->GetSupportedFeatures(
[&result_features](fidlbredr::AudioOffloadExt_GetSupportedFeatures_Result features) {
result_features = std::move(features.response());
});
RunLoopUntilIdle();
EXPECT_TRUE(result_features->has_audio_offload_features());
const std::vector<fidlbredr::AudioOffloadFeatures>& audio_offload_features =
result_features->audio_offload_features();
const uint32_t audio_offload_features_size =
std::bitset<std::numeric_limits<uint32_t>::digits>(a2dp_offload_capabilities).count();
EXPECT_EQ(audio_offload_features_size, audio_offload_features.size());
uint32_t capabilities = 0;
const uint32_t sbc_capability = static_cast<uint32_t>(android_hci::A2dpCodecType::SBC);
const uint32_t aac_capability = static_cast<uint32_t>(android_hci::A2dpCodecType::AAC);
for (const fidlbredr::AudioOffloadFeatures& feature : audio_offload_features) {
if (feature.is_sbc()) {
capabilities |= sbc_capability;
}
if (feature.is_aac()) {
capabilities |= aac_capability;
}
}
EXPECT_EQ(capabilities, a2dp_offload_capabilities);
}
TEST_P(AndroidSupportedFeaturesTest, AudioOffloadExtStartAudioOffloadSuccess) {
const bool android_vendor_ext_support = GetParam().first;
const uint32_t a2dp_offload_capabilities = GetParam().second;
if (android_vendor_ext_support) {
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().BackingStorage().UncheckedWriteUInt(
a2dp_offload_capabilities);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
}
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
if (!android_vendor_ext_support || !a2dp_offload_capabilities) {
EXPECT_FALSE(response_channel->has_ext_audio_offload());
return;
}
ASSERT_TRUE(response_channel->has_ext_audio_offload());
// Set Audio Offload Configuration Values
std::unique_ptr<fidlbredr::AudioOffloadFeatures> codec = fidlbredr::AudioOffloadFeatures::New();
std::unique_ptr<fidlbredr::AudioSbcSupport> codec_value = fidlbredr::AudioSbcSupport::New();
codec->set_sbc(std::move(*codec_value));
std::unique_ptr<fidlbredr::AudioEncoderSettings> encoder_settings =
std::make_unique<fidlbredr::AudioEncoderSettings>();
std::unique_ptr<fuchsia::media::SbcEncoderSettings> encoder_settings_value =
fuchsia::media::SbcEncoderSettings::New();
encoder_settings->set_sbc(*encoder_settings_value);
std::unique_ptr<fidlbredr::AudioOffloadConfiguration> config =
std::make_unique<fidlbredr::AudioOffloadConfiguration>();
config->set_codec(std::move(*codec));
config->set_max_latency(10);
config->set_scms_t_enable(true);
config->set_sampling_frequency(fidlbredr::AudioSamplingFrequency::HZ_44100);
config->set_bits_per_sample(fidlbredr::AudioBitsPerSample::BPS_16);
config->set_channel_mode(fidlbredr::AudioChannelMode::MONO);
config->set_encoded_bit_rate(10);
config->set_encoder_settings(std::move(*encoder_settings));
fidlbredr::AudioOffloadExtPtr audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
fidlbredr::AudioOffloadControllerHandle controller_handle;
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller_request =
controller_handle.NewRequest();
audio_offload_ext_client->StartAudioOffload(std::move(*config), std::move(controller_request));
fidlbredr::AudioOffloadControllerPtr audio_offload_controller_client;
audio_offload_controller_client.Bind(std::move(controller_handle));
std::optional<zx_status_t> audio_offload_controller_epitaph;
audio_offload_controller_client.set_error_handler(
[&](zx_status_t status) { audio_offload_controller_epitaph = status; });
size_t on_started_count = 0;
audio_offload_controller_client.events().OnStarted = [&]() { on_started_count++; };
RunLoopUntilIdle();
// Verify that OnStarted event was sent successfully
EXPECT_EQ(on_started_count, 1u);
// Verify that |audio_offload_controller_client| was not closed with an epitaph
ASSERT_FALSE(audio_offload_controller_epitaph.has_value());
}
TEST_P(AndroidSupportedFeaturesTest, AudioOffloadExtStartAudioOffloadFail) {
FakeChannel::WeakPtr fake_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { fake_channel = std::move(chan); });
const bool android_vendor_ext_support = GetParam().first;
const uint32_t a2dp_offload_capabilities = GetParam().second;
if (android_vendor_ext_support) {
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().BackingStorage().UncheckedWriteUInt(
a2dp_offload_capabilities);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
}
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
if (!android_vendor_ext_support || !a2dp_offload_capabilities) {
EXPECT_FALSE(response_channel->has_ext_audio_offload());
return;
}
ASSERT_TRUE(response_channel->has_ext_audio_offload());
// Make A2DP offloading fail, resulting in |ZX_ERR_INTERNAL| epitaph
ASSERT_TRUE(fake_channel.is_alive());
fake_channel->set_a2dp_offload_fails(bt::HostError::kFailed);
// Set Audio Offload Configuration Values
std::unique_ptr<fidlbredr::AudioOffloadFeatures> codec = fidlbredr::AudioOffloadFeatures::New();
std::unique_ptr<fidlbredr::AudioSbcSupport> codec_value = fidlbredr::AudioSbcSupport::New();
codec->set_sbc(std::move(*codec_value));
std::unique_ptr<fidlbredr::AudioEncoderSettings> encoder_settings =
std::make_unique<fidlbredr::AudioEncoderSettings>();
std::unique_ptr<fuchsia::media::SbcEncoderSettings> encoder_settings_value =
fuchsia::media::SbcEncoderSettings::New();
encoder_settings->set_sbc(*encoder_settings_value);
std::unique_ptr<fidlbredr::AudioOffloadConfiguration> config =
std::make_unique<fidlbredr::AudioOffloadConfiguration>();
config->set_codec(std::move(*codec));
config->set_max_latency(10);
config->set_scms_t_enable(true);
config->set_sampling_frequency(fidlbredr::AudioSamplingFrequency::HZ_44100);
config->set_bits_per_sample(fidlbredr::AudioBitsPerSample::BPS_16);
config->set_channel_mode(fidlbredr::AudioChannelMode::MONO);
config->set_encoded_bit_rate(10);
config->set_encoder_settings(std::move(*encoder_settings));
fidlbredr::AudioOffloadExtPtr audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
fidlbredr::AudioOffloadControllerHandle controller_handle;
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller_request =
controller_handle.NewRequest();
audio_offload_ext_client->StartAudioOffload(std::move(*config), std::move(controller_request));
fidlbredr::AudioOffloadControllerPtr audio_offload_controller_client;
audio_offload_controller_client.Bind(std::move(controller_handle));
std::optional<zx_status_t> audio_offload_controller_epitaph;
audio_offload_controller_client.set_error_handler(
[&](zx_status_t status) { audio_offload_controller_epitaph = status; });
size_t cb_count = 0;
audio_offload_controller_client.events().OnStarted = [&]() { cb_count++; };
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 0u);
// Verify that |audio_offload_controller_client| was closed with |ZX_ERR_INTERNAL| epitaph
ASSERT_TRUE(audio_offload_controller_epitaph.has_value());
EXPECT_EQ(audio_offload_controller_epitaph.value(), ZX_ERR_INTERNAL);
}
TEST_P(AndroidSupportedFeaturesTest, AudioOffloadExtStartAudioOffloadInProgress) {
FakeChannel::WeakPtr fake_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { fake_channel = std::move(chan); });
const bool android_vendor_ext_support = GetParam().first;
const uint32_t a2dp_offload_capabilities = GetParam().second;
if (android_vendor_ext_support) {
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().BackingStorage().UncheckedWriteUInt(
a2dp_offload_capabilities);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
}
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
if (!android_vendor_ext_support || !a2dp_offload_capabilities) {
EXPECT_FALSE(response_channel->has_ext_audio_offload());
return;
}
ASSERT_TRUE(response_channel->has_ext_audio_offload());
// Make A2DP offloading fail, resulting in |ZX_ERR_ALREADY_BOUND| epitaph
ASSERT_TRUE(fake_channel.is_alive());
fake_channel->set_a2dp_offload_fails(bt::HostError::kInProgress);
// Set Audio Offload Configuration Values
std::unique_ptr<fidlbredr::AudioOffloadFeatures> codec = fidlbredr::AudioOffloadFeatures::New();
std::unique_ptr<fidlbredr::AudioSbcSupport> codec_value = fidlbredr::AudioSbcSupport::New();
codec->set_sbc(std::move(*codec_value));
std::unique_ptr<fidlbredr::AudioEncoderSettings> encoder_settings =
std::make_unique<fidlbredr::AudioEncoderSettings>();
std::unique_ptr<fuchsia::media::SbcEncoderSettings> encoder_settings_value =
fuchsia::media::SbcEncoderSettings::New();
encoder_settings->set_sbc(*encoder_settings_value);
std::unique_ptr<fidlbredr::AudioOffloadConfiguration> config =
std::make_unique<fidlbredr::AudioOffloadConfiguration>();
config->set_codec(std::move(*codec));
config->set_max_latency(10);
config->set_scms_t_enable(true);
config->set_sampling_frequency(fidlbredr::AudioSamplingFrequency::HZ_44100);
config->set_bits_per_sample(fidlbredr::AudioBitsPerSample::BPS_16);
config->set_channel_mode(fidlbredr::AudioChannelMode::MONO);
config->set_encoded_bit_rate(10);
config->set_encoder_settings(std::move(*encoder_settings));
fidlbredr::AudioOffloadExtPtr audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
fidlbredr::AudioOffloadControllerHandle controller_handle;
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller_request =
controller_handle.NewRequest();
audio_offload_ext_client->StartAudioOffload(std::move(*config), std::move(controller_request));
fidlbredr::AudioOffloadControllerPtr audio_offload_controller_client;
audio_offload_controller_client.Bind(std::move(controller_handle));
std::optional<zx_status_t> audio_offload_controller_epitaph;
audio_offload_controller_client.set_error_handler(
[&](zx_status_t status) { audio_offload_controller_epitaph = status; });
size_t cb_count = 0;
audio_offload_controller_client.events().OnStarted = [&]() { cb_count++; };
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 0u);
// Verify that |audio_offload_controller_client| was closed with |ZX_ERR_ALREADY_BOUND| epitaph
ASSERT_TRUE(audio_offload_controller_epitaph.has_value());
EXPECT_EQ(audio_offload_controller_epitaph.value(), ZX_ERR_ALREADY_BOUND);
}
TEST_P(AndroidSupportedFeaturesTest, AudioOffloadExtStartAudioOffloadControllerError) {
FakeChannel::WeakPtr fake_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback(
[&](FakeChannel::WeakPtr chan) { fake_channel = std::move(chan); });
const bool android_vendor_ext_support = GetParam().first;
const uint32_t a2dp_offload_capabilities = GetParam().second;
if (android_vendor_ext_support) {
adapter()->mutable_state().controller_features |= FeaturesBits::kAndroidVendorExtensions;
bt::StaticPacket<
pw::bluetooth::vendor::android_hci::LEGetVendorCapabilitiesCommandCompleteEventWriter>
params;
params.SetToZeros();
params.view().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS);
params.view().version_supported().major_number().Write(0);
params.view().version_supported().minor_number().Write(98);
params.view().a2dp_source_offload_capability_mask().BackingStorage().UncheckedWriteUInt(
a2dp_offload_capabilities);
adapter()->mutable_state().android_vendor_capabilities =
bt::gap::AndroidVendorCapabilities::New(params.view());
}
const bt::PeerId peer_id(1);
const fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
// Set L2CAP channel parameters
fidlbredr::L2capParameters l2cap_params;
fidlbredr::ConnectParameters conn_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
l2cap_params.set_parameters(fidlbredr::ChannelParameters());
conn_params.set_l2cap(std::move(l2cap_params));
std::optional<fidlbredr::Channel> response_channel;
client()->Connect(fidl_peer_id, std::move(conn_params),
[&response_channel](fidlbredr::Profile_Connect_Result result) {
ASSERT_TRUE(result.is_response());
response_channel = std::move(result.response().channel);
});
RunLoopUntilIdle();
ASSERT_TRUE(response_channel.has_value());
if (!android_vendor_ext_support || !a2dp_offload_capabilities) {
EXPECT_FALSE(response_channel->has_ext_audio_offload());
return;
}
ASSERT_TRUE(response_channel->has_ext_audio_offload());
// Set Audio Offload Configuration Values
std::unique_ptr<fidlbredr::AudioOffloadFeatures> codec = fidlbredr::AudioOffloadFeatures::New();
std::unique_ptr<fidlbredr::AudioSbcSupport> codec_value = fidlbredr::AudioSbcSupport::New();
codec->set_sbc(std::move(*codec_value));
std::unique_ptr<fidlbredr::AudioEncoderSettings> encoder_settings =
std::make_unique<fidlbredr::AudioEncoderSettings>();
std::unique_ptr<fuchsia::media::SbcEncoderSettings> encoder_settings_value =
fuchsia::media::SbcEncoderSettings::New();
encoder_settings->set_sbc(*encoder_settings_value);
std::unique_ptr<fidlbredr::AudioOffloadConfiguration> config =
std::make_unique<fidlbredr::AudioOffloadConfiguration>();
config->set_codec(std::move(*codec));
config->set_max_latency(10);
config->set_scms_t_enable(true);
config->set_sampling_frequency(fidlbredr::AudioSamplingFrequency::HZ_44100);
config->set_bits_per_sample(fidlbredr::AudioBitsPerSample::BPS_16);
config->set_channel_mode(fidlbredr::AudioChannelMode::MONO);
config->set_encoded_bit_rate(10);
config->set_encoder_settings(std::move(*encoder_settings));
fidlbredr::AudioOffloadExtPtr audio_offload_ext_client =
response_channel->mutable_ext_audio_offload()->Bind();
fidlbredr::AudioOffloadControllerHandle controller_handle;
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller_request =
controller_handle.NewRequest();
audio_offload_ext_client->StartAudioOffload(std::move(*config), std::move(controller_request));
fidlbredr::AudioOffloadControllerPtr audio_offload_controller_client;
audio_offload_controller_client.Bind(std::move(controller_handle));
std::optional<zx_status_t> audio_offload_controller_epitaph;
audio_offload_controller_client.set_error_handler(
[&](zx_status_t status) { audio_offload_controller_epitaph = status; });
size_t cb_count = 0;
audio_offload_controller_client.events().OnStarted = [&]() { cb_count++; };
// Close client end of protocol to trigger audio offload error handler
audio_offload_controller_client.Unbind();
RunLoopUntilIdle();
EXPECT_EQ(cb_count, 0u);
ASSERT_FALSE(audio_offload_controller_epitaph.has_value());
}
const std::vector<std::pair<bool, uint32_t>> kVendorCapabilitiesParams = {
{{true, static_cast<uint32_t>(android_hci::A2dpCodecType::SBC)},
{true, static_cast<uint32_t>(android_hci::A2dpCodecType::AAC)},
{true, static_cast<uint32_t>(android_hci::A2dpCodecType::SBC) |
static_cast<uint32_t>(android_hci::A2dpCodecType::AAC)},
{true, 0},
{false, 0}}};
INSTANTIATE_TEST_SUITE_P(ProfileServerTestFakeAdapter, AndroidSupportedFeaturesTest,
::testing::ValuesIn(kVendorCapabilitiesParams));
TEST_F(ProfileServerTestFakeAdapter, ServiceFoundRelayedToFidlClient) {
fidlbredr::SearchResultsHandle search_results_handle;
FakeSearchResults search_results(search_results_handle.NewRequest(), dispatcher());
fidlbredr::ServiceClassProfileIdentifier search_uuid =
fidlbredr::ServiceClassProfileIdentifier::AUDIO_SINK;
EXPECT_EQ(adapter()->fake_bredr()->registered_searches().size(), 0u);
EXPECT_EQ(search_results.service_found_count(), 0u);
// FIDL client registers a service search.
fidlbredr::ProfileSearchRequest request;
request.set_service_uuid(search_uuid);
request.set_attr_ids({});
request.set_results(std::move(search_results_handle));
client()->Search(std::move(request));
RunLoopUntilIdle();
EXPECT_EQ(adapter()->fake_bredr()->registered_searches().size(), 1u);
// Trigger a match on the service search with some data. Should be received by the FIDL
// client.
bt::PeerId peer_id = bt::PeerId{10};
bt::UUID uuid(static_cast<uint32_t>(search_uuid));
bt::sdp::AttributeId attr_id = 50; // Random Attribute ID
bt::sdp::DataElement elem = bt::sdp::DataElement();
elem.SetUrl("https://foobar.dev"); // Random URL
auto attributes = std::map<bt::sdp::AttributeId, bt::sdp::DataElement>();
attributes.emplace(attr_id, std::move(elem));
adapter()->fake_bredr()->TriggerServiceFound(peer_id, uuid, std::move(attributes));
RunLoopUntilIdle();
EXPECT_EQ(search_results.service_found_count(), 1u);
EXPECT_EQ(search_results.peer_id().value().value, peer_id.value());
EXPECT_EQ(search_results.attributes().value().size(), 1u);
EXPECT_EQ(search_results.attributes().value()[0].id, attr_id);
EXPECT_EQ(search_results.attributes().value()[0].element.url(),
std::string("https://foobar.dev"));
}
TEST_F(ProfileServerTestFakeAdapter, SearchWithMissingServiceUuidFails) {
fidlbredr::SearchResultsHandle search_results_handle;
FakeSearchResults search_results(search_results_handle.NewRequest(), dispatcher());
// service_uuid is not set
fidlbredr::ProfileSearchRequest request;
request.set_results(std::move(search_results_handle));
client()->Search(std::move(request));
RunLoopUntilIdle();
EXPECT_EQ(adapter()->fake_bredr()->registered_searches().size(), 0u);
EXPECT_TRUE(search_results.closed());
}
TEST_F(ProfileServerTestFakeAdapter, SearchWithMissingResultsClientFails) {
// results is not set
fidlbredr::ProfileSearchRequest request;
request.set_service_uuid(fidlbredr::ServiceClassProfileIdentifier::AUDIO_SINK);
client()->Search(std::move(request));
RunLoopUntilIdle();
EXPECT_EQ(adapter()->fake_bredr()->registered_searches().size(), 0u);
}
TEST_F(ProfileServerTestFakeAdapter, SearchWithMissingAttrIdsSucceeds) {
fidlbredr::SearchResultsHandle search_results_handle;
FakeSearchResults search_results(search_results_handle.NewRequest(), dispatcher());
fidlbredr::ProfileSearchRequest request;
request.set_service_uuid(fidlbredr::ServiceClassProfileIdentifier::AUDIO_SINK);
request.set_results(std::move(search_results_handle));
client()->Search(std::move(request));
RunLoopUntilIdle();
EXPECT_EQ(adapter()->fake_bredr()->registered_searches().size(), 1u);
}
TEST_F(ProfileServerTestScoConnected, ScoConnectionRead2Packets) {
// Queue a read request before the packet is received.
std::optional<fidlbredr::RxPacketStatus> packet_status;
std::optional<std::vector<uint8_t>> packet;
sco_connection()->Read([&](fidlbredr::RxPacketStatus status, std::vector<uint8_t> cb_packet) {
packet_status = status;
packet = std::move(cb_packet);
});
RunLoopUntilIdle();
EXPECT_FALSE(packet_status);
EXPECT_FALSE(packet);
bt::StaticByteBuffer packet_buffer_0(
bt::LowerBits(sco_handle()),
bt::UpperBits(sco_handle()) | 0x30, // handle + packet status flag: kDataPartiallyLost
0x01, // payload length
0x00 // payload
);
bt::BufferView packet_buffer_0_payload =
packet_buffer_0.view(sizeof(bt::hci_spec::SynchronousDataHeader));
test_device()->SendScoDataChannelPacket(packet_buffer_0);
RunLoopUntilIdle();
ASSERT_TRUE(packet_status);
EXPECT_EQ(packet_status.value(), fidlbredr::RxPacketStatus::DATA_PARTIALLY_LOST);
ASSERT_TRUE(packet);
EXPECT_THAT(packet.value(), ::testing::ElementsAreArray(packet_buffer_0_payload));
packet_status.reset();
packet.reset();
// Receive a second packet. This time, receive the packet before Read() is called.
bt::StaticByteBuffer packet_buffer_1(
bt::LowerBits(sco_handle()),
bt::UpperBits(sco_handle()), // handle + packet status flag: kCorrectlyReceived
0x01, // payload length
0x01 // payload
);
bt::BufferView packet_buffer_1_payload =
packet_buffer_1.view(sizeof(bt::hci_spec::SynchronousDataHeader));
test_device()->SendScoDataChannelPacket(packet_buffer_1);
RunLoopUntilIdle();
sco_connection()->Read([&](fidlbredr::RxPacketStatus status, std::vector<uint8_t> cb_packet) {
packet_status = status;
packet = std::move(cb_packet);
});
RunLoopUntilIdle();
ASSERT_TRUE(packet_status);
EXPECT_EQ(packet_status.value(), fidlbredr::RxPacketStatus::CORRECTLY_RECEIVED_DATA);
ASSERT_TRUE(packet);
EXPECT_THAT(packet.value(), ::testing::ElementsAreArray(packet_buffer_1_payload));
}
TEST_F(ProfileServerTestScoConnected, ScoConnectionReadWhileReadPendingClosesConnection) {
std::optional<fidlbredr::RxPacketStatus> packet_status_0;
std::optional<std::vector<uint8_t>> packet_0;
sco_connection()->Read([&](fidlbredr::RxPacketStatus status, std::vector<uint8_t> cb_packet) {
packet_status_0 = status;
packet_0 = std::move(cb_packet);
});
RunLoopUntilIdle();
EXPECT_FALSE(packet_status_0);
EXPECT_FALSE(packet_0);
std::optional<fidlbredr::RxPacketStatus> packet_status_1;
std::optional<std::vector<uint8_t>> packet_1;
sco_connection()->Read([&](fidlbredr::RxPacketStatus status, std::vector<uint8_t> cb_packet) {
packet_status_1 = status;
packet_1 = std::move(cb_packet);
});
RunLoopUntilIdle();
EXPECT_FALSE(packet_status_0);
EXPECT_FALSE(packet_0);
EXPECT_FALSE(packet_status_1);
EXPECT_FALSE(packet_1);
EXPECT_FALSE(sco_connection());
ASSERT_TRUE(sco_conn_error());
EXPECT_EQ(sco_conn_error().value(), ZX_ERR_BAD_STATE);
}
TEST_F(ProfileServerTestOffloadedScoConnected, ScoConnectionReadFails) {
std::optional<fidlbredr::RxPacketStatus> packet_status;
std::optional<std::vector<uint8_t>> packet;
sco_connection()->Read([&](fidlbredr::RxPacketStatus status, std::vector<uint8_t> cb_packet) {
packet_status = status;
packet = std::move(cb_packet);
});
RunLoopUntilIdle();
EXPECT_FALSE(packet_status);
EXPECT_FALSE(packet);
EXPECT_FALSE(sco_connection());
ASSERT_TRUE(sco_conn_error());
EXPECT_EQ(sco_conn_error().value(), ZX_ERR_IO_NOT_PRESENT);
}
TEST_F(ProfileServerTestScoConnected, ScoConnectionWriteTwice) {
bt::StaticByteBuffer payload_0(0x00);
bt::DynamicByteBuffer packet_buffer_0 = bt::testing::ScoDataPacket(
sco_handle(), bt::hci_spec::SynchronousDataPacketStatusFlag::kCorrectlyReceived,
payload_0.view());
bt::StaticByteBuffer payload_1(0x01);
bt::DynamicByteBuffer packet_buffer_1 = bt::testing::ScoDataPacket(
sco_handle(), bt::hci_spec::SynchronousDataPacketStatusFlag::kCorrectlyReceived,
payload_1.view());
int sco_cb_count = 0;
test_device()->SetScoDataCallback([&](const bt::ByteBuffer& buffer) {
if (sco_cb_count == 0) {
EXPECT_THAT(buffer, ::testing::ElementsAreArray(packet_buffer_0));
} else if (sco_cb_count == 1) {
EXPECT_THAT(buffer, ::testing::ElementsAreArray(packet_buffer_1));
} else {
ADD_FAILURE() << "Unexpected packet sent";
}
sco_cb_count++;
});
int write_cb_0_count = 0;
sco_connection()->Write(payload_0.ToVector(), [&] { write_cb_0_count++; });
RunLoopUntilIdle();
EXPECT_EQ(sco_cb_count, 1);
EXPECT_EQ(write_cb_0_count, 1);
int write_cb_1_count = 0;
sco_connection()->Write(payload_1.ToVector(), [&] { write_cb_1_count++; });
RunLoopUntilIdle();
EXPECT_EQ(sco_cb_count, 2);
EXPECT_EQ(write_cb_1_count, 1);
test_device()->ClearScoDataCallback();
}
TEST_F(ProfileServerTestScoConnected, ScoConnectionWriteTooLarge) {
bt::StaticByteBuffer<kSynchronousDataPacketLength + 1> payload_buffer;
payload_buffer.Fill(0x00);
int sco_cb_count = 0;
test_device()->SetScoDataCallback([&](const bt::ByteBuffer& buffer) { sco_cb_count++; });
int write_cb_count = 0;
sco_connection()->Write(payload_buffer.ToVector(), [&] { write_cb_count++; });
RunLoopUntilIdle();
EXPECT_EQ(sco_cb_count, 0);
EXPECT_EQ(write_cb_count, 0);
EXPECT_FALSE(sco_connection());
ASSERT_TRUE(sco_conn_error());
EXPECT_EQ(sco_conn_error().value(), ZX_ERR_IO);
test_device()->ClearScoDataCallback();
}
TEST_F(ProfileServerTestOffloadedScoConnected, ScoConnectionWriteFails) {
int write_cb_count = 0;
sco_connection()->Write(/*data=*/{0x00}, [&] { write_cb_count++; });
RunLoopUntilIdle();
EXPECT_EQ(write_cb_count, 0);
EXPECT_FALSE(sco_connection());
ASSERT_TRUE(sco_conn_error());
EXPECT_EQ(sco_conn_error().value(), ZX_ERR_IO_NOT_PRESENT);
}
} // namespace
} // namespace bthost