blob: 27761080af7f6f0226afb9b1b569fc59e6ce0d74 [file] [log] [blame]
// Copyright 2017 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/hci/connection.h"
#include "pw_bluetooth/hci_common.emb.h"
#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/bredr_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/low_energy_connection.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/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_helpers.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/fake_acl_connection.h"
namespace bt::hci {
namespace {
const hci_spec::LEConnectionParameters kTestParams(1, 1, 1);
const DeviceAddress kLEAddress1(DeviceAddress::Type::kLEPublic, {1});
const DeviceAddress kLEAddress2(DeviceAddress::Type::kLEPublic, {2});
const DeviceAddress kACLAddress1(DeviceAddress::Type::kBREDR, {3});
const DeviceAddress kACLAddress2(DeviceAddress::Type::kBREDR, {4});
constexpr UInt128 kLTK{{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}};
constexpr uint64_t kRand = 1;
constexpr uint16_t kEDiv = 255;
constexpr hci_spec::LinkKeyType kLinkKeyType =
hci_spec::LinkKeyType::kAuthenticatedCombination256;
const DataBufferInfo kBrEdrBufferInfo(1024, 5);
const DataBufferInfo kLeBufferInfo(1024, 1);
using bt::testing::CommandTransaction;
using TestingBase =
bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
const StaticByteBuffer kReadEncryptionKeySizeCommand =
StaticByteBuffer(0x08,
0x14, // opcode: HCI_ReadEncryptionKeySize
0x02, // parameter size
0x01,
0x00 // connection handle: 0x0001
);
// HCI_Disconnect (handle: 0x0001, reason: RemoteUserTerminatedConnection)
const StaticByteBuffer kDisconnectCommand(
0x06,
0x04, // opcode: HCI_Disconnect
0x03, // parameter total size
0x01,
0x00, // handle: 1
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION);
// HCI_Disconnect (handle: 0x0001, reason: RemoteUserTerminatedConnection)
const StaticByteBuffer kDisconnectCommandAuthFailure(
0x06,
0x04, // opcode: HCI_Disconnect
0x03, // parameter total size
0x01,
0x00, // handle: 1
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
class ConnectionTest : public TestingBase {
public:
ConnectionTest() = default;
~ConnectionTest() override = default;
protected:
void SetUp() override {
TestingBase::SetUp();
InitializeACLDataChannel(kBrEdrBufferInfo, kLeBufferInfo);
}
std::unique_ptr<LowEnergyConnection> NewLEConnection(
pw::bluetooth::emboss::ConnectionRole role =
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
hci_spec::ConnectionHandle handle = kTestHandle) {
return std::make_unique<LowEnergyConnection>(handle,
kLEAddress1,
kLEAddress2,
kTestParams,
role,
transport()->GetWeakPtr());
}
std::unique_ptr<BrEdrConnection> NewACLConnection(
pw::bluetooth::emboss::ConnectionRole role =
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
hci_spec::ConnectionHandle handle = kTestHandle) {
return std::make_unique<BrEdrConnection>(
handle, kACLAddress1, kACLAddress2, role, transport()->GetWeakPtr());
}
};
// Tests using this harness will be instantiated using ACL and LE link types.
// See INSTANTIATE_TEST_SUITE_P(ConnectionTest, LinkTypeConnectionTest, ...)
class LinkTypeConnectionTest
: public ConnectionTest,
public ::testing::WithParamInterface<bt::LinkType> {
protected:
std::unique_ptr<AclConnection> NewConnection(
pw::bluetooth::emboss::ConnectionRole role =
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
hci_spec::ConnectionHandle handle = kTestHandle) {
const bt::LinkType ll_type = GetParam();
switch (ll_type) {
case bt::LinkType::kACL:
return NewACLConnection(role, handle);
case bt::LinkType::kLE:
return NewLEConnection(role, handle);
default:
break;
}
BT_PANIC("Invalid link type: %u", static_cast<unsigned>(ll_type));
return nullptr;
}
// Assigns the appropriate test link key based on the type of link being
// tested.
void SetTestLinkKey(Connection* connection) {
const bt::LinkType ll_type = GetParam();
if (ll_type == bt::LinkType::kLE) {
static_cast<LowEnergyConnection*>(connection)
->set_ltk(hci_spec::LinkKey(kLTK, kRand, kEDiv));
} else {
static_cast<BrEdrConnection*>(connection)
->set_link_key(hci_spec::LinkKey(kLTK, 0, 0), kLinkKeyType);
}
}
};
using HCI_ConnectionTest = ConnectionTest;
TEST_F(ConnectionTest, Getters) {
std::unique_ptr<LowEnergyConnection> connection = NewLEConnection();
EXPECT_EQ(kTestHandle, connection->handle());
EXPECT_EQ(pw::bluetooth::emboss::ConnectionRole::CENTRAL, connection->role());
EXPECT_EQ(kTestParams, connection->low_energy_parameters());
EXPECT_EQ(kLEAddress1, connection->local_address());
EXPECT_EQ(kLEAddress2, connection->peer_address());
EXPECT_EQ(std::nullopt, connection->ltk());
connection->set_ltk(hci_spec::LinkKey());
ASSERT_TRUE(connection->ltk().has_value());
EXPECT_EQ(hci_spec::LinkKey(), connection->ltk().value());
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_F(ConnectionTest, AclLinkKeyAndTypeAccessors) {
std::unique_ptr<BrEdrConnection> connection = NewACLConnection();
EXPECT_EQ(std::nullopt, connection->ltk());
EXPECT_EQ(std::nullopt, connection->ltk_type());
connection->set_link_key(hci_spec::LinkKey(), kLinkKeyType);
ASSERT_TRUE(connection->ltk().has_value());
EXPECT_EQ(hci_spec::LinkKey(), connection->ltk().value());
ASSERT_TRUE(connection->ltk_type().has_value());
EXPECT_EQ(kLinkKeyType, connection->ltk_type().value());
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_P(LinkTypeConnectionTest, Disconnect) {
// clang-format off
// Respond with Command Status and Disconnection Complete.
StaticByteBuffer cmd_status_bytes(
hci_spec::kCommandStatusEventCode, 0x04, pw::bluetooth::emboss::StatusCode::SUCCESS, 1,
0x06, 0x04);
StaticByteBuffer disc_cmpl_bytes(
hci_spec::kDisconnectionCompleteEventCode, 0x04,
pw::bluetooth::emboss::StatusCode::SUCCESS, 0x01, 0x00,
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST);
// clang-format on
EXPECT_CMD_PACKET_OUT(
test_device(), kDisconnectCommand, &cmd_status_bytes, &disc_cmpl_bytes);
bool callback_called = false;
test_device()->SetTransactionCallback(
[&callback_called] { callback_called = true; });
auto connection = NewConnection();
size_t disconn_cb_count = 0;
auto disconn_complete_cb = [&](const Connection& cb_conn, auto reason) {
disconn_cb_count++;
EXPECT_EQ(
reason,
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST);
};
connection->set_peer_disconnect_callback(disconn_complete_cb);
connection->Disconnect(
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION);
RunUntilIdle();
EXPECT_TRUE(callback_called);
EXPECT_EQ(1u, disconn_cb_count);
}
TEST_P(LinkTypeConnectionTest, LinkRegistrationAndLocalDisconnection) {
const bt::LinkType ll_type = GetParam();
const hci_spec::ConnectionHandle kHandle0 = 0x0001;
const hci_spec::ConnectionHandle kHandle1 = 0x0002;
const auto& kBufferInfo =
ll_type == bt::LinkType::kACL ? kBrEdrBufferInfo : kLeBufferInfo;
// Should register connection with ACL Data Channel.
FakeAclConnection acl_connection_0(acl_data_channel(), kHandle0, ll_type);
FakeAclConnection acl_connection_1(acl_data_channel(), kHandle1, ll_type);
acl_data_channel()->RegisterConnection(acl_connection_0.GetWeakPtr());
acl_data_channel()->RegisterConnection(acl_connection_1.GetWeakPtr());
// HCI Connections corresponding to respective |acl_connection_*|
auto hci_connection_0 =
NewConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle0);
auto hci_connection_1 =
NewConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle1);
// Fill up BR/EDR controller buffer
for (size_t i = 0; i < kBufferInfo.max_num_packets(); i++) {
// Connection handle should have been registered with ACL Data Channel.
const StaticByteBuffer kPacket(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle0),
UpperBits(kHandle0),
// payload length
0x01,
0x00,
// payload
static_cast<uint8_t>(i));
EXPECT_ACL_PACKET_OUT(test_device(), kPacket);
// Create packet to send on |acl_connection_0|
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle0,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(i);
acl_connection_0.QueuePacket(std::move(packet));
RunUntilIdle();
}
// Create packet to send on |acl_connection_1|
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle1,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(1);
acl_connection_1.QueuePacket(std::move(packet));
RunUntilIdle();
// Packet for |acl_connection_1| should not have been sent because controller
// buffer is full
EXPECT_EQ(acl_connection_0.queued_packets().size(), 0u);
EXPECT_EQ(acl_connection_1.queued_packets().size(), 1u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
const auto disconnect_status_rsp =
bt::testing::DisconnectStatusResponsePacket();
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kHandle0),
&disconnect_status_rsp);
hci_connection_0->Disconnect(
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION);
RunUntilIdle();
acl_data_channel()->UnregisterConnection(kHandle0);
// Controller packet counts for |kHandle0| should not have been cleared after
// disconnect. Disconnection Complete handler should clear controller packet
// counts, so packet for |kHandle1| should be sent.
DynamicByteBuffer disconnection_complete(
bt::testing::DisconnectionCompletePacket(kHandle0));
test_device()->SendCommandChannelPacket(disconnection_complete);
// Send out last packet
EXPECT_ACL_PACKET_OUT(test_device(),
StaticByteBuffer(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle1),
UpperBits(kHandle1),
// payload length
0x01,
0x00,
// payload
1));
RunUntilIdle();
// Connection handle |kHandle0| should have been unregistered with ACL Data
// Channel. Since controller packet count was cleared, packet for |kHandle1|
// should have been sent.
EXPECT_EQ(acl_connection_0.queued_packets().size(), 0u);
EXPECT_EQ(acl_connection_1.queued_packets().size(), 0u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
// |acl_connection_1| is destroyed in test destructor
EXPECT_CMD_PACKET_OUT(test_device(), bt::testing::DisconnectPacket(kHandle1));
}
// In remote disconnection, Connection::Disconnect is not called. Instead,
// Connection::OnDisconnectionComplete is invoked and handles all cleanup.
TEST_P(LinkTypeConnectionTest, LinkRegistrationAndRemoteDisconnection) {
const bt::LinkType ll_type = GetParam();
const hci_spec::ConnectionHandle kHandle0 = 0x0001;
const hci_spec::ConnectionHandle kHandle1 = 0x0002;
const auto& kBufferInfo =
ll_type == bt::LinkType::kACL ? kBrEdrBufferInfo : kLeBufferInfo;
// Should register connection with ACL Data Channel.
FakeAclConnection acl_connection_0(acl_data_channel(), kHandle0, ll_type);
FakeAclConnection acl_connection_1(acl_data_channel(), kHandle1, ll_type);
acl_data_channel()->RegisterConnection(acl_connection_0.GetWeakPtr());
acl_data_channel()->RegisterConnection(acl_connection_1.GetWeakPtr());
// HCI Connections corresponding to respective |acl_connection_*|
auto hci_connection_0 =
NewConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle0);
auto hci_connection_1 =
NewConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle1);
// Fill up BR/EDR controller buffer
for (size_t i = 0; i < kBufferInfo.max_num_packets(); i++) {
// Connection handle should have been registered with ACL Data Channel.
const StaticByteBuffer kPacket(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle0),
UpperBits(kHandle0),
// payload length
0x01,
0x00,
// payload
static_cast<uint8_t>(i));
EXPECT_ACL_PACKET_OUT(test_device(), kPacket);
// Create packet to send on |acl_connection_0|
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle0,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(i);
acl_connection_0.QueuePacket(std::move(packet));
RunUntilIdle();
}
// Create packet to send on |acl_connection_1|
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle1,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(1);
acl_connection_1.QueuePacket(std::move(packet));
RunUntilIdle();
// Packet for |acl_connection_1| should not have been sent because controller
// buffer is full
EXPECT_EQ(acl_connection_0.queued_packets().size(), 0u);
EXPECT_EQ(acl_connection_1.queued_packets().size(), 1u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
size_t disconn_cb_count = 0;
auto disconn_complete_cb = [&](const Connection& cb_conn, auto /*reason*/) {
EXPECT_EQ(kHandle0, cb_conn.handle());
disconn_cb_count++;
};
hci_connection_0->set_peer_disconnect_callback(disconn_complete_cb);
acl_data_channel()->UnregisterConnection(kHandle0);
// Disconnection Complete handler should clear controller packet counts, so
// packet for |kHandle1| should be sent.
DynamicByteBuffer disconnection_complete(
bt::testing::DisconnectionCompletePacket(kHandle0));
test_device()->SendCommandChannelPacket(disconnection_complete);
// Send out last packet
EXPECT_ACL_PACKET_OUT(test_device(),
StaticByteBuffer(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle1),
UpperBits(kHandle1),
// payload length
0x01,
0x00,
// payload
1));
test_device()->SendCommandChannelPacket(
bt::testing::NumberOfCompletedPacketsPacket(kHandle0, 10));
RunUntilIdle();
// Connection handle |kHandle0| should have been unregistered with ACL Data
// Channel. Since controller packet count was cleared, packet for |kHandle1|
// should have been sent.
EXPECT_EQ(acl_connection_0.queued_packets().size(), 0u);
EXPECT_EQ(acl_connection_1.queued_packets().size(), 0u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
// |acl_connection_1| is destroyed in test destructor
EXPECT_CMD_PACKET_OUT(test_device(), bt::testing::DisconnectPacket(kHandle1));
}
TEST_F(ConnectionTest, StartEncryptionFailsAsLowEnergyPeripheral) {
auto conn =
NewLEConnection(pw::bluetooth::emboss::ConnectionRole::PERIPHERAL);
conn->set_ltk(hci_spec::LinkKey());
EXPECT_FALSE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_F(ConnectionTest, StartEncryptionSucceedsAsLowEnergyCentral) {
auto conn = NewLEConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL);
auto ltk = hci_spec::LinkKey();
conn->set_ltk(ltk);
EXPECT_TRUE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::LEStartEncryptionPacket(
kTestHandle, ltk.rand(), ltk.ediv(), ltk.value()));
}
TEST_F(ConnectionTest, StartEncryptionSucceedsWithBrEdrLinkKeyType) {
auto conn = NewACLConnection();
conn->set_link_key(hci_spec::LinkKey(), kLinkKeyType);
EXPECT_TRUE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(
test_device(),
bt::testing::SetConnectionEncryption(kTestHandle, /*enable=*/true));
}
TEST_P(LinkTypeConnectionTest, DisconnectError) {
// clang-format off
// Respond with Command Status and Disconnection Complete.
StaticByteBuffer cmd_status_bytes(
hci_spec::kCommandStatusEventCode, 0x04, pw::bluetooth::emboss::StatusCode::SUCCESS, 1,
0x06, 0x04);
StaticByteBuffer disc_cmpl_bytes(
hci_spec::kDisconnectionCompleteEventCode, 0x04,
pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED, 0x01, 0x00,
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST);
// clang-format on
EXPECT_CMD_PACKET_OUT(
test_device(), kDisconnectCommand, &cmd_status_bytes, &disc_cmpl_bytes);
// The callback should get called regardless of the procedure status.
bool callback_called = false;
test_device()->SetTransactionCallback(
[&callback_called] { callback_called = true; });
auto connection = NewConnection();
connection->Disconnect(
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION);
RunUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_P(LinkTypeConnectionTest, StartEncryptionNoLinkKey) {
auto conn = NewConnection();
EXPECT_FALSE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
// HCI Command Status event is received with an error status.
TEST_F(ConnectionTest, LEStartEncryptionFailsAtStatus) {
// clang-format off
StaticByteBuffer kExpectedCommand(
0x19, 0x20, // HCI_LE_Start_Encryption
28, // parameter total size
0x01, 0x00, // connection handle: 1
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // rand: 1
0xFF, 0x00, // ediv: 255
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 // LTK
);
StaticByteBuffer kErrorStatus(
0x0F, // HCI Command Status event code
4, // parameter total size
0x0C, // "Command Disallowed" error
1, // num_hci_command_packets
0x19, 0x20 // opcode: HCI_LE_Start_Encryption
);
// clang-format on
EXPECT_CMD_PACKET_OUT(test_device(), kExpectedCommand, &kErrorStatus);
bool callback = false;
auto conn = NewLEConnection();
conn->set_ltk(hci_spec::LinkKey(kLTK, kRand, kEDiv));
conn->set_encryption_change_callback([&](Result<bool> result) {
ASSERT_TRUE(result.is_error());
EXPECT_TRUE(result.error_value().is(
pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED));
callback = true;
});
EXPECT_TRUE(conn->StartEncryption());
RunUntilIdle();
EXPECT_TRUE(callback);
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_F(ConnectionTest, LEStartEncryptionSendsSetLeConnectionEncryptionCommand) {
StaticByteBuffer kExpectedCommand(0x19,
0x20, // HCI_LE_Start_Encryption
28, // parameter total size
0x01,
0x00, // connection handle: 1
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00, // rand: 1
0xFF,
0x00, // ediv: 255
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16 // LTK
);
StaticByteBuffer kStatus(0x0F, // HCI Command Status event code
4, // parameter total size
0x00, // success status
1, // num_hci_command_packets
0x19,
0x20 // opcode: HCI_LE_Start_Encryption
);
EXPECT_CMD_PACKET_OUT(test_device(), kExpectedCommand, &kStatus);
bool callback = false;
auto conn = NewLEConnection();
conn->set_ltk(hci_spec::LinkKey(kLTK, kRand, kEDiv));
conn->set_encryption_change_callback([&](auto) { callback = true; });
EXPECT_TRUE(conn->StartEncryption());
// Callback shouldn't be called until the controller sends an encryption
// changed event.
RunUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
// HCI Command Status event is received with an error status.
TEST_F(ConnectionTest, AclStartEncryptionFailsAtStatus) {
StaticByteBuffer kExpectedCommand(0x13,
0x04, // HCI_Set_Connection_Encryption
3, // parameter total size
0x01,
0x00, // connection handle
0x01 // encryption enable
);
StaticByteBuffer kErrorStatus(0x0F, // HCI Command Status event code
4, // parameter total size
0x0C, // "Command Disallowed" error
1, // num_hci_command_packets
0x13,
0x04 // opcode: HCI_Set_Connection_Encryption
);
EXPECT_CMD_PACKET_OUT(test_device(), kExpectedCommand, &kErrorStatus);
bool callback = false;
auto conn = NewACLConnection();
conn->set_link_key(hci_spec::LinkKey(kLTK, 0, 0), kLinkKeyType);
conn->set_encryption_change_callback([&](Result<bool> result) {
ASSERT_TRUE(result.is_error());
EXPECT_TRUE(result.error_value().is(
pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED));
callback = true;
});
EXPECT_TRUE(conn->StartEncryption());
RunUntilIdle();
EXPECT_TRUE(callback);
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_F(ConnectionTest, AclStartEncryptionSendsSetConnectionEncryptionCommand) {
StaticByteBuffer kExpectedCommand(0x13,
0x04, // HCI_Set_Connection_Encryption
3, // parameter total size
0x01,
0x00, // connection handle
0x01 // encryption enable
);
StaticByteBuffer kStatus(0x0F, // HCI Command Status event code
4, // parameter total size
0x00, // success status
1, // num_hci_command_packets
0x13,
0x04 // opcode: HCI_Set_Connection_Encryption
);
EXPECT_CMD_PACKET_OUT(test_device(), kExpectedCommand, &kStatus);
bool callback = false;
auto conn = NewACLConnection();
conn->set_link_key(hci_spec::LinkKey(kLTK, 0, 0), kLinkKeyType);
conn->set_encryption_change_callback([&](auto) { callback = true; });
EXPECT_TRUE(conn->StartEncryption());
// Callback shouldn't be called until the controller sends an encryption
// changed event.
RunUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_P(LinkTypeConnectionTest, EncryptionChangeIgnoredEvents) {
// clang-format off
StaticByteBuffer kEncChangeMalformed(
0x08, // HCI Encryption Change event code
3, // parameter total size
0x00, // status
0x01, 0x00 // connection handle: 1
// Last byte missing
);
StaticByteBuffer kEncChangeWrongHandle(
0x08, // HCI Encryption Change event code
4, // parameter total size
0x00, // status
0x02, 0x00, // connection handle: 2
0x01 // encryption enabled
);
// clang-format on
bool callback = false;
auto conn = NewConnection();
SetTestLinkKey(conn.get());
conn->set_encryption_change_callback([&](auto) { callback = true; });
test_device()->SendCommandChannelPacket(kEncChangeMalformed);
test_device()->SendCommandChannelPacket(kEncChangeWrongHandle);
RunUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_P(LinkTypeConnectionTest, EncryptionChangeEvents) {
// clang-format off
StaticByteBuffer kEncryptionChangeEventDisabled(
0x08, // HCI Encryption Change event code
4, // parameter total size
0x00, // status
0x01, 0x00, // connection handle: 1
0x00 // encryption disabled
);
StaticByteBuffer kEncryptionChangeEventFailed(
0x08, // HCI Encryption Change event code
4, // parameter total size
0x06, // status: Pin or Key missing
0x01, 0x00, // connection handle: 1
0x00 // encryption disabled
);
StaticByteBuffer kKeySizeComplete(
0x0E, // event code: Command Complete
0x07, // parameters total size
0xFF, // num command packets allowed (255)
0x08, 0x14, // original opcode
// return parameters
0x00, // status (success)
0x01, 0x00, // connection handle: 0x0001
0x10 // encryption key size: 16
);
// clang-format on
int callback_count = 0;
auto conn = NewConnection();
Result<bool> result = fit::error(Error(HostError::kFailed));
conn->set_encryption_change_callback([&](Result<bool> cb_result) {
callback_count++;
result = cb_result;
});
if (GetParam() == bt::LinkType::kACL) {
// The host tries to validate the size of key used to encrypt ACL links.
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
}
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::SUCCESS,
kTestHandle,
hci_spec::EncryptionStatus::kOn));
RunUntilIdle();
EXPECT_EQ(1, callback_count);
EXPECT_EQ(fit::ok(), result);
EXPECT_TRUE(result.value_or(false));
test_device()->SendCommandChannelPacket(kEncryptionChangeEventDisabled);
RunUntilIdle();
EXPECT_EQ(2, callback_count);
EXPECT_EQ(fit::ok(), result);
EXPECT_FALSE(result.value_or(true));
// The host should disconnect the link if encryption fails.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(kEncryptionChangeEventFailed);
RunUntilIdle();
EXPECT_EQ(3, callback_count);
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING)
.error_value(),
result);
}
TEST_F(ConnectionTest, EncryptionFailureNotifiesPeerDisconnectCallback) {
bool peer_disconnect_callback_received = false;
auto conn = NewLEConnection();
conn->set_peer_disconnect_callback([&](const auto& self, auto /*reason*/) {
EXPECT_EQ(conn.get(), &self);
peer_disconnect_callback_received = true;
});
// Send the encryption change failure. The host should disconnect the link as
// a result.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_MIC_FAILURE,
kTestHandle,
hci_spec::EncryptionStatus::kOff));
RunUntilIdle();
EXPECT_FALSE(peer_disconnect_callback_received);
// Send the disconnection complete resulting from the encryption failure (this
// usually does not correspond to the Disconnect command sent by
// hci::Connection, which will cause a later subsequent event).
test_device()->SendCommandChannelPacket(
bt::testing::DisconnectionCompletePacket(
kTestHandle,
pw::bluetooth::emboss::StatusCode::
CONNECTION_TERMINATED_MIC_FAILURE));
RunUntilIdle();
EXPECT_TRUE(peer_disconnect_callback_received);
}
TEST_F(ConnectionTest, AclEncryptionEnableCanNotReadKeySizeClosesLink) {
StaticByteBuffer kKeySizeComplete(0x0E, // event code: Command Complete
0x07, // parameters total size
0xFF, // num command packets allowed (255)
0x08,
0x14, // original opcode
// return parameters
0x2F, // status (insufficient security)
0x01,
0x00, // connection handle: 0x0001
0x10 // encryption key size: 16
);
int callback_count = 0;
auto conn = NewACLConnection();
conn->set_encryption_change_callback([&callback_count](Result<bool> result) {
callback_count++;
EXPECT_TRUE(result.is_error());
});
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::SUCCESS,
kTestHandle,
hci_spec::EncryptionStatus::kOn));
RunUntilIdle();
EXPECT_EQ(1, callback_count);
}
TEST_F(ConnectionTest, AclEncryptionEnableKeySizeOneByteClosesLink) {
StaticByteBuffer kKeySizeComplete(0x0E, // event code: Command Complete
0x07, // parameters total size
0xFF, // num command packets allowed (255)
0x08,
0x14, // original opcode
// return parameters
0x00, // status (success)
0x01,
0x00, // connection handle: 0x0001
0x01 // encryption key size: 1
);
int callback_count = 0;
auto conn = NewACLConnection();
conn->set_encryption_change_callback([&callback_count](Result<bool> result) {
callback_count++;
EXPECT_TRUE(result.is_error());
});
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::SUCCESS,
kTestHandle,
hci_spec::EncryptionStatus::kOn));
RunUntilIdle();
EXPECT_EQ(1, callback_count);
}
TEST_F(ConnectionTest, SecureConnectionsSucceedsWithAESEncryptionAlgorithm) {
StaticByteBuffer kKeySizeComplete(0x0E, // event code: Command Complete
0x07, // parameters total size
0xFF, // num command packets allowed (255)
0x08,
0x14, // original opcode
// return parameters
0x00, // status (success)
0x01,
0x00, // connection handle: 0x0001
0x10 // encryption key size: 16
);
std::unique_ptr<BrEdrConnection> connection = NewACLConnection(
pw::bluetooth::emboss::ConnectionRole::CENTRAL, kTestHandle);
int callback_count = 0;
Result<bool> result = fit::error(Error(HostError::kFailed));
connection->set_encryption_change_callback([&](Result<bool> cb_result) {
callback_count++;
result = cb_result;
});
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::SUCCESS,
kTestHandle,
hci_spec::EncryptionStatus::kBredrSecureConnections));
RunUntilIdle();
EXPECT_EQ(1, callback_count);
EXPECT_EQ(fit::ok(), result);
EXPECT_TRUE(result.value_or(false));
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommand);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
}
TEST_F(ConnectionTest, EncryptionSecureConnectionsWrongAlgorithmClosesLink) {
std::unique_ptr<BrEdrConnection> connection = NewACLConnection(
pw::bluetooth::emboss::ConnectionRole::CENTRAL, kTestHandle);
connection->set_use_secure_connections(true);
int callback_count = 0;
Result<bool> result = fit::error(Error(HostError::kFailed));
connection->set_encryption_change_callback([&](Result<bool> cb_result) {
ASSERT_TRUE(cb_result.is_error());
EXPECT_TRUE(cb_result.error_value().is(HostError::kInsufficientSecurity));
callback_count++;
result = cb_result;
});
// The host should disconnect the link if encryption fails.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(
bt::testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::SUCCESS,
kTestHandle,
hci_spec::EncryptionStatus::kOn));
RunUntilIdle();
EXPECT_EQ(1, callback_count);
EXPECT_TRUE(result.is_error());
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
}
TEST_P(LinkTypeConnectionTest, EncryptionKeyRefreshEvents) {
// clang-format off
StaticByteBuffer kEncryptionKeyRefresh(
0x30, // HCI Encryption Key Refresh Complete event
3, // parameter total size
0x00, // status
0x01, 0x00 // connection handle: 1
);
StaticByteBuffer kEncryptionKeyRefreshFailed(
0x30, // HCI Encryption Key Refresh Complete event
3, // parameter total size
0x06, // status: Pin or Key missing
0x01, 0x00 // connection handle: 1
);
// clang-format on
int callback_count = 0;
auto conn = NewConnection();
Result<bool> result = fit::error(Error(HostError::kFailed));
conn->set_encryption_change_callback([&](Result<bool> cb_result) {
callback_count++;
result = cb_result;
});
test_device()->SendCommandChannelPacket(kEncryptionKeyRefresh);
RunUntilIdle();
EXPECT_EQ(1, callback_count);
ASSERT_EQ(fit::ok(), result);
EXPECT_TRUE(result.value());
// The host should disconnect the link if encryption fails.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommandAuthFailure);
test_device()->SendCommandChannelPacket(kEncryptionKeyRefreshFailed);
RunUntilIdle();
EXPECT_EQ(2, callback_count);
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING)
.error_value(),
result);
}
TEST_F(ConnectionTest, LELongTermKeyRequestIgnoredEvent) {
// clang-format off
StaticByteBuffer kMalformed(
0x3E, // LE Meta Event code
12, // parameter total size
0x05, // LE LTK Request subevent code
0x01, 0x00, // connection handle: 1
// rand:
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ediv: (missing 1 byte)
0x00
);
StaticByteBuffer kWrongHandle(
0x3E, // LE Meta Event code
13, // parameter total size
0x05, // LE LTK Request subevent code
0x02, 0x00, // connection handle: 2 (wrong)
// rand: 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ediv: 0
0x00, 0x00
);
// clang-format on
auto conn = NewLEConnection();
conn->set_ltk(hci_spec::LinkKey(kLTK, 0, 0));
test_device()->SendCommandChannelPacket(kMalformed);
test_device()->SendCommandChannelPacket(kWrongHandle);
RunUntilIdle();
// Test will fail if the connection sends a response without ignoring these
// events.
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kTestHandle));
}
TEST_F(ConnectionTest, LELongTermKeyRequestNoKey) {
// clang-format off
StaticByteBuffer kEvent(
0x3E, // LE Meta Event code
13, // parameter total size
0x05, // LE LTK Request subevent code
0x01, 0x00, // connection handle: 2 (wrong)
// rand: 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ediv: 0
0x00, 0x00
);
StaticByteBuffer kResponse(
0x1B, 0x20, // opcode: HCI_LE_Long_Term_Key_Request_Negative_Reply
2, // parameter total size
0x01, 0x00 // connection handle: 1
);
// clang-format on
// The request should be rejected since there is no LTK.
EXPECT_CMD_PACKET_OUT(test_device(), kResponse);
auto conn = NewLEConnection();
test_device()->SendCommandChannelPacket(kEvent);
RunUntilIdle();
}
// There is a link key but EDiv and Rand values don't match.
TEST_F(ConnectionTest, LELongTermKeyRequestNoMatchinKey) {
// clang-format off
StaticByteBuffer kEvent(
0x3E, // LE Meta Event code
13, // parameter total size
0x05, // LE LTK Request subevent code
0x01, 0x00, // connection handle: 2 (wrong)
// rand: 0
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ediv: 0
0x00, 0x00
);
StaticByteBuffer kResponse(
0x1B, 0x20, // opcode: HCI_LE_Long_Term_Key_Request_Negative_Reply
2, // parameter total size
0x01, 0x00 // connection handle: 1
);
// clang-format on
// The request should be rejected since there is no LTK.
EXPECT_CMD_PACKET_OUT(test_device(), kResponse);
auto conn = NewLEConnection();
conn->set_ltk(hci_spec::LinkKey(kLTK, 1, 1));
test_device()->SendCommandChannelPacket(kEvent);
RunUntilIdle();
}
TEST_F(ConnectionTest, LELongTermKeyRequestReply) {
// clang-format off
StaticByteBuffer kEvent(
0x3E, // LE Meta Event code
13, // parameter total size
0x05, // LE LTK Request subevent code
0x01, 0x00, // connection handle: 2 (wrong)
// rand: 0x8899AABBCCDDEEFF
0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA, 0x99, 0x88,
// ediv: 0xBEEF
0xEF, 0xBE
);
StaticByteBuffer kResponse(
0x1A, 0x20, // opcode: HCI_LE_Long_Term_Key_Request_Reply
18, // parameter total size
0x01, 0x00, // connection handle: 1
// LTK:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
);
// clang-format on
// The request should be rejected since there is no LTK.
EXPECT_CMD_PACKET_OUT(test_device(), kResponse);
auto conn = NewLEConnection();
conn->set_ltk(hci_spec::LinkKey(kLTK, 0x8899AABBCCDDEEFF, 0xBEEF));
test_device()->SendCommandChannelPacket(kEvent);
RunUntilIdle();
}
TEST_F(
ConnectionTest,
QueuedPacketsGetDroppedOnDisconnectionCompleteAndStalePacketsAreNotSentOnHandleReuse) {
const hci_spec::ConnectionHandle kHandle = 0x0001;
// Should register connection with ACL Data Channel.
FakeAclConnection acl_connection_0(
acl_data_channel(), kHandle, bt::LinkType::kACL);
acl_data_channel()->RegisterConnection(acl_connection_0.GetWeakPtr());
// HCI Connection corresponding to |acl_connection_0|
auto hci_connection_0 =
NewACLConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle);
// Fill up BR/EDR controller buffer then queue one additional packet
for (size_t i = 0; i < kBrEdrBufferInfo.max_num_packets() + 1; i++) {
// Last packet should remain queued
if (i < kBrEdrBufferInfo.max_num_packets()) {
const StaticByteBuffer kPacket(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle),
UpperBits(kHandle),
// payload length
0x01,
0x00,
// payload
static_cast<uint8_t>(i));
EXPECT_ACL_PACKET_OUT(test_device(), kPacket);
}
// Create packet to send
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(i);
acl_connection_0.QueuePacket(std::move(packet));
RunUntilIdle();
}
// Run until the data is flushed out to the MockController.
RunUntilIdle();
// Only packets that fit in buffer should have been received.
EXPECT_EQ(acl_connection_0.queued_packets().size(), 1u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
acl_data_channel()->UnregisterConnection(kHandle);
// All future packets received should be for the next connection.
const auto disconnect_status_rsp =
bt::testing::DisconnectStatusResponsePacket();
DynamicByteBuffer disconnection_complete(
bt::testing::DisconnectionCompletePacket(kHandle));
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kHandle),
&disconnect_status_rsp,
&disconnection_complete);
// Disconnect |hci_connection_0| by destroying it. The received disconnection
// complete event will cause the handler to clear pending packets.
hci_connection_0.reset();
RunUntilIdle();
// Register connection with same handle.
FakeAclConnection acl_connection_1(
acl_data_channel(), kHandle, bt::LinkType::kACL);
acl_data_channel()->RegisterConnection(acl_connection_1.GetWeakPtr());
// HCI Connection corresponding to |acl_connection_1|
auto hci_connection_1 =
NewACLConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle);
// Fill up BR/EDR controller buffer then queue one additional packet
for (size_t i = 0; i < kBrEdrBufferInfo.max_num_packets() + 1; i++) {
// Last packet should remain queued
if (i < kBrEdrBufferInfo.max_num_packets()) {
const StaticByteBuffer kPacket(
// ACL data header (handle: 0, length 1)
LowerBits(kHandle),
UpperBits(kHandle),
// payload length
0x01,
0x00,
// payload
static_cast<uint8_t>(i));
EXPECT_ACL_PACKET_OUT(test_device(), kPacket);
}
// Create packet to send
ACLDataPacketPtr packet =
ACLDataPacket::New(kHandle,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(i);
acl_connection_1.QueuePacket(std::move(packet));
RunUntilIdle();
}
// Run until the data is flushed out to the MockController.
RunUntilIdle();
EXPECT_EQ(acl_connection_1.queued_packets().size(), 1u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
acl_data_channel()->UnregisterConnection(kHandle);
// Disconnect |hci_connection_1| by destroying it. The received disconnection
// complete event will cause the handler to clear pending packets.
hci_connection_1.reset();
EXPECT_CMD_PACKET_OUT(test_device(),
bt::testing::DisconnectPacket(kHandle),
&disconnect_status_rsp,
&disconnection_complete);
RunUntilIdle();
}
TEST_F(ConnectionTest, PeerDisconnectCallback) {
const hci_spec::ConnectionHandle kHandle = 0x0001;
auto conn =
NewACLConnection(pw::bluetooth::emboss::ConnectionRole::CENTRAL, kHandle);
size_t cb_count = 0;
auto disconn_complete_cb = [&](const Connection& cb_conn, auto /*reason*/) {
cb_count++;
// Should be safe to destroy connection from this callback, as a connection
// manager does.
conn.reset();
};
conn->set_peer_disconnect_callback(disconn_complete_cb);
RunUntilIdle();
EXPECT_EQ(0u, cb_count);
DynamicByteBuffer disconnection_complete(
bt::testing::DisconnectionCompletePacket(kHandle));
test_device()->SendCommandChannelPacket(disconnection_complete);
RunUntilIdle();
EXPECT_EQ(1u, cb_count);
}
// Test connection handling cases for all types of links.
INSTANTIATE_TEST_SUITE_P(ConnectionTest,
LinkTypeConnectionTest,
::testing::Values(bt::LinkType::kACL,
bt::LinkType::kLE));
} // namespace
} // namespace bt::hci