blob: 392011daed5b4bd8096f78d32122b87a273b39ec [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 <lib/fit/defer.h>
#include <zircon/assert.h>
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/supplement_data.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/transport.h"
namespace bt::gap {
namespace {
template <typename EventParamType, typename ResultType>
std::unordered_set<Peer*> ProcessInquiryResult(PeerCache* cache, const hci::EventPacket& event) {
std::unordered_set<Peer*> updated;
bt_log(TRACE, "gap-bredr", "inquiry result received");
const size_t event_payload_size = event.view().payload_size();
ZX_ASSERT_MSG(event_payload_size >= sizeof(EventParamType), "undersized (%zu) inquiry event",
event_payload_size);
size_t result_size = event_payload_size - sizeof(EventParamType);
ZX_ASSERT_MSG(result_size % sizeof(ResultType) == 0, "wrong size result (%zu %% %zu != 0)",
result_size, sizeof(ResultType));
const auto params_data = event.view().payload_data();
const auto num_responses = params_data.ReadMember<&EventParamType::num_responses>();
for (int i = 0; i < num_responses; i++) {
const auto response = params_data.ReadMember<&EventParamType::responses>(i);
DeviceAddress addr(DeviceAddress::Type::kBREDR, response.bd_addr);
Peer* peer = cache->FindByAddress(addr);
if (!peer) {
peer = cache->NewPeer(addr, true);
}
ZX_ASSERT(peer);
peer->MutBrEdr().SetInquiryData(response);
updated.insert(peer);
}
return updated;
}
} // namespace
BrEdrDiscoverySession::BrEdrDiscoverySession(fxl::WeakPtr<BrEdrDiscoveryManager> manager)
: manager_(manager) {}
BrEdrDiscoverySession::~BrEdrDiscoverySession() {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
manager_->RemoveDiscoverySession(this);
}
void BrEdrDiscoverySession::NotifyDiscoveryResult(const Peer& peer) const {
if (peer_found_callback_) {
peer_found_callback_(peer);
}
}
void BrEdrDiscoverySession::NotifyError() const {
if (error_callback_) {
error_callback_();
}
}
BrEdrDiscoverableSession::BrEdrDiscoverableSession(fxl::WeakPtr<BrEdrDiscoveryManager> manager)
: manager_(manager) {}
BrEdrDiscoverableSession::~BrEdrDiscoverableSession() {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
manager_->RemoveDiscoverableSession(this);
}
BrEdrDiscoveryManager::BrEdrDiscoveryManager(fxl::WeakPtr<hci::Transport> hci,
hci::InquiryMode mode, PeerCache* peer_cache)
: hci_(std::move(hci)),
dispatcher_(async_get_default_dispatcher()),
cache_(peer_cache),
result_handler_id_(0u),
desired_inquiry_mode_(mode),
current_inquiry_mode_(hci::InquiryMode::kStandard),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(cache_);
ZX_DEBUG_ASSERT(hci_);
ZX_DEBUG_ASSERT(dispatcher_);
result_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kInquiryResultEventCode, fit::bind_member(this, &BrEdrDiscoveryManager::InquiryResult));
ZX_DEBUG_ASSERT(result_handler_id_);
rssi_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kInquiryResultWithRSSIEventCode,
fbl::BindMember(this, &BrEdrDiscoveryManager::InquiryResult));
ZX_DEBUG_ASSERT(rssi_handler_id_);
eir_handler_id_ = hci_->command_channel()->AddEventHandler(
hci::kExtendedInquiryResultEventCode,
fbl::BindMember(this, &BrEdrDiscoveryManager::ExtendedInquiryResult));
ZX_DEBUG_ASSERT(eir_handler_id_);
// Set the Inquiry Scan Settings
WriteInquiryScanSettings(kInquiryScanInterval, kInquiryScanWindow, true);
}
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) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(callback);
bt_log(INFO, "gap-bredr", "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()) {
bt_log(DEBUG, "gap-bredr", "discovery starting, add to pending");
pending_discovery_.push(std::move(callback));
return;
}
// If we're already scanning, just add a session.
if (!discovering_.empty() || !zombie_discovering_.empty()) {
bt_log(DEBUG, "gap-bredr", "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()) {
bt_log(DEBUG, "gap-bredr", "no sessions, not starting inquiry");
return;
}
bt_log(TRACE, "gap-bredr", "starting inquiry");
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_payload<hci::WriteInquiryModeCommandParams>()->inquiry_mode =
desired_inquiry_mode_;
hci_->command_channel()->SendCommand(
std::move(packet), [self, mode = desired_inquiry_mode_](auto, const auto& event) {
if (!self) {
return;
}
if (!hci_is_error(event, ERROR, "gap-bredr", "write inquiry mode failed")) {
self->current_inquiry_mode_ = mode;
}
});
}
auto inquiry = hci::CommandPacket::New(hci::kInquiry, sizeof(hci::InquiryCommandParams));
auto params = inquiry->mutable_payload<hci::InquiryCommandParams>();
params->lap = hci::kGIAC;
params->inquiry_length = kInquiryLengthDefault;
params->num_responses = 0;
hci_->command_channel()->SendExclusiveCommand(
std::move(inquiry),
[self](auto, const auto& event) {
if (!self) {
return;
}
auto status = event.ToStatus();
if (bt_is_error(status, WARN, "gap-bredr", "inquiry error")) {
// 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(fxbug.dev/1109): 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;
}
ZX_DEBUG_ASSERT(event.event_code() == hci::kInquiryCompleteEventCode);
self->zombie_discovering_.clear();
if (bt_is_error(status, TRACE, "gap", "inquiry complete error")) {
return;
}
// We've stopped scanning because we timed out.
bt_log(TRACE, "gap-bredr", "inquiry complete, restart");
self->MaybeStartInquiry();
},
hci::kInquiryCompleteEventCode, {hci::kRemoteNameRequest});
}
// Stops the inquiry procedure.
void BrEdrDiscoveryManager::StopInquiry() {
ZX_DEBUG_ASSERT(result_handler_id_);
bt_log(TRACE, "gap-bredr", "cancelling inquiry");
auto inq_cancel = hci::CommandPacket::New(hci::kInquiryCancel);
hci_->command_channel()->SendCommand(std::move(inq_cancel), [](long, const auto& event) {
// Warn if the command failed.
hci_is_error(event, WARN, "gap-bredr", "inquiry cancel failed");
});
}
hci::CommandChannel::EventCallbackResult BrEdrDiscoveryManager::InquiryResult(
const hci::EventPacket& event) {
std::unordered_set<Peer*> peers;
if (event.event_code() == hci::kInquiryResultEventCode) {
peers = ProcessInquiryResult<hci::InquiryResultEventParams, hci::InquiryResult>(cache_, event);
} else if (event.event_code() == hci::kInquiryResultWithRSSIEventCode) {
peers = ProcessInquiryResult<hci::InquiryResultWithRSSIEventParams, hci::InquiryResultRSSI>(
cache_, event);
} else {
bt_log(ERROR, "gap-bredr", "unsupported inquiry result type");
return hci::CommandChannel::EventCallbackResult::kContinue;
}
for (Peer* peer : peers) {
if (!peer->name()) {
RequestPeerName(peer->identifier());
}
for (const auto& session : discovering_) {
session->NotifyDiscoveryResult(*peer);
}
}
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrDiscoveryManager::ExtendedInquiryResult(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci::kExtendedInquiryResultEventCode);
bt_log(TRACE, "gap-bredr", "ExtendedInquiryResult received");
if (event.view().payload_size() != sizeof(hci::ExtendedInquiryResultEventParams)) {
bt_log(WARN, "gap-bredr", "ignoring malformed result (%zu bytes)", event.view().payload_size());
return hci::CommandChannel::EventCallbackResult::kContinue;
}
const auto& result = event.params<hci::ExtendedInquiryResultEventParams>();
DeviceAddress addr(DeviceAddress::Type::kBREDR, result.bd_addr);
Peer* peer = cache_->FindByAddress(addr);
if (!peer) {
peer = cache_->NewPeer(addr, true);
}
ZX_DEBUG_ASSERT(peer);
peer->MutBrEdr().SetInquiryData(result);
if (!peer->name()) {
RequestPeerName(peer->identifier());
}
for (const auto& session : discovering_) {
session->NotifyDiscoveryResult(*peer);
}
return hci::CommandChannel::EventCallbackResult::kContinue;
}
void BrEdrDiscoveryManager::UpdateEIRResponseData(std::string name, hci::StatusCallback callback) {
DataType name_type = DataType::kCompleteLocalName;
size_t name_size = name.size();
if (name.size() >= (hci::kExtendedInquiryResponseMaxNameBytes)) {
name_type = DataType::kShortenedLocalName;
name_size = hci::kExtendedInquiryResponseMaxNameBytes;
}
auto self = weak_ptr_factory_.GetWeakPtr();
auto eir_packet = hci::CommandPacket::New(hci::kWriteExtendedInquiryResponse,
sizeof(hci::WriteExtendedInquiryResponseParams));
eir_packet->mutable_payload<hci::WriteExtendedInquiryResponseParams>()->fec_required = 0x00;
auto eir_response_buf =
MutableBufferView(eir_packet->mutable_payload<hci::WriteExtendedInquiryResponseParams>()
->extended_inquiry_response,
hci::kExtendedInquiryResponseBytes);
eir_response_buf.Fill(0);
eir_response_buf[0] = name_size + 1;
eir_response_buf[1] = static_cast<uint8_t>(name_type);
eir_response_buf.mutable_view(2).Write(reinterpret_cast<const uint8_t*>(name.data()), name_size);
self->hci_->command_channel()->SendCommand(
std::move(eir_packet), [self, name = std::move(name), cb = std::move(callback)](
auto, const hci::EventPacket& event) mutable {
if (!hci_is_error(event, WARN, "gap", "write EIR failed")) {
self->local_name_ = std::move(name);
}
cb(event.ToStatus());
});
}
void BrEdrDiscoveryManager::UpdateLocalName(std::string name, hci::StatusCallback callback) {
size_t name_size = name.size();
if (name.size() >= hci::kMaxNameLength) {
name_size = hci::kMaxNameLength;
}
auto self = weak_ptr_factory_.GetWeakPtr();
auto write_name =
hci::CommandPacket::New(hci::kWriteLocalName, sizeof(hci::WriteLocalNameCommandParams));
auto name_buf =
MutableBufferView(write_name->mutable_payload<hci::WriteLocalNameCommandParams>()->local_name,
hci::kMaxNameLength);
name_buf.Fill(0);
name_buf.Write(reinterpret_cast<const uint8_t*>(name.data()), name_size);
hci_->command_channel()->SendCommand(
std::move(write_name), [self, name = std::move(name), cb = std::move(callback)](
auto, const hci::EventPacket& event) mutable {
if (hci_is_error(event, WARN, "gap", "set local name failed")) {
cb(event.ToStatus());
return;
}
// If the WriteLocalName command was successful, update the extended inquiry data.
self->UpdateEIRResponseData(std::move(name), std::move(cb));
});
}
void BrEdrDiscoveryManager::RequestPeerName(PeerId id) {
if (requesting_names_.count(id)) {
bt_log(TRACE, "gap-bredr", "already requesting name for %s", bt_str(id));
return;
}
Peer* peer = cache_->FindById(id);
if (!peer) {
bt_log(WARN, "gap-bredr", "cannot request name, unknown peer: %s", bt_str(id));
return;
}
auto packet =
hci::CommandPacket::New(hci::kRemoteNameRequest, sizeof(hci::RemoteNameRequestCommandParams));
packet->mutable_view()->mutable_payload_data().SetToZeros();
auto params = packet->mutable_payload<hci::RemoteNameRequestCommandParams>();
ZX_DEBUG_ASSERT(peer->bredr());
ZX_DEBUG_ASSERT(peer->bredr()->page_scan_repetition_mode());
params->bd_addr = peer->address().value();
params->page_scan_repetition_mode = *(peer->bredr()->page_scan_repetition_mode());
if (peer->bredr()->clock_offset()) {
params->clock_offset = htole16(*(peer->bredr()->clock_offset()));
}
auto cb = [id, self = weak_ptr_factory_.GetWeakPtr()](auto, const auto& event) {
if (!self) {
return;
}
if (hci_is_error(event, TRACE, "gap-bredr", "remote name request failed")) {
self->requesting_names_.erase(id);
return;
}
if (event.event_code() == hci::kCommandStatusEventCode) {
return;
}
ZX_DEBUG_ASSERT(event.event_code() == hci::kRemoteNameRequestCompleteEventCode);
self->requesting_names_.erase(id);
const auto& params = event.view().template payload<hci::RemoteNameRequestCompleteEventParams>();
Peer* const peer = self->cache_->FindById(id);
if (!peer) {
return;
}
const auto remote_name_end = std::find(params.remote_name, std::end(params.remote_name), '\0');
peer->SetName(std::string(params.remote_name, remote_name_end));
};
auto cmd_id = hci_->command_channel()->SendExclusiveCommand(
std::move(packet), std::move(cb), hci::kRemoteNameRequestCompleteEventCode, {hci::kInquiry});
if (cmd_id) {
requesting_names_.insert(id);
}
}
void BrEdrDiscoveryManager::RequestDiscoverable(DiscoverableCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(callback);
bt_log(INFO, "gap-bredr", "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()) {
bt_log(DEBUG, "gap-bredr", "discoverable mode starting, add to pending");
pending_discoverable_.push(std::move(status_cb));
return;
}
// If we're already discoverable, just add a session.
if (!discoverable_.empty()) {
bt_log(DEBUG, "gap-bredr", "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();
bt_log(DEBUG, "gap-bredr", "%s inquiry scan", (enable ? "enabling" : "disabling"));
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 = fit::defer([self, &status]() {
while (!self->pending_discoverable_.empty()) {
auto cb = std::move(self->pending_discoverable_.front());
self->pending_discoverable_.pop();
cb(status);
}
});
if (bt_is_error(status, WARN, "gap-bredr", "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) {
bt_log(INFO, "gap-bredr", "inquiry scan already %s", (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_payload<hci::WriteScanEnableCommandParams>()->scan_enable = scan_type;
resolve_pending.cancel();
self->hci_->command_channel()->SendCommand(
std::move(write_enable), [self](auto, const hci::EventPacket& event) {
if (!self) {
return;
}
// Warn if the command failed
hci_is_error(event, WARN, "gap-bredr", "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), std::move(scan_enable_cb));
}
void BrEdrDiscoveryManager::WriteInquiryScanSettings(uint16_t interval, uint16_t window,
bool interlaced) {
// TODO(jamuraa): add a callback for success or failure?
auto write_activity = hci::CommandPacket::New(hci::kWriteInquiryScanActivity,
sizeof(hci::WriteInquiryScanActivityCommandParams));
auto* activity_params =
write_activity->mutable_payload<hci::WriteInquiryScanActivityCommandParams>();
activity_params->inquiry_scan_interval = htole16(interval);
activity_params->inquiry_scan_window = htole16(window);
hci_->command_channel()->SendCommand(
std::move(write_activity), [](auto id, const hci::EventPacket& event) {
if (hci_is_error(event, WARN, "gap-bredr", "write inquiry scan activity failed")) {
return;
}
bt_log(TRACE, "gap-bredr", "inquiry scan activity updated");
});
auto write_type = hci::CommandPacket::New(hci::kWriteInquiryScanType,
sizeof(hci::WriteInquiryScanTypeCommandParams));
auto* type_params = write_type->mutable_payload<hci::WriteInquiryScanTypeCommandParams>();
type_params->inquiry_scan_type =
(interlaced ? hci::InquiryScanType::kInterlacedScan : hci::InquiryScanType::kStandardScan);
hci_->command_channel()->SendCommand(
std::move(write_type), [](auto id, const hci::EventPacket& event) {
if (hci_is_error(event, WARN, "gap-bredr", "write inquiry scan type failed")) {
return;
}
bt_log(TRACE, "gap-bredr", "inquiry scan type updated");
});
}
std::unique_ptr<BrEdrDiscoverySession> BrEdrDiscoveryManager::AddDiscoverySession() {
bt_log(TRACE, "gap-bredr", "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()));
ZX_DEBUG_ASSERT(discovering_.find(session.get()) == discovering_.end());
discovering_.insert(session.get());
return session;
}
void BrEdrDiscoveryManager::RemoveDiscoverySession(BrEdrDiscoverySession* session) {
bt_log(TRACE, "gap-bredr", "removing discovery session");
auto removed = discovering_.erase(session);
// TODO(fxbug.dev/668): Cancel the running inquiry with StopInquiry() instead.
if (removed) {
zombie_discovering_.insert(session);
}
}
std::unique_ptr<BrEdrDiscoverableSession> BrEdrDiscoveryManager::AddDiscoverableSession() {
bt_log(TRACE, "gap-bredr", "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()));
ZX_DEBUG_ASSERT(discoverable_.find(session.get()) == discoverable_.end());
discoverable_.insert(session.get());
return session;
}
void BrEdrDiscoveryManager::RemoveDiscoverableSession(BrEdrDiscoverableSession* session) {
bt_log(DEBUG, "gap-bredr", "removing discoverable session");
discoverable_.erase(session);
if (discoverable_.empty()) {
bt_log(INFO, "gap-bredr", "removed last discoverable session, enabling inquiry scan");
SetInquiryScan();
}
}
void BrEdrDiscoveryManager::InvalidateDiscoverySessions() {
for (auto session : discovering_) {
session->NotifyError();
}
discovering_.clear();
}
} // namespace bt::gap