blob: b0fb01cedb661deb857c5ed42f4eb7e9929aa1d8 [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/l2cap/logical_link.h"
#include <functional>
#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/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/trace.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_dynamic_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/bredr_signaling_channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/channel.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/le_signaling_channel.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"
namespace bt::l2cap::internal {
using pw::bluetooth::AclPriority;
namespace {
const char* kInspectHandlePropertyName = "handle";
const char* kInspectLinkTypePropertyName = "link_type";
const char* kInspectChannelsNodeName = "channels";
const char* kInspectChannelNodePrefix = "channel_";
const char* kInspectFlushTimeoutPropertyName = "flush_timeout_ms";
constexpr bool IsValidLEFixedChannel(ChannelId id) {
switch (id) {
case kATTChannelId:
case kLESignalingChannelId:
case kLESMPChannelId:
return true;
default:
break;
}
return false;
}
constexpr bool IsValidBREDRFixedChannel(ChannelId id) {
switch (id) {
case kSignalingChannelId:
case kConnectionlessChannelId:
case kSMPChannelId:
return true;
default:
break;
}
return false;
}
} // namespace
LogicalLink::LogicalLink(hci_spec::ConnectionHandle handle,
bt::LinkType type,
pw::bluetooth::emboss::ConnectionRole role,
uint16_t max_acl_payload_size,
QueryServiceCallback query_service_cb,
hci::AclDataChannel* acl_data_channel,
hci::CommandChannel* cmd_channel,
bool random_channel_ids,
A2dpOffloadManager& a2dp_offload_manager,
pw::async::Dispatcher& dispatcher)
: pw_dispatcher_(dispatcher),
handle_(handle),
type_(type),
role_(role),
max_acl_payload_size_(max_acl_payload_size),
flush_timeout_(
pw::chrono::SystemClock::duration::max(), /*convert=*/
[](pw::chrono::SystemClock::duration f) {
return std::chrono::duration_cast<std::chrono::milliseconds>(f)
.count();
}),
closed_(false),
recombiner_(handle),
acl_data_channel_(acl_data_channel),
cmd_channel_(cmd_channel),
query_service_cb_(std::move(query_service_cb)),
a2dp_offload_manager_(a2dp_offload_manager),
weak_conn_interface_(this),
weak_self_(this) {
BT_ASSERT(type_ == bt::LinkType::kLE || type_ == bt::LinkType::kACL);
BT_ASSERT(acl_data_channel_);
BT_ASSERT(cmd_channel_);
BT_ASSERT(query_service_cb_);
// Allow packets to be sent on this link immediately.
acl_data_channel_->RegisterConnection(weak_conn_interface_.GetWeakPtr());
// Set up the signaling channel and dynamic channels.
if (type_ == bt::LinkType::kLE) {
signaling_channel_ = std::make_unique<LESignalingChannel>(
OpenFixedChannel(kLESignalingChannelId), role_, pw_dispatcher_);
// TODO(armansito): Initialize LE registry when it exists.
ServeConnectionParameterUpdateRequest();
} else {
signaling_channel_ = std::make_unique<BrEdrSignalingChannel>(
OpenFixedChannel(kSignalingChannelId), role_, pw_dispatcher_);
dynamic_registry_ = std::make_unique<BrEdrDynamicChannelRegistry>(
signaling_channel_.get(),
fit::bind_member<&LogicalLink::OnChannelDisconnectRequest>(this),
fit::bind_member<&LogicalLink::OnServiceRequest>(this),
random_channel_ids);
SendFixedChannelsSupportedInformationRequest();
}
}
LogicalLink::~LogicalLink() {
bt_log(DEBUG, "l2cap", "LogicalLink destroyed (handle: %#.4x)", handle_);
BT_ASSERT(closed_);
}
Channel::WeakPtr LogicalLink::OpenFixedChannel(ChannelId id) {
BT_DEBUG_ASSERT(!closed_);
TRACE_DURATION("bluetooth",
"LogicalLink::OpenFixedChannel",
"handle",
handle_,
"channel id",
id);
// We currently only support the pre-defined fixed-channels.
if (!AllowsFixedChannel(id)) {
bt_log(ERROR, "l2cap", "cannot open fixed channel with id %#.4x", id);
return Channel::WeakPtr();
}
auto iter = channels_.find(id);
if (iter != channels_.end()) {
bt_log(ERROR,
"l2cap",
"channel is already open! (id: %#.4x, handle: %#.4x)",
id,
handle_);
return Channel::WeakPtr();
}
std::unique_ptr<ChannelImpl> chan =
ChannelImpl::CreateFixedChannel(pw_dispatcher_,
id,
GetWeakPtr(),
cmd_channel_->AsWeakPtr(),
max_acl_payload_size_,
a2dp_offload_manager_);
auto pp_iter = pending_pdus_.find(id);
if (pp_iter != pending_pdus_.end()) {
for (auto& pdu : pp_iter->second) {
TRACE_FLOW_END(
"bluetooth", "LogicalLink::HandleRxPacket queued", pdu.trace_id());
chan->HandleRxPdu(std::move(pdu));
}
pending_pdus_.erase(pp_iter);
}
if (inspect_properties_.channels_node) {
chan->AttachInspect(inspect_properties_.channels_node,
inspect_properties_.channels_node.UniqueName(
kInspectChannelNodePrefix));
}
channels_[id] = std::move(chan);
// Reset round robin iterator
current_channel_ = channels_.begin();
return channels_[id]->GetWeakPtr();
}
void LogicalLink::OpenChannel(Psm psm,
ChannelParameters params,
ChannelCallback callback) {
BT_DEBUG_ASSERT(!closed_);
// TODO(https://fxbug.dev/42178956): Implement channels for LE credit-based
// connections
if (type_ == bt::LinkType::kLE) {
bt_log(WARN, "l2cap", "not opening LE channel for PSM %.4x", psm);
CompleteDynamicOpen(/*dyn_chan=*/nullptr, std::move(callback));
return;
}
auto create_channel =
[this, cb = std::move(callback)](const DynamicChannel* dyn_chan) mutable {
CompleteDynamicOpen(dyn_chan, std::move(cb));
};
dynamic_registry_->OpenOutbound(psm, params, std::move(create_channel));
// Reset round robin iterator
current_channel_ = channels_.begin();
}
void LogicalLink::HandleRxPacket(hci::ACLDataPacketPtr packet) {
BT_DEBUG_ASSERT(packet);
BT_DEBUG_ASSERT(!closed_);
TRACE_DURATION("bluetooth", "LogicalLink::HandleRxPacket", "handle", handle_);
// We do not support the Connectionless data channel, and the active broadcast
// flag can only be used on the connectionless channel. Drop packets that are
// broadcast.
if (packet->broadcast_flag() ==
hci_spec::ACLBroadcastFlag::kActivePeripheralBroadcast) {
bt_log(DEBUG, "l2cap", "Unsupported Broadcast Frame dropped");
return;
}
auto result = recombiner_.ConsumeFragment(std::move(packet));
if (result.frames_dropped) {
bt_log(TRACE, "l2cap", "Frame(s) dropped due to recombination error");
}
if (!result.pdu) {
// Either a partial fragment was received, which was buffered for
// recombination, or the packet was dropped.
return;
}
BT_DEBUG_ASSERT(result.pdu->is_valid());
uint16_t channel_id = result.pdu->channel_id();
auto iter = channels_.find(channel_id);
PendingPduMap::iterator pp_iter;
if (iter == channels_.end()) {
// Only buffer data for fixed channels. This prevents stale data that is
// intended for a closed dynamic channel from being delivered to a new
// channel that recycled the former's ID. The downside is that it's possible
// to lose any data that is received after a dynamic channel's connection
// request and before its completed configuration. This would require tricky
// additional state to track "pending open" channels here and it's not clear
// if that is necessary since hosts should not send data before a channel is
// first configured.
if (!AllowsFixedChannel(channel_id)) {
bt_log(WARN,
"l2cap",
"Dropping PDU for nonexistent dynamic channel %#.4x on link %#.4x",
channel_id,
handle_);
return;
}
// The packet was received on a channel for which no ChannelImpl currently
// exists. Buffer packets for the channel to receive when it gets created.
pp_iter = pending_pdus_.emplace(channel_id, std::list<PDU>()).first;
} else {
// A channel exists. |pp_iter| will be valid only if the drain task has not
// run yet (see LogicalLink::OpenFixedChannel()).
pp_iter = pending_pdus_.find(channel_id);
}
if (pp_iter != pending_pdus_.end()) {
result.pdu->set_trace_id(TRACE_NONCE());
TRACE_FLOW_BEGIN("bluetooth",
"LogicalLink::HandleRxPacket queued",
result.pdu->trace_id());
pp_iter->second.emplace_back(std::move(*result.pdu));
bt_log(TRACE,
"l2cap",
"PDU buffered (channel: %#.4x, ll: %#.4x)",
channel_id,
handle_);
return;
}
iter->second->HandleRxPdu(std::move(*result.pdu));
}
void LogicalLink::UpgradeSecurity(sm::SecurityLevel level,
sm::ResultFunction<> callback) {
BT_DEBUG_ASSERT(security_callback_);
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignoring security request on closed link");
return;
}
// Report success If the link already has the expected security level.
if (level <= security().level()) {
callback(fit::ok());
return;
}
bt_log(DEBUG,
"l2cap",
"Security upgrade requested (level = %s)",
sm::LevelToString(level));
security_callback_(handle_, level, std::move(callback));
}
void LogicalLink::AssignSecurityProperties(
const sm::SecurityProperties& security) {
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignoring security request on closed link");
return;
}
bt_log(DEBUG,
"l2cap",
"Link security updated (handle: %#.4x): %s",
handle_,
security.ToString().c_str());
security_ = security;
}
bool LogicalLink::HasAvailablePacket() const {
for (auto& [_, channel] : channels_) {
// TODO(https://fxbug.dev/42074553): Check HasSDUs() after transmission
// engines are refactored
if (channel->HasPDUs() || channel->HasFragments()) {
return true;
}
}
return false;
}
void LogicalLink::RoundRobinChannels() {
// Go through all channels in map
if (next(current_channel_) == channels_.end()) {
current_channel_ = channels_.begin();
} else {
current_channel_++;
}
}
bool LogicalLink::IsNextPacketContinuingFragment() const {
return current_pdus_channel_.is_alive() &&
current_pdus_channel_->HasFragments();
}
std::unique_ptr<hci::ACLDataPacket> LogicalLink::GetNextOutboundPacket() {
for (size_t i = 0; i < channels_.size(); i++) {
if (!IsNextPacketContinuingFragment()) {
current_pdus_channel_ = ChannelImpl::WeakPtr();
// Go to next channel to try and get next packet to send
RoundRobinChannels();
if (current_channel_->second->HasPDUs()) {
current_pdus_channel_ = current_channel_->second->GetWeakPtr();
}
}
if (current_pdus_channel_.is_alive()) {
// Next packet will either be a starting or continuing fragment
return current_pdus_channel_->GetNextOutboundPacket();
}
}
// All channels are empty
// This should never actually return a nullptr since we only call
// LogicalLink::GetNextOutboundPacket() when LogicalLink::HasAvailablePacket()
// is true
return nullptr;
}
void LogicalLink::OnOutboundPacketAvailable() {
acl_data_channel_->OnOutboundPacketAvailable();
}
void LogicalLink::set_error_callback(fit::closure callback) {
link_error_cb_ = std::move(callback);
}
void LogicalLink::set_security_upgrade_callback(
SecurityUpgradeCallback callback) {
security_callback_ = std::move(callback);
}
void LogicalLink::set_connection_parameter_update_callback(
LEConnectionParameterUpdateCallback callback) {
connection_parameter_update_callback_ = std::move(callback);
}
bool LogicalLink::AllowsFixedChannel(ChannelId id) {
return (type_ == bt::LinkType::kLE) ? IsValidLEFixedChannel(id)
: IsValidBREDRFixedChannel(id);
}
void LogicalLink::RemoveChannel(Channel* chan, fit::closure removed_cb) {
BT_DEBUG_ASSERT(chan);
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignore RemoveChannel() on closed link");
removed_cb();
return;
}
const ChannelId id = chan->id();
auto iter = channels_.find(id);
if (iter == channels_.end()) {
removed_cb();
return;
}
// Ignore if the found channel doesn't match the requested one (even though
// their IDs are the same).
if (iter->second.get() != chan) {
removed_cb();
return;
}
pending_pdus_.erase(id);
channels_.erase(iter);
// Reset round robin iterator
current_channel_ = channels_.begin();
// Disconnect the channel if it's a dynamic channel. This path is for local-
// initiated closures and does not invoke callbacks back to the channel user.
// TODO(armansito): Change this if statement into an assert when a registry
// gets created for LE channels.
if (dynamic_registry_) {
dynamic_registry_->CloseChannel(id, std::move(removed_cb));
return;
}
removed_cb();
}
void LogicalLink::SignalError() {
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignore SignalError() on closed link");
return;
}
bt_log(INFO,
"l2cap",
"Upper layer error on link %#.4x; closing all channels",
handle());
size_t num_channels_closing = channels_.size();
if (signaling_channel_) {
BT_ASSERT(channels_.count(kSignalingChannelId) ||
channels_.count(kLESignalingChannelId));
// There is no need to close the signaling channel.
num_channels_closing--;
}
if (num_channels_closing == 0) {
link_error_cb_();
return;
}
// num_channels_closing is shared across all callbacks.
fit::closure channel_removed_cb =
[this, num_channels_closing = num_channels_closing]() mutable {
num_channels_closing--;
if (num_channels_closing != 0) {
return;
}
bt_log(TRACE,
"l2cap",
"Channels on link %#.4x closed; passing error to lower layer",
handle());
// Invoking error callback may destroy this LogicalLink.
link_error_cb_();
};
for (auto channel_iter = channels_.begin();
channel_iter != channels_.end();) {
auto& [id, channel] = *channel_iter++;
// Do not close the signaling channel, as it is used to close the dynamic
// channels.
if (id == kSignalingChannelId || id == kLESignalingChannelId) {
continue;
}
// Signal the channel, as it did not request the closure.
channel->OnClosed();
// This erases from |channel_| and invalidates any iterator pointing to
// |channel|.
RemoveChannel(channel.get(), channel_removed_cb.share());
}
}
void LogicalLink::Close() {
BT_DEBUG_ASSERT(!closed_);
closed_ = true;
acl_data_channel_->UnregisterConnection(handle_);
for (auto& iter : channels_) {
iter.second->OnClosed();
}
channels_.clear();
dynamic_registry_.reset();
}
std::optional<DynamicChannelRegistry::ServiceInfo>
LogicalLink::OnServiceRequest(Psm psm) {
BT_DEBUG_ASSERT(!closed_);
// Query upper layer for a service handler attached to this PSM.
auto result = query_service_cb_(handle_, psm);
if (!result) {
return std::nullopt;
}
auto channel_cb = [this, chan_cb = std::move(result->channel_cb)](
const DynamicChannel* dyn_chan) mutable {
CompleteDynamicOpen(dyn_chan, std::move(chan_cb));
};
return DynamicChannelRegistry::ServiceInfo(result->channel_params,
std::move(channel_cb));
}
void LogicalLink::OnChannelDisconnectRequest(const DynamicChannel* dyn_chan) {
BT_DEBUG_ASSERT(dyn_chan);
BT_DEBUG_ASSERT(!closed_);
auto iter = channels_.find(dyn_chan->local_cid());
if (iter == channels_.end()) {
bt_log(WARN,
"l2cap",
"No ChannelImpl found for closing dynamic channel %#.4x",
dyn_chan->local_cid());
return;
}
ChannelImpl* channel = iter->second.get();
BT_DEBUG_ASSERT(channel->remote_id() == dyn_chan->remote_cid());
// Signal closure because this is a remote disconnection.
channel->OnClosed();
channels_.erase(iter);
// Reset round robin iterator
current_channel_ = channels_.begin();
}
void LogicalLink::CompleteDynamicOpen(const DynamicChannel* dyn_chan,
ChannelCallback open_cb) {
BT_DEBUG_ASSERT(!closed_);
if (!dyn_chan) {
open_cb(Channel::WeakPtr());
return;
}
const ChannelId local_cid = dyn_chan->local_cid();
const ChannelId remote_cid = dyn_chan->remote_cid();
bt_log(DEBUG,
"l2cap",
"Link %#.4x: Channel opened with ID %#.4x (remote ID: %#.4x, psm: %s)",
handle_,
local_cid,
remote_cid,
PsmToString(dyn_chan->psm()).c_str());
auto chan_info = dyn_chan->info();
// Extract preferred flush timeout to avoid creating channel with a flush
// timeout that hasn't been successfully configured yet.
auto preferred_flush_timeout = chan_info.flush_timeout;
chan_info.flush_timeout.reset();
std::unique_ptr<ChannelImpl> chan =
ChannelImpl::CreateDynamicChannel(pw_dispatcher_,
local_cid,
remote_cid,
GetWeakPtr(),
chan_info,
cmd_channel_->AsWeakPtr(),
max_acl_payload_size_,
a2dp_offload_manager_);
auto chan_weak = chan->GetWeakPtr();
channels_[local_cid] = std::move(chan);
if (inspect_properties_.channels_node) {
chan_weak->AttachInspect(inspect_properties_.channels_node,
inspect_properties_.channels_node.UniqueName(
kInspectChannelNodePrefix));
}
// If a flush timeout was requested for this channel, try to set it before
// returning the channel to the client to ensure outbound PDUs have correct
// flushable flag.
if (preferred_flush_timeout.has_value()) {
chan_weak->SetBrEdrAutomaticFlushTimeout(
preferred_flush_timeout.value(),
[cb = std::move(open_cb), chan_weak](auto /*result*/) {
cb(chan_weak);
});
return;
}
open_cb(std::move(chan_weak));
}
void LogicalLink::SendFixedChannelsSupportedInformationRequest() {
BT_ASSERT(signaling_channel_);
BrEdrCommandHandler cmd_handler(signaling_channel_.get());
if (!cmd_handler.SendInformationRequest(
InformationType::kFixedChannelsSupported,
[self = GetWeakPtr()](auto& rsp) {
if (self.is_alive()) {
self->OnRxFixedChannelsSupportedInfoRsp(rsp);
}
})) {
bt_log(ERROR,
"l2cap",
"Failed to send Fixed Channels Supported Information Request");
return;
}
bt_log(TRACE, "l2cap", "Sent Fixed Channels Supported Information Request");
}
void LogicalLink::OnRxFixedChannelsSupportedInfoRsp(
const BrEdrCommandHandler::InformationResponse& rsp) {
if (rsp.status() == BrEdrCommandHandler::Status::kReject) {
bt_log(
TRACE,
"l2cap",
"Fixed Channels Supported Information Request rejected (reason %#.4hx)",
static_cast<unsigned short>(rsp.reject_reason()));
return;
}
if (rsp.result() == InformationResult::kNotSupported) {
bt_log(TRACE,
"l2cap",
"Received Fixed Channels Supported Information Response (result: "
"Not Supported)");
return;
}
if (rsp.result() != InformationResult::kSuccess) {
bt_log(TRACE,
"l2cap",
"Received Fixed Channels Supported Information Response (result: "
"%.4hx)",
static_cast<uint16_t>(rsp.result()));
return;
}
if (rsp.type() != InformationType::kFixedChannelsSupported) {
bt_log(TRACE,
"l2cap",
"Incorrect Fixed Channels Supported Information Response type "
"(type: %#.4hx)",
static_cast<unsigned short>(rsp.type()));
return;
}
bt_log(
TRACE,
"l2cap",
"Received Fixed Channels Supported Information Response (mask: %#016lx)",
rsp.fixed_channels());
}
void LogicalLink::SendConnectionParameterUpdateRequest(
hci_spec::LEPreferredConnectionParameters params,
ConnectionParameterUpdateRequestCallback request_cb) {
BT_ASSERT(signaling_channel_);
BT_ASSERT(type_ == bt::LinkType::kLE);
BT_ASSERT(role_ == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL);
LowEnergyCommandHandler cmd_handler(signaling_channel_.get());
cmd_handler.SendConnectionParameterUpdateRequest(
params.min_interval(),
params.max_interval(),
params.max_latency(),
params.supervision_timeout(),
[cb = std::move(request_cb)](
const LowEnergyCommandHandler::ConnectionParameterUpdateResponse&
rsp) mutable {
bool accepted = false;
if (rsp.status() != LowEnergyCommandHandler::Status::kSuccess) {
bt_log(TRACE,
"l2cap",
"LE Connection Parameter Update Request rejected (reason: "
"%#.4hx)",
static_cast<unsigned short>(rsp.reject_reason()));
} else {
accepted = rsp.result() == ConnectionParameterUpdateResult::kAccepted;
}
cb(accepted);
});
}
void LogicalLink::RequestAclPriority(
Channel::WeakPtr channel,
AclPriority priority,
fit::callback<void(fit::result<fit::failed>)> callback) {
BT_ASSERT(channel.is_alive());
auto iter = channels_.find(channel->id());
BT_ASSERT(iter != channels_.end());
pending_acl_requests_.push(
PendingAclRequest{std::move(channel), priority, std::move(callback)});
if (pending_acl_requests_.size() == 1) {
HandleNextAclPriorityRequest();
}
}
void LogicalLink::SetBrEdrAutomaticFlushTimeout(
pw::chrono::SystemClock::duration flush_timeout,
hci::ResultCallback<> callback) {
if (type_ != bt::LinkType::kACL) {
bt_log(
ERROR, "l2cap", "attempt to set flush timeout on non-ACL logical link");
callback(ToResult(
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS));
return;
}
auto callback_wrapper = [self = GetWeakPtr(),
flush_timeout,
cb = std::move(callback)](auto result) mutable {
if (self.is_alive() && result.is_ok()) {
self->flush_timeout_.Set(flush_timeout);
}
cb(result);
};
if (flush_timeout < std::chrono::milliseconds(1) ||
(flush_timeout > hci_spec::kMaxAutomaticFlushTimeoutDuration &&
flush_timeout != pw::chrono::SystemClock::duration::max())) {
callback_wrapper(ToResult(
pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS));
return;
}
uint16_t converted_flush_timeout;
if (flush_timeout == pw::chrono::SystemClock::duration::max()) {
// The command treats a flush timeout of 0 as infinite.
converted_flush_timeout = 0;
} else {
// Slight imprecision from casting or converting to ms is fine for the flush
// timeout (a few ms difference from the requested value doesn't matter).
// Overflow is not possible because of the max value check above.
converted_flush_timeout = static_cast<uint16_t>(
static_cast<float>(
std::chrono::duration_cast<std::chrono::milliseconds>(flush_timeout)
.count()) *
hci_spec::kFlushTimeoutMsToCommandParameterConversionFactor);
BT_ASSERT(converted_flush_timeout != 0);
BT_ASSERT(converted_flush_timeout <=
hci_spec::kMaxAutomaticFlushTimeoutCommandParameterValue);
}
auto write_timeout = hci::EmbossCommandPacket::New<
pw::bluetooth::emboss::WriteAutomaticFlushTimeoutCommandWriter>(
hci_spec::kWriteAutomaticFlushTimeout);
auto write_timeout_view = write_timeout.view_t();
write_timeout_view.connection_handle().Write(handle_);
write_timeout_view.flush_timeout().Write(converted_flush_timeout);
cmd_channel_->SendCommand(
std::move(write_timeout),
[cb = std::move(callback_wrapper), handle = handle_, flush_timeout](
auto, const hci::EventPacket& event) mutable {
if (event.ToResult().is_error()) {
bt_log(WARN,
"hci",
"WriteAutomaticFlushTimeout command failed (result: %s, "
"handle: %#.4x)",
bt_str(event.ToResult()),
handle);
} else {
bt_log(DEBUG,
"hci",
"automatic flush timeout updated (handle: %#.4x, timeout: "
"%lld ms)",
handle,
std::chrono::duration_cast<std::chrono::milliseconds>(
flush_timeout)
.count());
}
cb(event.ToResult());
});
}
void LogicalLink::AttachInspect(inspect::Node& parent, std::string name) {
if (!parent) {
return;
}
auto node = parent.CreateChild(name);
inspect_properties_.handle =
node.CreateString(kInspectHandlePropertyName,
bt_lib_cpp_string::StringPrintf("%#.4x", handle_));
inspect_properties_.link_type =
node.CreateString(kInspectLinkTypePropertyName, LinkTypeToString(type_));
inspect_properties_.channels_node =
node.CreateChild(kInspectChannelsNodeName);
flush_timeout_.AttachInspect(node, kInspectFlushTimeoutPropertyName);
inspect_properties_.node = std::move(node);
for (auto& [_, chan] : channels_) {
chan->AttachInspect(inspect_properties_.channels_node,
inspect_properties_.channels_node.UniqueName(
kInspectChannelNodePrefix));
}
}
void LogicalLink::HandleNextAclPriorityRequest() {
if (pending_acl_requests_.empty() || closed_) {
return;
}
auto& request = pending_acl_requests_.front();
BT_ASSERT(request.callback);
// Prevent closed channels with queued requests from upgrading channel
// priority. Allow closed channels to downgrade priority so that they can
// clean up their priority on destruction.
if (!request.channel.is_alive() && request.priority != AclPriority::kNormal) {
request.callback(fit::failed());
pending_acl_requests_.pop();
HandleNextAclPriorityRequest();
return;
}
// Skip sending command if desired priority is already set. Do this here
// instead of Channel in case Channel queues up multiple requests.
if (request.priority == acl_priority_) {
request.callback(fit::ok());
pending_acl_requests_.pop();
HandleNextAclPriorityRequest();
return;
}
// If priority is not kNormal, then a channel might be using a conflicting
// priority, and the new priority should not be requested.
if (acl_priority_ != AclPriority::kNormal) {
for (auto& [chan_id, chan] : channels_) {
if ((request.channel.is_alive() &&
chan.get() == &request.channel.get()) ||
chan->requested_acl_priority() == AclPriority::kNormal) {
continue;
}
// If the request returns priority to normal but a different channel still
// requires high priority, skip sending command and just report success.
if (request.priority == AclPriority::kNormal) {
request.callback(fit::ok());
break;
}
// If the request tries to upgrade priority but it conflicts with another
// channel's priority (e.g. sink vs. source), report an error.
if (request.priority != chan->requested_acl_priority()) {
request.callback(fit::failed());
break;
}
}
if (!request.callback) {
pending_acl_requests_.pop();
HandleNextAclPriorityRequest();
return;
}
}
auto cb_wrapper = [self = GetWeakPtr(),
cb = std::move(request.callback),
priority = request.priority](auto result) mutable {
if (!self.is_alive()) {
return;
}
if (result.is_ok()) {
self->acl_priority_ = priority;
}
cb(result);
self->pending_acl_requests_.pop();
self->HandleNextAclPriorityRequest();
};
acl_data_channel_->RequestAclPriority(
request.priority, handle_, std::move(cb_wrapper));
}
void LogicalLink::ServeConnectionParameterUpdateRequest() {
BT_ASSERT(signaling_channel_);
BT_ASSERT(type_ == bt::LinkType::kLE);
LowEnergyCommandHandler cmd_handler(signaling_channel_.get());
cmd_handler.ServeConnectionParameterUpdateRequest(
fit::bind_member<&LogicalLink::OnRxConnectionParameterUpdateRequest>(
this));
}
void LogicalLink::OnRxConnectionParameterUpdateRequest(
uint16_t interval_min,
uint16_t interval_max,
uint16_t peripheral_latency,
uint16_t timeout_multiplier,
LowEnergyCommandHandler::ConnectionParameterUpdateResponder* responder) {
// Only a LE peripheral can send this command. "If a Peripheral’s Host
// receives an L2CAP_CONNECTION_PARAMETER_UPDATE_REQ packet it shall respond
// with an L2CAP_COMMAND_REJECT_RSP packet with reason 0x0000 (Command not
// understood)." (v5.0, Vol 3, Part A, Section 4.20)
if (role_ == pw::bluetooth::emboss::ConnectionRole::PERIPHERAL) {
bt_log(
DEBUG, "l2cap", "rejecting conn. param. update request from central");
responder->RejectNotUnderstood();
return;
}
// Reject the connection parameters if they are outside the ranges allowed by
// the HCI specification (see HCI_LE_Connection_Update command v5.0, Vol 2,
// Part E, Section 7.8.18).
bool reject = false;
hci_spec::LEPreferredConnectionParameters params(
interval_min, interval_max, peripheral_latency, timeout_multiplier);
if (params.min_interval() > params.max_interval()) {
bt_log(DEBUG, "l2cap", "conn. min interval larger than max");
reject = true;
} else if (params.min_interval() < hci_spec::kLEConnectionIntervalMin) {
bt_log(DEBUG,
"l2cap",
"conn. min interval outside allowed range: %#.4x",
params.min_interval());
reject = true;
} else if (params.max_interval() > hci_spec::kLEConnectionIntervalMax) {
bt_log(DEBUG,
"l2cap",
"conn. max interval outside allowed range: %#.4x",
params.max_interval());
reject = true;
} else if (params.max_latency() > hci_spec::kLEConnectionLatencyMax) {
bt_log(DEBUG,
"l2cap",
"conn. peripheral latency too large: %#.4x",
params.max_latency());
reject = true;
} else if (params.supervision_timeout() <
hci_spec::kLEConnectionSupervisionTimeoutMin ||
params.supervision_timeout() >
hci_spec::kLEConnectionSupervisionTimeoutMax) {
bt_log(DEBUG,
"l2cap",
"conn supv. timeout outside allowed range: %#.4x",
params.supervision_timeout());
reject = true;
}
ConnectionParameterUpdateResult result =
reject ? ConnectionParameterUpdateResult::kRejected
: ConnectionParameterUpdateResult::kAccepted;
responder->Send(result);
if (!reject) {
if (!connection_parameter_update_callback_) {
bt_log(DEBUG,
"l2cap",
"no callback set for LE Connection Parameter Update Request");
return;
}
connection_parameter_update_callback_(params);
}
}
} // namespace bt::l2cap::internal