blob: c68bacf38a71961685b449db15cdc9afbc1bc8ea [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/gap/pairing_state.h"
#include <gtest/gtest.h>
#include "lib/gtest/test_loop_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/fake_bredr_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/mock_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/error.h"
namespace bt::gap {
namespace {
using hci::testing::FakeBrEdrConnection;
using hci_spec::AuthRequirements;
using hci_spec::IOCapability;
using hci_spec::kUserConfirmationRequestEventCode;
using hci_spec::kUserPasskeyNotificationEventCode;
using hci_spec::kUserPasskeyRequestEventCode;
const hci_spec::ConnectionHandle kTestHandle(0x0A0B);
const DeviceAddress kLocalAddress(DeviceAddress::Type::kBREDR,
{0x22, 0x11, 0x00, 0xCC, 0xBB, 0xAA});
const DeviceAddress kPeerAddress(DeviceAddress::Type::kBREDR, {0x99, 0x88, 0x77, 0xFF, 0xEE, 0xDD});
const auto kTestLocalIoCap = sm::IOCapability::kDisplayYesNo;
const auto kTestPeerIoCap = IOCapability::kDisplayOnly;
const uint32_t kTestPasskey = 123456;
const auto kTestLinkKeyValue = UInt128{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
const hci_spec::LinkKey kTestLinkKey(kTestLinkKeyValue, 0, 0);
const auto kTestUnauthenticatedLinkKeyType = hci_spec::LinkKeyType::kUnauthenticatedCombination192;
const auto kTestAuthenticatedLinkKeyType = hci_spec::LinkKeyType::kAuthenticatedCombination192;
const auto kTestLegacyLinkKeyType = hci_spec::LinkKeyType::kCombination;
const auto kTestChangedLinkKeyType = hci_spec::LinkKeyType::kChangedCombination;
const BrEdrSecurityRequirements kNoSecurityRequirements{.authentication = false,
.secure_connections = false};
void NoOpStatusCallback(hci_spec::ConnectionHandle, hci::Result<>) {}
void NoOpUserConfirmationCallback(bool) {}
void NoOpUserPasskeyCallback(std::optional<uint32_t>) {}
class NoOpPairingDelegate final : public PairingDelegate {
public:
NoOpPairingDelegate(sm::IOCapability io_capability)
: io_capability_(io_capability), weak_ptr_factory_(this) {}
fxl::WeakPtr<NoOpPairingDelegate> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
// PairingDelegate overrides that do nothing.
~NoOpPairingDelegate() override = default;
sm::IOCapability io_capability() const override { return io_capability_; }
void CompletePairing(PeerId peer_id, sm::Result<> status) override {}
void ConfirmPairing(PeerId peer_id, ConfirmCallback confirm) override {}
void DisplayPasskey(PeerId peer_id, uint32_t passkey, DisplayMethod method,
ConfirmCallback confirm) override {}
void RequestPasskey(PeerId peer_id, PasskeyResponseCallback respond) override {}
private:
const sm::IOCapability io_capability_;
fxl::WeakPtrFactory<NoOpPairingDelegate> weak_ptr_factory_;
};
using TestBase = testing::ControllerTest<testing::MockController>;
class PairingStateTest : public TestBase {
public:
PairingStateTest() = default;
virtual ~PairingStateTest() = default;
void SetUp() override {
TestBase::SetUp();
InitializeACLDataChannel();
peer_cache_ = std::make_unique<PeerCache>();
peer_ = peer_cache_->NewPeer(kPeerAddress, /*connectable=*/true);
auth_request_count_ = 0;
send_auth_request_callback_ = [this]() { auth_request_count_++; };
}
void TearDown() override {
peer_ = nullptr;
peer_cache_ = nullptr;
TestBase::TearDown();
}
fit::closure MakeAuthRequestCallback() { return send_auth_request_callback_.share(); }
std::unique_ptr<FakeBrEdrConnection> MakeFakeConnection() {
return std::make_unique<FakeBrEdrConnection>(kTestHandle, kLocalAddress, kPeerAddress,
hci_spec::ConnectionRole::kCentral,
transport()->WeakPtr());
}
PeerCache* peer_cache() const { return peer_cache_.get(); }
Peer* peer() const { return peer_; }
size_t auth_request_count() const { return auth_request_count_; }
private:
inspect::Inspector inspector_;
std::unique_ptr<PeerCache> peer_cache_;
Peer* peer_;
size_t auth_request_count_;
fit::closure send_auth_request_callback_;
};
class PairingStateDeathTest : public PairingStateTest {};
TEST_F(PairingStateTest, PairingStateStartsAsResponder) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest, PairingStateRemainsResponderAfterPeerIoCapResponse) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest, PairingStateBecomesInitiatorAfterLocalPairingInitiated) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(1u, auth_request_count());
EXPECT_TRUE(pairing_state.initiator());
}
TEST_F(PairingStateTest, PairingStateSendsAuthenticationRequestOnceForDuplicateRequest) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(1u, auth_request_count());
EXPECT_TRUE(pairing_state.initiator());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(1u, auth_request_count());
EXPECT_TRUE(pairing_state.initiator());
}
TEST_F(PairingStateTest,
PairingStateRemainsResponderIfPairingInitiatedWhileResponderPairingInProgress) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_FALSE(pairing_state.initiator());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest, StatusCallbackMayDestroyPairingState) {
auto connection = MakeFakeConnection();
std::unique_ptr<PairingState> pairing_state;
bool cb_called = false;
auto status_cb = [&pairing_state, &cb_called](hci_spec::ConnectionHandle handle,
hci::Result<> status) {
EXPECT_TRUE(status.is_error());
cb_called = true;
// Note that this lambda is owned by the PairingState so its captures are invalid after this.
pairing_state = nullptr;
};
pairing_state = std::make_unique<PairingState>(peer()->GetWeakPtr(), connection.get(),
/*link_initiated=*/false,
MakeAuthRequestCallback(), status_cb);
// Unexpected event that should cause the status callback to be called with an error.
pairing_state->OnUserPasskeyNotification(kTestPasskey);
EXPECT_TRUE(cb_called);
}
TEST_F(PairingStateTest, InitiatorCallbackMayDestroyPairingState) {
auto connection = MakeFakeConnection();
std::unique_ptr<PairingState> pairing_state = std::make_unique<PairingState>(
peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false, MakeAuthRequestCallback(),
NoOpStatusCallback);
bool cb_called = false;
auto status_cb = [&pairing_state, &cb_called](hci_spec::ConnectionHandle handle,
hci::Result<> status) {
EXPECT_TRUE(status.is_error());
cb_called = true;
// Note that this lambda is owned by the PairingState so its captures are invalid after this.
pairing_state = nullptr;
};
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state->SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state->InitiatePairing(kNoSecurityRequirements, status_cb);
// Unexpected event that should cause the status callback to be called with an error.
pairing_state->OnUserPasskeyNotification(kTestPasskey);
EXPECT_TRUE(cb_called);
}
// Test helper to inspect StatusCallback invocations.
class TestStatusHandler final {
public:
auto MakeStatusCallback() {
return [this](hci_spec::ConnectionHandle handle, hci::Result<> status) {
call_count_++;
handle_ = handle;
status_ = status;
};
}
auto call_count() const { return call_count_; }
// Returns std::nullopt if |call_count() < 1|, otherwise values from the most
// recent callback invocation.
auto& handle() const { return handle_; }
auto& status() const { return status_; }
private:
int call_count_ = 0;
std::optional<hci_spec::ConnectionHandle> handle_;
std::optional<hci::Result<>> status_;
};
TEST_F(PairingStateTest, TestStatusHandlerTracksStatusCallbackInvocations) {
TestStatusHandler handler;
EXPECT_EQ(0, handler.call_count());
EXPECT_FALSE(handler.status());
PairingState::StatusCallback status_cb = handler.MakeStatusCallback();
EXPECT_EQ(0, handler.call_count());
EXPECT_FALSE(handler.status());
status_cb(hci_spec::ConnectionHandle(0x0A0B), ToResult(hci_spec::StatusCode::kPairingNotAllowed));
EXPECT_EQ(1, handler.call_count());
ASSERT_TRUE(handler.handle());
EXPECT_EQ(hci_spec::ConnectionHandle(0x0A0B), *handler.handle());
ASSERT_TRUE(handler.status());
EXPECT_EQ(ToResult(hci_spec::StatusCode::kPairingNotAllowed), *handler.status());
}
TEST_F(PairingStateTest, InitiatingPairingAfterErrorTriggersStatusCallbackWithError) {
TestStatusHandler link_status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), link_status_handler.MakeStatusCallback());
// Unexpected event that should cause the status callback to be called with an error.
pairing_state.OnUserPasskeyNotification(kTestPasskey);
EXPECT_EQ(1, link_status_handler.call_count());
ASSERT_TRUE(link_status_handler.handle());
EXPECT_EQ(kTestHandle, *link_status_handler.handle());
ASSERT_TRUE(link_status_handler.status());
EXPECT_EQ(ToResult(HostError::kNotSupported), *link_status_handler.status());
// Try to initiate pairing again.
TestStatusHandler pairing_status_handler;
pairing_state.InitiatePairing(kNoSecurityRequirements,
pairing_status_handler.MakeStatusCallback());
// The status callback for pairing attempts made after a pairing failure should be rejected as
// canceled.
EXPECT_EQ(1, pairing_status_handler.call_count());
ASSERT_TRUE(pairing_status_handler.handle());
EXPECT_EQ(kTestHandle, *pairing_status_handler.handle());
ASSERT_TRUE(pairing_status_handler.status());
EXPECT_EQ(ToResult(HostError::kCanceled), *pairing_status_handler.status());
}
TEST_F(PairingStateTest, UnexpectedEncryptionChangeDoesNotTriggerStatusCallback) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state.OnLinkKeyRequest());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_EQ(0, connection->start_encryption_count());
ASSERT_EQ(0, status_handler.call_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, PeerMayNotChangeLinkKeyWhenNotEncrypted) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
ASSERT_FALSE(connection->ltk().has_value());
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestChangedLinkKeyType);
EXPECT_FALSE(connection->ltk().has_value());
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(PairingStateTest, PeerMayChangeLinkKeyWhenInIdleState) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
connection->set_link_key(hci_spec::LinkKey(UInt128(), 0, 0), kTestAuthenticatedLinkKeyType);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestChangedLinkKeyType);
ASSERT_TRUE(connection->ltk().has_value());
EXPECT_EQ(kTestLinkKeyValue, connection->ltk().value().value());
ASSERT_TRUE(connection->ltk_type().has_value());
EXPECT_EQ(kTestChangedLinkKeyType, connection->ltk_type().value());
EXPECT_EQ(0, status_handler.call_count());
}
// Inject events that occur during the course of a successful pairing as an initiator, but not
// including enabling link encryption.
void AdvanceToEncryptionAsInitiator(PairingState* pairing_state) {
static_cast<void>(pairing_state->OnLinkKeyRequest());
static_cast<void>(pairing_state->OnIoCapabilityRequest());
pairing_state->OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state->OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state->OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state->OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
}
TEST_F(PairingStateTest, SuccessfulEncryptionChangeTriggersStatusCallback) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
AdvanceToEncryptionAsInitiator(&pairing_state);
ASSERT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fitx::ok(), *status_handler.status());
}
TEST_F(PairingStateTest, EncryptionChangeErrorTriggersStatusCallbackWithError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
static_cast<void>(pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback));
AdvanceToEncryptionAsInitiator(&pairing_state);
ASSERT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::error(Error(HostError::kInsufficientSecurity)));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(PairingStateTest, EncryptionChangeToDisabledTriggersStatusCallbackWithError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
AdvanceToEncryptionAsInitiator(&pairing_state);
ASSERT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(false));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kFailed), *status_handler.status());
}
TEST_F(PairingStateTest, EncryptionChangeToEnableCallsInitiatorCallbacks) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
TestStatusHandler status_handler_0;
pairing_state.InitiatePairing(kNoSecurityRequirements, status_handler_0.MakeStatusCallback());
AdvanceToEncryptionAsInitiator(&pairing_state);
EXPECT_TRUE(pairing_state.initiator());
// Try to initiate pairing while pairing is in progress.
TestStatusHandler status_handler_1;
static_cast<void>(pairing_state.InitiatePairing(kNoSecurityRequirements,
status_handler_1.MakeStatusCallback()));
EXPECT_TRUE(pairing_state.initiator());
ASSERT_EQ(0, status_handler_0.call_count());
ASSERT_EQ(0, status_handler_1.call_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler_0.call_count());
EXPECT_EQ(1, status_handler_1.call_count());
ASSERT_TRUE(status_handler_0.handle());
EXPECT_EQ(kTestHandle, *status_handler_0.handle());
ASSERT_TRUE(status_handler_0.status());
EXPECT_EQ(fitx::ok(), *status_handler_0.status());
ASSERT_TRUE(status_handler_1.handle());
EXPECT_EQ(kTestHandle, *status_handler_1.handle());
ASSERT_TRUE(status_handler_1.status());
EXPECT_EQ(fitx::ok(), *status_handler_1.status());
// Errors for a new pairing shouldn't invoke the initiators' callbacks.
pairing_state.OnUserPasskeyNotification(kTestPasskey);
EXPECT_EQ(1, status_handler_0.call_count());
EXPECT_EQ(1, status_handler_1.call_count());
}
TEST_F(PairingStateTest, InitiatingPairingOnResponderWaitsForPairingToFinish) {
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), NoOpStatusCallback);
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine as pairing responder.
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Try to initiate pairing while pairing is in progress.
TestStatusHandler status_handler;
pairing_state.InitiatePairing(kNoSecurityRequirements, status_handler.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
// Keep advancing state machine.
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
EXPECT_FALSE(pairing_state.initiator());
ASSERT_EQ(0, status_handler.call_count());
// The attempt to initiate pairing should have its status callback notified.
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fitx::ok(), *status_handler.status());
// Errors for a new pairing shouldn't invoke the attempted initiator's callback.
pairing_state.OnUserPasskeyNotification(kTestPasskey);
EXPECT_EQ(1, status_handler.call_count());
}
TEST_F(PairingStateTest, UnresolvedPairingCallbackIsCalledOnDestruction) {
auto connection = MakeFakeConnection();
TestStatusHandler overall_status, request_status;
{
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), overall_status.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine as pairing responder.
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Try to initiate pairing while pairing is in progress.
pairing_state.InitiatePairing(kNoSecurityRequirements, request_status.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
// Keep advancing state machine.
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
// as pairing_state falls out of scope, we expect additional pairing callbacks to be called
ASSERT_EQ(0, overall_status.call_count());
ASSERT_EQ(0, request_status.call_count());
}
ASSERT_EQ(0, overall_status.call_count());
ASSERT_EQ(1, request_status.call_count());
ASSERT_TRUE(request_status.handle());
EXPECT_EQ(kTestHandle, *request_status.handle());
EXPECT_EQ(ToResult(HostError::kLinkDisconnected), *request_status.status());
}
TEST_F(PairingStateTest, InitiatorPairingStateRejectsIoCapReqWithoutPairingDelegate) {
auto connection = MakeFakeConnection();
TestStatusHandler owner_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), owner_status_handler.MakeStatusCallback());
TestStatusHandler initiator_status_handler;
// Advance state machine to Initiator Waiting IOCap Request
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_TRUE(pairing_state.initiator());
// We should permit the pairing state machine to continue even without a PairingDelegate, as we
// may have an existing bond to restore, which can be done without a PairingDelegate.
EXPECT_EQ(0, owner_status_handler.call_count());
EXPECT_EQ(0, initiator_status_handler.call_count());
// We will only start the pairing process if there is no stored bond
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
// We expect to be notified that there are no IOCapabilities, as there is no PairingDelegate to
// provide them
EXPECT_EQ(std::nullopt, pairing_state.OnIoCapabilityRequest());
// All callbacks should be notified of pairing failure
EXPECT_EQ(1, owner_status_handler.call_count());
EXPECT_EQ(1, initiator_status_handler.call_count());
ASSERT_TRUE(initiator_status_handler.status());
EXPECT_EQ(ToResult(HostError::kNotReady), *initiator_status_handler.status());
EXPECT_EQ(initiator_status_handler.status(), owner_status_handler.status());
}
TEST_F(PairingStateTest, ResponderPairingStateRejectsIoCapReqWithoutPairingDelegate) {
auto connection = MakeFakeConnection();
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
// Advance state machine to Responder Waiting IOCap Request
pairing_state.OnIoCapabilityResponse(hci_spec::IOCapability::kDisplayYesNo);
EXPECT_FALSE(pairing_state.initiator());
EXPECT_EQ(0, status_handler.call_count());
// We expect to be notified that there are no IOCapabilities, as there is no PairingDelegate to
// provide them.
EXPECT_EQ(std::nullopt, pairing_state.OnIoCapabilityRequest());
// All callbacks should be notified of pairing failure
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kNotReady), *status_handler.status());
}
TEST_F(PairingStateTest, UnexpectedLinkKeyAuthenticationRaisesError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
// Provide an authenticated link key when this should have resulted in an
// unauthenticated link key.
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestAuthenticatedLinkKeyType);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(PairingStateTest, LegacyPairingLinkKeyRaisesError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
// Provide a legacy pairing link key type.
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestLegacyLinkKeyType);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(PairingStateTest, PairingSetsConnectionLinkKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
ASSERT_FALSE(connection->ltk());
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_TRUE(connection->ltk());
EXPECT_EQ(kTestLinkKeyValue, connection->ltk()->value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, NumericComparisonPairingComparesPasskeyOnInitiatorDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.InitiatePairing(kNoSecurityRequirements, status_handler.MakeStatusCallback());
ASSERT_TRUE(pairing_state.initiator());
static_cast<void>(pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
pairing_delegate.SetDisplayPasskeyCallback(
[this](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(kTestPasskey, value);
EXPECT_EQ(PairingDelegate::DisplayMethod::kComparison, method);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, NumericComparisonPairingComparesPasskeyOnResponderDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetDisplayPasskeyCallback(
[this](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(kTestPasskey, value);
EXPECT_EQ(PairingDelegate::DisplayMethod::kComparison, method);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
// v5.0, Vol 3, Part C, Sec 5.2.2.6 call this "Numeric Comparison with automatic
// confirmation on device B only and Yes/No confirmation on whether to pair on
// device A. Device A does not show the confirmation value." and it should
// result in user consent.
TEST_F(PairingStateTest, NumericComparisonWithoutValueRequestsConsentFromDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, PasskeyEntryPairingDisplaysPasskeyToDisplayOnlySide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayOnly, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetDisplayPasskeyCallback(
[this](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(kTestPasskey, value);
EXPECT_EQ(PairingDelegate::DisplayMethod::kPeerEntry, method);
EXPECT_TRUE(cb);
});
pairing_state.OnUserPasskeyNotification(kTestPasskey);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, PasskeyEntryPairingRequestsPasskeyFromKeyboardOnlySide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayOnly);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kKeyboardOnly, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetRequestPasskeyCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(kTestPasskey);
});
bool cb_called = false;
std::optional<uint32_t> passkey;
auto passkey_cb = [&cb_called, &passkey](std::optional<uint32_t> pairing_state_passkey) {
cb_called = true;
passkey = pairing_state_passkey;
};
pairing_state.OnUserPasskeyRequest(std::move(passkey_cb));
EXPECT_TRUE(cb_called);
ASSERT_TRUE(passkey);
EXPECT_EQ(kTestPasskey, *passkey);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, JustWorksPairingOutgoingConnectDoesNotRequestUserActionInitiator) {
auto connection = MakeFakeConnection();
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/true,
MakeAuthRequestCallback(), owner_status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine to Initiator Waiting IOCap Request
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_TRUE(pairing_state.initiator());
static_cast<void>(pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, owner_status_handler.call_count());
EXPECT_EQ(0, initiator_status_handler.call_count());
}
TEST_F(PairingStateTest, JustWorksPairingOutgoingConnectDoesNotRequestUserActionResponder) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/true,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, JustWorksPairingIncomingConnectRequiresConfirmationRejectedResponder) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(false);
});
bool confirmed = true;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_FALSE(confirmed);
// Eventually the controller sends a SimplePairingComplete indicating the failure.
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_TRUE(status.is_error());
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(hci_spec::StatusCode::kAuthenticationFailure), *status_handler.status());
}
TEST_F(PairingStateTest, JustWorksPairingIncomingConnectRequiresConfirmationRejectedInitiator) {
auto connection = MakeFakeConnection();
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), owner_status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine to Initiator Waiting IOCap Request
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_TRUE(pairing_state.initiator());
static_cast<void>(pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(false);
});
bool confirmed = true;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_FALSE(confirmed);
// Eventually the controller sends a SimplePairingComplete indicating the failure.
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_TRUE(status.is_error());
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, owner_status_handler.call_count());
ASSERT_TRUE(owner_status_handler.status());
EXPECT_EQ(ToResult(hci_spec::StatusCode::kAuthenticationFailure), *owner_status_handler.status());
EXPECT_EQ(1, initiator_status_handler.call_count());
ASSERT_TRUE(initiator_status_handler.status());
EXPECT_EQ(ToResult(hci_spec::StatusCode::kAuthenticationFailure),
*initiator_status_handler.status());
}
TEST_F(PairingStateTest, JustWorksPairingIncomingConnectRequiresConfirmationAcceptedResponder) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, JustWorksPairingIncomingConnectRequiresConfirmationAcceptedInitiator) {
auto connection = MakeFakeConnection();
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), owner_status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine to Initiator Waiting IOCap Request
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_TRUE(pairing_state.initiator());
static_cast<void>(pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, owner_status_handler.call_count());
EXPECT_EQ(0, initiator_status_handler.call_count());
}
// Event injectors. Return values are necessarily ignored in order to make types
// match, so don't use these functions to test return values. Likewise,
// arguments have been filled with test defaults for a successful pairing flow.
void LinkKeyRequest(PairingState* pairing_state) {
static_cast<void>(pairing_state->OnLinkKeyRequest());
}
void IoCapabilityRequest(PairingState* pairing_state) {
static_cast<void>(pairing_state->OnIoCapabilityRequest());
}
void IoCapabilityResponse(PairingState* pairing_state) {
pairing_state->OnIoCapabilityResponse(kTestPeerIoCap);
}
void UserConfirmationRequest(PairingState* pairing_state) {
pairing_state->OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
}
void UserPasskeyRequest(PairingState* pairing_state) {
pairing_state->OnUserPasskeyRequest(NoOpUserPasskeyCallback);
}
void UserPasskeyNotification(PairingState* pairing_state) {
pairing_state->OnUserPasskeyNotification(kTestPasskey);
}
void SimplePairingComplete(PairingState* pairing_state) {
pairing_state->OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
}
void LinkKeyNotification(PairingState* pairing_state) {
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
}
void AuthenticationComplete(PairingState* pairing_state) {
pairing_state->OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
}
// Test suite fixture that genericizes an injected pairing state event. The
// event being tested should be retrieved with the GetParam method, which
// returns a default event injector. For example:
//
// PairingState pairing_state;
// GetParam()(&pairing_state);
//
// This is named so that the instantiated test description looks correct:
//
// PairingStateTest/HandlesEvent.<test case>/<index of event>
class HandlesEvent : public PairingStateTest,
public ::testing::WithParamInterface<void (*)(PairingState*)> {
public:
void SetUp() override {
PairingStateTest::SetUp();
connection_ = MakeFakeConnection();
pairing_delegate_ = std::make_unique<NoOpPairingDelegate>(kTestLocalIoCap);
pairing_state_ = std::make_unique<PairingState>(
peer()->GetWeakPtr(), connection_.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler_.MakeStatusCallback());
pairing_state().SetPairingDelegate(pairing_delegate_->GetWeakPtr());
}
void TearDown() override {
pairing_state_.reset();
connection_.reset();
PairingStateTest::TearDown();
}
const FakeBrEdrConnection& connection() const { return *connection_; }
const TestStatusHandler& status_handler() const { return status_handler_; }
PairingState& pairing_state() { return *pairing_state_; }
// Returns an event injector that was passed to INSTANTIATE_TEST_SUITE_P.
auto* event() const { return GetParam(); }
void InjectEvent() { event()(&pairing_state()); }
private:
std::unique_ptr<FakeBrEdrConnection> connection_;
TestStatusHandler status_handler_;
std::unique_ptr<NoOpPairingDelegate> pairing_delegate_;
std::unique_ptr<PairingState> pairing_state_;
};
// The tests here exercise that PairingState can be successfully advances
// through the expected pairing flow and generates errors when the pairing flow
// occurs out of order. This is intended to cover its internal state machine
// transitions and not the side effects.
INSTANTIATE_TEST_SUITE_P(PairingStateTest, HandlesEvent,
::testing::Values(LinkKeyRequest, IoCapabilityRequest,
IoCapabilityResponse, UserConfirmationRequest,
UserPasskeyRequest, UserPasskeyNotification,
SimplePairingComplete, LinkKeyNotification,
AuthenticationComplete));
TEST_P(HandlesEvent, InIdleState) {
RETURN_IF_FATAL(InjectEvent());
if (event() == LinkKeyRequest || event() == IoCapabilityResponse) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().handle());
EXPECT_EQ(kTestHandle, *status_handler().handle());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitLinkKeyRequestState) {
// Advance state machine.
static_cast<void>(pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback));
RETURN_IF_FATAL(InjectEvent());
if (event() == LinkKeyRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitIoCapRequest) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitAuthCompleteSkippingSimplePairing) {
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_NE(std::nullopt, pairing_state().OnLinkKeyRequest());
RETURN_IF_FATAL(InjectEvent());
if (event() == AuthenticationComplete) {
EXPECT_EQ(0, status_handler().call_count());
EXPECT_EQ(1, connection().start_encryption_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitIoCapResponseState) {
// Advance state machine.
static_cast<void>(pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback));
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityResponse) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InResponderWaitIoCapRequestState) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InErrorStateAfterIoCapRequestRejectedWithoutPairingDelegate) {
// Clear the default pairing delegate set by the fixture.
pairing_state().SetPairingDelegate(fxl::WeakPtr<PairingDelegate>());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
EXPECT_FALSE(pairing_state().OnIoCapabilityRequest());
// PairingState no longer accepts events because being not ready to pair has raised an error.
RETURN_IF_FATAL(InjectEvent());
EXPECT_LE(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
if (event() == LinkKeyRequest || event() == IoCapabilityResponse) {
// Peer attempted to pair again, which raises an additional "not ready" error.
EXPECT_EQ(ToResult(HostError::kNotReady), status_handler().status());
} else {
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserConfirmationStateAsInitiator) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserConfirmationRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserPasskeyRequestStateAsInitiator) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayOnly);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserPasskeyNotificationStateAsInitiator) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
// TODO(xow): Split into three tests depending on the pairing event expected.
TEST_P(HandlesEvent, InWaitUserConfirmationStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserConfirmationRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserPasskeyRequestStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayOnly);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserNotificationStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitPairingCompleteState) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
RETURN_IF_FATAL(InjectEvent());
if (event() == SimplePairingComplete) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitLinkKeyState) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, connection().start_encryption_count());
RETURN_IF_FATAL(InjectEvent());
if (event() == LinkKeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
EXPECT_EQ(1, connection().start_encryption_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitAuthCompleteStateAfterSimplePairing) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_TRUE(pairing_state().initiator());
EXPECT_EQ(0, connection().start_encryption_count());
RETURN_IF_FATAL(InjectEvent());
if (event() == AuthenticationComplete) {
EXPECT_EQ(0, status_handler().call_count());
EXPECT_EQ(1, connection().start_encryption_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitEncryptionStateAsInitiator) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state().OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityResponse) {
// Restarting the pairing is allowed in this state.
EXPECT_EQ(0, status_handler().call_count());
} else {
// Should not receive anything else other than OnEncryptionChange.
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitEncryptionStateAsResponder) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityResponse) {
// Restarting the pairing is allowed in this state.
EXPECT_EQ(0, status_handler().call_count());
} else {
// Should not receive anything else other than OnEncryptionChange.
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitEncryptionStateAsResponderForBonded) {
// We are previously bonded.
auto existing_link_key =
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey);
peer()->MutBrEdr().SetBondData(existing_link_key);
// Advance state machine.
static_cast<void>(pairing_state().OnLinkKeyRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == IoCapabilityResponse) {
// This re-starts the pairing as a responder.
EXPECT_EQ(0, status_handler().call_count());
} else {
// Should not receive anything else other than OnEncryptionChange, receiving anything else is a
// failure.
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InIdleStateAfterOnePairing) {
// Advance state machine.
static_cast<void>(pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback));
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state().OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
ASSERT_TRUE(pairing_state().initiator());
// Successfully enabling encryption should allow pairing to start again.
pairing_state().OnEncryptionChange(fitx::ok(true));
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(fitx::ok(), *status_handler().status());
EXPECT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == LinkKeyRequest || event() == IoCapabilityResponse) {
EXPECT_EQ(1, status_handler().call_count());
} else {
EXPECT_EQ(2, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InFailedStateAfterPairingFailed) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Inject failure status.
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_FALSE(status_handler().status()->is_ok());
RETURN_IF_FATAL(InjectEvent());
EXPECT_EQ(2, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
TEST_P(HandlesEvent, InFailedStateAfterAuthenticationFailed) {
// Advance state machine.
static_cast<void>(pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback));
static_cast<void>(pairing_state().OnLinkKeyRequest());
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
// Inject failure status.
pairing_state().OnAuthenticationComplete(hci_spec::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_FALSE(status_handler().status()->is_ok());
RETURN_IF_FATAL(InjectEvent());
EXPECT_EQ(2, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(ToResult(HostError::kNotSupported), status_handler().status());
}
// PairingAction expected answers are inferred from "device A" Authentication
// Stage 1 specs in v5.0 Vol 3, Part C, Sec 5.2.2.6, Table 5.7.
TEST_F(PairingStateTest, GetInitiatorPairingAction) {
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kComparePasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kGetConsent,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic, GetInitiatorPairingAction(IOCapability::kNoInputNoOutput,
IOCapability::kNoInputNoOutput));
}
// Ibid., but for "device B."
TEST_F(PairingStateTest, GetResponderPairingAction) {
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic, GetResponderPairingAction(IOCapability::kNoInputNoOutput,
IOCapability::kNoInputNoOutput));
}
// Events are obtained from ibid. association models, mapped to HCI sequences in
// v5.0 Vol 3, Vol 2, Part F, Sec 4.2.10–15.
TEST_F(PairingStateTest, GetExpectedEvent) {
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
// Level of authentication from ibid. table.
TEST_F(PairingStateTest, IsPairingAuthenticated) {
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_FALSE(
IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
TEST_F(PairingStateTest, GetInitiatorAuthRequirements) {
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kKeyboardOnly));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kNoInputNoOutput));
}
TEST_F(PairingStateTest, GetResponderAuthRequirements) {
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
TEST_F(PairingStateTest, SkipPairingIfExistingKeyMeetsSecurityRequirements) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
connection->set_link_key(kTestLinkKey, kTestAuthenticatedLinkKeyType);
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
TestStatusHandler initiator_status_handler;
pairing_state.InitiatePairing(kSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
EXPECT_EQ(0, status_handler.call_count());
ASSERT_EQ(1, initiator_status_handler.call_count());
EXPECT_EQ(fitx::ok(), *initiator_status_handler.status());
}
TEST_F(PairingStateTest,
InitiatorAuthRequiredCausesOnLinkKeyRequestToReturnNullIfUnauthenticatedKeyExists) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
BrEdrSecurityRequirements security{.authentication = true, .secure_connections = false};
pairing_state.InitiatePairing(security, status_handler.MakeStatusCallback());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, InitiatorNoSecurityRequirementsCausesOnLinkKeyRequestToReturnExistingKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_FALSE(connection->ltk().has_value());
auto reply_key = pairing_state.OnLinkKeyRequest();
ASSERT_TRUE(reply_key.has_value());
EXPECT_EQ(kTestLinkKey, reply_key.value());
EXPECT_EQ(0, status_handler.call_count());
EXPECT_TRUE(connection->ltk().has_value());
}
TEST_F(PairingStateTest, InitiatorOnLinkKeyRequestReturnsNullIfBondDataDoesNotExist) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
auto reply_key = pairing_state.OnLinkKeyRequest();
EXPECT_FALSE(reply_key.has_value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, IdleStateOnLinkKeyRequestReturnsLinkKeyWhenBondDataExists) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_FALSE(connection->ltk().has_value());
auto reply_key = pairing_state.OnLinkKeyRequest();
ASSERT_TRUE(reply_key.has_value());
EXPECT_EQ(kTestLinkKey, reply_key.value());
EXPECT_EQ(0, status_handler.call_count());
EXPECT_TRUE(connection->ltk().has_value());
}
TEST_F(PairingStateTest, IdleStateOnLinkKeyRequestReturnsNullWhenBondDataDoesNotExist) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
auto reply_key = pairing_state.OnLinkKeyRequest();
EXPECT_FALSE(reply_key.has_value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, SimplePairingCompleteWithErrorCodeReceivedEarlyFailsPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
EXPECT_EQ(0, status_handler.call_count());
const auto status_code = hci_spec::StatusCode::kPairingNotAllowed;
pairing_state.OnSimplePairingComplete(status_code);
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(ToResult(status_code), status_handler.status().value());
}
TEST_F(PairingStateDeathTest, OnLinkKeyRequestReceivedMissingPeerAsserts) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_TRUE(peer_cache()->RemoveDisconnectedPeer(peer()->identifier()));
ASSERT_DEATH_IF_SUPPORTED({ __UNUSED auto reply_key = pairing_state.OnLinkKeyRequest(); },
".*peer.*");
}
TEST_F(PairingStateTest, AuthenticationCompleteWithErrorCodeReceivedEarlyFailsPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(0, status_handler.call_count());
const auto status_code = hci_spec::StatusCode::kAuthenticationFailure;
pairing_state.OnAuthenticationComplete(status_code);
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(ToResult(status_code), status_handler.status().value());
}
TEST_F(PairingStateTest,
AuthenticationCompleteWithMissingKeyRetriesWithoutKeyAndDoesntAutoConfirmRejected) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/true,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
auto existing_link_key =
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey);
peer()->MutBrEdr().SetBondData(existing_link_key);
EXPECT_FALSE(connection->ltk().has_value());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(1u, auth_request_count());
auto reply_key = pairing_state.OnLinkKeyRequest();
ASSERT_TRUE(reply_key.has_value());
EXPECT_EQ(kTestLinkKey, reply_key.value());
EXPECT_EQ(0, status_handler.call_count());
// Peer says that they don't have a key.
pairing_state.OnAuthenticationComplete(hci_spec::StatusCode::kPinOrKeyMissing);
ASSERT_EQ(0, status_handler.call_count());
// We should retry the authentication request, this time pretending we don't have a key.
EXPECT_EQ(2u, auth_request_count());
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(0, status_handler.call_count());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(false);
});
bool confirmed = true;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_FALSE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_TRUE(status.is_error());
});
// The controller sends a SimplePairingComplete indicating the failure after we send a
// Negative Confirmation.
const auto status_code = hci_spec::StatusCode::kAuthenticationFailure;
pairing_state.OnSimplePairingComplete(status_code);
// The bonding key should not have been touched.
EXPECT_EQ(existing_link_key, peer()->bredr()->link_key());
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status().has_value());
EXPECT_EQ(ToResult(status_code), status_handler.status().value());
}
TEST_F(PairingStateTest, ResponderSignalsCompletionOfPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
auto existing_link_key =
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey);
peer()->MutBrEdr().SetBondData(existing_link_key);
EXPECT_FALSE(connection->ltk().has_value());
auto reply_key = pairing_state.OnLinkKeyRequest();
ASSERT_TRUE(reply_key.has_value());
EXPECT_EQ(kTestLinkKey, reply_key.value());
EXPECT_EQ(0, status_handler.call_count());
// If a pairing request comes in after the peer has already asked for the key, we
// add it's completion to the queue.
TestStatusHandler new_pairing_handler;
pairing_state.InitiatePairing(kNoSecurityRequirements, new_pairing_handler.MakeStatusCallback());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
auto expected_status = hci_spec::StatusCode::kSuccess;
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status().has_value());
EXPECT_EQ(ToResult(expected_status), status_handler.status().value());
// and the new pairing handler gets called back too
EXPECT_EQ(1, new_pairing_handler.call_count());
ASSERT_TRUE(new_pairing_handler.status().has_value());
EXPECT_EQ(ToResult(expected_status), new_pairing_handler.status().value());
// The link key should be stored in the connection now.
EXPECT_EQ(kTestLinkKey, connection->ltk());
}
TEST_F(PairingStateTest,
AuthenticationCompleteWithMissingKeyRetriesWithoutKeyAndDoesntAutoConfirmAccepted) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/true,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
auto existing_link_key =
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey);
peer()->MutBrEdr().SetBondData(existing_link_key);
EXPECT_FALSE(connection->ltk().has_value());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_EQ(1u, auth_request_count());
auto reply_key = pairing_state.OnLinkKeyRequest();
ASSERT_TRUE(reply_key.has_value());
EXPECT_EQ(kTestLinkKey, reply_key.value());
EXPECT_EQ(0, status_handler.call_count());
// Peer says that they don't have a key.
pairing_state.OnAuthenticationComplete(hci_spec::StatusCode::kPinOrKeyMissing);
ASSERT_EQ(0, status_handler.call_count());
// We should retry the authentication request, this time pretending we don't have a key.
EXPECT_EQ(2u, auth_request_count());
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(0, status_handler.call_count());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
pairing_delegate.SetConfirmPairingCallback([this](PeerId peer_id, auto cb) {
EXPECT_EQ(peer()->identifier(), peer_id);
ASSERT_TRUE(cb);
cb(true);
});
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([this](PeerId peer_id, sm::Result<> status) {
EXPECT_EQ(peer()->identifier(), peer_id);
EXPECT_EQ(fitx::ok(), status);
});
// The controller sends a SimplePairingComplete indicating the success, then the controller
// sends us the new link key, and Authentication Complete.
// Negative Confirmation.
auto status_code = hci_spec::StatusCode::kSuccess;
pairing_state.OnSimplePairingComplete(status_code);
const auto new_link_key_value = UInt128{0xC0, 0xDE, 0xFA, 0xCE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04};
pairing_state.OnLinkKeyNotification(new_link_key_value, kTestUnauthenticatedLinkKeyType);
pairing_state.OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
// then we request encryption, which when it finishes, completes pairing.
ASSERT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status().has_value());
EXPECT_EQ(ToResult(status_code), status_handler.status().value());
// The new link key should be stored in the connection now.
auto new_link_key = hci_spec::LinkKey(new_link_key_value, 0, 0);
EXPECT_EQ(new_link_key, connection->ltk());
}
TEST_F(PairingStateTest,
MultipleQueuedPairingRequestsWithSameSecurityRequirementsCompleteAtSameTimeWithSuccess) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
TestStatusHandler initiate_status_handler_0;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiate_status_handler_0.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
TestStatusHandler initiate_status_handler_1;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiate_status_handler_1.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
AdvanceToEncryptionAsInitiator(&pairing_state);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fitx::ok(), *status_handler.status());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(fitx::ok(), *initiate_status_handler_0.status());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(fitx::ok(), *initiate_status_handler_1.status());
}
TEST_F(
PairingStateTest,
MultipleQueuedPairingRequestsWithAuthSecurityRequirementsCompleteAtSameTimeWithInsufficientSecurityFailure) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
TestStatusHandler initiate_status_handler_0;
pairing_state.InitiatePairing(kSecurityRequirements,
initiate_status_handler_0.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
TestStatusHandler initiate_status_handler_1;
pairing_state.InitiatePairing(kSecurityRequirements,
initiate_status_handler_1.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
// Pair with unauthenticated link key.
AdvanceToEncryptionAsInitiator(&pairing_state);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fitx::ok(), *status_handler.status());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), initiate_status_handler_0.status().value());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), initiate_status_handler_1.status().value());
}
TEST_F(PairingStateTest,
AuthPairingRequestDuringInitiatorNoAuthPairingFailsQueuedAuthPairingRequest) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate_no_io(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate_no_io.GetWeakPtr());
TestStatusHandler initiate_status_handler_0;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiate_status_handler_0.MakeStatusCallback());
TestStatusHandler initiate_status_handler_1;
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
pairing_state.InitiatePairing(kSecurityRequirements,
initiate_status_handler_1.MakeStatusCallback());
// Pair with unauthenticated link key.
AdvanceToEncryptionAsInitiator(&pairing_state);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
FakePairingDelegate fake_pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(fake_pairing_delegate.GetWeakPtr());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fitx::ok(), *status_handler.status());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(fitx::ok(), *initiate_status_handler_0.status());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(ToResult(HostError::kInsufficientSecurity), initiate_status_handler_1.status().value());
// Pairing for second request should not start.
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest, InitiatingPairingDuringAuthenticationWithExistingUnauthenticatedLinkKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(peer()->GetWeakPtr(), connection.get(), /*link_initiated=*/false,
MakeAuthRequestCallback(), status_handler.MakeStatusCallback());
FakePairingDelegate fake_pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(fake_pairing_delegate.GetWeakPtr());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
TestStatusHandler initiator_status_handler_0;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler_0.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
TestStatusHandler initiator_status_handler_1;
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
pairing_state.InitiatePairing(kSecurityRequirements,
initiator_status_handler_1.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
// Authenticate with link key.
EXPECT_NE(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_TRUE(connection->ltk().has_value());
pairing_state.OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(fitx::ok(), *status_handler.status());
ASSERT_EQ(1, initiator_status_handler_0.call_count());
EXPECT_EQ(fitx::ok(), *initiator_status_handler_0.status());
EXPECT_EQ(0, initiator_status_handler_1.call_count());
fake_pairing_delegate.SetDisplayPasskeyCallback([](PeerId peer_id, uint32_t value,
PairingDelegate::DisplayMethod method,
auto cb) { cb(true); });
fake_pairing_delegate.SetCompletePairingCallback(
[](PeerId peer_id, sm::Result<> status) { EXPECT_EQ(fitx::ok(), status); });
// Pairing for second request should start.
EXPECT_EQ(2u, auth_request_count());
EXPECT_TRUE(pairing_state.initiator());
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_state.OnSimplePairingComplete(hci_spec::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestAuthenticatedLinkKeyType);
pairing_state.OnAuthenticationComplete(hci_spec::StatusCode::kSuccess);
EXPECT_EQ(2, connection->start_encryption_count());
connection->TriggerEncryptionChangeCallback(fitx::ok(true));
ASSERT_EQ(2, status_handler.call_count());
EXPECT_EQ(fitx::ok(), *status_handler.status());
EXPECT_EQ(1, initiator_status_handler_0.call_count());
ASSERT_EQ(1, initiator_status_handler_1.call_count());
EXPECT_EQ(fitx::ok(), *initiator_status_handler_1.status());
// No further pairing should occur.
EXPECT_EQ(2u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
} // namespace
} // namespace bt::gap