blob: 4f4f60503810dffa8e02060966569ae4b00e6489 [file] [log] [blame]
// 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_just_works_numeric_comparison.h"
#include <memory>
#include <gtest/gtest.h>
#include "lib/async/default.h"
#include "lib/fit/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::kJustWorks;
};
class SMP_ScStage1JustWorksNumericComparisonTest : public l2cap::testing::FakeChannelTest {
public:
SMP_ScStage1JustWorksNumericComparisonTest() = default;
~SMP_ScStage1JustWorksNumericComparisonTest() = default;
protected:
using ConfirmCallback = FakeListener::ConfirmCallback;
void SetUp() override { NewScStage1JustWorksNumericComparison(); }
void TearDown() override { stage_1_ = nullptr; }
void NewScStage1JustWorksNumericComparison(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_ = maybe_reader.value();
last_packet_internal_ = std::move(sent_packet);
},
async_get_default_dispatcher());
stage_1_ = std::make_unique<ScStage1JustWorksNumericComparison>(
listener_->as_weak_ptr(), args.role, args.local_pub_key_x, args.peer_pub_key_x, args.method,
sm_chan_->GetWeakPtr(),
[this](fit::result<ScStage1::Output, ErrorCode> out) { last_results_ = out; });
}
UInt128 GenerateConfirmValue(const UInt128& random) const {
UInt256 responder_key = args_.local_pub_key_x, initiator_key = args_.peer_pub_key_x;
if (args_.role == Role::kInitiator) {
std::swap(responder_key, initiator_key);
}
return util::F4(responder_key, initiator_key, random, 0).value();
}
struct MatchingPair {
UInt128 confirm;
UInt128 random;
};
MatchingPair GenerateMatchingConfirmAndRandom() const {
MatchingPair pair;
zx_cprng_draw(pair.random.data(), pair.random.size());
pair.confirm = GenerateConfirmValue(pair.random);
return pair;
}
void DestroyStage1() { stage_1_ = nullptr; }
ScStage1JustWorksNumericComparison* stage_1() { return stage_1_.get(); }
FakeListener* listener() { return listener_.get(); }
std::optional<ValidPacketReader> last_packet() const { return last_packet_; }
std::optional<fit::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<ScStage1JustWorksNumericComparison> stage_1_;
std::optional<ValidPacketReader> last_packet_ = std::nullopt;
// To store the last sent SDU so that the last_packet_ PacketReader points at valid data.
ByteBufferPtr last_packet_internal_;
std::optional<fit::result<ScStage1::Output, ErrorCode>> last_results_ = std::nullopt;
};
using SMP_ScStage1JustWorksNumericComparisonDeathTest = SMP_ScStage1JustWorksNumericComparisonTest;
TEST_F(SMP_ScStage1JustWorksNumericComparisonDeathTest, InvalidMethodsDie) {
ScStage1Args args;
args.method = PairingMethod::kOutOfBand;
ASSERT_DEATH_IF_SUPPORTED(NewScStage1JustWorksNumericComparison(args), ".*method.*");
args.method = PairingMethod::kPasskeyEntryDisplay;
ASSERT_DEATH_IF_SUPPORTED(NewScStage1JustWorksNumericComparison(args), ".*method.*");
args.method = PairingMethod::kPasskeyEntryInput;
ASSERT_DEATH_IF_SUPPORTED(NewScStage1JustWorksNumericComparison(args), ".*method.*");
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, InitiatorJustWorks) {
ScStage1Args args;
args.role = Role::kInitiator;
args.method = PairingMethod::kJustWorks;
NewScStage1JustWorksNumericComparison(args);
MatchingPair vals = GenerateMatchingConfirmAndRandom();
ScStage1::Output expected_results{
.initiator_r = {0}, .responder_r = {0}, .responder_rand = vals.random};
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingRandom, last_packet()->code());
expected_results.initiator_rand = last_packet()->payload<PairingRandomValue>();
stage_1()->OnPairingRandom(vals.random);
ASSERT_TRUE(last_results()->is_ok());
EXPECT_EQ(expected_results, last_results()->value());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, InitiatorNumericComparison) {
ScStage1Args args;
args.role = Role::kInitiator;
args.method = PairingMethod::kNumericComparison;
NewScStage1JustWorksNumericComparison(args);
std::optional<uint32_t> compare = std::nullopt;
ConfirmCallback user_confirm = nullptr;
listener()->set_display_delegate(
[&](uint32_t cmp, Delegate::DisplayMethod method, ConfirmCallback cb) {
ASSERT_EQ(Delegate::DisplayMethod::kComparison, method);
compare = cmp;
user_confirm = std::move(cb);
});
MatchingPair vals = GenerateMatchingConfirmAndRandom();
ScStage1::Output expected_results{
.initiator_r = {0}, .responder_r = {0}, .responder_rand = vals.random};
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingRandom, last_packet()->code());
expected_results.initiator_rand = last_packet()->payload<PairingRandomValue>();
stage_1()->OnPairingRandom(vals.random);
ASSERT_TRUE(user_confirm);
// Results should not be ready until user input is received through user_confirm
ASSERT_FALSE(last_results().has_value());
uint32_t kExpectedCompare =
*util::G2(args.local_pub_key_x, args.peer_pub_key_x, expected_results.initiator_rand,
expected_results.responder_rand);
kExpectedCompare %= 1000000;
EXPECT_EQ(kExpectedCompare, compare);
user_confirm(true);
ASSERT_TRUE(last_results()->is_ok());
EXPECT_EQ(expected_results, last_results()->value());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, InitiatorReceivesConfirmTwiceFails) {
ScStage1Args args;
args.role = Role::kInitiator;
NewScStage1JustWorksNumericComparison(args);
stage_1()->Run();
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingConfirm(Random<PairingConfirmValue>());
stage_1()->OnPairingConfirm(Random<PairingConfirmValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, InitiatorReceiveRandomOutOfOrder) {
stage_1()->Run();
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
// Test to demonstrate receiving random twice for responder causes pairing to fail.
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ResponderReceiveRandomTwiceFails) {
ScStage1Args args;
args.role = Role::kResponder;
NewScStage1JustWorksNumericComparison(args);
stage_1()->Run();
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, InitiatorMismatchedConfirmAndRand) {
ScStage1Args args;
args.role = Role::kInitiator;
NewScStage1JustWorksNumericComparison(args);
MatchingPair vals = GenerateMatchingConfirmAndRandom();
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunLoopUntilIdle();
vals.random[0] -= 1;
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingRandom(vals.random);
EXPECT_EQ(ErrorCode::kConfirmValueFailed, last_results()->error());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ResponderJustWorks) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kJustWorks;
NewScStage1JustWorksNumericComparison(args);
UInt128 kPeerRand = Random<PairingRandomValue>();
ScStage1::Output expected_results{
.initiator_r = {0}, .responder_r = {0}, .initiator_rand = kPeerRand};
stage_1()->Run();
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingConfirm, last_packet()->code());
UInt128 sent_confirm = last_packet()->payload<PairingConfirmValue>();
stage_1()->OnPairingRandom(kPeerRand);
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingRandom, last_packet()->code());
expected_results.responder_rand = last_packet()->payload<PairingRandomValue>();
EXPECT_EQ(GenerateConfirmValue(expected_results.responder_rand), sent_confirm);
ASSERT_TRUE(last_results()->is_ok());
EXPECT_EQ(expected_results, last_results()->value());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ResponderNumericComparison) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kNumericComparison;
NewScStage1JustWorksNumericComparison(args);
std::optional<uint32_t> compare = std::nullopt;
ConfirmCallback user_confirm = nullptr;
listener()->set_display_delegate(
[&](uint32_t cmp, Delegate::DisplayMethod method, ConfirmCallback cb) {
ASSERT_EQ(Delegate::DisplayMethod::kComparison, method);
compare = cmp;
user_confirm = std::move(cb);
});
UInt128 kPeerRand = Random<PairingRandomValue>();
ScStage1::Output expected_results{
.initiator_r = {0}, .responder_r = {0}, .initiator_rand = kPeerRand};
stage_1()->Run();
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingConfirm, last_packet()->code());
UInt128 sent_confirm = last_packet()->payload<PairingConfirmValue>();
stage_1()->OnPairingRandom(kPeerRand);
RunLoopUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingRandom, last_packet()->code());
expected_results.responder_rand = last_packet()->payload<PairingRandomValue>();
EXPECT_EQ(GenerateConfirmValue(expected_results.responder_rand), sent_confirm);
ASSERT_TRUE(user_confirm);
// Results should not be ready until user input is received through user_confirm
ASSERT_FALSE(last_results().has_value());
uint32_t kExpectedCompare =
*util::G2(args.peer_pub_key_x, args.local_pub_key_x, expected_results.initiator_rand,
expected_results.responder_rand);
kExpectedCompare %= 1000000;
EXPECT_EQ(kExpectedCompare, compare);
user_confirm(true);
RunLoopUntilIdle();
ASSERT_TRUE(last_results()->is_ok());
EXPECT_EQ(expected_results, last_results()->value());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ResponderReceivesConfirmFails) {
ScStage1Args args;
args.role = Role::kResponder;
NewScStage1JustWorksNumericComparison(args);
stage_1()->Run();
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingConfirm(Random<PairingConfirmValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ResponderReceiveRandomOutOfOrder) {
NewScStage1JustWorksNumericComparison(ScStage1Args{.role = Role::kResponder});
// `stage_1_` was not `Run`, so the Pairing Confirm hasn't been sent and the peer should not have
// sent the Pairing Random.
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
// This test uses responder flow, but the behavior under test is the same for initiator.
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ListenerRejectsJustWorks) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kJustWorks;
NewScStage1JustWorksNumericComparison(args);
ConfirmCallback user_confirm = nullptr;
listener()->set_confirm_delegate([&](ConfirmCallback cb) { user_confirm = std::move(cb); });
stage_1()->Run();
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
ASSERT_TRUE(user_confirm);
// No results should be reported until the confirmation is rejected.
ASSERT_FALSE(last_results().has_value());
user_confirm(false);
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error());
}
// This test uses responder flow, but the behavior under test is the same for initiator.
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, ListenerRejectsNumericComparison) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kNumericComparison;
NewScStage1JustWorksNumericComparison(args);
ConfirmCallback user_confirm = nullptr;
listener()->set_display_delegate(
[&](uint32_t, Delegate::DisplayMethod method, ConfirmCallback cb) {
ASSERT_EQ(Delegate::DisplayMethod::kComparison, method);
user_confirm = std::move(cb);
});
stage_1()->Run();
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
ASSERT_TRUE(user_confirm);
// No results should be reported until the numeric comparison is rejected.
ASSERT_FALSE(last_results().has_value());
user_confirm(false);
EXPECT_EQ(ErrorCode::kNumericComparisonFailed, last_results()->error());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, StageDestroyedWhileWaitingForJustWorksConfirm) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kJustWorks;
NewScStage1JustWorksNumericComparison(args);
ConfirmCallback user_confirm = nullptr;
listener()->set_confirm_delegate([&](ConfirmCallback cb) { user_confirm = std::move(cb); });
stage_1()->Run();
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
ASSERT_TRUE(user_confirm);
DestroyStage1();
// No results should be reported after Stage 1 is destroyed.
user_confirm(true);
RunLoopUntilIdle();
EXPECT_FALSE(last_results().has_value());
}
TEST_F(SMP_ScStage1JustWorksNumericComparisonTest, StageDestroyedWhileWaitingForNumericComparison) {
ScStage1Args args;
args.role = Role::kResponder;
args.method = PairingMethod::kNumericComparison;
NewScStage1JustWorksNumericComparison(args);
ConfirmCallback user_confirm = nullptr;
listener()->set_display_delegate(
[&](uint32_t, Delegate::DisplayMethod method, ConfirmCallback cb) {
ASSERT_EQ(Delegate::DisplayMethod::kComparison, method);
user_confirm = std::move(cb);
});
stage_1()->Run();
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
ASSERT_TRUE(user_confirm);
DestroyStage1();
// No results should be reported after Stage 1 is destroyed.
user_confirm(true);
RunLoopUntilIdle();
EXPECT_FALSE(last_results().has_value());
}
} // namespace
} // namespace bt::sm