xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/dynamic_channel_registry.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/internal/host/l2cap/dynamic_channel_registry.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
18 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
19 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
20 
21 namespace bt::l2cap::internal {
22 // Run return callbacks on the L2CAP thread. LogicalLink takes care of out-of-
23 // thread dispatch for delivering the pointer to the channel.
OpenOutbound(Psm psm,ChannelParameters params,DynamicChannelCallback open_cb)24 void DynamicChannelRegistry::OpenOutbound(Psm psm,
25                                           ChannelParameters params,
26                                           DynamicChannelCallback open_cb) {
27   const ChannelId id = FindAvailableChannelId();
28   if (id == kInvalidChannelId) {
29     bt_log(ERROR, "l2cap", "No dynamic channel IDs available");
30     open_cb(nullptr);
31     return;
32   }
33 
34   auto iter = channels_.emplace(id, MakeOutbound(psm, id, params)).first;
35   ActivateChannel(iter->second.get(), std::move(open_cb), /*pass_failed=*/true);
36 }
37 
CloseChannel(ChannelId local_cid,fit::closure close_callback)38 void DynamicChannelRegistry::CloseChannel(ChannelId local_cid,
39                                           fit::closure close_callback) {
40   DynamicChannel* channel = FindChannelByLocalId(local_cid);
41   if (!channel) {
42     close_callback();
43     return;
44   }
45 
46   PW_DCHECK(channel->IsConnected());
47   auto disconn_done_cb =
48       [self = GetWeakPtr(), close_cb = std::move(close_callback), channel] {
49         if (!self.is_alive()) {
50           close_cb();
51           return;
52         }
53         self->RemoveChannel(channel);
54         close_cb();
55       };
56   channel->Disconnect(std::move(disconn_done_cb));
57 }
58 
DynamicChannelRegistry(uint16_t max_num_channels,DynamicChannelCallback close_cb,ServiceRequestCallback service_request_cb,bool random_channel_ids)59 DynamicChannelRegistry::DynamicChannelRegistry(
60     uint16_t max_num_channels,
61     DynamicChannelCallback close_cb,
62     ServiceRequestCallback service_request_cb,
63     bool random_channel_ids)
64     : WeakSelf(this),
65       max_num_channels_(max_num_channels),
66       close_cb_(std::move(close_cb)),
67       service_request_cb_(std::move(service_request_cb)),
68       random_channel_ids_(random_channel_ids) {
69   PW_DCHECK(max_num_channels > 0);
70   PW_DCHECK(max_num_channels < 65473);
71   PW_DCHECK(close_cb_);
72   PW_DCHECK(service_request_cb_);
73 }
74 
RequestService(Psm psm,ChannelId local_cid,ChannelId remote_cid)75 DynamicChannel* DynamicChannelRegistry::RequestService(Psm psm,
76                                                        ChannelId local_cid,
77                                                        ChannelId remote_cid) {
78   PW_DCHECK(local_cid != kInvalidChannelId);
79 
80   auto service_info = service_request_cb_(psm);
81   if (!service_info) {
82     bt_log(WARN,
83            "l2cap",
84            "No service found for PSM %#.4x from %#.4x",
85            psm,
86            remote_cid);
87     return nullptr;
88   }
89 
90   auto iter =
91       channels_
92           .emplace(
93               local_cid,
94               MakeInbound(
95                   psm, local_cid, remote_cid, service_info->channel_params))
96           .first;
97   ActivateChannel(iter->second.get(),
98                   std::move(service_info->channel_cb),
99                   /*pass_failed=*/false);
100   return iter->second.get();
101 }
102 
FindAvailableChannelId()103 ChannelId DynamicChannelRegistry::FindAvailableChannelId() {
104   uint16_t offset = 0;
105   if (random_channel_ids_) {
106     random_generator()->GetInt(offset, max_num_channels_);
107   }
108   for (uint16_t i = 0; i < max_num_channels_; i++) {
109     ChannelId id = kFirstDynamicChannelId + ((offset + i) % max_num_channels_);
110     if (channels_.count(id) == 0) {
111       return id;
112     }
113   }
114 
115   return kInvalidChannelId;
116 }
117 
AliveChannelCount() const118 size_t DynamicChannelRegistry::AliveChannelCount() const {
119   return channels_.size();
120 }
121 
FindChannelByLocalId(ChannelId local_cid) const122 DynamicChannel* DynamicChannelRegistry::FindChannelByLocalId(
123     ChannelId local_cid) const {
124   auto iter = channels_.find(local_cid);
125   if (iter == channels_.end()) {
126     return nullptr;
127   }
128   return iter->second.get();
129 }
130 
FindChannelByRemoteId(ChannelId remote_cid) const131 DynamicChannel* DynamicChannelRegistry::FindChannelByRemoteId(
132     ChannelId remote_cid) const {
133   for (auto& [id, channel_ptr] : channels_) {
134     if (channel_ptr->remote_cid() == remote_cid) {
135       return channel_ptr.get();
136     }
137   }
138   return nullptr;
139 }
140 
ForEach(fit::function<void (DynamicChannel *)> f) const141 void DynamicChannelRegistry::ForEach(
142     fit::function<void(DynamicChannel*)> f) const {
143   for (auto iter = channels_.begin(); iter != channels_.end();) {
144     // f() may remove the channel from the registry, so get next iterator to
145     // avoid invalidation. Only the erased iterator is invalidated.
146     auto next = std::next(iter);
147     f(iter->second.get());
148     iter = next;
149   }
150 }
151 
ActivateChannel(DynamicChannel * channel,DynamicChannelCallback open_callback,bool pass_failed)152 void DynamicChannelRegistry::ActivateChannel(
153     DynamicChannel* channel,
154     DynamicChannelCallback open_callback,
155     bool pass_failed) {
156   // It's safe to capture |this| here because the callback will be owned by the
157   // DynamicChannel, which this registry owns.
158   auto return_chan = [this,
159                       channel,
160                       open_cb = std::move(open_callback),
161                       pass_failed]() mutable {
162     if (channel->IsOpen()) {
163       open_cb(channel);
164       return;
165     }
166 
167     bt_log(DEBUG,
168            "l2cap",
169            "Failed to open dynamic channel %#.4x (remote %#.4x) for PSM %#.4x",
170            channel->local_cid(),
171            channel->remote_cid(),
172            channel->psm());
173 
174     // TODO(fxbug.dev/42057179): Maybe negotiate channel parameters here?
175     // For now, just disconnect the channel. Move the callback to the stack
176     // to prepare for channel destruction.
177     auto pass_failure = [cb = std::move(open_cb), pass_failed] {
178       if (pass_failed) {
179         cb(nullptr);
180       }
181     };
182 
183     // This lambda is owned by the channel, so captures are no longer valid
184     // after this call.
185     auto disconn_done_cb = [self = GetWeakPtr(), channel] {
186       if (!self.is_alive()) {
187         return;
188       }
189       self->RemoveChannel(channel);
190     };
191     channel->Disconnect(std::move(disconn_done_cb));
192 
193     pass_failure();
194   };
195 
196   channel->Open(std::move(return_chan));
197 }
198 
OnChannelDisconnected(DynamicChannel * channel)199 void DynamicChannelRegistry::OnChannelDisconnected(DynamicChannel* channel) {
200   if (channel->opened()) {
201     close_cb_(channel);
202   }
203   RemoveChannel(channel);
204 }
205 
RemoveChannel(DynamicChannel * channel)206 void DynamicChannelRegistry::RemoveChannel(DynamicChannel* channel) {
207   PW_DCHECK(channel);
208   PW_DCHECK(!channel->IsConnected());
209 
210   auto iter = channels_.find(channel->local_cid());
211   if (iter == channels_.end()) {
212     return;
213   }
214 
215   if (channel != iter->second.get()) {
216     return;
217   }
218 
219   channels_.erase(iter);
220 }
221 
222 }  // namespace bt::l2cap::internal
223