blob: 3064ccc573d89db889648cb75d14e0a618d978db [file] [log] [blame]
// Copyright 2018 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 "dynamic_channel_registry.h"
#include <zircon/assert.h>
#include "lib/zx/channel.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/l2cap.h"
namespace bt {
namespace l2cap {
namespace internal {
DynamicChannelRegistry::~DynamicChannelRegistry() {
// Clean up connected channels.
for (auto& [id, channel] : channels_) {
if (channel->IsConnected()) {
const auto no_op = [] {};
channel->Disconnect(no_op);
}
}
}
// Run return callbacks on the L2CAP thread. LogicalLink takes care of out-of-
// thread dispatch for delivering the pointer to the channel.
void DynamicChannelRegistry::OpenOutbound(PSM psm, ChannelParameters params,
DynamicChannelCallback open_cb) {
const ChannelId id = FindAvailableChannelId();
if (id == kInvalidChannelId) {
bt_log(ERROR, "l2cap", "No dynamic channel IDs available");
open_cb(nullptr);
return;
}
auto iter = channels_.emplace(id, MakeOutbound(psm, id, params)).first;
ActivateChannel(iter->second.get(), std::move(open_cb), true);
}
void DynamicChannelRegistry::CloseChannel(ChannelId local_cid) {
DynamicChannel* channel = FindChannelByLocalId(local_cid);
if (!channel) {
return;
}
ZX_DEBUG_ASSERT(channel->IsConnected());
auto disconn_done_cb = [self = weak_ptr_factory_.GetWeakPtr(), channel] {
if (!self) {
return;
}
self->RemoveChannel(channel);
};
channel->Disconnect(std::move(disconn_done_cb));
}
DynamicChannelRegistry::DynamicChannelRegistry(ChannelId largest_channel_id,
DynamicChannelCallback close_cb,
ServiceRequestCallback service_request_cb)
: largest_channel_id_(largest_channel_id),
close_cb_(std::move(close_cb)),
service_request_cb_(std::move(service_request_cb)),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(largest_channel_id_ >= kFirstDynamicChannelId);
ZX_DEBUG_ASSERT(close_cb_);
ZX_DEBUG_ASSERT(service_request_cb_);
}
DynamicChannel* DynamicChannelRegistry::RequestService(PSM psm, ChannelId local_cid,
ChannelId remote_cid) {
ZX_DEBUG_ASSERT(local_cid != kInvalidChannelId);
auto service_info = service_request_cb_(psm);
if (!service_info) {
bt_log(WARN, "l2cap", "No service found for PSM %#.4x from %#.4x", psm, remote_cid);
return nullptr;
}
auto iter =
channels_
.emplace(local_cid, MakeInbound(psm, local_cid, remote_cid, service_info->channel_params))
.first;
ActivateChannel(iter->second.get(), std::move(service_info->channel_cb), false);
return iter->second.get();
}
ChannelId DynamicChannelRegistry::FindAvailableChannelId() const {
for (ChannelId id = kFirstDynamicChannelId; id != largest_channel_id_ + 1; id++) {
if (channels_.count(id) == 0) {
return id;
}
}
return kInvalidChannelId;
}
DynamicChannel* DynamicChannelRegistry::FindChannelByLocalId(ChannelId local_cid) const {
auto iter = channels_.find(local_cid);
if (iter == channels_.end()) {
return nullptr;
}
return iter->second.get();
}
DynamicChannel* DynamicChannelRegistry::FindChannelByRemoteId(ChannelId remote_cid) const {
for (auto& [id, channel_ptr] : channels_) {
if (channel_ptr->remote_cid() == remote_cid) {
return channel_ptr.get();
}
}
return nullptr;
}
void DynamicChannelRegistry::ForEach(fit::function<void(DynamicChannel*)> f) const {
for (auto& [id, channel_ptr] : channels_) {
f(channel_ptr.get());
}
}
void DynamicChannelRegistry::ActivateChannel(DynamicChannel* channel,
DynamicChannelCallback open_cb, bool pass_failed) {
// It's safe to capture |this| here because the callback will be owned by the
// DynamicChannel, which this registry owns.
auto return_chan = [this, channel, open_cb = std::move(open_cb), pass_failed]() mutable {
if (channel->IsOpen()) {
open_cb(channel);
return;
}
bt_log(TRACE, "l2cap", "Failed to open dynamic channel %#.4x (remote %#.4x) for PSM %#.4x",
channel->local_cid(), channel->remote_cid(), channel->psm());
// TODO(NET-1084): Maybe negotiate channel parameters here? For now, just
// disconnect the channel.
// Move the callback to the stack to prepare for channel destruction.
auto pass_failure = [open_cb = std::move(open_cb), pass_failed] {
if (pass_failed) {
open_cb(nullptr);
}
};
// This lambda is owned by the channel, so captures are no longer valid
// after this call.
auto disconn_done_cb = [self = weak_ptr_factory_.GetWeakPtr(), channel] {
if (!self) {
return;
}
self->RemoveChannel(channel);
};
channel->Disconnect(std::move(disconn_done_cb));
pass_failure();
};
channel->Open(std::move(return_chan));
}
void DynamicChannelRegistry::OnChannelDisconnected(DynamicChannel* channel) {
if (channel->opened()) {
close_cb_(channel);
}
RemoveChannel(channel);
}
void DynamicChannelRegistry::RemoveChannel(DynamicChannel* channel) {
ZX_DEBUG_ASSERT(channel);
ZX_DEBUG_ASSERT(!channel->IsConnected());
auto iter = channels_.find(channel->local_cid());
if (iter == channels_.end()) {
return;
}
if (channel != iter->second.get()) {
return;
}
channels_.erase(iter);
}
} // namespace internal
} // namespace l2cap
} // namespace bt