blob: 3e82c8ec78996600baf4b72a194a8454de9ab9db [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 "garnet/drivers/bluetooth/lib/hci/transport.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "bredr_signaling_channel.h"
#include "channel.h"
#include "le_signaling_channel.h"
namespace btlib {
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
LogicalLink::LogicalLink(hci::ConnectionHandle handle,
hci::Connection::LinkType type,
hci::Connection::Role role,
async_dispatcher_t* dispatcher,
fxl::RefPtr<hci::Transport> hci)
: hci_(hci),
dispatcher_(dispatcher),
handle_(handle),
type_(type),
role_(role),
fragmenter_(handle),
weak_ptr_factory_(this) {
FXL_DCHECK(hci_);
FXL_DCHECK(dispatcher_);
FXL_DCHECK(type_ == hci::Connection::LinkType::kLE ||
type_ == hci::Connection::LinkType::kACL);
if (type_ == hci::Connection::LinkType::kLE) {
FXL_DCHECK(hci_->acl_data_channel()->GetLEBufferInfo().IsAvailable());
fragmenter_.set_max_acl_payload_size(
hci_->acl_data_channel()->GetLEBufferInfo().max_data_length());
} else {
FXL_DCHECK(hci_->acl_data_channel()->GetBufferInfo().IsAvailable());
fragmenter_.set_max_acl_payload_size(
hci_->acl_data_channel()->GetBufferInfo().max_data_length());
}
// Set up the signaling channel.
if (type_ == hci::Connection::LinkType::kLE) {
signaling_channel_ = std::make_unique<LESignalingChannel>(
OpenFixedChannel(kLESignalingChannelId), role_);
} else {
signaling_channel_ = std::make_unique<BrEdrSignalingChannel>(
OpenFixedChannel(kSignalingChannelId), role_);
}
}
LogicalLink::~LogicalLink() { Close(); }
fbl::RefPtr<Channel> LogicalLink::OpenFixedChannel(ChannelId id) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
// We currently only support the pre-defined fixed-channels.
if (!AllowsFixedChannel(id)) {
FXL_LOG(ERROR) << fxl::StringPrintf(
"l2cap: Cannot open fixed channel with id 0x%04x", id);
return nullptr;
}
auto iter = channels_.find(id);
if (iter != channels_.end()) {
FXL_LOG(ERROR) << fxl::StringPrintf(
"l2cap: Channel is already open! (id: 0x%04x, handle: 0x%04x)", 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 */,
weak_ptr_factory_.GetWeakPtr(),
std::move(pending)));
channels_[id] = chan;
return chan;
}
void LogicalLink::HandleRxPacket(hci::ACLDataPacketPtr packet) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(!recombiner_.ready());
FXL_DCHECK(packet);
if (!recombiner_.AddFragment(std::move(packet))) {
FXL_VLOG(1) << fxl::StringPrintf(
"l2cap: ACL data packet rejected (handle: 0x%04x)", 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|.
FXL_DCHECK(!packet);
FXL_DCHECK(!recombiner_.empty());
// Wait for continuation fragments if a partial fragment was received.
if (!recombiner_.ready())
return;
PDU pdu;
recombiner_.Release(&pdu);
FXL_DCHECK(pdu.is_valid());
uint16_t channel_id = pdu.channel_id();
auto iter = channels_.find(channel_id);
PendingPduMap::iterator pp_iter;
// TODO(armansito): This buffering scheme could be problematic for dynamically
// negotiated channels if a channel id were to be recycled, as it requires
// careful management of the timing between channel destruction and data
// buffering. Probably only buffer data for fixed channels?
if (iter == channels_.end()) {
// 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));
FXL_VLOG(2) << fxl::StringPrintf(
"l2cap: PDU buffered (channel: 0x%04x, ll: 0x%04x)", channel_id,
handle_);
return;
}
iter->second->HandleRxPdu(std::move(pdu));
}
void LogicalLink::SendBasicFrame(ChannelId id,
const common::ByteBuffer& payload) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
// 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();
FXL_DCHECK(!fragments.is_empty());
hci_->acl_data_channel()->SendPackets(std::move(fragments), type_);
}
void LogicalLink::set_error_callback(fit::closure callback,
async_dispatcher_t* dispatcher) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(static_cast<bool>(callback) == static_cast<bool>(dispatcher));
link_error_cb_ = std::move(callback);
link_error_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) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(chan);
auto iter = channels_.find(chan->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(chan->id());
channels_.erase(iter);
}
void LogicalLink::SignalError() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
if (link_error_cb_) {
async::PostTask(link_error_dispatcher_, std::move(link_error_cb_));
link_error_dispatcher_ = nullptr;
}
}
void LogicalLink::Close() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
auto channels = std::move(channels_);
for (auto& iter : channels) {
static_cast<ChannelImpl*>(iter.second.get())->OnLinkClosed();
}
FXL_DCHECK(channels_.empty());
}
} // namespace internal
} // namespace l2cap
} // namespace btlib