blob: 4bf3b66be3833f8c87f2ee21135e4330a50ad436 [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 <zircon/assert.h>
#include <functional>
#include "bredr_dynamic_channel.h"
#include "bredr_signaling_channel.h"
#include "channel.h"
#include "le_signaling_channel.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.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bt {
namespace l2cap {
namespace internal {
namespace {
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,
async_dispatcher_t* dispatcher, size_t max_acl_payload_size,
SendPacketsCallback send_packets_cb, DropQueuedAclCallback drop_queued_acl_cb,
QueryServiceCallback query_service_cb) {
auto ll = fbl::AdoptRef(new LogicalLink(handle, type, role, dispatcher, max_acl_payload_size,
std::move(send_packets_cb), std::move(drop_queued_acl_cb),
std::move(query_service_cb)));
ll->Initialize();
return ll;
}
LogicalLink::LogicalLink(hci::ConnectionHandle handle, hci::Connection::LinkType type,
hci::Connection::Role role, async_dispatcher_t* dispatcher,
size_t max_acl_payload_size, SendPacketsCallback send_packets_cb,
DropQueuedAclCallback drop_queued_acl_cb,
QueryServiceCallback query_service_cb)
: dispatcher_(dispatcher),
handle_(handle),
type_(type),
role_(role),
closed_(false),
fragmenter_(handle, max_acl_payload_size),
send_packets_cb_(std::move(send_packets_cb)),
drop_queued_acl_cb_(std::move(drop_queued_acl_cb)),
query_service_cb_(std::move(query_service_cb)),
weak_ptr_factory_(this) {
ZX_ASSERT(dispatcher_);
ZX_ASSERT(type_ == hci::Connection::LinkType::kLE || type_ == hci::Connection::LinkType::kACL);
ZX_ASSERT(send_packets_cb_);
ZX_ASSERT(drop_queued_acl_cb_);
ZX_ASSERT(query_service_cb_);
}
void LogicalLink::Initialize() {
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));
SendFixedChannelsSupportedInformationRequest();
}
}
LogicalLink::~LogicalLink() { ZX_DEBUG_ASSERT(closed_); }
fbl::RefPtr<Channel> LogicalLink::OpenFixedChannel(ChannelId id) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
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, fbl::RefPtr(this));
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;
return chan;
}
void LogicalLink::OpenChannel(PSM psm, ChannelParameters params, ChannelCallback callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!closed_);
// TODO(NET-1437): 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), dispatcher);
return;
}
auto create_channel = [this, cb = std::move(callback),
dispatcher](const DynamicChannel* dyn_chan) mutable {
CompleteDynamicOpen(dyn_chan, std::move(cb), dispatcher);
};
dynamic_registry_->OpenOutbound(psm, params, std::move(create_channel));
}
void LogicalLink::HandleRxPacket(hci::ACLDataPacketPtr packet) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!recombiner_.ready());
ZX_DEBUG_ASSERT(packet);
ZX_DEBUG_ASSERT(!closed_);
TRACE_DURATION("bluetooth", "LogicalLink::HandleRxPacket", "handle", handle_);
if (!recombiner_.AddFragment(std::move(packet))) {
bt_log(TRACE, "l2cap", "ACL data packet rejected (handle: %#.4x)", handle_);
// TODO(armansito): This indicates that this connection is not reliable.
// This needs to notify the channels of this state.
return;
}
// |recombiner_| should have taken ownership of |packet|.
ZX_DEBUG_ASSERT(!packet);
ZX_DEBUG_ASSERT(!recombiner_.empty());
// Wait for continuation fragments if a partial fragment was received.
if (!recombiner_.ready())
return;
PDU pdu;
recombiner_.Release(&pdu);
ZX_DEBUG_ASSERT(pdu.is_valid());
uint16_t channel_id = 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()) {
pdu.set_trace_id(TRACE_NONCE());
TRACE_FLOW_BEGIN("bluetooth", "LogicalLink::HandleRxPacket queued", pdu.trace_id());
pp_iter->second.emplace_back(std::move(pdu));
bt_log(SPEW, "l2cap", "PDU buffered (channel: %#.4x, ll: %#.4x)", channel_id, handle_);
return;
}
iter->second->HandleRxPdu(std::move(pdu));
}
void LogicalLink::UpgradeSecurity(sm::SecurityLevel level, sm::StatusCallback callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(security_callback_);
ZX_DEBUG_ASSERT(dispatcher);
if (closed_) {
bt_log(TRACE, "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(TRACE, "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_.IsCreationThreadCurrent());
if (closed_) {
bt_log(TRACE, "l2cap", "Ignoring security request on closed link");
return;
}
bt_log(TRACE, "l2cap", "Link security updated (handle: %#.4x): %s", handle_,
security.ToString().c_str());
std::lock_guard<std::mutex> lock(mtx_);
security_ = security;
}
void LogicalLink::SendFrame(ChannelId id, const ByteBuffer& payload,
FrameCheckSequenceOption fcs_option) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
if (closed_) {
bt_log(TRACE, "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);
auto fragments = pdu.ReleaseFragments();
ZX_ASSERT(!fragments.is_empty());
send_packets_cb_(std::move(fragments), id);
}
void LogicalLink::set_error_callback(fit::closure callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
link_error_cb_ = std::move(callback);
}
void LogicalLink::set_security_upgrade_callback(SecurityUpgradeCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
security_callback_ = std::move(callback);
}
void LogicalLink::set_connection_parameter_update_callback(
LEConnectionParameterUpdateCallback callback) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
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);
}
void LogicalLink::RemoveChannel(Channel* chan) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(chan);
if (closed_) {
bt_log(TRACE, "l2cap", "Ignore RemoveChannel() on closed link");
return;
}
const ChannelId id = chan->id();
auto iter = channels_.find(id);
if (iter == channels_.end())
return;
// Ignore if the found channel doesn't match the requested one (even though
// their IDs are the same).
if (iter->second.get() != chan)
return;
pending_pdus_.erase(id);
channels_.erase(iter);
// Drop stale packets queued for this channel.
hci::ACLPacketPredicate predicate = [this, id](const auto& packet, l2cap::ChannelId channel_id) {
return packet->connection_handle() == handle_ && id == channel_id;
};
drop_queued_acl_cb_(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_) {
dynamic_registry_->CloseChannel(id);
}
}
void LogicalLink::SignalError() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
if (closed_) {
bt_log(TRACE, "l2cap", "Ignore SignalError() on closed link");
return;
}
bt_log(INFO, "l2cap", "Signal upper layer error on link %#.4x; closing all channels", handle());
if (link_error_cb_) {
link_error_cb_();
}
for (auto channel_iter = channels_.begin(); channel_iter != channels_.end();) {
auto& [_, channel] = *channel_iter++;
// 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());
}
// Link is expected to be closed by its owner.
}
void LogicalLink::Close() {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!closed_);
closed_ = true;
auto channels = std::move(channels_);
for (auto& iter : channels) {
iter.second->OnClosed();
}
}
std::optional<DynamicChannelRegistry::ServiceInfo> LogicalLink::OnServiceRequest(PSM psm) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
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), nullptr);
};
return DynamicChannelRegistry::ServiceInfo(result->channel_params, std::move(channel_cb));
}
void LogicalLink::OnChannelDisconnectRequest(const DynamicChannel* dyn_chan) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
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;
}
fbl::RefPtr<ChannelImpl> channel = std::move(iter->second);
ZX_DEBUG_ASSERT(channel->remote_id() == dyn_chan->remote_cid());
channels_.erase(iter);
hci::ACLPacketPredicate predicate = [this, id = channel->id()](const auto& packet,
l2cap::ChannelId channel_id) {
return packet->connection_handle() == handle_ && id == channel_id;
};
drop_queued_acl_cb_(std::move(predicate));
// Signal closure because this is a remote disconnection.
channel->OnClosed();
}
void LogicalLink::CompleteDynamicOpen(const DynamicChannel* dyn_chan, ChannelCallback open_cb,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!closed_);
if (!dyn_chan) {
RunOrPost(std::bind(std::move(open_cb), nullptr), dispatcher);
return;
}
const ChannelId local_cid = dyn_chan->local_cid();
const ChannelId remote_cid = dyn_chan->remote_cid();
bt_log(TRACE, "l2cap", "Link %#.4x: Channel opened with ID %#.4x (remote ID %#.4x)", handle_,
local_cid, remote_cid);
auto chan =
ChannelImpl::CreateDynamicChannel(local_cid, remote_cid, fbl::RefPtr(this), dyn_chan->info());
channels_[local_cid] = chan;
RunOrPost(std::bind(std::move(open_cb), std::move(chan)), dispatcher);
}
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(SPEW, "l2cap", "Sent Fixed Channels Supported Information Request");
}
void LogicalLink::OnRxFixedChannelsSupportedInfoRsp(
const BrEdrCommandHandler::InformationResponse& rsp) {
if (rsp.status() == BrEdrCommandHandler::Status::kReject) {
bt_log(SPEW, "l2cap", "Fixed Channels Supported Information Request rejected (reason %#.4hx)",
rsp.reject_reason());
return;
}
if (rsp.result() == InformationResult::kNotSupported) {
bt_log(SPEW, "l2cap",
"Received Fixed Channels Supported Information Response (result: Not Supported)");
return;
}
if (rsp.type() != InformationType::kFixedChannelsSupported) {
bt_log(SPEW, "l2cap",
"Incorrect Fixed Channels Supported Information Response type (type: %#.4hx)",
rsp.type());
return;
}
bt_log(SPEW, "l2cap", "Received Fixed Channels Supported Information Response (mask: %#016lx)",
rsp.fixed_channels());
}
void LogicalLink::SendConnectionParameterUpdateRequest(
hci::LEPreferredConnectionParameters params,
ConnectionParameterUpdateRequestCallback request_cb, async_dispatcher_t* dispatcher) {
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),
dispatcher](const LowEnergyCommandHandler::ConnectionParameterUpdateResponse& rsp) mutable {
bool accepted = false;
if (rsp.status() != LowEnergyCommandHandler::Status::kSuccess) {
bt_log(SPEW, "l2cap", "LE Connection Parameter Update Request rejected (reason: %#.4hx)",
rsp.reject_reason());
} else {
accepted = rsp.result() == ConnectionParameterUpdateResult::kAccepted;
}
RunOrPost(std::bind(std::move(cb), accepted), dispatcher);
});
}
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(TRACE, "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(TRACE, "l2cap", "conn. min interval larger than max");
reject = true;
} else if (params.min_interval() < hci::kLEConnectionIntervalMin) {
bt_log(TRACE, "l2cap", "conn. min interval outside allowed range: %#.4x",
params.min_interval());
reject = true;
} else if (params.max_interval() > hci::kLEConnectionIntervalMax) {
bt_log(TRACE, "l2cap", "conn. max interval outside allowed range: %#.4x",
params.max_interval());
reject = true;
} else if (params.max_latency() > hci::kLEConnectionLatencyMax) {
bt_log(TRACE, "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(TRACE, "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(TRACE, "l2cap", "no callback set for LE Connection Parameter Update Request");
return;
}
connection_parameter_update_callback_(params);
}
}
} // namespace internal
} // namespace l2cap
} // namespace bt