blob: 39b2122863c88c1daddfcbb8525993185a2d8ebb [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_connection_manager.h"
#include <lib/async/time.h>
#include <zircon/assert.h>
#include "src/connectivity/bluetooth/core/bt-host/common/expiring_set.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/bredr_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/bredr_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/sequential_command_runner.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/types.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/error.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/transport.h"
namespace bt::gap {
using std::unique_ptr;
using ConnectionState = Peer::ConnectionState;
namespace {
const char* const kInspectRequestsNodeName = "connection_requests";
const char* const kInspectRequestNodeNamePrefix = "request_";
const char* const kInspectConnectionsNodeName = "connections";
const char* const kInspectConnectionNodeNamePrefix = "connection_";
const char* const kInspectLastDisconnectedListName = "last_disconnected";
const char* const kInspectLastDisconnectedItemDurationPropertyName = "duration_s";
const char* const kInspectLastDisconnectedItemPeerPropertyName = "peer_id";
const char* const kInspectTimestampPropertyName = "@time";
const char* const kInspectOutgoingNodeName = "outgoing";
const char* const kInspectIncomingNodeName = "incoming";
const char* const kInspectConnectionAttemptsNodeName = "connection_attempts";
const char* const kInspectSuccessfulConnectionsNodeName = "successful_connections";
const char* const kInspectFailedConnectionsNodeName = "failed_connections";
const char* const kInspectInterrogationCompleteCountNodeName = "interrogation_complete_count";
const char* const kInspectLocalApiRequestCountNodeName = "disconnect_local_api_request_count";
const char* const kInspectInterrogationFailedCountNodeName =
"disconnect_interrogation_failed_count";
const char* const kInspectPairingFailedCountNodeName = "disconnect_pairing_failed_count";
const char* const kInspectAclLinkErrorCountNodeName = "disconnect_acl_link_error_count";
const char* const kInspectPeerDisconnectionCountNodeName = "disconnect_peer_disconnection_count";
std::string ReasonAsString(DisconnectReason reason) {
switch (reason) {
case DisconnectReason::kApiRequest:
return "ApiRequest";
case DisconnectReason::kInterrogationFailed:
return "InterrogationFailed";
case DisconnectReason::kPairingFailed:
return "PairingFailed";
case DisconnectReason::kAclLinkError:
return "AclLinkError";
case DisconnectReason::kPeerDisconnection:
return "PeerDisconnection";
default:
return "<Unknown Reason>";
}
}
// This procedure can continue to operate independently of the existence of an
// BrEdrConnectionManager instance, which will begin to disable Page Scan as it shuts down.
void SetPageScanEnabled(bool enabled, fxl::WeakPtr<hci::Transport> hci,
async_dispatcher_t* dispatcher, hci::ResultFunction<> cb) {
ZX_DEBUG_ASSERT(cb);
auto read_enable = hci::CommandPacket::New(hci_spec::kReadScanEnable);
auto finish_enable_cb = [enabled, hci, finish_cb = std::move(cb)](
auto, const hci::EventPacket& event) mutable {
if (hci_is_error(event, WARN, "gap-bredr", "read scan enable failed")) {
finish_cb(event.ToResult());
return;
}
auto params = event.return_params<hci_spec::ReadScanEnableReturnParams>();
uint8_t scan_type = params->scan_enable;
if (enabled) {
scan_type |= static_cast<uint8_t>(hci_spec::ScanEnableBit::kPage);
} else {
scan_type &= ~static_cast<uint8_t>(hci_spec::ScanEnableBit::kPage);
}
auto write_enable = hci::CommandPacket::New(hci_spec::kWriteScanEnable,
sizeof(hci_spec::WriteScanEnableCommandParams));
write_enable->mutable_payload<hci_spec::WriteScanEnableCommandParams>()->scan_enable =
scan_type;
hci->command_channel()->SendCommand(
std::move(write_enable),
[cb = std::move(finish_cb)](auto, const hci::EventPacket& event) { cb(event.ToResult()); });
};
hci->command_channel()->SendCommand(std::move(read_enable), std::move(finish_enable_cb));
}
} // namespace
// An event signifying that a connection was completed by the controller
BrEdrConnectionManager::ConnectionComplete::ConnectionComplete(const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kConnectionCompleteEventCode);
const auto& params = event.params<hci_spec::ConnectionCompleteEventParams>();
handle = letoh16(params.connection_handle);
addr = DeviceAddress(DeviceAddress::Type::kBREDR, params.bd_addr);
status_code = params.status;
link_type = params.link_type;
}
// An event signifying that an incoming connection is being requested by a peer
BrEdrConnectionManager::ConnectionRequestEvent::ConnectionRequestEvent(
const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kConnectionRequestEventCode);
const auto& params = event.params<hci_spec::ConnectionRequestEventParams>();
addr = DeviceAddress(DeviceAddress::Type::kBREDR, params.bd_addr);
link_type = params.link_type;
class_of_device = params.class_of_device;
}
hci::CommandChannel::EventHandlerId BrEdrConnectionManager::AddEventHandler(
const hci_spec::EventCode& code, hci::CommandChannel::EventCallback cb) {
auto self = weak_ptr_factory_.GetWeakPtr();
auto event_id = hci_->command_channel()->AddEventHandler(
code, [self, callback = std::move(cb)](const auto& event) {
if (self) {
return callback(event);
}
return hci::CommandChannel::EventCallbackResult::kRemove;
});
ZX_DEBUG_ASSERT(event_id);
event_handler_ids_.push_back(event_id);
return event_id;
}
BrEdrConnectionManager::BrEdrConnectionManager(fxl::WeakPtr<hci::Transport> hci,
PeerCache* peer_cache, DeviceAddress local_address,
l2cap::ChannelManager* l2cap,
bool use_interlaced_scan)
: hci_(std::move(hci)),
cache_(peer_cache),
local_address_(local_address),
l2cap_(l2cap),
page_scan_interval_(0),
page_scan_window_(0),
use_interlaced_scan_(use_interlaced_scan),
request_timeout_(kBrEdrCreateConnectionTimeout),
dispatcher_(async_get_default_dispatcher()),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(hci_);
ZX_DEBUG_ASSERT(cache_);
ZX_DEBUG_ASSERT(l2cap_);
ZX_DEBUG_ASSERT(dispatcher_);
hci_cmd_runner_ = std::make_unique<hci::SequentialCommandRunner>(hci_);
// Register event handlers
AddEventHandler(hci_spec::kAuthenticationCompleteEventCode,
fit::bind_member<&BrEdrConnectionManager::OnAuthenticationComplete>(this));
AddEventHandler(hci_spec::kConnectionCompleteEventCode, [this](const hci::EventPacket& event) {
OnConnectionComplete(ConnectionComplete(event));
return hci::CommandChannel::EventCallbackResult::kContinue;
});
AddEventHandler(hci_spec::kConnectionRequestEventCode, [this](const hci::EventPacket& event) {
OnConnectionRequest(ConnectionRequestEvent(event));
return hci::CommandChannel::EventCallbackResult::kContinue;
});
AddEventHandler(hci_spec::kIOCapabilityRequestEventCode,
fit::bind_member<&BrEdrConnectionManager::OnIoCapabilityRequest>(this));
AddEventHandler(hci_spec::kIOCapabilityResponseEventCode,
fit::bind_member<&BrEdrConnectionManager::OnIoCapabilityResponse>(this));
AddEventHandler(hci_spec::kLinkKeyRequestEventCode,
fit::bind_member<&BrEdrConnectionManager::OnLinkKeyRequest>(this));
AddEventHandler(hci_spec::kLinkKeyNotificationEventCode,
fit::bind_member<&BrEdrConnectionManager::OnLinkKeyNotification>(this));
AddEventHandler(hci_spec::kSimplePairingCompleteEventCode,
fit::bind_member<&BrEdrConnectionManager::OnSimplePairingComplete>(this));
AddEventHandler(hci_spec::kUserConfirmationRequestEventCode,
fit::bind_member<&BrEdrConnectionManager::OnUserConfirmationRequest>(this));
AddEventHandler(hci_spec::kUserPasskeyRequestEventCode,
fit::bind_member<&BrEdrConnectionManager::OnUserPasskeyRequest>(this));
AddEventHandler(hci_spec::kUserPasskeyNotificationEventCode,
fit::bind_member<&BrEdrConnectionManager::OnUserPasskeyNotification>(this));
AddEventHandler(hci_spec::kRoleChangeEventCode,
fit::bind_member<&BrEdrConnectionManager::OnRoleChange>(this));
// Set the timeout for outbound connections explicitly to the spec default.
WritePageTimeout(hci_spec::kDefaultPageTimeoutDuration, [](const hci::Result<> status) {
[[maybe_unused]] bool _ = bt_is_error(status, WARN, "gap-bredr", "write page timeout failed");
});
}
BrEdrConnectionManager::~BrEdrConnectionManager() {
if (pending_request_ && pending_request_->Cancel())
SendCreateConnectionCancelCommand(pending_request_->peer_address());
// Disconnect any connections that we're holding.
connections_.clear();
// Become unconnectable
SetPageScanEnabled(/*enabled=*/false, hci_, dispatcher_, [](const auto) {});
// Remove all event handlers
for (auto handler_id : event_handler_ids_) {
hci_->command_channel()->RemoveEventHandler(handler_id);
}
}
void BrEdrConnectionManager::SetConnectable(bool connectable, hci::ResultFunction<> status_cb) {
auto self = weak_ptr_factory_.GetWeakPtr();
if (!connectable) {
auto not_connectable_cb = [self, cb = std::move(status_cb)](const auto& status) {
if (self) {
self->page_scan_interval_ = 0;
self->page_scan_window_ = 0;
} else if (status.is_ok()) {
cb(ToResult(HostError::kFailed));
return;
}
cb(status);
};
SetPageScanEnabled(/*enabled=*/false, hci_, dispatcher_, std::move(not_connectable_cb));
return;
}
WritePageScanSettings(
hci_spec::kPageScanR1Interval, hci_spec::kPageScanR1Window, use_interlaced_scan_,
[self, cb = std::move(status_cb)](const auto& status) mutable {
if (bt_is_error(status, WARN, "gap-bredr", "Write Page Scan Settings failed")) {
cb(status);
return;
}
if (!self) {
cb(ToResult(HostError::kFailed));
return;
}
SetPageScanEnabled(/*enabled=*/true, self->hci_, self->dispatcher_, std::move(cb));
});
}
void BrEdrConnectionManager::SetPairingDelegate(fxl::WeakPtr<PairingDelegate> delegate) {
pairing_delegate_ = std::move(delegate);
for (auto& [handle, connection] : connections_) {
connection.pairing_state().SetPairingDelegate(pairing_delegate_);
}
}
PeerId BrEdrConnectionManager::GetPeerId(hci_spec::ConnectionHandle handle) const {
auto it = connections_.find(handle);
if (it == connections_.end()) {
return kInvalidPeerId;
}
auto* peer = cache_->FindByAddress(it->second.link().peer_address());
ZX_DEBUG_ASSERT_MSG(peer, "Couldn't find peer for handle %#.4x", handle);
return peer->identifier();
}
void BrEdrConnectionManager::Pair(PeerId peer_id, BrEdrSecurityRequirements security,
hci::ResultFunction<> callback) {
auto conn_pair = FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "can't pair to peer_id %s: connection not found", bt_str(peer_id));
callback(ToResult(HostError::kNotFound));
return;
}
auto& [handle, connection] = *conn_pair;
auto pairing_callback = [pair_callback = std::move(callback)](auto, hci::Result<> status) {
pair_callback(status);
};
connection->pairing_state().InitiatePairing(security, std::move(pairing_callback));
}
void BrEdrConnectionManager::OpenL2capChannel(PeerId peer_id, l2cap::PSM psm,
BrEdrSecurityRequirements security_reqs,
l2cap::ChannelParameters params,
l2cap::ChannelCallback cb) {
auto pairing_cb = [self = weak_ptr_factory_.GetWeakPtr(), peer_id, psm, params,
cb = std::move(cb)](auto status) mutable {
bt_log(TRACE, "gap-bredr", "got pairing status %s, %sreturning socket to %s", bt_str(status),
status.is_ok() ? "" : "not ", bt_str(peer_id));
if (status.is_error() || !self) {
// Report the failure to the user with a null channel.
cb(nullptr);
return;
}
auto conn_pair = self->FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(INFO, "gap-bredr", "can't open l2cap channel: connection not found (peer: %s)",
bt_str(peer_id));
cb(nullptr);
return;
}
auto& [handle, connection] = *conn_pair;
connection->OpenL2capChannel(psm, params,
[cb = std::move(cb)](auto chan) { cb(std::move(chan)); });
};
Pair(peer_id, security_reqs, std::move(pairing_cb));
}
BrEdrConnectionManager::SearchId BrEdrConnectionManager::AddServiceSearch(
const UUID& uuid, std::unordered_set<sdp::AttributeId> attributes,
BrEdrConnectionManager::SearchCallback callback) {
auto on_service_discovered = [self = weak_ptr_factory_.GetWeakPtr(), uuid,
client_cb = std::move(callback)](PeerId peer_id, auto& attributes) {
if (self) {
Peer* const peer = self->cache_->FindById(peer_id);
ZX_ASSERT(peer);
peer->MutBrEdr().AddService(uuid);
}
client_cb(peer_id, attributes);
};
return discoverer_.AddSearch(uuid, std::move(attributes), std::move(on_service_discovered));
}
bool BrEdrConnectionManager::RemoveServiceSearch(SearchId id) {
return discoverer_.RemoveSearch(id);
}
std::optional<BrEdrConnectionManager::ScoRequestHandle> BrEdrConnectionManager::OpenScoConnection(
PeerId peer_id, hci_spec::SynchronousConnectionParameters parameters,
sco::ScoConnectionManager::OpenConnectionCallback callback) {
auto conn_pair = FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "Can't open SCO connection to unconnected peer (peer: %s)",
bt_str(peer_id));
callback(fitx::error(HostError::kNotFound));
return std::nullopt;
};
return conn_pair->second->OpenScoConnection(parameters, std::move(callback));
}
std::optional<BrEdrConnectionManager::ScoRequestHandle> BrEdrConnectionManager::AcceptScoConnection(
PeerId peer_id, std::vector<hci_spec::SynchronousConnectionParameters> parameters,
sco::ScoConnectionManager::AcceptConnectionCallback callback) {
auto conn_pair = FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "Can't accept SCO connection from unconnected peer (peer: %s)",
bt_str(peer_id));
callback(fitx::error(HostError::kNotFound));
return std::nullopt;
};
return conn_pair->second->AcceptScoConnection(std::move(parameters), std::move(callback));
}
bool BrEdrConnectionManager::Disconnect(PeerId peer_id, DisconnectReason reason) {
bt_log_scope("peer: %s", bt_str(peer_id));
bt_log(INFO, "gap-bredr", "Disconnect Requested (reason %hhu - %s)", reason,
ReasonAsString(reason).c_str());
// TODO(fxbug.dev/65157) - If a disconnect request is received when we have a pending connection,
// we should instead abort the connection, by either:
// * removing the request if it has not yet been processed
// * sending a cancel command to the controller and waiting for it to be processed
// * sending a cancel command, and if we already complete, then beginning a disconnect procedure
if (connection_requests_.find(peer_id) != connection_requests_.end()) {
bt_log(WARN, "gap-bredr", "Can't disconnect because it's being connected to");
return false;
}
auto conn_pair = FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(INFO, "gap-bredr", "No need to disconnect: It is not connected");
return true;
}
auto [handle, connection] = *conn_pair;
const DeviceAddress& peer_addr = connection->link().peer_address();
bt_log_scope("addr: %s", bt_str(peer_addr));
if (reason == DisconnectReason::kApiRequest) {
bt_log(DEBUG, "gap-bredr", "requested disconnect from peer, cooldown for %lds",
kLocalDisconnectCooldownDuration.to_secs());
deny_incoming_.add_until(
peer_addr, async::Now(async_get_default_dispatcher()) + kLocalDisconnectCooldownDuration);
}
CleanUpConnection(handle, std::move(connections_.extract(handle).mapped()), reason);
return true;
}
void BrEdrConnectionManager::AttachInspect(inspect::Node& parent, std::string name) {
inspect_node_ = parent.CreateChild(name);
inspect_properties_.connections_node_ = inspect_node_.CreateChild(kInspectConnectionsNodeName);
inspect_properties_.last_disconnected_list.AttachInspect(inspect_node_,
kInspectLastDisconnectedListName);
inspect_properties_.requests_node_ = inspect_node_.CreateChild(kInspectRequestsNodeName);
for (auto& [_, req] : connection_requests_) {
req.AttachInspect(inspect_properties_.requests_node_,
inspect_properties_.requests_node_.UniqueName(kInspectRequestNodeNamePrefix));
}
inspect_properties_.outgoing_.node_ = inspect_node_.CreateChild(kInspectOutgoingNodeName);
inspect_properties_.outgoing_.connection_attempts_.AttachInspect(
inspect_properties_.outgoing_.node_, kInspectConnectionAttemptsNodeName);
inspect_properties_.outgoing_.successful_connections_.AttachInspect(
inspect_properties_.outgoing_.node_, kInspectSuccessfulConnectionsNodeName);
inspect_properties_.outgoing_.failed_connections_.AttachInspect(
inspect_properties_.outgoing_.node_, kInspectFailedConnectionsNodeName);
inspect_properties_.incoming_.node_ = inspect_node_.CreateChild(kInspectIncomingNodeName);
inspect_properties_.incoming_.connection_attempts_.AttachInspect(
inspect_properties_.incoming_.node_, kInspectConnectionAttemptsNodeName);
inspect_properties_.incoming_.successful_connections_.AttachInspect(
inspect_properties_.incoming_.node_, kInspectSuccessfulConnectionsNodeName);
inspect_properties_.incoming_.failed_connections_.AttachInspect(
inspect_properties_.incoming_.node_, kInspectFailedConnectionsNodeName);
inspect_properties_.interrogation_complete_count_.AttachInspect(
inspect_node_, kInspectInterrogationCompleteCountNodeName);
inspect_properties_.disconnect_local_api_request_count_.AttachInspect(
inspect_node_, kInspectLocalApiRequestCountNodeName);
inspect_properties_.disconnect_interrogation_failed_count_.AttachInspect(
inspect_node_, kInspectInterrogationFailedCountNodeName);
inspect_properties_.disconnect_pairing_failed_count_.AttachInspect(
inspect_node_, kInspectPairingFailedCountNodeName);
inspect_properties_.disconnect_acl_link_error_count_.AttachInspect(
inspect_node_, kInspectAclLinkErrorCountNodeName);
inspect_properties_.disconnect_peer_disconnection_count_.AttachInspect(
inspect_node_, kInspectPeerDisconnectionCountNodeName);
}
void BrEdrConnectionManager::WritePageTimeout(zx::duration page_timeout, hci::ResultFunction<> cb) {
ZX_ASSERT(page_timeout >= hci_spec::kMinPageTimeoutDuration);
ZX_ASSERT(page_timeout <= hci_spec::kMaxPageTimeoutDuration);
const int64_t raw_page_timeout = page_timeout / hci_spec::kDurationPerPageTimeoutUnit;
ZX_ASSERT(raw_page_timeout >= hci_spec::kMinPageTimeoutCommandParameterValue);
ZX_ASSERT(raw_page_timeout <= hci_spec::kMaxPageTimeoutCommandParameterValue);
auto write_page_timeout_cmd = hci::CommandPacket::New(
hci_spec::kWritePageTimeout, sizeof(hci_spec::WritePageTimeoutCommandParams));
auto& params =
*write_page_timeout_cmd->mutable_payload<hci_spec::WritePageTimeoutCommandParams>();
params.page_timeout = static_cast<uint16_t>(raw_page_timeout);
hci_->command_channel()->SendCommand(
std::move(write_page_timeout_cmd),
[cb = std::move(cb)](auto id, const hci::EventPacket& event) { cb(event.ToResult()); });
}
void BrEdrConnectionManager::WritePageScanSettings(uint16_t interval, uint16_t window,
bool interlaced, hci::ResultFunction<> cb) {
auto self = weak_ptr_factory_.GetWeakPtr();
if (!hci_cmd_runner_->IsReady()) {
// TODO(jamuraa): could run the three "settings" commands in parallel and
// remove the sequence runner.
cb(ToResult(HostError::kInProgress));
return;
}
auto write_activity = hci::CommandPacket::New(
hci_spec::kWritePageScanActivity, sizeof(hci_spec::WritePageScanActivityCommandParams));
auto* activity_params =
write_activity->mutable_payload<hci_spec::WritePageScanActivityCommandParams>();
activity_params->page_scan_interval = htole16(interval);
activity_params->page_scan_window = htole16(window);
hci_cmd_runner_->QueueCommand(
std::move(write_activity), [self, interval, window](const hci::EventPacket& event) {
if (!self || hci_is_error(event, WARN, "gap-bredr", "write page scan activity failed")) {
return;
}
self->page_scan_interval_ = interval;
self->page_scan_window_ = window;
bt_log(TRACE, "gap-bredr", "page scan activity updated");
});
auto write_type = hci::CommandPacket::New(hci_spec::kWritePageScanType,
sizeof(hci_spec::WritePageScanTypeCommandParams));
auto* type_params = write_type->mutable_payload<hci_spec::WritePageScanTypeCommandParams>();
type_params->page_scan_type = (interlaced ? hci_spec::PageScanType::kInterlacedScan
: hci_spec::PageScanType::kStandardScan);
hci_cmd_runner_->QueueCommand(
std::move(write_type), [self, interlaced](const hci::EventPacket& event) {
if (!self || hci_is_error(event, WARN, "gap-bredr", "write page scan type failed")) {
return;
}
self->page_scan_type_ = (interlaced ? hci_spec::PageScanType::kInterlacedScan
: hci_spec::PageScanType::kStandardScan);
bt_log(TRACE, "gap-bredr", "page scan type updated");
});
hci_cmd_runner_->RunCommands(std::move(cb));
}
std::optional<std::pair<hci_spec::ConnectionHandle, BrEdrConnection*>>
BrEdrConnectionManager::FindConnectionById(PeerId peer_id) {
auto it = std::find_if(connections_.begin(), connections_.end(),
[peer_id](const auto& c) { return c.second.peer_id() == peer_id; });
if (it == connections_.end()) {
return std::nullopt;
}
auto& [handle, conn] = *it;
return std::pair(handle, &conn);
}
std::optional<std::pair<hci_spec::ConnectionHandle, BrEdrConnection*>>
BrEdrConnectionManager::FindConnectionByAddress(const DeviceAddressBytes& bd_addr) {
auto* const peer = cache_->FindByAddress(DeviceAddress(DeviceAddress::Type::kBREDR, bd_addr));
if (!peer) {
return std::nullopt;
}
return FindConnectionById(peer->identifier());
}
Peer* BrEdrConnectionManager::FindOrInitPeer(DeviceAddress addr) {
Peer* peer = cache_->FindByAddress(addr);
if (!peer) {
peer = cache_->NewPeer(addr, /*connectable*/ true);
}
return peer;
}
// Build connection state for a new connection and begin interrogation. L2CAP is not enabled for
// this link but pairing is allowed before interrogation completes.
void BrEdrConnectionManager::InitializeConnection(DeviceAddress addr,
hci_spec::ConnectionHandle connection_handle,
hci_spec::ConnectionRole role) {
auto link =
std::make_unique<hci::BrEdrConnection>(connection_handle, local_address_, addr, role, hci_);
Peer* const peer = FindOrInitPeer(addr);
auto peer_id = peer->identifier();
bt_log(INFO, "gap-bredr", "Beginning interrogation for peer %s", bt_str(peer_id));
// We should never have more than one link to a given peer
ZX_DEBUG_ASSERT(!FindConnectionById(peer_id));
// The controller has completed the HCI connection procedure, so the connection request can no
// longer be failed by a lower layer error. Now tie error reporting of the request to the lifetime
// of the connection state object (BrEdrConnection RAII).
auto node = connection_requests_.extract(peer_id);
auto request = node ? std::optional(std::move(node.mapped())) : std::nullopt;
const hci_spec::ConnectionHandle handle = link->handle();
auto send_auth_request_cb = [this, handle]() {
this->SendAuthenticationRequested(handle, [handle](auto status) {
bt_is_error(status, WARN, "gap-bredr", "authentication requested command failed for %#.4x",
handle);
});
};
auto disconnect_cb = [this, peer_id] { Disconnect(peer_id, DisconnectReason::kPairingFailed); };
auto on_peer_disconnect_cb = [this, link = link.get()] { OnPeerDisconnect(link); };
auto [conn_iter, success] = connections_.try_emplace(
handle, peer->GetWeakPtr(), std::move(link), std::move(send_auth_request_cb),
std::move(disconnect_cb), std::move(on_peer_disconnect_cb), l2cap_, hci_, std::move(request));
ZX_ASSERT(success);
BrEdrConnection& connection = conn_iter->second;
connection.pairing_state().SetPairingDelegate(pairing_delegate_);
connection.AttachInspect(
inspect_properties_.connections_node_,
inspect_properties_.connections_node_.UniqueName(kInspectConnectionNodeNamePrefix));
// Interrogate this peer to find out its version/capabilities.
connection.Interrogate([this, peer = peer->GetWeakPtr(), handle](hci::Result<> result) {
bt_log_scope("peer: %s, handle: %#.4x", bt_str(peer->identifier()), handle);
if (bt_is_error(result, WARN, "gap-bredr", "interrogation failed, dropping connection")) {
// If this connection was locally requested, requester(s) are notified by the disconnection.
Disconnect(peer->identifier(), DisconnectReason::kInterrogationFailed);
return;
}
bt_log(INFO, "gap-bredr", "interrogation complete");
CompleteConnectionSetup(peer.get(), handle);
});
// If this was our in-flight request, close it
if (pending_request_.has_value() && addr == pending_request_->peer_address()) {
pending_request_.reset();
}
TryCreateNextConnection();
}
// Finish connection setup after a successful interrogation.
void BrEdrConnectionManager::CompleteConnectionSetup(Peer* peer,
hci_spec::ConnectionHandle handle) {
auto self = weak_ptr_factory_.GetWeakPtr();
bt_log_scope("peer: %s, handle: %#.4x", bt_str(peer->identifier()), handle);
auto connections_iter = connections_.find(handle);
if (connections_iter == connections_.end()) {
bt_log(WARN, "gap-bredr", "Connection to complete not found");
return;
}
BrEdrConnection& conn_state = connections_iter->second;
if (conn_state.peer_id() != peer->identifier()) {
bt_log(WARN, "gap-bredr",
"Connection switched peers! (now to %s), ignoring interrogation result",
bt_str(conn_state.peer_id()));
return;
}
hci::BrEdrConnection* const connection = &conn_state.link();
auto error_handler = [self, log_ctx = capture_log_context(), peer_id = peer->identifier(),
connection = connection->GetWeakPtr()] {
if (!self || !connection)
return;
add_parent_context(log_ctx);
bt_log(WARN, "gap-bredr", "Link error received, closing connection");
self->Disconnect(peer_id, DisconnectReason::kAclLinkError);
};
// TODO(fxbug.dev/37650): Implement this callback as a call to InitiatePairing().
auto security_callback = [log_ctx = capture_log_context()](hci_spec::ConnectionHandle handle,
sm::SecurityLevel level, auto cb) {
add_parent_context(log_ctx);
bt_log(INFO, "gap-bredr", "Ignoring security upgrade request; not implemented");
cb(ToResult(HostError::kNotSupported));
};
// Register with L2CAP to handle services on the ACL signaling channel.
l2cap_->AddACLConnection(handle, connection->role(), error_handler, std::move(security_callback));
// Remove from the denylist if we successfully connect.
deny_incoming_.remove(peer->address());
inspect_properties_.interrogation_complete_count_.Add(1);
if (discoverer_.search_count()) {
l2cap_->OpenL2capChannel(handle, l2cap::kSDP, l2cap::ChannelParameters(),
[self, peer_id = peer->identifier()](auto channel) {
if (!self)
return;
if (!channel) {
bt_log_scope("peer: %s", bt_str(peer_id));
bt_log(ERROR, "gap", "failed to create l2cap channel for SDP");
return;
}
auto client = sdp::Client::Create(std::move(channel));
self->discoverer_.StartServiceDiscovery(peer_id, std::move(client));
});
}
conn_state.OnInterrogationComplete();
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnAuthenticationComplete(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kAuthenticationCompleteEventCode);
const auto& params = event.params<hci_spec::AuthenticationCompleteEventParams>();
auto connection_handle = params.connection_handle;
auto iter = connections_.find(connection_handle);
if (iter == connections_.end()) {
bt_log(INFO, "gap-bredr",
"ignoring authentication complete (status: %s) for unknown connection handle %#.04x",
bt_str(ToResult(params.status)), connection_handle);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci_spec::StatusCode status_code;
event.ToStatusCode(&status_code);
iter->second.pairing_state().OnAuthenticationComplete(status_code);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bool BrEdrConnectionManager::ExistsIncomingRequest(PeerId id) {
auto request = connection_requests_.find(id);
return (request != connection_requests_.end() && request->second.HasIncoming());
}
void BrEdrConnectionManager::OnConnectionRequest(ConnectionRequestEvent event) {
if (deny_incoming_.contains(event.addr)) {
bt_log(INFO, "gap-bredr", "rejecting incoming from peer (addr: %s) on cooldown",
bt_str(event.addr));
SendRejectConnectionRequest(event.addr, hci_spec::StatusCode::kConnectionRejectedBadBdAddr);
return;
}
// Initialize the peer if it doesn't exist, to ensure we have allocated a PeerId
auto peer = FindOrInitPeer(event.addr);
auto peer_id = peer->identifier();
bt_log_scope("peer: %s, addr: %s, link_type: %s, class: %s", bt_str(peer_id), bt_str(event.addr),
hci_spec::LinkTypeToString(event.link_type).c_str(), bt_str(event.class_of_device));
// In case of concurrent incoming requests from the same peer, reject all but the first
if (ExistsIncomingRequest(peer_id)) {
bt_log(WARN, "gap-bredr", "rejecting duplicate incoming connection request");
SendRejectConnectionRequest(event.addr, hci_spec::StatusCode::kConnectionRejectedBadBdAddr);
return;
}
if (event.link_type == hci_spec::LinkType::kACL) {
// If we happen to be already connected (for example, if our outgoing raced, or we received
// duplicate requests), we reject the request with 'ConnectionAlreadyExists'
if (FindConnectionById(peer_id)) {
bt_log(WARN, "gap-bredr", "rejecting incoming connection request; already connected");
SendRejectConnectionRequest(event.addr, hci_spec::StatusCode::kConnectionAlreadyExists);
return;
}
// Accept the connection, performing a role switch. We receive a Connection Complete event
// when the connection is complete, and finish the link then.
bt_log(INFO, "gap-bredr", "accepting incoming connection");
// Register that we're in the middle of an incoming request for this peer - create a new
// request if one doesn't already exist
auto [request, _ignore] = connection_requests_.try_emplace(
peer_id, event.addr, peer_id, peer->MutBrEdr().RegisterInitializingConnection());
inspect_properties_.incoming_.connection_attempts_.Add(1);
request->second.BeginIncoming();
request->second.AttachInspect(
inspect_properties_.requests_node_,
inspect_properties_.requests_node_.UniqueName(kInspectRequestNodeNamePrefix));
SendAcceptConnectionRequest(
event.addr.value(),
[addr = event.addr, self = weak_ptr_factory_.GetWeakPtr(), peer_id](auto status) {
if (self && status.is_error())
self->CompleteRequest(peer_id, addr, status, /*handle=*/0);
});
return;
}
if (event.link_type == hci_spec::LinkType::kSCO ||
event.link_type == hci_spec::LinkType::kExtendedSCO) {
auto conn_pair = FindConnectionByAddress(event.addr.value());
if (conn_pair) {
// The ScoConnectionManager owned by the BrEdrConnection will respond.
bt_log(INFO, "gap-bredr", "delegating incoming SCO connection to ScoConnectionManager");
return;
}
bt_log(WARN, "gap-bredr", "rejecting (e)SCO connection request for peer that is not connected");
SendRejectSynchronousRequest(event.addr,
hci_spec::StatusCode::kUnacceptableConnectionParameters);
} else {
auto link_type = static_cast<unsigned int>(event.link_type);
bt_log(WARN, "gap-bredr", "reject unsupported connection type %u", link_type);
SendRejectConnectionRequest(event.addr, hci_spec::StatusCode::kUnsupportedFeatureOrParameter);
}
}
void BrEdrConnectionManager::OnConnectionComplete(ConnectionComplete event) {
if (event.link_type != hci_spec::LinkType::kACL) {
// Only ACL links are processed
return;
}
// Initialize the peer if it doesn't exist, to ensure we have allocated a PeerId (we should
// usually have a peer by this point)
auto peer = FindOrInitPeer(event.addr);
CompleteRequest(peer->identifier(), event.addr, event.ToResult(), event.handle);
}
// A request for a connection - from an upstream client _or_ a remote peer - completed, successfully
// or not. This may be due to a ConnectionComplete event being received, or due to a CommandStatus
// response being received in response to a CreateConnection command
void BrEdrConnectionManager::CompleteRequest(PeerId peer_id, DeviceAddress address,
hci::Result<> status,
hci_spec::ConnectionHandle handle) {
bt_log_scope("peer: %s, addr: %s, handle: %#.4x", bt_str(peer_id), bt_str(address), handle);
auto req_iter = connection_requests_.find(peer_id);
if (req_iter == connection_requests_.end()) {
// Prevent logspam for rejected during cooldown.
if (deny_incoming_.contains(address)) {
return;
}
// This could potentially happen if the peer expired from the peer cache during the connection
// procedure
bt_log(INFO, "gap-bredr", "ConnectionComplete received with no known request (status: %s)",
bt_str(status));
return;
}
auto& request = req_iter->second;
bool completes_outgoing_request =
pending_request_.has_value() && pending_request_->peer_address() == address;
bool failed = status.is_error();
const char* direction = completes_outgoing_request ? "outgoing" : "incoming";
const char* result = status.is_ok() ? "complete" : "error";
hci_spec::ConnectionRole role = completes_outgoing_request
? hci_spec::ConnectionRole::kCentral
: hci_spec::ConnectionRole::kPeripheral;
if (request.role_change()) {
role = request.role_change().value();
}
bt_log(INFO, "gap-bredr", "%s connection %s (status: %s, role: %s)", direction, result,
bt_str(status), role == hci_spec::ConnectionRole::kCentral ? "leader" : "follower");
if (completes_outgoing_request) {
// Determine the modified status in case of cancellation or timeout
status = pending_request_->CompleteRequest(status);
pending_request_.reset();
} else {
// This incoming connection arrived while we're trying to make an outgoing connection; not an
// impossible coincidence but log it in case it's interesting.
// TODO(fxbug.dev/92299): Added to investigate timing and can be removed if it adds no value
if (pending_request_.has_value()) {
bt_log(INFO, "gap-bredr",
"doesn't complete pending outgoing connection to peer %s (addr: %s)",
bt_str(pending_request_->peer_id()), bt_str(pending_request_->peer_address()));
}
// If this was an incoming attempt, clear it
request.CompleteIncoming();
}
if (failed) {
if (request.HasIncoming() || (!completes_outgoing_request && request.AwaitingOutgoing())) {
// This request failed, but we're still waiting on either:
// * an in-progress incoming request or
// * to attempt our own outgoing request
// Therefore we don't notify yet - instead take no action, and wait until we finish those
// steps before completing the request and notifying callbacks
TryCreateNextConnection();
return;
}
if (completes_outgoing_request && connection_requests_.size() == 1 &&
request.ShouldRetry(status.error_value())) {
bt_log(INFO, "gap-bredr",
"no pending connection requests to other peers, so %sretrying outbound connection",
request.HasIncoming() ? "waiting for inbound request completion before potentially "
: "");
// By not erasing |request| from |connection_requests_|, even if TryCreateNextConnection does
// not directly retry because there's an inbound request to the same peer, the retry will
// happen if the inbound request completes unusuccessfully.
TryCreateNextConnection();
return;
}
if (completes_outgoing_request) {
inspect_properties_.outgoing_.failed_connections_.Add(1);
} else if (request.HasIncoming()) {
inspect_properties_.incoming_.failed_connections_.Add(1);
}
request.NotifyCallbacks(status, [] { return nullptr; });
connection_requests_.erase(req_iter);
} else {
if (completes_outgoing_request) {
inspect_properties_.outgoing_.successful_connections_.Add(1);
} else if (request.HasIncoming()) {
inspect_properties_.incoming_.successful_connections_.Add(1);
}
// Callbacks will be notified when interrogation completes
InitializeConnection(address, handle, role);
}
TryCreateNextConnection();
}
void BrEdrConnectionManager::OnPeerDisconnect(const hci::Connection* connection) {
auto handle = connection->handle();
auto it = connections_.find(handle);
if (it == connections_.end()) {
bt_log(WARN, "gap-bredr", "disconnect from unknown connection handle %#.4x", handle);
return;
}
auto conn = std::move(it->second);
connections_.erase(it);
bt_log(INFO, "gap-bredr", "peer disconnected (peer: %s, handle: %#.4x)", bt_str(conn.peer_id()),
handle);
CleanUpConnection(handle, std::move(conn), DisconnectReason::kPeerDisconnection);
}
void BrEdrConnectionManager::CleanUpConnection(hci_spec::ConnectionHandle handle,
BrEdrConnection conn, DisconnectReason reason) {
l2cap_->RemoveConnection(handle);
RecordDisconnectInspect(conn, reason);
// |conn| is destroyed when it goes out of scope.
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnIoCapabilityRequest(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kIOCapabilityRequestEventCode);
const auto& params = event.params<hci_spec::IOCapabilityRequestEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(ERROR, "gap-bredr", "got %s for unconnected addr %s", __func__, bt_str(params.bd_addr));
SendIoCapabilityRequestNegativeReply(params.bd_addr, hci_spec::StatusCode::kPairingNotAllowed);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto [handle, conn_ptr] = *conn_pair;
auto reply = conn_ptr->pairing_state().OnIoCapabilityRequest();
if (!reply) {
SendIoCapabilityRequestNegativeReply(params.bd_addr, hci_spec::StatusCode::kPairingNotAllowed);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
const hci_spec::IOCapability io_capability = *reply;
// TODO(fxbug.dev/601): Add OOB status from PeerCache.
const uint8_t oob_data_present = 0x00; // None present.
// TODO(fxbug.dev/1249): Determine this based on the service requirements.
const hci_spec::AuthRequirements auth_requirements =
io_capability == hci_spec::IOCapability::kNoInputNoOutput
? hci_spec::AuthRequirements::kGeneralBonding
: hci_spec::AuthRequirements::kMITMGeneralBonding;
SendIoCapabilityRequestReply(params.bd_addr, io_capability, oob_data_present, auth_requirements);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnIoCapabilityResponse(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kIOCapabilityResponseEventCode);
const auto& params = event.params<hci_spec::IOCapabilityResponseEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(INFO, "gap-bredr", "got %s for unconnected addr %s", __func__, bt_str(params.bd_addr));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
conn_pair->second->pairing_state().OnIoCapabilityResponse(params.io_capability);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnLinkKeyRequest(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kLinkKeyRequestEventCode);
const auto& params = event.params<hci_spec::LinkKeyRequestParams>();
DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr);
auto* peer = cache_->FindByAddress(addr);
if (!peer) {
bt_log(WARN, "gap-bredr", "no peer with address %s found", bt_str(addr));
SendLinkKeyRequestNegativeReply(params.bd_addr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto peer_id = peer->identifier();
auto conn_pair = FindConnectionById(peer_id);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "can't find connection for ltk (id: %s)", bt_str(peer_id));
SendLinkKeyRequestNegativeReply(params.bd_addr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto& [handle, conn] = *conn_pair;
auto link_key = conn->pairing_state().OnLinkKeyRequest();
if (!link_key.has_value()) {
SendLinkKeyRequestNegativeReply(params.bd_addr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
SendLinkKeyRequestReply(params.bd_addr, link_key.value());
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnLinkKeyNotification(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kLinkKeyNotificationEventCode);
const auto& params = event.params<hci_spec::LinkKeyNotificationEventParams>();
DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr);
auto* peer = cache_->FindByAddress(addr);
if (!peer) {
bt_log(WARN, "gap-bredr",
"no known peer with address %s found; link key not stored (key type: %u)", bt_str(addr),
params.key_type);
cache_->LogBrEdrBondingEvent(false);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bt_log(INFO, "gap-bredr", "got link key notification (key type: %u, peer: %s)", params.key_type,
bt_str(peer->identifier()));
auto key_type = hci_spec::LinkKeyType{params.key_type};
sm::SecurityProperties sec_props;
if (key_type == hci_spec::LinkKeyType::kChangedCombination) {
if (!peer->bredr() || !peer->bredr()->bonded()) {
bt_log(WARN, "gap-bredr", "can't update link key of unbonded peer %s",
bt_str(peer->identifier()));
cache_->LogBrEdrBondingEvent(false);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
// Reuse current properties
ZX_DEBUG_ASSERT(peer->bredr()->link_key());
sec_props = peer->bredr()->link_key()->security();
key_type = *sec_props.GetLinkKeyType();
} else {
sec_props = sm::SecurityProperties(key_type);
}
auto peer_id = peer->identifier();
if (sec_props.level() == sm::SecurityLevel::kNoSecurity) {
bt_log(WARN, "gap-bredr", "link key for peer %s has insufficient security; not stored",
bt_str(peer_id));
cache_->LogBrEdrBondingEvent(false);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
UInt128 key_value;
std::copy(params.link_key, &params.link_key[key_value.size()], key_value.begin());
hci_spec::LinkKey hci_key(key_value, 0, 0);
sm::LTK key(sec_props, hci_key);
auto handle = FindConnectionById(peer_id);
if (!handle) {
bt_log(WARN, "gap-bredr", "can't find current connection for ltk (peer: %s)", bt_str(peer_id));
} else {
handle->second->link().set_link_key(hci_key, key_type);
handle->second->pairing_state().OnLinkKeyNotification(key_value, key_type);
}
if (cache_->StoreBrEdrBond(addr, key)) {
cache_->LogBrEdrBondingEvent(true);
} else {
cache_->LogBrEdrBondingEvent(false);
bt_log(ERROR, "gap-bredr", "failed to cache bonding data (peer: %s)", bt_str(peer_id));
}
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnSimplePairingComplete(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kSimplePairingCompleteEventCode);
const auto& params = event.params<hci_spec::SimplePairingCompleteEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "got Simple Pairing Complete (status: %s) for unconnected addr %s",
bt_str(ToResult(params.status)), bt_str(params.bd_addr));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
conn_pair->second->pairing_state().OnSimplePairingComplete(params.status);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnUserConfirmationRequest(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kUserConfirmationRequestEventCode);
const auto& params = event.params<hci_spec::UserConfirmationRequestEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "got %s for unconnected addr %s", __func__, bt_str(params.bd_addr));
SendUserConfirmationRequestNegativeReply(params.bd_addr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto confirm_cb = [this, self = weak_ptr_factory_.GetWeakPtr(),
bd_addr = params.bd_addr](bool confirm) {
if (!self) {
return;
}
if (confirm) {
SendUserConfirmationRequestReply(bd_addr);
} else {
SendUserConfirmationRequestNegativeReply(bd_addr);
}
};
conn_pair->second->pairing_state().OnUserConfirmationRequest(letoh32(params.numeric_value),
std::move(confirm_cb));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnUserPasskeyRequest(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kUserPasskeyRequestEventCode);
const auto& params = event.params<hci_spec::UserPasskeyRequestEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "got %s for unconnected addr %s", __func__, bt_str(params.bd_addr));
SendUserPasskeyRequestNegativeReply(params.bd_addr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto passkey_cb = [this, self = weak_ptr_factory_.GetWeakPtr(),
bd_addr = params.bd_addr](std::optional<uint32_t> passkey) {
if (!self) {
return;
}
if (passkey) {
SendUserPasskeyRequestReply(bd_addr, *passkey);
} else {
SendUserPasskeyRequestNegativeReply(bd_addr);
}
};
conn_pair->second->pairing_state().OnUserPasskeyRequest(std::move(passkey_cb));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnUserPasskeyNotification(
const hci::EventPacket& event) {
ZX_DEBUG_ASSERT(event.event_code() == hci_spec::kUserPasskeyNotificationEventCode);
const auto& params = event.params<hci_spec::UserPasskeyNotificationEventParams>();
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "got %s for unconnected addr %s", __func__, bt_str(params.bd_addr));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
conn_pair->second->pairing_state().OnUserPasskeyNotification(letoh32(params.numeric_value));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult BrEdrConnectionManager::OnRoleChange(
const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kRoleChangeEventCode);
const auto& params = event.params<hci_spec::RoleChangeEventParams>();
DeviceAddress address(DeviceAddress::Type::kBREDR, params.bd_addr);
Peer* peer = cache_->FindByAddress(address);
if (!peer) {
bt_log(WARN, "gap-bredr", "got %s for unknown peer (address: %s)", __func__, bt_str(address));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
PeerId peer_id = peer->identifier();
// When a role change is requested in the HCI_Accept_Connection_Request command, a HCI_Role_Change
// event may be received prior to the HCI_Connection_Complete event (so no connection object will
// exist yet) (Core Spec v5.2, Vol 2, Part F, Sec 3.1).
auto request_iter = connection_requests_.find(peer_id);
if (request_iter != connection_requests_.end()) {
request_iter->second.set_role_change(params.new_role);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto conn_pair = FindConnectionByAddress(params.bd_addr);
if (!conn_pair) {
bt_log(WARN, "gap-bredr", "got %s for unconnected peer %s", __func__, bt_str(peer_id));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
if (hci_is_error(event, WARN, "gap-bredr", "role change failed and remains %s (peer: %s)",
hci_spec::ConnectionRoleToString(params.new_role).c_str(), bt_str(peer_id))) {
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bt_log(DEBUG, "gap-bredr", "role changed to %s (peer: %s)",
hci_spec::ConnectionRoleToString(params.new_role).c_str(), bt_str(peer_id));
conn_pair->second->link().set_role(params.new_role);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bool BrEdrConnectionManager::Connect(PeerId peer_id, ConnectResultCallback on_connection_result) {
Peer* peer = cache_->FindById(peer_id);
if (!peer) {
bt_log(WARN, "gap-bredr", "%s: peer not found (peer: %s)", __func__, bt_str(peer_id));
return false;
}
if (peer->technology() == TechnologyType::kLowEnergy) {
bt_log(ERROR, "gap-bredr", "peer does not support BrEdr: %s", bt_str(*peer));
return false;
}
// Br/Edr peers should always be connectable by definition
ZX_ASSERT(peer->connectable());
// Succeed immediately or after interrogation if there is already an active connection.
auto conn = FindConnectionById(peer_id);
if (conn) {
conn->second->AddRequestCallback(std::move(on_connection_result));
return true;
}
// If we are already waiting to connect to |peer_id| then we store
// |on_connection_result| to be processed after the connection attempt
// completes (in either success of failure).
auto pending_iter = connection_requests_.find(peer_id);
if (pending_iter != connection_requests_.end()) {
pending_iter->second.AddCallback(std::move(on_connection_result));
return true;
}
// If we are not already connected or pending, initiate a new connection
auto [request_iter, _] = connection_requests_.try_emplace(
peer_id, peer->address(), peer_id, peer->MutBrEdr().RegisterInitializingConnection(),
std::move(on_connection_result));
request_iter->second.AttachInspect(
inspect_properties_.requests_node_,
inspect_properties_.requests_node_.UniqueName(kInspectRequestNodeNamePrefix));
TryCreateNextConnection();
return true;
}
std::optional<BrEdrConnectionManager::CreateConnectionParams>
BrEdrConnectionManager::NextCreateConnectionParams() {
for (auto& [peer_id, request] : connection_requests_) {
// The peer should still be in PeerCache because it was marked "initializing" when the
// connection was requested.
const Peer* peer = cache_->FindById(peer_id);
ZX_ASSERT(peer);
if (peer->bredr() && !request.HasIncoming()) {
return std::optional(CreateConnectionParams{peer->identifier(), request.address(),
peer->bredr()->clock_offset(),
peer->bredr()->page_scan_repetition_mode()});
}
}
bt_log(TRACE, "gap-bredr", "no pending outbound connection requests remaining");
return std::nullopt;
}
void BrEdrConnectionManager::TryCreateNextConnection() {
// There can only be one outstanding BrEdr CreateConnection request at a time
if (pending_request_) {
return;
}
auto next = NextCreateConnectionParams();
if (next) {
InitiatePendingConnection(*next);
}
}
void BrEdrConnectionManager::InitiatePendingConnection(CreateConnectionParams params) {
auto self = weak_ptr_factory_.GetWeakPtr();
auto on_failure = [self, addr = params.addr](hci::Result<> status, auto peer_id) {
if (self && status.is_error())
self->CompleteRequest(peer_id, addr, status, /*handle=*/0);
};
auto on_timeout = [self] {
if (self)
self->OnRequestTimeout();
};
BrEdrConnectionRequest& pending_gap_req = connection_requests_.find(params.peer_id)->second;
pending_request_.emplace(params.peer_id, params.addr, on_timeout);
pending_request_->CreateConnection(hci_->command_channel(), dispatcher_, params.clock_offset,
params.page_scan_repetition_mode, request_timeout_,
on_failure);
pending_gap_req.RecordHciCreateConnectionAttempt();
inspect_properties_.outgoing_.connection_attempts_.Add(1);
}
void BrEdrConnectionManager::OnRequestTimeout() {
if (pending_request_) {
pending_request_->Timeout();
SendCreateConnectionCancelCommand(pending_request_->peer_address());
}
}
void BrEdrConnectionManager::SendCreateConnectionCancelCommand(DeviceAddress addr) {
auto cancel = hci::CommandPacket::New(hci_spec::kCreateConnectionCancel,
sizeof(hci_spec::CreateConnectionCancelCommandParams));
auto params = cancel->mutable_payload<hci_spec::CreateConnectionCancelCommandParams>();
params->bd_addr = addr.value();
hci_->command_channel()->SendCommand(std::move(cancel), [](auto, const hci::EventPacket& event) {
hci_is_error(event, WARN, "hci-bredr", "failed to cancel connection request");
});
}
void BrEdrConnectionManager::SendAuthenticationRequested(hci_spec::ConnectionHandle handle,
hci::ResultFunction<> cb) {
auto auth_request = hci::CommandPacket::New(
hci_spec::kAuthenticationRequested, sizeof(hci_spec::AuthenticationRequestedCommandParams));
auth_request->mutable_payload<hci_spec::AuthenticationRequestedCommandParams>()
->connection_handle = htole16(handle);
// Complete on command status because Authentication Complete Event is already registered.
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
hci_->command_channel()->SendCommand(std::move(auth_request), std::move(command_cb),
hci_spec::kCommandStatusEventCode);
}
void BrEdrConnectionManager::SendIoCapabilityRequestReply(
DeviceAddressBytes bd_addr, hci_spec::IOCapability io_capability, uint8_t oob_data_present,
hci_spec::AuthRequirements auth_requirements, hci::ResultFunction<> cb) {
auto packet = hci::CommandPacket::New(hci_spec::kIOCapabilityRequestReply,
sizeof(hci_spec::IOCapabilityRequestReplyCommandParams));
auto params = packet->mutable_payload<hci_spec::IOCapabilityRequestReplyCommandParams>();
params->bd_addr = bd_addr;
params->io_capability = io_capability;
params->oob_data_present = oob_data_present;
params->auth_requirements = auth_requirements;
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendIoCapabilityRequestNegativeReply(DeviceAddressBytes bd_addr,
hci_spec::StatusCode reason,
hci::ResultFunction<> cb) {
auto packet =
hci::CommandPacket::New(hci_spec::kIOCapabilityRequestNegativeReply,
sizeof(hci_spec::IOCapabilityRequestNegativeReplyCommandParams));
auto params = packet->mutable_payload<hci_spec::IOCapabilityRequestNegativeReplyCommandParams>();
params->bd_addr = bd_addr;
params->reason = reason;
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendUserConfirmationRequestReply(DeviceAddressBytes bd_addr,
hci::ResultFunction<> cb) {
auto packet =
hci::CommandPacket::New(hci_spec::kUserConfirmationRequestReply,
sizeof(hci_spec::UserConfirmationRequestReplyCommandParams));
packet->mutable_payload<hci_spec::UserConfirmationRequestReplyCommandParams>()->bd_addr = bd_addr;
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendUserConfirmationRequestNegativeReply(DeviceAddressBytes bd_addr,
hci::ResultFunction<> cb) {
auto packet =
hci::CommandPacket::New(hci_spec::kUserConfirmationRequestNegativeReply,
sizeof(hci_spec::UserConfirmationRequestNegativeReplyCommandParams));
packet->mutable_payload<hci_spec::UserConfirmationRequestNegativeReplyCommandParams>()->bd_addr =
bd_addr;
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendUserPasskeyRequestReply(DeviceAddressBytes bd_addr,
uint32_t numeric_value,
hci::ResultFunction<> cb) {
auto packet = hci::CommandPacket::New(hci_spec::kUserPasskeyRequestReply,
sizeof(hci_spec::UserPasskeyRequestReplyCommandParams));
auto params = packet->mutable_payload<hci_spec::UserPasskeyRequestReplyCommandParams>();
params->bd_addr = bd_addr;
params->numeric_value = htole32(numeric_value);
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendUserPasskeyRequestNegativeReply(DeviceAddressBytes bd_addr,
hci::ResultFunction<> cb) {
auto packet =
hci::CommandPacket::New(hci_spec::kUserPasskeyRequestNegativeReply,
sizeof(hci_spec::UserPasskeyRequestNegativeReplyCommandParams));
packet->mutable_payload<hci_spec::UserPasskeyRequestNegativeReplyCommandParams>()->bd_addr =
bd_addr;
SendCommandWithStatusCallback(std::move(packet), std::move(cb));
}
void BrEdrConnectionManager::SendLinkKeyRequestNegativeReply(DeviceAddressBytes bd_addr,
hci::ResultFunction<> cb) {
auto negative_reply =
hci::CommandPacket::New(hci_spec::kLinkKeyRequestNegativeReply,
sizeof(hci_spec::LinkKeyRequestNegativeReplyCommandParams));
auto negative_reply_params =
negative_reply->mutable_payload<hci_spec::LinkKeyRequestNegativeReplyCommandParams>();
negative_reply_params->bd_addr = bd_addr;
SendCommandWithStatusCallback(std::move(negative_reply), std::move(cb));
}
void BrEdrConnectionManager::SendLinkKeyRequestReply(DeviceAddressBytes bd_addr,
hci_spec::LinkKey link_key,
hci::ResultFunction<> cb) {
auto reply = hci::CommandPacket::New(hci_spec::kLinkKeyRequestReply,
sizeof(hci_spec::LinkKeyRequestReplyCommandParams));
auto reply_params = reply->mutable_payload<hci_spec::LinkKeyRequestReplyCommandParams>();
reply_params->bd_addr = bd_addr;
const auto& key_value = link_key.value();
std::copy(key_value.begin(), key_value.end(), reply_params->link_key);
SendCommandWithStatusCallback(std::move(reply), std::move(cb));
}
void BrEdrConnectionManager::SendCommandWithStatusCallback(
std::unique_ptr<hci::CommandPacket> command_packet, hci::ResultFunction<> cb) {
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
hci_->command_channel()->SendCommand(std::move(command_packet), std::move(command_cb));
}
void BrEdrConnectionManager::SendAcceptConnectionRequest(DeviceAddressBytes addr,
hci::ResultFunction<> cb) {
auto accept = hci::CommandPacket::New(hci_spec::kAcceptConnectionRequest,
sizeof(hci_spec::AcceptConnectionRequestCommandParams));
auto accept_params = accept->mutable_payload<hci_spec::AcceptConnectionRequestCommandParams>();
accept_params->bd_addr = addr;
// This role switch preference can fail. A HCI_Role_Change event will be generated if the role
// switch is successful (Core Spec v5.2, Vol 2, Part F, Sec 3.1).
accept_params->role = hci_spec::ConnectionRole::kCentral;
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
hci_->command_channel()->SendCommand(std::move(accept), std::move(command_cb),
hci_spec::kCommandStatusEventCode);
}
void BrEdrConnectionManager::SendRejectConnectionRequest(DeviceAddress addr,
hci_spec::StatusCode reason,
hci::ResultFunction<> cb) {
auto reject = hci::CommandPacket::New(hci_spec::kRejectConnectionRequest,
sizeof(hci_spec::RejectConnectionRequestCommandParams));
auto reject_params = reject->mutable_payload<hci_spec::RejectConnectionRequestCommandParams>();
reject_params->bd_addr = addr.value();
reject_params->reason = reason;
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
hci_->command_channel()->SendCommand(std::move(reject), std::move(command_cb),
hci_spec::kCommandStatusEventCode);
}
void BrEdrConnectionManager::SendRejectSynchronousRequest(DeviceAddress addr,
hci_spec::StatusCode reason,
hci::ResultFunction<> cb) {
auto reject =
hci::CommandPacket::New(hci_spec::kRejectSynchronousConnectionRequest,
sizeof(hci_spec::RejectSynchronousConnectionRequestCommandParams));
auto reject_params =
reject->mutable_payload<hci_spec::RejectSynchronousConnectionRequestCommandParams>();
reject_params->bd_addr = addr.value();
reject_params->reason = reason;
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
hci_->command_channel()->SendCommand(std::move(reject), std::move(command_cb),
hci_spec::kCommandStatusEventCode);
}
void BrEdrConnectionManager::RecordDisconnectInspect(const BrEdrConnection& conn,
DisconnectReason reason) {
// Add item to recent disconnections list.
auto& inspect_item = inspect_properties_.last_disconnected_list.CreateItem();
inspect_item.node.RecordString(kInspectLastDisconnectedItemPeerPropertyName,
conn.peer_id().ToString());
inspect_item.node.RecordUint(kInspectLastDisconnectedItemDurationPropertyName,
conn.duration().to_secs());
inspect_item.node.RecordInt(kInspectTimestampPropertyName, async::Now(dispatcher_).get());
switch (reason) {
case DisconnectReason::kApiRequest:
inspect_properties_.disconnect_local_api_request_count_.Add(1);
break;
case DisconnectReason::kInterrogationFailed:
inspect_properties_.disconnect_interrogation_failed_count_.Add(1);
break;
case DisconnectReason::kPairingFailed:
inspect_properties_.disconnect_pairing_failed_count_.Add(1);
break;
case DisconnectReason::kAclLinkError:
inspect_properties_.disconnect_acl_link_error_count_.Add(1);
break;
case DisconnectReason::kPeerDisconnection:
inspect_properties_.disconnect_peer_disconnection_count_.Add(1);
break;
default:
break;
}
}
} // namespace bt::gap