| // 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 "idle_phase.h" |
| |
| #include <memory> |
| |
| #include <fbl/macros.h> |
| #include <gtest/gtest.h> |
| |
| #include "lib/fit/result.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.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/sm/fake_phase_listener.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/packet.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/smp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/types.h" |
| #include "src/connectivity/bluetooth/core/bt-host/sm/util.h" |
| #include "src/lib/fxl/memory/weak_ptr.h" |
| |
| namespace bt { |
| namespace sm { |
| namespace { |
| |
| class SMP_IdlePhaseTest : public l2cap::testing::FakeChannelTest { |
| public: |
| SMP_IdlePhaseTest() = default; |
| ~SMP_IdlePhaseTest() override = default; |
| |
| protected: |
| void SetUp() override { NewIdlePhase(); } |
| |
| void TearDown() override { idle_phase_ = nullptr; } |
| |
| void NewIdlePhase(hci::Connection::Role role = hci::Connection::Role::kMaster, |
| hci::Connection::LinkType ll_type = hci::Connection::LinkType::kLE) { |
| l2cap::ChannelId cid = |
| ll_type == hci::Connection::LinkType::kLE ? l2cap::kLESMPChannelId : l2cap::kSMPChannelId; |
| ChannelOptions options(cid); |
| options.link_type = ll_type; |
| |
| listener_ = std::make_unique<FakeListener>(); |
| fake_chan_ = CreateFakeChannel(options); |
| sm_chan_ = std::make_unique<PairingChannel>(fake_chan_); |
| idle_phase_ = std::make_unique<IdlePhase>( |
| sm_chan_->GetWeakPtr(), listener_->as_weak_ptr(), role, |
| [this](PairingRequestParams preq) { last_pairing_req_ = preq; }, |
| [this](AuthReqField auth_req) { last_security_req_ = auth_req; }); |
| } |
| |
| l2cap::testing::FakeChannel* fake_chan() const { return fake_chan_.get(); } |
| IdlePhase* idle_phase() { return idle_phase_.get(); } |
| |
| std::optional<PairingRequestParams> last_pairing_req() { return last_pairing_req_; } |
| std::optional<AuthReqField> last_security_req() { return last_security_req_; } |
| |
| private: |
| std::unique_ptr<FakeListener> listener_; |
| fbl::RefPtr<l2cap::testing::FakeChannel> fake_chan_; |
| std::unique_ptr<PairingChannel> sm_chan_; |
| std::unique_ptr<IdlePhase> idle_phase_; |
| |
| std::optional<PairingRequestParams> last_pairing_req_; |
| std::optional<AuthReqField> last_security_req_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(SMP_IdlePhaseTest); |
| }; |
| |
| TEST_F(SMP_IdlePhaseTest, HandlesChannelClosedGracefully) { |
| fake_chan()->Close(); |
| RunLoopUntilIdle(); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, PairingRequestAsSlavePassedThrough) { |
| NewIdlePhase(hci::Connection::Role::kSlave); |
| StaticByteBuffer<util::PacketSize<PairingRequestParams>()> preq_packet; |
| PacketWriter writer(kPairingRequest, &preq_packet); |
| auto preq = PairingRequestParams{.auth_req = 0x01, .responder_key_dist_gen = 0x03}; |
| *writer.mutable_payload<PairingRequestParams>() = preq; |
| ASSERT_FALSE(last_pairing_req().has_value()); |
| fake_chan()->Receive(preq_packet); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_pairing_req().has_value()); |
| PairingRequestParams last_preq = last_pairing_req().value(); |
| ASSERT_EQ(0, memcmp(&last_preq, &preq, sizeof(PairingRequestParams))); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, PairingRequestAsMasterSendsPairingFailed) { |
| StaticByteBuffer<util::PacketSize<PairingRequestParams>()> preq_packet; |
| PacketWriter writer(kPairingRequest, &preq_packet); |
| auto preq = PairingRequestParams{.auth_req = 0x01, .responder_key_dist_gen = 0x03}; |
| *writer.mutable_payload<PairingRequestParams>() = preq; |
| |
| Code packet_header = kPairingConfirm; // random, non-failure packet header |
| ErrorCode ecode = ErrorCode::kNoError; |
| fake_chan()->SetSendCallback( |
| [&ecode, &packet_header](ByteBufferPtr sdu) { |
| PacketReader reader(sdu.get()); |
| packet_header = reader.code(); |
| ecode = reader.payload<ErrorCode>(); |
| }, |
| dispatcher()); |
| |
| ASSERT_FALSE(last_pairing_req().has_value()); |
| fake_chan()->Receive(preq_packet); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(last_pairing_req().has_value()); |
| ASSERT_EQ(packet_header, kPairingFailed); |
| ASSERT_EQ(ecode, ErrorCode::kCommandNotSupported); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, SecurityRequestAsMasterPassedThrough) { |
| AuthReqField generic_auth_req = 0x03; |
| auto security_req = CreateStaticByteBuffer(kSecurityRequest, generic_auth_req); |
| ASSERT_FALSE(last_security_req().has_value()); |
| fake_chan()->Receive(security_req); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(last_security_req().has_value()); |
| AuthReqField last_security_req_payload = last_security_req().value(); |
| ASSERT_EQ(generic_auth_req, last_security_req_payload); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, SecurityRequestAsSlaveSendsPairingFailed) { |
| NewIdlePhase(hci::Connection::Role::kSlave); |
| AuthReqField generic_auth_req = 0x03; |
| auto security_req = CreateStaticByteBuffer(kSecurityRequest, generic_auth_req); |
| |
| Code packet_header = kPairingConfirm; // random, non-failure packet header |
| ErrorCode ecode = ErrorCode::kNoError; |
| fake_chan()->SetSendCallback( |
| [&ecode, &packet_header](ByteBufferPtr sdu) { |
| PacketReader reader(sdu.get()); |
| packet_header = reader.code(); |
| ecode = reader.payload<ErrorCode>(); |
| }, |
| dispatcher()); |
| |
| ASSERT_FALSE(last_security_req().has_value()); |
| fake_chan()->Receive(security_req); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(last_security_req().has_value()); |
| ASSERT_EQ(packet_header, kPairingFailed); |
| ASSERT_EQ(ecode, ErrorCode::kCommandNotSupported); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, NonSecurityPairingRequestMessageDropped) { |
| StaticByteBuffer<util::PacketSize<PairingResponseParams>()> pres_packet; |
| PacketWriter writer(kPairingResponse, &pres_packet); |
| *writer.mutable_payload<PairingResponseParams>() = PairingResponseParams(); |
| |
| bool message_sent = false; |
| fake_chan()->SetSendCallback([&message_sent](ByteBufferPtr sdu) { message_sent = true; }, |
| dispatcher()); |
| |
| fake_chan()->Receive(pres_packet); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(last_pairing_req().has_value()); |
| ASSERT_FALSE(last_security_req().has_value()); |
| ASSERT_FALSE(message_sent); |
| } |
| |
| TEST_F(SMP_IdlePhaseTest, DropsInvalidPacket) { |
| auto bad_packet = CreateStaticByteBuffer(0xFF); // 0xFF is not a valid SMP header code |
| |
| bool message_sent = false; |
| fake_chan()->SetSendCallback([&message_sent](ByteBufferPtr sdu) { message_sent = true; }, |
| dispatcher()); |
| |
| fake_chan()->Receive(bad_packet); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(last_pairing_req().has_value()); |
| ASSERT_FALSE(last_security_req().has_value()); |
| ASSERT_FALSE(message_sent); |
| } |
| |
| } // namespace |
| } // namespace sm |
| } // namespace bt |