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