blob: bb71bf0b3e95bf8a1e4701167be213c871d902c1 [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/hci/connection.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/mock_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/test_packets.h"
namespace bt::hci {
namespace {
constexpr ConnectionHandle kTestHandle = 0x0001;
const 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 LinkKeyType kLinkKeyType = LinkKeyType::kAuthenticatedCombination256;
const DataBufferInfo kBrEdrBufferInfo(1024, 5);
const DataBufferInfo kLeBufferInfo(1024, 1);
using bt::testing::CommandTransaction;
using TestingBase = bt::testing::ControllerTest<bt::testing::MockController>;
class ConnectionTest : public TestingBase {
public:
ConnectionTest() = default;
~ConnectionTest() override = default;
protected:
void SetUp() override {
TestingBase::SetUp();
InitializeACLDataChannel(kBrEdrBufferInfo, kLeBufferInfo);
StartTestDevice();
}
ConnectionPtr NewLEConnection(Connection::Role role = Connection::Role::kMaster,
ConnectionHandle handle = kTestHandle) {
return Connection::CreateLE(handle, role, kLEAddress1, kLEAddress2, kTestParams,
transport()->WeakPtr());
}
ConnectionPtr NewACLConnection(Connection::Role role = Connection::Role::kMaster,
ConnectionHandle handle = kTestHandle) {
return Connection::CreateACL(handle, role, kACLAddress1, kACLAddress2, transport()->WeakPtr());
}
};
// Tests using this harness will be instantiated using ACL and LE link types.
// See INSTANTIATE_TEST_SUITE_P(HCI_ConnectionTest, LinkTypeConnectionTest, ...)
class LinkTypeConnectionTest : public ConnectionTest,
public ::testing::WithParamInterface<Connection::LinkType> {
protected:
ConnectionPtr NewConnection(Connection::Role role = Connection::Role::kMaster,
ConnectionHandle handle = kTestHandle) {
const Connection::LinkType ll_type = GetParam();
switch (ll_type) {
case Connection::LinkType::kACL:
return NewACLConnection(role, handle);
case Connection::LinkType::kLE:
return NewLEConnection(role, handle);
default:
break;
}
ZX_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 Connection::LinkType ll_type = GetParam();
if (ll_type == Connection::LinkType::kLE) {
connection->set_le_ltk(LinkKey(kLTK, kRand, kEDiv));
} else {
connection->set_bredr_link_key(LinkKey(kLTK, 0, 0), kLinkKeyType);
}
}
};
using HCI_ConnectionTest = ConnectionTest;
TEST_F(HCI_ConnectionTest, Getters) {
auto connection = NewLEConnection();
EXPECT_EQ(Connection::LinkType::kLE, connection->ll_type());
EXPECT_EQ(kTestHandle, connection->handle());
EXPECT_EQ(Connection::Role::kMaster, 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_le_ltk(LinkKey());
ASSERT_TRUE(connection->ltk().has_value());
EXPECT_EQ(LinkKey(), connection->ltk().value());
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_F(HCI_ConnectionTest, AclLinkKeyAndTypeAccessors) {
auto connection = NewACLConnection();
EXPECT_EQ(Connection::LinkType::kACL, connection->ll_type());
EXPECT_EQ(std::nullopt, connection->ltk());
EXPECT_EQ(std::nullopt, connection->ltk_type());
connection->set_bredr_link_key(LinkKey(), kLinkKeyType);
ASSERT_TRUE(connection->ltk().has_value());
EXPECT_EQ(LinkKey(), connection->ltk().value());
ASSERT_TRUE(connection->ltk_type().has_value());
EXPECT_EQ(kLinkKeyType, connection->ltk_type().value());
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_P(LinkTypeConnectionTest, Disconnect) {
// clang-format off
// HCI_Disconnect (handle: 0x0001, reason: RemoteUserTerminatedConnection)
auto req_bytes = CreateStaticByteBuffer(
0x06, 0x04, 0x03, 0x01, 0x00, StatusCode::kRemoteUserTerminatedConnection);
// Respond with Command Status and Disconnection Complete.
auto cmd_status_bytes = CreateStaticByteBuffer(
kCommandStatusEventCode, 0x04, StatusCode::kSuccess, 1, 0x06, 0x04);
auto disc_cmpl_bytes = CreateStaticByteBuffer(
kDisconnectionCompleteEventCode, 0x04,
StatusCode::kSuccess, 0x01, 0x00, StatusCode::kConnectionTerminatedByLocalHost);
// clang-format on
EXPECT_CMD_PACKET_OUT(test_device(), req_bytes, &cmd_status_bytes, &disc_cmpl_bytes);
bool callback_called = false;
test_device()->SetTransactionCallback([&callback_called] { callback_called = true; },
dispatcher());
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, StatusCode::kConnectionTerminatedByLocalHost);
};
connection->set_peer_disconnect_callback(disconn_complete_cb);
connection->Disconnect(StatusCode::kRemoteUserTerminatedConnection);
RunLoopUntilIdle();
EXPECT_TRUE(callback_called);
EXPECT_EQ(1u, disconn_cb_count);
}
TEST_P(LinkTypeConnectionTest, LinkRegistrationAndLocalDisconnection) {
const Connection::LinkType ll_type = GetParam();
const ConnectionHandle kHandle0 = 0x0001;
const ConnectionHandle kHandle1 = 0x0002;
const auto& kBufferInfo =
ll_type == Connection::LinkType::kACL ? kBrEdrBufferInfo : kLeBufferInfo;
// Should register connection with ACL Data Channel.
auto conn0 = NewConnection(Connection::Role::kMaster, kHandle0);
auto conn1 = NewConnection(Connection::Role::kMaster, kHandle1);
size_t handle0_packet_count = 0;
size_t handle1_packet_count = 0;
auto data_callback = [&](const ByteBuffer& bytes) {
PacketView<hci::ACLDataHeader> packet(&bytes, bytes.size() - sizeof(ACLDataHeader));
ConnectionHandle connection_handle = le16toh(packet.header().handle_and_flags) & 0xFFF;
if (connection_handle == kHandle0) {
handle0_packet_count++;
} else {
ASSERT_EQ(kHandle1, connection_handle);
handle1_packet_count++;
}
};
test_device()->SetDataCallback(data_callback, dispatcher());
// Fill controller buffer.
for (size_t i = 0; i < kBufferInfo.max_num_packets(); i++) {
// Connection handle should have been registered with ACL Data Channel.
EXPECT_TRUE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle0, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
}
EXPECT_TRUE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle1, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
RunLoopUntilIdle();
EXPECT_EQ(handle0_packet_count, kBufferInfo.max_num_packets());
EXPECT_EQ(handle1_packet_count, 0u);
const auto disconnect_status_rsp = testing::DisconnectStatusResponsePacket();
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kHandle0), &disconnect_status_rsp);
conn0->Disconnect(StatusCode::kRemoteUserTerminatedConnection);
RunLoopUntilIdle();
// controller packet counts for |kHandle0| should not have been cleared after disconnect.
EXPECT_EQ(handle1_packet_count, 0u);
// Disconnection Complete handler should clear controller packet counts, so packet for |kHandle1|
// should be sent.
DynamicByteBuffer disconnection_complete(testing::DisconnectionCompletePacket(kHandle0));
test_device()->SendCommandChannelPacket(disconnection_complete);
RunLoopUntilIdle();
EXPECT_EQ(handle1_packet_count, 1u);
// Connection handle should have been unregistered with ACL Data Channel.
EXPECT_FALSE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle0, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
EXPECT_CMD_PACKET_OUT(test_device(), 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 Connection::LinkType ll_type = GetParam();
const ConnectionHandle kHandle0 = 0x0001;
const ConnectionHandle kHandle1 = 0x0002;
const auto& kBufferInfo =
ll_type == Connection::LinkType::kACL ? kBrEdrBufferInfo : kLeBufferInfo;
// Should register connection with ACL Data Channel.
auto conn0 = NewConnection(Connection::Role::kMaster, kHandle0);
auto conn1 = NewConnection(Connection::Role::kMaster, kHandle1);
size_t handle0_packet_count = 0;
size_t handle1_packet_count = 0;
auto data_callback = [&](const ByteBuffer& bytes) {
PacketView<hci::ACLDataHeader> packet(&bytes, bytes.size() - sizeof(ACLDataHeader));
ConnectionHandle connection_handle = le16toh(packet.header().handle_and_flags) & 0xFFF;
if (connection_handle == kHandle0) {
handle0_packet_count++;
} else {
ASSERT_EQ(kHandle1, connection_handle);
handle1_packet_count++;
}
};
test_device()->SetDataCallback(data_callback, dispatcher());
// Fill controller buffer.
for (size_t i = 0; i < kBufferInfo.max_num_packets(); i++) {
// Connection handle should have been registered with ACL Data Channel.
EXPECT_TRUE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle0, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
}
EXPECT_TRUE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle1, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
RunLoopUntilIdle();
EXPECT_EQ(handle0_packet_count, kBufferInfo.max_num_packets());
EXPECT_EQ(handle1_packet_count, 0u);
size_t disconn_cb_count = 0;
auto disconn_complete_cb = [&](const Connection* cb_conn, auto /*reason*/) {
ASSERT_TRUE(cb_conn);
EXPECT_EQ(kHandle0, cb_conn->handle());
disconn_cb_count++;
};
conn0->set_peer_disconnect_callback(disconn_complete_cb);
// Disconnection Complete handler should clear controller packet counts, so packet for |kHandle1|
// should be sent.
DynamicByteBuffer disconnection_complete(testing::DisconnectionCompletePacket(kHandle0));
test_device()->SendCommandChannelPacket(disconnection_complete);
RunLoopUntilIdle();
EXPECT_EQ(1u, disconn_cb_count);
// Connection handle should have been unregistered with ACL Data Channel.
EXPECT_FALSE(acl_data_channel()->SendPacket(
ACLDataPacket::New(kHandle0, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1),
l2cap::kInvalidChannelId, AclDataChannel::PacketPriority::kLow));
// Since controller packet count was cleared, packet for |kHandle1| should
// have been sent.
EXPECT_EQ(handle1_packet_count, 1u);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kHandle1));
}
TEST_F(HCI_ConnectionTest, StartEncryptionFailsAsLowEnergySlave) {
auto conn = NewLEConnection(Connection::Role::kSlave);
conn->set_le_ltk(LinkKey());
EXPECT_FALSE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_F(HCI_ConnectionTest, StartEncryptionSucceedsAsLowEnergyMaster) {
auto conn = NewLEConnection(Connection::Role::kMaster);
auto ltk = LinkKey();
conn->set_le_ltk(ltk);
EXPECT_TRUE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(), testing::LEStartEncryptionPacket(kTestHandle, ltk.rand(),
ltk.ediv(), ltk.value()));
}
TEST_F(HCI_ConnectionTest, StartEncryptionSucceedsWithBrEdrLinkKeyType) {
auto conn = NewACLConnection();
conn->set_bredr_link_key(LinkKey(), kLinkKeyType);
EXPECT_TRUE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(),
testing::SetConnectionEncryption(kTestHandle, /*enable=*/true));
}
TEST_P(LinkTypeConnectionTest, DisconnectError) {
// clang-format off
// HCI_Disconnect (handle: 0x0001, reason: RemoteUserTerminatedConnection)
auto req_bytes = CreateStaticByteBuffer(
0x06, 0x04, 0x03, 0x01, 0x00, StatusCode::kRemoteUserTerminatedConnection);
// Respond with Command Status and Disconnection Complete.
auto cmd_status_bytes = CreateStaticByteBuffer(
kCommandStatusEventCode, 0x04, StatusCode::kSuccess, 1, 0x06, 0x04);
auto disc_cmpl_bytes = CreateStaticByteBuffer(
kDisconnectionCompleteEventCode, 0x04,
StatusCode::kCommandDisallowed, 0x01, 0x00, StatusCode::kConnectionTerminatedByLocalHost);
// clang-format on
EXPECT_CMD_PACKET_OUT(test_device(), req_bytes, &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; },
dispatcher());
auto connection = NewConnection();
connection->Disconnect(StatusCode::kRemoteUserTerminatedConnection);
RunLoopUntilIdle();
EXPECT_TRUE(callback_called);
}
TEST_P(LinkTypeConnectionTest, StartEncryptionNoLinkKey) {
auto conn = NewConnection();
EXPECT_FALSE(conn->StartEncryption());
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
// HCI Command Status event is received with an error status.
TEST_F(HCI_ConnectionTest, LEStartEncryptionFailsAtStatus) {
// clang-format off
auto kExpectedCommand = CreateStaticByteBuffer(
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
);
auto kErrorStatus = CreateStaticByteBuffer(
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_le_ltk(LinkKey(kLTK, kRand, kEDiv));
conn->set_encryption_change_callback([&](Status status, bool enabled) {
EXPECT_FALSE(status);
EXPECT_FALSE(enabled);
EXPECT_EQ(StatusCode::kCommandDisallowed, status.protocol_error());
callback = true;
});
EXPECT_TRUE(conn->StartEncryption());
RunLoopUntilIdle();
EXPECT_TRUE(callback);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_F(HCI_ConnectionTest, LEStartEncryptionSendsSetLeConnectionEncryptionCommand) {
auto kExpectedCommand =
CreateStaticByteBuffer(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
);
auto kStatus = CreateStaticByteBuffer(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_le_ltk(LinkKey(kLTK, kRand, kEDiv));
conn->set_encryption_change_callback([&](Status status, bool enabled) { callback = true; });
EXPECT_TRUE(conn->StartEncryption());
// Callback shouldn't be called until the controller sends an encryption
// changed event.
RunLoopUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
// HCI Command Status event is received with an error status.
TEST_F(HCI_ConnectionTest, AclStartEncryptionFailsAtStatus) {
auto kExpectedCommand = CreateStaticByteBuffer(0x13, 0x04, // HCI_Set_Connection_Encryption
3, // parameter total size
0x01, 0x00, // connection handle
0x01 // encryption enable
);
auto kErrorStatus = CreateStaticByteBuffer(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_bredr_link_key(LinkKey(kLTK, 0, 0), kLinkKeyType);
conn->set_encryption_change_callback([&](Status status, bool enabled) {
EXPECT_FALSE(status);
EXPECT_FALSE(enabled);
EXPECT_EQ(StatusCode::kCommandDisallowed, status.protocol_error());
callback = true;
});
EXPECT_TRUE(conn->StartEncryption());
RunLoopUntilIdle();
EXPECT_TRUE(callback);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_F(HCI_ConnectionTest, AclStartEncryptionSendsSetConnectionEncryptionCommand) {
auto kExpectedCommand = CreateStaticByteBuffer(0x13, 0x04, // HCI_Set_Connection_Encryption
3, // parameter total size
0x01, 0x00, // connection handle
0x01 // encryption enable
);
auto kStatus = CreateStaticByteBuffer(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_bredr_link_key(LinkKey(kLTK, 0, 0), kLinkKeyType);
conn->set_encryption_change_callback([&](Status status, bool enabled) { callback = true; });
EXPECT_TRUE(conn->StartEncryption());
// Callback shouldn't be called until the controller sends an encryption changed event.
RunLoopUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_P(LinkTypeConnectionTest, EncryptionChangeIgnoredEvents) {
// clang-format off
auto kEncChangeMalformed = CreateStaticByteBuffer(
0x08, // HCI Encryption Change event code
3, // parameter total size
0x00, // status
0x01, 0x00 // connection handle: 1
// Last byte missing
);
auto kEncChangeWrongHandle = CreateStaticByteBuffer(
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([&](Status, bool) { callback = true; });
test_device()->SendCommandChannelPacket(kEncChangeMalformed);
test_device()->SendCommandChannelPacket(kEncChangeWrongHandle);
RunLoopUntilIdle();
EXPECT_FALSE(callback);
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
const auto kEncryptionChangeEventEnabled =
CreateStaticByteBuffer(0x08, // HCI Encryption Change event code
4, // parameter total size
0x00, // status
0x01, 0x00, // connection handle: 1
0x01 // encryption enabled
);
const auto kReadEncryptionKeySizeCommand =
CreateStaticByteBuffer(0x08, 0x14, // opcode: HCI_ReadEncryptionKeySize
0x02, // parameter size
0x01, 0x00 // connection handle: 0x0001
);
const auto kDisconnectCommand = CreateStaticByteBuffer(0x06, 0x04, // opcode: HCI_Disconnect
0x03, // parameter total size
0x01, 0x00, // handle: 1
0x05 // reason: authentication failure
);
TEST_P(LinkTypeConnectionTest, EncryptionChangeEvents) {
// clang-format off
auto kEncryptionChangeEventDisabled = CreateStaticByteBuffer(
0x08, // HCI Encryption Change event code
4, // parameter total size
0x00, // status
0x01, 0x00, // connection handle: 1
0x00 // encryption disabled
);
auto kEncryptionChangeEventFailed = CreateStaticByteBuffer(
0x08, // HCI Encryption Change event code
4, // parameter total size
0x06, // status: Pin or Key missing
0x01, 0x00, // connection handle: 1
0x00 // encryption disabled
);
auto kKeySizeComplete = CreateStaticByteBuffer(
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();
Status status(HostError::kFailed);
bool enabled = false;
conn->set_encryption_change_callback([&](Status cb_status, bool cb_enabled) {
callback_count++;
status = cb_status;
enabled = cb_enabled;
});
if (conn->ll_type() == Connection::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(kEncryptionChangeEventEnabled);
RunLoopUntilIdle();
EXPECT_EQ(1, callback_count);
EXPECT_TRUE(status);
EXPECT_TRUE(enabled);
test_device()->SendCommandChannelPacket(kEncryptionChangeEventDisabled);
RunLoopUntilIdle();
EXPECT_EQ(2, callback_count);
EXPECT_TRUE(status);
EXPECT_FALSE(enabled);
// The host should disconnect the link if encryption fails.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommand);
test_device()->SendCommandChannelPacket(kEncryptionChangeEventFailed);
RunLoopUntilIdle();
EXPECT_EQ(3, callback_count);
EXPECT_FALSE(status);
EXPECT_EQ(StatusCode::kPinOrKeyMissing, status.protocol_error());
}
TEST_F(HCI_ConnectionTest, EncryptionFailureNotifiesPeerDisconnectCallback) {
bool peer_disconnect_callback_received = false;
auto conn = NewLEConnection();
conn->set_peer_disconnect_callback([&](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(), kDisconnectCommand);
test_device()->SendCommandChannelPacket(testing::EncryptionChangeEventPacket(
hci::StatusCode::kConnectionTerminatedMICFailure, kTestHandle, EncryptionStatus::kOff));
RunLoopUntilIdle();
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(testing::DisconnectionCompletePacket(
kTestHandle, StatusCode::kConnectionTerminatedMICFailure));
RunLoopUntilIdle();
EXPECT_TRUE(peer_disconnect_callback_received);
}
TEST_F(HCI_ConnectionTest, AclEncryptionEnableCanNotReadKeySizeClosesLink) {
auto kKeySizeComplete = CreateStaticByteBuffer(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](Status status, bool enabled) {
callback_count++;
EXPECT_FALSE(status);
EXPECT_TRUE(enabled);
});
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommand);
test_device()->SendCommandChannelPacket(kEncryptionChangeEventEnabled);
RunLoopUntilIdle();
EXPECT_EQ(1, callback_count);
}
TEST_F(HCI_ConnectionTest, AclEncryptionEnableKeySizeOneByteClosesLink) {
auto kKeySizeComplete = CreateStaticByteBuffer(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](Status status, bool enabled) {
callback_count++;
EXPECT_FALSE(status);
EXPECT_TRUE(enabled);
});
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySizeCommand, &kKeySizeComplete);
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommand);
test_device()->SendCommandChannelPacket(kEncryptionChangeEventEnabled);
RunLoopUntilIdle();
EXPECT_EQ(1, callback_count);
}
TEST_P(LinkTypeConnectionTest, EncryptionKeyRefreshEvents) {
// clang-format off
auto kEncryptionKeyRefresh = CreateStaticByteBuffer(
0x30, // HCI Encryption Key Refresh Complete event
3, // parameter total size
0x00, // status
0x01, 0x00 // connection handle: 1
);
auto kEncryptionKeyRefreshFailed = CreateStaticByteBuffer(
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();
Status status(HostError::kFailed);
bool enabled = false;
conn->set_encryption_change_callback([&](Status cb_status, bool cb_enabled) {
callback_count++;
status = cb_status;
enabled = cb_enabled;
});
test_device()->SendCommandChannelPacket(kEncryptionKeyRefresh);
RunLoopUntilIdle();
EXPECT_EQ(1, callback_count);
EXPECT_TRUE(status);
EXPECT_TRUE(enabled);
// The host should disconnect the link if encryption fails.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnectCommand);
test_device()->SendCommandChannelPacket(kEncryptionKeyRefreshFailed);
RunLoopUntilIdle();
EXPECT_EQ(2, callback_count);
EXPECT_FALSE(status);
EXPECT_EQ(StatusCode::kPinOrKeyMissing, status.protocol_error());
EXPECT_FALSE(enabled);
}
TEST_F(HCI_ConnectionTest, LELongTermKeyRequestIgnoredEvent) {
// clang-format off
auto kMalformed = CreateStaticByteBuffer(
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
);
auto kWrongHandle = CreateStaticByteBuffer(
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_le_ltk(LinkKey(kLTK, 0, 0));
test_device()->SendCommandChannelPacket(kMalformed);
test_device()->SendCommandChannelPacket(kWrongHandle);
RunLoopUntilIdle();
// Test will fail if the connection sends a response without ignoring these
// events.
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kTestHandle));
}
TEST_F(HCI_ConnectionTest, LELongTermKeyRequestNoKey) {
// clang-format off
auto kEvent = CreateStaticByteBuffer(
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
);
auto kResponse = CreateStaticByteBuffer(
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);
RunLoopUntilIdle();
}
// There is a link key but EDiv and Rand values don't match.
TEST_F(HCI_ConnectionTest, LELongTermKeyRequestNoMatchinKey) {
// clang-format off
auto kEvent = CreateStaticByteBuffer(
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
);
auto kResponse = CreateStaticByteBuffer(
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_le_ltk(LinkKey(kLTK, 1, 1));
test_device()->SendCommandChannelPacket(kEvent);
RunLoopUntilIdle();
}
TEST_F(HCI_ConnectionTest, LELongTermKeyRequestReply) {
// clang-format off
auto kEvent = CreateStaticByteBuffer(
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
);
auto kResponse = CreateStaticByteBuffer(
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_le_ltk(LinkKey(kLTK, 0x8899AABBCCDDEEFF, 0xBEEF));
test_device()->SendCommandChannelPacket(kEvent);
RunLoopUntilIdle();
}
TEST_F(HCI_ConnectionTest,
QueuedPacketsGetDroppedOnDisconnectionCompleteAndStalePacketsAreNotSentOnHandleReuse) {
const ConnectionHandle kHandle = 0x0001;
// Should register connection with ACL Data Channel.
auto conn0 = NewACLConnection(Connection::Role::kMaster, kHandle);
testing::MockController::DataCallback data_cb = [](const ByteBuffer& packet) {};
size_t packet_count = 0;
auto data_cb_wrapper = [&data_cb, &packet_count](const ByteBuffer& packet) {
packet_count++;
ASSERT_EQ(packet.size(), sizeof(ACLDataHeader) + 1);
data_cb(packet);
};
test_device()->SetDataCallback(data_cb_wrapper, dispatcher());
const uint8_t payload0 = 0x01;
data_cb = [payload0](const ByteBuffer& packet) {
EXPECT_EQ(packet[sizeof(ACLDataHeader)], payload0);
};
// Fill controller buffer, + 1 packet in queue.
for (size_t i = 0; i < kBrEdrBufferInfo.max_num_packets() + 1; i++) {
auto packet = ACLDataPacket::New(kHandle, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1);
packet->mutable_view()->mutable_payload_bytes()[0] = payload0;
EXPECT_TRUE(acl_data_channel()->SendPacket(std::move(packet), l2cap::kInvalidChannelId,
AclDataChannel::PacketPriority::kLow));
}
// Run until the data is flushed out to the MockController.
RunLoopUntilIdle();
// Only packets that fit in buffer should have been received.
EXPECT_EQ(packet_count, kBrEdrBufferInfo.max_num_packets());
// All future packets received should be for the next connection.
const uint8_t payload1 = 0x02;
data_cb = [payload1](const ByteBuffer& packet) {
EXPECT_EQ(packet[sizeof(ACLDataHeader)], payload1);
};
const auto disconnect_status_rsp = testing::DisconnectStatusResponsePacket();
DynamicByteBuffer disconnection_complete(testing::DisconnectionCompletePacket(kHandle));
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kHandle), &disconnect_status_rsp,
&disconnection_complete);
// Disconnect |conn0| by destroying it. The received disconnection complete event will cause the
// handler to unregister the link and clear pending packets.
conn0.reset();
RunLoopUntilIdle();
// Register connection with same handle.
auto conn1 = NewACLConnection(Connection::Role::kMaster, kHandle);
// Fill controller buffer, + 1 packet in queue.
for (size_t i = 0; i < kBrEdrBufferInfo.max_num_packets(); i++) {
auto packet = ACLDataPacket::New(kHandle, ACLPacketBoundaryFlag::kFirstNonFlushable,
ACLBroadcastFlag::kPointToPoint, 1);
packet->mutable_view()->mutable_payload_bytes()[0] = payload1;
EXPECT_TRUE(acl_data_channel()->SendPacket(std::move(packet), l2cap::kInvalidChannelId,
AclDataChannel::PacketPriority::kLow));
}
RunLoopUntilIdle();
EXPECT_EQ(packet_count, 2 * kBrEdrBufferInfo.max_num_packets());
conn1.reset();
EXPECT_CMD_PACKET_OUT(test_device(), testing::DisconnectPacket(kHandle), &disconnect_status_rsp,
&disconnection_complete);
RunLoopUntilIdle();
}
TEST_F(HCI_ConnectionTest, PeerDisconnectCallback) {
const ConnectionHandle kHandle = 0x0001;
auto conn = NewACLConnection(Connection::Role::kMaster, kHandle);
size_t cb_count = 0;
auto disconn_complete_cb = [&](const Connection* cb_conn, auto /*reason*/) {
ASSERT_TRUE(cb_conn);
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);
RunLoopUntilIdle();
EXPECT_EQ(0u, cb_count);
DynamicByteBuffer disconnection_complete(testing::DisconnectionCompletePacket(kHandle));
test_device()->SendCommandChannelPacket(disconnection_complete);
RunLoopUntilIdle();
EXPECT_EQ(1u, cb_count);
}
// Test connection handling cases for all types of links.
INSTANTIATE_TEST_SUITE_P(HCI_ConnectionTest, LinkTypeConnectionTest,
::testing::Values(Connection::LinkType::kACL, Connection::LinkType::kLE));
} // namespace
} // namespace bt::hci