blob: b1ebc81a05aab33c2e0992462189a6a7e8fb87a6 [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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/fidl/adapter_test_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/fake_adapter_test_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_l2cap.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h"
namespace bthost {
namespace {
namespace fidlbredr = fuchsia::bluetooth::bredr;
namespace {
using FakeChannel = bt::l2cap::testing::FakeChannel;
void NopAdvertiseCallback(fidlbredr::Profile_Advertise_Result){};
const bt::DeviceAddress kTestDevAddr(bt::DeviceAddress::Type::kBREDR, {1});
constexpr bt::l2cap::PSM kPSM = bt::l2cap::kAVDTP;
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);
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;
}
} // namespace
using TestingBase = bthost::testing::AdapterTestFixture;
class FIDL_ProfileServerTest : public TestingBase {
public:
FIDL_ProfileServerTest() = default;
~FIDL_ProfileServerTest() override = default;
protected:
void SetUp() override {
TestingBase::SetUp();
fidl::InterfaceHandle<fidlbredr::Profile> profile_handle;
client_.Bind(std::move(profile_handle));
server_ =
std::make_unique<ProfileServer>(adapter()->AsWeakPtr(), client_.NewRequest(dispatcher()));
}
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_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_ProfileServerTest);
};
// TODO(fxbug.dev/64167): Replace GMock usage with homegrown mocks.
class MockConnectionReceiver : public fidlbredr::testing::ConnectionReceiver_TestBase {
public:
MockConnectionReceiver(fidl::InterfaceRequest<ConnectionReceiver> request,
async_dispatcher_t* dispatcher)
: binding_(this, std::move(request), dispatcher) {}
~MockConnectionReceiver() override = default;
MOCK_METHOD(void, Connected,
(fuchsia::bluetooth::PeerId peer_id, fidlbredr::Channel channel,
std::vector<fidlbredr::ProtocolDescriptor> protocol),
(override));
private:
fidl::Binding<ConnectionReceiver> binding_;
void NotImplemented_(const std::string& name) override {
FAIL() << name << " is not implemented";
}
};
TEST_F(FIDL_ProfileServerTest, ErrorOnInvalidDefinition) {
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::ConnectionReceiver> receiver_handle;
auto 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 = [](auto response) {
EXPECT_TRUE(response.is_err());
EXPECT_EQ(response.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Advertise(std::move(services), fidlbredr::ChannelParameters(),
std::move(receiver_handle), 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(FIDL_ProfileServerTest, ErrorOnMultipleAdvertiseRequests) {
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::ConnectionReceiver> receiver_handle1;
auto 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++; };
client()->Advertise(std::move(services1), fidlbredr::ChannelParameters(),
std::move(receiver_handle1), std::move(cb1));
RunLoopUntilIdle();
ASSERT_EQ(cb1_count, 0u);
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::ConnectionReceiver> receiver_handle2;
auto 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 = [&](auto response) {
cb2_count++;
EXPECT_TRUE(response.is_err());
EXPECT_EQ(response.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Advertise(std::move(services2), fidlbredr::ChannelParameters(),
std::move(receiver_handle2), 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(FIDL_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::ChannelParameters channel_params;
l2cap_params.set_parameters(std::move(channel_params));
fidlbredr::ConnectParameters connection;
connection.set_l2cap(std::move(l2cap_params));
// Expect an error result.
auto sock_cb = [](auto result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Connect(peer_id, std::move(connection), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(FIDL_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 connection;
connection.set_rfcomm(std::move(rfcomm_params));
// Expect an error result.
auto sock_cb = [](auto result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS);
};
client()->Connect(peer_id, std::move(connection), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(FIDL_ProfileServerTest, UnregisterAdvertisementTriggersCallback) {
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::ConnectionReceiver> receiver_handle;
auto request = receiver_handle.NewRequest();
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
size_t cb_count = 0;
auto cb = [&](auto result) {
cb_count++;
EXPECT_TRUE(result.is_response());
};
client()->Advertise(std::move(services), fidlbredr::ChannelParameters(),
std::move(receiver_handle), 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);
}
class FIDL_ProfileServerTest_ConnectedPeer : public FIDL_ProfileServerTest {
public:
FIDL_ProfileServerTest_ConnectedPeer() = default;
~FIDL_ProfileServerTest_ConnectedPeer() override = default;
protected:
void SetUp() override {
FIDL_ProfileServerTest::SetUp();
peer_ = peer_cache()->NewPeer(kTestDevAddr, true);
auto fake_peer = std::make_unique<bt::testing::FakePeer>(kTestDevAddr);
test_device()->AddPeer(std::move(fake_peer));
bt::testing::FakeController::Settings settings;
settings.ApplyDualModeDefaults();
test_device()->set_settings(settings);
bt::hci::Status status(bt::HostError::kFailed);
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();
EXPECT_TRUE(status);
ASSERT_TRUE(connection_);
EXPECT_EQ(peer_->identifier(), connection_->peer_id());
EXPECT_EQ(bt::gap::Peer::ConnectionState::kConnected, peer_->bredr()->connection_state());
}
void TearDown() override {
connection_ = nullptr;
peer_ = nullptr;
FIDL_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_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_ProfileServerTest_ConnectedPeer);
};
TEST_F(FIDL_ProfileServerTest_ConnectedPeer, ConnectL2capChannelParameters) {
auto 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::Status status) { EXPECT_TRUE(status.is_success()); });
bt::l2cap::ChannelParameters expected_params;
expected_params.mode = bt::l2cap::ChannelMode::kEnhancedRetransmission;
expected_params.max_rx_sdu_size = bt::l2cap::kMinACLMTU;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41,
expected_params);
fidlbredr::ChannelParameters fidl_params;
fidl_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
fidl_params.set_max_rx_sdu_size(bt::l2cap::kMinACLMTU);
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto chan_cb = [&channel](auto result) {
EXPECT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
// Initiates pairing
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(kPSM);
l2cap_params.set_parameters(std::move(fidl_params));
fidlbredr::ConnectParameters connection;
connection.set_l2cap(std::move(l2cap_params));
client()->Connect(peer_id, std::move(connection), std::move(chan_cb));
RunLoopUntilIdle();
ASSERT_TRUE(channel.has_value());
EXPECT_TRUE(channel->has_socket());
EXPECT_FALSE(channel->IsEmpty());
EXPECT_EQ(channel->channel_mode(), fidl_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(FIDL_ProfileServerTest_ConnectedPeer,
ConnectWithAuthenticationRequiredButLinkKeyNotAuthenticatedFails) {
auto pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kNoInputNoOutput);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
pairing_delegate->SetCompletePairingCallback(
[&](bt::PeerId, bt::sm::Status status) { EXPECT_TRUE(status.is_success()); });
fidlbredr::SecurityRequirements security;
security.set_authentication_required(true);
fidlbredr::ChannelParameters chan_params;
chan_params.set_security_requirements(std::move(security));
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(kPSM);
l2cap_params.set_parameters(std::move(chan_params));
fidlbredr::ConnectParameters conn_params;
conn_params.set_l2cap(std::move(l2cap_params));
size_t sock_cb_count = 0;
auto sock_cb = [&](auto 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(FIDL_ProfileServerTest_ConnectedPeer, ConnectEmptyChannelResponse) {
auto 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::Status status) { EXPECT_TRUE(status.is_success()); });
// Make the l2cap channel creation fail.
l2cap()->set_simulate_open_channel_failure(true);
bt::l2cap::ChannelParameters expected_params;
expected_params.mode = bt::l2cap::ChannelMode::kEnhancedRetransmission;
expected_params.max_rx_sdu_size = bt::l2cap::kMinACLMTU;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41,
expected_params);
fidlbredr::ChannelParameters fidl_params;
fidl_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
fidl_params.set_max_rx_sdu_size(bt::l2cap::kMinACLMTU);
auto sock_cb = [](auto result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(fuchsia::bluetooth::ErrorCode::FAILED, result.err());
};
// Initiates pairing
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(kPSM);
l2cap_params.set_parameters(std::move(fidl_params));
fidlbredr::ConnectParameters connection;
connection.set_l2cap(std::move(l2cap_params));
client()->Connect(peer_id, std::move(connection), std::move(sock_cb));
RunLoopUntilIdle();
}
TEST_F(FIDL_ProfileServerTest_ConnectedPeer,
AdvertiseChannelParametersReceivedInOnChannelConnectedCallback) {
fidlbredr::ChannelParameters fidl_chan_params;
fidl_chan_params.set_channel_mode(fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
constexpr uint16_t kTxMtu = bt::l2cap::kMinACLMTU;
auto pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
using ::testing::StrictMock;
fidl::InterfaceHandle<fidlbredr::ConnectionReceiver> connect_receiver_handle;
auto connect_receiver = std::make_unique<StrictMock<MockConnectionReceiver>>(
connect_receiver_handle.NewRequest(), dispatcher());
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
client()->Advertise(std::move(services), std::move(fidl_chan_params),
std::move(connect_receiver_handle), NopAdvertiseCallback);
RunLoopUntilIdle();
EXPECT_CALL(*connect_receiver, Connected(::testing::_, ::testing::_, ::testing::_))
.WillOnce([expected_id = peer()->identifier(), kTxMtu](fuchsia::bluetooth::PeerId peer_id,
fidlbredr::Channel channel,
::testing::Unused) {
ASSERT_EQ(expected_id.value(), peer_id.value);
ASSERT_TRUE(channel.has_socket());
EXPECT_EQ(channel.channel_mode(), fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION);
EXPECT_EQ(channel.max_tx_sdu_size(), kTxMtu);
EXPECT_FALSE(channel.has_ext_direction());
EXPECT_FALSE(channel.has_flush_timeout());
});
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41, kTxMtu));
RunLoopUntilIdle();
}
class AclPrioritySupportedTest : public FIDL_ProfileServerTest_ConnectedPeer {
public:
void SetUp() override {
set_vendor_features(BT_VENDOR_FEATURES_SET_ACL_PRIORITY_COMMAND);
FIDL_ProfileServerTest_ConnectedPeer::SetUp();
}
};
class PriorityTest
: public AclPrioritySupportedTest,
public ::testing::WithParamInterface<std::pair<fidlbredr::A2dpDirectionPriority, bool>> {};
TEST_P(PriorityTest, OutboundConnectAndSetPriority) {
const auto kPriority = GetParam().first;
const bool kExpectSuccess = GetParam().second;
auto 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::Status status) { EXPECT_TRUE(status.is_success()); });
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41,
bt::l2cap::ChannelParameters());
fbl::RefPtr<bt::l2cap::testing::FakeChannel> fake_channel;
l2cap()->set_channel_callback([&](auto chan) { fake_channel = std::move(chan); });
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto chan_cb = [&channel](auto result) {
ASSERT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(kPSM);
fidlbredr::ConnectParameters conn_params;
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);
ASSERT_TRUE(channel.has_value());
ASSERT_TRUE(channel->has_ext_direction());
auto client = channel->mutable_ext_direction()->Bind();
size_t priority_cb_count = 0;
fake_channel->set_acl_priority_fails(!kExpectSuccess);
client->SetPriority(kPriority, [&](auto 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(), bt::hci::AclPriority::kSource);
break;
case fidlbredr::A2dpDirectionPriority::SINK:
EXPECT_EQ(fake_channel->requested_acl_priority(), bt::hci::AclPriority::kSink);
break;
default:
EXPECT_EQ(fake_channel->requested_acl_priority(), bt::hci::AclPriority::kNormal);
}
} else {
EXPECT_EQ(fake_channel->requested_acl_priority(), bt::hci::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(FIDL_ProfileServerTest_ConnectedPeer, PriorityTest,
::testing::ValuesIn(kPriorityParams));
TEST_F(AclPrioritySupportedTest, InboundConnectAndSetPriority) {
fidlbredr::ChannelParameters fidl_chan_params;
constexpr uint16_t kTxMtu = bt::l2cap::kMinACLMTU;
auto pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
fbl::RefPtr<bt::l2cap::testing::FakeChannel> fake_channel;
l2cap()->set_channel_callback([&](auto chan) { fake_channel = std::move(chan); });
using ::testing::StrictMock;
fidl::InterfaceHandle<fidlbredr::ConnectionReceiver> connect_receiver_handle;
auto connect_receiver = std::make_unique<StrictMock<MockConnectionReceiver>>(
connect_receiver_handle.NewRequest(), dispatcher());
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
client()->Advertise(std::move(services), std::move(fidl_chan_params),
std::move(connect_receiver_handle), NopAdvertiseCallback);
RunLoopUntilIdle();
std::optional<fidlbredr::Channel> channel;
EXPECT_CALL(*connect_receiver, Connected(::testing::_, ::testing::_, ::testing::_))
.WillOnce([&channel](fuchsia::bluetooth::PeerId peer_id, fidlbredr::Channel cb_channel,
::testing::Unused) { channel = std::move(cb_channel); });
EXPECT_TRUE(
l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41, kTxMtu));
RunLoopUntilIdle();
ASSERT_TRUE(channel.has_value());
ASSERT_TRUE(channel->has_ext_direction());
auto client = channel->mutable_ext_direction()->Bind();
size_t priority_cb_count = 0;
client->SetPriority(fidlbredr::A2dpDirectionPriority::SINK, [&](auto result) {
EXPECT_TRUE(result.is_response());
priority_cb_count++;
});
RunLoopUntilIdle();
EXPECT_EQ(priority_cb_count, 1u);
ASSERT_TRUE(fake_channel);
EXPECT_EQ(fake_channel->requested_acl_priority(), bt::hci::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(FIDL_ProfileServerTest_ConnectedPeer, ConnectReturnsValidSocket) {
auto 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::Status status) { EXPECT_TRUE(status.is_success()); });
bt::l2cap::ChannelParameters expected_params;
l2cap()->ExpectOutboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41,
expected_params);
fidlbredr::ChannelParameters fidl_params;
std::optional<fbl::RefPtr<bt::l2cap::testing::FakeChannel>> fake_chan;
l2cap()->set_channel_callback([&fake_chan](auto chan) { fake_chan = std::move(chan); });
// Expect a non-empty channel result.
std::optional<fidlbredr::Channel> channel;
auto result_cb = [&channel](auto result) {
EXPECT_TRUE(result.is_response());
channel = std::move(result.response().channel);
};
fuchsia::bluetooth::PeerId peer_id{peer()->identifier().value()};
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(kPSM);
l2cap_params.set_parameters(std::move(fidl_params));
fidlbredr::ConnectParameters connection;
connection.set_l2cap(std::move(l2cap_params));
// Initiates pairing
client()->Connect(peer_id, std::move(connection), 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());
auto fake_chan_ptr = fake_chan->get();
size_t send_count = 0;
fake_chan_ptr->SetSendCallback([&send_count](auto buffer) { send_count++; },
async_get_default_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(FIDL_ProfileServerTest_ConnectedPeer, ConnectionReceiverReturnsValidSocket) {
auto pairing_delegate =
std::make_unique<bt::gap::FakePairingDelegate>(bt::sm::IOCapability::kDisplayYesNo);
adapter()->SetPairingDelegate(pairing_delegate->GetWeakPtr());
using ::testing::StrictMock;
fidl::InterfaceHandle<fidlbredr::ConnectionReceiver> connect_receiver_handle;
auto connect_receiver = std::make_unique<StrictMock<MockConnectionReceiver>>(
connect_receiver_handle.NewRequest(), dispatcher());
std::optional<fbl::RefPtr<bt::l2cap::testing::FakeChannel>> fake_chan;
l2cap()->set_channel_callback([&fake_chan](auto chan) { fake_chan = std::move(chan); });
fidlbredr::ChannelParameters fidl_chan_params;
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
std::optional<fidlbredr::Channel> channel;
EXPECT_CALL(*connect_receiver, Connected(::testing::_, ::testing::_, ::testing::_))
.WillOnce([expected_id = peer()->identifier(), &channel](fuchsia::bluetooth::PeerId peer_id,
fidlbredr::Channel cb_channel,
::testing::Unused) {
ASSERT_EQ(expected_id.value(), peer_id.value);
channel = std::move(cb_channel);
});
client()->Advertise(std::move(services), std::move(fidl_chan_params),
std::move(connect_receiver_handle), NopAdvertiseCallback);
RunLoopUntilIdle();
EXPECT_TRUE(l2cap()->TriggerInboundL2capChannel(connection()->link().handle(), kPSM, 0x40, 0x41));
RunLoopUntilIdle();
ASSERT_TRUE(channel.has_value());
ASSERT_TRUE(channel->has_socket());
auto& socket = channel->socket();
ASSERT_TRUE(fake_chan.has_value());
auto fake_chan_ptr = fake_chan->get();
size_t send_count = 0;
fake_chan_ptr->SetSendCallback([&send_count](auto buffer) { send_count++; },
async_get_default_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);
}
fidlbredr::ScoConnectionParameters CreateScoConnectionParameters() {
fidlbredr::ScoConnectionParameters params;
params.set_parameter_set(fidlbredr::HfpParameterSet::MSBC_T2);
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;
}
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(::fuchsia::bluetooth::bredr::ScoConnection connection) override {
connection_ = std::move(connection);
connected_count_++;
}
void Error(::fuchsia::bluetooth::bredr::ScoErrorCode error) override {
error_ = error;
error_count_++;
}
void Close() { binding_.Close(ZX_ERR_PEER_CLOSED); }
size_t connected_count() const { return connected_count_; }
const std::optional<fidlbredr::ScoConnection>& connection() const { return connection_; }
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_;
std::optional<fidlbredr::ScoConnection> connection_;
size_t error_count_;
std::optional<fidlbredr::ScoErrorCode> error_;
virtual void NotImplemented_(const std::string& name) { FAIL() << name << " is not implemented"; }
};
TEST_F(FIDL_ProfileServerTest, ConnectScoWithInvalidParameters) {
fidlbredr::ScoConnectionParameters bad_sco_params;
fidl::InterfaceHandle<fidlbredr::ScoConnectionReceiver> receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
client()->ConnectSco(fuchsia::bluetooth::PeerId{1}, true, std::move(bad_sco_params),
std::move(receiver_handle));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
EXPECT_FALSE(receiver.connection().has_value());
}
TEST_F(FIDL_ProfileServerTest, ConnectScoWithUnconnectedPeerReturnsError) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
fidl::InterfaceHandle<fidlbredr::ScoConnectionReceiver> receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
client()->ConnectSco(fuchsia::bluetooth::PeerId{1}, true, std::move(sco_params),
std::move(receiver_handle));
RunLoopUntilIdle();
ASSERT_TRUE(receiver.error().has_value());
EXPECT_EQ(receiver.error().value(), fidlbredr::ScoErrorCode::FAILURE);
EXPECT_FALSE(receiver.connection().has_value());
}
TEST_F(FIDL_ProfileServerTest_ConnectedPeer, ConnectScoInitiatorSuccess) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
fidl::InterfaceHandle<fidlbredr::ScoConnectionReceiver> receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
client()->ConnectSco(fuchsia::bluetooth::PeerId{peer()->identifier().value()}, /*initiator=*/true,
std::move(sco_params), std::move(receiver_handle));
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
ASSERT_TRUE(receiver.connection().has_value());
EXPECT_TRUE(receiver.connection()->has_socket());
}
TEST_F(FIDL_ProfileServerTest_ConnectedPeer, ConnectScoInitiatorAndCloseReceiver) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
fidl::InterfaceHandle<fidlbredr::ScoConnectionReceiver> receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
client()->ConnectSco(fuchsia::bluetooth::PeerId{peer()->identifier().value()}, /*initiator=*/true,
std::move(sco_params), std::move(receiver_handle));
receiver.Close();
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().has_value());
}
// Verifies that the profile server gracefully ignores connection results after the receiver has
// closed.
TEST_F(FIDL_ProfileServerTest_ConnectedPeer,
ConnectScoInitiatorAndCloseReceiverBeforeCompleteEvent) {
fidlbredr::ScoConnectionParameters sco_params = CreateScoConnectionParameters();
EXPECT_TRUE(fidl_helpers::FidlToScoParameters(sco_params).is_ok());
fidl::InterfaceHandle<fidlbredr::ScoConnectionReceiver> receiver_handle;
FakeScoConnectionReceiver receiver(receiver_handle.NewRequest(), dispatcher());
test_device()->SetDefaultCommandStatus(bt::hci::kEnhancedSetupSynchronousConnection,
bt::hci::kSuccess);
client()->ConnectSco(fuchsia::bluetooth::PeerId{peer()->identifier().value()}, /*initiator=*/true,
std::move(sco_params), std::move(receiver_handle));
receiver.Close();
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().has_value());
test_device()->SendCommandChannelPacket(bt::testing::SynchronousConnectionCompletePacket(
0x00, peer()->address(), bt::hci::LinkType::kSCO, bt::hci::StatusCode::kConnectionTimeout));
RunLoopUntilIdle();
EXPECT_FALSE(receiver.error().has_value());
EXPECT_FALSE(receiver.connection().has_value());
}
class FIDL_ProfileServerTest_FakeAdapter : public bt::gap::testing::FakeAdapterTestFixture {
public:
FIDL_ProfileServerTest_FakeAdapter() = default;
~FIDL_ProfileServerTest_FakeAdapter() override = default;
void SetUp() override {
FakeAdapterTestFixture::SetUp();
fidl::InterfaceHandle<fidlbredr::Profile> 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_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(FIDL_ProfileServerTest_FakeAdapter);
};
TEST_F(FIDL_ProfileServerTest_FakeAdapter, ConnectChannelParametersContainsFlushTimeout) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const zx::duration kFlushTimeout(zx::msec(100));
fbl::RefPtr<FakeChannel> last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback([&](auto chan) { last_channel = chan; });
fidlbredr::ChannelParameters chan_params;
chan_params.set_flush_timeout(kFlushTimeout.get());
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
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(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);
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.get());
}
TEST_F(FIDL_ProfileServerTest_FakeAdapter, AdvertiseChannelParametersContainsFlushTimeout) {
const zx::duration kFlushTimeout(zx::msec(100));
const bt::hci::ConnectionHandle kHandle(1);
std::vector<fidlbredr::ServiceDefinition> services;
services.emplace_back(MakeFIDLServiceDefinition());
fidlbredr::ChannelParameters chan_params;
chan_params.set_flush_timeout(kFlushTimeout.get());
std::optional<fidlbredr::Channel> fidl_channel;
using ::testing::StrictMock;
fidl::InterfaceHandle<fidlbredr::ConnectionReceiver> connect_receiver_handle;
auto connect_receiver = std::make_unique<StrictMock<MockConnectionReceiver>>(
connect_receiver_handle.NewRequest(), dispatcher());
EXPECT_CALL(*connect_receiver, Connected(::testing::_, ::testing::_, ::testing::_))
.WillOnce([&fidl_channel](::testing::Unused, fidlbredr::Channel cb_channel,
::testing::Unused) { fidl_channel = std::move(cb_channel); });
client()->Advertise(std::move(services), std::move(chan_params),
std::move(connect_receiver_handle), 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 = fbl::AdoptRef(new FakeChannel(bt::l2cap::kFirstDynamicChannelId,
bt::l2cap::kFirstDynamicChannelId, kHandle,
bt::hci::Connection::LinkType::kACL, chan_info));
service_iter->second.connect_callback(channel, MakeL2capProtocolListElement());
RunLoopUntilIdle();
ASSERT_TRUE(fidl_channel);
ASSERT_TRUE(fidl_channel->has_flush_timeout());
EXPECT_EQ(fidl_channel->flush_timeout(), kFlushTimeout.get());
}
TEST_F(FIDL_ProfileServerTest_FakeAdapter, L2capParametersExtRequestParametersSucceeds) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const zx::duration kFlushTimeout(zx::msec(100));
const uint16_t kMaxRxSduSize(200);
fbl::RefPtr<FakeChannel> last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback([&](auto chan) { last_channel = chan; });
fidlbredr::ChannelParameters chan_params;
chan_params.set_channel_mode(fidlbredr::ChannelMode::BASIC);
chan_params.set_max_rx_sdu_size(kMaxRxSduSize);
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
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(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);
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;
auto l2cap_client = response_channel->mutable_ext_l2cap()->Bind();
l2cap_client->RequestParameters(
std::move(request_chan_params),
[&](fidlbredr::ChannelParameters new_params) { result_chan_params = std::move(new_params); });
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(fxb/73039): 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(FIDL_ProfileServerTest_FakeAdapter, L2capParametersExtRequestParametersFails) {
const bt::PeerId kPeerId;
const fuchsia::bluetooth::PeerId kFidlPeerId{kPeerId.value()};
const zx::duration kFlushTimeout(zx::msec(100));
fbl::RefPtr<FakeChannel> last_channel;
adapter()->fake_bredr()->set_l2cap_channel_callback([&](auto chan) { last_channel = chan; });
fidlbredr::L2capParameters l2cap_params;
l2cap_params.set_psm(fidlbredr::PSM_AVDTP);
fidlbredr::ConnectParameters conn_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);
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;
auto l2cap_client = response_channel->mutable_ext_l2cap()->Bind();
l2cap_client->RequestParameters(
std::move(request_chan_params),
[&](fidlbredr::ChannelParameters new_params) { result_chan_params = std::move(new_params); });
RunLoopUntilIdle();
ASSERT_TRUE(result_chan_params.has_value());
EXPECT_FALSE(result_chan_params->has_flush_timeout());
l2cap_client.Unbind();
RunLoopUntilIdle();
}
} // namespace
} // namespace bthost