| // 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 |