blob: 7d80b0943e1977fa3f14d941bc22cfc40ae0c634 [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/sc_stage_1_just_works_numeric_comparison.h"
#include <memory>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uint128.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/connection.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/fake_channel_test.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/fake_phase_listener.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/packet.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/sc_stage_1.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/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 ScStage1JustWorksNumericComparisonTest
: public l2cap::testing::FakeChannelTest {
public:
ScStage1JustWorksNumericComparisonTest() = default;
~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_->GetWeakPtr());
fake_chan_->SetSendCallback(
[this](ByteBufferPtr sent_packet) {
fit::result<ErrorCode, ValidPacketReader> maybe_reader =
ValidPacketReader::ParseSdu(sent_packet);
ASSERT_TRUE(maybe_reader.is_ok())
<< "Sent invalid packet: "
<< ProtocolErrorTraits<sm::ErrorCode>::ToString(
maybe_reader.error_value());
last_packet_.emplace(maybe_reader.value());
last_packet_internal_ = std::move(sent_packet);
},
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<ErrorCode, ScStage1::Output> 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() {
MatchingPair pair;
random_generator()->Get(
{reinterpret_cast<std::byte*>(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<ErrorCode, ScStage1::Output>> last_results() const {
return last_results_;
}
private:
ScStage1Args args_;
std::unique_ptr<FakeListener> listener_;
std::unique_ptr<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<ErrorCode, ScStage1::Output>> last_results_ =
std::nullopt;
};
using ScStage1JustWorksNumericComparisonDeathTest =
ScStage1JustWorksNumericComparisonTest;
TEST_F(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(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},
.initiator_rand = {0},
.responder_rand = vals.random};
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunUntilIdle();
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(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},
.initiator_rand = {0},
.responder_rand = vals.random};
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunUntilIdle();
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(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_value());
}
TEST_F(ScStage1JustWorksNumericComparisonTest,
InitiatorReceiveRandomOutOfOrder) {
stage_1()->Run();
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingRandom(Random<PairingRandomValue>());
EXPECT_EQ(ErrorCode::kUnspecifiedReason, last_results()->error_value());
}
// Test to demonstrate receiving random twice for responder causes pairing to
// fail.
TEST_F(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_value());
}
TEST_F(ScStage1JustWorksNumericComparisonTest,
InitiatorMismatchedConfirmAndRand) {
ScStage1Args args;
args.role = Role::kInitiator;
NewScStage1JustWorksNumericComparison(args);
MatchingPair vals = GenerateMatchingConfirmAndRandom();
stage_1()->Run();
stage_1()->OnPairingConfirm(vals.confirm);
RunUntilIdle();
vals.random[0] -= 1;
ASSERT_FALSE(last_results().has_value());
stage_1()->OnPairingRandom(vals.random);
EXPECT_EQ(ErrorCode::kConfirmValueFailed, last_results()->error_value());
}
TEST_F(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,
.responder_rand = {0}};
stage_1()->Run();
RunUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingConfirm, last_packet()->code());
UInt128 sent_confirm = last_packet()->payload<PairingConfirmValue>();
stage_1()->OnPairingRandom(kPeerRand);
RunUntilIdle();
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(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,
.responder_rand = {0}};
stage_1()->Run();
RunUntilIdle();
ASSERT_TRUE(last_packet().has_value());
EXPECT_EQ(kPairingConfirm, last_packet()->code());
UInt128 sent_confirm = last_packet()->payload<PairingConfirmValue>();
stage_1()->OnPairingRandom(kPeerRand);
RunUntilIdle();
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);
RunUntilIdle();
ASSERT_TRUE(last_results()->is_ok());
EXPECT_EQ(expected_results, last_results()->value());
}
TEST_F(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_value());
}
TEST_F(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_value());
}
// This test uses responder flow, but the behavior under test is the same for
// initiator.
TEST_F(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_value());
}
// This test uses responder flow, but the behavior under test is the same for
// initiator.
TEST_F(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_value());
}
TEST_F(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);
RunUntilIdle();
EXPECT_FALSE(last_results().has_value());
}
TEST_F(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);
RunUntilIdle();
EXPECT_FALSE(last_results().has_value());
}
} // namespace
} // namespace bt::sm