blob: 4f9956db08b5290712382905d7fe170ab23bd568 [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/public/pw_bluetooth_sapphire/internal/host/gap/pairing_state.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/fake_bredr_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/controller_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/fake_peer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect_util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/mock_controller.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_packets.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/error.h"
namespace bt::gap {
namespace {
using namespace inspect::testing;
using hci::testing::FakeBrEdrConnection;
using hci_spec::kUserConfirmationRequestEventCode;
using hci_spec::kUserPasskeyNotificationEventCode;
using hci_spec::kUserPasskeyRequestEventCode;
using pw::bluetooth::emboss::AuthenticationRequirements;
using pw::bluetooth::emboss::IoCapability;
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::DISPLAY_ONLY;
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 kTestUnauthenticatedLinkKeyType192 =
hci_spec::LinkKeyType::kUnauthenticatedCombination192;
const auto kTestAuthenticatedLinkKeyType192 =
hci_spec::LinkKeyType::kAuthenticatedCombination192;
const auto kTestUnauthenticatedLinkKeyType256 =
hci_spec::LinkKeyType::kUnauthenticatedCombination256;
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_self_(this) {}
PairingDelegate::WeakPtr GetWeakPtr() { return weak_self_.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_;
WeakSelf<PairingDelegate> weak_self_;
};
using TestBase = testing::FakeDispatcherControllerTest<testing::MockController>;
class PairingStateTest : public TestBase {
public:
PairingStateTest() = default;
virtual ~PairingStateTest() = default;
void SetUp() override {
TestBase::SetUp();
InitializeACLDataChannel();
peer_cache_ = std::make_unique<PeerCache>(dispatcher());
peer_ = peer_cache_->NewPeer(kPeerAddress, /*connectable=*/true);
auth_request_count_ = 0;
send_auth_request_callback_ = [this]() { auth_request_count_++; };
connection_ = MakeFakeConnection();
}
void TearDown() override {
peer_ = nullptr;
peer_cache_ = nullptr;
EXPECT_CMD_PACKET_OUT(test_device(),
testing::DisconnectPacket(kTestHandle));
connection_.reset();
TestBase::TearDown();
}
fit::closure MakeAuthRequestCallback() {
return send_auth_request_callback_.share();
}
std::unique_ptr<FakeBrEdrConnection> MakeFakeConnection() {
return std::make_unique<FakeBrEdrConnection>(
kTestHandle,
kLocalAddress,
kPeerAddress,
pw::bluetooth::emboss::ConnectionRole::CENTRAL,
transport()->GetWeakPtr());
}
FakeBrEdrConnection* connection() const { return connection_.get(); }
PeerCache* peer_cache() const { return peer_cache_.get(); }
Peer* peer() const { return peer_; }
size_t auth_request_count() const { return auth_request_count_; }
private:
std::unique_ptr<PeerCache> peer_cache_;
Peer* peer_;
size_t auth_request_count_;
fit::closure send_auth_request_callback_;
std::unique_ptr<FakeBrEdrConnection> connection_;
};
class PairingStateDeathTest : public PairingStateTest {};
TEST_F(PairingStateTest, PairingStateStartsAsResponder) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
NoOpStatusCallback);
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest, PairingStateRemainsResponderAfterPeerIoCapResponse) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
NoOpStatusCallback);
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(PairingStateTest,
PairingStateBecomesInitiatorAfterLocalPairingInitiated) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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) {
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(),
/*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) {
std::unique_ptr<PairingState> pairing_state =
std::make_unique<PairingState>(peer()->GetWeakPtr(),
connection(),
/*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(pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED));
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(pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED),
*handler.status());
}
TEST_F(PairingStateTest,
InitiatingPairingAfterErrorTriggersStatusCallbackWithError) {
TestStatusHandler link_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::ok(true));
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, PeerMayNotChangeLinkKeyWhenNotEncrypted) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
connection()->set_link_key(hci_spec::LinkKey(UInt128(), 0, 0),
kTestAuthenticatedLinkKeyType192);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
pairing_state->OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
}
TEST_F(PairingStateTest, SuccessfulEncryptionChangeTriggersStatusCallback) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::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(fit::ok(), *status_handler.status());
}
TEST_F(PairingStateTest, EncryptionChangeErrorTriggersStatusCallbackWithError) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(
fit::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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::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) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::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(fit::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(fit::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) {
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
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(fit::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(fit::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) {
TestStatusHandler overall_status, request_status;
{
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
// 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) {
TestStatusHandler owner_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
// Advance state machine to Responder Waiting IOCap Request
pairing_state.OnIoCapabilityResponse(
pw::bluetooth::emboss::IoCapability::DISPLAY_YES_NO);
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey,
NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
// Provide an authenticated link key when this should have resulted in an
// unauthenticated link key
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue,
kTestAuthenticatedLinkKeyType192);
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey,
NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
// 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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey,
NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
ASSERT_FALSE(connection()->ltk());
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
ASSERT_TRUE(connection()->ltk());
EXPECT_EQ(kTestLinkKeyValue, connection()->ltk()->value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
SecureConnectionsRequiresSecureConnectionsLinkKeySuccess) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Set peer lmp_features: Secure Connections (Host Support)
peer()->SetFeaturePage(
1u,
static_cast<uint64_t>(
hci_spec::LMPFeature::kSecureConnectionsHostSupport));
// Set peer lmp_features: Secure Connections (Controller Support)
peer()->SetFeaturePage(
2u,
static_cast<uint64_t>(
hci_spec::LMPFeature::kSecureConnectionsControllerSupport));
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IoCapability::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey,
NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
// Ensure that P-256 authenticated link key was provided
pairing_state.OnLinkKeyNotification(
kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType256,
/*local_secure_connections_supported=*/true);
ASSERT_TRUE(connection()->ltk());
EXPECT_EQ(kTestLinkKeyValue, connection()->ltk()->value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
SecureConnectionsRequiresSecureConnectionsLinkKeyFail) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Set peer lmp_features: Secure Connections (Host Support)
peer()->SetFeaturePage(
1u,
static_cast<uint64_t>(
hci_spec::LMPFeature::kSecureConnectionsHostSupport));
// Set peer lmp_features: Secure Connections (Controller Support)
peer()->SetFeaturePage(
2u,
static_cast<uint64_t>(
hci_spec::LMPFeature::kSecureConnectionsControllerSupport));
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IoCapability::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey,
NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
// Provide P-192 authenticated link key when this should have resulted in an
// P-256 link key
pairing_state.OnLinkKeyNotification(
kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192,
/*local_secure_connections_supported=*/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(ToResult(HostError::kInsufficientSecurity),
*status_handler.status());
}
TEST_F(PairingStateTest,
NumericComparisonPairingComparesPasskeyOnInitiatorDisplayYesNoSide) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_YES_NO,
*pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IoCapability::DISPLAY_YES_NO);
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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
NumericComparisonPairingComparesPasskeyOnResponderDisplayYesNoSide) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_YES_NO);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::DISPLAY_YES_NO,
*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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::DISPLAY_YES_NO,
*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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest, PasskeyEntryPairingDisplaysPasskeyToDisplayOnlySide) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::KEYBOARD_ONLY);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::DISPLAY_ONLY, *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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
PasskeyEntryPairingRequestsPasskeyFromKeyboardOnlySide) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::DISPLAY_ONLY);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::KEYBOARD_ONLY,
*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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
JustWorksPairingOutgoingConnectDoesNotRequestUserActionInitiator) {
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT,
*pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IoCapability::NO_INPUT_NO_OUTPUT);
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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, owner_status_handler.call_count());
EXPECT_EQ(0, initiator_status_handler.call_count());
}
TEST_F(PairingStateTest,
JustWorksPairingOutgoingConnectDoesNotRequestUserActionResponder) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::NO_INPUT_NO_OUTPUT,
*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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
JustWorksPairingIncomingConnectRequiresConfirmationRejectedResponder) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::NO_INPUT_NO_OUTPUT,
*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(
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE),
*status_handler.status());
}
TEST_F(PairingStateTest,
JustWorksPairingIncomingConnectRequiresConfirmationRejectedInitiator) {
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT,
*pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IoCapability::NO_INPUT_NO_OUTPUT);
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(
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
EXPECT_EQ(1, owner_status_handler.call_count());
ASSERT_TRUE(owner_status_handler.status());
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE),
*owner_status_handler.status());
EXPECT_EQ(1, initiator_status_handler.call_count());
ASSERT_TRUE(initiator_status_handler.status());
EXPECT_EQ(ToResult(pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE),
*initiator_status_handler.status());
}
TEST_F(PairingStateTest,
JustWorksPairingIncomingConnectRequiresConfirmationAcceptedResponder) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IoCapability::NO_INPUT_NO_OUTPUT,
*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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(PairingStateTest,
JustWorksPairingIncomingConnectRequiresConfirmationAcceptedInitiator) {
TestStatusHandler owner_status_handler;
TestStatusHandler initiator_status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT,
*pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IoCapability::NO_INPUT_NO_OUTPUT);
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(fit::ok(), status);
});
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
}
void LinkKeyNotification(PairingState* pairing_state) {
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
}
void AuthenticationComplete(PairingState* pairing_state) {
pairing_state->OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
}
// 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();
pairing_delegate_ = std::make_unique<NoOpPairingDelegate>(kTestLocalIoCap);
pairing_state_ =
std::make_unique<PairingState>(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler_.MakeStatusCallback());
pairing_state().SetPairingDelegate(pairing_delegate_->GetWeakPtr());
}
void TearDown() override {
pairing_state_.reset();
PairingStateTest::TearDown();
}
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:
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(kTestUnauthenticatedLinkKeyType192),
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(PairingDelegate::WeakPtr());
// 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::DISPLAY_YES_NO);
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::DISPLAY_ONLY);
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::KEYBOARD_ONLY);
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::DISPLAY_YES_NO);
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::DISPLAY_ONLY);
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::KEYBOARD_ONLY);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
pairing_state().OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
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(kTestUnauthenticatedLinkKeyType192), 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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
pairing_state().OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
ASSERT_TRUE(pairing_state().initiator());
// Successfully enabling encryption should allow pairing to start again.
pairing_state().OnEncryptionChange(fit::ok(true));
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(fit::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(
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue,
kTestUnauthenticatedLinkKeyType192);
// Inject failure status.
pairing_state().OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE);
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::DISPLAY_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kComparePasskey,
GetInitiatorPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kGetConsent,
GetInitiatorPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::NO_INPUT_NO_OUTPUT));
}
// Ibid., but for "device B."
TEST_F(PairingStateTest, GetResponderPairingAction) {
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::DISPLAY_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::DISPLAY_YES_NO,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::KEYBOARD_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::NO_INPUT_NO_OUTPUT));
}
// 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::DISPLAY_ONLY, IoCapability::DISPLAY_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IoCapability::DISPLAY_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::DISPLAY_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IoCapability::DISPLAY_YES_NO,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::DISPLAY_YES_NO,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IoCapability::KEYBOARD_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::KEYBOARD_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::NO_INPUT_NO_OUTPUT));
}
// Level of authentication from ibid. table.
TEST_F(PairingStateTest, IsPairingAuthenticated) {
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::DISPLAY_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::DISPLAY_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_ONLY));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_YES_NO));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::DISPLAY_YES_NO,
IoCapability::KEYBOARD_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::DISPLAY_YES_NO,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_YES_NO));
EXPECT_TRUE(IsPairingAuthenticated(IoCapability::KEYBOARD_ONLY,
IoCapability::KEYBOARD_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::KEYBOARD_ONLY,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_YES_NO));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::KEYBOARD_ONLY));
EXPECT_FALSE(IsPairingAuthenticated(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::NO_INPUT_NO_OUTPUT));
}
TEST_F(PairingStateTest, GetInitiatorAuthenticationRequirements) {
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetInitiatorAuthenticationRequirements(IoCapability::DISPLAY_ONLY));
EXPECT_EQ(
AuthenticationRequirements::MITM_GENERAL_BONDING,
GetInitiatorAuthenticationRequirements(IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(
AuthenticationRequirements::MITM_GENERAL_BONDING,
GetInitiatorAuthenticationRequirements(IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(
AuthenticationRequirements::GENERAL_BONDING,
GetInitiatorAuthenticationRequirements(IoCapability::NO_INPUT_NO_OUTPUT));
}
TEST_F(PairingStateTest, GetResponderAuthenticationRequirements) {
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::DISPLAY_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::DISPLAY_ONLY, IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::DISPLAY_ONLY, IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::DISPLAY_ONLY, IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::DISPLAY_YES_NO,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::DISPLAY_YES_NO, IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::DISPLAY_YES_NO, IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(
AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::DISPLAY_YES_NO,
IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::KEYBOARD_ONLY,
IoCapability::DISPLAY_ONLY));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::KEYBOARD_ONLY, IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(AuthenticationRequirements::MITM_GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::KEYBOARD_ONLY, IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::KEYBOARD_ONLY, IoCapability::NO_INPUT_NO_OUTPUT));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::NO_INPUT_NO_OUTPUT, IoCapability::DISPLAY_ONLY));
EXPECT_EQ(
AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::DISPLAY_YES_NO));
EXPECT_EQ(AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(
IoCapability::NO_INPUT_NO_OUTPUT, IoCapability::KEYBOARD_ONLY));
EXPECT_EQ(
AuthenticationRequirements::GENERAL_BONDING,
GetResponderAuthenticationRequirements(IoCapability::NO_INPUT_NO_OUTPUT,
IoCapability::NO_INPUT_NO_OUTPUT));
}
TEST_F(PairingStateTest, SkipPairingIfExistingKeyMeetsSecurityRequirements) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
connection()->set_link_key(kTestLinkKey, kTestAuthenticatedLinkKeyType192);
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(fit::ok(), *initiator_status_handler.status());
}
TEST_F(
PairingStateTest,
InitiatorAuthRequiredCausesOnLinkKeyRequestToReturnNullIfUnauthenticatedKeyExists) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192),
kTestLinkKey));
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(
PairingStateTest,
InitiatorNoSecurityRequirementsCausesOnLinkKeyRequestToReturnExistingKey) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192),
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192),
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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::NO_INPUT_NO_OUTPUT,
*pairing_state.OnIoCapabilityRequest());
EXPECT_EQ(0, status_handler.call_count());
const auto status_code =
pw::bluetooth::emboss::StatusCode::PAIRING_NOT_ALLOWED;
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(
{ [[maybe_unused]] auto reply_key = pairing_state.OnLinkKeyRequest(); },
".*peer.*");
}
TEST_F(PairingStateTest,
AuthenticationCompleteWithErrorCodeReceivedEarlyFailsPairing) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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 =
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE;
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192), 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(
pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING);
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::NO_INPUT_NO_OUTPUT);
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 =
pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE;
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
auto existing_link_key = sm::LTK(
sm::SecurityProperties(kTestUnauthenticatedLinkKeyType192), 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(fit::ok(true));
auto expected_status = pw::bluetooth::emboss::StatusCode::SUCCESS;
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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192), 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(
pw::bluetooth::emboss::StatusCode::PIN_OR_KEY_MISSING);
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::NO_INPUT_NO_OUTPUT);
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(fit::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 = pw::bluetooth::emboss::StatusCode::SUCCESS;
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,
kTestUnauthenticatedLinkKeyType192);
pairing_state.OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
// then we request encryption, which when it finishes, completes pairing.
ASSERT_EQ(1, connection()->start_encryption_count());
connection()->TriggerEncryptionChangeCallback(fit::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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fit::ok(), *status_handler.status());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(fit::ok(), *initiate_status_handler_0.status());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(fit::ok(), *initiate_status_handler_1.status());
}
TEST_F(
PairingStateTest,
MultipleQueuedPairingRequestsWithAuthSecurityRequirementsCompleteAtSameTimeWithInsufficientSecurityFailure) {
TestStatusHandler status_handler;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fit::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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(fit::ok(true));
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(fit::ok(), *status_handler.status());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(fit::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;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*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(kTestUnauthenticatedLinkKeyType192),
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(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection()->start_encryption_count());
connection()->TriggerEncryptionChangeCallback(fit::ok(true));
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(fit::ok(), *status_handler.status());
ASSERT_EQ(1, initiator_status_handler_0.call_count());
EXPECT_EQ(fit::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(fit::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::DISPLAY_YES_NO,
*pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IoCapability::DISPLAY_YES_NO);
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(
kTestPasskey, [&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_state.OnSimplePairingComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue,
kTestAuthenticatedLinkKeyType192);
pairing_state.OnAuthenticationComplete(
pw::bluetooth::emboss::StatusCode::SUCCESS);
EXPECT_EQ(2, connection()->start_encryption_count());
connection()->TriggerEncryptionChangeCallback(fit::ok(true));
ASSERT_EQ(2, status_handler.call_count());
EXPECT_EQ(fit::ok(), *status_handler.status());
EXPECT_EQ(1, initiator_status_handler_0.call_count());
ASSERT_EQ(1, initiator_status_handler_1.call_count());
EXPECT_EQ(fit::ok(), *initiator_status_handler_1.status());
// No further pairing should occur.
EXPECT_EQ(2u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
#ifndef NINSPECT
TEST_F(PairingStateTest, Inspect) {
TestStatusHandler status_handler;
inspect::Inspector inspector;
PairingState pairing_state(peer()->GetWeakPtr(),
connection(),
/*link_initiated=*/false,
MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
pairing_state.AttachInspect(inspector.GetRoot(), "pairing_state");
auto security_properties_matcher = AllOf(NodeMatches(AllOf(
NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(StringIs("level", "not secure"),
BoolIs("encrypted", false),
BoolIs("secure_connections", false),
BoolIs("authenticated", false))))));
auto pairing_state_matcher =
AllOf(NodeMatches(AllOf(NameMatches("pairing_state"),
PropertyList(UnorderedElementsAre(
StringIs("encryption_status", "OFF"))))),
ChildrenMatch(UnorderedElementsAre(security_properties_matcher)));
inspect::Hierarchy hierarchy = bt::testing::ReadInspect(inspector);
EXPECT_THAT(hierarchy, ChildrenMatch(ElementsAre(pairing_state_matcher)));
}
#endif // NINSPECT
} // namespace
} // namespace bt::gap