blob: 2e0b55f0f661f92d200edd10dd0f645d1a8a956d [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/gap/interrogator.h"
#include <lib/async/default.h>
#include "src/connectivity/bluetooth/core/bt-host/common/status.h"
#include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/status.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.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/testing/test_packets.h"
namespace bt::gap {
constexpr hci::ConnectionHandle kConnectionHandle = 0x0BAA;
const DeviceAddress kTestDevAddr(DeviceAddress::Type::kBREDR, {1});
class TestInterrogator final : public Interrogator {
public:
// Reuse constructor
using Interrogator::Interrogator;
~TestInterrogator() override = default;
using InterrogationRefPtr = Interrogator::InterrogationRefPtr;
void set_send_commands_cb(fit::function<void(InterrogationRefPtr)> cb) {
send_commands_cb_ = std::move(cb);
}
// Make command methods public for testing:
using Interrogator::ReadRemoteVersionInformation;
private:
// Interrogator overrides:
void SendCommands(InterrogationRefPtr interrogation) override {
if (send_commands_cb_) {
send_commands_cb_(std::move(interrogation));
}
}
fit::function<void(InterrogationRefPtr)> send_commands_cb_;
};
using InterrogationRefPtr = TestInterrogator::InterrogationRefPtr;
using TestingBase = bt::testing::ControllerTest<bt::testing::MockController>;
class InterrogatorTest : public TestingBase {
public:
InterrogatorTest() = default;
~InterrogatorTest() override = default;
void SetUp() override {
TestingBase::SetUp();
peer_cache_ = std::make_unique<PeerCache>();
interrogator_ = std::make_unique<TestInterrogator>(peer_cache_.get(), transport()->WeakPtr());
StartTestDevice();
}
void TearDown() override {
RunLoopUntilIdle();
test_device()->Stop();
interrogator_ = nullptr;
peer_cache_ = nullptr;
TestingBase::TearDown();
}
protected:
PeerCache* peer_cache() const { return peer_cache_.get(); }
TestInterrogator* interrogator() const { return interrogator_.get(); }
void DeleteInterrogator() { interrogator_.reset(); }
private:
std::unique_ptr<PeerCache> peer_cache_;
std::unique_ptr<TestInterrogator> interrogator_;
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(InterrogatorTest);
};
using GAP_InterrogatorTest = InterrogatorTest;
TEST_F(GAP_InterrogatorTest, DroppingInterrogationRefCompletesInterrogation) {
std::optional<InterrogationRefPtr> ref;
interrogator()->set_send_commands_cb([&ref](InterrogationRefPtr r) { ref = std::move(r); });
auto* peer = peer_cache()->NewPeer(kTestDevAddr, true);
ASSERT_FALSE(ref.has_value());
std::optional<hci::Status> result;
interrogator()->Start(peer->identifier(), kConnectionHandle,
[&](hci::Status status) { result = status; });
ASSERT_TRUE(ref.has_value());
EXPECT_FALSE(result.has_value());
// Dropping ref should call result callback with success status.
ref.reset();
ASSERT_TRUE(result.has_value());
EXPECT_TRUE(result->is_success());
}
TEST_F(GAP_InterrogatorTest,
DestroyingInterrogatorBeforeReadRemoteVersionInformationCompleteCallbackCalledDoesNotCrash) {
std::optional<InterrogationRefPtr> ref;
interrogator()->set_send_commands_cb([this, &ref](InterrogationRefPtr r) {
ref = std::move(r);
EXPECT_CMD_PACKET_OUT(test_device(), testing::ReadRemoteVersionInfoPacket(kConnectionHandle));
interrogator()->ReadRemoteVersionInformation(*ref);
});
auto* peer = peer_cache()->NewPeer(kTestDevAddr, true);
ASSERT_FALSE(ref.has_value());
std::optional<hci::Status> result;
interrogator()->Start(peer->identifier(), kConnectionHandle,
[&](hci::Status status) { result = status; });
ASSERT_TRUE(ref.has_value());
EXPECT_FALSE(result.has_value());
DeleteInterrogator();
// The result callback should be called synchronously.
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), hci::Status(HostError::kCanceled));
test_device()->SendCommandChannelPacket(
testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle));
// Process command complete callback.
RunLoopUntilIdle();
}
TEST_F(GAP_InterrogatorTest, Cancel) {
std::optional<InterrogationRefPtr> ref;
interrogator()->set_send_commands_cb([this, &ref](InterrogationRefPtr r) {
ref = std::move(r);
EXPECT_CMD_PACKET_OUT(test_device(), testing::ReadRemoteVersionInfoPacket(kConnectionHandle));
interrogator()->ReadRemoteVersionInformation(*ref);
});
auto* peer = peer_cache()->NewPeer(kTestDevAddr, true);
ASSERT_FALSE(ref.has_value());
std::optional<hci::Status> result;
interrogator()->Start(peer->identifier(), kConnectionHandle,
[&](hci::Status status) { result = status; });
ASSERT_TRUE(ref.has_value());
EXPECT_FALSE(result.has_value());
interrogator()->Cancel(peer->identifier());
// The result callback should be called synchronously.
EXPECT_TRUE(result.has_value());
EXPECT_EQ(result.value(), hci::Status(HostError::kCanceled));
// Events after Cancel() should be ignored.
test_device()->SendCommandChannelPacket(
testing::ReadRemoteVersionInfoCompletePacket(kConnectionHandle));
// Process command complete callback.
RunLoopUntilIdle();
EXPECT_EQ(result.value(), hci::Status(HostError::kCanceled));
}
} // namespace bt::gap