blob: cb829404a11ab7f2bf9b3fe846168fdb954e1361 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/logical_link.h"
#include <memory>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/connection.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/test_packets.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/link_type.h"
namespace bt::l2cap::internal {
namespace {
using Conn = hci::Connection;
using TestingBase =
bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
const hci_spec::ConnectionHandle kConnHandle = 0x0001;
class LogicalLinkTest : public TestingBase {
public:
LogicalLinkTest() = default;
~LogicalLinkTest() override = default;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LogicalLinkTest);
protected:
void SetUp() override {
TestingBase::SetUp();
InitializeACLDataChannel();
NewLogicalLink();
}
void TearDown() override {
if (link_) {
link_->Close();
link_ = nullptr;
}
a2dp_offload_manager_ = nullptr;
TestingBase::TearDown();
}
void NewLogicalLink(bt::LinkType type = bt::LinkType::kLE) {
const size_t kMaxPayload = kDefaultMTU;
auto query_service_cb = [](hci_spec::ConnectionHandle, Psm) {
return std::nullopt;
};
a2dp_offload_manager_ = std::make_unique<A2dpOffloadManager>(
transport()->command_channel()->AsWeakPtr());
link_ = std::make_unique<LogicalLink>(
kConnHandle,
type,
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
kMaxPayload,
std::move(query_service_cb),
transport()->acl_data_channel(),
transport()->command_channel(),
/*random_channel_ids=*/true,
*a2dp_offload_manager_,
dispatcher());
}
void ResetAndCreateNewLogicalLink(LinkType type = LinkType::kACL) {
link()->Close();
DeleteLink();
NewLogicalLink(type);
}
LogicalLink* link() const { return link_.get(); }
void DeleteLink() { link_ = nullptr; }
private:
std::unique_ptr<LogicalLink> link_;
std::unique_ptr<A2dpOffloadManager> a2dp_offload_manager_;
};
struct QueueAclConnectionRetVal {
l2cap::CommandId extended_features_id;
l2cap::CommandId fixed_channels_supported_id;
};
static constexpr l2cap::ExtendedFeatures kExtendedFeatures =
l2cap::kExtendedFeaturesBitEnhancedRetransmission;
using LogicalLinkDeathTest = LogicalLinkTest;
TEST_F(LogicalLinkDeathTest, DestructedWithoutClosingDies) {
// Deleting the link without calling `Close` on it should trigger an
// assertion.
ASSERT_DEATH_IF_SUPPORTED(DeleteLink(), ".*closed.*");
}
TEST_F(LogicalLinkTest, FixedChannelHasCorrectMtu) {
Channel::WeakPtr fixed_chan = link()->OpenFixedChannel(kATTChannelId);
ASSERT_TRUE(fixed_chan.is_alive());
EXPECT_EQ(kMaxMTU, fixed_chan->max_rx_sdu_size());
EXPECT_EQ(kMaxMTU, fixed_chan->max_tx_sdu_size());
}
TEST_F(LogicalLinkTest, DropsBroadcastPackets) {
ResetAndCreateNewLogicalLink();
QueueAclConnectionRetVal cmd_ids;
cmd_ids.extended_features_id = 1;
cmd_ids.fixed_channels_supported_id = 2;
const auto kExtFeaturesRsp = l2cap::testing::AclExtFeaturesInfoRsp(
cmd_ids.extended_features_id, kConnHandle, kExtendedFeatures);
EXPECT_ACL_PACKET_OUT(test_device(),
l2cap::testing::AclExtFeaturesInfoReq(
cmd_ids.extended_features_id, kConnHandle),
&kExtFeaturesRsp);
EXPECT_ACL_PACKET_OUT(test_device(),
l2cap::testing::AclFixedChannelsSupportedInfoReq(
cmd_ids.fixed_channels_supported_id, kConnHandle));
Channel::WeakPtr connectionless_chan =
link()->OpenFixedChannel(kConnectionlessChannelId);
ASSERT_TRUE(connectionless_chan.is_alive());
size_t rx_count = 0;
bool activated = connectionless_chan->Activate(
[&](ByteBufferPtr) { rx_count++; }, []() {});
ASSERT_TRUE(activated);
StaticByteBuffer group_frame(0x0A,
0x00, // Length (PSM + info = 10)
0x02,
0x00, // Connectionless data channel
0xF0,
0x0F, // PSM
'S',
'a',
'p',
'p',
'h',
'i',
'r',
'e' // Info Payload
);
hci::ACLDataPacketPtr packet = hci::ACLDataPacket::New(
kConnHandle,
hci_spec::ACLPacketBoundaryFlag::kCompletePDU,
hci_spec::ACLBroadcastFlag::kActivePeripheralBroadcast,
group_frame.size());
ASSERT_TRUE(packet);
packet->mutable_view()->mutable_payload_data().Write(group_frame);
link()->HandleRxPacket(std::move(packet));
// Should be dropped.
EXPECT_EQ(0u, rx_count);
}
// LE links are unsupported, so result should be an error. No command should be
// sent.
TEST_F(LogicalLinkTest, SetBrEdrAutomaticFlushTimeoutFailsForLELink) {
constexpr std::chrono::milliseconds kTimeout(100);
ResetAndCreateNewLogicalLink(LinkType::kLE);
bool cb_called = false;
link()->SetBrEdrAutomaticFlushTimeout(kTimeout, [&](auto result) {
cb_called = true;
ASSERT_TRUE(result.is_error());
EXPECT_EQ(
ToResult(
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS),
result.error_value());
});
EXPECT_TRUE(cb_called);
}
TEST_F(LogicalLinkTest, SetAutomaticFlushTimeoutSuccess) {
ResetAndCreateNewLogicalLink();
QueueAclConnectionRetVal cmd_ids;
cmd_ids.extended_features_id = 1;
cmd_ids.fixed_channels_supported_id = 2;
const auto kExtFeaturesRsp = l2cap::testing::AclExtFeaturesInfoRsp(
cmd_ids.extended_features_id, kConnHandle, kExtendedFeatures);
EXPECT_ACL_PACKET_OUT(test_device(),
l2cap::testing::AclExtFeaturesInfoReq(
cmd_ids.extended_features_id, kConnHandle),
&kExtFeaturesRsp);
EXPECT_ACL_PACKET_OUT(test_device(),
l2cap::testing::AclFixedChannelsSupportedInfoReq(
cmd_ids.fixed_channels_supported_id, kConnHandle));
std::optional<hci::Result<>> cb_status;
auto result_cb = [&](auto status) { cb_status = status; };
// Test command complete error
const auto kCommandCompleteError = bt::testing::CommandCompletePacket(
hci_spec::kWriteAutomaticFlushTimeout,
pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID);
EXPECT_CMD_PACKET_OUT(
test_device(),
bt::testing::WriteAutomaticFlushTimeoutPacket(link()->handle(), 0),
&kCommandCompleteError);
link()->SetBrEdrAutomaticFlushTimeout(
pw::chrono::SystemClock::duration::max(), result_cb);
RunUntilIdle();
ASSERT_TRUE(cb_status.has_value());
ASSERT_TRUE(cb_status->is_error());
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID),
*cb_status);
cb_status.reset();
// Test flush timeout = 0 (no command should be sent)
link()->SetBrEdrAutomaticFlushTimeout(std::chrono::milliseconds(0),
result_cb);
RunUntilIdle();
ASSERT_TRUE(cb_status.has_value());
EXPECT_TRUE(cb_status->is_error());
EXPECT_EQ(
ToResult(
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS),
*cb_status);
// Test infinite flush timeout (flush timeout of 0 should be sent).
const auto kCommandComplete = bt::testing::CommandCompletePacket(
hci_spec::kWriteAutomaticFlushTimeout,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
bt::testing::WriteAutomaticFlushTimeoutPacket(link()->handle(), 0),
&kCommandComplete);
link()->SetBrEdrAutomaticFlushTimeout(
pw::chrono::SystemClock::duration::max(), result_cb);
RunUntilIdle();
ASSERT_TRUE(cb_status.has_value());
EXPECT_EQ(fit::ok(), *cb_status);
cb_status.reset();
// Test msec to parameter conversion
// (hci_spec::kMaxAutomaticFlushTimeoutDuration(1279) * conversion_factor(1.6)
// = 2046).
EXPECT_CMD_PACKET_OUT(
test_device(),
bt::testing::WriteAutomaticFlushTimeoutPacket(link()->handle(), 2046),
&kCommandComplete);
link()->SetBrEdrAutomaticFlushTimeout(
hci_spec::kMaxAutomaticFlushTimeoutDuration, result_cb);
RunUntilIdle();
ASSERT_TRUE(cb_status.has_value());
EXPECT_EQ(fit::ok(), *cb_status);
cb_status.reset();
// Test too large flush timeout (no command should be sent).
link()->SetBrEdrAutomaticFlushTimeout(
hci_spec::kMaxAutomaticFlushTimeoutDuration +
std::chrono::milliseconds(1),
result_cb);
RunUntilIdle();
ASSERT_TRUE(cb_status.has_value());
EXPECT_TRUE(cb_status->is_error());
EXPECT_EQ(
ToResult(
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS),
*cb_status);
}
} // namespace
} // namespace bt::l2cap::internal