blob: 64b8e459f6a1c5db69a0921e495c4b2810ea7d05 [file] [log] [blame]
// Copyright 2020 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.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
namespace bt::gap::internal {
namespace {
constexpr const char* kInspectPeerIdPropertyName = "peer_id";
constexpr const char* kInspectPeerAddressPropertyName = "peer_address";
constexpr const char* kInspectRefsPropertyName = "ref_count";
// Connection parameters to use when the peer's preferred connection parameters
// are not known.
static const hci_spec::LEPreferredConnectionParameters
kDefaultPreferredConnectionParameters(
hci_spec::defaults::kLEConnectionIntervalMin,
hci_spec::defaults::kLEConnectionIntervalMax,
/*max_latency=*/0,
hci_spec::defaults::kLESupervisionTimeout);
} // namespace
std::unique_ptr<LowEnergyConnection> LowEnergyConnection::Create(
Peer::WeakPtr peer,
std::unique_ptr<hci::LowEnergyConnection> link,
LowEnergyConnectionOptions connection_options,
PeerDisconnectCallback peer_disconnect_cb,
ErrorCallback error_cb,
WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
l2cap::ChannelManager* l2cap,
gatt::GATT::WeakPtr gatt,
hci::CommandChannel::WeakPtr cmd_channel,
pw::async::Dispatcher& dispatcher) {
// Catch any errors/disconnects during connection initialization so that they
// are reported by returning a nullptr. This is less error-prone than calling
// the user's callbacks during initialization.
bool error = false;
auto peer_disconnect_cb_temp = [&error](auto) { error = true; };
auto error_cb_temp = [&error] { error = true; };
// TODO(https://fxbug.dev/325646523): Only create an IsoStreamManager instance
// if our adapter supports Isochronous streams.
std::unique_ptr<iso::IsoStreamManager> iso_mgr =
std::make_unique<iso::IsoStreamManager>(link->handle(), cmd_channel);
std::unique_ptr<LowEnergyConnection> connection(
new LowEnergyConnection(std::move(peer),
std::move(link),
connection_options,
std::move(peer_disconnect_cb_temp),
std::move(error_cb_temp),
std::move(conn_mgr),
std::move(iso_mgr),
l2cap,
std::move(gatt),
std::move(cmd_channel),
dispatcher));
// This looks strange, but it is possible for InitializeFixedChannels() to
// trigger an error and still return true, so |error| can change between the
// first and last check.
if (error || !connection->InitializeFixedChannels() || error) {
return nullptr;
}
// Now it is safe to set the user's callbacks, as no more errors/disconnects
// can be signaled before returning.
connection->set_peer_disconnect_callback(std::move(peer_disconnect_cb));
connection->set_error_callback(std::move(error_cb));
return connection;
}
LowEnergyConnection::LowEnergyConnection(
Peer::WeakPtr peer,
std::unique_ptr<hci::LowEnergyConnection> link,
LowEnergyConnectionOptions connection_options,
PeerDisconnectCallback peer_disconnect_cb,
ErrorCallback error_cb,
WeakSelf<LowEnergyConnectionManager>::WeakPtr conn_mgr,
std::unique_ptr<iso::IsoStreamManager> iso_mgr,
l2cap::ChannelManager* l2cap,
gatt::GATT::WeakPtr gatt,
hci::CommandChannel::WeakPtr cmd_channel,
pw::async::Dispatcher& dispatcher)
: dispatcher_(dispatcher),
peer_(std::move(peer)),
link_(std::move(link)),
connection_options_(connection_options),
conn_mgr_(std::move(conn_mgr)),
iso_mgr_(std::move(iso_mgr)),
l2cap_(l2cap),
gatt_(std::move(gatt)),
cmd_(std::move(cmd_channel)),
peer_disconnect_callback_(std::move(peer_disconnect_cb)),
error_callback_(std::move(error_cb)),
refs_(/*convert=*/[](const auto& refs) { return refs.size(); }),
weak_self_(this),
weak_delegate_(this) {
BT_ASSERT(peer_.is_alive());
BT_ASSERT(link_);
BT_ASSERT(conn_mgr_.is_alive());
BT_ASSERT(gatt_.is_alive());
BT_ASSERT(cmd_.is_alive());
BT_ASSERT(peer_disconnect_callback_);
BT_ASSERT(error_callback_);
link_->set_peer_disconnect_callback(
[this](const auto&, auto reason) { peer_disconnect_callback_(reason); });
RegisterEventHandlers();
StartConnectionPauseTimeout();
}
LowEnergyConnection::~LowEnergyConnection() {
cmd_->RemoveEventHandler(conn_update_cmpl_handler_id_);
// Unregister this link from the GATT profile and the L2CAP plane. This
// invalidates all L2CAP channels that are associated with this link.
gatt_->RemoveConnection(peer_id());
l2cap_->RemoveConnection(link_->handle());
// Notify all active references that the link is gone. This will
// synchronously notify all refs.
CloseRefs();
}
std::unique_ptr<bt::gap::LowEnergyConnectionHandle>
LowEnergyConnection::AddRef() {
auto self = GetWeakPtr();
auto release_cb = [self](LowEnergyConnectionHandle* handle) {
if (self.is_alive()) {
self->conn_mgr_->ReleaseReference(handle);
}
};
auto bondable_cb = [self] {
BT_ASSERT(self.is_alive());
return self->bondable_mode();
};
auto security_cb = [self] {
BT_ASSERT(self.is_alive());
return self->security();
};
auto role_cb = [self] {
BT_ASSERT(self.is_alive());
return self->role();
};
std::unique_ptr<bt::gap::LowEnergyConnectionHandle> conn_ref(
new LowEnergyConnectionHandle(peer_id(),
handle(),
std::move(release_cb),
std::move(bondable_cb),
std::move(security_cb),
std::move(role_cb)));
BT_ASSERT(conn_ref);
refs_.Mutable()->insert(conn_ref.get());
bt_log(DEBUG,
"gap-le",
"added ref (peer: %s, handle %#.4x, count: %lu)",
bt_str(peer_id()),
handle(),
ref_count());
return conn_ref;
}
void LowEnergyConnection::DropRef(LowEnergyConnectionHandle* ref) {
BT_DEBUG_ASSERT(ref);
size_t res = refs_.Mutable()->erase(ref);
BT_ASSERT_MSG(res == 1u, "DropRef called with wrong connection reference");
bt_log(DEBUG,
"gap-le",
"dropped ref (peer: %s, handle: %#.4x, count: %lu)",
bt_str(peer_id()),
handle(),
ref_count());
}
// Registers this connection with L2CAP and initializes the fixed channel
// protocols.
[[nodiscard]] bool LowEnergyConnection::InitializeFixedChannels() {
auto self = GetWeakPtr();
// Ensure error_callback_ is only called once if link_error_cb is called
// multiple times.
auto link_error_cb = [self]() {
if (self.is_alive() && self->error_callback_) {
self->error_callback_();
}
};
auto update_conn_params_cb = [self](auto params) {
if (self.is_alive()) {
self->OnNewLEConnectionParams(params);
}
};
auto security_upgrade_cb = [self](auto handle, auto level, auto cb) {
if (!self.is_alive()) {
return;
}
bt_log(INFO,
"gap-le",
"received security upgrade request on L2CAP channel (level: %s, "
"peer: %s, handle: %#.4x)",
sm::LevelToString(level),
bt_str(self->peer_id()),
handle);
BT_ASSERT(self->handle() == handle);
self->OnSecurityRequest(level, std::move(cb));
};
l2cap::ChannelManager::LEFixedChannels fixed_channels =
l2cap_->AddLEConnection(link_->handle(),
link_->role(),
std::move(link_error_cb),
update_conn_params_cb,
security_upgrade_cb);
return OnL2capFixedChannelsOpened(std::move(fixed_channels.att),
std::move(fixed_channels.smp),
connection_options_);
}
// Used to respond to protocol/service requests for increased security.
void LowEnergyConnection::OnSecurityRequest(sm::SecurityLevel level,
sm::ResultFunction<> cb) {
BT_ASSERT(sm_);
sm_->UpgradeSecurity(
level,
[cb = std::move(cb), peer_id = peer_id(), handle = handle()](
sm::Result<> status, const auto& sp) {
bt_log(INFO,
"gap-le",
"pairing status: %s, properties: %s (peer: %s, handle: %#.4x)",
bt_str(status),
bt_str(sp),
bt_str(peer_id),
handle);
cb(status);
});
}
// Handles a pairing request (i.e. security upgrade) received from "higher
// levels", likely initiated from GAP. This will only be used by pairing
// requests that are initiated in the context of testing. May only be called on
// an already-established connection.
void LowEnergyConnection::UpgradeSecurity(sm::SecurityLevel level,
sm::BondableMode bondable_mode,
sm::ResultFunction<> cb) {
BT_ASSERT(sm_);
sm_->set_bondable_mode(bondable_mode);
OnSecurityRequest(level, std::move(cb));
}
// Cancels any on-going pairing procedures and sets up SMP to use the provided
// new I/O capabilities for future pairing procedures.
void LowEnergyConnection::ResetSecurityManager(sm::IOCapability ioc) {
sm_->Reset(ioc);
}
void LowEnergyConnection::OnInterrogationComplete() {
BT_ASSERT(!interrogation_completed_);
interrogation_completed_ = true;
MaybeUpdateConnectionParameters();
}
void LowEnergyConnection::AttachInspect(inspect::Node& parent,
std::string name) {
inspect_node_ = parent.CreateChild(name);
inspect_properties_.peer_id = inspect_node_.CreateString(
kInspectPeerIdPropertyName, peer_id().ToString());
inspect_properties_.peer_address = inspect_node_.CreateString(
kInspectPeerAddressPropertyName,
link_.get() ? link_->peer_address().ToString() : "");
refs_.AttachInspect(inspect_node_, kInspectRefsPropertyName);
}
void LowEnergyConnection::StartConnectionPauseTimeout() {
if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
StartConnectionPauseCentralTimeout();
} else {
StartConnectionPausePeripheralTimeout();
}
}
void LowEnergyConnection::RegisterEventHandlers() {
auto self = GetWeakPtr();
conn_update_cmpl_handler_id_ = cmd_->AddLEMetaEventHandler(
hci_spec::kLEConnectionUpdateCompleteSubeventCode,
[self](const hci::EmbossEventPacket& event) {
if (self.is_alive()) {
self->OnLEConnectionUpdateComplete(event);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
return hci::CommandChannel::EventCallbackResult::kRemove;
});
}
// Connection parameter updates by the peripheral are not allowed until the
// central has been idle for kLEConnectionPauseCentral and
// kLEConnectionPausePeripheral has passed since the connection was established
// (Core Spec v5.2, Vol 3, Part C, Sec 9.3.12).
// TODO(https://fxbug.dev/42159733): Wait to update connection parameters until
// all initialization procedures have completed.
void LowEnergyConnection::StartConnectionPausePeripheralTimeout() {
BT_ASSERT(!conn_pause_peripheral_timeout_.has_value());
conn_pause_peripheral_timeout_.emplace(
dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
if (!status.ok()) {
return;
}
// Destroying this task will invalidate the capture list,
// so we need to save a self pointer.
auto self = this;
conn_pause_peripheral_timeout_.reset();
self->MaybeUpdateConnectionParameters();
});
conn_pause_peripheral_timeout_->PostAfter(kLEConnectionPausePeripheral);
}
// Connection parameter updates by the central are not allowed until the central
// is idle and the peripheral has been idle for kLEConnectionPauseCentral (Core
// Spec v5.2, Vol 3, Part C, Sec 9.3.12).
// TODO(https://fxbug.dev/42159733): Wait to update connection parameters until
// all initialization procedures have completed.
void LowEnergyConnection::StartConnectionPauseCentralTimeout() {
BT_ASSERT(!conn_pause_central_timeout_.has_value());
conn_pause_central_timeout_.emplace(
dispatcher_, [this](pw::async::Context /*ctx*/, pw::Status status) {
if (!status.ok()) {
return;
}
// Destroying this task will invalidate the capture list, so
// we need to save a self pointer.
auto self = this;
conn_pause_central_timeout_.reset();
self->MaybeUpdateConnectionParameters();
});
conn_pause_central_timeout_->PostAfter(kLEConnectionPauseCentral);
}
bool LowEnergyConnection::OnL2capFixedChannelsOpened(
l2cap::Channel::WeakPtr att,
l2cap::Channel::WeakPtr smp,
LowEnergyConnectionOptions connection_options) {
bt_log(DEBUG,
"gap-le",
"ATT and SMP fixed channels open (peer: %s)",
bt_str(peer_id()));
// Obtain existing pairing data, if any.
std::optional<sm::LTK> ltk;
if (peer_->le() && peer_->le()->bond_data()) {
// Legacy pairing allows both devices to generate and exchange LTKs. "The
// Central must have the security information (LTK, EDIV, and Rand)
// distributed by the Peripheral in LE legacy [...] to setup an encrypted
// session" (v5.3, Vol. 3 Part H 2.4.4.2). For Secure Connections peer_ltk
// and local_ltk will be equal, so this check is unnecessary but correct.
ltk = (link()->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL)
? peer_->le()->bond_data()->peer_ltk
: peer_->le()->bond_data()->local_ltk;
}
// Obtain the local I/O capabilities from the delegate. Default to
// NoInputNoOutput if no delegate is available.
auto io_cap = sm::IOCapability::kNoInputNoOutput;
if (conn_mgr_->pairing_delegate().is_alive()) {
io_cap = conn_mgr_->pairing_delegate()->io_capability();
}
LESecurityMode security_mode = conn_mgr_->security_mode();
sm_ = conn_mgr_->sm_factory_func()(link_->GetWeakPtr(),
std::move(smp),
io_cap,
weak_delegate_.GetWeakPtr(),
connection_options.bondable_mode,
security_mode,
dispatcher_);
// Provide SMP with the correct LTK from a previous pairing with the peer, if
// it exists. This will start encryption if the local device is the link-layer
// central.
if (ltk) {
bt_log(INFO,
"gap-le",
"assigning existing LTK (peer: %s, handle: %#.4x)",
bt_str(peer_id()),
handle());
sm_->AssignLongTermKey(*ltk);
}
return InitializeGatt(std::move(att), connection_options.service_uuid);
}
void LowEnergyConnection::OnNewLEConnectionParams(
const hci_spec::LEPreferredConnectionParameters& params) {
bt_log(INFO,
"gap-le",
"LE connection parameters received (peer: %s, handle: %#.4x)",
bt_str(peer_id()),
link_->handle());
BT_ASSERT(peer_.is_alive());
peer_->MutLe().SetPreferredConnectionParameters(params);
UpdateConnectionParams(params);
}
void LowEnergyConnection::RequestConnectionParameterUpdate(
const hci_spec::LEPreferredConnectionParameters& params) {
BT_ASSERT_MSG(
link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
"tried to send connection parameter update request as central");
BT_ASSERT(peer_.is_alive());
// Ensure interrogation has completed.
BT_ASSERT(peer_->le()->features().has_value());
// TODO(https://fxbug.dev/42126713): check local controller support for LL
// Connection Parameters Request procedure (mask is currently in Adapter le
// state, consider propagating down)
bool ll_connection_parameters_req_supported =
peer_->le()->features()->le_features &
static_cast<uint64_t>(
hci_spec::LESupportedFeature::kConnectionParametersRequestProcedure);
bt_log(TRACE,
"gap-le",
"ll connection parameters req procedure supported: %s",
ll_connection_parameters_req_supported ? "true" : "false");
if (ll_connection_parameters_req_supported) {
auto self = weak_self_.GetWeakPtr();
auto status_cb = [self, params](hci::Result<> status) {
if (!self.is_alive()) {
return;
}
self->HandleRequestConnectionParameterUpdateCommandStatus(params, status);
};
UpdateConnectionParams(params, std::move(status_cb));
} else {
L2capRequestConnectionParameterUpdate(params);
}
}
void LowEnergyConnection::HandleRequestConnectionParameterUpdateCommandStatus(
hci_spec::LEPreferredConnectionParameters params, hci::Result<> status) {
// The next LE Connection Update complete event is for this command iff the
// command |status| is success.
if (status.is_error()) {
if (status ==
ToResult(
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE)) {
// Retry connection parameter update with l2cap if the peer doesn't
// support LL procedure.
bt_log(INFO,
"gap-le",
"peer does not support HCI LE Connection Update command, trying "
"l2cap request (peer: %s)",
bt_str(peer_id()));
L2capRequestConnectionParameterUpdate(params);
}
return;
}
// Note that this callback is for the Connection Update Complete event, not
// the Connection Update status event, which is handled by the above code (see
// v5.2, Vol. 4, Part E 7.7.15 / 7.7.65.3).
le_conn_update_complete_command_callback_ =
[this, params](pw::bluetooth::emboss::StatusCode status) {
// Retry connection parameter update with l2cap if the peer doesn't
// support LL procedure.
if (status ==
pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE) {
bt_log(INFO,
"gap-le",
"peer does not support HCI LE Connection Update command, "
"trying l2cap request "
"(peer: %s)",
bt_str(peer_id()));
L2capRequestConnectionParameterUpdate(params);
}
};
}
void LowEnergyConnection::L2capRequestConnectionParameterUpdate(
const hci_spec::LEPreferredConnectionParameters& params) {
BT_ASSERT_MSG(
link_->role() == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL,
"tried to send l2cap connection parameter update request as central");
bt_log(DEBUG,
"gap-le",
"sending l2cap connection parameter update request (peer: %s)",
bt_str(peer_id()));
auto response_cb = [handle = handle(), peer_id = peer_id()](bool accepted) {
if (accepted) {
bt_log(DEBUG,
"gap-le",
"peer accepted l2cap connection parameter update request (peer: "
"%s, handle: %#.4x)",
bt_str(peer_id),
handle);
} else {
bt_log(INFO,
"gap-le",
"peer rejected l2cap connection parameter update request (peer: "
"%s, handle: %#.4x)",
bt_str(peer_id),
handle);
}
};
// TODO(https://fxbug.dev/42126716): don't send request until after
// kLEConnectionParameterTimeout of an l2cap conn parameter update response
// being received (Core Spec v5.2, Vol 3, Part C, Sec 9.3.9).
l2cap_->RequestConnectionParameterUpdate(
handle(), params, std::move(response_cb));
}
void LowEnergyConnection::UpdateConnectionParams(
const hci_spec::LEPreferredConnectionParameters& params,
StatusCallback status_cb) {
bt_log(DEBUG,
"gap-le",
"updating connection parameters (peer: %s)",
bt_str(peer_id()));
auto command = hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::LEConnectionUpdateCommandWriter>(
hci_spec::kLEConnectionUpdate);
auto view = command.view_t();
view.connection_handle().Write(handle());
// TODO(https://fxbug.dev/42074287): Handle invalid connection parameters
// before sending them to the controller.
view.connection_interval_min().UncheckedWrite(params.min_interval());
view.connection_interval_max().UncheckedWrite(params.max_interval());
view.max_latency().UncheckedWrite(params.max_latency());
view.supervision_timeout().UncheckedWrite(params.supervision_timeout());
view.min_connection_event_length().Write(0x0000);
view.max_connection_event_length().Write(0x0000);
auto status_cb_wrapper = [handle = handle(), cb = std::move(status_cb)](
auto id, const hci::EventPacket& event) mutable {
BT_ASSERT(event.event_code() == hci_spec::kCommandStatusEventCode);
hci_is_error(event,
TRACE,
"gap-le",
"controller rejected connection parameters (handle: %#.4x)",
handle);
if (cb) {
cb(event.ToResult());
}
};
cmd_->SendCommand(std::move(command),
std::move(status_cb_wrapper),
hci_spec::kCommandStatusEventCode);
}
void LowEnergyConnection::OnLEConnectionUpdateComplete(
const hci::EmbossEventPacket& event) {
BT_ASSERT(event.event_code() == hci_spec::kLEMetaEventCode);
auto view = event.view<pw::bluetooth::emboss::LEMetaEventView>();
BT_ASSERT(view.subevent_code().Read() ==
hci_spec::kLEConnectionUpdateCompleteSubeventCode);
auto payload = event.view<
pw::bluetooth::emboss::LEConnectionUpdateCompleteSubeventView>();
hci_spec::ConnectionHandle handle = payload.connection_handle().Read();
// Ignore events for other connections.
if (handle != link_->handle()) {
return;
}
// This event may be the result of the LE Connection Update command.
if (le_conn_update_complete_command_callback_) {
le_conn_update_complete_command_callback_(payload.status().Read());
}
if (payload.status().Read() != pw::bluetooth::emboss::StatusCode::SUCCESS) {
bt_log(WARN,
"gap-le",
"HCI LE Connection Update Complete event with error "
"(peer: %s, status: %#.2hhx, handle: %#.4x)",
bt_str(peer_id()),
static_cast<unsigned char>(payload.status().Read()),
handle);
return;
}
bt_log(
INFO, "gap-le", "conn. parameters updated (peer: %s)", bt_str(peer_id()));
hci_spec::LEConnectionParameters params(
payload.connection_interval().UncheckedRead(),
payload.peripheral_latency().UncheckedRead(),
payload.supervision_timeout().UncheckedRead());
link_->set_low_energy_parameters(params);
BT_ASSERT(peer_.is_alive());
peer_->MutLe().SetConnectionParameters(params);
}
void LowEnergyConnection::MaybeUpdateConnectionParameters() {
if (connection_parameters_update_requested_ || conn_pause_central_timeout_ ||
conn_pause_peripheral_timeout_ || !interrogation_completed_) {
return;
}
connection_parameters_update_requested_ = true;
if (link_->role() == pw::bluetooth::emboss::ConnectionRole::CENTRAL) {
// If the GAP service preferred connection parameters characteristic has not
// been read by now, just use the default parameters.
// TODO(https://fxbug.dev/42144795): Wait for preferred connection
// parameters to be read.
BT_ASSERT(peer_.is_alive());
auto conn_params = peer_->le()->preferred_connection_parameters().value_or(
kDefaultPreferredConnectionParameters);
UpdateConnectionParams(conn_params);
} else {
RequestConnectionParameterUpdate(kDefaultPreferredConnectionParameters);
}
}
bool LowEnergyConnection::InitializeGatt(l2cap::Channel::WeakPtr att_channel,
std::optional<UUID> service_uuid) {
att_bearer_ = att::Bearer::Create(std::move(att_channel), dispatcher_);
if (!att_bearer_) {
// This can happen if the link closes before the Bearer activates the
// channel.
bt_log(WARN, "gatt", "failed to initialize ATT bearer");
return false;
}
// The att::Bearer object is owned by LowEnergyConnection, so it outlives the
// gatt::Server and Client objects. As such, they can safely take WeakPtrs to
// the Bearer.
auto server_factory =
[att_bearer = att_bearer_->GetWeakPtr()](
PeerId peer_id,
gatt::LocalServiceManager::WeakPtr local_services) mutable {
return gatt::Server::Create(
peer_id, std::move(local_services), std::move(att_bearer));
};
std::unique_ptr<gatt::Client> gatt_client =
gatt::Client::Create(att_bearer_->GetWeakPtr());
gatt_->AddConnection(
peer_id(), std::move(gatt_client), std::move(server_factory));
std::vector<UUID> service_uuids;
if (service_uuid) {
// TODO(https://fxbug.dev/42144310): De-duplicate services.
service_uuids = {*service_uuid, kGenericAccessService};
}
gatt_->InitializeClient(peer_id(), std::move(service_uuids));
auto self = weak_self_.GetWeakPtr();
gatt_->ListServices(
peer_id(), {kGenericAccessService}, [self](auto status, auto services) {
if (self.is_alive()) {
self->OnGattServicesResult(status, std::move(services));
}
});
return true;
}
void LowEnergyConnection::OnGattServicesResult(att::Result<> status,
gatt::ServiceList services) {
if (bt_is_error(status,
INFO,
"gap-le",
"error discovering GAP service (peer: %s)",
bt_str(peer_id()))) {
return;
}
if (services.empty()) {
// The GAP service is mandatory for both central and peripheral, so this is
// unexpected.
bt_log(
INFO, "gap-le", "GAP service not found (peer: %s)", bt_str(peer_id()));
return;
}
gap_service_client_.emplace(peer_id(), services.front());
auto self = weak_self_.GetWeakPtr();
gap_service_client_->ReadDeviceName([self](att::Result<std::string> result) {
if (!self.is_alive() || result.is_error()) {
return;
}
self->peer_->RegisterName(result.value(),
Peer::NameSource::kGenericAccessService);
});
gap_service_client_->ReadAppearance([self](att::Result<uint16_t> result) {
if (!self.is_alive() || result.is_error()) {
return;
}
self->peer_->SetAppearance(result.value());
});
if (!peer_->le()->preferred_connection_parameters().has_value()) {
gap_service_client_->ReadPeripheralPreferredConnectionParameters(
[self](att::Result<hci_spec::LEPreferredConnectionParameters> result) {
if (!self.is_alive()) {
return;
}
if (result.is_error()) {
bt_log(INFO,
"gap-le",
"error reading peripheral preferred connection parameters "
"(status: %s, peer: %s)",
::bt::internal::ToString(result).c_str(),
bt_str(self->peer_id()));
return;
}
auto params = result.value();
self->peer_->MutLe().SetPreferredConnectionParameters(params);
});
}
}
void LowEnergyConnection::CloseRefs() {
for (auto* ref : *refs_.Mutable()) {
ref->MarkClosed();
}
refs_.Mutable()->clear();
}
void LowEnergyConnection::OnNewPairingData(
const sm::PairingData& pairing_data) {
const std::optional<sm::LTK> ltk =
pairing_data.peer_ltk ? pairing_data.peer_ltk : pairing_data.local_ltk;
// Consider the pairing temporary if no link key was received. This
// means we'll remain encrypted with the STK without creating a bond and
// reinitiate pairing when we reconnect in the future.
if (!ltk.has_value()) {
bt_log(INFO,
"gap-le",
"temporarily paired with peer (peer: %s)",
bt_str(peer_id()));
return;
}
bt_log(INFO,
"gap-le",
"new %s pairing data: [%s%s%s%s%s%s] (peer: %s)",
ltk->security().secure_connections() ? "secure connections" : "legacy",
pairing_data.peer_ltk ? "peer_ltk " : "",
pairing_data.local_ltk ? "local_ltk " : "",
pairing_data.irk ? "irk " : "",
pairing_data.cross_transport_key ? "ct_key " : "",
pairing_data.identity_address
? bt_lib_cpp_string::StringPrintf(
"(identity: %s) ", bt_str(*pairing_data.identity_address))
.c_str()
: "",
pairing_data.csrk ? "csrk " : "",
bt_str(peer_id()));
if (!peer_->MutLe().StoreBond(pairing_data)) {
bt_log(ERROR,
"gap-le",
"failed to cache bonding data (id: %s)",
bt_str(peer_id()));
}
}
void LowEnergyConnection::OnPairingComplete(sm::Result<> status) {
bt_log(INFO,
"gap-le",
"pairing complete (status: %s, peer: %s)",
bt_str(status),
bt_str(peer_id()));
auto delegate = conn_mgr_->pairing_delegate();
if (delegate.is_alive()) {
delegate->CompletePairing(peer_id(), status);
}
}
void LowEnergyConnection::OnAuthenticationFailure(hci::Result<> status) {
// TODO(armansito): Clear bonding data from the remote peer cache as any
// stored link key is not valid.
bt_log(WARN,
"gap-le",
"link layer authentication failed (status: %s, peer: %s)",
bt_str(status),
bt_str(peer_id()));
}
void LowEnergyConnection::OnNewSecurityProperties(
const sm::SecurityProperties& sec) {
bt_log(INFO,
"gap-le",
"new link security properties (properties: %s, peer: %s)",
bt_str(sec),
bt_str(peer_id()));
// Update the data plane with the correct link security level.
l2cap_->AssignLinkSecurityProperties(link_->handle(), sec);
}
std::optional<sm::IdentityInfo>
LowEnergyConnection::OnIdentityInformationRequest() {
if (!conn_mgr_->local_address_delegate()->irk()) {
bt_log(TRACE, "gap-le", "no local identity information to exchange");
return std::nullopt;
}
bt_log(DEBUG,
"gap-le",
"will distribute local identity information (peer: %s)",
bt_str(peer_id()));
sm::IdentityInfo id_info;
id_info.irk = *conn_mgr_->local_address_delegate()->irk();
id_info.address = conn_mgr_->local_address_delegate()->identity_address();
return id_info;
}
void LowEnergyConnection::ConfirmPairing(ConfirmCallback confirm) {
bt_log(INFO,
"gap-le",
"pairing delegate request for pairing confirmation w/ no passkey "
"(peer: %s)",
bt_str(peer_id()));
auto delegate = conn_mgr_->pairing_delegate();
if (!delegate.is_alive()) {
bt_log(ERROR,
"gap-le",
"rejecting pairing without a PairingDelegate! (peer: %s)",
bt_str(peer_id()));
confirm(false);
} else {
delegate->ConfirmPairing(peer_id(), std::move(confirm));
}
}
void LowEnergyConnection::DisplayPasskey(uint32_t passkey,
sm::Delegate::DisplayMethod method,
ConfirmCallback confirm) {
bt_log(INFO,
"gap-le",
"pairing delegate request (method: %s, peer: %s)",
sm::util::DisplayMethodToString(method).c_str(),
bt_str(peer_id()));
auto delegate = conn_mgr_->pairing_delegate();
if (!delegate.is_alive()) {
bt_log(ERROR, "gap-le", "rejecting pairing without a PairingDelegate!");
confirm(false);
} else {
delegate->DisplayPasskey(peer_id(), passkey, method, std::move(confirm));
}
}
void LowEnergyConnection::RequestPasskey(PasskeyResponseCallback respond) {
bt_log(INFO,
"gap-le",
"pairing delegate request for passkey entry (peer: %s)",
bt_str(peer_id()));
auto delegate = conn_mgr_->pairing_delegate();
if (!delegate.is_alive()) {
bt_log(ERROR,
"gap-le",
"rejecting pairing without a PairingDelegate! (peer: %s)",
bt_str(peer_id()));
respond(-1);
} else {
delegate->RequestPasskey(peer_id(), std::move(respond));
}
}
} // namespace bt::gap::internal