| // Copyright 2020 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 "sc_stage_1_passkey.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "lib/async/default.h" |
| #include "lib/fpromise/result.h" |
| #include "lib/gtest/test_loop_fixture.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/random.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/uint128.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/fake_channel_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/fake_phase_listener.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/packet.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/sc_stage_1.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/smp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/util.h" |
| |
| namespace bt::sm { |
| namespace { |
| |
| struct ScStage1Args { |
| Role role = Role::kInitiator; |
| UInt256 local_pub_key_x = {1}; |
| UInt256 peer_pub_key_x = {2}; |
| PairingMethod method = PairingMethod::kPasskeyEntryDisplay; |
| }; |
| |
| class ScStage1PasskeyTest : public l2cap::testing::FakeChannelTest { |
| public: |
| ScStage1PasskeyTest() = default; |
| ~ScStage1PasskeyTest() = default; |
| |
| protected: |
| using ConfirmCallback = FakeListener::ConfirmCallback; |
| using PasskeyResponseCallback = FakeListener::PasskeyResponseCallback; |
| |
| void SetUp() override { NewScStage1Passkey(); } |
| void TearDown() override { stage_1_ = nullptr; } |
| void NewScStage1Passkey(ScStage1Args args = ScStage1Args()) { |
| args_ = args; |
| listener_ = std::make_unique<FakeListener>(); |
| fake_chan_ = CreateFakeChannel(ChannelOptions(l2cap::kLESMPChannelId)); |
| sm_chan_ = std::make_unique<PairingChannel>(fake_chan_); |
| fake_chan_->SetSendCallback( |
| [this](ByteBufferPtr sent_packet) { |
| auto maybe_reader = ValidPacketReader::ParseSdu(sent_packet); |
| ASSERT_TRUE(maybe_reader.is_ok()) |
| << "Sent invalid packet: " |
| << ProtocolErrorTraits<sm::ErrorCode>::ToString(maybe_reader.error()); |
| last_packet_.emplace(maybe_reader.value()); |
| last_packet_internal_ = std::move(sent_packet); |
| }, |
| async_get_default_dispatcher()); |
| stage_1_ = std::make_unique<ScStage1Passkey>( |
| listener_->as_weak_ptr(), args.role, args.local_pub_key_x, args.peer_pub_key_x, args.method, |
| sm_chan_->GetWeakPtr(), |
| [this](fpromise::result<ScStage1::Output, ErrorCode> out) { last_results_ = out; }); |
| } |
| |
| UInt128 GenerateConfirmValue(const UInt128& random, bool gen_initiator_confirm, uint8_t r) const { |
| UInt256 initiator_key = args_.local_pub_key_x, responder_key = args_.peer_pub_key_x; |
| if (args_.role == Role::kResponder) { |
| std::swap(responder_key, initiator_key); |
| } |
| |
| return gen_initiator_confirm ? util::F4(initiator_key, responder_key, random, r).value() |
| : util::F4(responder_key, initiator_key, random, r).value(); |
| } |
| |
| struct MatchingPair { |
| UInt128 confirm; |
| UInt128 random; |
| }; |
| MatchingPair GenerateMatchingConfirmAndRandom(uint8_t r) const { |
| MatchingPair pair; |
| zx_cprng_draw(pair.random.data(), pair.random.size()); |
| // If the args_ has Role::kResponder, then we are testing responder flow, so the test code will |
| // act in the initiator role, and vice versa if args_ has Role::kInitiator. |
| pair.confirm = GenerateConfirmValue(pair.random, args_.role == Role::kResponder, r); |
| return pair; |
| } |
| |
| void DestroyStage1() { stage_1_ = nullptr; } |
| ScStage1Passkey* stage_1() { return stage_1_.get(); } |
| FakeListener* listener() { return listener_.get(); } |
| std::optional<ValidPacketReader> last_packet() const { return last_packet_; } |
| std::optional<fpromise::result<ScStage1::Output, ErrorCode>> last_results() const { |
| return last_results_; |
| } |
| |
| private: |
| ScStage1Args args_; |
| std::unique_ptr<FakeListener> listener_; |
| fbl::RefPtr<l2cap::testing::FakeChannel> fake_chan_; |
| std::unique_ptr<PairingChannel> sm_chan_; |
| std::unique_ptr<ScStage1Passkey> stage_1_; |
| std::optional<ValidPacketReader> last_packet_ = std::nullopt; |
| // To store the last sent SDU so that the the last_packet_ PacketReader points at valid data. |
| ByteBufferPtr last_packet_internal_; |
| std::optional<fpromise::result<ScStage1::Output, ErrorCode>> last_results_ = std::nullopt; |
| }; |
| |
| using ScStage1PasskeyDeathTest = ScStage1PasskeyTest; |
| TEST_F(ScStage1PasskeyDeathTest, InvalidMethodsDie) { |
| ScStage1Args args; |
| args.method = PairingMethod::kOutOfBand; |
| ASSERT_DEATH_IF_SUPPORTED(NewScStage1Passkey(args), ".*method.*"); |
| args.method = PairingMethod::kNumericComparison; |
| ASSERT_DEATH_IF_SUPPORTED(NewScStage1Passkey(args), ".*method.*"); |
| args.method = PairingMethod::kJustWorks; |
| ASSERT_DEATH_IF_SUPPORTED(NewScStage1Passkey(args), ".*method.*"); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, InitiatorPasskeyEntryDisplay) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryDisplay; |
| args.role = Role::kInitiator; |
| NewScStage1Passkey(args); |
| uint64_t passkey; |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_display_delegate( |
| [&](uint64_t disp_passkey, Delegate::DisplayMethod method, ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| confirm_cb = std::move(cb); |
| passkey = disp_passkey; |
| }); |
| stage_1()->Run(); |
| ASSERT_TRUE(confirm_cb); |
| confirm_cb(true); |
| RunLoopUntilIdle(); |
| |
| MatchingPair vals; |
| UInt128 last_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| const uint8_t r = (passkey & (1 << i)) ? 0x81 : 0x80; |
| vals = GenerateMatchingConfirmAndRandom(r); |
| PairingConfirmValue init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/true, r), init_confirm); |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| } |
| UInt128 passkey_array{0}; |
| // Copy little-endian uint64 passkey to the UInt128 array needed for Stage 2 |
| std::memcpy(passkey_array.data(), &passkey, sizeof(uint64_t)); |
| ScStage1::Output expected_results{.initiator_r = passkey_array, |
| .responder_r = passkey_array, |
| .initiator_rand = last_rand, |
| .responder_rand = vals.random}; |
| ASSERT_TRUE(last_results()->is_ok()); |
| ASSERT_EQ(expected_results, last_results()->value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, InitiatorPasskeyEntryInput) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kInitiator; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_cb); |
| passkey_cb(kPasskey); |
| RunLoopUntilIdle(); |
| |
| MatchingPair vals; |
| UInt128 last_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| const uint8_t r = (kPasskey & (1 << i)) ? 0x81 : 0x80; |
| vals = GenerateMatchingConfirmAndRandom(r); |
| PairingConfirmValue init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/true, r), init_confirm); |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| } |
| UInt128 passkey_array{0}; |
| // Copy little-endian uint32 kPasskey to the UInt128 array needed for Stage 2 |
| std::memcpy(passkey_array.data(), &kPasskey, sizeof(uint64_t)); |
| ScStage1::Output expected_results{.initiator_r = passkey_array, |
| .responder_r = passkey_array, |
| .initiator_rand = last_rand, |
| .responder_rand = vals.random}; |
| ASSERT_TRUE(last_results()->is_ok()); |
| EXPECT_EQ(expected_results, last_results()->value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, InitiatorPeerConfirmBeforeUserInputFails) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kInitiator; |
| NewScStage1Passkey(args); |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_cb); |
| |
| uint8_t r = 0x80; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| // The initiator expects to send the pairing confirm first, so it should now reject pairing |
| ASSERT_TRUE(last_results()->is_error()); |
| EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ReceiveRandomBeforePeerConfirm) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kInitiator; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| listener()->set_request_passkey_delegate([=](PasskeyResponseCallback cb) { cb(kPasskey); }); |
| stage_1()->Run(); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| |
| const uint8_t r = (kPasskey & 1) ? 0x81 : 0x80; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingRandom(vals.random); |
| ASSERT_TRUE(last_results()->is_error()); |
| EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ListenerRejectsPasskeyEntryDisplay) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryDisplay; |
| NewScStage1Passkey(args); |
| ConfirmCallback user_confirm = nullptr; |
| listener()->set_display_delegate( |
| [&](uint32_t, Delegate::DisplayMethod method, ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| user_confirm = std::move(cb); |
| }); |
| |
| stage_1()->Run(); |
| ASSERT_TRUE(user_confirm); |
| user_confirm(false); |
| // No packets should be sent, but Stage 1 should end with kPasskeyEntryFailed |
| EXPECT_FALSE(last_packet().has_value()); |
| EXPECT_EQ(ErrorCode::kPasskeyEntryFailed, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ListenerRejectsPasskeyEntryInput) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| NewScStage1Passkey(args); |
| PasskeyResponseCallback passkey_responder = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_responder = std::move(cb); }); |
| |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_responder); |
| |
| // Responding with a negative number indicates failure. |
| passkey_responder(-12345); |
| EXPECT_FALSE(last_packet().has_value()); |
| EXPECT_EQ(ErrorCode::kPasskeyEntryFailed, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, StageDestroyedWhileWaitingForPasskeyEntryDisplay) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryDisplay; |
| NewScStage1Passkey(args); |
| ConfirmCallback user_confirm = nullptr; |
| listener()->set_display_delegate( |
| [&](uint32_t, Delegate::DisplayMethod method, ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| user_confirm = std::move(cb); |
| }); |
| |
| stage_1()->Run(); |
| ASSERT_TRUE(user_confirm); |
| |
| DestroyStage1(); |
| // No results should be reported after Stage 1 is destroyed. |
| user_confirm(true); |
| EXPECT_FALSE(last_packet().has_value()); |
| EXPECT_FALSE(last_results().has_value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, StageDestroyedWhileWaitingForPasskeyEntryInput) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| NewScStage1Passkey(args); |
| PasskeyResponseCallback passkey_responder = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_responder = std::move(cb); }); |
| |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_responder); |
| |
| DestroyStage1(); |
| // No results should be reported after Stage 1 is destroyed. |
| passkey_responder(12345); |
| EXPECT_FALSE(last_packet().has_value()); |
| EXPECT_FALSE(last_results().has_value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ResponderPasskeyEntryDisplay) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryDisplay; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| uint64_t passkey; |
| ConfirmCallback confirm_cb = nullptr; |
| listener()->set_display_delegate( |
| [&](uint64_t disp_passkey, Delegate::DisplayMethod method, ConfirmCallback cb) { |
| ASSERT_EQ(Delegate::DisplayMethod::kPeerEntry, method); |
| confirm_cb = std::move(cb); |
| passkey = disp_passkey; |
| }); |
| stage_1()->Run(); |
| ASSERT_TRUE(confirm_cb); |
| confirm_cb(true); |
| |
| MatchingPair vals; |
| UInt128 last_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| const uint8_t r = (passkey & (1 << i)) ? 0x81 : 0x80; |
| vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| PairingConfirmValue init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/false, r), init_confirm); |
| } |
| UInt128 passkey_array{0}; |
| // Copy little-endian uint64 passkey to the UInt128 array needed for Stage 2 |
| std::memcpy(passkey_array.data(), &passkey, sizeof(uint64_t)); |
| ScStage1::Output expected_results{.initiator_r = passkey_array, |
| .responder_r = passkey_array, |
| .initiator_rand = vals.random, |
| .responder_rand = last_rand}; |
| ASSERT_TRUE(last_results()->is_ok()); |
| EXPECT_EQ(expected_results, last_results()->value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ResponderPasskeyEntryInput) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_cb); |
| passkey_cb(kPasskey); |
| |
| MatchingPair vals; |
| UInt128 last_rand; |
| for (size_t i = 0; i < 20; ++i) { |
| const uint8_t r = (kPasskey & (1 << i)) ? 0x81 : 0x80; |
| vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| PairingConfirmValue init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/false, r), init_confirm); |
| } |
| UInt128 passkey_array{0}; |
| // Copy little-endian uint64 passkey to the UInt128 array needed for Stage 2 |
| std::memcpy(passkey_array.data(), &kPasskey, sizeof(uint64_t)); |
| ScStage1::Output expected_results{.initiator_r = passkey_array, |
| .responder_r = passkey_array, |
| .initiator_rand = vals.random, |
| .responder_rand = last_rand}; |
| ASSERT_TRUE(last_results()->is_ok()); |
| EXPECT_EQ(expected_results, last_results()->value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ResponderPeerConfirmBeforeUserInputOk) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_cb); |
| |
| // Do 0th iteration outside loop so we can notify the callback after receiving the confirm. |
| uint8_t r = (kPasskey & 1) ? 0x81 : 0x80; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| ASSERT_FALSE(last_packet().has_value()); |
| |
| passkey_cb(kPasskey); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| PairingConfirmValue init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| UInt128 last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/false, r), init_confirm); |
| for (size_t i = 1; i < 20; ++i) { |
| r = (kPasskey & (1 << i)) ? 0x81 : 0x80; |
| vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingConfirm, last_packet()->code()); |
| init_confirm = last_packet()->payload<PairingConfirmValue>(); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(kPairingRandom, last_packet()->code()); |
| last_rand = last_packet()->payload<PairingRandomValue>(); |
| ASSERT_EQ(GenerateConfirmValue(last_rand, /*gen_initiator_confirm=*/false, r), init_confirm); |
| } |
| UInt128 passkey_array{0}; |
| // Copy little-endian uint64 passkey to the UInt128 array needed for Stage 2 |
| std::memcpy(passkey_array.data(), &kPasskey, sizeof(uint64_t)); |
| ScStage1::Output expected_results{.initiator_r = passkey_array, |
| .responder_r = passkey_array, |
| .initiator_rand = vals.random, |
| .responder_rand = last_rand}; |
| ASSERT_TRUE(last_results()->is_ok()); |
| EXPECT_EQ(expected_results, last_results()->value()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ReceiveTwoPairingConfirmsFails) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| listener()->set_request_passkey_delegate([=](PasskeyResponseCallback cb) { cb(kPasskey); }); |
| stage_1()->Run(); |
| |
| const uint8_t r = (kPasskey & 1) ? 0x81 : 0x80; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kPairingConfirm, last_packet()->code()); |
| |
| stage_1()->OnPairingConfirm(vals.confirm); |
| ASSERT_TRUE(last_results()->is_error()); |
| EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ReceiveTwoPairingRandomsFails) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| listener()->set_request_passkey_delegate([=](PasskeyResponseCallback cb) { cb(kPasskey); }); |
| stage_1()->Run(); |
| |
| const uint8_t r = (kPasskey & 1) ? 0x81 : 0x80; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kPairingConfirm, last_packet()->code()); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kPairingRandom, last_packet()->code()); |
| |
| stage_1()->OnPairingRandom(vals.confirm); |
| ASSERT_TRUE(last_results()->is_error()); |
| EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error()); |
| } |
| |
| TEST_F(ScStage1PasskeyTest, ReceiveMismatchedPairingConfirmFails) { |
| ScStage1Args args; |
| args.method = PairingMethod::kPasskeyEntryInput; |
| args.role = Role::kResponder; |
| NewScStage1Passkey(args); |
| const uint64_t kPasskey = 123456; |
| PasskeyResponseCallback passkey_cb = nullptr; |
| listener()->set_request_passkey_delegate( |
| [&](PasskeyResponseCallback cb) { passkey_cb = std::move(cb); }); |
| stage_1()->Run(); |
| ASSERT_TRUE(passkey_cb); |
| passkey_cb(kPasskey); |
| |
| // Here we reverse the bit of the passkey used to generate the confirm, so the random and confirm |
| // values do not match |
| uint8_t r = (kPasskey & 1) ? 0x80 : 0x81; |
| MatchingPair vals = GenerateMatchingConfirmAndRandom(r); |
| stage_1()->OnPairingConfirm(vals.confirm); |
| RunLoopUntilIdle(); |
| EXPECT_EQ(kPairingConfirm, last_packet()->code()); |
| |
| stage_1()->OnPairingRandom(vals.random); |
| ASSERT_TRUE(last_results()->is_error()); |
| EXPECT_EQ(ErrorCode::kConfirmValueFailed, last_results()->error()); |
| } |
| |
| } // namespace |
| } // namespace bt::sm |