blob: 9cb7c5bcd914acd50d3185af268460578ce333e3 [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 "channel.h"
#include <zircon/assert.h>
#include <functional>
#include <memory>
#include <trace/event.h>
#include "logical_link.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/l2cap/basic_mode_rx_engine.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/basic_mode_tx_engine.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/enhanced_retransmission_mode_engines.h"
#include "src/lib/fxl/strings/string_printf.h"
namespace bt {
namespace l2cap {
Channel::Channel(ChannelId id, ChannelId remote_id, hci::Connection::LinkType link_type,
hci::ConnectionHandle link_handle, ChannelInfo info)
: id_(id),
remote_id_(remote_id),
link_type_(link_type),
link_handle_(link_handle),
info_(info) {
ZX_DEBUG_ASSERT(id_);
ZX_DEBUG_ASSERT(link_type_ == hci::Connection::LinkType::kLE ||
link_type_ == hci::Connection::LinkType::kACL);
}
namespace internal {
fbl::RefPtr<ChannelImpl> ChannelImpl::CreateFixedChannel(ChannelId id,
fbl::RefPtr<internal::LogicalLink> link) {
// A fixed channel's endpoints have the same local and remote identifiers.
// Signaling channels use the default MTU (Core Spec v5.1, Vol 3, Part A, Section 4).
return fbl::AdoptRef(
new ChannelImpl(id, id, link, ChannelInfo::MakeBasicMode(kDefaultMTU, kDefaultMTU)));
}
fbl::RefPtr<ChannelImpl> ChannelImpl::CreateDynamicChannel(ChannelId id, ChannelId peer_id,
fbl::RefPtr<internal::LogicalLink> link,
ChannelInfo info) {
return fbl::AdoptRef(new ChannelImpl(id, peer_id, link, info));
}
ChannelImpl::ChannelImpl(ChannelId id, ChannelId remote_id, fbl::RefPtr<internal::LogicalLink> link,
ChannelInfo info)
: Channel(id, remote_id, link->type(), link->handle(), info),
active_(false),
dispatcher_(nullptr),
link_(link) {
ZX_ASSERT(link_);
ZX_ASSERT_MSG(
info_.mode == ChannelMode::kBasic || info_.mode == ChannelMode::kEnhancedRetransmission,
"Channel constructed with unsupported mode: %hhu\n", info.mode);
// B-frames for Basic Mode contain only an "Information payload" (v5.0 Vol 3, Part A, Sec 3.1)
FrameCheckSequenceOption fcs_option = info_.mode == ChannelMode::kEnhancedRetransmission
? FrameCheckSequenceOption::kIncludeFcs
: FrameCheckSequenceOption::kNoFcs;
auto send_cb = [rid = remote_id, link, fcs_option](auto pdu) {
async::PostTask(link->dispatcher(), [=, pdu = std::move(pdu)] {
// |link| is expected to ignore this call and drop the packet if it has been closed.
link->SendFrame(rid, *pdu, fcs_option);
});
};
if (info_.mode == ChannelMode::kBasic) {
rx_engine_ = std::make_unique<BasicModeRxEngine>();
tx_engine_ = std::make_unique<BasicModeTxEngine>(id, max_tx_sdu_size(), send_cb);
} else {
// Must capture |link| and not |link_| to avoid having to take |mutex_|.
auto connection_failure_cb = [this, link] {
ZX_ASSERT(thread_checker_.IsCreationThreadCurrent());
// This isn't called until Channel has been activated and the callback is destroyed by
// Deactivate, so even without taking |mutex_| we know that the channel is active. However,
// this may be called from a locked context in HandleRxPdu, so defer the signal to after the
// critical section so that we don't deadlock when removing this channel.
async::PostTask(link->dispatcher(), [link] {
// |link| is expected to ignore this call if it has been closed.
link->SignalError();
});
};
std::tie(rx_engine_, tx_engine_) = MakeLinkedEnhancedRetransmissionModeEngines(
id, max_tx_sdu_size(), info_.max_transmissions, info_.n_frames_in_tx_window, send_cb,
std::move(connection_failure_cb));
}
}
const sm::SecurityProperties ChannelImpl::security() {
std::lock_guard lock(mtx_);
if (link_) {
return link_->security();
}
return sm::SecurityProperties();
}
bool ChannelImpl::ActivateWithDispatcher(RxCallback rx_callback, ClosedCallback closed_callback,
async_dispatcher_t* dispatcher) {
ZX_DEBUG_ASSERT(rx_callback);
ZX_DEBUG_ASSERT(closed_callback);
fit::closure task;
bool run_task = false;
{
std::lock_guard lock(mtx_);
// Activating on a closed link has no effect. We also clear this on
// deactivation to prevent a channel from being activated more than once.
if (!link_)
return false;
ZX_DEBUG_ASSERT(!active_);
active_ = true;
ZX_DEBUG_ASSERT(!dispatcher_);
dispatcher_ = dispatcher;
rx_cb_ = std::move(rx_callback);
closed_cb_ = std::move(closed_callback);
// Route the buffered packets.
if (!pending_rx_sdus_.empty()) {
run_task = true;
task = [func = rx_cb_.share(), pending = std::move(pending_rx_sdus_)]() mutable {
TRACE_DURATION("bluetooth", "ChannelImpl::ActivateWithDispatcher pending drain");
while (!pending.empty()) {
TRACE_FLOW_END("bluetooth", "ChannelImpl::HandleRxPdu queued", pending.size());
func(std::move(pending.front()));
pending.pop();
}
};
ZX_DEBUG_ASSERT(pending_rx_sdus_.empty());
}
}
if (run_task) {
RunOrPost(std::move(task), dispatcher);
}
return true;
}
bool ChannelImpl::ActivateOnDataDomain(RxCallback rx_callback, ClosedCallback closed_callback) {
return ActivateWithDispatcher(std::move(rx_callback), std::move(closed_callback), nullptr);
}
void ChannelImpl::Deactivate() {
std::lock_guard lock(mtx_);
// De-activating on a closed link has no effect.
if (!link_ || !active_) {
link_ = nullptr;
return;
}
active_ = false;
dispatcher_ = nullptr;
rx_cb_ = {};
closed_cb_ = {};
rx_engine_ = {};
tx_engine_ = {};
// Tell the link to release this channel on its thread.
async::PostTask(link_->dispatcher(), [this, link = link_] {
// |link| is expected to ignore this call if it has been closed.
link->RemoveChannel(this);
});
link_ = nullptr;
}
void ChannelImpl::SignalLinkError() {
std::lock_guard lock(mtx_);
// Cannot signal an error on a closed or deactivated link.
if (!link_ || !active_)
return;
async::PostTask(link_->dispatcher(), [link = link_] {
// |link| is expected to ignore this call if it has been closed.
link->SignalError();
});
}
bool ChannelImpl::Send(ByteBufferPtr sdu) {
ZX_DEBUG_ASSERT(sdu);
std::lock_guard lock(mtx_);
TRACE_DURATION("bluetooth", "l2cap:channel_send", "handle", link_->handle(), "id", id());
if (!link_) {
bt_log(ERROR, "l2cap", "cannot send SDU on a closed link");
return false;
}
// Drop the packet if the channel is inactive.
if (!active_)
return false;
return tx_engine_->QueueSdu(std::move(sdu));
}
void ChannelImpl::UpgradeSecurity(sm::SecurityLevel level, sm::StatusCallback callback,
async_dispatcher_t* dispatcher) {
ZX_ASSERT(callback);
ZX_ASSERT(dispatcher);
std::lock_guard lock(mtx_);
if (!link_ || !active_) {
bt_log(TRACE, "l2cap", "Ignoring security request on inactive channel");
return;
}
async::PostTask(link_->dispatcher(),
[link = link_, level, callback = std::move(callback), dispatcher]() mutable {
link->UpgradeSecurity(level, std::move(callback), dispatcher);
});
}
void ChannelImpl::OnClosed() {
async_dispatcher_t* dispatcher;
fit::closure task;
{
std::lock_guard lock(mtx_);
if (!link_ || !active_) {
link_ = nullptr;
return;
}
ZX_DEBUG_ASSERT(closed_cb_);
dispatcher = dispatcher_;
task = std::move(closed_cb_);
active_ = false;
link_ = nullptr;
dispatcher_ = nullptr;
}
RunOrPost(std::move(task), dispatcher);
}
void ChannelImpl::HandleRxPdu(PDU&& pdu) {
async_dispatcher_t* dispatcher;
fit::closure task;
{
std::lock_guard lock(mtx_);
TRACE_DURATION("bluetooth", "ChannelImpl::HandleRxPdu", "handle", link_->handle(), "channel_id",
id_);
// link_ may be nullptr if a pdu is received after the channel has been deactivated but
// before LogicalLink::RemoveChannel has been dispatched
if (!link_) {
bt_log(SPEW, "l2cap", "ignoring pdu on deactivated channel");
return;
}
ZX_DEBUG_ASSERT(rx_engine_);
ByteBufferPtr sdu = rx_engine_->ProcessPdu(std::move(pdu));
if (!sdu) {
// The PDU may have been invalid, out-of-sequence, or part of a segmented
// SDU.
// * If invalid, we drop the PDU (per Core Spec Ver 5, Vol 3, Part A,
// Secs. 3.3.6 and/or 3.3.7).
// * If out-of-sequence or part of a segmented SDU, we expect that some
// later call to ProcessPdu() will return us an SDU containing this
// PDU's data.
return;
}
// Buffer the packets if the channel hasn't been activated.
if (!active_) {
pending_rx_sdus_.emplace(std::move(sdu));
// Tracing: we assume pending_rx_sdus_ is only filled once and use the length of queue
// for trace ids.
TRACE_FLOW_BEGIN("bluetooth", "ChannelImpl::HandleRxPdu queued", pending_rx_sdus_.size());
return;
}
trace_flow_id_t trace_id = TRACE_NONCE();
TRACE_FLOW_BEGIN("bluetooth", "ChannelImpl::HandleRxPdu callback", trace_id);
dispatcher = dispatcher_;
task = [func = rx_cb_.share(), sdu = std::move(sdu), trace_id]() mutable {
TRACE_DURATION("bluetooth", "ChannelImpl::HandleRxPdu callback task");
TRACE_FLOW_END("bluetooth", "ChannelImpl::HandleRxPdu callback", trace_id);
func(std::move(sdu));
};
ZX_DEBUG_ASSERT(rx_cb_);
}
RunOrPost(std::move(task), dispatcher);
}
} // namespace internal
} // namespace l2cap
} // namespace bt