blob: f6944b6fd772db1464c3df9bc71fb4ee2badd539 [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_defs.h"
namespace bt::l2cap::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, fit::closure close_cb) {
DynamicChannel* channel = FindChannelByLocalId(local_cid);
if (!channel) {
close_cb();
return;
}
ZX_DEBUG_ASSERT(channel->IsConnected());
auto disconn_done_cb = [self = weak_ptr_factory_.GetWeakPtr(), close_cb = std::move(close_cb),
channel] {
if (!self) {
close_cb();
return;
}
self->RemoveChannel(channel);
close_cb();
};
channel->Disconnect(std::move(disconn_done_cb));
}
DynamicChannelRegistry::DynamicChannelRegistry(uint16_t max_num_channels,
DynamicChannelCallback close_cb,
ServiceRequestCallback service_request_cb,
bool random_channel_ids)
: max_num_channels_(max_num_channels),
close_cb_(std::move(close_cb)),
service_request_cb_(std::move(service_request_cb)),
weak_ptr_factory_(this) {
ZX_DEBUG_ASSERT(max_num_channels > 0);
ZX_DEBUG_ASSERT(max_num_channels < 65473);
ZX_DEBUG_ASSERT(close_cb_);
ZX_DEBUG_ASSERT(service_request_cb_);
if (random_channel_ids) {
rng_ = std::default_random_engine(Random<uint64_t>());
}
}
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() {
uint16_t offset = 0;
if (rng_.has_value()) {
std::uniform_int_distribution<ChannelId> distribution(0, max_num_channels_);
offset = distribution(*rng_);
}
for (uint16_t i = 0; i < max_num_channels_; i++) {
ChannelId id = kFirstDynamicChannelId + ((offset + i) % max_num_channels_);
if (channels_.count(id) == 0) {
return id;
}
}
return kInvalidChannelId;
}
size_t DynamicChannelRegistry::AliveChannelCount() const { return channels_.size(); }
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 iter = channels_.begin(); iter != channels_.end();) {
// f() may remove the channel from the registry, so get next iterator to avoid invalidation.
// Only the erased iterator is invalidated.
auto next = std::next(iter);
f(iter->second.get());
iter = next;
}
}
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(DEBUG, "l2cap", "Failed to open dynamic channel %#.4x (remote %#.4x) for PSM %#.4x",
channel->local_cid(), channel->remote_cid(), channel->psm());
// TODO(fxbug.dev/1059): 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 bt::l2cap::internal