blob: f56abae13778ee0b4d3d0685c6c5a981a5a343d5 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/connectivity/bluetooth/core/bt-host/gap/pairing_state.h"
#include <gtest/gtest.h>
#include "lib/gtest/test_loop_fixture.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/fake_pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/fake_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h"
namespace bt::gap {
namespace {
using hci::AuthRequirements;
using hci::IOCapability;
using hci::kUserConfirmationRequestEventCode;
using hci::kUserPasskeyNotificationEventCode;
using hci::kUserPasskeyRequestEventCode;
using hci::testing::FakeConnection;
const PeerId kTestPeerId(0xf00f1becf00fface);
const hci::ConnectionHandle kTestHandle(0x0A0B);
const DeviceAddress kLocalAddress(DeviceAddress::Type::kBREDR,
{0x22, 0x11, 0x00, 0xCC, 0xBB, 0xAA});
const DeviceAddress kPeerAddress(DeviceAddress::Type::kBREDR, {0x99, 0x88, 0x77, 0xFF, 0xEE, 0xDD});
const auto kTestLocalIoCap = sm::IOCapability::kDisplayYesNo;
const auto kTestPeerIoCap = IOCapability::kDisplayOnly;
const uint32_t kTestPasskey = 123456;
const auto kTestLinkKeyValue = UInt128{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
const hci::LinkKey kTestLinkKey(kTestLinkKeyValue, 0, 0);
const auto kTestUnauthenticatedLinkKeyType = hci::LinkKeyType::kUnauthenticatedCombination192;
const auto kTestAuthenticatedLinkKeyType = hci::LinkKeyType::kAuthenticatedCombination192;
const auto kTestLegacyLinkKeyType = hci::LinkKeyType::kCombination;
const auto kTestChangedLinkKeyType = hci::LinkKeyType::kChangedCombination;
const BrEdrSecurityRequirements kNoSecurityRequirements{.authentication = false,
.secure_connections = false};
void NoOpStatusCallback(hci::ConnectionHandle, hci::Status){};
void NoOpUserConfirmationCallback(bool){};
void NoOpUserPasskeyCallback(std::optional<uint32_t>){};
class NoOpPairingDelegate final : public PairingDelegate {
public:
NoOpPairingDelegate(sm::IOCapability io_capability)
: io_capability_(io_capability), weak_ptr_factory_(this) {}
fxl::WeakPtr<NoOpPairingDelegate> GetWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
// PairingDelegate overrides that do nothing.
~NoOpPairingDelegate() override = default;
sm::IOCapability io_capability() const override { return io_capability_; }
void CompletePairing(PeerId peer_id, sm::Status status) override {}
void ConfirmPairing(PeerId peer_id, ConfirmCallback confirm) override {}
void DisplayPasskey(PeerId peer_id, uint32_t passkey, DisplayMethod method,
ConfirmCallback confirm) override {}
void RequestPasskey(PeerId peer_id, PasskeyResponseCallback respond) override {}
private:
const sm::IOCapability io_capability_;
fxl::WeakPtrFactory<NoOpPairingDelegate> weak_ptr_factory_;
};
FakeConnection MakeFakeConnection() {
return FakeConnection(kTestHandle, hci::Connection::LinkType::kACL,
hci::Connection::Role::kMaster, kLocalAddress, kPeerAddress);
}
class GAP_PairingStateTest : public ::gtest::TestLoopFixture {
public:
GAP_PairingStateTest() = default;
virtual ~GAP_PairingStateTest() = default;
void SetUp() override {
peer_cache_ = std::make_unique<PeerCache>();
peer_ = peer_cache_->NewPeer(kPeerAddress, true);
auth_request_count_ = 0;
send_auth_request_callback_ = [this]() { auth_request_count_++; };
}
void TearDown() override {
peer_ = nullptr;
peer_cache_ = nullptr;
}
fit::closure MakeAuthRequestCallback() { return send_auth_request_callback_.share(); }
PeerCache* peer_cache() const { return peer_cache_.get(); }
Peer* peer() const { return peer_; }
size_t auth_request_count() const { return auth_request_count_; }
private:
inspect::Inspector inspector_;
std::unique_ptr<PeerCache> peer_cache_;
Peer* peer_;
size_t auth_request_count_;
fit::closure send_auth_request_callback_;
};
TEST_F(GAP_PairingStateTest, PairingStateStartsAsResponder) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
NoOpStatusCallback);
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(GAP_PairingStateTest, PairingStateRemainsResponderAfterPeerIoCapResponse) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
NoOpStatusCallback);
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(GAP_PairingStateTest, PairingStateBecomesInitiatorAfterLocalPairingInitiated) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(GAP_PairingStateTest, PairingStateSendsAuthenticationRequestExactlyOnce) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(GAP_PairingStateTest,
PairingStateRemainsResponderIfPairingInitiatedWhileResponderPairingInProgress) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(GAP_PairingStateTest, StatusCallbackMayDestroyPairingState) {
auto connection = MakeFakeConnection();
std::unique_ptr<PairingState> pairing_state;
bool cb_called = false;
auto status_cb = [&pairing_state, &cb_called](hci::ConnectionHandle handle, hci::Status status) {
EXPECT_FALSE(status.is_success());
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>(kTestPeerId, &connection, peer_cache(),
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(GAP_PairingStateTest, InitiatorCallbackMayDestroyPairingState) {
auto connection = MakeFakeConnection();
std::unique_ptr<PairingState> pairing_state = std::make_unique<PairingState>(
kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(), NoOpStatusCallback);
bool cb_called = false;
auto status_cb = [&pairing_state, &cb_called](hci::ConnectionHandle handle, hci::Status status) {
EXPECT_FALSE(status.is_success());
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::ConnectionHandle handle, hci::Status 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::ConnectionHandle> handle_;
std::optional<hci::Status> status_;
};
TEST_F(GAP_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::ConnectionHandle(0x0A0B), hci::Status(hci::StatusCode::kPairingNotAllowed));
EXPECT_EQ(1, handler.call_count());
ASSERT_TRUE(handler.handle());
EXPECT_EQ(hci::ConnectionHandle(0x0A0B), *handler.handle());
ASSERT_TRUE(handler.status());
EXPECT_EQ(hci::Status(hci::StatusCode::kPairingNotAllowed), *handler.status());
}
TEST_F(GAP_PairingStateTest, InitiatingPairingAfterErrorTriggersStatusCallbackWithError) {
TestStatusHandler link_status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(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 this attempt should be notified with an error.
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(hci::Status(HostError::kNotReady), *pairing_status_handler.status());
}
TEST_F(GAP_PairingStateTest, UnexpectedEncryptionChangeDoesNotTriggerStatusCallback) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(kPeerAddress));
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(hci::Status(), true);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, PeerMayNotChangeLinkKeyWhenNotEncrypted) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(HostError::kInsufficientSecurity, status_handler.status()->error());
}
TEST_F(GAP_PairingStateTest, PeerMayChangeLinkKeyWhenInIdleState) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
connection.set_bredr_link_key(hci::LinkKey(UInt128(), 0, 0), kTestAuthenticatedLinkKeyType);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestChangedLinkKeyType);
ASSERT_TRUE(connection.ltk().has_value());
EXPECT_EQ(kTestLinkKeyValue, connection.ltk().value().value());
ASSERT_TRUE(connection.ltk_type().has_value());
EXPECT_EQ(kTestChangedLinkKeyType, connection.ltk_type().value());
EXPECT_EQ(0, status_handler.call_count());
}
// Inject events that occur during the course of a successful pairing as an initiator, but not
// including enabling link encryption.
void AdvanceToEncryptionAsInitiator(PairingState* pairing_state) {
static_cast<void>(pairing_state->OnLinkKeyRequest(kPeerAddress));
static_cast<void>(pairing_state->OnIoCapabilityRequest());
pairing_state->OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state->OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state->OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state->OnAuthenticationComplete(hci::StatusCode::kSuccess);
}
TEST_F(GAP_PairingStateTest, SuccessfulEncryptionChangeTriggersStatusCallback) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), 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(hci::Status(), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, EncryptionChangeErrorTriggersStatusCallbackWithError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(HostError::kInsufficientSecurity), 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(hci::Status(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, EncryptionChangeToDisabledTriggersStatusCallbackWithError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), 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(hci::Status(HostError::kFailed), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, EncryptionChangeToEnableCallsInitiatorCallbacks) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), 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(hci::Status(), *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(hci::Status(), *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(GAP_PairingStateTest, InitiatingPairingOnResponderWaitsForPairingToFinish) {
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
NoOpStatusCallback);
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine as pairing responder.
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Try to initiate pairing while pairing is in progress.
TestStatusHandler status_handler;
pairing_state.InitiatePairing(kNoSecurityRequirements, status_handler.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
// Keep advancing state machine.
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
EXPECT_FALSE(pairing_state.initiator());
ASSERT_EQ(0, status_handler.call_count());
// The attempt to initiate pairing should have its status callback notified.
connection.TriggerEncryptionChangeCallback(hci::Status(), 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(hci::Status(), *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(GAP_PairingStateTest, UnresolvedPairingCallbackIsCalledOnDestruction) {
auto connection = MakeFakeConnection();
TestStatusHandler overall_status, request_status;
{
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
overall_status.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(kTestLocalIoCap);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine as pairing responder.
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Try to initiate pairing while pairing is in progress.
pairing_state.InitiatePairing(kNoSecurityRequirements, request_status.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
// Keep advancing state machine.
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
// as pairing_state falls out of scope, we expect additional pairing callbacks to be called
ASSERT_EQ(0, overall_status.call_count());
ASSERT_EQ(0, request_status.call_count());
}
ASSERT_EQ(0, overall_status.call_count());
ASSERT_EQ(1, request_status.call_count());
ASSERT_TRUE(request_status.handle());
EXPECT_EQ(kTestHandle, *request_status.handle());
EXPECT_EQ(hci::Status(HostError::kLinkDisconnected), *request_status.status());
}
TEST_F(GAP_PairingStateTest, PairingStateRejectsPairingInitiationWithoutPairingDelegate) {
auto connection = MakeFakeConnection();
TestStatusHandler owner_status_handler;
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
owner_status_handler.MakeStatusCallback());
TestStatusHandler initiator_status_handler;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_FALSE(pairing_state.initiator());
// The owning link doesn't get an error for a failure to initiator pairing.
EXPECT_EQ(0, owner_status_handler.call_count());
// But the initiator should get an error.
EXPECT_EQ(1, initiator_status_handler.call_count());
ASSERT_TRUE(initiator_status_handler.status());
EXPECT_EQ(hci::Status(HostError::kNotReady), *initiator_status_handler.status());
}
TEST_F(GAP_PairingStateTest, PairingStateRejectsPairingResponseWithoutPairingDelegate) {
auto connection = MakeFakeConnection();
TestStatusHandler status_handler;
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
pairing_state.OnIoCapabilityResponse(kTestPeerIoCap);
// PairingState rejects the pairing with a negative reply.
EXPECT_FALSE(pairing_state.OnIoCapabilityRequest());
EXPECT_FALSE(pairing_state.initiator());
// The owner should be alerted with an error.
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(hci::Status(HostError::kNotReady), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, UnexpectedLinkKeyAuthenticationRaisesError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
// Provide an authenticated link key when this should have resulted in an
// unauthenticated link key.
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestAuthenticatedLinkKeyType);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(hci::Status(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, LegacyPairingLinkKeyRaisesError) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
// Provide a legacy pairing link key type.
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestLegacyLinkKeyType);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.handle());
EXPECT_EQ(kTestHandle, *status_handler.handle());
ASSERT_TRUE(status_handler.status());
EXPECT_EQ(hci::Status(HostError::kInsufficientSecurity), *status_handler.status());
}
TEST_F(GAP_PairingStateTest, PairingSetsConnectionLinkKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
static_cast<void>(pairing_state.OnIoCapabilityRequest());
pairing_state.OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
ASSERT_FALSE(connection.ltk());
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_TRUE(connection.ltk());
EXPECT_EQ(kTestLinkKeyValue, connection.ltk()->value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, NumericComparisonPairingComparesPasskeyOnInitiatorDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(kPeerAddress));
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(kTestPeerId, 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([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, NumericComparisonPairingComparesPasskeyOnResponderDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(kTestPeerId, 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([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
// v5.0, Vol 3, Part C, Sec 5.2.2.6 call this "Numeric Comparison with automatic
// confirmation on device B only and Yes/No confirmation on whether to pair on
// device A. Device A does not show the confirmation value." and it should
// result in user consent.
TEST_F(GAP_PairingStateTest, NumericComparisonWithoutValueRequestsConsentFromDisplayYesNoSide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetConfirmPairingCallback([](PeerId peer_id, auto cb) {
EXPECT_EQ(kTestPeerId, 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([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, PasskeyEntryPairingDisplaysPasskeyToDisplayOnlySide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kDisplayOnly, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetDisplayPasskeyCallback(
[](PeerId peer_id, uint32_t value, PairingDelegate::DisplayMethod method, auto cb) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_EQ(kTestPasskey, value);
EXPECT_EQ(PairingDelegate::DisplayMethod::kPeerEntry, method);
EXPECT_TRUE(cb);
});
pairing_state.OnUserPasskeyNotification(kTestPasskey);
pairing_delegate.SetCompletePairingCallback([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, PasskeyEntryPairingRequestsPasskeyFromKeyboardOnlySide) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayOnly);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kKeyboardOnly, *pairing_state.OnIoCapabilityRequest());
pairing_delegate.SetRequestPasskeyCallback([](PeerId peer_id, auto cb) {
EXPECT_EQ(kTestPeerId, 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([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, JustWorksPairingDoesNotRequestUserAction) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state.OnIoCapabilityResponse(IOCapability::kNoInputNoOutput);
ASSERT_FALSE(pairing_state.initiator());
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_delegate.SetCompletePairingCallback([](PeerId peer_id, sm::Status status) {
EXPECT_EQ(kTestPeerId, peer_id);
EXPECT_TRUE(status);
});
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, 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(kPeerAddress));
}
void IoCapabilityRequest(PairingState* pairing_state) {
static_cast<void>(pairing_state->OnIoCapabilityRequest());
}
void IoCapabilityResponse(PairingState* pairing_state) {
pairing_state->OnIoCapabilityResponse(kTestPeerIoCap);
}
void UserConfirmationRequest(PairingState* pairing_state) {
pairing_state->OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
}
void UserPasskeyRequest(PairingState* pairing_state) {
pairing_state->OnUserPasskeyRequest(NoOpUserPasskeyCallback);
}
void UserPasskeyNotification(PairingState* pairing_state) {
pairing_state->OnUserPasskeyNotification(kTestPasskey);
}
void SimplePairingComplete(PairingState* pairing_state) {
pairing_state->OnSimplePairingComplete(hci::StatusCode::kSuccess);
}
void LinkKeyNotification(PairingState* pairing_state) {
pairing_state->OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
}
void AuthenticationComplete(PairingState* pairing_state) {
pairing_state->OnAuthenticationComplete(hci::StatusCode::kSuccess);
}
// Test suite fixture that genericizes an injected pairing state event. The
// event being tested should be retrieved with the GetParam method, which
// returns a default event injector. For example:
//
// PairingState pairing_state;
// GetParam()(&pairing_state);
//
// This is named so that the instantiated test description looks correct:
//
// GAP_PairingStateTest/HandlesEvent.<test case>/<index of event>
class HandlesEvent : public GAP_PairingStateTest,
public ::testing::WithParamInterface<void (*)(PairingState*)> {
public:
HandlesEvent() : connection_(MakeFakeConnection()){};
virtual ~HandlesEvent() = default;
void SetUp() override {
GAP_PairingStateTest::SetUp();
pairing_delegate_ = std::make_unique<NoOpPairingDelegate>(kTestLocalIoCap);
pairing_state_ = std::make_unique<PairingState>(kTestPeerId, &connection_, peer_cache(),
MakeAuthRequestCallback(),
status_handler_.MakeStatusCallback());
pairing_state().SetPairingDelegate(pairing_delegate_->GetWeakPtr());
}
const FakeConnection& connection() const { return connection_; }
const TestStatusHandler& status_handler() const { return status_handler_; }
PairingState& pairing_state() { return *pairing_state_; }
// Returns an event injector that was passed to INSTANTIATE_TEST_SUITE_P.
auto* event() const { return GetParam(); }
void InjectEvent() { event()(&pairing_state()); }
private:
FakeConnection connection_;
TestStatusHandler status_handler_;
std::unique_ptr<NoOpPairingDelegate> pairing_delegate_;
std::unique_ptr<PairingState> pairing_state_;
};
// The tests here exercise that PairingState can be successfully advances
// through the expected pairing flow and generates errors when the pairing flow
// occurs out of order. This is intended to cover its internal state machine
// transitions and not the side effects.
INSTANTIATE_TEST_SUITE_P(GAP_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(hci::Status(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(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitIoCapRequest) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest(kPeerAddress));
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(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitAuthCompleteSkippingSimplePairing) {
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
EXPECT_NE(std::nullopt, pairing_state().OnLinkKeyRequest(kPeerAddress));
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(hci::Status(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(kPeerAddress));
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(hci::Status(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(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InErrorStateAfterIoCapRequestRejectedWithoutPairingDelegate) {
// Clear the default pairing delegate set by the fixture.
pairing_state().SetPairingDelegate(fxl::WeakPtr<PairingDelegate>());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
EXPECT_FALSE(pairing_state().OnIoCapabilityRequest());
// PairingState no longer accepts events because being not ready to pair has raised an error.
RETURN_IF_FATAL(InjectEvent());
EXPECT_LE(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
if (event() == LinkKeyRequest || event() == IoCapabilityResponse) {
// Peer attempted to pair again, which raises an additional "not ready" error.
EXPECT_EQ(hci::Status(HostError::kNotReady), status_handler().status());
} else {
EXPECT_EQ(hci::Status(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(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserConfirmationRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayOnly);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
// TODO(xow): Split into three tests depending on the pairing event expected.
TEST_P(HandlesEvent, InWaitUserConfirmationStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserConfirmationRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserPasskeyRequestStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kKeyboardOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kDisplayOnly);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyRequest) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitUserNotificationStateAsResponder) {
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kDisplayOnly);
pairing_state().SetPairingDelegate(pairing_delegate.GetWeakPtr());
// Advance state machine.
pairing_state().OnIoCapabilityResponse(IOCapability::kKeyboardOnly);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
if (event() == UserPasskeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitLinkKeyState) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, connection().start_encryption_count());
RETURN_IF_FATAL(InjectEvent());
if (event() == LinkKeyNotification) {
EXPECT_EQ(0, status_handler().call_count());
EXPECT_EQ(1, connection().start_encryption_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InInitiatorWaitAuthCompleteStateAfterSimplePairing) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_TRUE(pairing_state().initiator());
EXPECT_EQ(0, connection().start_encryption_count());
RETURN_IF_FATAL(InjectEvent());
if (event() == AuthenticationComplete) {
EXPECT_EQ(0, status_handler().call_count());
EXPECT_EQ(1, connection().start_encryption_count());
} else {
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InWaitEncryptionStateAsInitiator) {
// Advance state machine.
pairing_state().InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
static_cast<void>(pairing_state().OnLinkKeyRequest(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state().OnAuthenticationComplete(hci::StatusCode::kSuccess);
ASSERT_TRUE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
// Should not receive anything other than OnEncryptionChange.
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(HostError::kNotSupported), status_handler().status());
}
TEST_P(HandlesEvent, InWaitEncryptionStateAsResponder) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
ASSERT_FALSE(pairing_state().initiator());
RETURN_IF_FATAL(InjectEvent());
// Should not receive anything other than OnEncryptionChange.
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
pairing_state().OnAuthenticationComplete(hci::StatusCode::kSuccess);
ASSERT_TRUE(pairing_state().initiator());
// Successfully enabling encryption should allow pairing to start again.
pairing_state().OnEncryptionChange(hci::Status(), true);
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_TRUE(status_handler().status()->is_success());
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(hci::Status(HostError::kNotSupported), status_handler().status());
}
}
TEST_P(HandlesEvent, InFailedStateAfterPairingFailed) {
// Advance state machine.
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
// Inject failure status.
pairing_state().OnSimplePairingComplete(hci::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_FALSE(status_handler().status()->is_success());
RETURN_IF_FATAL(InjectEvent());
EXPECT_EQ(2, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(kPeerAddress));
static_cast<void>(pairing_state().OnIoCapabilityRequest());
pairing_state().OnIoCapabilityResponse(kTestPeerIoCap);
pairing_state().OnUserConfirmationRequest(kTestPasskey, NoOpUserConfirmationCallback);
pairing_state().OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state().OnLinkKeyNotification(kTestLinkKeyValue, kTestUnauthenticatedLinkKeyType);
// Inject failure status.
pairing_state().OnAuthenticationComplete(hci::StatusCode::kAuthenticationFailure);
EXPECT_EQ(1, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_FALSE(status_handler().status()->is_success());
RETURN_IF_FATAL(InjectEvent());
EXPECT_EQ(2, status_handler().call_count());
ASSERT_TRUE(status_handler().status());
EXPECT_EQ(hci::Status(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(GAP_PairingStateTest, GetInitiatorPairingAction) {
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kComparePasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kGetConsent,
GetInitiatorPairingAction(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kAutomatic,
GetInitiatorPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic, GetInitiatorPairingAction(IOCapability::kNoInputNoOutput,
IOCapability::kNoInputNoOutput));
}
// Ibid., but for "device B."
TEST_F(GAP_PairingStateTest, GetResponderPairingAction) {
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kComparePasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kDisplayPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kRequestPasskey,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(PairingAction::kAutomatic,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(PairingAction::kGetConsent,
GetResponderPairingAction(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(PairingAction::kAutomatic, GetResponderPairingAction(IOCapability::kNoInputNoOutput,
IOCapability::kNoInputNoOutput));
}
// Events are obtained from ibid. association models, mapped to HCI sequences in
// v5.0 Vol 3, Vol 2, Part F, Sec 4.2.10–15.
TEST_F(GAP_PairingStateTest, GetExpectedEvent) {
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyNotificationEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserPasskeyRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(kUserConfirmationRequestEventCode,
GetExpectedEvent(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
// Level of authentication from ibid. table.
TEST_F(GAP_PairingStateTest, IsPairingAuthenticated) {
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_TRUE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_FALSE(IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_FALSE(
IsPairingAuthenticated(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
TEST_F(GAP_PairingStateTest, GetInitiatorAuthRequirements) {
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kKeyboardOnly));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetInitiatorAuthRequirements(IOCapability::kNoInputNoOutput));
}
TEST_F(GAP_PairingStateTest, GetResponderAuthRequirements) {
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kDisplayYesNo, IOCapability::kNoInputNoOutput));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kDisplayOnly));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kDisplayYesNo));
EXPECT_EQ(AuthRequirements::kMITMGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kKeyboardOnly, IOCapability::kNoInputNoOutput));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kDisplayOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kDisplayYesNo));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kKeyboardOnly));
EXPECT_EQ(
AuthRequirements::kGeneralBonding,
GetResponderAuthRequirements(IOCapability::kNoInputNoOutput, IOCapability::kNoInputNoOutput));
}
TEST_F(GAP_PairingStateTest, SkipPairingIfExistingKeyMeetsSecurityRequirements) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
connection.set_bredr_link_key(kTestLinkKey, kTestAuthenticatedLinkKeyType);
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
TestStatusHandler initiator_status_handler;
pairing_state.InitiatePairing(kSecurityRequirements,
initiator_status_handler.MakeStatusCallback());
EXPECT_EQ(0u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
EXPECT_EQ(0, status_handler.call_count());
ASSERT_EQ(1, initiator_status_handler.call_count());
EXPECT_TRUE(initiator_status_handler.status()->is_success());
}
TEST_F(GAP_PairingStateTest,
InitiatorAuthRequiredCausesOnLinkKeyRequestToReturnNullIfUnauthenticatedKeyExists) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
BrEdrSecurityRequirements security{.authentication = true, .secure_connections = false};
pairing_state.InitiatePairing(security, status_handler.MakeStatusCallback());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_EQ(std::nullopt, pairing_state.OnLinkKeyRequest(kPeerAddress));
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest,
InitiatorNoSecurityRequirementsCausesOnLinkKeyRequestToReturnExistingKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
pairing_state.InitiatePairing(kNoSecurityRequirements, NoOpStatusCallback);
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_FALSE(connection.ltk().has_value());
auto reply_key = pairing_state.OnLinkKeyRequest(kPeerAddress);
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(GAP_PairingStateTest, InitiatorOnLinkKeyRequestReturnsNullIfBondDataDoesNotExist) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(kPeerAddress);
EXPECT_FALSE(reply_key.has_value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, IdleStateOnLinkKeyRequestReturnsLinkKeyWhenBondDataExists) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
EXPECT_FALSE(connection.ltk().has_value());
auto reply_key = pairing_state.OnLinkKeyRequest(kPeerAddress);
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(GAP_PairingStateTest, IdleStateOnLinkKeyRequestReturnsNullWhenBondDataDoesNotExist) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
NoOpPairingDelegate pairing_delegate(sm::IOCapability::kNoInputNoOutput);
pairing_state.SetPairingDelegate(pairing_delegate.GetWeakPtr());
auto reply_key = pairing_state.OnLinkKeyRequest(kPeerAddress);
EXPECT_FALSE(reply_key.has_value());
EXPECT_EQ(0, status_handler.call_count());
}
TEST_F(GAP_PairingStateTest, SimplePairingCompleteWithErrorCodeReceivedEarlyFailsPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(kPeerAddress));
EXPECT_EQ(IOCapability::kNoInputNoOutput, *pairing_state.OnIoCapabilityRequest());
EXPECT_EQ(0, status_handler.call_count());
const auto status_code = hci::StatusCode::kPairingNotAllowed;
pairing_state.OnSimplePairingComplete(status_code);
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(hci::Status(status_code), status_handler.status().value());
}
TEST_F(GAP_PairingStateTest, OnLinkKeyRequestReceivedMissingPeerFailsPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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()));
auto reply_key = pairing_state.OnLinkKeyRequest(kPeerAddress);
EXPECT_FALSE(reply_key.has_value());
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(hci::Status(HostError::kFailed), status_handler.status().value());
}
TEST_F(GAP_PairingStateTest, AuthenticationCompleteWithErrorCodeReceivedEarlyFailsPairing) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(kPeerAddress));
EXPECT_EQ(0, status_handler.call_count());
const auto status_code = hci::StatusCode::kAuthenticationFailure;
pairing_state.OnAuthenticationComplete(status_code);
ASSERT_EQ(1, status_handler.call_count());
EXPECT_EQ(hci::Status(status_code), status_handler.status().value());
}
TEST_F(GAP_PairingStateTest,
MultipleQueuedPairingRequestsWithSameSecurityRequirementsCompleteAtSameTimeWithSuccess) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), true);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_TRUE(status_handler.status()->is_success());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_TRUE(initiate_status_handler_0.status()->is_success());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_TRUE(initiate_status_handler_1.status()->is_success());
}
TEST_F(
GAP_PairingStateTest,
MultipleQueuedPairingRequestsWithAuthSecurityRequirementsCompleteAtSameTimeWithInsufficientSecurityFailure) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), true);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_TRUE(status_handler.status()->is_success());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_EQ(hci::Status(HostError::kInsufficientSecurity),
initiate_status_handler_0.status().value());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(hci::Status(HostError::kInsufficientSecurity),
initiate_status_handler_1.status().value());
}
TEST_F(GAP_PairingStateTest,
AuthPairingRequestDuringInitiatorNoAuthPairingFailsQueuedAuthPairingRequest) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), 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(hci::Status(), true);
EXPECT_EQ(1, status_handler.call_count());
ASSERT_TRUE(status_handler.status());
EXPECT_TRUE(status_handler.status()->is_success());
ASSERT_EQ(1, initiate_status_handler_0.call_count());
EXPECT_TRUE(initiate_status_handler_0.status()->is_success());
ASSERT_EQ(1, initiate_status_handler_1.call_count());
EXPECT_EQ(hci::Status(HostError::kInsufficientSecurity),
initiate_status_handler_1.status().value());
// Pairing for second request should not start.
EXPECT_FALSE(pairing_state.initiator());
}
TEST_F(GAP_PairingStateTest,
InitiatingPairingDuringAuthenticationWithExistingUnauthenticatedLinkKey) {
TestStatusHandler status_handler;
auto connection = MakeFakeConnection();
PairingState pairing_state(kTestPeerId, &connection, peer_cache(), MakeAuthRequestCallback(),
status_handler.MakeStatusCallback());
FakePairingDelegate fake_pairing_delegate(sm::IOCapability::kDisplayYesNo);
pairing_state.SetPairingDelegate(fake_pairing_delegate.GetWeakPtr());
peer()->MutBrEdr().SetBondData(
sm::LTK(sm::SecurityProperties(kTestUnauthenticatedLinkKeyType), kTestLinkKey));
TestStatusHandler initiator_status_handler_0;
pairing_state.InitiatePairing(kNoSecurityRequirements,
initiator_status_handler_0.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
TestStatusHandler initiator_status_handler_1;
constexpr BrEdrSecurityRequirements kSecurityRequirements{.authentication = true,
.secure_connections = false};
pairing_state.InitiatePairing(kSecurityRequirements,
initiator_status_handler_1.MakeStatusCallback());
EXPECT_EQ(1u, auth_request_count());
// Authenticate with link key.
EXPECT_NE(std::nullopt, pairing_state.OnLinkKeyRequest(kPeerAddress));
EXPECT_TRUE(connection.ltk().has_value());
pairing_state.OnAuthenticationComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(0, status_handler.call_count());
EXPECT_EQ(1, connection.start_encryption_count());
connection.TriggerEncryptionChangeCallback(hci::Status(), true);
ASSERT_EQ(1, status_handler.call_count());
EXPECT_TRUE(status_handler.status()->is_success());
ASSERT_EQ(1, initiator_status_handler_0.call_count());
EXPECT_TRUE(initiator_status_handler_0.status()->is_success());
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::Status status) { EXPECT_TRUE(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(kPeerAddress));
EXPECT_EQ(IOCapability::kDisplayYesNo, *pairing_state.OnIoCapabilityRequest());
pairing_state.OnIoCapabilityResponse(IOCapability::kDisplayYesNo);
bool confirmed = false;
pairing_state.OnUserConfirmationRequest(kTestPasskey,
[&confirmed](bool confirm) { confirmed = confirm; });
EXPECT_TRUE(confirmed);
pairing_state.OnSimplePairingComplete(hci::StatusCode::kSuccess);
pairing_state.OnLinkKeyNotification(kTestLinkKeyValue, kTestAuthenticatedLinkKeyType);
pairing_state.OnAuthenticationComplete(hci::StatusCode::kSuccess);
EXPECT_EQ(2, connection.start_encryption_count());
connection.TriggerEncryptionChangeCallback(hci::Status(), true);
ASSERT_EQ(2, status_handler.call_count());
EXPECT_TRUE(status_handler.status()->is_success());
EXPECT_EQ(1, initiator_status_handler_0.call_count());
ASSERT_EQ(1, initiator_status_handler_1.call_count());
EXPECT_TRUE(initiator_status_handler_1.status()->is_success());
// No further pairing should occur.
EXPECT_EQ(2u, auth_request_count());
EXPECT_FALSE(pairing_state.initiator());
}
} // namespace
} // namespace bt::gap