| // 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 "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| |
| namespace bt { |
| namespace l2cap { |
| namespace internal { |
| |
| DynamicChannelRegistry::~DynamicChannelRegistry() { |
| // Clean up connected channels. |
| for (auto& id_and_chan : channels_) { |
| auto& chan = id_and_chan.second; |
| if (chan->IsConnected()) { |
| chan->Disconnect(); |
| } |
| } |
| } |
| |
| // 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, |
| 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)).first; |
| ActivateChannel(iter->second.get(), std::move(open_cb), true); |
| } |
| |
| void DynamicChannelRegistry::CloseChannel(ChannelId local_cid) { |
| DynamicChannel* channel = FindChannel(local_cid); |
| if (!channel) { |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(channel->IsConnected()); |
| channel->Disconnect(); |
| RemoveChannel(channel); |
| } |
| |
| 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)) { |
| 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); |
| |
| DynamicChannelCallback return_chan_cb = service_request_cb_(psm); |
| if (!return_chan_cb) { |
| 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)) |
| .first; |
| ActivateChannel(iter->second.get(), std::move(return_chan_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::FindChannel(ChannelId local_cid) const { |
| auto iter = channels_.find(local_cid); |
| if (iter == channels_.end()) { |
| return nullptr; |
| } |
| return iter->second.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]() { |
| 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. |
| if (channel->IsConnected()) { |
| channel->Disconnect(); |
| } |
| if (pass_failed) { |
| open_cb(nullptr); |
| } |
| |
| // This lambda is owned by the channel, so captures are no longer valid |
| // after this call. |
| RemoveChannel(channel); |
| }; |
| |
| 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 |