blob: 632fcbd3117f50407bbfc46ea5568f83e75d2308 [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 "logical_link.h"
#include <lib/fit/bridge.h>
#include <zircon/assert.h>
#include <functional>
#include "bredr_dynamic_channel.h"
#include "bredr_signaling_channel.h"
#include "channel.h"
#include "fbl/ref_ptr.h"
#include "le_signaling_channel.h"
#include "lib/fit/promise.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/run_or_post.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/transport.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap_defs.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bt::l2cap::internal {
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
// static
fbl::RefPtr<LogicalLink> LogicalLink::New(
hci::ConnectionHandle handle, hci::Connection::LinkType type, hci::Connection::Role role,
fit::executor* executor, size_t max_acl_payload_size, QueryServiceCallback query_service_cb,
hci::AclDataChannel* acl_data_channel, bool random_channel_ids) {
auto ll = fbl::AdoptRef(new LogicalLink(handle, type, role, executor, max_acl_payload_size,
std::move(query_service_cb), acl_data_channel));
ll->Initialize(random_channel_ids);
return ll;
}
LogicalLink::LogicalLink(hci::ConnectionHandle handle, hci::Connection::LinkType type,
hci::Connection::Role role, fit::executor* executor,
size_t max_acl_payload_size, QueryServiceCallback query_service_cb,
hci::AclDataChannel* acl_data_channel)
: handle_(handle),
type_(type),
role_(role),
flush_timeout_(zx::duration::infinite(), /*convert=*/[](auto f) { return f.to_msecs(); }),
closed_(false),
fragmenter_(handle, max_acl_payload_size),
recombiner_(handle),
acl_data_channel_(acl_data_channel),
query_service_cb_(std::move(query_service_cb)),
executor_(executor),
weak_ptr_factory_(this) {
ZX_ASSERT(type_ == hci::Connection::LinkType::kLE || type_ == hci::Connection::LinkType::kACL);
ZX_ASSERT(executor_);
ZX_ASSERT(acl_data_channel_);
ZX_ASSERT(query_service_cb_);
}
void LogicalLink::Initialize(bool randomize_channel_ids) {
ZX_DEBUG_ASSERT(!signaling_channel_);
ZX_DEBUG_ASSERT(!dynamic_registry_);
// Set up the signaling channel and dynamic channels.
if (type_ == hci::Connection::LinkType::kLE) {
signaling_channel_ =
std::make_unique<LESignalingChannel>(OpenFixedChannel(kLESignalingChannelId), role_);
// TODO(armansito): Initialize LE registry when it exists.
ServeConnectionParameterUpdateRequest();
} else {
signaling_channel_ =
std::make_unique<BrEdrSignalingChannel>(OpenFixedChannel(kSignalingChannelId), role_);
dynamic_registry_ = std::make_unique<BrEdrDynamicChannelRegistry>(
signaling_channel_.get(), fit::bind_member(this, &LogicalLink::OnChannelDisconnectRequest),
fit::bind_member(this, &LogicalLink::OnServiceRequest), randomize_channel_ids);
SendFixedChannelsSupportedInformationRequest();
}
}
LogicalLink::~LogicalLink() {
bt_log(DEBUG, "l2cap", "LogicalLink destroyed (handle: %#.4x)", handle_);
ZX_ASSERT(closed_);
}
fbl::RefPtr<Channel> LogicalLink::OpenFixedChannel(ChannelId id) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_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 nullptr;
}
auto iter = channels_.find(id);
if (iter != channels_.end()) {
bt_log(ERROR, "l2cap", "channel is already open! (id: %#.4x, handle: %#.4x)", id, handle_);
return nullptr;
}
auto chan = ChannelImpl::CreateFixedChannel(id, GetWeakPtr());
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);
}
channels_[id] = chan;
if (inspect_properties_.channels_node) {
chan->AttachInspect(inspect_properties_.channels_node,
inspect_properties_.channels_node.UniqueName(kInspectChannelNodePrefix));
}
return chan;
}
void LogicalLink::OpenChannel(PSM psm, ChannelParameters params, ChannelCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(!closed_);
// TODO(fxbug.dev/968): Implement channels for LE credit-based connections
if (type_ == hci::Connection::LinkType::kLE) {
bt_log(WARN, "l2cap", "not opening LE channel for PSM %.4x", psm);
CompleteDynamicOpen(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));
}
void LogicalLink::HandleRxPacket(hci::ACLDataPacketPtr packet) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(packet);
ZX_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::ACLBroadcastFlag::kActiveSlaveBroadcast) {
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;
}
ZX_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::StatusCallback callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(security_callback_);
ZX_DEBUG_ASSERT(dispatcher);
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignoring security request on closed link");
return;
}
auto status_cb = [dispatcher, f = std::move(callback)](sm::Status status) mutable {
async::PostTask(dispatcher, [f = std::move(f), status] { f(status); });
};
// Report success If the link already has the expected security level.
if (level <= security().level()) {
status_cb(sm::Status());
return;
}
bt_log(DEBUG, "l2cap", "Security upgrade requested (level = %s)", sm::LevelToString(level));
security_callback_(handle_, level, std::move(status_cb));
}
void LogicalLink::AssignSecurityProperties(const sm::SecurityProperties& security) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
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;
}
void LogicalLink::SendFrame(ChannelId id, const ByteBuffer& payload,
FrameCheckSequenceOption fcs_option, bool flushable) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
if (closed_) {
bt_log(DEBUG, "l2cap", "Drop out-bound packet on closed link");
return;
}
// Copy payload into L2CAP frame fragments, sized for the HCI data transport.
PDU pdu = fragmenter_.BuildFrame(id, payload, fcs_option, flushable);
auto fragments = pdu.ReleaseFragments();
ZX_ASSERT(!fragments.is_empty());
acl_data_channel_->SendPackets(std::move(fragments), id, ChannelPriority(id));
}
void LogicalLink::set_error_callback(fit::closure callback) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
link_error_cb_ = std::move(callback);
}
void LogicalLink::set_security_upgrade_callback(SecurityUpgradeCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
security_callback_ = std::move(callback);
}
void LogicalLink::set_connection_parameter_update_callback(
LEConnectionParameterUpdateCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
connection_parameter_update_callback_ = std::move(callback);
}
LESignalingChannel* LogicalLink::le_signaling_channel() const {
return (type_ == hci::Connection::LinkType::kLE)
? static_cast<LESignalingChannel*>(signaling_channel_.get())
: nullptr;
}
bool LogicalLink::AllowsFixedChannel(ChannelId id) {
return (type_ == hci::Connection::LinkType::kLE) ? IsValidLEFixedChannel(id)
: IsValidBREDRFixedChannel(id);
}
fit::promise<> LogicalLink::RemoveChannel(Channel* chan) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(chan);
if (closed_) {
bt_log(DEBUG, "l2cap", "Ignore RemoveChannel() on closed link");
return fit::make_ok_promise();
}
const ChannelId id = chan->id();
auto iter = channels_.find(id);
if (iter == channels_.end()) {
return fit::make_ok_promise();
}
// Ignore if the found channel doesn't match the requested one (even though
// their IDs are the same).
if (iter->second.get() != chan) {
return fit::make_ok_promise();
}
pending_pdus_.erase(id);
channels_.erase(iter);
// Drop stale packets queued for this channel.
hci::AclDataChannel::AclPacketPredicate predicate = [this, id](const auto& packet,
l2cap::ChannelId channel_id) {
return packet->connection_handle() == handle_ && id == channel_id;
};
acl_data_channel_->DropQueuedPackets(std::move(predicate));
// 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_) {
// This bridge can and probably should be pushed farther down through DynamicChannelRegistry,
// BrEdrCommandHandler, SignalingChannel, and ACLDataChannel all the way up to the underlying
// async::Wait.
//
// Note that completing a bridge seems to always produce a new task for fulfilling dependent
// consumer promises, which for fit::executor means posting the consumer onto its dispatcher.
fit::bridge<> bridge;
dynamic_registry_->CloseChannel(id, bridge.completer.bind());
// promise_or converts completer abandonment (e.g. destroyed in an early error branch) to a
// promise that yields a result.
return bridge.consumer.promise_or(fit::ok());
}
return fit::make_ok_promise();
}
void LogicalLink::SignalError() {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
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());
std::vector<fit::promise<>> channel_closures;
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|.
channel_closures.emplace_back(RemoveChannel(channel.get()));
}
fit::promise<> error_signaler =
fit::join_promise_vector(std::move(channel_closures))
.and_then([this](const std::vector<fit::result<>>& results) {
bt_log(TRACE, "l2cap", "Channels on link %#.4x closed; passing error to lower layer",
handle());
// Invoking this may destroy this LogicalLink.
link_error_cb_();
// Link is expected to be closed by its owner.
return fit::ok();
});
executor_->schedule_task(std::move(error_signaler));
}
void LogicalLink::Close() {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(!closed_);
closed_ = true;
for (auto& iter : channels_) {
iter.second->OnClosed();
}
channels_.clear();
}
std::optional<DynamicChannelRegistry::ServiceInfo> LogicalLink::OnServiceRequest(PSM psm) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_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) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(dyn_chan);
ZX_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;
}
auto channel = iter->second;
ZX_DEBUG_ASSERT(channel->remote_id() == dyn_chan->remote_cid());
hci::AclDataChannel::AclPacketPredicate predicate =
[this, id = channel->id()](const auto& packet, l2cap::ChannelId channel_id) {
return packet->connection_handle() == handle_ && id == channel_id;
};
acl_data_channel_->DropQueuedPackets(std::move(predicate));
// Signal closure because this is a remote disconnection.
channel->OnClosed();
channels_.erase(iter);
}
void LogicalLink::CompleteDynamicOpen(const DynamicChannel* dyn_chan, ChannelCallback open_cb) {
ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid());
ZX_DEBUG_ASSERT(!closed_);
if (!dyn_chan) {
open_cb(nullptr);
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();
auto chan = ChannelImpl::CreateDynamicChannel(local_cid, remote_cid, GetWeakPtr(), chan_info);
channels_[local_cid] = chan;
if (inspect_properties_.channels_node) {
chan->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->SetBrEdrAutomaticFlushTimeout(
preferred_flush_timeout.value(),
[cb = std::move(open_cb), chan](auto /*result*/) { cb(chan); });
return;
}
open_cb(std::move(chan));
}
void LogicalLink::SendFixedChannelsSupportedInformationRequest() {
ZX_ASSERT(signaling_channel_);
BrEdrCommandHandler cmd_handler(signaling_channel_.get());
if (!cmd_handler.SendInformationRequest(InformationType::kFixedChannelsSupported,
[self = GetWeakPtr()](auto& rsp) {
if (self) {
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)",
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)",
rsp.type());
return;
}
bt_log(TRACE, "l2cap", "Received Fixed Channels Supported Information Response (mask: %#016lx)",
rsp.fixed_channels());
}
void LogicalLink::SendConnectionParameterUpdateRequest(
hci::LEPreferredConnectionParameters params,
ConnectionParameterUpdateRequestCallback request_cb) {
ZX_ASSERT(signaling_channel_);
ZX_ASSERT(type_ == hci::Connection::LinkType::kLE);
ZX_ASSERT(role_ == hci::Connection::Role::kSlave);
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)",
rsp.reject_reason());
} else {
accepted = rsp.result() == ConnectionParameterUpdateResult::kAccepted;
}
cb(accepted);
});
}
void LogicalLink::RequestAclPriority(Channel* channel, hci::AclPriority priority,
fit::callback<void(fit::result<>)> callback) {
ZX_ASSERT(channel);
auto iter = channels_.find(channel->id());
ZX_ASSERT(iter != channels_.end());
auto chan_ref = iter->second;
// Store ref pointer to channel to ensure it stays alive until this request is handled.
pending_acl_requests_.push(PendingAclRequest{chan_ref, priority, std::move(callback)});
if (pending_acl_requests_.size() == 1) {
HandleNextAclPriorityRequest();
}
}
void LogicalLink::SetBrEdrAutomaticFlushTimeout(
zx::duration flush_timeout, fit::callback<void(fit::result<void, hci::StatusCode>)> callback) {
if (type_ != hci::Connection::LinkType::kACL) {
bt_log(ERROR, "l2cap", "attempt to set flush timeout on non-ACL logical link");
callback(fit::error(hci::StatusCode::kInvalidHCICommandParameters));
return;
}
auto callback_wrapper = [self = GetWeakPtr(), flush_timeout,
cb = std::move(callback)](auto result) mutable {
if (self && result.is_ok()) {
self->flush_timeout_.Set(flush_timeout);
}
cb(result);
};
acl_data_channel_->SetBrEdrAutomaticFlushTimeout(flush_timeout, handle_,
std::move(callback_wrapper));
}
void LogicalLink::AttachInspect(inspect::Node& parent, std::string name) {
if (!parent) {
return;
}
auto node = parent.CreateChild(name);
inspect_properties_.handle =
node.CreateString(kInspectHandlePropertyName, fxl::StringPrintf("%#.4x", handle_));
inspect_properties_.link_type =
node.CreateString(kInspectLinkTypePropertyName, hci::Connection::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));
}
}
hci::AclDataChannel::PacketPriority LogicalLink::ChannelPriority(ChannelId id) {
switch (id) {
case kSignalingChannelId:
case kLESignalingChannelId:
case kSMPChannelId:
case kLESMPChannelId:
return hci::AclDataChannel::PacketPriority::kHigh;
default:
return hci::AclDataChannel::PacketPriority::kLow;
}
}
void LogicalLink::HandleNextAclPriorityRequest() {
if (pending_acl_requests_.empty() || closed_) {
return;
}
auto& request = pending_acl_requests_.front();
ZX_ASSERT(request.channel);
ZX_ASSERT(request.callback);
// Prevent closed channels with queued requests from upgrading channel priority.
if (channels_.find(request.channel->id()) == channels_.end() &&
request.priority != hci::AclPriority::kNormal) {
request.callback(fit::error());
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.channel->requested_acl_priority() == request.priority) {
request.callback(fit::ok());
pending_acl_requests_.pop();
HandleNextAclPriorityRequest();
return;
}
for (auto& [chan_id, chan] : channels_) {
if (chan.get() == request.channel.get()) {
continue;
}
if (chan->requested_acl_priority() != hci::AclPriority::kNormal) {
// If the request returns priority to normal but a different channel still requires high
// priority OR if another channel already is using the requested priority, skip sending
// command and just report success.
if (request.priority == hci::AclPriority::kNormal ||
request.priority == chan->requested_acl_priority()) {
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 != hci::AclPriority::kNormal &&
request.priority != chan->requested_acl_priority()) {
request.callback(fit::error());
break;
}
}
}
if (!request.callback) {
pending_acl_requests_.pop();
HandleNextAclPriorityRequest();
return;
}
auto cb_wrapper = [self = GetWeakPtr(), cb = std::move(request.callback)](auto result) mutable {
if (!self) {
return;
}
cb(result);
self->pending_acl_requests_.pop();
self->HandleNextAclPriorityRequest();
};
acl_data_channel_->RequestAclPriority(request.priority, handle_, std::move(cb_wrapper));
}
void LogicalLink::ServeConnectionParameterUpdateRequest() {
ZX_ASSERT(signaling_channel_);
ZX_ASSERT(type_ == hci::Connection::LinkType::kLE);
LowEnergyCommandHandler cmd_handler(signaling_channel_.get());
cmd_handler.ServeConnectionParameterUpdateRequest(
fit::bind_member(this, &LogicalLink::OnRxConnectionParameterUpdateRequest));
}
void LogicalLink::OnRxConnectionParameterUpdateRequest(
uint16_t interval_min, uint16_t interval_max, uint16_t slave_latency,
uint16_t timeout_multiplier,
LowEnergyCommandHandler::ConnectionParameterUpdateResponder* responder) {
// Only a LE slave can send this command. "If an LE slave Host receives a
// Connection Parameter Update Request packet it shall respond with a Command
// Reject Packet [...]" (v5.0, Vol 3, Part A, Section 4.20).
if (role_ == hci::Connection::Role::kSlave) {
bt_log(DEBUG, "l2cap", "rejecting conn. param. update request from master");
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::LEPreferredConnectionParameters params(interval_min, interval_max, slave_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::kLEConnectionIntervalMin) {
bt_log(DEBUG, "l2cap", "conn. min interval outside allowed range: %#.4x",
params.min_interval());
reject = true;
} else if (params.max_interval() > hci::kLEConnectionIntervalMax) {
bt_log(DEBUG, "l2cap", "conn. max interval outside allowed range: %#.4x",
params.max_interval());
reject = true;
} else if (params.max_latency() > hci::kLEConnectionLatencyMax) {
bt_log(DEBUG, "l2cap", "conn. slave latency too large: %#.4x", params.max_latency());
reject = true;
} else if (params.supervision_timeout() < hci::kLEConnectionSupervisionTimeoutMin ||
params.supervision_timeout() > hci::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