blob: 0034971ff532d251165bed8ecdc75d9d3428d52e [file] [log] [blame]
// Copyright 2018 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/gap/bredr_connection_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/constants.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-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_l2cap.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/sdp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect.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/error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/fake_acl_connection.h"
#include "src/connectivity/bluetooth/lib/cpp-string/string_printf.h"
namespace bt::gap {
namespace {
using namespace inspect::testing;
using bt::testing::CommandTransaction;
using pw::bluetooth::emboss::AuthenticationRequirements;
using pw::bluetooth::emboss::IoCapability;
using TestingBase =
bt::testing::FakeDispatcherControllerTest<bt::testing::MockController>;
constexpr hci_spec::ConnectionHandle kConnectionHandle = 0x0BAA;
constexpr hci_spec::ConnectionHandle kConnectionHandle2 = 0x0BAB;
const DeviceAddress kLocalDevAddr(DeviceAddress::Type::kBREDR, {0});
const DeviceAddress kTestDevAddr(DeviceAddress::Type::kBREDR, {1});
const DeviceAddress kTestDevAddrLe(DeviceAddress::Type::kLEPublic, {2});
const DeviceAddress kTestDevAddr2(DeviceAddress::Type::kBREDR, {3});
constexpr uint32_t kPasskey = 123456;
const hci_spec::LinkKey kRawKey({0xc0,
0xde,
0xfa,
0x57,
0x4b,
0xad,
0xf0,
0x0d,
0xa7,
0x60,
0x06,
0x1e,
0xca,
0x1e,
0xca,
0xfe},
0,
0);
const sm::LTK kLinkKey(
sm::SecurityProperties(hci_spec::LinkKeyType::kAuthenticatedCombination192),
kRawKey);
constexpr BrEdrSecurityRequirements kNoSecurityRequirements{
.authentication = false, .secure_connections = false};
constexpr BrEdrSecurityRequirements kAuthSecurityRequirements{
.authentication = true, .secure_connections = false};
// A default size for PDUs when generating responses for testing.
const uint16_t PDU_MAX = 0xFFF;
#define TEST_DEV_ADDR_BYTES_LE 0x01, 0x00, 0x00, 0x00, 0x00, 0x00
const StaticByteBuffer kReadScanEnable(LowerBits(hci_spec::kReadScanEnable),
UpperBits(hci_spec::kReadScanEnable),
0x00 // No parameters
);
#define READ_SCAN_ENABLE_RSP(scan_enable) \
StaticByteBuffer(hci_spec::kCommandCompleteEventCode, \
0x05, \
0xF0, \
LowerBits(hci_spec::kReadScanEnable), \
UpperBits(hci_spec::kReadScanEnable), \
pw::bluetooth::emboss::StatusCode::SUCCESS, \
(scan_enable))
const auto kReadScanEnableRspNone = READ_SCAN_ENABLE_RSP(0x00);
const auto kReadScanEnableRspInquiry = READ_SCAN_ENABLE_RSP(0x01);
const auto kReadScanEnableRspPage = READ_SCAN_ENABLE_RSP(0x02);
const auto kReadScanEnableRspBoth = READ_SCAN_ENABLE_RSP(0x03);
#undef READ_SCAN_ENABLE_RSP
#define WRITE_SCAN_ENABLE_CMD(scan_enable) \
StaticByteBuffer(LowerBits(hci_spec::kWriteScanEnable), \
UpperBits(hci_spec::kWriteScanEnable), \
0x01, \
(scan_enable))
const auto kWriteScanEnableNone = WRITE_SCAN_ENABLE_CMD(0x00);
const auto kWriteScanEnableInq = WRITE_SCAN_ENABLE_CMD(0x01);
const auto kWriteScanEnablePage = WRITE_SCAN_ENABLE_CMD(0x02);
const auto kWriteScanEnableBoth = WRITE_SCAN_ENABLE_CMD(0x03);
#undef WRITE_SCAN_ENABLE_CMD
#define COMMAND_COMPLETE_RSP(opcode) \
StaticByteBuffer(hci_spec::kCommandCompleteEventCode, \
0x04, \
0xF0, \
LowerBits((opcode)), \
UpperBits((opcode)), \
pw::bluetooth::emboss::StatusCode::SUCCESS)
const auto kWriteScanEnableRsp =
COMMAND_COMPLETE_RSP(hci_spec::kWriteScanEnable);
const StaticByteBuffer kWritePageScanActivity(
LowerBits(hci_spec::kWritePageScanActivity),
UpperBits(hci_spec::kWritePageScanActivity),
0x04, // parameter_total_size (4 bytes)
0x00,
0x08, // 1.28s interval (R1)
0x11,
0x00 // 10.625ms window (R1)
);
const auto kWritePageScanActivityRsp =
COMMAND_COMPLETE_RSP(hci_spec::kWritePageScanActivity);
const StaticByteBuffer kWritePageScanType(
LowerBits(hci_spec::kWritePageScanType),
UpperBits(hci_spec::kWritePageScanType),
0x01, // parameter_total_size (1 byte)
0x01 // Interlaced scan
);
const auto kWritePageScanTypeRsp =
COMMAND_COMPLETE_RSP(hci_spec::kWritePageScanType);
#define COMMAND_STATUS_RSP(opcode, statuscode) \
StaticByteBuffer(hci_spec::kCommandStatusEventCode, \
0x04, \
(statuscode), \
0xF0, \
LowerBits((opcode)), \
UpperBits((opcode)))
const auto kWritePageTimeoutRsp =
COMMAND_COMPLETE_RSP(hci_spec::kWritePageTimeout);
const auto kConnectionRequest = testing::ConnectionRequestPacket(kTestDevAddr);
const auto kAcceptConnectionRequest =
testing::AcceptConnectionRequestPacket(kTestDevAddr);
const auto kAcceptConnectionRequestRsp =
COMMAND_STATUS_RSP(hci_spec::kAcceptConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kConnectionComplete =
testing::ConnectionCompletePacket(kTestDevAddr, kConnectionHandle);
const auto kConnectionCompletePageTimeout = testing::ConnectionCompletePacket(
kTestDevAddr,
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::PAGE_TIMEOUT);
const StaticByteBuffer kConnectionCompleteError(
hci_spec::kConnectionCompleteEventCode,
0x0B, // parameter_total_size (11 byte payload)
pw::bluetooth::emboss::StatusCode::
CONNECTION_FAILED_TO_BE_ESTABLISHED, // status
0x00,
0x00, // connection_handle
TEST_DEV_ADDR_BYTES_LE, // peer address
0x01, // link_type (ACL)
0x00 // encryption not enabled
);
const StaticByteBuffer kConnectionCompleteCanceled(
hci_spec::kConnectionCompleteEventCode,
0x0B, // parameter_total_size (11 byte payload)
pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID, // status
0x00,
0x00, // connection_handle
TEST_DEV_ADDR_BYTES_LE, // peer address
0x01, // link_type (ACL)
0x00 // encryption not enabled
);
const StaticByteBuffer kCreateConnection(
LowerBits(hci_spec::kCreateConnection),
UpperBits(hci_spec::kCreateConnection),
0x0d, // parameter_total_size (13 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
LowerBits(hci::kEnableAllPacketTypes), // allowable packet types
UpperBits(hci::kEnableAllPacketTypes), // allowable packet types
0x02, // page_scan_repetition_mode (R2)
0x00, // reserved
0x00,
0x00, // clock_offset
0x00 // allow_role_switch (don't)
);
const auto kCreateConnectionRsp = COMMAND_STATUS_RSP(
hci_spec::kCreateConnection, pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kCreateConnectionRspError = COMMAND_STATUS_RSP(
hci_spec::kCreateConnection,
pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED);
const StaticByteBuffer kCreateConnectionCancel(
LowerBits(hci_spec::kCreateConnectionCancel),
UpperBits(hci_spec::kCreateConnectionCancel),
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kCreateConnectionCancelRsp =
COMMAND_COMPLETE_RSP(hci_spec::kCreateConnectionCancel);
const StaticByteBuffer kRemoteNameRequest(
LowerBits(hci_spec::kRemoteNameRequest),
UpperBits(hci_spec::kRemoteNameRequest),
0x0a, // parameter_total_size (10 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
0x00, // page_scan_repetition_mode (R0)
0x00, // reserved
0x00,
0x00 // clock_offset
);
const auto kRemoteNameRequestRsp = COMMAND_STATUS_RSP(
hci_spec::kRemoteNameRequest, pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kRemoteNameRequestComplete =
testing::RemoteNameRequestCompletePacket(
kTestDevAddr,
{'F', 'u', 'c', 'h', 's', 'i', 'a', '\xF0', '\x9F',
'\x92', '\x96', '\x00', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19',
'\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '\x20'}
// remote name (Fuchsia💖)
// Everything after the 0x00 should be ignored.
);
const StaticByteBuffer kReadRemoteVersionInfo(
LowerBits(hci_spec::kReadRemoteVersionInfo),
UpperBits(hci_spec::kReadRemoteVersionInfo),
0x02, // Parameter_total_size (2 bytes)
0xAA,
0x0B // connection_handle
);
const auto kReadRemoteVersionInfoRsp =
COMMAND_STATUS_RSP(hci_spec::kReadRemoteVersionInfo,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kRemoteVersionInfoComplete = StaticByteBuffer(
hci_spec::kReadRemoteVersionInfoCompleteEventCode,
0x08, // parameter_total_size (8 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle
pw::bluetooth::emboss::CoreSpecificationVersion::V4_2, // version
0xE0,
0x00, // company_identifier (Google)
0xAD,
0xDE // subversion (anything)
);
const auto kReadRemoteSupportedFeaturesRsp =
COMMAND_STATUS_RSP(hci_spec::kReadRemoteSupportedFeatures,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kReadRemoteSupportedFeaturesComplete =
StaticByteBuffer(hci_spec::kReadRemoteSupportedFeaturesCompleteEventCode,
0x0B, // parameter_total_size (11 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle,
0xFF,
0x00,
0x00,
0x00,
0x40,
0x00,
0x00,
0x80
// lmp_features_page0: 3 slot packets, 5 slot packets,
// Encryption, Slot Offset, Timing Accuracy, Role Switch,
// Hold Mode, Sniff Mode, LE Supported, Extended Features
);
const auto kReadRemoteExtended1 =
StaticByteBuffer(LowerBits(hci_spec::kReadRemoteExtendedFeatures),
UpperBits(hci_spec::kReadRemoteExtendedFeatures),
0x03, // parameter_total_size (3 bytes)
0xAA,
0x0B, // connection_handle
0x01 // page_number (1)
);
const auto kReadRemoteExtendedFeaturesRsp =
COMMAND_STATUS_RSP(hci_spec::kReadRemoteExtendedFeatures,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kReadRemoteExtended1Complete = StaticByteBuffer(
hci_spec::kReadRemoteExtendedFeaturesCompleteEventCode,
0x0D, // parameter_total_size (13 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle,
0x01, // page_number
0x02, // max_page_number
0x0F,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
// lmp_features_page1: Secure Simple Pairing (Host Support), LE Supported
// (Host), Previously Used, Secure Connections (Host Support)
);
const auto kReadRemoteExtended2 =
StaticByteBuffer(LowerBits(hci_spec::kReadRemoteExtendedFeatures),
UpperBits(hci_spec::kReadRemoteExtendedFeatures),
0x03, // parameter_total_size (3 bytes)
0xAA,
0x0B, // connection_handle
0x02 // page_number (2)
);
const auto kReadRemoteExtended2Complete =
StaticByteBuffer(hci_spec::kReadRemoteExtendedFeaturesCompleteEventCode,
0x0D, // parameter_total_size (13 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle,
0x02, // page_number
0x02, // max_page_number
0x00,
0x00,
0x00,
0x00,
0x02,
0x00,
0xFF,
0x00
// lmp_features_page2 - All the bits should be ignored.
);
const auto kDisconnect = testing::DisconnectPacket(kConnectionHandle);
const auto kDisconnectRsp = COMMAND_STATUS_RSP(
hci_spec::kDisconnect, pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kDisconnectionComplete = testing::DisconnectionCompletePacket(
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION);
const auto kAuthenticationRequested =
testing::AuthenticationRequestedPacket(kConnectionHandle);
const auto kAuthenticationRequestedStatus =
COMMAND_STATUS_RSP(hci_spec::kAuthenticationRequested,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const auto kAuthenticationComplete =
StaticByteBuffer(hci_spec::kAuthenticationCompleteEventCode,
0x03, // parameter_total_size (3 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B // connection_handle
);
const auto kAuthenticationCompleteFailed = StaticByteBuffer(
hci_spec::kAuthenticationCompleteEventCode,
0x03, // parameter_total_size (3 bytes)
pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED, // status
0xAA,
0x0B // connection_handle
);
const StaticByteBuffer kLinkKeyRequest(hci_spec::kLinkKeyRequestEventCode,
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kLinkKeyRequestNegativeReply =
StaticByteBuffer(LowerBits(hci_spec::kLinkKeyRequestNegativeReply),
UpperBits(hci_spec::kLinkKeyRequestNegativeReply),
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kLinkKeyRequestNegativeReplyRsp =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kLinkKeyRequestNegativeReply),
UpperBits(hci_spec::kLinkKeyRequestNegativeReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE // peer address
);
auto MakeIoCapabilityResponse(IoCapability io_cap,
AuthenticationRequirements auth_req) {
return StaticByteBuffer(hci_spec::kIOCapabilityResponseEventCode,
0x09, // parameter_total_size (9 bytes)
TEST_DEV_ADDR_BYTES_LE, // address
io_cap,
0x00, // OOB authentication data not present
auth_req);
}
const StaticByteBuffer kIoCapabilityRequest(
hci_spec::kIOCapabilityRequestEventCode,
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // address
);
auto MakeIoCapabilityRequestReply(IoCapability io_cap,
AuthenticationRequirements auth_req) {
return StaticByteBuffer(LowerBits(hci_spec::kIOCapabilityRequestReply),
UpperBits(hci_spec::kIOCapabilityRequestReply),
0x09, // parameter_total_size (9 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
io_cap,
0x00, // No OOB data present
auth_req);
}
const StaticByteBuffer kIoCapabilityRequestReplyRsp(
hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kIOCapabilityRequestReply),
UpperBits(hci_spec::kIOCapabilityRequestReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kIoCapabilityRequestNegativeReply =
StaticByteBuffer(LowerBits(hci_spec::kIOCapabilityRequestNegativeReply),
UpperBits(hci_spec::kIOCapabilityRequestNegativeReply),
0x07, // parameter_total_size (7 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED);
const auto kIoCapabilityRequestNegativeReplyRsp =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kIOCapabilityRequestNegativeReply),
UpperBits(hci_spec::kIOCapabilityRequestNegativeReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE); // peer address
auto MakeUserConfirmationRequest(uint32_t passkey) {
const auto passkey_bytes = ToBytes(kPasskey);
return StaticByteBuffer(hci_spec::kUserConfirmationRequestEventCode,
0x0A, // parameter_total_size (10 byte payload)
TEST_DEV_ADDR_BYTES_LE, // peer address
passkey_bytes[0],
passkey_bytes[1],
passkey_bytes[2],
0x00 // numeric value
);
}
const auto kUserConfirmationRequestReply =
StaticByteBuffer(LowerBits(hci_spec::kUserConfirmationRequestReply),
UpperBits(hci_spec::kUserConfirmationRequestReply),
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kUserConfirmationRequestReplyRsp =
COMMAND_COMPLETE_RSP(hci_spec::kUserConfirmationRequestReply);
const auto kUserConfirmationRequestNegativeReply =
StaticByteBuffer(LowerBits(hci_spec::kUserConfirmationRequestNegativeReply),
UpperBits(hci_spec::kUserConfirmationRequestNegativeReply),
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kUserConfirmationRequestNegativeReplyRsp =
COMMAND_COMPLETE_RSP(hci_spec::kUserConfirmationRequestNegativeReply);
const auto kSimplePairingCompleteSuccess =
StaticByteBuffer(hci_spec::kSimplePairingCompleteEventCode,
0x07, // parameter_total_size (7 byte payload)
0x00, // status (success)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kSimplePairingCompleteError =
StaticByteBuffer(hci_spec::kSimplePairingCompleteEventCode,
0x07, // parameter_total_size (7 byte payload)
0x05, // status (authentication failure)
TEST_DEV_ADDR_BYTES_LE // peer address
);
DynamicByteBuffer MakeLinkKeyNotification(hci_spec::LinkKeyType key_type) {
return DynamicByteBuffer(StaticByteBuffer(
hci_spec::kLinkKeyNotificationEventCode,
0x17, // parameter_total_size (17 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
0xc0,
0xde,
0xfa,
0x57,
0x4b,
0xad,
0xf0,
0x0d,
0xa7,
0x60,
0x06,
0x1e,
0xca,
0x1e,
0xca,
0xfe, // link key
static_cast<uint8_t>(key_type) // key type
));
}
const auto kLinkKeyNotification = MakeLinkKeyNotification(
hci_spec::LinkKeyType::kAuthenticatedCombination192);
const StaticByteBuffer kLinkKeyRequestReply(
LowerBits(hci_spec::kLinkKeyRequestReply),
UpperBits(hci_spec::kLinkKeyRequestReply),
0x16, // parameter_total_size (22 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
0xc0,
0xde,
0xfa,
0x57,
0x4b,
0xad,
0xf0,
0x0d,
0xa7,
0x60,
0x06,
0x1e,
0xca,
0x1e,
0xca,
0xfe // link key
);
const StaticByteBuffer kLinkKeyRequestReplyRsp(
hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kLinkKeyRequestReply),
UpperBits(hci_spec::kLinkKeyRequestReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kLinkKeyNotificationChanged =
StaticByteBuffer(hci_spec::kLinkKeyNotificationEventCode,
0x17, // parameter_total_size (17 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
0xfa,
0xce,
0xb0,
0x0c,
0xa5,
0x1c,
0xcd,
0x15,
0xea,
0x5e,
0xfe,
0xdb,
0x1d,
0x0d,
0x0a,
0xd5, // link key
0x06 // key type (Changed Combination Key)
);
const StaticByteBuffer kSetConnectionEncryption(
LowerBits(hci_spec::kSetConnectionEncryption),
UpperBits(hci_spec::kSetConnectionEncryption),
0x03, // parameter total size
0xAA,
0x0B, // connection handle
0x01 // encryption enable
);
const auto kSetConnectionEncryptionRsp =
COMMAND_STATUS_RSP(hci_spec::kSetConnectionEncryption,
pw::bluetooth::emboss::StatusCode::SUCCESS);
const StaticByteBuffer kEncryptionChangeEvent(
hci_spec::kEncryptionChangeEventCode,
4, // parameter total size
0x00, // status
0xAA,
0x0B, // connection handle
0x01 // encryption enabled: E0 for BR/EDR or AES-CCM for LE
);
const StaticByteBuffer kReadEncryptionKeySize(
LowerBits(hci_spec::kReadEncryptionKeySize),
UpperBits(hci_spec::kReadEncryptionKeySize),
0x02, // parameter size
0xAA,
0x0B // connection handle
);
const StaticByteBuffer kReadEncryptionKeySizeRsp(
hci_spec::kCommandCompleteEventCode,
0x07, // parameters total size
0xFF, // num command packets allowed (255)
LowerBits(hci_spec::kReadEncryptionKeySize),
UpperBits(hci_spec::kReadEncryptionKeySize),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection handle
0x10 // encryption key size: 16
);
auto MakeUserPasskeyRequestReply(uint32_t passkey) {
const auto passkey_bytes = ToBytes(kPasskey);
return StaticByteBuffer(LowerBits(hci_spec::kUserPasskeyRequestReply),
UpperBits(hci_spec::kUserPasskeyRequestReply),
0x0A, // parameter_total_size (10 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
passkey_bytes[0],
passkey_bytes[1],
passkey_bytes[2],
0x00 // numeric value
);
}
const StaticByteBuffer kUserPasskeyRequestReplyRsp(
hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kUserPasskeyRequestReply),
UpperBits(hci_spec::kUserPasskeyRequestReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE // peer address
);
auto MakeUserPasskeyNotification(uint32_t passkey) {
const auto passkey_bytes = ToBytes(kPasskey);
return StaticByteBuffer(hci_spec::kUserPasskeyNotificationEventCode,
0x0A, // parameter_total_size (10 byte payload)
TEST_DEV_ADDR_BYTES_LE, // peer address
passkey_bytes[0],
passkey_bytes[1],
passkey_bytes[2],
0x00 // numeric value
);
}
const hci::DataBufferInfo kBrEdrBufferInfo(1024, 1);
const hci::DataBufferInfo kLeBufferInfo(1024, 1);
constexpr l2cap::ChannelParameters kChannelParams;
::testing::AssertionResult IsInitializing(Peer* peer) {
if (Peer::ConnectionState::kInitializing !=
peer->bredr()->connection_state()) {
return ::testing::AssertionFailure()
<< "Expected peer connection_state: kInitializing, found "
<< Peer::ConnectionStateToString(peer->bredr()->connection_state());
}
return ::testing::AssertionSuccess()
<< "Peer connection state is kInitializing";
}
::testing::AssertionResult IsConnected(Peer* peer) {
if (Peer::ConnectionState::kConnected != peer->bredr()->connection_state()) {
return ::testing::AssertionFailure()
<< "Expected peer to be in a connected state: kConnected, found "
<< Peer::ConnectionStateToString(peer->bredr()->connection_state());
}
if (peer->temporary()) {
return ::testing::AssertionFailure()
<< "Expected peer to be non-temporary, but found temporary";
}
return ::testing::AssertionSuccess() << "Peer connection state is kConnected";
}
::testing::AssertionResult IsNotConnected(Peer* peer) {
if (Peer::ConnectionState::kNotConnected !=
peer->bredr()->connection_state()) {
return ::testing::AssertionFailure()
<< "Expected peer connection_state: kNotConnected, found "
<< Peer::ConnectionStateToString(peer->bredr()->connection_state());
}
return ::testing::AssertionSuccess()
<< "Peer connection state is kNotConnected";
;
}
::testing::AssertionResult HasConnectionTo(Peer* peer, BrEdrConnection* conn) {
if (!conn) {
return ::testing::AssertionFailure()
<< "Expected BrEdrConnection, but found nullptr";
}
if (peer->identifier() != conn->peer_id()) {
return ::testing::AssertionFailure()
<< "Expected connection peer_id " << peer->identifier()
<< " but found " << conn->peer_id();
}
return ::testing::AssertionSuccess() << "Peer " << peer->identifier()
<< " connected to the connection given";
}
#define CALLBACK_EXPECT_FAILURE(status_param) \
([&status_param](auto cb_status, auto conn_ref) { \
EXPECT_FALSE(conn_ref); \
status_param = cb_status; \
})
class BrEdrConnectionManagerTest : public TestingBase {
public:
BrEdrConnectionManagerTest() = default;
~BrEdrConnectionManagerTest() override = default;
void SetUp() override {
TestingBase::SetUp();
InitializeACLDataChannel(kBrEdrBufferInfo, kLeBufferInfo);
peer_cache_ = std::make_unique<PeerCache>(dispatcher());
l2cap_ = std::make_unique<l2cap::testing::FakeL2cap>(dispatcher());
// Respond to BrEdrConnectionManager controller setup with success.
EXPECT_CMD_PACKET_OUT(test_device(),
testing::WritePageTimeoutPacket(static_cast<uint16_t>(
pw::bluetooth::emboss::PageTimeout::DEFAULT)),
&kWritePageTimeoutRsp);
connection_manager_ = std::make_unique<BrEdrConnectionManager>(
transport()->GetWeakPtr(),
peer_cache_.get(),
kLocalDevAddr,
l2cap_.get(),
/*use_interlaced_scan=*/true,
/*local_secure_connections_supported=*/true,
dispatcher());
RunUntilIdle();
test_device()->SetTransactionCallback([this] { transaction_count_++; });
}
void TearDown() override {
int expected_transaction_count = transaction_count();
if (connection_manager_ != nullptr) {
expected_transaction_count += 2;
// deallocating the connection manager disables connectivity.
EXPECT_CMD_PACKET_OUT(
test_device(), kReadScanEnable, &kReadScanEnableRspBoth);
EXPECT_CMD_PACKET_OUT(
test_device(), kWriteScanEnableInq, &kWriteScanEnableRsp);
connection_manager_ = nullptr;
}
RunUntilIdle();
// A disconnection may also occur for a queued disconnection, allow up to 1
// extra transaction.
EXPECT_LE(expected_transaction_count, transaction_count());
EXPECT_GE(expected_transaction_count + 1, transaction_count());
// Don't trigger the transaction callback for the rest.
test_device()->ClearTransactionCallback();
test_device()->Stop();
l2cap_ = nullptr;
peer_cache_ = nullptr;
TestingBase::TearDown();
}
inspect::Inspector inspector() { return inspector_; }
protected:
static constexpr const int kShortInterrogationTransactions = 3;
static constexpr const int kInterrogationTransactions =
kShortInterrogationTransactions + 2;
static constexpr const int kIncomingConnTransactions =
1 + kInterrogationTransactions;
static constexpr const int kDisconnectionTransactions = 1;
// Currently unused, for reference:
// static constexpr const int kIncomingConnShortTransactions = 1 +
// kShortInterrogationTransactions;
BrEdrConnectionManager* connmgr() const { return connection_manager_.get(); }
void SetConnectionManager(std::unique_ptr<BrEdrConnectionManager> mgr) {
connection_manager_ = std::move(mgr);
}
PeerCache* peer_cache() const { return peer_cache_.get(); }
l2cap::testing::FakeL2cap* l2cap() const { return l2cap_.get(); }
int transaction_count() const { return transaction_count_; }
// Expect an incoming connection that is accepted.
void QueueSuccessfulAccept(
DeviceAddress addr = kTestDevAddr,
hci_spec::ConnectionHandle handle = kConnectionHandle,
std::optional<pw::bluetooth::emboss::ConnectionRole> role_change =
std::nullopt) const {
const auto connection_complete =
testing::ConnectionCompletePacket(addr, handle);
if (role_change) {
const auto role_change_event =
testing::RoleChangePacket(addr, role_change.value());
EXPECT_CMD_PACKET_OUT(test_device(),
testing::AcceptConnectionRequestPacket(addr),
&kAcceptConnectionRequestRsp,
&role_change_event,
&connection_complete);
} else {
EXPECT_CMD_PACKET_OUT(test_device(),
testing::AcceptConnectionRequestPacket(addr),
&kAcceptConnectionRequestRsp,
&connection_complete);
}
}
// Add expectations and simulated responses for the outbound commands sent
// after an inbound Connection Request Event is received, for a peer that is
// already interrogated. Results in kIncomingConnShortTransactions
// transaction.
void QueueRepeatIncomingConn(
DeviceAddress addr = kTestDevAddr,
hci_spec::ConnectionHandle handle = kConnectionHandle,
std::optional<pw::bluetooth::emboss::ConnectionRole> role_change =
std::nullopt) const {
QueueSuccessfulAccept(addr, handle, role_change);
QueueShortInterrogation(handle);
}
// Add expectations and simulated responses for the outbound commands sent
// after an inbound Connection Request Event is received, for a peer that is
// already interrogated.
// Results in |kIncomingConnTransactions| transactions.
void QueueSuccessfulIncomingConn(
DeviceAddress addr = kTestDevAddr,
hci_spec::ConnectionHandle handle = kConnectionHandle,
std::optional<pw::bluetooth::emboss::ConnectionRole> role_change =
std::nullopt) const {
QueueSuccessfulAccept(addr, handle, role_change);
QueueSuccessfulInterrogation(addr, handle);
}
void QueueSuccessfulCreateConnection(Peer* peer,
hci_spec::ConnectionHandle conn) const {
const DynamicByteBuffer complete_packet =
testing::ConnectionCompletePacket(peer->address(), conn);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::CreateConnectionPacket(peer->address()),
&kCreateConnectionRsp,
&complete_packet);
}
void QueueShortInterrogation(hci_spec::ConnectionHandle conn) const {
const DynamicByteBuffer remote_extended1_complete_packet =
testing::ReadRemoteExtended1CompletePacket(conn);
const DynamicByteBuffer remote_extended2_complete_packet =
testing::ReadRemoteExtended2CompletePacket(conn);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteExtended1Packet(conn),
&kReadRemoteExtendedFeaturesRsp,
&remote_extended1_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteExtended2Packet(conn),
&kReadRemoteExtendedFeaturesRsp,
&remote_extended2_complete_packet);
}
void QueueSuccessfulInterrogation(DeviceAddress addr,
hci_spec::ConnectionHandle conn) const {
const DynamicByteBuffer remote_name_complete_packet =
testing::RemoteNameRequestCompletePacket(addr);
const DynamicByteBuffer remote_version_complete_packet =
testing::ReadRemoteVersionInfoCompletePacket(conn);
const DynamicByteBuffer remote_supported_complete_packet =
testing::ReadRemoteSupportedFeaturesCompletePacket(
conn, /*extended_features=*/true);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::RemoteNameRequestPacket(addr),
&kRemoteNameRequestRsp,
&remote_name_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteVersionInfoPacket(conn),
&kReadRemoteVersionInfoRsp,
&remote_version_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteSupportedFeaturesPacket(conn),
&kReadRemoteSupportedFeaturesRsp,
&remote_supported_complete_packet);
QueueShortInterrogation(conn);
}
// Queue all interrogation packets except for the remote extended complete
// packet 2.
void QueueIncompleteInterrogation(DeviceAddress addr,
hci_spec::ConnectionHandle conn) const {
const DynamicByteBuffer remote_name_complete_packet =
testing::RemoteNameRequestCompletePacket(addr);
const DynamicByteBuffer remote_version_complete_packet =
testing::ReadRemoteVersionInfoCompletePacket(conn);
const DynamicByteBuffer remote_supported_complete_packet =
testing::ReadRemoteSupportedFeaturesCompletePacket(
conn, /*extended_features=*/true);
const DynamicByteBuffer remote_extended1_complete_packet =
testing::ReadRemoteExtended1CompletePacket(conn);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::RemoteNameRequestPacket(addr),
&kRemoteNameRequestRsp,
&remote_name_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteVersionInfoPacket(conn),
&kReadRemoteVersionInfoRsp,
&remote_version_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteSupportedFeaturesPacket(conn),
&kReadRemoteSupportedFeaturesRsp,
&remote_supported_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteExtended1Packet(conn),
&kReadRemoteExtendedFeaturesRsp,
&remote_extended1_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteExtended2Packet(conn),
&kReadRemoteExtendedFeaturesRsp);
}
// Completes an interrogation started with QueueIncompleteInterrogation.
void CompleteInterrogation(hci_spec::ConnectionHandle conn) {
const DynamicByteBuffer remote_extended2_complete_packet =
testing::ReadRemoteExtended2CompletePacket(conn);
test_device()->SendCommandChannelPacket(remote_extended2_complete_packet);
}
void QueueSuccessfulPairing(
hci_spec::LinkKeyType key_type =
hci_spec::LinkKeyType::kAuthenticatedCombination192) {
EXPECT_CMD_PACKET_OUT(test_device(),
kAuthenticationRequested,
&kAuthenticationRequestedStatus,
&kLinkKeyRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp,
&kIoCapabilityRequest);
const auto kIoCapabilityResponse = MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kIoCapabilityResponse,
&kUserConfirmationRequest);
const auto kLinkKeyNotificationWithKeyType =
MakeLinkKeyNotification(key_type);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotificationWithKeyType,
&kAuthenticationComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
}
// Use when pairing with no IO, where authenticated pairing is not possible.
void QueueSuccessfulUnauthenticatedPairing(
hci_spec::LinkKeyType key_type =
hci_spec::LinkKeyType::kUnauthenticatedCombination192) {
EXPECT_CMD_PACKET_OUT(test_device(),
kAuthenticationRequested,
&kAuthenticationRequestedStatus,
&kLinkKeyRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp,
&kIoCapabilityRequest);
const auto kIoCapabilityReply = MakeIoCapabilityRequestReply(
IoCapability::NO_INPUT_NO_OUTPUT,
AuthenticationRequirements::GENERAL_BONDING);
const auto kIoCapabilityResponse =
MakeIoCapabilityResponse(IoCapability::NO_INPUT_NO_OUTPUT,
AuthenticationRequirements::GENERAL_BONDING);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
kIoCapabilityReply,
&kIoCapabilityRequestReplyRsp,
&kIoCapabilityResponse,
&kUserConfirmationRequest);
const auto kLinkKeyNotificationWithKeyType =
MakeLinkKeyNotification(key_type);
// User Confirmation Request Reply will be automatic due to no IO.
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotificationWithKeyType,
&kAuthenticationComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
}
void QueueDisconnection(
hci_spec::ConnectionHandle conn,
pw::bluetooth::emboss::StatusCode reason =
pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION)
const {
const DynamicByteBuffer disconnect_complete =
testing::DisconnectionCompletePacket(conn, reason);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(conn, reason),
&kDisconnectRsp,
&disconnect_complete);
}
private:
std::unique_ptr<BrEdrConnectionManager> connection_manager_;
std::unique_ptr<PeerCache> peer_cache_;
std::unique_ptr<l2cap::testing::FakeL2cap> l2cap_;
int transaction_count_ = 0;
inspect::Inspector inspector_;
BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BrEdrConnectionManagerTest);
};
using GAP_BrEdrConnectionManagerTest = BrEdrConnectionManagerTest;
TEST_F(BrEdrConnectionManagerTest, DisableConnectivity) {
size_t cb_count = 0;
auto cb = [&cb_count](const auto& status) {
cb_count++;
EXPECT_EQ(fit::ok(), status);
};
EXPECT_CMD_PACKET_OUT(
test_device(), kReadScanEnable, &kReadScanEnableRspPage);
EXPECT_CMD_PACKET_OUT(
test_device(), kWriteScanEnableNone, &kWriteScanEnableRsp);
connmgr()->SetConnectable(/*connectable=*/false, cb);
RunUntilIdle();
EXPECT_EQ(1u, cb_count);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadScanEnable, &kReadScanEnableRspBoth);
EXPECT_CMD_PACKET_OUT(
test_device(), kWriteScanEnableInq, &kWriteScanEnableRsp);
connmgr()->SetConnectable(/*connectable=*/false, cb);
RunUntilIdle();
EXPECT_EQ(2u, cb_count);
}
TEST_F(BrEdrConnectionManagerTest, EnableConnectivity) {
size_t cb_count = 0;
auto cb = [&cb_count](const auto& status) {
cb_count++;
EXPECT_EQ(fit::ok(), status);
};
EXPECT_CMD_PACKET_OUT(
test_device(), kWritePageScanActivity, &kWritePageScanActivityRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kWritePageScanType, &kWritePageScanTypeRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadScanEnable, &kReadScanEnableRspNone);
EXPECT_CMD_PACKET_OUT(
test_device(), kWriteScanEnablePage, &kWriteScanEnableRsp);
connmgr()->SetConnectable(/*connectable=*/true, cb);
RunUntilIdle();
EXPECT_EQ(1u, cb_count);
EXPECT_CMD_PACKET_OUT(
test_device(), kWritePageScanActivity, &kWritePageScanActivityRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kWritePageScanType, &kWritePageScanTypeRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadScanEnable, &kReadScanEnableRspInquiry);
EXPECT_CMD_PACKET_OUT(
test_device(), kWriteScanEnableBoth, &kWriteScanEnableRsp);
connmgr()->SetConnectable(/*connectable=*/true, cb);
RunUntilIdle();
EXPECT_EQ(2u, cb_count);
}
// Test: An incoming connection request should trigger an acceptance and
// interrogation should allow a peer that only report the first Extended
// Features page.
TEST_F(BrEdrConnectionManagerTest,
IncomingConnection_BrokenExtendedPageResponse) {
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp,
&kReadRemoteSupportedFeaturesComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(6, transaction_count());
// When we deallocate the connection manager during teardown, we should
// disconnect.
QueueDisconnection(kConnectionHandle);
}
// Test: An incoming connection request should trigger an acceptance and an
// interrogation to discover capabilities.
TEST_F(BrEdrConnectionManagerTest, IncomingConnectionSuccess) {
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
// Confirm remote name request during interrogation sets proper name source.
EXPECT_EQ(peer->name_source(), Peer::NameSource::kNameDiscoveryProcedure);
// When we deallocate the connection manager during teardown, we should
// disconnect.
QueueDisconnection(kConnectionHandle);
}
// Test: An incoming connection request should upgrade a known LE peer with a
// matching address to a dual mode peer.
TEST_F(BrEdrConnectionManagerTest,
IncomingConnectionUpgradesKnownLowEnergyPeerToDualMode) {
const DeviceAddress le_alias_addr(DeviceAddress::Type::kLEPublic,
kTestDevAddr.value());
Peer* const peer = peer_cache()->NewPeer(le_alias_addr, /*connectable=*/true);
ASSERT_TRUE(peer);
ASSERT_EQ(TechnologyType::kLowEnergy, peer->technology());
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_EQ(peer, peer_cache()->FindByAddress(kTestDevAddr));
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
EXPECT_EQ(TechnologyType::kDualMode, peer->technology());
// Prepare for disconnection upon teardown.
QueueDisconnection(kConnectionHandle);
}
// Test: A remote disconnect should correctly remove the connection.
TEST_F(BrEdrConnectionManagerTest, RemoteDisconnect) {
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
// Remote end disconnects.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
}
const auto kRemoteNameRequestCompleteFailed =
StaticByteBuffer(hci_spec::kRemoteNameRequestCompleteEventCode,
0x01, // parameter_total_size (1 bytes)
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
const auto kReadRemoteSupportedFeaturesCompleteFailed =
StaticByteBuffer(hci_spec::kReadRemoteSupportedFeaturesCompleteEventCode,
0x01, // parameter_total_size (1 bytes)
pw::bluetooth::emboss::StatusCode::HARDWARE_FAILURE);
// Test: if the interrogation fails, we disconnect.
// - Receiving extra responses after a command fails will not fail
// - We don't query extended features if we don't receive an answer.
TEST_F(BrEdrConnectionManagerTest, IncomingConnectionFailedInterrogation) {
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestCompleteFailed);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp,
&kReadRemoteSupportedFeaturesCompleteFailed);
EXPECT_CMD_PACKET_OUT(
test_device(), kDisconnect, &kDisconnectRsp, &kDisconnectionComplete);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(5, transaction_count());
}
// Test: replies negative to IO Capability Requests before PairingDelegate is
// set
TEST_F(BrEdrConnectionManagerTest,
IoCapabilityRequestNegativeReplyWithNoPairingDelegate) {
EXPECT_CMD_PACKET_OUT(test_device(),
kIoCapabilityRequestNegativeReply,
&kIoCapabilityRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
RunUntilIdle();
EXPECT_EQ(1, transaction_count());
}
// Test: replies negative to IO Capability Requests for unconnected peers
TEST_F(BrEdrConnectionManagerTest,
IoCapabilityRequestNegativeReplyWhenNotConnected) {
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
kIoCapabilityRequestNegativeReply,
&kIoCapabilityRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
RunUntilIdle();
EXPECT_EQ(1, transaction_count());
}
// Test: replies to IO Capability Requests for connected peers
TEST_F(BrEdrConnectionManagerTest, IoCapabilityRequestReplyWhenConnected) {
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_EQ(kIncomingConnTransactions, transaction_count());
EXPECT_CMD_PACKET_OUT(
test_device(),
MakeIoCapabilityRequestReply(IoCapability::NO_INPUT_NO_OUTPUT,
AuthenticationRequirements::GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_ONLY,
AuthenticationRequirements::MITM_GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
QueueDisconnection(kConnectionHandle);
}
// Test: Responds to Secure Simple Pairing with user rejection of Numeric
// Comparison association
TEST_F(BrEdrConnectionManagerTest,
RespondToNumericComparisonPairingAfterUserRejects) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_ONLY, AuthenticationRequirements::GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
EXPECT_EQ(kPasskey, passkey);
EXPECT_EQ(PairingDelegate::DisplayMethod::kComparison, method);
ASSERT_TRUE(confirm_cb);
confirm_cb(false);
});
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestNegativeReply,
&kUserConfirmationRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(
MakeUserConfirmationRequest(kPasskey));
pairing_delegate.SetCompletePairingCallback([](PeerId, sm::Result<> status) {
EXPECT_EQ(ToResult(HostError::kFailed), status);
});
test_device()->SendCommandChannelPacket(kSimplePairingCompleteError);
// We disconnect the peer when authentication fails.
QueueDisconnection(kConnectionHandle);
RunUntilIdle();
}
const auto kUserPasskeyRequest =
StaticByteBuffer(hci_spec::kUserPasskeyRequestEventCode,
0x06, // parameter_total_size (6 byte payload)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kUserPasskeyRequestNegativeReply =
StaticByteBuffer(LowerBits(hci_spec::kUserPasskeyRequestNegativeReply),
UpperBits(hci_spec::kUserPasskeyRequestNegativeReply),
0x06, // parameter_total_size (6 bytes)
TEST_DEV_ADDR_BYTES_LE // peer address
);
const auto kUserPasskeyRequestNegativeReplyRsp =
StaticByteBuffer(hci_spec::kCommandCompleteEventCode,
0x0A,
0xF0,
LowerBits(hci_spec::kUserPasskeyRequestNegativeReply),
UpperBits(hci_spec::kUserPasskeyRequestNegativeReply),
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
TEST_DEV_ADDR_BYTES_LE // peer address
);
// Test: Responds to Secure Simple Pairing as the input side of Passkey Entry
// association after the user declines or provides invalid input
TEST_F(BrEdrConnectionManagerTest,
RespondToPasskeyEntryPairingAfterUserProvidesInvalidPasskey) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
FakePairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::KEYBOARD_ONLY,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_ONLY, AuthenticationRequirements::GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
pairing_delegate.SetRequestPasskeyCallback([](PeerId, auto response_cb) {
ASSERT_TRUE(response_cb);
response_cb(-128); // Negative values indicate rejection.
});
EXPECT_CMD_PACKET_OUT(test_device(),
kUserPasskeyRequestNegativeReply,
&kUserPasskeyRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(kUserPasskeyRequest);
pairing_delegate.SetCompletePairingCallback([](PeerId, sm::Result<> status) {
EXPECT_EQ(ToResult(HostError::kFailed), status);
});
test_device()->SendCommandChannelPacket(kSimplePairingCompleteError);
// We disconnect the peer when authentication fails.
QueueDisconnection(kConnectionHandle);
RunUntilIdle();
}
// Test: replies negative to Link Key Requests for unknown and unbonded peers
TEST_F(BrEdrConnectionManagerTest, LinkKeyRequestAndNegativeReply) {
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
EXPECT_EQ(1, transaction_count());
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
ASSERT_FALSE(peer->bonded());
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 2, transaction_count());
// Queue disconnection for teardown
QueueDisconnection(kConnectionHandle);
}
// Test: replies to Link Key Requests for bonded peer
TEST_F(BrEdrConnectionManagerTest, RecallLinkKeyForBondedPeer) {
ASSERT_TRUE(
peer_cache()->AddBondedPeer(BondingData{.identifier = PeerId(999),
.address = kTestDevAddr,
.name = std::nullopt,
.le_pairing_data = {},
.bredr_link_key = kLinkKey,
.bredr_services = {}}));
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsNotConnected(peer));
ASSERT_TRUE(peer->bonded());
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
ASSERT_TRUE(IsInitializing(peer));
EXPECT_CMD_PACKET_OUT(
test_device(), kLinkKeyRequestReply, &kLinkKeyRequestReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
/// Peer is still initializing until the Pairing is complete
/// (OnPairingComplete)
ASSERT_TRUE(IsInitializing(peer));
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
// Queue disconnection for teardown.
QueueDisconnection(kConnectionHandle);
}
// Test: Responds to Secure Simple Pairing as the input side of Passkey Entry
// association after the user provides the correct passkey
TEST_F(BrEdrConnectionManagerTest,
EncryptAfterPasskeyEntryPairingAndUserProvidesAcceptedPasskey) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
FakePairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::KEYBOARD_ONLY,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_ONLY, AuthenticationRequirements::GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
pairing_delegate.SetRequestPasskeyCallback([](PeerId, auto response_cb) {
ASSERT_TRUE(response_cb);
response_cb(kPasskey);
});
EXPECT_CMD_PACKET_OUT(test_device(),
MakeUserPasskeyRequestReply(kPasskey),
&kUserPasskeyRequestReplyRsp);
test_device()->SendCommandChannelPacket(kUserPasskeyRequest);
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
test_device()->SendCommandChannelPacket(kSimplePairingCompleteSuccess);
test_device()->SendCommandChannelPacket(kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsConnected(peer));
EXPECT_TRUE(peer->bonded());
QueueDisconnection(kConnectionHandle);
}
// Test: Responds to Secure Simple Pairing as the display side of Passkey Entry
// association after the user provides the correct passkey on the peer
TEST_F(BrEdrConnectionManagerTest, EncryptAfterPasskeyDisplayPairing) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_ONLY,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(
MakeIoCapabilityResponse(IoCapability::KEYBOARD_ONLY,
AuthenticationRequirements::GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
EXPECT_EQ(kPasskey, passkey);
EXPECT_EQ(PairingDelegate::DisplayMethod::kPeerEntry, method);
EXPECT_TRUE(confirm_cb);
});
test_device()->SendCommandChannelPacket(
MakeUserPasskeyNotification(kPasskey));
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsInitializing(peer));
test_device()->SendCommandChannelPacket(kSimplePairingCompleteSuccess);
test_device()->SendCommandChannelPacket(kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsConnected(peer));
EXPECT_TRUE(peer->bonded());
QueueDisconnection(kConnectionHandle);
}
// Test: Responds to Secure Simple Pairing and user confirmation of Numeric
// Comparison association, then bonds and encrypts using resulting link key
TEST_F(BrEdrConnectionManagerTest,
EncryptAndBondAfterNumericComparisonPairingAndUserConfirms) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp);
test_device()->SendCommandChannelPacket(
MakeIoCapabilityResponse(IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
EXPECT_EQ(kPasskey, passkey);
EXPECT_EQ(PairingDelegate::DisplayMethod::kComparison, method);
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp);
test_device()->SendCommandChannelPacket(
MakeUserConfirmationRequest(kPasskey));
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsInitializing(peer));
test_device()->SendCommandChannelPacket(kSimplePairingCompleteSuccess);
test_device()->SendCommandChannelPacket(kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsConnected(peer));
EXPECT_TRUE(peer->bonded());
EXPECT_CMD_PACKET_OUT(
test_device(), kLinkKeyRequestReply, &kLinkKeyRequestReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
QueueDisconnection(kConnectionHandle);
}
// Test: can't change the link key of an unbonded peer
TEST_F(BrEdrConnectionManagerTest, UnbondedPeerChangeLinkKey) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
// Change the link key.
test_device()->SendCommandChannelPacket(kLinkKeyNotificationChanged);
RunUntilIdle();
ASSERT_FALSE(IsConnected(peer));
EXPECT_FALSE(peer->bonded());
EXPECT_CMD_PACKET_OUT(
test_device(), kLinkKeyRequestNegativeReply, &kLinkKeyRequestReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
ASSERT_FALSE(IsConnected(peer));
EXPECT_FALSE(peer->bonded());
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
QueueDisconnection(kConnectionHandle);
}
const auto kLinkKeyNotificationLegacy =
StaticByteBuffer(hci_spec::kLinkKeyNotificationEventCode,
0x17, // parameter_total_size (17 bytes)
TEST_DEV_ADDR_BYTES_LE, // peer address
0x41,
0x33,
0x7c,
0x0d,
0xef,
0xee,
0xda,
0xda,
0xba,
0xad,
0x0f,
0xf1,
0xce,
0xc0,
0xff,
0xee, // link key
0x00 // key type (Combination Key)
);
// Test: don't bond or mark successfully connected if the link key resulted from
// legacy pairing
TEST_F(BrEdrConnectionManagerTest, LegacyLinkKeyNotBonded) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
test_device()->SendCommandChannelPacket(kLinkKeyNotificationLegacy);
RunUntilIdle();
EXPECT_FALSE(peer->bonded());
EXPECT_CMD_PACKET_OUT(
test_device(), kLinkKeyRequestNegativeReply, &kLinkKeyRequestReplyRsp);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
RunUntilIdle();
ASSERT_FALSE(IsConnected(peer));
EXPECT_FALSE(peer->bonded());
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
QueueDisconnection(kConnectionHandle);
}
// Test: if L2CAP gets a link error, we disconnect the connection
TEST_F(BrEdrConnectionManagerTest, DisconnectOnLinkError) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
// When we deallocate the connection manager next, we should disconnect.
QueueDisconnection(kConnectionHandle);
l2cap()->TriggerLinkError(kConnectionHandle);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
}
TEST_F(BrEdrConnectionManagerTest, InitializingPeerDoesNotTimeout) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_FALSE(peer->bonded());
// We want to make sure the connection doesn't expire just because they didn't
// pair.
RunFor(std::chrono::seconds(600));
auto* peer_still = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer_still);
ASSERT_EQ(peer->identifier(), peer_still->identifier());
// Remote end disconnects.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
// Peer should still be there, but not connected anymore, until they time out.
peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_TRUE(IsNotConnected(peer));
EXPECT_FALSE(peer->bonded());
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
}
inline uint16_t tid_from_sdp_packet(const ByteBufferPtr& packet) {
return (*packet)[1] << CHAR_BIT | (*packet)[2];
}
TEST_F(BrEdrConnectionManagerTest,
PeerServicesAddedBySearchAndRetainedIfNotSearchedFor) {
constexpr UUID kServiceUuid1 = sdp::profile::kAudioSink;
auto* const peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
peer->MutBrEdr().AddService(kServiceUuid1);
// Search for different service.
constexpr UUID kServiceUuid2 = sdp::profile::kAudioSource;
size_t search_cb_count = 0;
connmgr()->AddServiceSearch(kServiceUuid2,
{sdp::kServiceId},
[&](auto, auto&) { search_cb_count++; });
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint32_t> sdp_request_tid;
l2cap()->set_channel_callback(
[this, &sdp_chan, &sdp_request_tid](auto new_chan) {
new_chan->SetSendCallback(
[&sdp_request_tid](auto packet) {
sdp_request_tid = tid_from_sdp_packet(packet);
},
dispatcher());
sdp_chan = std::move(new_chan);
});
// No searches in this connection.
QueueSuccessfulIncomingConn();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_EQ(0u, search_cb_count);
// Positive response to search.
sdp::ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, sdp::kServiceId, sdp::DataElement(UUID()));
auto rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(1u, search_cb_count);
// Prior connections' services retained and newly discovered service added.
EXPECT_EQ(1u, peer->bredr()->services().count(kServiceUuid1));
EXPECT_EQ(1u, peer->bredr()->services().count(kServiceUuid2));
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
PeerServiceNotErasedByEmptyResultsForSearchOfSameService) {
constexpr UUID kServiceUuid = sdp::profile::kAudioSink;
auto* const peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
peer->MutBrEdr().AddService(kServiceUuid);
size_t search_cb_count = 0;
connmgr()->AddServiceSearch(
kServiceUuid, {sdp::kServiceId}, [&](auto, auto&) { search_cb_count++; });
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint32_t> sdp_request_tid;
l2cap()->set_channel_callback(
[this, &sdp_chan, &sdp_request_tid](auto new_chan) {
new_chan->SetSendCallback(
[&sdp_request_tid](auto packet) {
sdp_request_tid = tid_from_sdp_packet(packet);
},
dispatcher());
sdp_chan = std::move(new_chan);
});
QueueSuccessfulIncomingConn();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_EQ(0u, search_cb_count);
sdp::ServiceSearchAttributeResponse empty_rsp;
auto rsp_ptr = empty_rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
// Search callback isn't called by empty attribute list from peer.
ASSERT_EQ(0u, search_cb_count);
EXPECT_EQ(1u, peer->bredr()->services().count(kServiceUuid));
QueueDisconnection(kConnectionHandle);
}
l2cap::testing::FakeChannel::SendCallback MakeAudioSinkSearchExpected(
std::optional<uint16_t>* tid) {
return [tid](auto packet) {
const StaticByteBuffer kSearchExpectedParams(
// ServiceSearchPattern
0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x11,
0x0B, // UUID (kAudioSink)
0xFF,
0xFF, // MaxAttributeByteCount (no max)
// Attribute ID list
0x35,
0x03, // Sequence uint8 3 bytes
0x09,
0x00,
0x03, // uint16_t (kServiceId)
0x00 // No continuation state
);
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(sdp::kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams, packet->view(sizeof(bt::sdp::Header)));
if (tid != nullptr) {
}
*tid = tid_from_sdp_packet(packet);
};
}
TEST_F(BrEdrConnectionManagerTest, ServiceSearch) {
size_t search_cb_count = 0;
auto search_cb = [&](auto id, const auto& attributes) {
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_EQ(id, peer->identifier());
ASSERT_EQ(1u, attributes.count(sdp::kServiceId));
search_cb_count++;
};
auto search_id = connmgr()->AddServiceSearch(
sdp::profile::kAudioSink, {sdp::kServiceId}, search_cb);
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint16_t> sdp_request_tid;
l2cap()->set_channel_callback(
[&sdp_chan, &sdp_request_tid, this](auto new_chan) {
new_chan->SetSendCallback(MakeAudioSinkSearchExpected(&sdp_request_tid),
dispatcher());
sdp_chan = std::move(new_chan);
});
QueueSuccessfulIncomingConn();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(0u, search_cb_count);
sdp::ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, sdp::kServiceId, sdp::DataElement(UUID()));
auto rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(1u, search_cb_count);
// Remote end disconnects.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
sdp_request_tid.reset();
EXPECT_TRUE(connmgr()->RemoveServiceSearch(search_id));
EXPECT_FALSE(connmgr()->RemoveServiceSearch(search_id));
// Second connection is shortened because we have already interrogated,
// and we don't search for SDP services because none are registered
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended2Complete);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// We shouldn't have searched for anything.
ASSERT_FALSE(sdp_request_tid);
ASSERT_EQ(1u, search_cb_count);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, SearchAfterConnected) {
// We have no services registered, so this will not start a SDP search.
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
size_t search_cb_count = 0;
auto search_cb = [&](auto id, const auto& attributes) {
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_EQ(id, peer->identifier());
ASSERT_EQ(1u, attributes.count(sdp::kServiceId));
search_cb_count++;
};
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint16_t> sdp_request_tid;
l2cap()->set_channel_callback(
[&sdp_chan, &sdp_request_tid, this](auto new_chan) {
new_chan->SetSendCallback(MakeAudioSinkSearchExpected(&sdp_request_tid),
dispatcher());
sdp_chan = std::move(new_chan);
});
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
// When this gets added, the service search will immediately be done on the
// already-connected peer.
auto search_id = connmgr()->AddServiceSearch(
sdp::profile::kAudioSink, {sdp::kServiceId}, search_cb);
ASSERT_NE(sdp::ServiceDiscoverer::kInvalidSearchId, search_id);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(0u, search_cb_count);
sdp::ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, sdp::kServiceId, sdp::DataElement(UUID()));
auto rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(1u, search_cb_count);
// Remote end disconnects.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
sdp_request_tid.reset();
sdp_chan.reset();
// Second connection is shortened because we have already interrogated,
// we repeat the search for SDP services.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended2Complete);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(1u, search_cb_count);
// Reusing the (empty) answer from before
rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
// We should have another search callback.
ASSERT_EQ(2u, search_cb_count);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, SearchOnReconnect) {
size_t search_cb_count = 0;
auto search_cb = [&](auto id, const auto& attributes) {
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_EQ(id, peer->identifier());
ASSERT_EQ(1u, attributes.count(sdp::kServiceId));
search_cb_count++;
};
connmgr()->AddServiceSearch(
sdp::profile::kAudioSink, {sdp::kServiceId}, search_cb);
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint16_t> sdp_request_tid;
l2cap()->set_channel_callback(
[&sdp_chan, &sdp_request_tid, this](auto new_chan) {
new_chan->SetSendCallback(MakeAudioSinkSearchExpected(&sdp_request_tid),
dispatcher());
sdp_chan = std::move(new_chan);
});
// This test uses a modified peer and interrogation which doesn't use extended
// pages.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
const DynamicByteBuffer remote_name_complete_packet =
testing::RemoteNameRequestCompletePacket(kTestDevAddr);
const DynamicByteBuffer remote_version_complete_packet =
testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle);
const DynamicByteBuffer remote_supported_complete_packet =
testing::ReadRemoteSupportedFeaturesCompletePacket(
kConnectionHandle,
/*extended_features=*/false);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::RemoteNameRequestPacket(kTestDevAddr),
&kRemoteNameRequestRsp,
&remote_name_complete_packet);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::ReadRemoteVersionInfoPacket(kConnectionHandle),
&kReadRemoteVersionInfoRsp,
&remote_version_complete_packet);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::ReadRemoteSupportedFeaturesPacket(kConnectionHandle),
&kReadRemoteSupportedFeaturesRsp,
&remote_supported_complete_packet);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(0u, search_cb_count);
sdp::ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, sdp::kServiceId, sdp::DataElement(UUID()));
auto rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(1u, search_cb_count);
// Remote end disconnects.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
sdp_request_tid.reset();
sdp_chan.reset();
// Second connection is shortened because we have already interrogated.
// We still search for SDP services.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
// We don't send any interrogation packets, because there is none to be done.
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// We should have searched again.
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(1u, search_cb_count);
rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(2u, search_cb_count);
QueueDisconnection(kConnectionHandle);
}
// Test: when opening an L2CAP channel on an unbonded peer, indicate that we
// have no link key then pair, authenticate, bond, and encrypt the link, then
// try to open the channel.
TEST_F(BrEdrConnectionManagerTest, OpenL2capPairsAndEncryptsThenRetries) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
std::optional<l2cap::Channel::WeakPtr> connected_chan;
auto chan_cb = [&](auto chan) { connected_chan = std::move(chan); };
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initial connection request
// Pairing initiation and flow that results in bonding then encryption, but
// verifying the strength of the encryption key doesn't complete
EXPECT_CMD_PACKET_OUT(test_device(),
kAuthenticationRequested,
&kAuthenticationRequestedStatus,
&kLinkKeyRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp,
&kIoCapabilityRequest);
const auto kIoCapabilityResponse = MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kIoCapabilityResponse,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification,
&kAuthenticationComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySize, );
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
chan_cb);
RETURN_IF_FATAL(RunUntilIdle());
// We should not have a channel because the L2CAP open callback shouldn't have
// been called, but the LTK should be stored since the link key got received.
ASSERT_FALSE(connected_chan);
// We should be initializing, since we have not completed pairing.
ASSERT_TRUE(IsInitializing(peer));
test_device()->SendCommandChannelPacket(kReadEncryptionKeySizeRsp);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kAVDTP, 0x40, 0x41, kChannelParams);
RETURN_IF_FATAL(RunUntilIdle());
// We should signal to PeerCache as connected once we finish pairing.
ASSERT_TRUE(IsConnected(peer));
// The socket should be returned.
ASSERT_TRUE(connected_chan);
connected_chan.reset();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kAVDTP, 0x40, 0x41, kChannelParams);
// A second connection request should not require another authentication.
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
chan_cb);
RunUntilIdle();
ASSERT_TRUE(connected_chan);
QueueDisconnection(kConnectionHandle);
}
// Test: when the peer is already bonded, the link key gets stored when it is
// provided to the connection.
TEST_F(BrEdrConnectionManagerTest, OpenL2capEncryptsForBondedPeerThenRetries) {
ASSERT_TRUE(
peer_cache()->AddBondedPeer(BondingData{.identifier = PeerId(999),
.address = kTestDevAddr,
.name = std::nullopt,
.le_pairing_data = {},
.bredr_link_key = kLinkKey,
.bredr_services = {}}));
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsNotConnected(peer));
ASSERT_TRUE(peer->bonded());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
ASSERT_FALSE(IsNotConnected(peer));
std::optional<l2cap::Channel::WeakPtr> connected_chan;
auto socket_cb = [&](auto chan) { connected_chan = std::move(chan); };
// Initial connection request
// Note: this skips some parts of the pairing flow, because the link key being
// received is the important part of this. The key is not received when the
// authentication fails.
EXPECT_CMD_PACKET_OUT(
test_device(), kAuthenticationRequested, &kAuthenticationRequestedStatus);
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
socket_cb);
RunUntilIdle();
// L2CAP connect shouldn't have been called, and callback shouldn't be called.
// We should not have a socket.
ASSERT_FALSE(connected_chan);
ASSERT_FALSE(IsNotConnected(peer));
// The authentication flow will request the existing link key, which should be
// returned and stored, and then the authentication is complete.
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestReply,
&kLinkKeyRequestReplyRsp,
&kAuthenticationComplete);
test_device()->SendCommandChannelPacket(kLinkKeyRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySize, );
RunUntilIdle();
// No socket until the encryption verification completes.
ASSERT_FALSE(connected_chan);
test_device()->SendCommandChannelPacket(kReadEncryptionKeySizeRsp);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kAVDTP, 0x40, 0x41, kChannelParams);
RunUntilIdle();
// Once the L2CAP channel has connected, we have connected.
ASSERT_TRUE(IsConnected(peer));
// The socket should be connected.
ASSERT_TRUE(connected_chan);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
OpenL2capAuthenticationFailureReturnsInvalidSocketAndDisconnects) {
QueueSuccessfulIncomingConn();
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
std::optional<l2cap::Channel::WeakPtr> connected_chan;
auto socket_cb = [&](auto chan) { connected_chan = std::move(chan); };
// Initial connection request
// Note: this skips some parts of the pairing flow, because the link key being
// received is the important part of this. The key is not received when the
// authentication fails.
EXPECT_CMD_PACKET_OUT(
test_device(), kAuthenticationRequested, &kAuthenticationRequestedStatus);
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
socket_cb);
RunUntilIdle();
// The L2CAP shouldn't have been called
// We should not have a channel, and the callback shouldn't have been called.
ASSERT_FALSE(connected_chan);
test_device()->SendCommandChannelPacket(kAuthenticationCompleteFailed);
int count = transaction_count();
// We disconnect the peer when authentication fails.
QueueDisconnection(kConnectionHandle);
RunUntilIdle();
// An invalid channel should have been sent because the connection failed.
ASSERT_TRUE(connected_chan);
ASSERT_FALSE(connected_chan.value().is_alive());
ASSERT_EQ(count + kDisconnectionTransactions, transaction_count());
}
TEST_F(BrEdrConnectionManagerTest, OpenL2capPairingFinishesButDisconnects) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initial connection request
// Pairing initiation and flow that results in bonding then encryption, but
// verifying the strength of the encryption key doesn't complete
EXPECT_CMD_PACKET_OUT(test_device(),
kAuthenticationRequested,
&kAuthenticationRequestedStatus,
&kLinkKeyRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kLinkKeyRequestNegativeReply,
&kLinkKeyRequestNegativeReplyRsp,
&kIoCapabilityRequest);
const auto kIoCapabilityResponse = MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kIoCapabilityResponse,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification,
&kAuthenticationComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySize, );
std::optional<l2cap::Channel::WeakPtr> connected_chan;
auto chan_cb = [&](auto chan) { connected_chan = std::move(chan); };
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
chan_cb);
RETURN_IF_FATAL(RunUntilIdle());
// We should not have a channel because the L2CAP open callback shouldn't have
// been called, but the LTK should be stored since the link key got received.
ASSERT_FALSE(connected_chan);
// The remote device disconnects now, when the pairing has been started, then
// pairing completes.
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
test_device()->SendCommandChannelPacket(kReadEncryptionKeySizeRsp);
RunUntilIdle();
// We should get a callback from the OpenL2capChannel
ASSERT_TRUE(connected_chan);
EXPECT_FALSE(connected_chan.value().is_alive());
connected_chan.reset();
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
chan_cb);
// The L2CAP should be called right away without a channel.
ASSERT_TRUE(connected_chan);
EXPECT_FALSE(connected_chan.value().is_alive());
connected_chan.reset();
}
// Test: when pairing is in progress, opening an L2CAP channel waits for the
// pairing to complete before retrying.
TEST_F(BrEdrConnectionManagerTest,
OpenL2capDuringPairingWaitsForPairingToComplete) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
std::optional<l2cap::Channel::WeakPtr> connected_chan;
auto socket_cb = [&](auto chan) { connected_chan = std::move(chan); };
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initiate pairing from the peer
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING));
RETURN_IF_FATAL(RunUntilIdle());
// Initial connection request
// Pair and bond as the responder. Note that Authentication Requested is not
// sent even though we are opening the L2CAP channel because the peer started
// pairing first.
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(test_device(), kReadEncryptionKeySize, );
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
socket_cb);
RETURN_IF_FATAL(RunUntilIdle());
// We should not have a socket because the L2CAP open callback shouldn't have
// been called, but the LTK should be stored since the link key got received.
ASSERT_FALSE(connected_chan);
test_device()->SendCommandChannelPacket(kReadEncryptionKeySizeRsp);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kAVDTP, 0x40, 0x41, kChannelParams);
RETURN_IF_FATAL(RunUntilIdle());
// The socket should be returned.
ASSERT_TRUE(connected_chan);
QueueDisconnection(kConnectionHandle);
}
// Test: when pairing is in progress, opening an L2CAP channel waits for the
// pairing to complete before retrying.
TEST_F(BrEdrConnectionManagerTest,
InterrogationInProgressAllowsBondingButNotL2cap) {
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Trigger inbound connection and respond to some (but not all) of
// interrogation.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// Ensure that the interrogation has begun but the peer hasn't yet bonded
EXPECT_EQ(4, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bredr()->bonded());
// Approve pairing requests
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initiate pairing from the peer before interrogation completes
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
RETURN_IF_FATAL(RunUntilIdle());
// At this point the peer is bonded and the link is encrypted but
// interrogation has not completed so host-side L2CAP should still be inactive
// on this link (though it may be buffering packets).
EXPECT_FALSE(l2cap()->IsLinkConnected(kConnectionHandle));
bool socket_cb_called = false;
auto socket_fails_cb = [&socket_cb_called](auto chan_sock) {
EXPECT_FALSE(chan_sock.is_alive());
socket_cb_called = true;
};
connmgr()->OpenL2capChannel(peer->identifier(),
l2cap::kAVDTP,
kNoSecurityRequirements,
kChannelParams,
socket_fails_cb);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(socket_cb_called);
// Complete interrogation successfully.
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
test_device()->SendCommandChannelPacket(kReadRemoteSupportedFeaturesComplete);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(l2cap()->IsLinkConnected(kConnectionHandle));
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, ConnectUnknownPeer) {
EXPECT_FALSE(connmgr()->Connect(PeerId(456), {}));
}
TEST_F(BrEdrConnectionManagerTest, ConnectLowEnergyPeer) {
auto* peer = peer_cache()->NewPeer(kTestDevAddrLe, /*connectable=*/true);
EXPECT_FALSE(connmgr()->Connect(peer->identifier(), {}));
}
TEST_F(BrEdrConnectionManagerTest, DisconnectUnknownPeerDoesNothing) {
EXPECT_TRUE(
connmgr()->Disconnect(PeerId(999), DisconnectReason::kApiRequest));
RunUntilIdle();
EXPECT_EQ(0, transaction_count());
}
// Test: user-initiated disconnection
TEST_F(BrEdrConnectionManagerTest, DisconnectClosesHciConnection) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_FALSE(IsNotConnected(peer));
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
EXPECT_TRUE(IsNotConnected(peer));
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
EXPECT_TRUE(IsNotConnected(peer));
}
TEST_F(BrEdrConnectionManagerTest, DisconnectSamePeerIsIdempotent) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
ASSERT_TRUE(IsNotConnected(peer));
// Try to disconnect again while the first disconnect is in progress (HCI
// Disconnection Complete not yet received).
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
ASSERT_TRUE(IsNotConnected(peer));
// Try to disconnect once more, now that the link is gone.
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
}
TEST_F(BrEdrConnectionManagerTest, RemovePeerFromPeerCacheDuringDisconnection) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_FALSE(IsNotConnected(peer));
QueueDisconnection(kConnectionHandle);
const PeerId id = peer->identifier();
EXPECT_TRUE(connmgr()->Disconnect(id, DisconnectReason::kApiRequest));
ASSERT_TRUE(IsNotConnected(peer));
// Remove the peer from PeerCache before receiving HCI Disconnection Complete.
EXPECT_TRUE(peer_cache()->RemoveDisconnectedPeer(id));
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions + 1, transaction_count());
EXPECT_FALSE(peer_cache()->FindById(id));
EXPECT_FALSE(peer_cache()->FindByAddress(kTestDevAddr));
}
TEST_F(BrEdrConnectionManagerTest, AddServiceSearchAll) {
size_t search_cb_count = 0;
auto search_cb = [&](auto id, const auto&) {
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_EQ(id, peer->identifier());
search_cb_count++;
};
connmgr()->AddServiceSearch(sdp::profile::kAudioSink, {}, search_cb);
l2cap::testing::FakeChannel::WeakPtr sdp_chan;
std::optional<uint32_t> sdp_request_tid;
l2cap()->set_channel_callback(
[this, &sdp_chan, &sdp_request_tid](auto new_chan) {
new_chan->SetSendCallback(
[&sdp_request_tid](auto packet) {
const StaticByteBuffer kSearchExpectedParams(
// ServiceSearchPattern
0x35,
0x03, // Sequence uint8 3 bytes
0x19,
0x11,
0x0B, // UUID (kAudioSink)
0xFF,
0xFF, // MaxAttributeByteCount (none)
// Attribute ID list
0x35,
0x05, // Sequence uint8 5 bytes
0x0A,
0x00,
0x00,
0xFF,
0xFF, // uint32_t (all attributes)
0x00 // No continuation state
);
// First byte should be type.
ASSERT_LE(3u, packet->size());
ASSERT_EQ(sdp::kServiceSearchAttributeRequest, (*packet)[0]);
ASSERT_EQ(kSearchExpectedParams,
packet->view(sizeof(bt::sdp::Header)));
sdp_request_tid = tid_from_sdp_packet(packet);
},
dispatcher());
sdp_chan = std::move(new_chan);
});
QueueSuccessfulIncomingConn();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, 0x40, 0x41, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
ASSERT_TRUE(sdp_chan.is_alive());
ASSERT_TRUE(sdp_request_tid);
ASSERT_EQ(0u, search_cb_count);
sdp::ServiceSearchAttributeResponse rsp;
rsp.SetAttribute(0, sdp::kServiceId, sdp::DataElement(UUID()));
auto rsp_ptr = rsp.GetPDU(0xFFFF /* max attribute bytes */,
*sdp_request_tid,
PDU_MAX,
BufferView());
sdp_chan->Receive(*rsp_ptr);
RunUntilIdle();
ASSERT_EQ(1u, search_cb_count);
QueueDisconnection(kConnectionHandle);
}
// An error is received via the HCI Command cb_status event
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerErrorStatus) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRspError);
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsNotConnected(peer));
hci::Result<> status = fit::ok();
EXPECT_TRUE(
connmgr()->Connect(peer->identifier(), CALLBACK_EXPECT_FAILURE(status)));
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::
CONNECTION_FAILED_TO_BE_ESTABLISHED),
status);
EXPECT_TRUE(IsNotConnected(peer));
}
// Connection Complete event reports error
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerFailure) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionCompleteError);
hci::Result<> status = ToResult(HostError::kFailed);
bool callback_run = false;
auto callback = [&status, &callback_run](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
status = cb_status;
callback_run = true;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
EXPECT_TRUE(callback_run);
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::
CONNECTION_FAILED_TO_BE_ESTABLISHED),
status);
EXPECT_TRUE(IsNotConnected(peer));
}
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerTimeout) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnectionCancel,
&kCreateConnectionCancelRsp,
&kConnectionCompleteCanceled);
hci::Result<> status = fit::ok();
auto callback = [&status](auto cb_status, auto conn_ref) {
EXPECT_FALSE(conn_ref);
status = cb_status;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunFor(kBrEdrCreateConnectionTimeout);
RunFor(kBrEdrCreateConnectionTimeout);
EXPECT_EQ(ToResult(HostError::kTimedOut), status);
EXPECT_TRUE(IsNotConnected(peer));
}
// Successful connection to single peer
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeer) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// Queue up the connection
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL);
}
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerFailedInterrogation) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// Queue up outbound connection.
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
// Queue up most of interrogation.
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp);
hci::Result<> status = fit::ok();
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_FALSE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
RETURN_IF_FATAL(RunUntilIdle());
test_device()->SendCommandChannelPacket(
kReadRemoteSupportedFeaturesCompleteFailed);
QueueDisconnection(kConnectionHandle);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_EQ(ToResult(HostError::kNotSupported), status);
EXPECT_TRUE(IsNotConnected(peer));
}
// Connecting to an already connected peer should complete instantly
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerAlreadyConnected) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// Queue up the connection
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
int num_callbacks = 0;
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref, &num_callbacks](auto cb_status,
auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
++num_callbacks;
};
// Connect to the peer for the first time
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_EQ(num_callbacks, 1);
// Attempt to connect again to the already connected peer. callback should be
// called synchronously.
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
EXPECT_EQ(num_callbacks, 2);
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
}
// Initiating Two Connections to the same (currently unconnected) peer should
// successfully establish both
TEST_F(BrEdrConnectionManagerTest, ConnectSinglePeerTwoInFlight) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// Queue up the connection
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
int num_callbacks = 0;
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref, &num_callbacks](auto cb_status,
auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
++num_callbacks;
};
// Launch one request, but don't run the loop
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
// Launch second inflight request
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
// Run the loop which should complete both requests
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_EQ(num_callbacks, 2);
}
TEST_F(BrEdrConnectionManagerTest,
ConnectInterrogatingPeerOnlyCompletesAfterInterrogation) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
// Prevent interrogation from completing so that we can queue a second request
// during interrogation.
QueueIncompleteInterrogation(kTestDevAddr, kConnectionHandle);
QueueDisconnection(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
int num_callbacks = 0;
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref, &num_callbacks](auto cb_status,
auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
++num_callbacks;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
// Launch second request, which should not complete immediately.
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
EXPECT_EQ(num_callbacks, 0);
// Finishing interrogation should complete both requests.
CompleteInterrogation(kConnectionHandle);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_EQ(num_callbacks, 2);
}
TEST_F(BrEdrConnectionManagerTest, ConnectSecondPeerFirstTimesOut) {
auto* peer_a = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
auto* peer_b = peer_cache()->NewPeer(kTestDevAddr2, /*connectable=*/true);
// Enqueue first connection request (which will timeout and be cancelled)
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnectionCancel,
&kCreateConnectionCancelRsp,
&kConnectionCompleteCanceled);
// Enqueue second connection (which will succeed once previous has ended)
QueueSuccessfulCreateConnection(peer_b, kConnectionHandle2);
QueueSuccessfulInterrogation(peer_b->address(), kConnectionHandle2);
QueueDisconnection(kConnectionHandle2);
// Initialize as success to verify that |callback_a| assigns failure.
hci::Result<> status_a = fit::ok();
auto callback_a = [&status_a](auto cb_status, auto cb_conn_ref) {
status_a = cb_status;
EXPECT_FALSE(cb_conn_ref);
};
// Initialize as error to verify that |callback_b| assigns success.
hci::Result<> status_b = ToResult(HostError::kFailed);
BrEdrConnection* connection = nullptr;
auto callback_b = [&status_b, &connection](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status_b = cb_status;
connection = std::move(cb_conn_ref);
};
// Launch one request (this will timeout)
EXPECT_TRUE(connmgr()->Connect(peer_a->identifier(), callback_a));
ASSERT_TRUE(peer_a->bredr());
EXPECT_TRUE(IsInitializing(peer_a));
RunUntilIdle();
// Launch second inflight request (this will wait for the first)
EXPECT_TRUE(connmgr()->Connect(peer_b->identifier(), callback_b));
ASSERT_TRUE(peer_b->bredr());
// Run the loop which should complete both requests
RunFor(kBrEdrCreateConnectionTimeout);
RunFor(kBrEdrCreateConnectionTimeout);
EXPECT_TRUE(status_a.is_error());
EXPECT_EQ(fit::ok(), status_b);
EXPECT_TRUE(HasConnectionTo(peer_b, connection));
EXPECT_TRUE(IsNotConnected(peer_a));
EXPECT_FALSE(IsNotConnected(peer_b));
}
class FirstLowEnergyOnlyPeer : public BrEdrConnectionManagerTest,
public ::testing::WithParamInterface<bool> {};
TEST_P(FirstLowEnergyOnlyPeer, ConnectToDualModePeerThatWasFirstLowEnergyOnly) {
const DeviceAddress kTestDevAddrLeAlias(DeviceAddress::Type::kLEPublic,
kTestDevAddr.value());
auto* peer =
peer_cache()->NewPeer(kTestDevAddrLeAlias, /*connectable=*/GetParam());
EXPECT_TRUE(peer->temporary());
EXPECT_EQ(TechnologyType::kLowEnergy, peer->technology());
// Make peer dual mode
peer->MutBrEdr();
EXPECT_EQ(TechnologyType::kDualMode, peer->technology());
// Queue up the connection
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
test_device()->SendCommandChannelPacket(kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_FALSE(IsNotConnected(peer));
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL);
}
INSTANTIATE_TEST_SUITE_P(BrEdrConnectionManagerTest,
FirstLowEnergyOnlyPeer,
::testing::Values(true, false));
// Tests the successful retry case. "don't retry for other error codes" is
// implicitly tested in ConnectSinglePeerFailure - MockController would error if
// we unexpectedly retried.
TEST_F(BrEdrConnectionManagerTest, SuccessfulHciRetriesAfterPageTimeout) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// We send a first HCI Create Connection which will hang for 14s, and then
// respond on the test device with a ConnectionCompletePageTimeout event,
// which will cause a retry. The retry will also hang for 14s, then will
// receive another PageTimeout response, which will cause another retry, which
// will finally be permitted to succeed
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
// Cause the initial Create Connection to wait for 14s for Connection Complete
RunFor(std::chrono::seconds(14));
ASSERT_TRUE(
test_device()->SendCommandChannelPacket(kConnectionCompletePageTimeout));
// Verify higher layers have not been notified of failure.
EXPECT_EQ(ToResult(HostError::kFailed), status);
// Cause the first retry Create Connection to wait for 14s for Connection
// Complete - now 28s since the first Create Connection, bumping up on the
// retry window limit of 30s.
RunFor(std::chrono::seconds(14));
// Cause a second retry.
ASSERT_TRUE(
test_device()->SendCommandChannelPacket(kConnectionCompletePageTimeout));
// Verify higher layers have not been notified of failure until the Connection
// Complete propagates
EXPECT_EQ(ToResult(HostError::kFailed), status);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, conn_ref));
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL);
}
TEST_F(BrEdrConnectionManagerTest, DontRetryAfterWindowClosed) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// We send a first HCI Create Connection which will hang for 15s, and then
// respond on the test device with a ConnectionCompletePageTimeout event,
// which will cause a retry. The retry will hang for 16s, then will receive
// another PageTimeout response. Because this will be 31s after the initial
// HCI Create Connection, the retry window will be closed and the Connect()
// will fail.
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
// Initialize as success to verify that |callback| assigns error.
hci::Result<> status = fit::ok();
auto callback = [&status](auto cb_status, auto cb_conn_ref) {
EXPECT_FALSE(cb_conn_ref);
status = cb_status;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
RunFor(std::chrono::seconds(15));
// Higher layers should not be notified yet.
EXPECT_EQ(fit::ok(), status);
ASSERT_TRUE(
test_device()->SendCommandChannelPacket(kConnectionCompletePageTimeout));
// Create Connection will retry, and it hangs for 16s before
// ConnectionCompletePageTimeout
RunFor(std::chrono::seconds(16));
ASSERT_TRUE(
test_device()->SendCommandChannelPacket(kConnectionCompletePageTimeout));
RunUntilIdle();
// Create Connection will *not* be tried again as we are outside of the retry
// window.
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::PAGE_TIMEOUT), status);
}
TEST_F(BrEdrConnectionManagerTest,
ConnectSecondPeerFirstFailsWithPageTimeoutAndDoesNotRetry) {
auto* peer_a = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
auto* peer_b = peer_cache()->NewPeer(kTestDevAddr2, /*connectable=*/true);
// First peer's Create Connection Request will complete with a page timeout
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionCompletePageTimeout);
// Immediately enqueue successful connection request to peer_b, without any
// retry in between for the Connect() call to peer_a.
QueueSuccessfulCreateConnection(peer_b, kConnectionHandle2);
QueueSuccessfulInterrogation(peer_b->address(), kConnectionHandle2);
QueueDisconnection(kConnectionHandle2);
// Initialize as success to verify that |callback_a| assigns failure.
hci::Result<> status_a = fit::ok();
auto callback_a = [&status_a](auto cb_status, auto cb_conn_ref) {
status_a = cb_status;
EXPECT_FALSE(cb_conn_ref);
};
// Initialize as error to verify that |callback_b| assigns success.
hci::Result<> status_b = ToResult(HostError::kFailed);
BrEdrConnection* connection = nullptr;
auto callback_b = [&status_b, &connection](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status_b = cb_status;
connection = std::move(cb_conn_ref);
};
// Launch one request, which will cause a Connection Complete: page timeout
// controller event.
EXPECT_TRUE(connmgr()->Connect(peer_a->identifier(), callback_a));
EXPECT_TRUE(IsInitializing(peer_a));
// Launch second inflight request (this will wait for the first)
EXPECT_TRUE(connmgr()->Connect(peer_b->identifier(), callback_b));
EXPECT_TRUE(IsInitializing(peer_b));
// Run the loop which should complete both requests
RunUntilIdle();
// The Connect() request to peer_a should fail with the Page Timeout status
// code without retrying
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::PAGE_TIMEOUT),
status_a);
EXPECT_EQ(fit::ok(), status_b);
EXPECT_TRUE(HasConnectionTo(peer_b, connection));
EXPECT_TRUE(IsNotConnected(peer_a));
EXPECT_FALSE(IsNotConnected(peer_b));
}
TEST_F(BrEdrConnectionManagerTest, DisconnectPendingConnections) {
auto* peer_a = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
auto* peer_b = peer_cache()->NewPeer(kTestDevAddr2, /*connectable=*/true);
// Enqueue first connection request (which will await Connection Complete)
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnectionCancel,
&kCreateConnectionCancelRsp,
&kConnectionCompleteCanceled);
// No-op connection callbacks
auto callback_a = [](auto, auto) {};
auto callback_b = [](auto, auto) {};
// Launch both requests (second one is queued. Neither completes.)
EXPECT_TRUE(connmgr()->Connect(peer_a->identifier(), callback_a));
EXPECT_TRUE(connmgr()->Connect(peer_b->identifier(), callback_b));
// Put the first connection into flight.
RETURN_IF_FATAL(RunUntilIdle());
ASSERT_TRUE(IsInitializing(peer_a));
ASSERT_TRUE(IsInitializing(peer_b));
EXPECT_FALSE(connmgr()->Disconnect(peer_a->identifier(),
DisconnectReason::kApiRequest));
EXPECT_FALSE(connmgr()->Disconnect(peer_b->identifier(),
DisconnectReason::kApiRequest));
}
TEST_F(GAP_BrEdrConnectionManagerTest, DisconnectCooldownIncoming) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
// Peer successfully connects to us.
QueueSuccessfulIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// Disconnect locally from an API Request.
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
// Peer tries to connect to us. We should reject the connection.
auto status_event =
testing::CommandStatusPacket(hci_spec::kRejectConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto reject_packet = testing::RejectConnectionRequestPacket(
kTestDevAddr,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR);
EXPECT_CMD_PACKET_OUT(test_device(), reject_packet, &status_event);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
// After the cooldown time, a successful incoming connection can happen.
RunFor(BrEdrConnectionManager::kLocalDisconnectCooldownDuration);
QueueRepeatIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// Can still connect out if we disconnect locally.
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
QueueSuccessfulCreateConnection(peer, kConnectionHandle);
// Interrogation is short because the peer is already known
QueueShortInterrogation(kConnectionHandle);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* connection = nullptr;
auto callback = [&status, &connection](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
connection = std::move(cb_conn_ref);
};
// Launch request.
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
// Complete connection.
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_EQ(fit::ok(), status);
EXPECT_TRUE(HasConnectionTo(peer, connection));
EXPECT_FALSE(IsNotConnected(peer));
// Remote disconnections can reconnect immediately
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
QueueRepeatIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// If the reason is not kApiRequest, then the remote peer can reconnect
// immediately.
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(connmgr()->Disconnect(peer->identifier(),
DisconnectReason::kPairingFailed));
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
QueueRepeatIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// Queue disconnection for teardown.
QueueDisconnection(kConnectionHandle);
}
TEST_F(GAP_BrEdrConnectionManagerTest, DisconnectCooldownCancelOnOutgoing) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
// Peer successfully connects to us.
QueueSuccessfulIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// Disconnect locally from an API Request.
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
// Peer tries to connect to us. We should reject the connection.
auto status_event =
testing::CommandStatusPacket(hci_spec::kRejectConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto reject_packet = testing::RejectConnectionRequestPacket(
kTestDevAddr,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR);
EXPECT_CMD_PACKET_OUT(test_device(), reject_packet, &status_event);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(IsNotConnected(peer));
// If we initiate a connection out, then an incoming connection can succeed,
// even if we fail to make the connection out.
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRspError);
// Initialize as ok to verify that |callback| assigns failure
hci::Result<> status = fit::ok();
EXPECT_TRUE(
connmgr()->Connect(peer->identifier(), CALLBACK_EXPECT_FAILURE(status)));
EXPECT_TRUE(IsInitializing(peer));
RunUntilIdle();
// The outgoing connection failed to succeed
EXPECT_TRUE(IsNotConnected(peer));
// but an incoming connection can now succeed, since our intent is to connect
// now
QueueRepeatIncomingConn(kTestDevAddr);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_FALSE(IsNotConnected(peer));
// Queue disconnection for teardown.
QueueDisconnection(kConnectionHandle);
}
// If SDP channel creation fails, null channel should be caught and
// not be dereferenced. Search should fail to return results.
TEST_F(BrEdrConnectionManagerTest, SDPChannelCreationFailsGracefully) {
constexpr l2cap::ChannelId kLocalCId = 0x40;
constexpr l2cap::ChannelId kRemoteCId = 0x41;
// Channel creation should fail.
l2cap()->set_channel_callback(
[](auto new_chan) { ASSERT_FALSE(new_chan.is_alive()); });
// Since SDP channel creation fails, search_cb should not be called by SDP.
auto search_cb = [&](auto id, const auto& attributes) { FAIL(); };
connmgr()->AddServiceSearch(
sdp::profile::kAudioSink, {sdp::kServiceId}, search_cb);
QueueSuccessfulIncomingConn();
l2cap()->set_simulate_open_channel_failure(true);
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, l2cap::kSDP, kLocalCId, kRemoteCId, kChannelParams);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// Peer should still connect successfully.
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
EXPECT_FALSE(IsNotConnected(peer));
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
EXPECT_TRUE(IsNotConnected(peer));
}
TEST_F(
BrEdrConnectionManagerTest,
PendingPacketsNotClearedOnDisconnectAndClearedOnDisconnectionCompleteEvent) {
constexpr size_t kMaxNumPackets = 1;
ASSERT_EQ(kMaxNumPackets, kBrEdrBufferInfo.max_num_packets());
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle2));
QueueSuccessfulIncomingConn(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn(kTestDevAddr2, kConnectionHandle2);
test_device()->SendCommandChannelPacket(
testing::ConnectionRequestPacket(kTestDevAddr2));
RunUntilIdle();
auto* peer2 = peer_cache()->FindByAddress(kTestDevAddr2);
ASSERT_TRUE(peer2);
EXPECT_EQ(peer2->identifier(), connmgr()->GetPeerId(kConnectionHandle2));
EXPECT_EQ(2 * kIncomingConnTransactions, transaction_count());
size_t packet_count = 0;
test_device()->SetDataCallback([&](const auto&) { packet_count++; });
// Should register connection with ACL Data Channel.
hci::FakeAclConnection connection_0(
acl_data_channel(), kConnectionHandle, LinkType::kACL);
hci::FakeAclConnection connection_1(
acl_data_channel(), kConnectionHandle2, LinkType::kACL);
acl_data_channel()->RegisterConnection(connection_0.GetWeakPtr());
acl_data_channel()->RegisterConnection(connection_1.GetWeakPtr());
EXPECT_ACL_PACKET_OUT(test_device(),
StaticByteBuffer(
// ACL data header (handle: 0, length 1)
LowerBits(kConnectionHandle),
UpperBits(kConnectionHandle),
// payload length
0x01,
0x00,
// payload
1));
// Create packet to send on |acl_connection_0|
hci::ACLDataPacketPtr packet_0 = hci::ACLDataPacket::New(
kConnectionHandle,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet_0->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(1);
connection_0.QueuePacket(std::move(packet_0));
RunUntilIdle();
EXPECT_ACL_PACKET_OUT(test_device(),
StaticByteBuffer(
// ACL data header (handle: 0, length 1)
LowerBits(kConnectionHandle2),
UpperBits(kConnectionHandle2),
// payload length
0x01,
0x00,
// payload
1));
hci::ACLDataPacketPtr packet_1 = hci::ACLDataPacket::New(
kConnectionHandle2,
hci_spec::ACLPacketBoundaryFlag::kFirstNonFlushable,
hci_spec::ACLBroadcastFlag::kPointToPoint,
/*payload_size=*/1);
packet_1->mutable_view()->mutable_payload_data()[0] = static_cast<uint8_t>(1);
connection_1.QueuePacket(std::move(packet_1));
RunUntilIdle();
EXPECT_EQ(connection_0.queued_packets().size(), 0u);
EXPECT_EQ(connection_1.queued_packets().size(), 1u);
EXPECT_FALSE(test_device()->AllExpectedDataPacketsSent());
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnect, &kDisconnectRsp);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RunUntilIdle();
// Packet for |kConnectionHandle2| should not have been sent before
// Disconnection Complete event.
EXPECT_EQ(connection_0.queued_packets().size(), 0u);
EXPECT_EQ(connection_1.queued_packets().size(), 1u);
EXPECT_FALSE(test_device()->AllExpectedDataPacketsSent());
acl_data_channel()->UnregisterConnection(kConnectionHandle);
test_device()->SendCommandChannelPacket(kDisconnectionComplete);
RunUntilIdle();
EXPECT_TRUE(IsNotConnected(peer));
// Disconnection Complete handler should clear controller packet counts, so
// packet for |kConnectionHandle2| should be sent
EXPECT_EQ(connection_0.queued_packets().size(), 0u);
EXPECT_EQ(connection_1.queued_packets().size(), 0u);
EXPECT_TRUE(test_device()->AllExpectedDataPacketsSent());
// Connection handle |kConnectionHandle| should have been unregistered with
// ACL Data Channel.
QueueDisconnection(kConnectionHandle2);
}
TEST_F(BrEdrConnectionManagerTest, PairUnconnectedPeer) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
ASSERT_EQ(peer_cache()->count(), 1u);
uint count_cb_called = 0;
auto cb = [&count_cb_called](hci::Result<> status) {
ASSERT_EQ(ToResult(bt::HostError::kNotFound), status);
count_cb_called++;
};
connmgr()->Pair(peer->identifier(), kNoSecurityRequirements, cb);
ASSERT_EQ(count_cb_called, 1u);
}
TEST_F(BrEdrConnectionManagerTest, Pair) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
QueueSuccessfulPairing();
// Make the pairing error a "bad" error to confirm the callback is called at
// the end of the pairing process.
hci::Result<> pairing_status = ToResult(HostError::kPacketMalformed);
auto pairing_complete_cb = [&pairing_status](hci::Result<> status) {
ASSERT_EQ(fit::ok(), status);
pairing_status = status;
};
connmgr()->Pair(
peer->identifier(), kNoSecurityRequirements, pairing_complete_cb);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
RunUntilIdle();
ASSERT_EQ(fit::ok(), pairing_status);
ASSERT_TRUE(IsConnected(peer));
ASSERT_TRUE(peer->bonded());
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, PairTwice) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
QueueSuccessfulPairing();
// Make the pairing error a "bad" error to confirm the callback is called at
// the end of the pairing process.
hci::Result<> pairing_status = ToResult(HostError::kPacketMalformed);
auto pairing_complete_cb = [&pairing_status](hci::Result<> status) {
ASSERT_EQ(fit::ok(), status);
pairing_status = status;
};
connmgr()->Pair(
peer->identifier(), kNoSecurityRequirements, pairing_complete_cb);
RunUntilIdle();
ASSERT_EQ(fit::ok(), pairing_status);
ASSERT_TRUE(IsConnected(peer));
ASSERT_TRUE(peer->bonded());
pairing_status = ToResult(HostError::kPacketMalformed);
connmgr()->Pair(
peer->identifier(), kNoSecurityRequirements, pairing_complete_cb);
// Note that we do not call QueueSuccessfulPairing twice, even though we pair
// twice - this is to test that pairing on an already-paired link succeeds
// without sending any messages to the peer.
RunUntilIdle();
ASSERT_EQ(fit::ok(), pairing_status);
ASSERT_TRUE(peer->bonded());
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
OpenL2capChannelCreatesChannelWithChannelParameters) {
constexpr l2cap::Psm kPsm = l2cap::kAVDTP;
constexpr l2cap::ChannelId kLocalId = l2cap::kFirstDynamicChannelId;
l2cap::ChannelParameters params;
params.mode =
l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission;
params.max_rx_sdu_size = l2cap::kMinACLMTU;
QueueSuccessfulIncomingConn(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
QueueSuccessfulPairing();
RunUntilIdle();
l2cap()->ExpectOutboundL2capChannel(
kConnectionHandle, kPsm, kLocalId, 0x41, params);
std::optional<l2cap::ChannelInfo> chan_info;
size_t sock_cb_count = 0;
auto sock_cb = [&](auto chan) {
sock_cb_count++;
ASSERT_TRUE(chan.is_alive());
chan_info = chan->info();
};
connmgr()->OpenL2capChannel(
peer->identifier(), kPsm, kNoSecurityRequirements, params, sock_cb);
RunUntilIdle();
EXPECT_EQ(1u, sock_cb_count);
ASSERT_TRUE(chan_info);
EXPECT_EQ(*params.mode, chan_info->mode);
EXPECT_EQ(*params.max_rx_sdu_size, chan_info->max_rx_sdu_size);
QueueDisconnection(kConnectionHandle);
}
// Tests that the connection manager cleans up its connection map correctly
// following a disconnection due to encryption failure.
TEST_F(BrEdrConnectionManagerTest,
ConnectionCleanUpFollowingEncryptionFailure) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
EXPECT_TRUE(peer->temporary());
// Queue up the connection
EXPECT_CMD_PACKET_OUT(test_device(),
kCreateConnection,
&kCreateConnectionRsp,
&kConnectionComplete);
QueueSuccessfulInterrogation(peer->address(), kConnectionHandle);
QueueDisconnection(kConnectionHandle,
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
// Initialize as error to verify that |callback| assigns success.
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto callback = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(peer->bredr());
RunUntilIdle();
ASSERT_EQ(fit::ok(), status);
test_device()->SendCommandChannelPacket(testing::EncryptionChangeEventPacket(
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_MIC_FAILURE,
kConnectionHandle,
hci_spec::EncryptionStatus::kOff));
test_device()->SendCommandChannelPacket(testing::DisconnectionCompletePacket(
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_MIC_FAILURE));
RunUntilIdle();
EXPECT_TRUE(IsNotConnected(peer));
}
TEST_F(BrEdrConnectionManagerTest, OpenL2capChannelUpgradesLinkKey) {
QueueSuccessfulIncomingConn(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
FakePairingDelegate pairing_delegate_no_io(
sm::IOCapability::kNoInputNoOutput);
connmgr()->SetPairingDelegate(pairing_delegate_no_io.GetWeakPtr());
pairing_delegate_no_io.SetConfirmPairingCallback(
[&peer](PeerId peer_id, auto cb) {
EXPECT_EQ(peer->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
pairing_delegate_no_io.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
size_t sock_cb_count = 0;
auto sock_cb = [&](auto chan_sock) {
sock_cb_count++;
EXPECT_TRUE(chan_sock.is_alive());
};
// Pairing caused by missing link key.
QueueSuccessfulUnauthenticatedPairing();
constexpr auto kPsm0 = l2cap::kHIDControl;
constexpr l2cap::ChannelId kLocalId0 = l2cap::kFirstDynamicChannelId;
constexpr l2cap::ChannelId kRemoteId0 = 0x41;
l2cap()->ExpectOutboundL2capChannel(kConnectionHandle,
kPsm0,
kLocalId0,
kRemoteId0,
l2cap::ChannelParameters());
connmgr()->OpenL2capChannel(peer->identifier(),
kPsm0,
kNoSecurityRequirements,
l2cap::ChannelParameters(),
sock_cb);
RunUntilIdle();
EXPECT_EQ(1u, sock_cb_count);
// New pairing delegate with display can support authenticated pairing.
FakePairingDelegate pairing_delegate_with_display(
sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate_with_display.GetWeakPtr());
pairing_delegate_with_display.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
confirm_cb(true);
});
pairing_delegate_with_display.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Pairing caused by insufficient link key.
QueueSuccessfulPairing();
constexpr auto kPsm1 = l2cap::kHIDInteerup;
constexpr l2cap::ChannelId kLocalId1 = kLocalId0 + 1;
constexpr l2cap::ChannelId kRemoteId1 = kRemoteId0 + 1;
l2cap()->ExpectOutboundL2capChannel(kConnectionHandle,
kPsm1,
kLocalId1,
kRemoteId1,
l2cap::ChannelParameters());
connmgr()->OpenL2capChannel(peer->identifier(),
kPsm1,
kAuthSecurityRequirements,
l2cap::ChannelParameters(),
sock_cb);
RunUntilIdle();
EXPECT_EQ(2u, sock_cb_count);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, OpenL2capChannelUpgradeLinkKeyFails) {
QueueSuccessfulIncomingConn(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->identifier(), connmgr()->GetPeerId(kConnectionHandle));
FakePairingDelegate pairing_delegate_no_io(
sm::IOCapability::kNoInputNoOutput);
connmgr()->SetPairingDelegate(pairing_delegate_no_io.GetWeakPtr());
pairing_delegate_no_io.SetConfirmPairingCallback(
[&peer](PeerId peer_id, auto cb) {
EXPECT_EQ(peer->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
pairing_delegate_no_io.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
size_t sock_cb_count = 0;
auto sock_cb = [&](auto chan_sock) {
if (sock_cb_count == 0) {
EXPECT_TRUE(chan_sock.is_alive());
} else {
// Second OpenL2capChannel fails due to insufficient security.
EXPECT_FALSE(chan_sock.is_alive());
}
sock_cb_count++;
};
// Initial pairing.
QueueSuccessfulUnauthenticatedPairing();
constexpr auto kPsm0 = l2cap::kHIDControl;
constexpr l2cap::ChannelId kLocalId = l2cap::kFirstDynamicChannelId;
constexpr l2cap::ChannelId kRemoteId = 0x41;
l2cap()->ExpectOutboundL2capChannel(kConnectionHandle,
kPsm0,
kLocalId,
kRemoteId,
l2cap::ChannelParameters());
connmgr()->OpenL2capChannel(peer->identifier(),
kPsm0,
kNoSecurityRequirements,
l2cap::ChannelParameters(),
sock_cb);
RunUntilIdle();
EXPECT_EQ(1u, sock_cb_count);
// Pairing caused by insufficient link key.
QueueSuccessfulUnauthenticatedPairing();
constexpr auto kPsm1 = l2cap::kHIDInteerup;
connmgr()->OpenL2capChannel(peer->identifier(),
kPsm1,
kAuthSecurityRequirements,
l2cap::ChannelParameters(),
sock_cb);
RunUntilIdle();
EXPECT_EQ(2u, sock_cb_count);
// Pairing should not be attempted a third time.
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
OpenScoConnectionWithoutExistingBrEdrConnectionFails) {
std::optional<sco::ScoConnectionManager::OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto result) {
conn_result = std::move(result);
};
auto handle = connmgr()->OpenScoConnection(
PeerId(1),
{bt::StaticPacket<
pw::bluetooth::emboss::SynchronousConnectionParametersWriter>{}},
std::move(conn_cb));
EXPECT_FALSE(handle.has_value());
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_error());
EXPECT_EQ(conn_result->error_value(), HostError::kNotFound);
}
TEST_F(BrEdrConnectionManagerTest, OpenScoConnectionInitiator) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
kScoConnection;
kScoConnection.SetToZeros();
constexpr hci_spec::ConnectionHandle kScoConnectionHandle = 0x41;
auto setup_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedSetupSynchronousConnection,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto conn_complete_packet = testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
peer->address(),
hci_spec::LinkType::kExtendedSCO,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedSetupSynchronousConnectionPacket(kConnectionHandle, {}),
&setup_status_packet,
&conn_complete_packet);
std::optional<sco::ScoConnectionManager::OpenConnectionResult> conn_result;
auto conn_cb = [&conn_result](auto result) {
conn_result = std::move(result);
};
auto req_handle = connmgr()->OpenScoConnection(
peer->identifier(), {kScoConnection}, std::move(conn_cb));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value()->handle(), kScoConnectionHandle);
// Disconnecting from a peer should first disconnect SCO connections, then
// disconnect the ACL connection.
QueueDisconnection(kScoConnectionHandle);
QueueDisconnection(kConnectionHandle);
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest);
RunUntilIdle();
}
class ScoLinkTypesTest
: public BrEdrConnectionManagerTest,
public ::testing::WithParamInterface<hci_spec::LinkType> {};
TEST_P(ScoLinkTypesTest, OpenScoConnectionResponder) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
sco_conn_params;
if (GetParam() == hci_spec::LinkType::kSCO) {
sco_conn_params.view().packet_types().hv3().Write(true);
} else {
sco_conn_params.view().packet_types().ev3().Write(true);
}
std::optional<sco::ScoConnectionManager::AcceptConnectionResult> conn_result;
auto conn_cb =
[&conn_result](sco::ScoConnectionManager::AcceptConnectionResult result) {
EXPECT_TRUE(result.is_ok());
conn_result = std::move(result);
};
auto req_handle = connmgr()->AcceptScoConnection(
peer->identifier(), {sco_conn_params}, std::move(conn_cb));
auto conn_req_packet = testing::ConnectionRequestPacket(
peer->address(), /*link_type=*/GetParam());
test_device()->SendCommandChannelPacket(conn_req_packet);
auto accept_status_packet = testing::CommandStatusPacket(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::EnhancedAcceptSynchronousConnectionRequestPacket(
peer->address(), sco_conn_params),
&accept_status_packet);
RunUntilIdle();
EXPECT_FALSE(conn_result.has_value());
constexpr hci_spec::ConnectionHandle kScoConnectionHandle = 0x41;
test_device()->SendCommandChannelPacket(
testing::SynchronousConnectionCompletePacket(
kScoConnectionHandle,
peer->address(),
/*link_type=*/GetParam(),
pw::bluetooth::emboss::StatusCode::SUCCESS));
RunUntilIdle();
ASSERT_TRUE(conn_result.has_value());
ASSERT_TRUE(conn_result->is_ok());
EXPECT_EQ(conn_result->value().first->handle(), kScoConnectionHandle);
// Disconnecting from a peer should first disconnect SCO connections, then
// disconnect the ACL connection.
QueueDisconnection(kScoConnectionHandle);
QueueDisconnection(kConnectionHandle);
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest);
RunUntilIdle();
}
INSTANTIATE_TEST_SUITE_P(BrEdrConnectionManagerTest,
ScoLinkTypesTest,
::testing::Values(hci_spec::LinkType::kSCO,
hci_spec::LinkType::kExtendedSCO));
class UnconnectedLinkTypesTest
: public BrEdrConnectionManagerTest,
public ::testing::WithParamInterface<hci_spec::LinkType> {};
// Test that an unexpected SCO connection request is rejected for
// kUnacceptableConnectionParameters
TEST_P(UnconnectedLinkTypesTest, RejectUnsupportedSCOConnectionRequests) {
auto status_event = testing::CommandStatusPacket(
hci_spec::kRejectSynchronousConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto complete_event = testing::ConnectionCompletePacket(
kTestDevAddr,
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::UNACCEPTABLE_CONNECTION_PARAMETERS);
EXPECT_CMD_PACKET_OUT(test_device(),
testing::RejectSynchronousConnectionRequest(
kTestDevAddr,
pw::bluetooth::emboss::StatusCode::
UNACCEPTABLE_CONNECTION_PARAMETERS),
&status_event,
&complete_event);
test_device()->SendCommandChannelPacket(
testing::ConnectionRequestPacket(kTestDevAddr, /*link_type=*/GetParam()));
RunUntilIdle();
}
INSTANTIATE_TEST_SUITE_P(BrEdrConnectionManagerTest,
UnconnectedLinkTypesTest,
::testing::Values(hci_spec::LinkType::kSCO,
hci_spec::LinkType::kExtendedSCO));
// Test that an unexpected link type connection request is rejected for
// kUnsupportedFeatureOrParameter
TEST_F(BrEdrConnectionManagerTest, RejectUnsupportedConnectionRequest) {
auto linktype = static_cast<hci_spec::LinkType>(0x09);
auto status_event =
testing::CommandStatusPacket(hci_spec::kRejectConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto complete_event = testing::ConnectionCompletePacket(
kTestDevAddr,
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER);
EXPECT_CMD_PACKET_OUT(
test_device(),
testing::RejectConnectionRequestPacket(
kTestDevAddr,
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER),
&status_event,
&complete_event);
test_device()->SendCommandChannelPacket(
testing::ConnectionRequestPacket(kTestDevAddr, linktype));
RunUntilIdle();
}
TEST_F(BrEdrConnectionManagerTest, IncomingConnectionRacesOutgoing) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
ASSERT_TRUE(peer->bredr() && IsNotConnected(peer));
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto should_succeed = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
// We expect this callback to be executed, with a succesful connection
EXPECT_TRUE(cb_conn_ref);
EXPECT_EQ(fit::ok(), cb_status);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
// A client calls Connect() for the Peer, beginning an outgoing connection. We
// expect a CreateConnection, and ack with a status response but don't
// complete yet
EXPECT_CMD_PACKET_OUT(
test_device(), kCreateConnection, &kCreateConnectionRsp);
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), should_succeed));
// Meanwhile, an incoming connection is requested from the Peer
test_device()->SendCommandChannelPacket(kConnectionRequest);
// We expect it to be accepted, and then return a command status response, but
// not a ConnectionComplete event yet
EXPECT_CMD_PACKET_OUT(
test_device(), kAcceptConnectionRequest, &kAcceptConnectionRequestRsp);
RunUntilIdle();
// The controller now establishes the link, but will respond to the outgoing
// connection with the hci error: `ConnectionAlreadyExists` First, the
// controller notifies us of the failed outgoing connection - as from its
// perspective, we've already connected
const auto complete_already = testing::ConnectionCompletePacket(
kTestDevAddr,
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS);
test_device()->SendCommandChannelPacket(complete_already);
// Then the controller notifies us of the successful incoming connection
test_device()->SendCommandChannelPacket(kConnectionComplete);
// We expect to connect and begin interrogation, and for our connect()
// callback to have been run
QueueSuccessfulInterrogation(kTestDevAddr, kConnectionHandle);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
// Peers are marked as initializing until a pairing procedure finishes.
EXPECT_TRUE(IsInitializing(peer));
// Prepare for disconnection upon teardown.
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, OutgoingConnectionRacesIncoming) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
ASSERT_TRUE(peer->bredr() && IsNotConnected(peer));
hci::Result<> status = ToResult(HostError::kFailed);
BrEdrConnection* conn_ref = nullptr;
auto should_succeed = [&status, &conn_ref](auto cb_status, auto cb_conn_ref) {
EXPECT_TRUE(cb_conn_ref);
EXPECT_EQ(fit::ok(), cb_status);
status = cb_status;
conn_ref = std::move(cb_conn_ref);
};
// An incoming connection is requested from the Peer
test_device()->SendCommandChannelPacket(kConnectionRequest);
// We expect it to be accepted, and then return a command status response, but
// not a ConnectionComplete event yet
EXPECT_CMD_PACKET_OUT(
test_device(), kAcceptConnectionRequest, &kAcceptConnectionRequestRsp);
RunUntilIdle();
// Meanwhile, a client calls Connect() for the peer. We don't expect any
// packets out as the connection manager will defer requests that have an
// active incoming request. Instead, this request will be completed when the
// incoming procedure completes.
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), should_succeed));
// We should still expect to connect
RunUntilIdle();
// The controller now notifies us of the complete incoming connection
test_device()->SendCommandChannelPacket(kConnectionComplete);
// We expect to connect and begin interrogation, and for the callback passed
// to Connect() to have been executed when the incoming connection succeeded.
QueueSuccessfulInterrogation(kTestDevAddr, kConnectionHandle);
RunUntilIdle();
EXPECT_EQ(fit::ok(), status);
// Peers are marked as initializing until a pairing procedure finishes.
EXPECT_TRUE(IsInitializing(peer));
// Prepare for disconnection upon teardown.
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
DuplicateIncomingConnectionsFromSamePeerRejected) {
auto* peer = peer_cache()->NewPeer(kTestDevAddr, /*connectable=*/true);
ASSERT_TRUE(peer->bredr() && IsNotConnected(peer));
// Our first request should be accepted - we send back a success status, not
// the connection complete yet
EXPECT_CMD_PACKET_OUT(
test_device(), kAcceptConnectionRequest, &kAcceptConnectionRequestRsp);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto status_event =
testing::CommandStatusPacket(hci_spec::kRejectConnectionRequest,
pw::bluetooth::emboss::StatusCode::SUCCESS);
auto complete_error = testing::ConnectionCompletePacket(
kTestDevAddr,
kConnectionHandle,
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER);
auto reject_packet = testing::RejectConnectionRequestPacket(
kTestDevAddr,
pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR);
// Our second request should be rejected - we already have an incoming request
EXPECT_CMD_PACKET_OUT(test_device(), reject_packet, &status_event);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
QueueSuccessfulInterrogation(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(kConnectionComplete);
RunUntilIdle();
test_device()->SendCommandChannelPacket(complete_error);
RunUntilIdle();
EXPECT_FALSE(IsNotConnected(peer));
// Prepare for disconnection upon teardown.
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, IncomingRequestInitializesPeer) {
// Initially, we should not have a peer for the given address
auto peer = peer_cache()->FindByAddress(kTestDevAddr);
EXPECT_FALSE(peer);
// Send a request, and once accepted send back a success status but not the
// connection complete yet
EXPECT_CMD_PACKET_OUT(
test_device(), kAcceptConnectionRequest, &kAcceptConnectionRequestRsp);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// We should now have a peer in the cache to track our incoming request
// address The peer is marked as 'Initializing` immediately.
peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(peer->bredr());
ASSERT_EQ(peer->bredr()->connection_state(),
Peer::ConnectionState::kInitializing);
}
#ifndef NINSPECT
TEST_F(BrEdrConnectionManagerTest, Inspect) {
connmgr()->AttachInspect(inspector().GetRoot(), "bredr_connection_manager");
// Don't receive connection complete yet in order to keep request pending.
EXPECT_CMD_PACKET_OUT(test_device(),
testing::AcceptConnectionRequestPacket(kTestDevAddr),
&kAcceptConnectionRequestRsp);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto requests_one_request_matcher = AllOf(
NodeMatches(NameMatches("connection_requests")),
ChildrenMatch(ElementsAre(NodeMatches(NameMatches("request_0x0")))));
auto conn_mgr_with_request_matcher = AllOf(
NodeMatches(NameMatches("bredr_connection_manager")),
ChildrenMatch(::testing::IsSupersetOf({requests_one_request_matcher})));
EXPECT_THAT(inspect::ReadFromVmo(inspector().DuplicateVmo()).value(),
ChildrenMatch(ElementsAre(conn_mgr_with_request_matcher)));
QueueSuccessfulInterrogation(kTestDevAddr, kConnectionHandle);
const auto connection_complete =
testing::ConnectionCompletePacket(kTestDevAddr, kConnectionHandle);
test_device()->SendCommandChannelPacket(connection_complete);
RunUntilIdle();
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_EQ(peer->bredr()->connection_state(),
Peer::ConnectionState::kInitializing);
auto empty_requests_matcher =
AllOf(NodeMatches(NameMatches("connection_requests")),
ChildrenMatch(::testing::IsEmpty()));
auto connection_matcher =
NodeMatches(AllOf(NameMatches("connection_0x1"),
PropertyList(ElementsAre(StringIs(
"peer_id", peer->identifier().ToString())))));
auto connections_matcher =
AllOf(NodeMatches(NameMatches("connections")),
ChildrenMatch(ElementsAre(connection_matcher)));
auto recent_conn_list_matcher =
AllOf(NodeMatches(NameMatches("last_disconnected")),
ChildrenMatch(::testing::IsEmpty()));
auto incoming_matcher =
AllOf(NodeMatches(AllOf(NameMatches("incoming"),
PropertyList(UnorderedElementsAre(
UintIs("connection_attempts", 1),
UintIs("failed_connections", 0),
UintIs("successful_connections", 0))))));
auto outgoing_matcher =
AllOf(NodeMatches(AllOf(NameMatches("outgoing"),
PropertyList(UnorderedElementsAre(
UintIs("connection_attempts", 0),
UintIs("failed_connections", 0),
UintIs("successful_connections", 0))))));
auto conn_mgr_matcher = AllOf(
NodeMatches(AllOf(NameMatches("bredr_connection_manager"),
PropertyList(UnorderedElementsAre(
UintIs("disconnect_acl_link_error_count", 0),
UintIs("disconnect_interrogation_failed_count", 0),
UintIs("disconnect_local_api_request_count", 0),
UintIs("disconnect_pairing_failed_count", 0),
UintIs("disconnect_peer_disconnection_count", 0),
UintIs("interrogation_complete_count", 1),
StringIs("security_mode", "Mode 4"))))),
ChildrenMatch(UnorderedElementsAre(empty_requests_matcher,
connections_matcher,
recent_conn_list_matcher,
incoming_matcher,
outgoing_matcher)));
auto hierarchy = inspect::ReadFromVmo(inspector().DuplicateVmo());
EXPECT_THAT(hierarchy.value(), ChildrenMatch(ElementsAre(conn_mgr_matcher)));
// Delay disconnect so connection has non-zero duration.
RunFor(std::chrono::seconds(1));
QueueDisconnection(kConnectionHandle);
EXPECT_TRUE(
connmgr()->Disconnect(peer->identifier(), DisconnectReason::kApiRequest));
RunUntilIdle();
auto incoming_matcher_after_disconnect =
AllOf(NodeMatches(AllOf(NameMatches("incoming"),
PropertyList(UnorderedElementsAre(
UintIs("connection_attempts", 1),
UintIs("failed_connections", 0),
UintIs("successful_connections", 0))))));
auto requests_matcher = AllOf(NodeMatches(NameMatches("connection_requests")),
ChildrenMatch(::testing::IsEmpty()));
auto connections_after_disconnect_matcher =
AllOf(NodeMatches(NameMatches("connections")),
ChildrenMatch(::testing::IsEmpty()));
auto recent_conn_list_after_disconnect_matcher =
AllOf(NodeMatches(NameMatches("last_disconnected")),
ChildrenMatch(ElementsAre(NodeMatches(
AllOf(NameMatches("0"),
PropertyList(UnorderedElementsAre(
StringIs("peer_id", peer->identifier().ToString()),
UintIs("duration_s", 1u),
IntIs("@time", 1'000'000'000))))))));
auto conn_mgr_after_disconnect_matcher = AllOf(
NodeMatches(AllOf(NameMatches("bredr_connection_manager"),
PropertyList(UnorderedElementsAre(
UintIs("disconnect_acl_link_error_count", 0),
UintIs("disconnect_interrogation_failed_count", 0),
UintIs("disconnect_local_api_request_count", 1),
UintIs("disconnect_pairing_failed_count", 0),
UintIs("disconnect_peer_disconnection_count", 0),
UintIs("interrogation_complete_count", 1),
StringIs("security_mode", "Mode 4"))))),
ChildrenMatch(
UnorderedElementsAre(empty_requests_matcher,
connections_after_disconnect_matcher,
outgoing_matcher,
incoming_matcher_after_disconnect,
recent_conn_list_after_disconnect_matcher)));
hierarchy = inspect::ReadFromVmo(inspector().DuplicateVmo());
EXPECT_THAT(hierarchy.value(),
ChildrenMatch(ElementsAre(conn_mgr_after_disconnect_matcher)));
}
#endif // NINSPECT
TEST_F(BrEdrConnectionManagerTest, RoleChangeAfterInboundConnection) {
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->bredr()->connection_state(),
Peer::ConnectionState::kInitializing);
// Request an outbound connection in order to get a pointer to the existing
// connection. No packets should be sent.
BrEdrConnection* conn_ref = nullptr;
auto callback = [&conn_ref](auto /*status*/, auto cb_conn_ref) {
conn_ref = cb_conn_ref;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(conn_ref);
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::PERIPHERAL);
test_device()->SendCommandChannelPacket(testing::RoleChangePacket(
kTestDevAddr, pw::bluetooth::emboss::ConnectionRole::CENTRAL));
RunUntilIdle();
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest,
RoleChangeWithFailureStatusAfterInboundConnection) {
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->bredr()->connection_state(),
Peer::ConnectionState::kInitializing);
// Request an outbound connection in order to get a pointer to the existing
// connection. No packets should be sent.
BrEdrConnection* conn_ref = nullptr;
auto callback = [&conn_ref](auto /*status*/, auto cb_conn_ref) {
conn_ref = cb_conn_ref;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(conn_ref);
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::PERIPHERAL);
test_device()->SendCommandChannelPacket(testing::RoleChangePacket(
kTestDevAddr,
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
pw::bluetooth::emboss::StatusCode::UNSPECIFIED_ERROR));
RunUntilIdle();
// The role should not change.
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::PERIPHERAL);
QueueDisconnection(kConnectionHandle);
}
TEST_F(BrEdrConnectionManagerTest, RoleChangeDuringInboundConnectionProcedure) {
EXPECT_EQ(kInvalidPeerId, connmgr()->GetPeerId(kConnectionHandle));
QueueSuccessfulIncomingConn(
kTestDevAddr,
kConnectionHandle,
/*role_change=*/pw::bluetooth::emboss::ConnectionRole::CENTRAL);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
auto* peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
EXPECT_EQ(peer->bredr()->connection_state(),
Peer::ConnectionState::kInitializing);
// Request an outbound connection in order to get a pointer to the existing
// connection. No packets should be sent.
BrEdrConnection* conn_ref = nullptr;
auto callback = [&conn_ref](auto /*status*/, auto cb_conn_ref) {
conn_ref = cb_conn_ref;
};
EXPECT_TRUE(connmgr()->Connect(peer->identifier(), callback));
ASSERT_TRUE(conn_ref);
EXPECT_EQ(conn_ref->link().role(),
pw::bluetooth::emboss::ConnectionRole::CENTRAL);
QueueDisconnection(kConnectionHandle);
}
// Peer and local Secure Connections (SC) are supported and key is of SC type
TEST_F(BrEdrConnectionManagerTest,
SecureConnectionsSupportedCorrectLinkKeyTypeSucceeds) {
const auto kReadRemoteExtended2Complete = StaticByteBuffer(
hci_spec::kReadRemoteExtendedFeaturesCompleteEventCode,
0x0D, // parameter_total_size (13 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle,
0x02, // page_number
0x02, // max_page_number
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
// lmp_features_page2: Secure Connections (Controller Support)
);
const auto kLinkKeyNotification = MakeLinkKeyNotification(
hci_spec::LinkKeyType::kAuthenticatedCombination256);
const StaticByteBuffer kEncryptionChangeEvent(
hci_spec::kEncryptionChangeEventCode,
4, // parameter total size
0x00, // status
0xAA,
0x0B, // connection handle
0x02 // encryption enabled: AES-CCM for BR/EDR
);
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Trigger inbound connection and respond to interrogation. LMP features are
// set to support peer host and controller Secure Connections.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp,
&kReadRemoteSupportedFeaturesComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended2Complete);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// Ensure that the interrogation has begun but the peer hasn't yet bonded
EXPECT_EQ(6, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bredr()->bonded());
// Approve pairing requests
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initiate pairing from the peer before interrogation completes
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification);
EXPECT_CMD_PACKET_OUT(test_device(),
kSetConnectionEncryption,
&kSetConnectionEncryptionRsp,
&kEncryptionChangeEvent);
EXPECT_CMD_PACKET_OUT(
test_device(), kReadEncryptionKeySize, &kReadEncryptionKeySizeRsp);
RETURN_IF_FATAL(RunUntilIdle());
EXPECT_TRUE(l2cap()->IsLinkConnected(kConnectionHandle));
QueueDisconnection(kConnectionHandle);
}
// Peer and local Secure Connections (SC) are supported, but key is not of SC
// type
TEST_F(BrEdrConnectionManagerTest,
SecureConnectionsSupportedIncorrectLinkKeyTypeFails) {
const auto kReadRemoteExtended2Complete = StaticByteBuffer(
hci_spec::kReadRemoteExtendedFeaturesCompleteEventCode,
0x0D, // parameter_total_size (13 bytes)
pw::bluetooth::emboss::StatusCode::SUCCESS, // status
0xAA,
0x0B, // connection_handle,
0x02, // page_number
0x02, // max_page_number
0x00,
0x01,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00
// lmp_features_page2: Secure Connections (Controller Support)
);
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Trigger inbound connection and respond to interrogation. LMP features are
// set to support peer host and controller Secure Connections.
EXPECT_CMD_PACKET_OUT(test_device(),
kAcceptConnectionRequest,
&kAcceptConnectionRequestRsp,
&kConnectionComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kRemoteNameRequest,
&kRemoteNameRequestRsp,
&kRemoteNameRequestComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteVersionInfo,
&kReadRemoteVersionInfoRsp,
&kRemoteVersionInfoComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
hci_spec::kReadRemoteSupportedFeatures,
&kReadRemoteSupportedFeaturesRsp,
&kReadRemoteSupportedFeaturesComplete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended1,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended1Complete);
EXPECT_CMD_PACKET_OUT(test_device(),
kReadRemoteExtended2,
&kReadRemoteExtendedFeaturesRsp,
&kReadRemoteExtended2Complete);
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
// Ensure that the interrogation has begun but the peer hasn't yet bonded
EXPECT_EQ(6, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bredr()->bonded());
// Approve pairing requests
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
// Initiate pairing from the peer
test_device()->SendCommandChannelPacket(MakeIoCapabilityResponse(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING));
test_device()->SendCommandChannelPacket(kIoCapabilityRequest);
const auto kUserConfirmationRequest = MakeUserConfirmationRequest(kPasskey);
EXPECT_CMD_PACKET_OUT(test_device(),
MakeIoCapabilityRequestReply(
IoCapability::DISPLAY_YES_NO,
AuthenticationRequirements::MITM_GENERAL_BONDING),
&kIoCapabilityRequestReplyRsp,
&kUserConfirmationRequest);
EXPECT_CMD_PACKET_OUT(test_device(),
kUserConfirmationRequestReply,
&kUserConfirmationRequestReplyRsp,
&kSimplePairingCompleteSuccess,
&kLinkKeyNotification);
// Connection terminates because kLinkKeyNotification's key type is
// kAuthenticatedCombination192. When SC is supported, key type must be of SC
// type (kUnauthenticatedCombination256 or kAuthenticatedCombination256).
QueueDisconnection(kConnectionHandle);
RETURN_IF_FATAL(RunUntilIdle());
}
// Active connections that do not meeting the requirements for Secure
// Connections Only mode are disconnected when the security mode is changed to
// SC Only.
TEST_F(BrEdrConnectionManagerTest,
SecureConnectionsOnlyDisconnectsInsufficientSecurity) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
QueueSuccessfulPairing(); // kAuthenticatedCombination192 default is not of
// SC type
// Initialize as error to verify that |pairing_complete_cb| assigns success.
hci::Result<> pairing_status = ToResult(HostError::kInsufficientSecurity);
auto pairing_complete_cb = [&pairing_status](hci::Result<> status) {
ASSERT_EQ(fit::ok(), status);
pairing_status = status;
};
connmgr()->Pair(
peer->identifier(), kNoSecurityRequirements, pairing_complete_cb);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
RunUntilIdle();
ASSERT_EQ(fit::ok(), pairing_status);
ASSERT_TRUE(IsConnected(peer));
ASSERT_TRUE(peer->bonded());
// Setting Secure Connections Only mode causes connections not allowed under
// this mode to be disconnected. In this case, |peer| is encrypted,
// authenticated, but not SC-generated.
EXPECT_CMD_PACKET_OUT(test_device(), kDisconnect);
connmgr()->SetSecurityMode(BrEdrSecurityMode::SecureConnectionsOnly);
RunUntilIdle();
EXPECT_EQ(BrEdrSecurityMode::SecureConnectionsOnly,
connmgr()->security_mode());
ASSERT_TRUE(IsNotConnected(peer));
}
// Active connections that meet the requirements for Secure Connections Only
// mode are not disconnected when the security mode is changed to SC Only.
TEST_F(BrEdrConnectionManagerTest,
SecureConnectionsOnlySufficientSecuritySucceeds) {
QueueSuccessfulIncomingConn();
test_device()->SendCommandChannelPacket(kConnectionRequest);
RunUntilIdle();
EXPECT_EQ(kIncomingConnTransactions, transaction_count());
auto* const peer = peer_cache()->FindByAddress(kTestDevAddr);
ASSERT_TRUE(peer);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
connmgr()->SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Approve pairing requests.
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId, uint32_t passkey, auto method, auto confirm_cb) {
ASSERT_TRUE(confirm_cb);
confirm_cb(true);
});
pairing_delegate.SetCompletePairingCallback(
[](PeerId, sm::Result<> status) { EXPECT_EQ(fit::ok(), status); });
QueueSuccessfulPairing(hci_spec::LinkKeyType::kAuthenticatedCombination256);
// Initialize as error to verify that |pairing_complete_cb| assigns success.
hci::Result<> pairing_status = ToResult(HostError::kInsufficientSecurity);
auto pairing_complete_cb = [&pairing_status](hci::Result<> status) {
ASSERT_EQ(fit::ok(), status);
pairing_status = status;
};
connmgr()->Pair(
peer->identifier(), kNoSecurityRequirements, pairing_complete_cb);
ASSERT_TRUE(IsInitializing(peer));
ASSERT_FALSE(peer->bonded());
RunUntilIdle();
ASSERT_EQ(fit::ok(), pairing_status);
ASSERT_TRUE(IsConnected(peer));
ASSERT_TRUE(peer->bonded());
// Setting Secure Connections Only mode causes connections not allowed under
// this mode to be disconnected. In this case, |peer| is encrypted,
// authenticated, and SC-generated.
connmgr()->SetSecurityMode(BrEdrSecurityMode::SecureConnectionsOnly);
RunUntilIdle();
EXPECT_EQ(BrEdrSecurityMode::SecureConnectionsOnly,
connmgr()->security_mode());
ASSERT_TRUE(IsConnected(peer));
QueueDisconnection(kConnectionHandle);
}
#undef COMMAND_COMPLETE_RSP
#undef COMMAND_STATUS_RSP
} // namespace
} // namespace bt::gap