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