blob: ff1720812acb15b5b0ad4ee8dbf8ddd325912f14 [file] [log] [blame]
// Copyright 2017 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
#include <optional>
#include <vector>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/gap.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/generic_access_client.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_connection.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/pairing_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/local_service_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci/local_address_delegate.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/channel_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/security_manager.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/util.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h"
#include "src/connectivity/bluetooth/lib/cpp-string/string_printf.h"
#pragma clang diagnostic ignored "-Wswitch-enum"
using bt::sm::BondableMode;
namespace bt::gap {
namespace {
// If an auto-connect attempt fails with any of the following error codes, we
// will stop auto- connecting to the peer until the next successful connection.
// We have only observed this issue with the 0x3e
// "kConnectionFailedToBeEstablished" error in the field, but have included
// these other errors based on their descriptions in v5.2 Vol. 1 Part F
// Section 2.
bool ShouldStopAlwaysAutoConnecting(pw::bluetooth::emboss::StatusCode err) {
switch (err) {
case pw::bluetooth::emboss::StatusCode::CONNECTION_TIMEOUT:
case pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_SECURITY:
case pw::bluetooth::emboss::StatusCode::CONNECTION_ACCEPT_TIMEOUT_EXCEEDED:
case pw::bluetooth::emboss::StatusCode::CONNECTION_TERMINATED_BY_LOCAL_HOST:
case pw::bluetooth::emboss::StatusCode::CONNECTION_FAILED_TO_BE_ESTABLISHED:
return true;
default:
return false;
}
}
// During the initial connection to a peripheral we use the initial high
// duty-cycle parameters to ensure that initiating procedures (bonding,
// encryption setup, service discovery) are completed quickly. Once these
// procedures are complete, we will change the connection interval to the
// peripheral's preferred connection parameters (see v5.0, Vol 3, Part C,
// Section 9.3.12).
static const hci_spec::LEPreferredConnectionParameters
kInitialConnectionParameters(kLEInitialConnIntervalMin,
kLEInitialConnIntervalMax,
/*max_latency=*/0,
hci_spec::defaults::kLESupervisionTimeout);
const char* kInspectRequestsNodeName = "pending_requests";
const char* kInspectRequestNodeNamePrefix = "pending_request_";
const char* kInspectConnectionsNodeName = "connections";
const char* kInspectConnectionNodePrefix = "connection_";
const char* kInspectOutboundConnectorNodeName = "outbound_connector";
const char* kInspectConnectionFailuresPropertyName =
"recent_connection_failures";
const char* kInspectOutgoingSuccessCountNodeName =
"outgoing_connection_success_count";
const char* kInspectOutgoingFailureCountNodeName =
"outgoing_connection_failure_count";
const char* kInspectIncomingSuccessCountNodeName =
"incoming_connection_success_count";
const char* kInspectIncomingFailureCountNodeName =
"incoming_connection_failure_count";
const char* kInspectDisconnectExplicitDisconnectNodeName =
"disconnect_explicit_disconnect_count";
const char* kInspectDisconnectLinkErrorNodeName = "disconnect_link_error_count";
const char* kInspectDisconnectZeroRefNodeName = "disconnect_zero_ref_count";
const char* kInspectDisconnectRemoteDisconnectionNodeName =
"disconnect_remote_disconnection_count";
} // namespace
LowEnergyConnectionManager::LowEnergyConnectionManager(
hci::CommandChannel::WeakPtr cmd_channel,
hci::LocalAddressDelegate* addr_delegate,
hci::LowEnergyConnector* connector,
PeerCache* peer_cache,
l2cap::ChannelManager* l2cap,
gatt::GATT::WeakPtr gatt,
LowEnergyDiscoveryManager::WeakPtr discovery_manager,
sm::SecurityManagerFactory sm_creator,
const AdapterState& adapter_state,
pw::async::Dispatcher& dispatcher)
: dispatcher_(dispatcher),
cmd_(std::move(cmd_channel)),
security_mode_(LESecurityMode::Mode1),
sm_factory_func_(std::move(sm_creator)),
request_timeout_(kLECreateConnectionTimeout),
peer_cache_(peer_cache),
l2cap_(l2cap),
gatt_(gatt),
adapter_state_(adapter_state),
discovery_manager_(discovery_manager),
hci_connector_(connector),
local_address_delegate_(addr_delegate),
weak_self_(this) {
BT_DEBUG_ASSERT(peer_cache_);
BT_DEBUG_ASSERT(l2cap_);
BT_DEBUG_ASSERT(gatt_.is_alive());
BT_DEBUG_ASSERT(cmd_.is_alive());
BT_DEBUG_ASSERT(hci_connector_);
BT_DEBUG_ASSERT(local_address_delegate_);
}
LowEnergyConnectionManager::~LowEnergyConnectionManager() {
bt_log(INFO, "gap-le", "LowEnergyConnectionManager shutting down");
weak_self_.InvalidatePtrs();
// Clear |pending_requests_| and notify failure.
for (auto& iter : pending_requests_) {
iter.second.NotifyCallbacks(fit::error(HostError::kFailed));
}
pending_requests_.clear();
current_request_.reset();
remote_connectors_.clear();
// Clean up all connections.
for (auto& iter : connections_) {
CleanUpConnection(std::move(iter.second));
}
connections_.clear();
}
void LowEnergyConnectionManager::Connect(
PeerId peer_id,
ConnectionResultCallback callback,
LowEnergyConnectionOptions connection_options) {
Peer* peer = peer_cache_->FindById(peer_id);
if (!peer) {
bt_log(WARN, "gap-le", "peer not found (id: %s)", bt_str(peer_id));
callback(fit::error(HostError::kNotFound));
return;
}
if (peer->technology() == TechnologyType::kClassic) {
bt_log(ERROR,
"gap-le",
"peer does not support LE: %s",
peer->ToString().c_str());
callback(fit::error(HostError::kNotFound));
return;
}
if (!peer->connectable()) {
bt_log(
ERROR, "gap-le", "peer not connectable: %s", peer->ToString().c_str());
callback(fit::error(HostError::kNotFound));
return;
}
// If we are already waiting to connect to |peer_id| then we store
// |callback| to be processed after the connection attempt completes (in
// either success of failure).
auto pending_iter = pending_requests_.find(peer_id);
if (pending_iter != pending_requests_.end()) {
if (!current_request_) {
bt_log(WARN,
"gap-le",
"Connect called for peer with pending request while no "
"current_request_ exists (peer: "
"%s)",
bt_str(peer_id));
}
// TODO(https://fxbug.dev/42144310): Merge connection_options with the
// options of the pending request.
pending_iter->second.AddCallback(std::move(callback));
// TODO(https://fxbug.dev/42148775): Try to create this connection.
return;
}
// Add callback to connecting request if |peer_id| matches.
if (current_request_ && current_request_->request.peer_id() == peer_id) {
// TODO(https://fxbug.dev/42144310): Merge connection_options with the
// options of the current request.
current_request_->request.AddCallback(std::move(callback));
return;
}
auto conn_iter = connections_.find(peer_id);
if (conn_iter != connections_.end()) {
// TODO(https://fxbug.dev/42144310): Handle connection_options that conflict
// with the existing connection.
callback(fit::ok(conn_iter->second->AddRef()));
return;
}
internal::LowEnergyConnectionRequest request(
peer_id,
std::move(callback),
connection_options,
peer->MutLe().RegisterInitializingConnection());
request.AttachInspect(
inspect_pending_requests_node_,
inspect_pending_requests_node_.UniqueName(kInspectRequestNodeNamePrefix));
pending_requests_.emplace(peer_id, std::move(request));
TryCreateNextConnection();
}
bool LowEnergyConnectionManager::Disconnect(PeerId peer_id,
LowEnergyDisconnectReason reason) {
auto remote_connector_iter = remote_connectors_.find(peer_id);
if (remote_connector_iter != remote_connectors_.end()) {
// Result callback will clean up connector.
remote_connector_iter->second.connector->Cancel();
}
auto request_iter = pending_requests_.find(peer_id);
if (request_iter != pending_requests_.end()) {
BT_ASSERT(current_request_->request.peer_id() != peer_id);
request_iter->second.NotifyCallbacks(fit::error(HostError::kCanceled));
pending_requests_.erase(request_iter);
}
if (current_request_ && current_request_->request.peer_id() == peer_id) {
// Connector will call result callback to clean up connection.
current_request_->connector->Cancel();
}
// Ignore Disconnect for peer that is not pending or connected:
auto iter = connections_.find(peer_id);
if (iter == connections_.end()) {
bt_log(INFO,
"gap-le",
"Disconnect called for unconnected peer (peer: %s)",
bt_str(peer_id));
return true;
}
// Handle peer that is already connected:
// Remove the connection state from the internal map right away.
auto conn = std::move(iter->second);
connections_.erase(iter);
// Since this was an intentional disconnect, update the auto-connection
// behavior appropriately.
peer_cache_->SetAutoConnectBehaviorForIntentionalDisconnect(peer_id);
bt_log(INFO,
"gap-le",
"disconnecting (peer: %s, link: %s)",
bt_str(conn->peer_id()),
bt_str(*conn->link()));
if (reason == LowEnergyDisconnectReason::kApiRequest) {
inspect_properties_.disconnect_explicit_disconnect_count_.Add(1);
} else {
inspect_properties_.disconnect_link_error_count_.Add(1);
}
CleanUpConnection(std::move(conn));
return true;
}
void LowEnergyConnectionManager::Pair(PeerId peer_id,
sm::SecurityLevel pairing_level,
sm::BondableMode bondable_mode,
sm::ResultFunction<> cb) {
auto iter = connections_.find(peer_id);
if (iter == connections_.end()) {
bt_log(WARN,
"gap-le",
"cannot pair: peer not connected (peer: %s)",
bt_str(peer_id));
cb(bt::ToResult(bt::HostError::kNotFound));
return;
}
bt_log(INFO,
"gap-le",
"pairing with security level: %d (peer: %s)",
static_cast<int>(pairing_level),
bt_str(peer_id));
iter->second->UpgradeSecurity(pairing_level, bondable_mode, std::move(cb));
}
void LowEnergyConnectionManager::SetSecurityMode(LESecurityMode mode) {
security_mode_ = mode;
if (mode == LESecurityMode::SecureConnectionsOnly) {
// `Disconnect`ing the peer must not be done while iterating through
// `connections_` as it removes the connection from `connections_`, hence
// the helper vector.
std::vector<PeerId> insufficiently_secure_peers;
for (auto& [peer_id, connection] : connections_) {
if (connection->security().level() !=
sm::SecurityLevel::kSecureAuthenticated &&
connection->security().level() != sm::SecurityLevel::kNoSecurity) {
insufficiently_secure_peers.push_back(peer_id);
}
}
for (PeerId id : insufficiently_secure_peers) {
Disconnect(id);
}
}
for (auto& iter : connections_) {
iter.second->set_security_mode(mode);
}
}
void LowEnergyConnectionManager::AttachInspect(inspect::Node& parent,
std::string name) {
inspect_node_ = parent.CreateChild(name);
inspect_properties_.recent_connection_failures.AttachInspect(
inspect_node_, kInspectConnectionFailuresPropertyName);
inspect_pending_requests_node_ =
inspect_node_.CreateChild(kInspectRequestsNodeName);
inspect_connections_node_ =
inspect_node_.CreateChild(kInspectConnectionsNodeName);
for (auto& request : pending_requests_) {
request.second.AttachInspect(inspect_pending_requests_node_,
inspect_pending_requests_node_.UniqueName(
kInspectRequestNodeNamePrefix));
}
for (auto& conn : connections_) {
conn.second->AttachInspect(
inspect_connections_node_,
inspect_connections_node_.UniqueName(kInspectConnectionNodePrefix));
}
if (current_request_) {
current_request_->connector->AttachInspect(
inspect_node_, kInspectOutboundConnectorNodeName);
}
inspect_properties_.outgoing_connection_success_count_.AttachInspect(
inspect_node_, kInspectOutgoingSuccessCountNodeName);
inspect_properties_.outgoing_connection_failure_count_.AttachInspect(
inspect_node_, kInspectOutgoingFailureCountNodeName);
inspect_properties_.incoming_connection_success_count_.AttachInspect(
inspect_node_, kInspectIncomingSuccessCountNodeName);
inspect_properties_.incoming_connection_failure_count_.AttachInspect(
inspect_node_, kInspectIncomingFailureCountNodeName);
inspect_properties_.disconnect_explicit_disconnect_count_.AttachInspect(
inspect_node_, kInspectDisconnectExplicitDisconnectNodeName);
inspect_properties_.disconnect_link_error_count_.AttachInspect(
inspect_node_, kInspectDisconnectLinkErrorNodeName);
inspect_properties_.disconnect_zero_ref_count_.AttachInspect(
inspect_node_, kInspectDisconnectZeroRefNodeName);
inspect_properties_.disconnect_remote_disconnection_count_.AttachInspect(
inspect_node_, kInspectDisconnectRemoteDisconnectionNodeName);
}
void LowEnergyConnectionManager::RegisterRemoteInitiatedLink(
std::unique_ptr<hci::LowEnergyConnection> link,
sm::BondableMode bondable_mode,
ConnectionResultCallback callback) {
BT_ASSERT(link);
Peer* peer = UpdatePeerWithLink(*link);
auto peer_id = peer->identifier();
bt_log(INFO,
"gap-le",
"new remote-initiated link (peer: %s, local addr: %s, link: %s)",
bt_str(peer_id),
bt_str(link->local_address()),
bt_str(*link));
// TODO(https://fxbug.dev/42143994): Use own address when storing the
// connection. Currently this will refuse the connection and disconnect the
// link if |peer| is already connected to us by a different local address.
if (connections_.find(peer_id) != connections_.end()) {
bt_log(INFO,
"gap-le",
"multiple links from peer; remote-initiated connection refused "
"(peer: %s)",
bt_str(peer_id));
callback(fit::error(HostError::kFailed));
return;
}
if (remote_connectors_.find(peer_id) != remote_connectors_.end()) {
bt_log(INFO,
"gap-le",
"remote connector for peer already exists; connection refused "
"(peer: %s)",
bt_str(peer_id));
callback(fit::error(HostError::kFailed));
return;
}
LowEnergyConnectionOptions connection_options{.bondable_mode = bondable_mode};
internal::LowEnergyConnectionRequest request(
peer_id,
std::move(callback),
connection_options,
peer->MutLe().RegisterInitializingConnection());
std::unique_ptr<internal::LowEnergyConnector> connector =
std::make_unique<internal::LowEnergyConnector>(peer_id,
connection_options,
cmd_,
peer_cache_,
weak_self_.GetWeakPtr(),
l2cap_,
gatt_,
adapter_state_,
dispatcher_);
auto [conn_iter, _] = remote_connectors_.emplace(
peer_id, RequestAndConnector{std::move(request), std::move(connector)});
// Wait until the connector is in the map to start in case the result callback
// is called synchronously.
auto result_cb =
std::bind(&LowEnergyConnectionManager::OnRemoteInitiatedConnectResult,
this,
peer_id,
std::placeholders::_1);
conn_iter->second.connector->StartInbound(std::move(link),
std::move(result_cb));
}
void LowEnergyConnectionManager::SetPairingDelegate(
const PairingDelegate::WeakPtr& delegate) {
// TODO(https://fxbug.dev/42169848): Add a test case for this once bug is
// resolved.
pairing_delegate_ = delegate;
// Tell existing connections to abort ongoing pairing procedures. The new
// delegate will receive calls to PairingDelegate::CompletePairing, unless it
// is null.
for (auto& iter : connections_) {
iter.second->ResetSecurityManager(delegate.is_alive()
? delegate->io_capability()
: sm::IOCapability::kNoInputNoOutput);
}
}
void LowEnergyConnectionManager::SetDisconnectCallbackForTesting(
DisconnectCallback callback) {
test_disconn_cb_ = std::move(callback);
}
void LowEnergyConnectionManager::ReleaseReference(
LowEnergyConnectionHandle* handle) {
BT_ASSERT(handle);
auto iter = connections_.find(handle->peer_identifier());
BT_ASSERT(iter != connections_.end());
iter->second->DropRef(handle);
if (iter->second->ref_count() != 0u)
return;
// Move the connection object before erasing the entry.
auto conn = std::move(iter->second);
connections_.erase(iter);
bt_log(INFO,
"gap-le",
"all refs dropped on connection (link: %s, peer: %s)",
bt_str(*conn->link()),
bt_str(conn->peer_id()));
inspect_properties_.disconnect_zero_ref_count_.Add(1);
CleanUpConnection(std::move(conn));
}
void LowEnergyConnectionManager::TryCreateNextConnection() {
if (current_request_.has_value()) {
bt_log(DEBUG, "gap-le", "%s: request already in progress", __FUNCTION__);
return;
}
if (pending_requests_.empty()) {
bt_log(TRACE, "gap-le", "%s: no pending requests remaining", __FUNCTION__);
return;
}
for (auto& iter : pending_requests_) {
auto peer_id = iter.first;
Peer* peer = peer_cache_->FindById(peer_id);
if (peer) {
auto request_pair = pending_requests_.extract(peer_id);
internal::LowEnergyConnectionRequest request =
std::move(request_pair.mapped());
std::unique_ptr<internal::LowEnergyConnector> connector =
std::make_unique<internal::LowEnergyConnector>(
peer_id,
request.connection_options(),
cmd_,
peer_cache_,
weak_self_.GetWeakPtr(),
l2cap_,
gatt_,
adapter_state_,
dispatcher_);
connector->AttachInspect(inspect_node_,
kInspectOutboundConnectorNodeName);
current_request_ =
RequestAndConnector{std::move(request), std::move(connector)};
// Wait until the connector is in current_request_ to start in case the
// result callback is called synchronously.
current_request_->connector->StartOutbound(
request_timeout_,
hci_connector_,
discovery_manager_,
fit::bind_member<
&LowEnergyConnectionManager::OnLocalInitiatedConnectResult>(
this));
return;
}
bt_log(WARN,
"gap-le",
"deferring connection attempt (peer: %s)",
bt_str(peer_id));
// TODO(https://fxbug.dev/42172291): For now the requests for this peer
// won't complete until the next peer discovery. This will no longer be an
// issue when we use background scanning.
}
}
void LowEnergyConnectionManager::OnLocalInitiatedConnectResult(
hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
BT_ASSERT(current_request_.has_value());
internal::LowEnergyConnectionRequest request =
std::move(current_request_->request);
current_request_.reset();
if (result.is_error()) {
inspect_properties_.outgoing_connection_failure_count_.Add(1);
bt_log(INFO,
"gap-le",
"failed to connect to peer (peer: %s, status: %s)",
bt_str(request.peer_id()),
bt_str(result));
} else {
inspect_properties_.outgoing_connection_success_count_.Add(1);
bt_log(INFO,
"gap-le",
"connection request successful (peer: %s)",
bt_str(request.peer_id()));
}
ProcessConnectResult(std::move(result), std::move(request));
TryCreateNextConnection();
}
void LowEnergyConnectionManager::OnRemoteInitiatedConnectResult(
PeerId peer_id,
hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result) {
auto remote_connector_node = remote_connectors_.extract(peer_id);
BT_ASSERT(!remote_connector_node.empty());
internal::LowEnergyConnectionRequest request =
std::move(remote_connector_node.mapped().request);
if (result.is_error()) {
inspect_properties_.incoming_connection_failure_count_.Add(1);
bt_log(INFO,
"gap-le",
"failed to complete remote initated connection with peer (peer: %s, "
"status: %s)",
bt_str(peer_id),
bt_str(result));
} else {
inspect_properties_.incoming_connection_success_count_.Add(1);
bt_log(INFO,
"gap-le",
"remote initiated connection successful (peer: %s)",
bt_str(peer_id));
}
ProcessConnectResult(std::move(result), std::move(request));
}
void LowEnergyConnectionManager::ProcessConnectResult(
hci::Result<std::unique_ptr<internal::LowEnergyConnection>> result,
internal::LowEnergyConnectionRequest request) {
PeerId peer_id = request.peer_id();
if (result.is_error()) {
const hci::Error err = result.error_value();
Peer* const peer = peer_cache_->FindById(peer_id);
// Peer may have been forgotten (causing this error).
// A separate connection may have been established in the other direction
// while this connection was connecting, in which case the peer state should
// not be updated.
if (peer && connections_.find(peer->identifier()) == connections_.end()) {
if (request.connection_options().auto_connect &&
err.is_protocol_error() &&
ShouldStopAlwaysAutoConnecting(err.protocol_error())) {
// We may see a peer's connectable advertisements, but fail to establish
// a connection to the peer (e.g. due to asymmetrical radio TX power).
// Unsetting the AutoConnect flag here prevents a loop of "see peer
// device, attempt auto-connect, fail to establish connection".
peer->MutLe().set_auto_connect_behavior(
Peer::AutoConnectBehavior::kSkipUntilNextConnection);
}
}
const HostError host_error =
err.is_host_error() ? err.host_error() : HostError::kFailed;
request.NotifyCallbacks(fit::error(host_error));
inspect_properties_.recent_connection_failures.Add(1);
return;
}
InitializeConnection(std::move(result).value(), std::move(request));
}
bool LowEnergyConnectionManager::InitializeConnection(
std::unique_ptr<internal::LowEnergyConnection> connection,
internal::LowEnergyConnectionRequest request) {
BT_ASSERT(connection);
auto peer_id = connection->peer_id();
// TODO(https://fxbug.dev/42143994): For now reject having more than one link
// with the same peer. This should change once this has more context on the
// local destination for remote initiated connections.
if (connections_.find(peer_id) != connections_.end()) {
bt_log(INFO,
"gap-le",
"cannot initialize multiple links to same peer; connection refused "
"(peer: %s)",
bt_str(peer_id));
// Notify request that duplicate connection could not be initialized.
request.NotifyCallbacks(fit::error(HostError::kFailed));
// Do not update peer state, as there is another active LE connection in
// connections_ for this peer.
return false;
}
Peer* peer = peer_cache_->FindById(peer_id);
BT_ASSERT(peer);
connection->AttachInspect(
inspect_connections_node_,
inspect_connections_node_.UniqueName(kInspectConnectionNodePrefix));
connection->set_peer_disconnect_callback(
std::bind(&LowEnergyConnectionManager::OnPeerDisconnect,
this,
connection->link(),
std::placeholders::_1));
connection->set_error_callback([this, peer_id]() {
Disconnect(peer_id, LowEnergyDisconnectReason::kError);
});
auto [conn_iter, inserted] =
connections_.try_emplace(peer_id, std::move(connection));
BT_ASSERT(inserted);
conn_iter->second->set_peer_conn_token(peer->MutLe().RegisterConnection());
// Create first ref to ensure that connection is cleaned up on early returns
// or if first request callback does not retain a ref.
auto first_ref = conn_iter->second->AddRef();
UpdatePeerWithLink(*conn_iter->second->link());
bt_log(TRACE,
"gap-le",
"notifying connection request callbacks (peer: %s)",
bt_str(peer_id));
request.NotifyCallbacks(fit::ok(std::bind(
&internal::LowEnergyConnection::AddRef, conn_iter->second.get())));
return true;
}
void LowEnergyConnectionManager::CleanUpConnection(
std::unique_ptr<internal::LowEnergyConnection> conn) {
BT_ASSERT(conn);
// Mark the peer peer as no longer connected.
Peer* peer = peer_cache_->FindById(conn->peer_id());
BT_ASSERT_MSG(peer,
"A connection was active for an unknown peer! (id: %s)",
bt_str(conn->peer_id()));
conn.reset();
}
Peer* LowEnergyConnectionManager::UpdatePeerWithLink(
const hci::LowEnergyConnection& link) {
Peer* peer = peer_cache_->FindByAddress(link.peer_address());
if (!peer) {
peer = peer_cache_->NewPeer(link.peer_address(), /*connectable=*/true);
}
peer->MutLe().SetConnectionParameters(link.low_energy_parameters());
peer_cache_->SetAutoConnectBehaviorForSuccessfulConnection(
peer->identifier());
return peer;
}
void LowEnergyConnectionManager::OnPeerDisconnect(
const hci::Connection* connection,
pw::bluetooth::emboss::StatusCode reason) {
auto handle = connection->handle();
if (test_disconn_cb_) {
test_disconn_cb_(handle);
}
// See if we can find a connection with a matching handle by walking the
// connections list.
auto iter = FindConnection(handle);
if (iter == connections_.end()) {
bt_log(WARN,
"gap-le",
"disconnect from unknown connection handle: %#.4x",
handle);
return;
}
// Found the connection. Remove the entry from |connections_| before notifying
// the "closed" handlers.
auto conn = std::move(iter->second);
connections_.erase(iter);
bt_log(INFO,
"gap-le",
"peer disconnected (peer: %s, handle: %#.4x)",
bt_str(conn->peer_id()),
handle);
inspect_properties_.disconnect_remote_disconnection_count_.Add(1);
CleanUpConnection(std::move(conn));
}
LowEnergyConnectionManager::ConnectionMap::iterator
LowEnergyConnectionManager::FindConnection(hci_spec::ConnectionHandle handle) {
auto iter = connections_.begin();
for (; iter != connections_.end(); ++iter) {
const auto& conn = *iter->second;
if (conn.handle() == handle)
break;
}
return iter;
}
} // namespace bt::gap