blob: e8cb551a1b0c9f327225b45f1961e61924ad0c9a [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/hci/transport.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,
fxl::RefPtr<hci::Transport> hci, QueryServiceCallback query_service_cb) {
auto ll = fbl::AdoptRef(new LogicalLink(handle, type, role, dispatcher, hci,
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,
fxl::RefPtr<hci::Transport> hci,
QueryServiceCallback query_service_cb)
: hci_(hci),
dispatcher_(dispatcher),
handle_(handle),
type_(type),
role_(role),
closed_(false),
fragmenter_(handle),
query_service_cb_(std::move(query_service_cb)) {
ZX_DEBUG_ASSERT(hci_);
ZX_DEBUG_ASSERT(dispatcher_);
ZX_DEBUG_ASSERT(type_ == hci::Connection::LinkType::kLE ||
type_ == hci::Connection::LinkType::kACL);
}
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) {
ZX_DEBUG_ASSERT(hci_->acl_data_channel()->GetLEBufferInfo().IsAvailable());
fragmenter_.set_max_acl_payload_size(
hci_->acl_data_channel()->GetLEBufferInfo().max_data_length());
signaling_channel_ = std::make_unique<LESignalingChannel>(
OpenFixedChannel(kLESignalingChannelId), role_);
// TODO(armansito): Initialize LE registry when it exists.
} else {
ZX_DEBUG_ASSERT(hci_->acl_data_channel()->GetBufferInfo().IsAvailable());
fragmenter_.set_max_acl_payload_size(
hci_->acl_data_channel()->GetBufferInfo().max_data_length());
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));
}
}
LogicalLink::~LogicalLink() { ZX_DEBUG_ASSERT(closed_); }
fbl::RefPtr<Channel> LogicalLink::OpenFixedChannel(ChannelId id) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(!closed_);
// 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;
}
std::list<PDU> pending;
auto pp_iter = pending_pdus_.find(id);
if (pp_iter != pending_pdus_.end()) {
pending = std::move(pp_iter->second);
pending_pdus_.erase(pp_iter);
}
// A fixed channel's endpoints have the same local and remote identifiers.
auto chan =
fbl::AdoptRef(new ChannelImpl(id /* id */, id /* remote_id */,
fbl::WrapRefPtr(this), std::move(pending)));
channels_[id] = chan;
return chan;
}
void LogicalLink::OpenChannel(PSM psm, 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, 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_);
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()) {
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));
async::PostTask(security_dispatcher_,
[handle = handle_, level, f = security_callback_.share(),
status_cb = std::move(status_cb)]() mutable {
f(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::SendBasicFrame(ChannelId id, const ByteBuffer& payload) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
if (closed_) {
bt_log(TRACE, "l2cap", "Drop out-bound packet on closed link");
return;
}
// TODO(armansito): The following makes a copy of |payload| when constructing
// |pdu|. Think about how this could be optimized, especially when |payload|
// fits inside a single ACL data fragment.
PDU pdu = fragmenter_.BuildBasicFrame(id, payload);
auto fragments = pdu.ReleaseFragments();
ZX_DEBUG_ASSERT(!fragments.is_empty());
hci_->acl_data_channel()->SendPackets(std::move(fragments), type_);
}
void LogicalLink::set_error_callback(fit::closure callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(static_cast<bool>(callback) == static_cast<bool>(dispatcher));
link_error_cb_ = std::move(callback);
link_error_dispatcher_ = dispatcher;
}
void LogicalLink::set_security_upgrade_callback(
SecurityUpgradeCallback callback, async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(thread_checker_.IsCreationThreadCurrent());
ZX_DEBUG_ASSERT(static_cast<bool>(callback) == static_cast<bool>(dispatcher));
security_callback_ = std::move(callback);
security_dispatcher_ = dispatcher;
}
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);
// 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;
}
if (link_error_cb_) {
async::PostTask(link_error_dispatcher_, std::move(link_error_cb_));
link_error_dispatcher_ = nullptr;
}
}
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();
}
}
DynamicChannelRegistry::DynamicChannelCallback 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.
ChannelCallback chan_cb = query_service_cb_(handle_, psm);
if (!chan_cb) {
return nullptr;
}
return [this, chan_cb = std::move(chan_cb)](
const DynamicChannel* dyn_chan) mutable {
CompleteDynamicOpen(dyn_chan, std::move(chan_cb), dispatcher_);
};
}
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);
// 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) {
async::PostTask(dispatcher, std::bind(std::move(open_cb), nullptr));
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 = fbl::AdoptRef(
new ChannelImpl(local_cid, remote_cid, fbl::WrapRefPtr(this), {}));
channels_[local_cid] = chan;
async::PostTask(dispatcher, std::bind(std::move(open_cb), std::move(chan)));
}
} // namespace internal
} // namespace l2cap
} // namespace bt