blob: e05bb30ad3d178d72ebd371060ae312f273931b1 [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 "bredr_discovery_manager.h"
#include <lib/async/default.h>
#include "garnet/drivers/bluetooth/lib/gap/remote_device_cache.h"
#include "garnet/drivers/bluetooth/lib/hci/transport.h"
#include "garnet/drivers/bluetooth/lib/hci/util.h"
#include "lib/fxl/functional/auto_call.h"
namespace btlib {
namespace gap {
namespace {
template <typename EventParamType, typename ResultType>
std::unordered_set<RemoteDevice*> ProcessInquiryResult(
RemoteDeviceCache* cache, const hci::EventPacket& event) {
std::unordered_set<RemoteDevice*> updated;
FXL_VLOG(2) << "gap (BR/EDR): InquiryResult received";
size_t result_size = event.view().payload_size() - sizeof(EventParamType);
if ((result_size % sizeof(ResultType)) != 0) {
FXL_LOG(INFO) << "gap (BR/EDR): ignoring malformed result ("
<< event.view().payload_size() << " bytes)";
return updated;
}
const auto& result = event.view().payload<EventParamType>();
for (int i = 0; i < result.num_responses; i++) {
common::DeviceAddress addr(common::DeviceAddress::Type::kBREDR,
result.responses[i].bd_addr);
RemoteDevice* device = cache->FindDeviceByAddress(addr);
if (!device) {
device = cache->NewDevice(addr, true);
}
FXL_DCHECK(device);
device->SetInquiryData(result.responses[i]);
updated.insert(device);
}
return updated;
}
} // namespace
BrEdrDiscoverySession::BrEdrDiscoverySession(
fxl::WeakPtr<BrEdrDiscoveryManager> manager)
: manager_(manager) {}
BrEdrDiscoverySession::~BrEdrDiscoverySession() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
manager_->RemoveDiscoverySession(this);
}
void BrEdrDiscoverySession::NotifyDiscoveryResult(
const RemoteDevice& device) const {
if (device_found_callback_) {
device_found_callback_(device);
}
}
void BrEdrDiscoverySession::NotifyError() const {
if (error_callback_) {
error_callback_();
}
}
BrEdrDiscoverableSession::BrEdrDiscoverableSession(
fxl::WeakPtr<BrEdrDiscoveryManager> manager)
: manager_(manager) {}
BrEdrDiscoverableSession::~BrEdrDiscoverableSession() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
manager_->RemoveDiscoverableSession(this);
}
BrEdrDiscoveryManager::BrEdrDiscoveryManager(fxl::RefPtr<hci::Transport> hci,
hci::InquiryMode mode,
RemoteDeviceCache* device_cache)
: hci_(hci),
dispatcher_(async_get_default_dispatcher()),
cache_(device_cache),
result_handler_id_(0u),
desired_inquiry_mode_(mode),
current_inquiry_mode_(hci::InquiryMode::kStandard),
weak_ptr_factory_(this) {
FXL_DCHECK(cache_);
FXL_DCHECK(hci_);
FXL_DCHECK(dispatcher_);
result_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kInquiryResultEventCode,
fit::bind_member(this, &BrEdrDiscoveryManager::InquiryResult),
dispatcher_);
FXL_DCHECK(result_handler_id_);
rssi_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kInquiryResultWithRSSIEventCode,
fbl::BindMember(this, &BrEdrDiscoveryManager::InquiryResult),
dispatcher_);
FXL_DCHECK(rssi_handler_id_);
// TODO(NET-729): add event handlers for the other inquiry modes
eir_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kExtendedInquiryResultEventCode,
fbl::BindMember(this, &BrEdrDiscoveryManager::ExtendedInquiryResult),
dispatcher_);
FXL_DCHECK(eir_handler_id_);
}
BrEdrDiscoveryManager::~BrEdrDiscoveryManager() {
hci_->command_channel()->RemoveEventHandler(eir_handler_id_);
hci_->command_channel()->RemoveEventHandler(rssi_handler_id_);
hci_->command_channel()->RemoveEventHandler(result_handler_id_);
InvalidateDiscoverySessions();
}
void BrEdrDiscoveryManager::RequestDiscovery(DiscoveryCallback callback) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(callback);
FXL_LOG(INFO) << "gap (BR/EDR): RequestDiscovery";
// If we're already waiting on a callback, then scanning is already starting.
// Queue this to create a session when the scanning starts.
if (!pending_discovery_.empty()) {
FXL_VLOG(1) << "gap (BR/EDR): discovery starting, add to pending";
pending_discovery_.push(std::move(callback));
return;
}
// If we're already scanning, just add a session.
if (!discovering_.empty()) {
FXL_VLOG(1) << "gap (BR/EDR): add to active sessions";
auto session = AddDiscoverySession();
callback(hci::Status(), std::move(session));
return;
}
pending_discovery_.push(std::move(callback));
MaybeStartInquiry();
}
// Starts the inquiry procedure if any sessions exist or are waiting to start.
void BrEdrDiscoveryManager::MaybeStartInquiry() {
if (pending_discovery_.empty() && discovering_.empty()) {
FXL_VLOG(1) << "gap (BR/EDR): no sessions, not starting inquiry";
return;
}
FXL_VLOG(1) << "gap (BR/EDR): starting inqiury";
auto self = weak_ptr_factory_.GetWeakPtr();
if (desired_inquiry_mode_ != current_inquiry_mode_) {
auto packet = hci::CommandPacket::New(
hci::kWriteInquiryMode, sizeof(hci::WriteInquiryModeCommandParams));
packet->mutable_view()
->mutable_payload<hci::WriteInquiryModeCommandParams>()
->inquiry_mode = desired_inquiry_mode_;
hci_->command_channel()->SendCommand(
std::move(packet), dispatcher_,
[self, mode = desired_inquiry_mode_](auto, const auto& event) {
if (!self) {
return;
}
if (!BTEV_TEST_LOG(event, INFO,
"gap (BR/EDR): write inquiry mode failed")) {
self->current_inquiry_mode_ = mode;
}
});
}
auto inquiry =
hci::CommandPacket::New(hci::kInquiry, sizeof(hci::InquiryCommandParams));
auto params =
inquiry->mutable_view()->mutable_payload<hci::InquiryCommandParams>();
params->lap = hci::kGIAC;
params->inquiry_length = hci::kInquiryLengthMax;
params->num_responses = 0;
hci_->command_channel()->SendCommand(
std::move(inquiry), dispatcher_,
[self](auto, const auto& event) {
if (!self) {
return;
}
auto status = event.ToStatus();
if (BT_TEST_WARN(status, "gap (BR/EDR): inquiry failure")) {
// Failure of some kind, signal error to the sessions.
self->InvalidateDiscoverySessions();
// Fallthrough for callback to pending sessions.
}
// Resolve the request if the controller sent back a Command Complete or
// Status event.
// TODO(NET-770): Make it impossible for Command Complete to happen here
// and remove handling for it.
if (event.event_code() == hci::kCommandStatusEventCode ||
event.event_code() == hci::kCommandCompleteEventCode) {
// Inquiry started, make sessions for our waiting callbacks.
while (!self->pending_discovery_.empty()) {
auto callback = std::move(self->pending_discovery_.front());
self->pending_discovery_.pop();
callback(status, (status ? self->AddDiscoverySession() : nullptr));
}
return;
}
FXL_DCHECK(event.event_code() == hci::kInquiryCompleteEventCode);
if (BT_TEST_VLOG(status, 2, "gap: inquiry complete failure")) {
return;
}
FXL_VLOG(1) << "gap (BR/EDR): inquiry complete, restart";
// We've stopped scanning because we timed out.
self->MaybeStartInquiry();
},
hci::kInquiryCompleteEventCode);
}
// Stops the inquiry procedure.
void BrEdrDiscoveryManager::StopInquiry() {
FXL_DCHECK(result_handler_id_);
FXL_VLOG(2) << "gap (BR/EDR): cancelling inquiry";
auto inq_cancel = hci::CommandPacket::New(hci::kInquiryCancel);
hci_->command_channel()->SendCommand(
std::move(inq_cancel), dispatcher_, [](long, const auto& event) {
BTEV_TEST_WARN(event, "gap (BR/EDR): InquiryCancel failed");
});
}
void BrEdrDiscoveryManager::InquiryResult(const hci::EventPacket& event) {
std::unordered_set<RemoteDevice*> devices;
if (event.event_code() == hci::kInquiryResultEventCode) {
devices =
ProcessInquiryResult<hci::InquiryResultEventParams, hci::InquiryResult>(
cache_, event);
} else if (event.event_code() == hci::kInquiryResultWithRSSIEventCode) {
devices = ProcessInquiryResult<hci::InquiryResultWithRSSIEventParams,
hci::InquiryResultRSSI>(cache_, event);
} else {
FXL_NOTREACHED() << "Unsupported Inquiry result type";
return;
}
for (RemoteDevice* device : devices) {
if (!device->name()) {
RequestRemoteDeviceName(device->identifier());
}
for (const auto& session : discovering_) {
session->NotifyDiscoveryResult(*device);
}
}
}
void BrEdrDiscoveryManager::ExtendedInquiryResult(
const hci::EventPacket& event) {
FXL_DCHECK(event.event_code() == hci::kExtendedInquiryResultEventCode);
FXL_VLOG(2) << "gap (BR/EDR): ExtendedInquiryResult received";
if (event.view().payload_size() !=
sizeof(hci::ExtendedInquiryResultEventParams)) {
FXL_LOG(INFO) << "gap (BR/EDR): ignoring malformed result ("
<< event.view().payload_size() << " bytes)";
return;
}
const auto& result =
event.view().payload<hci::ExtendedInquiryResultEventParams>();
common::DeviceAddress addr(common::DeviceAddress::Type::kBREDR,
result.bd_addr);
RemoteDevice* device = cache_->FindDeviceByAddress(addr);
if (!device) {
device = cache_->NewDevice(addr, true);
}
FXL_DCHECK(device);
device->SetInquiryData(result);
if (!device->name()) {
RequestRemoteDeviceName(device->identifier());
}
for (const auto& session : discovering_) {
session->NotifyDiscoveryResult(*device);
}
}
void BrEdrDiscoveryManager::RequestRemoteDeviceName(const std::string& id) {
RemoteDevice* device = cache_->FindDeviceById(id);
if (!device) {
FXL_LOG(WARNING) << "gap (BR/EDR): cannot request name, unknown id: " << id;
return;
}
auto packet = hci::CommandPacket::New(
hci::kRemoteNameRequest, sizeof(hci::RemoteNameRequestCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto params = packet->mutable_view()
->mutable_payload<hci::RemoteNameRequestCommandParams>();
FXL_DCHECK(device->page_scan_repetition_mode().HasValue());
params->bd_addr = device->address().value();
params->page_scan_repetition_mode = *(device->page_scan_repetition_mode());
if (device->clock_offset()) {
params->clock_offset = *(device->clock_offset());
}
auto cb = [id, self = weak_ptr_factory_.GetWeakPtr()](auto,
const auto& event) {
if (!self ||
BTEV_TEST_LOG(event, INFO, "gap (BR/EDR): RemoteNameRequest failed")) {
return;
}
if (event.event_code() == hci::kCommandStatusEventCode) {
return;
}
FXL_DCHECK(event.event_code() == hci::kRemoteNameRequestCompleteEventCode);
const auto& params =
event.view()
.template payload<hci::RemoteNameRequestCompleteEventParams>();
for (size_t len = 0; len <= hci::kMaxNameLength; len++) {
if (params.remote_name[len] == 0 || len == hci::kMaxNameLength) {
RemoteDevice* device = self->cache_->FindDeviceById(id);
if (device) {
device->SetName(
std::string(params.remote_name, params.remote_name + len));
}
return;
}
}
};
hci_->command_channel()->SendCommand(
std::move(packet), dispatcher_, std::move(cb),
hci::kRemoteNameRequestCompleteEventCode);
}
void BrEdrDiscoveryManager::RequestDiscoverable(DiscoverableCallback callback) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(callback);
FXL_LOG(INFO) << "gap (BR/EDR): RequestDiscoverable";
auto self = weak_ptr_factory_.GetWeakPtr();
auto status_cb = [self, cb = callback.share()](const auto& status) {
cb(status, (status ? self->AddDiscoverableSession() : nullptr));
};
if (!pending_discoverable_.empty()) {
FXL_VLOG(1) << "gap (BR/EDR): discovering starting, add to pending";
pending_discoverable_.push(std::move(status_cb));
return;
}
// If we're already discoverable, just add a session.
if (!discoverable_.empty()) {
FXL_VLOG(1) << "gap (BR/EDR): add to active discoverable";
auto session = AddDiscoverableSession();
callback(hci::Status(), std::move(session));
return;
}
pending_discoverable_.push(std::move(status_cb));
SetInquiryScan();
}
void BrEdrDiscoveryManager::SetInquiryScan() {
bool enable = !discoverable_.empty() || !pending_discoverable_.empty();
FXL_VLOG(2) << "gap (BR/EDR): " << (enable ? "enabling" : "disabling")
<< " inquiry scan ";
auto self = weak_ptr_factory_.GetWeakPtr();
auto scan_enable_cb = [self](auto, const hci::EventPacket& event) {
if (!self) {
return;
}
auto status = event.ToStatus();
auto resolve_pending = fxl::MakeAutoCall([self, &status]() {
while (!self->pending_discoverable_.empty()) {
auto cb = std::move(self->pending_discoverable_.front());
self->pending_discoverable_.pop();
cb(status);
}
});
if (BT_TEST_WARN(status, "gap (BR/EDR): Read Scan Enable failed: ")) {
return;
}
bool enable =
!self->discoverable_.empty() || !self->pending_discoverable_.empty();
auto params = event.return_params<hci::ReadScanEnableReturnParams>();
uint8_t scan_type = params->scan_enable;
bool enabled =
scan_type & static_cast<uint8_t>(hci::ScanEnableBit::kInquiry);
if (enable == enabled) {
FXL_LOG(INFO) << "gap (BR/EDR): inquiry scan already "
<< (enable ? "enabled" : "disabled");
return;
}
if (enable) {
scan_type |= static_cast<uint8_t>(hci::ScanEnableBit::kInquiry);
} else {
scan_type &= ~static_cast<uint8_t>(hci::ScanEnableBit::kInquiry);
}
auto write_enable = hci::CommandPacket::New(
hci::kWriteScanEnable, sizeof(hci::WriteScanEnableCommandParams));
write_enable->mutable_view()
->mutable_payload<hci::WriteScanEnableCommandParams>()
->scan_enable = scan_type;
resolve_pending.cancel();
self->hci_->command_channel()->SendCommand(
std::move(write_enable), self->dispatcher_,
[self, enable](auto, const hci::EventPacket& event) {
if (!self) {
return;
}
BTEV_TEST_WARN(event, "gap (BR/EDR): Write Scan Enable failed");
while (!self->pending_discoverable_.empty()) {
auto cb = std::move(self->pending_discoverable_.front());
self->pending_discoverable_.pop();
cb(event.ToStatus());
}
});
};
auto read_enable = hci::CommandPacket::New(hci::kReadScanEnable);
hci_->command_channel()->SendCommand(std::move(read_enable), dispatcher_,
std::move(scan_enable_cb));
}
std::unique_ptr<BrEdrDiscoverySession>
BrEdrDiscoveryManager::AddDiscoverySession() {
FXL_VLOG(2) << "gap (BR/EDR): adding discovery session";
// Cannot use make_unique here since BrEdrDiscoverySession has a private
// constructor.
std::unique_ptr<BrEdrDiscoverySession> session(
new BrEdrDiscoverySession(weak_ptr_factory_.GetWeakPtr()));
FXL_DCHECK(discovering_.find(session.get()) == discovering_.end());
discovering_.insert(session.get());
return session;
}
void BrEdrDiscoveryManager::RemoveDiscoverySession(
BrEdrDiscoverySession* session) {
FXL_VLOG(2) << "gap (BR/EDR): removing discovery session";
discovering_.erase(session);
// TODO(jamuraa): When NET-619 is finished, cancel the running inquiry
// With StopInquiry();
}
std::unique_ptr<BrEdrDiscoverableSession>
BrEdrDiscoveryManager::AddDiscoverableSession() {
FXL_VLOG(2) << "gap (BR/EDR): adding discoverable session";
// Cannot use make_unique here since BrEdrDiscoverableSession has a private
// constructor.
std::unique_ptr<BrEdrDiscoverableSession> session(
new BrEdrDiscoverableSession(weak_ptr_factory_.GetWeakPtr()));
FXL_DCHECK(discoverable_.find(session.get()) == discoverable_.end());
discoverable_.insert(session.get());
return session;
}
void BrEdrDiscoveryManager::RemoveDiscoverableSession(
BrEdrDiscoverableSession* session) {
FXL_VLOG(2) << "gap (BR/EDR): removing discoverable session";
discoverable_.erase(session);
if (discoverable_.empty()) {
SetInquiryScan();
}
}
void BrEdrDiscoveryManager::InvalidateDiscoverySessions() {
for (auto session : discovering_) {
session->NotifyError();
}
discovering_.clear();
}
} // namespace gap
} // namespace btlib