You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
464 lines
18 KiB
464 lines
18 KiB
/*
|
|
* Copyright 2019 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "neighbor/inquiry.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <future>
|
|
#include <map>
|
|
#include <memory>
|
|
|
|
#include "common/bind.h"
|
|
#include "common/callback.h"
|
|
#include "hci/address.h"
|
|
#include "hci/class_of_device.h"
|
|
#include "hci/hci_layer.h"
|
|
#include "hci/hci_packets.h"
|
|
#include "os/thread.h"
|
|
#include "packet/raw_builder.h"
|
|
|
|
namespace bluetooth {
|
|
namespace neighbor {
|
|
namespace {
|
|
|
|
static const uint8_t kNumberPacketsReadyToReceive = 1;
|
|
|
|
/**
|
|
* This structure reflects the current state of the bluetooth chip
|
|
* at any given time.
|
|
*/
|
|
static const int8_t kInitialInquiryResponseTransmitPowerLevel = 123;
|
|
static const uint16_t kInitialInquiryScanInterval = 1111;
|
|
static const uint16_t kInitialInquiryScanWindow = 2222;
|
|
|
|
struct HciRegister {
|
|
bool one_shot_inquiry_active;
|
|
bool periodic_inquiry_active;
|
|
int8_t inquiry_response_transmit_power_level;
|
|
uint16_t inquiry_scan_interval;
|
|
uint16_t inquiry_scan_window;
|
|
hci::InquiryScanType inquiry_scan_type;
|
|
hci::InquiryMode inquiry_mode;
|
|
uint8_t inquiry_length;
|
|
uint8_t num_responses;
|
|
uint16_t min_period_length;
|
|
uint16_t max_period_length;
|
|
} hci_register_{
|
|
.one_shot_inquiry_active = false,
|
|
.periodic_inquiry_active = false,
|
|
.inquiry_response_transmit_power_level = kInitialInquiryResponseTransmitPowerLevel,
|
|
.inquiry_scan_interval = kInitialInquiryScanInterval,
|
|
.inquiry_scan_window = kInitialInquiryScanWindow,
|
|
.inquiry_scan_type = hci::InquiryScanType::STANDARD,
|
|
.inquiry_mode = hci::InquiryMode::STANDARD,
|
|
.inquiry_length = 0,
|
|
.num_responses = 0,
|
|
.min_period_length = 0,
|
|
.max_period_length = 0,
|
|
};
|
|
|
|
hci::PacketView<hci::kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
|
|
auto bytes = std::make_shared<std::vector<uint8_t>>();
|
|
hci::BitInserter i(*bytes);
|
|
bytes->reserve(packet->size());
|
|
packet->Serialize(i);
|
|
return packet::PacketView<packet::kLittleEndian>(bytes);
|
|
}
|
|
|
|
class TestHciLayer : public hci::HciLayer {
|
|
public:
|
|
void EnqueueCommand(
|
|
std::unique_ptr<hci::CommandBuilder> command,
|
|
common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) override {
|
|
GetHandler()->Post(common::BindOnce(
|
|
&TestHciLayer::HandleCommand, common::Unretained(this), std::move(command), std::move(on_complete)));
|
|
}
|
|
|
|
void EnqueueCommand(
|
|
std::unique_ptr<hci::CommandBuilder> command,
|
|
common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) override {
|
|
GetHandler()->Post(common::BindOnce(
|
|
&TestHciLayer::HandleStatus, common::Unretained(this), std::move(command), std::move(on_status)));
|
|
}
|
|
|
|
void HandleCommand(
|
|
std::unique_ptr<hci::CommandBuilder> command_builder,
|
|
common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) {
|
|
hci::CommandView command = hci::CommandView::Create(GetPacketView(std::move(command_builder)));
|
|
ASSERT_TRUE(command.IsValid());
|
|
|
|
std::unique_ptr<packet::BasePacketBuilder> event_builder;
|
|
switch (command.GetOpCode()) {
|
|
case hci::OpCode::INQUIRY_CANCEL:
|
|
event_builder =
|
|
hci::InquiryCancelCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
hci_register_.one_shot_inquiry_active = false;
|
|
break;
|
|
|
|
case hci::OpCode::PERIODIC_INQUIRY_MODE: {
|
|
auto inquiry = hci::PeriodicInquiryModeView::Create(hci::DiscoveryCommandView::Create(command));
|
|
ASSERT_TRUE(inquiry.IsValid());
|
|
event_builder =
|
|
hci::PeriodicInquiryModeCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
hci_register_.periodic_inquiry_active = true;
|
|
hci_register_.inquiry_length = inquiry.GetInquiryLength();
|
|
hci_register_.num_responses = inquiry.GetNumResponses();
|
|
hci_register_.max_period_length = inquiry.GetMaxPeriodLength();
|
|
hci_register_.min_period_length = inquiry.GetMinPeriodLength();
|
|
} break;
|
|
|
|
case hci::OpCode::EXIT_PERIODIC_INQUIRY_MODE:
|
|
event_builder =
|
|
hci::ExitPeriodicInquiryModeCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
hci_register_.periodic_inquiry_active = false;
|
|
break;
|
|
|
|
case hci::OpCode::WRITE_INQUIRY_MODE:
|
|
event_builder =
|
|
hci::WriteInquiryModeCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
{
|
|
auto view = hci::WriteInquiryModeView::Create(hci::DiscoveryCommandView::Create(command));
|
|
ASSERT_TRUE(view.IsValid());
|
|
hci_register_.inquiry_mode = view.GetInquiryMode();
|
|
}
|
|
break;
|
|
|
|
case hci::OpCode::READ_INQUIRY_MODE:
|
|
event_builder = hci::ReadInquiryModeCompleteBuilder::Create(
|
|
kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS, hci_register_.inquiry_mode);
|
|
break;
|
|
|
|
case hci::OpCode::WRITE_INQUIRY_SCAN_ACTIVITY:
|
|
event_builder =
|
|
hci::WriteInquiryScanActivityCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
{
|
|
auto view = hci::WriteInquiryScanActivityView::Create(hci::DiscoveryCommandView::Create(command));
|
|
ASSERT_TRUE(view.IsValid());
|
|
hci_register_.inquiry_scan_interval = view.GetInquiryScanInterval();
|
|
hci_register_.inquiry_scan_window = view.GetInquiryScanWindow();
|
|
}
|
|
break;
|
|
|
|
case hci::OpCode::READ_INQUIRY_SCAN_ACTIVITY:
|
|
event_builder = hci::ReadInquiryScanActivityCompleteBuilder::Create(
|
|
kNumberPacketsReadyToReceive,
|
|
hci::ErrorCode::SUCCESS,
|
|
hci_register_.inquiry_scan_interval,
|
|
hci_register_.inquiry_scan_window);
|
|
break;
|
|
|
|
case hci::OpCode::WRITE_INQUIRY_SCAN_TYPE:
|
|
event_builder =
|
|
hci::WriteInquiryScanTypeCompleteBuilder::Create(kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS);
|
|
{
|
|
auto view = hci::WriteInquiryScanTypeView::Create(hci::DiscoveryCommandView::Create(command));
|
|
ASSERT_TRUE(view.IsValid());
|
|
hci_register_.inquiry_scan_type = view.GetInquiryScanType();
|
|
}
|
|
break;
|
|
|
|
case hci::OpCode::READ_INQUIRY_SCAN_TYPE:
|
|
event_builder = hci::ReadInquiryScanTypeCompleteBuilder::Create(
|
|
kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS, hci_register_.inquiry_scan_type);
|
|
break;
|
|
|
|
case hci::OpCode::READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL:
|
|
event_builder = hci::ReadInquiryResponseTransmitPowerLevelCompleteBuilder::Create(
|
|
kNumberPacketsReadyToReceive, hci::ErrorCode::SUCCESS, hci_register_.inquiry_response_transmit_power_level);
|
|
break;
|
|
|
|
default:
|
|
LOG_INFO("Dropping unhandled command:%s", hci::OpCodeText(command.GetOpCode()).c_str());
|
|
return;
|
|
}
|
|
hci::EventView event = hci::EventView::Create(GetPacketView(std::move(event_builder)));
|
|
ASSERT_TRUE(event.IsValid());
|
|
hci::CommandCompleteView command_complete = hci::CommandCompleteView::Create(event);
|
|
ASSERT_TRUE(command_complete.IsValid());
|
|
on_complete.Invoke(std::move(command_complete));
|
|
|
|
if (promise_sync_complete_ != nullptr) {
|
|
promise_sync_complete_->set_value(command.GetOpCode());
|
|
}
|
|
}
|
|
|
|
void HandleStatus(
|
|
std::unique_ptr<hci::CommandBuilder> command_builder,
|
|
common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) {
|
|
hci::CommandView command = hci::CommandView::Create(GetPacketView(std::move(command_builder)));
|
|
ASSERT_TRUE(command.IsValid());
|
|
|
|
std::unique_ptr<packet::BasePacketBuilder> event_builder;
|
|
switch (command.GetOpCode()) {
|
|
case hci::OpCode::INQUIRY: {
|
|
auto inquiry = hci::InquiryView::Create(hci::DiscoveryCommandView::Create(command));
|
|
ASSERT_TRUE(inquiry.IsValid());
|
|
event_builder = hci::InquiryStatusBuilder::Create(hci::ErrorCode::SUCCESS, kNumberPacketsReadyToReceive);
|
|
hci_register_.one_shot_inquiry_active = true;
|
|
hci_register_.num_responses = inquiry.GetNumResponses();
|
|
hci_register_.inquiry_length = inquiry.GetInquiryLength();
|
|
} break;
|
|
default:
|
|
LOG_INFO("Dropping unhandled status expecting command:%s", hci::OpCodeText(command.GetOpCode()).c_str());
|
|
return;
|
|
}
|
|
hci::EventView event = hci::EventView::Create(GetPacketView(std::move(event_builder)));
|
|
ASSERT_TRUE(event.IsValid());
|
|
hci::CommandStatusView command_status = hci::CommandStatusView::Create(event);
|
|
ASSERT_TRUE(command_status.IsValid());
|
|
on_status.Invoke(std::move(command_status));
|
|
|
|
if (promise_sync_complete_ != nullptr) {
|
|
promise_sync_complete_->set_value(command.GetOpCode());
|
|
}
|
|
}
|
|
|
|
void RegisterEventHandler(
|
|
hci::EventCode event_code, common::ContextualCallback<void(hci::EventView)> event_handler) override {
|
|
switch (event_code) {
|
|
case hci::EventCode::INQUIRY_RESULT:
|
|
inquiry_result_callback_ = event_handler;
|
|
break;
|
|
case hci::EventCode::INQUIRY_RESULT_WITH_RSSI:
|
|
inquiry_result_with_rssi_callback_ = event_handler;
|
|
break;
|
|
case hci::EventCode::EXTENDED_INQUIRY_RESULT:
|
|
extended_inquiry_result_callback_ = event_handler;
|
|
break;
|
|
case hci::EventCode::INQUIRY_COMPLETE:
|
|
inquiry_complete_callback_ = event_handler;
|
|
break;
|
|
default:
|
|
ASSERT_TRUE(false) << "Unexpected event handler being registered";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void UnregisterEventHandler(hci::EventCode event_code) override {
|
|
if (hci_register_.one_shot_inquiry_active || hci_register_.periodic_inquiry_active) {
|
|
LOG_ERROR("Event handlers may not be unregistered until inquiry is stopped");
|
|
return;
|
|
}
|
|
|
|
switch (event_code) {
|
|
case hci::EventCode::INQUIRY_RESULT:
|
|
inquiry_result_callback_ = {};
|
|
break;
|
|
case hci::EventCode::INQUIRY_RESULT_WITH_RSSI:
|
|
inquiry_result_with_rssi_callback_ = {};
|
|
break;
|
|
case hci::EventCode::EXTENDED_INQUIRY_RESULT:
|
|
extended_inquiry_result_callback_ = {};
|
|
break;
|
|
case hci::EventCode::INQUIRY_COMPLETE:
|
|
inquiry_complete_callback_ = {};
|
|
break;
|
|
default:
|
|
ASSERT_TRUE(false) << "Unexpected event handler being unregistered";
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Synchronize(std::function<void()> func, hci::OpCode op_code) {
|
|
ASSERT_EQ(promise_sync_complete_, nullptr);
|
|
promise_sync_complete_ = new std::promise<hci::OpCode>();
|
|
auto future = promise_sync_complete_->get_future();
|
|
func();
|
|
auto result = future.wait_for(std::chrono::milliseconds(100));
|
|
ASSERT_EQ(std::future_status::ready, result);
|
|
ASSERT_EQ(op_code, future.get());
|
|
delete promise_sync_complete_;
|
|
promise_sync_complete_ = nullptr;
|
|
}
|
|
|
|
void InjectInquiryResult(std::unique_ptr<hci::InquiryResultBuilder> result) {
|
|
hci::EventView view = hci::EventView::Create(GetPacketView(std::move(result)));
|
|
ASSERT_TRUE(view.IsValid());
|
|
inquiry_result_callback_.Invoke(std::move(view));
|
|
}
|
|
|
|
void ListDependencies(ModuleList* list) override {}
|
|
void Start() override {}
|
|
void Stop() override {}
|
|
|
|
private:
|
|
std::promise<hci::OpCode>* promise_sync_complete_{nullptr};
|
|
|
|
common::ContextualCallback<void(hci::EventView)> inquiry_result_callback_;
|
|
common::ContextualCallback<void(hci::EventView)> inquiry_result_with_rssi_callback_;
|
|
common::ContextualCallback<void(hci::EventView)> extended_inquiry_result_callback_;
|
|
common::ContextualCallback<void(hci::EventView)> inquiry_complete_callback_;
|
|
};
|
|
|
|
class InquiryTest : public ::testing::Test {
|
|
public:
|
|
void Result(hci::InquiryResultView view) {
|
|
ASSERT_TRUE(view.size() >= sizeof(uint16_t));
|
|
promise_result_complete_->set_value(true);
|
|
}
|
|
|
|
void WaitForInquiryResult(std::function<void()> func) {
|
|
ASSERT_EQ(promise_result_complete_, nullptr);
|
|
promise_result_complete_ = new std::promise<bool>();
|
|
auto future = promise_result_complete_->get_future();
|
|
func();
|
|
future.wait();
|
|
delete promise_result_complete_;
|
|
promise_result_complete_ = nullptr;
|
|
}
|
|
|
|
void ResultWithRssi(hci::InquiryResultWithRssiView view) {
|
|
ASSERT_TRUE(view.size() >= sizeof(uint16_t));
|
|
}
|
|
|
|
void ExtendedResult(hci::ExtendedInquiryResultView view) {
|
|
ASSERT_TRUE(view.size() >= sizeof(uint16_t));
|
|
}
|
|
|
|
void Complete(hci::ErrorCode status) {}
|
|
|
|
protected:
|
|
void SetUp() override {
|
|
test_hci_layer_ = new TestHciLayer;
|
|
fake_registry_.InjectTestModule(&hci::HciLayer::Factory, test_hci_layer_);
|
|
client_handler_ = fake_registry_.GetTestModuleHandler(&hci::HciLayer::Factory);
|
|
fake_registry_.Start<InquiryModule>(&thread_);
|
|
|
|
inquiry_module_ = static_cast<InquiryModule*>(fake_registry_.GetModuleUnderTest(&InquiryModule::Factory));
|
|
|
|
InquiryCallbacks inquiry_callbacks;
|
|
inquiry_callbacks.result = std::bind(&InquiryTest::Result, this, std::placeholders::_1);
|
|
inquiry_callbacks.result_with_rssi = std::bind(&InquiryTest::ResultWithRssi, this, std::placeholders::_1);
|
|
inquiry_callbacks.extended_result = std::bind(&InquiryTest::ExtendedResult, this, std::placeholders::_1);
|
|
inquiry_callbacks.complete = std::bind(&InquiryTest::Complete, this, std::placeholders::_1);
|
|
inquiry_module_->RegisterCallbacks(inquiry_callbacks);
|
|
}
|
|
|
|
void TearDown() override {
|
|
inquiry_module_->UnregisterCallbacks();
|
|
fake_registry_.StopAll();
|
|
}
|
|
|
|
TestModuleRegistry fake_registry_;
|
|
TestHciLayer* test_hci_layer_ = nullptr;
|
|
os::Thread& thread_ = fake_registry_.GetTestThread();
|
|
InquiryModule* inquiry_module_ = nullptr;
|
|
os::Handler* client_handler_ = nullptr;
|
|
|
|
std::promise<bool>* promise_result_complete_{nullptr};
|
|
};
|
|
|
|
TEST_F(InquiryTest, Module) {}
|
|
|
|
TEST_F(InquiryTest, SetInquiryModes) {
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->SetInquiryWithRssiResultMode(); }, hci::OpCode::WRITE_INQUIRY_MODE);
|
|
ASSERT_EQ(hci_register_.inquiry_mode, hci::InquiryMode::RSSI);
|
|
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->SetExtendedInquiryResultMode(); }, hci::OpCode::WRITE_INQUIRY_MODE);
|
|
ASSERT_EQ(hci_register_.inquiry_mode, hci::InquiryMode::RSSI_OR_EXTENDED);
|
|
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->SetStandardInquiryResultMode(); }, hci::OpCode::WRITE_INQUIRY_MODE);
|
|
ASSERT_EQ(hci_register_.inquiry_mode, hci::InquiryMode::STANDARD);
|
|
}
|
|
|
|
TEST_F(InquiryTest, SetScanType) {
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->SetInterlacedScan(); }, hci::OpCode::WRITE_INQUIRY_SCAN_TYPE);
|
|
ASSERT_EQ(hci_register_.inquiry_scan_type, hci::InquiryScanType::INTERLACED);
|
|
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->SetStandardScan(); }, hci::OpCode::WRITE_INQUIRY_SCAN_TYPE);
|
|
ASSERT_EQ(hci_register_.inquiry_scan_type, hci::InquiryScanType::STANDARD);
|
|
}
|
|
|
|
TEST_F(InquiryTest, ScanActivity) {
|
|
ScanParameters params{
|
|
.interval = 0x1234,
|
|
.window = 0x5678,
|
|
};
|
|
|
|
test_hci_layer_->Synchronize(
|
|
[this, params] { inquiry_module_->SetScanActivity(params); }, hci::OpCode::WRITE_INQUIRY_SCAN_ACTIVITY);
|
|
ASSERT_EQ(params.interval, hci_register_.inquiry_scan_interval);
|
|
ASSERT_EQ(params.window, hci_register_.inquiry_scan_window);
|
|
}
|
|
|
|
TEST_F(InquiryTest, OneShotGeneralInquiry) {
|
|
uint8_t inquiry_length = 128;
|
|
uint8_t num_responses = 100;
|
|
test_hci_layer_->Synchronize(
|
|
[this, inquiry_length, num_responses] { inquiry_module_->StartGeneralInquiry(inquiry_length, num_responses); },
|
|
hci::OpCode::INQUIRY);
|
|
ASSERT_EQ(inquiry_length, hci_register_.inquiry_length);
|
|
ASSERT_EQ(num_responses, hci_register_.num_responses);
|
|
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->StopInquiry(); }, hci::OpCode::INQUIRY_CANCEL);
|
|
}
|
|
|
|
TEST_F(InquiryTest, OneShotLimitedInquiry) {
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->StartLimitedInquiry(128, 100); }, hci::OpCode::INQUIRY);
|
|
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->StopInquiry(); }, hci::OpCode::INQUIRY_CANCEL);
|
|
}
|
|
|
|
TEST_F(InquiryTest, GeneralPeriodicInquiry) {
|
|
uint8_t inquiry_length = 128;
|
|
uint8_t num_responses = 100;
|
|
uint16_t max_delay = 1100;
|
|
uint16_t min_delay = 200;
|
|
test_hci_layer_->Synchronize(
|
|
[this, inquiry_length, num_responses, max_delay, min_delay] {
|
|
inquiry_module_->StartGeneralPeriodicInquiry(inquiry_length, num_responses, max_delay, min_delay);
|
|
},
|
|
hci::OpCode::PERIODIC_INQUIRY_MODE);
|
|
ASSERT_EQ(inquiry_length, hci_register_.inquiry_length);
|
|
ASSERT_EQ(num_responses, hci_register_.num_responses);
|
|
ASSERT_EQ(max_delay, hci_register_.max_period_length);
|
|
ASSERT_EQ(min_delay, hci_register_.min_period_length);
|
|
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->StopPeriodicInquiry(); }, hci::OpCode::EXIT_PERIODIC_INQUIRY_MODE);
|
|
}
|
|
|
|
TEST_F(InquiryTest, LimitedPeriodicInquiry) {
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->StartLimitedPeriodicInquiry(128, 100, 1100, 200); },
|
|
hci::OpCode::PERIODIC_INQUIRY_MODE);
|
|
|
|
test_hci_layer_->Synchronize(
|
|
[this] { inquiry_module_->StopPeriodicInquiry(); }, hci::OpCode::EXIT_PERIODIC_INQUIRY_MODE);
|
|
}
|
|
|
|
TEST_F(InquiryTest, InjectInquiryResult) {
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->StartGeneralInquiry(128, 100); }, hci::OpCode::INQUIRY);
|
|
|
|
WaitForInquiryResult([this] {
|
|
const std::vector<hci::InquiryResult> inquiry_results;
|
|
auto packet = hci::InquiryResultBuilder::Create(inquiry_results);
|
|
test_hci_layer_->InjectInquiryResult(std::move(packet));
|
|
});
|
|
test_hci_layer_->Synchronize([this] { inquiry_module_->StopInquiry(); }, hci::OpCode::INQUIRY_CANCEL);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace neighbor
|
|
} // namespace bluetooth
|