blob: 816f1b1f1998a7b3259bfa2225c817b6ce27d8e4 [file] [log] [blame]
// Copyright 2018 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 "garnet/drivers/bluetooth/lib/gap/bredr_discovery_manager.h"
#include "garnet/drivers/bluetooth/lib/common/test_helpers.h"
#include "garnet/drivers/bluetooth/lib/gap/remote_device_cache.h"
#include "garnet/drivers/bluetooth/lib/hci/hci.h"
#include "garnet/drivers/bluetooth/lib/testing/fake_controller_test.h"
#include "garnet/drivers/bluetooth/lib/testing/test_controller.h"
namespace btlib {
namespace gap {
namespace {
using ::btlib::testing::CommandTransaction;
using common::UpperBits;
using common::LowerBits;
using TestingBase =
::btlib::testing::FakeControllerTest<::btlib::testing::TestController>;
class BrEdrDiscoveryManagerTest : public TestingBase {
public:
BrEdrDiscoveryManagerTest() = default;
~BrEdrDiscoveryManagerTest() override = default;
void SetUp() override {
TestingBase::SetUp();
discovery_manager_ =
std::make_unique<BrEdrDiscoveryManager>(transport(), &device_cache_);
test_device()->StartCmdChannel(test_cmd_chan());
test_device()->StartAclChannel(test_acl_chan());
}
void TearDown() override {
discovery_manager_ = nullptr;
test_device()->Stop();
TestingBase::TearDown();
}
protected:
BrEdrDiscoveryManager* discovery_manager() const {
return discovery_manager_.get();
}
private:
RemoteDeviceCache device_cache_;
std::unique_ptr<BrEdrDiscoveryManager> discovery_manager_;
FXL_DISALLOW_COPY_AND_ASSIGN(BrEdrDiscoveryManagerTest);
};
using GAP_BrEdrDiscoveryManagerTest = BrEdrDiscoveryManagerTest;
// clang-format off
const auto kInquiry = common::CreateStaticByteBuffer(
LowerBits(hci::kInquiry), UpperBits(hci::kInquiry),
0x05, // Paramreter total size
0x33, 0x8B, 0x9E, // GIAC
0x30, // kInquiryMax
0x00 // Unlimited responses
);
const auto kInquiry_rsp = common::CreateStaticByteBuffer(
hci::kCommandStatusEventCode,
0x04, // parameter_total_size (4 bytes)
hci::kSuccess, 0xF0, // success, num_hci_command_packets (240)
LowerBits(hci::kInquiry), UpperBits(hci::kInquiry) // HCI_Inquiry opcode
);
const auto kInquiry_rsperror = common::CreateStaticByteBuffer(
hci::kCommandStatusEventCode,
0x04, // parameter_total_size (4 bytes)
hci::kHardwareFailure, 0xF0, // success, num_hci_command_packets (240)
LowerBits(hci::kInquiry), UpperBits(hci::kInquiry) // HCI_Inquiry opcode
);
const auto kInquiryComplete = common::CreateStaticByteBuffer(
hci::kInquiryCompleteEventCode,
0x01, // parameter_total_size (1 bytes)
hci::kSuccess
);
const auto kInquiryCompleteError = common::CreateStaticByteBuffer(
hci::kInquiryCompleteEventCode,
0x01, // parameter_total_size (1 bytes)
hci::kHardwareFailure
);
const auto kInquiryResult = common::CreateStaticByteBuffer(
hci::kInquiryResultEventCode,
0x0F, // parameter_total_size (15 bytes)
0x01, // num_responses
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // bd_addr[0]
0x00, // page_scan_repetition_mode[0] (R0)
0x00, // unused / reserved
0x00, // unused / reserved
0x00, 0x1F, 0x00, // class_of_device[0] (unspecified)
0x00, 0x00 // clock_offset[0]
);
const auto kInqCancel = common::CreateStaticByteBuffer(
LowerBits(hci::kInquiryCancel), UpperBits(hci::kInquiryCancel), // opcode
0x00 // parameter_total_size
);
#define COMMAND_COMPLETE_RSP(opcode) \
common::CreateStaticByteBuffer(hci::kCommandCompleteEventCode, 0x04, 0xF0, \
LowerBits((opcode)), UpperBits((opcode)), \
hci::kSuccess);
const auto kInqCancelRsp = COMMAND_COMPLETE_RSP(hci::kInquiryCancel);
const auto kReadScanEnable = common::CreateStaticByteBuffer(
LowerBits(hci::kReadScanEnable), UpperBits(hci::kReadScanEnable),
0x00 // No parameters
);
#define READ_SCAN_ENABLE_RSP(scan_enable) \
common::CreateStaticByteBuffer(hci::kCommandCompleteEventCode, 0x05, 0xF0, \
LowerBits(hci::kReadScanEnable), \
UpperBits(hci::kReadScanEnable), \
hci::kSuccess, (scan_enable))
const auto kReadScanEnableRspNone = READ_SCAN_ENABLE_RSP(0x00);
const auto kReadScanEnableRspInquiry = READ_SCAN_ENABLE_RSP(0x01);
const auto kReadScanEnableRspPage = READ_SCAN_ENABLE_RSP(0x02);
const auto kReadScanEnableRspBoth = READ_SCAN_ENABLE_RSP(0x03);
#undef READ_SCAN_ENABLE_RSP
#define WRITE_SCAN_ENABLE_CMD(scan_enable) \
common::CreateStaticByteBuffer(LowerBits(hci::kWriteScanEnable), \
UpperBits(hci::kWriteScanEnable), 0x01, \
(scan_enable))
const auto kWriteScanEnableNone = WRITE_SCAN_ENABLE_CMD(0x00);
const auto kWriteScanEnableInq = WRITE_SCAN_ENABLE_CMD(0x01);
const auto kWriteScanEnablePage = WRITE_SCAN_ENABLE_CMD(0x02);
const auto kWriteScanEnableBoth = WRITE_SCAN_ENABLE_CMD(0x03);
#undef WRITE_SCAN_ENABLE_CMD
const auto kWriteScanEnableRsp = COMMAND_COMPLETE_RSP(hci::kWriteScanEnable);
#undef COMMAND_COMPLETE_RSP
// clang-format on
// Test: discovering() answers correctly
// Test: requesting discovery should start inquiry
// Test: Inquiry Results that come in when there is discovery get reported up
// correctly to the sessions
// Test: Devices discovered are reported to the cache
// Test: Inquiry Results that come in when there's no discovery happening get
// discarded.
TEST_F(GAP_BrEdrDiscoveryManagerTest, RequestDiscoveryAndDrop) {
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
std::unique_ptr<BrEdrDiscoverySession> session;
size_t devices_found = 0u;
discovery_manager()->RequestDiscovery(
[&session, &devices_found](auto status, auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found](const auto& device) { devices_found++; });
session = std::move(cb_session);
});
EXPECT_FALSE(discovery_manager()->discovering());
RunLoopUntilIdle();
EXPECT_EQ(1u, devices_found);
EXPECT_TRUE(discovery_manager()->discovering());
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
test_device()->SendCommandChannelPacket(kInquiryComplete);
RunLoopUntilIdle();
EXPECT_EQ(2u, devices_found);
// TODO(jamuraa, NET-619): test InquiryCancel when it is implemented
session = nullptr;
test_device()->SendCommandChannelPacket(kInquiryResult);
RunLoopUntilIdle();
EXPECT_EQ(2u, devices_found);
EXPECT_FALSE(discovery_manager()->discovering());
test_device()->SendCommandChannelPacket(kInquiryComplete);
}
// Test: requesting a second discovery should start a session without sending
// any more HCI commands.
// Test: dropping the first discovery shouldn't stop inquiry
// Test: starting two sessions at once should only start inquiry once
TEST_F(GAP_BrEdrDiscoveryManagerTest, MultipleRequests) {
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
std::unique_ptr<BrEdrDiscoverySession> session1;
size_t devices_found1 = 0u;
discovery_manager()->RequestDiscovery(
[&session1, &devices_found1](auto status, auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found1](const auto& device) { devices_found1++; });
session1 = std::move(cb_session);
});
EXPECT_FALSE(discovery_manager()->discovering());
RunLoopUntilIdle();
EXPECT_TRUE(session1);
EXPECT_EQ(1u, devices_found1);
EXPECT_TRUE(discovery_manager()->discovering());
std::unique_ptr<BrEdrDiscoverySession> session2;
size_t devices_found2 = 0u;
discovery_manager()->RequestDiscovery(
[&session2, &devices_found2](auto status, auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found2](const auto& device) { devices_found2++; });
session2 = std::move(cb_session);
});
RunLoopUntilIdle();
EXPECT_TRUE(session2);
EXPECT_EQ(1u, devices_found1);
EXPECT_EQ(0u, devices_found2);
EXPECT_TRUE(discovery_manager()->discovering());
test_device()->SendCommandChannelPacket(kInquiryResult);
RunLoopUntilIdle();
EXPECT_EQ(2u, devices_found1);
EXPECT_EQ(1u, devices_found2);
session1 = nullptr;
RunLoopUntilIdle();
test_device()->SendCommandChannelPacket(kInquiryResult);
RunLoopUntilIdle();
EXPECT_EQ(2u, devices_found1);
EXPECT_EQ(2u, devices_found2);
// TODO(jamuraa, NET-619): test InquiryCancel when it is implemented
session2 = nullptr;
test_device()->SendCommandChannelPacket(kInquiryResult);
RunLoopUntilIdle();
EXPECT_EQ(2u, devices_found1);
EXPECT_EQ(2u, devices_found2);
EXPECT_FALSE(discovery_manager()->discovering());
test_device()->SendCommandChannelPacket(kInquiryComplete);
}
// Test: starting a session "while" the other one is stopping a session should
// restart the session
TEST_F(GAP_BrEdrDiscoveryManagerTest, RequestDiscoveryWhileStop) {
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
std::unique_ptr<BrEdrDiscoverySession> session1;
size_t devices_found1 = 0u;
discovery_manager()->RequestDiscovery(
[&session1, &devices_found1](auto status, auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found1](const auto& device) { devices_found1++; });
session1 = std::move(cb_session);
});
EXPECT_FALSE(discovery_manager()->discovering());
RunLoopUntilIdle();
EXPECT_TRUE(session1);
EXPECT_EQ(1u, devices_found1);
EXPECT_TRUE(discovery_manager()->discovering());
std::unique_ptr<BrEdrDiscoverySession> session2;
size_t devices_found2 = 0u;
// TODO(jamuraa, NET-619): test InquiryCancel when it is implemented
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
session1 = nullptr;
discovery_manager()->RequestDiscovery(
[&session2, &devices_found2](auto status, auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found2](const auto& device) { devices_found2++; });
session2 = std::move(cb_session);
});
// We're still waiting on the previous session to complete, so we haven't
// started tne new session yet.
RunLoopUntilIdle();
test_device()->SendCommandChannelPacket(kInquiryResult);
test_device()->SendCommandChannelPacket(kInquiryComplete);
RunLoopUntilIdle();
EXPECT_TRUE(session2);
EXPECT_EQ(1u, devices_found1);
EXPECT_EQ(1u, devices_found2);
EXPECT_TRUE(discovery_manager()->discovering());
test_device()->SendCommandChannelPacket(kInquiryResult);
RunLoopUntilIdle();
EXPECT_EQ(1u, devices_found1);
EXPECT_EQ(2u, devices_found2);
// TODO(jamuraa, NET-619): test InquiryCancel when it is implemented
session2 = nullptr;
RunLoopUntilIdle();
EXPECT_EQ(1u, devices_found1);
EXPECT_EQ(2u, devices_found2);
}
// Test: When Inquiry Fails to start, we report this back to the requester.
TEST_F(GAP_BrEdrDiscoveryManagerTest, RequestDiscoveryError) {
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsperror, &kInquiryResult}));
std::unique_ptr<BrEdrDiscoverySession> session;
size_t devices_found = 0u;
discovery_manager()->RequestDiscovery(
[&session, &devices_found](auto status, auto cb_session) {
EXPECT_FALSE(status);
EXPECT_FALSE(cb_session);
EXPECT_EQ(common::HostError::kProtocolError, status.error());
EXPECT_EQ(hci::kHardwareFailure, status.protocol_error());
});
EXPECT_FALSE(discovery_manager()->discovering());
RunLoopUntilIdle();
EXPECT_FALSE(discovery_manager()->discovering());
}
// Test: When inquiry complete indicates failure, we signal to the current
// sessions.
TEST_F(GAP_BrEdrDiscoveryManagerTest, ContinuingDiscoveryError) {
test_device()->QueueCommandTransaction(
CommandTransaction(kInquiry, {&kInquiry_rsp, &kInquiryResult}));
std::unique_ptr<BrEdrDiscoverySession> session;
size_t devices_found = 0u;
bool error_callback = false;
discovery_manager()->RequestDiscovery(
[&session, &devices_found, &error_callback](auto status,
auto cb_session) {
EXPECT_TRUE(status);
cb_session->set_result_callback(
[&devices_found](const auto& device) { devices_found++; });
cb_session->set_error_callback(
[&error_callback]() { error_callback = true; });
session = std::move(cb_session);
});
EXPECT_FALSE(discovery_manager()->discovering());
RunLoopUntilIdle();
EXPECT_EQ(1u, devices_found);
EXPECT_TRUE(discovery_manager()->discovering());
test_device()->SendCommandChannelPacket(kInquiryCompleteError);
RunLoopUntilIdle();
EXPECT_TRUE(error_callback);
EXPECT_FALSE(discovery_manager()->discovering());
session = nullptr;
RunLoopUntilIdle();
}
// Test: requesting discoverable works
// Test: requesting discoverable while discoverable is pending doesn't send
// any more HCI commands
TEST_F(GAP_BrEdrDiscoveryManagerTest, DiscoverableSet) {
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {}));
std::vector<std::unique_ptr<BrEdrDiscoverableSession>> sessions;
auto session_cb = [&sessions](auto status, auto cb_session) {
EXPECT_TRUE(status);
sessions.emplace_back(std::move(cb_session));
};
discovery_manager()->RequestDiscoverable(session_cb);
RunLoopUntilIdle();
EXPECT_EQ(0u, sessions.size());
EXPECT_FALSE(discovery_manager()->discoverable());
test_device()->QueueCommandTransaction(
CommandTransaction(kWriteScanEnableInq, {}));
test_device()->SendCommandChannelPacket(kReadScanEnableRspNone);
RunLoopUntilIdle();
// Request another session while the first is pending.
discovery_manager()->RequestDiscoverable(session_cb);
test_device()->SendCommandChannelPacket(kWriteScanEnableRsp);
RunLoopUntilIdle();
EXPECT_EQ(2u, sessions.size());
EXPECT_TRUE(discovery_manager()->discoverable());
discovery_manager()->RequestDiscoverable(session_cb);
EXPECT_EQ(3u, sessions.size());
EXPECT_TRUE(discovery_manager()->discoverable());
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {&kReadScanEnableRspInquiry}));
test_device()->QueueCommandTransaction(
CommandTransaction(kWriteScanEnableNone, {&kWriteScanEnableRsp}));
sessions.clear();
RunLoopUntilIdle();
EXPECT_FALSE(discovery_manager()->discoverable());
}
// Test: requesting discoverable while discovery is disabling leaves
// the discoverable enabled and reports success
// Test: enable/disable while page scan is enabled works.
TEST_F(GAP_BrEdrDiscoveryManagerTest, DiscoverableRequestWhileStopping) {
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {&kReadScanEnableRspPage}));
test_device()->QueueCommandTransaction(
CommandTransaction(kWriteScanEnableBoth, {&kWriteScanEnableRsp}));
std::vector<std::unique_ptr<BrEdrDiscoverableSession>> sessions;
auto session_cb = [&sessions](auto status, auto cb_session) {
EXPECT_TRUE(status);
sessions.emplace_back(std::move(cb_session));
};
discovery_manager()->RequestDiscoverable(session_cb);
RunLoopUntilIdle();
EXPECT_EQ(1u, sessions.size());
EXPECT_TRUE(discovery_manager()->discoverable());
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {}));
sessions.clear();
RunLoopUntilIdle();
// Request a new discovery before the procedure finishes.
// This will queue another ReadScanEnable just in case the disable write is
// in progress.
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {}));
discovery_manager()->RequestDiscoverable(session_cb);
test_device()->SendCommandChannelPacket(kReadScanEnableRspBoth);
// This shouldn't send any WriteScanEnable because we're already in the right
// mode (TestController will assert if we do as it's not expecting)
RunLoopUntilIdle();
EXPECT_EQ(1u, sessions.size());
EXPECT_TRUE(discovery_manager()->discoverable());
// If somehow the scan got turned off, we will still turn it back on.
test_device()->QueueCommandTransaction(
CommandTransaction(kWriteScanEnableBoth, {&kWriteScanEnableRsp}));
test_device()->SendCommandChannelPacket(kReadScanEnableRspPage);
RunLoopUntilIdle();
EXPECT_EQ(1u, sessions.size());
EXPECT_TRUE(discovery_manager()->discoverable());
test_device()->QueueCommandTransaction(
CommandTransaction(kReadScanEnable, {&kReadScanEnableRspBoth}));
test_device()->QueueCommandTransaction(
CommandTransaction(kWriteScanEnablePage, {&kWriteScanEnableRsp}));
sessions.clear();
RunLoopUntilIdle();
EXPECT_FALSE(discovery_manager()->discoverable());
}
} // namespace
} // namespace gap
} // namespace btlib