blob: 8cf756cf5729ca1613df713adabbae010b57de3c [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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/dynamic_channel_registry.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
namespace bt::l2cap::internal {
// 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), /*pass_failed=*/true);
}
void DynamicChannelRegistry::CloseChannel(ChannelId local_cid,
fit::closure close_cb) {
DynamicChannel* channel = FindChannelByLocalId(local_cid);
if (!channel) {
close_cb();
return;
}
BT_DEBUG_ASSERT(channel->IsConnected());
auto disconn_done_cb =
[self = GetWeakPtr(), close_cb = std::move(close_cb), channel] {
if (!self.is_alive()) {
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)
: WeakSelf(this),
max_num_channels_(max_num_channels),
close_cb_(std::move(close_cb)),
service_request_cb_(std::move(service_request_cb)),
random_channel_ids_(random_channel_ids) {
BT_DEBUG_ASSERT(max_num_channels > 0);
BT_DEBUG_ASSERT(max_num_channels < 65473);
BT_DEBUG_ASSERT(close_cb_);
BT_DEBUG_ASSERT(service_request_cb_);
}
DynamicChannel* DynamicChannelRegistry::RequestService(Psm psm,
ChannelId local_cid,
ChannelId remote_cid) {
BT_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),
/*pass_failed=*/false);
return iter->second.get();
}
ChannelId DynamicChannelRegistry::FindAvailableChannelId() {
uint16_t offset = 0;
if (random_channel_ids_) {
random_generator()->GetInt(offset, max_num_channels_);
}
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(https://fxbug.dev/42057179): 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 = GetWeakPtr(), channel] {
if (!self.is_alive()) {
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) {
BT_DEBUG_ASSERT(channel);
BT_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