blob: 146577d5bc02620764ac236d505137125f168f16 [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 "channel.h"
namespace bluetooth {
namespace l2cap {
namespace internal {
namespace {
constexpr bool IsValidLEFixedChannel(ChannelId id) {
switch (id) {
case kATTChannelId:
case kLESignalingChannelId:
case kSMPChannelId:
return true;
default:
break;
}
return false;
}
constexpr bool IsValidBREDRFixedChannel(ChannelId id) {
switch (id) {
case kSignalingChannelId:
case kConnectionlessChannelId:
case kSMChannelId:
return true;
default:
break;
}
return false;
}
} // namespace
LogicalLink::LogicalLink(hci::ConnectionHandle handle,
hci::Connection::LinkType type,
hci::Connection::Role role,
fxl::RefPtr<hci::Transport> hci)
: hci_(hci),
handle_(handle),
type_(type),
role_(role),
fragmenter_(handle) {
FXL_DCHECK(hci_);
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());
}
}
LogicalLink::~LogicalLink() {
cancelable_callback_factory_.CancelAll();
Close();
}
std::unique_ptr<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;
}
std::lock_guard<std::mutex> lock(mtx_);
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;
}
auto chan = std::unique_ptr<ChannelImpl>(new ChannelImpl(id, this));
// We grab a raw pointer and store it internally, weakly owning all channels
// that we create. We avoid dangling pointers by relying on the fact that each
// Channel notifies on destruction by calling RemoveChannel().
channels_[id] = chan.get();
// Handle pending packets on the channel, if any.
auto pp_iter = pending_pdus_.find(id);
if (pp_iter != pending_pdus_.end()) {
hci_->io_task_runner()->PostTask(
cancelable_callback_factory_.MakeTask([this, id] {
std::lock_guard<std::mutex> lock(mtx_);
// Make sure the channel is still open.
auto iter = channels_.find(id);
if (iter == channels_.end())
return;
auto pp_iter = pending_pdus_.find(id);
FXL_DCHECK(pp_iter != pending_pdus_.end());
auto chan = iter->second;
auto& pdus = pp_iter->second;
while (!pdus.empty()) {
chan->HandleRxPdu(std::move(pdus.front()));
pdus.pop_front();
}
pending_pdus_.erase(pp_iter);
}));
}
return chan;
}
void LogicalLink::HandleRxPacket(hci::ACLDataPacketPtr packet) {
// The creation thread of this object is expected to be different from the HCI
// I/O thread.
FXL_DCHECK(!thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(io_task_runner()->RunsTasksOnCurrentThread());
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());
std::lock_guard<std::mutex> lock(mtx_);
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 a ChannelImpl does not exist, we buffer its packets to be delivered
// later. If one DOES exist, we buffer it only if the task to drain the
// pending packets has not yet run. (See LogicalLink::OpenFixedChannel above).
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 {
pp_iter = pending_pdus_.find(channel_id);
}
if (pp_iter != pending_pdus_.end()) {
pp_iter->second.emplace_back(std::move(pdu));
FXL_VLOG(1) << fxl::StringPrintf(
"l2cap: PDU buffered (channel: 0x%04x, ll: 0x%04x)", channel_id,
handle_);
return;
}
// Off it goes.
iter->second->HandleRxPdu(std::move(pdu));
}
void LogicalLink::SendBasicFrame(ChannelId id,
const common::ByteBuffer& payload) {
FXL_DCHECK(io_task_runner()->RunsTasksOnCurrentThread());
// 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_);
}
bool LogicalLink::AllowsFixedChannel(ChannelId id) {
return (type_ == hci::Connection::LinkType::kLE)
? IsValidLEFixedChannel(id)
: IsValidBREDRFixedChannel(id);
}
void LogicalLink::RemoveChannel(ChannelImpl* channel) {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
FXL_DCHECK(channel);
std::lock_guard<std::mutex> lock(mtx_);
auto iter = channels_.find(channel->id());
FXL_DCHECK(iter != channels_.end()) << fxl::StringPrintf(
"l2cap: Attempted to remove unknown channel (id: 0x%04x, handle: 0x%04x)",
channel->id(), handle_);
FXL_DCHECK(iter->first == channel->id());
channels_.erase(iter);
pending_pdus_.erase(channel->id());
}
void LogicalLink::Close() {
FXL_DCHECK(thread_checker_.IsCreationThreadCurrent());
ChannelMap channels;
// Clear |channels_| before notifying each entry to avoid holding our |mtx_|
// while Channel's own internal mutex is locked.
{
std::lock_guard<std::mutex> lock(mtx_);
channels.swap(channels_);
}
for (auto& iter : channels) {
iter.second->OnLinkClosed();
}
}
} // namespace internal
} // namespace l2cap
} // namespace bluetooth