1 // Copyright 2024 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/le_dynamic_channel.h"
16
17 #include <pw_bluetooth/l2cap_frames.emb.h>
18
19 #include <variant>
20
21 #include "pw_bluetooth_sapphire/internal/host/l2cap/l2cap_defs.h"
22 #include "pw_bluetooth_sapphire/internal/host/l2cap/low_energy_command_handler.h"
23 #include "pw_bluetooth_sapphire/internal/host/l2cap/types.h"
24
25 namespace bt::l2cap::internal {
26 namespace {
27
28 constexpr uint16_t kLeDynamicChannelCount =
29 kLastLEDynamicChannelId - kFirstDynamicChannelId + 1;
30
31 // Helper to determine initial state based on whether we have received or need
32 // to send the connection request.
InitialState(bool has_remote_channel)33 LeDynamicChannel::State InitialState(bool has_remote_channel) {
34 return LeDynamicChannel::State{.exchanged_connection_request =
35 has_remote_channel};
36 }
37
ConvertMode(AnyChannelMode mode)38 CreditBasedFlowControlMode ConvertMode(AnyChannelMode mode) {
39 // LE dynamic channels only support credit-based flow control modes.
40 PW_CHECK(std::holds_alternative<CreditBasedFlowControlMode>(mode));
41 return std::get<CreditBasedFlowControlMode>(mode);
42 }
43
44 } // namespace
45
LeDynamicChannelRegistry(SignalingChannelInterface * sig,DynamicChannelCallback close_cb,ServiceRequestCallback service_request_cb,bool random_channel_ids)46 LeDynamicChannelRegistry::LeDynamicChannelRegistry(
47 SignalingChannelInterface* sig,
48 DynamicChannelCallback close_cb,
49 ServiceRequestCallback service_request_cb,
50 bool random_channel_ids)
51 : DynamicChannelRegistry(kLeDynamicChannelCount,
52 std::move(close_cb),
53 std::move(service_request_cb),
54 random_channel_ids),
55 sig_(sig) {
56 PW_DCHECK(sig_);
57 LowEnergyCommandHandler cmd_handler(sig_);
58 cmd_handler.ServeLeCreditBasedConnectionRequest(
59 fit::bind_member<
60 &LeDynamicChannelRegistry::OnRxLeCreditBasedConnectionRequest>(this));
61 }
62
MakeOutbound(Psm psm,ChannelId local_cid,ChannelParameters params)63 DynamicChannelPtr LeDynamicChannelRegistry::MakeOutbound(
64 Psm psm, ChannelId local_cid, ChannelParameters params) {
65 return LeDynamicChannel::MakeOutbound(this, sig_, psm, local_cid, params);
66 }
67
MakeInbound(Psm psm,ChannelId local_cid,ChannelId remote_cid,ChannelParameters params)68 DynamicChannelPtr LeDynamicChannelRegistry::MakeInbound(
69 Psm psm,
70 ChannelId local_cid,
71 ChannelId remote_cid,
72 ChannelParameters params) {
73 return LeDynamicChannel::MakeInbound(
74 this, sig_, psm, local_cid, remote_cid, params);
75 }
76
OnRxLeCreditBasedConnectionRequest(uint16_t psm,uint16_t remote_cid,uint16_t maximum_transmission_unit,uint16_t maximum_payload_size,uint16_t initial_credits,LowEnergyCommandHandler::LeCreditBasedConnectionResponder * responder)77 void LeDynamicChannelRegistry::OnRxLeCreditBasedConnectionRequest(
78 uint16_t psm,
79 uint16_t remote_cid,
80 uint16_t maximum_transmission_unit,
81 uint16_t maximum_payload_size,
82 uint16_t initial_credits,
83 LowEnergyCommandHandler::LeCreditBasedConnectionResponder* responder) {
84 bt_log(TRACE,
85 "l2cap-le",
86 "Got Connection Request for PSM %#.4x from channel %#.4x",
87 psm,
88 remote_cid);
89
90 if (remote_cid < kFirstDynamicChannelId) {
91 bt_log(DEBUG,
92 "l2cap-le",
93 "Invalid source CID; rejecting connection for PSM %#.4x from "
94 "channel %#.4x",
95 psm,
96 remote_cid);
97 responder->Send(
98 0, 0, 0, 0, LECreditBasedConnectionResult::kInvalidSourceCID);
99 return;
100 }
101
102 if (FindChannelByRemoteId(remote_cid) != nullptr) {
103 bt_log(DEBUG,
104 "l2cap-le",
105 "Remote CID already in use; rejecting connection for PSM %#.4x from "
106 "channel %#.4x",
107 psm,
108 remote_cid);
109 responder->Send(
110 0, 0, 0, 0, LECreditBasedConnectionResult::kSourceCIDAlreadyAllocated);
111 return;
112 }
113
114 ChannelId local_cid = FindAvailableChannelId();
115 if (local_cid == kInvalidChannelId) {
116 bt_log(DEBUG,
117 "l2cap-le",
118 "Out of IDs; rejecting connection for PSM %#.4x from channel %#.4x",
119 psm,
120 remote_cid);
121 responder->Send(0, 0, 0, 0, LECreditBasedConnectionResult::kNoResources);
122 return;
123 }
124
125 DynamicChannel* dyn_chan = RequestService(psm, local_cid, remote_cid);
126 if (!dyn_chan) {
127 bt_log(DEBUG,
128 "l2cap-le",
129 "Rejecting connection for unsupported PSM %#.4x from channel %#.4x",
130 psm,
131 remote_cid);
132 responder->Send(
133 0, 0, 0, 0, LECreditBasedConnectionResult::kPsmNotSupported);
134 return;
135 }
136
137 static_cast<LeDynamicChannel*>(dyn_chan)->CompleteInboundConnection(
138 LeChannelConfig{
139 .mtu = maximum_transmission_unit,
140 .mps = maximum_payload_size,
141 .initial_credits = initial_credits,
142 },
143 responder);
144 }
145
MakeOutbound(DynamicChannelRegistry * registry,SignalingChannelInterface * signaling_channel,Psm psm,ChannelId local_cid,ChannelParameters params)146 std::unique_ptr<LeDynamicChannel> LeDynamicChannel::MakeOutbound(
147 DynamicChannelRegistry* registry,
148 SignalingChannelInterface* signaling_channel,
149 Psm psm,
150 ChannelId local_cid,
151 ChannelParameters params) {
152 return std::unique_ptr<LeDynamicChannel>(
153 new LeDynamicChannel(registry,
154 signaling_channel,
155 psm,
156 local_cid,
157 kInvalidChannelId,
158 params,
159 true));
160 }
161
MakeInbound(DynamicChannelRegistry * registry,SignalingChannelInterface * signaling_channel,Psm psm,ChannelId local_cid,ChannelId remote_cid,ChannelParameters params)162 std::unique_ptr<LeDynamicChannel> LeDynamicChannel::MakeInbound(
163 DynamicChannelRegistry* registry,
164 SignalingChannelInterface* signaling_channel,
165 Psm psm,
166 ChannelId local_cid,
167 ChannelId remote_cid,
168 ChannelParameters params) {
169 return std::unique_ptr<LeDynamicChannel>(new LeDynamicChannel(
170 registry, signaling_channel, psm, local_cid, remote_cid, params, false));
171 }
172
ToString() const173 std::string LeDynamicChannel::State::ToString() const {
174 return std::string("{exchanged_connection_request: ") +
175 (exchanged_connection_request ? "true" : "false") +
176 ", exchanged_connection_response: " +
177 (exchanged_connection_response ? "true" : "false") +
178 ", exchanged_disconnect_request: " +
179 (exchanged_disconnect_request ? "true" : "false") + "}";
180 }
181
LeDynamicChannel(DynamicChannelRegistry * registry,SignalingChannelInterface * signaling_channel,Psm psm,ChannelId local_cid,ChannelId remote_cid,ChannelParameters params,bool is_outbound)182 LeDynamicChannel::LeDynamicChannel(DynamicChannelRegistry* registry,
183 SignalingChannelInterface* signaling_channel,
184 Psm psm,
185 ChannelId local_cid,
186 ChannelId remote_cid,
187 ChannelParameters params,
188 bool is_outbound)
189 : DynamicChannel(registry, psm, local_cid, remote_cid),
190 signaling_channel_(signaling_channel),
191 flow_control_mode_(ConvertMode(params.mode.value_or(
192 CreditBasedFlowControlMode::kLeCreditBasedFlowControl))),
193 state_(InitialState(remote_cid != kInvalidChannelId)),
194 local_config_(
195 LeChannelConfig{.mtu = params.max_rx_sdu_size.value_or(kDefaultMTU),
196 .mps = kMaxInboundPduPayloadSize}),
197 remote_config_(std::nullopt),
198 is_outbound_(is_outbound),
199 weak_self_(this) {}
200
TriggerOpenCallback()201 void LeDynamicChannel::TriggerOpenCallback() {
202 auto cb = std::move(open_result_cb_);
203 if (cb) {
204 cb();
205 }
206 }
207
Open(fit::closure open_cb)208 void LeDynamicChannel::Open(fit::closure open_cb) {
209 PW_CHECK(!open_result_cb_, "open callback already set");
210 open_result_cb_ = std::move(open_cb);
211
212 if (!is_outbound_) {
213 // Only save the callback and return early. Inbound channels complete their
214 // open in `CompleteInboundConnection` as we need more info from the request
215 // packet to complete the open.
216 return;
217 }
218
219 if (state_.exchanged_connection_request) {
220 TriggerOpenCallback();
221 return;
222 }
223
224 auto on_conn_rsp =
225 [self = weak_self_.GetWeakPtr()](
226 const LowEnergyCommandHandler::LeCreditBasedConnectionResponse&
227 rsp) mutable {
228 if (self.is_alive()) {
229 self->OnRxLeCreditConnRsp(rsp);
230 self->TriggerOpenCallback();
231 }
232 };
233
234 auto on_conn_rsp_timeout = [cid = local_cid()] {
235 bt_log(WARN,
236 "l2cap-le",
237 "Channel %#.4x: Timed out waiting for Connection Response",
238 cid);
239 };
240
241 LowEnergyCommandHandler cmd_handler(signaling_channel_,
242 std::move(on_conn_rsp_timeout));
243 if (!cmd_handler.SendLeCreditBasedConnectionRequest(psm(),
244 local_cid(),
245 local_config_.mtu,
246 local_config_.mps,
247 0,
248 on_conn_rsp)) {
249 bt_log(ERROR,
250 "l2cap-le",
251 "Channel %#.4x: Failed to send Connection Request",
252 local_cid());
253 TriggerOpenCallback();
254 return;
255 }
256
257 state_.exchanged_connection_request = true;
258 }
259
Disconnect(DisconnectDoneCallback done_cb)260 void LeDynamicChannel::Disconnect(DisconnectDoneCallback done_cb) {
261 PW_CHECK(done_cb);
262 if (!IsConnected()) {
263 done_cb();
264 return;
265 }
266
267 auto on_discon_rsp =
268 [local_cid = local_cid(),
269 remote_cid = remote_cid(),
270 self = weak_self_.GetWeakPtr(),
271 done_cb_shared = done_cb.share()](
272 const LowEnergyCommandHandler::DisconnectionResponse& rsp) mutable {
273 if (rsp.local_cid() != local_cid || rsp.remote_cid() != remote_cid) {
274 bt_log(WARN,
275 "l2cap-le",
276 "Channel %#.4x: Got Disconnection Response with ID %#.4x/"
277 "remote ID %#.4x on channel with remote ID %#.4x",
278 local_cid,
279 rsp.local_cid(),
280 rsp.remote_cid(),
281 remote_cid);
282 } else {
283 bt_log(TRACE,
284 "l2cap-le",
285 "Channel %#.4x: Got Disconnection Response",
286 local_cid);
287 }
288
289 if (self.is_alive()) {
290 done_cb_shared();
291 }
292 };
293
294 auto on_discon_rsp_timeout = [local_cid = local_cid(),
295 self = weak_self_.GetWeakPtr(),
296 done_cb_shared = done_cb.share()]() mutable {
297 bt_log(WARN,
298 "l2cap-le",
299 "Channel %#.4x: Timed out waiting for Disconnection Response; "
300 "completing disconnection",
301 local_cid);
302 if (self.is_alive()) {
303 done_cb_shared();
304 }
305 };
306
307 LowEnergyCommandHandler cmd_handler(signaling_channel_,
308 std::move(on_discon_rsp_timeout));
309 if (!cmd_handler.SendDisconnectionRequest(
310 remote_cid(), local_cid(), std::move(on_discon_rsp))) {
311 bt_log(WARN,
312 "l2cap-le",
313 "Channel %#.4x: Failed to send Disconnection Request",
314 local_cid());
315 done_cb();
316 return;
317 }
318
319 state_.exchanged_disconnect_request = true;
320 bt_log(TRACE,
321 "l2cap-le",
322 "Channel %#.4x: Sent Disconnection Request",
323 local_cid());
324 }
325
IsConnected() const326 bool LeDynamicChannel::IsConnected() const {
327 return state_.exchanged_connection_request &&
328 state_.exchanged_connection_response &&
329 !state_.exchanged_disconnect_request &&
330 remote_cid() != kInvalidChannelId;
331 }
332
IsOpen() const333 bool LeDynamicChannel::IsOpen() const {
334 // Since dynamic LE L2CAP channels don't have channel configuration state
335 // machines, `IsOpen` and `IsConnected` are equivalent.
336 return IsConnected();
337 }
338
info() const339 ChannelInfo LeDynamicChannel::info() const {
340 PW_CHECK(remote_config_.has_value());
341 return ChannelInfo::MakeCreditBasedFlowControlMode(
342 flow_control_mode_,
343 local_config_.mtu,
344 remote_config_->mtu,
345 remote_config_->mps,
346 remote_config_->initial_credits);
347 }
348
OnRxLeCreditConnRsp(const LowEnergyCommandHandler::LeCreditBasedConnectionResponse & rsp)349 void LeDynamicChannel::OnRxLeCreditConnRsp(
350 const LowEnergyCommandHandler::LeCreditBasedConnectionResponse& rsp) {
351 if (state_.exchanged_connection_response ||
352 !state_.exchanged_connection_request ||
353 remote_cid() != kInvalidChannelId) {
354 bt_log(ERROR,
355 "l2cap-le",
356 "Channel %#.4x: Unexpected Connection Response, state %s",
357 local_cid(),
358 bt_str(state_));
359 return;
360 }
361
362 if (rsp.status() == LowEnergyCommandHandler::Status::kReject) {
363 bt_log(ERROR,
364 "l2cap-le",
365 "Channel %#.4x: Connection Request rejected reason %#.4hx",
366 local_cid(),
367 static_cast<unsigned short>(rsp.reject_reason()));
368 return;
369 }
370
371 if (rsp.result() != LECreditBasedConnectionResult::kSuccess) {
372 bt_log(ERROR,
373 "l2cap-le",
374 "Channel %#.4x: Connection request failed, result %#.4hx",
375 local_cid(),
376 static_cast<uint16_t>(rsp.result()));
377 return;
378 }
379
380 if (rsp.destination_cid() < kFirstDynamicChannelId) {
381 bt_log(ERROR,
382 "l2cap-le",
383 "Channel %#.4x: Remote channel ID is invalid.",
384 local_cid());
385 return;
386 }
387
388 if (!SetRemoteChannelId(rsp.destination_cid())) {
389 bt_log(ERROR,
390 "l2cap-le",
391 "Channel %#.4x: Remote channel ID %#.4x is not unique",
392 local_cid(),
393 rsp.destination_cid());
394 return;
395 }
396
397 bt_log(TRACE,
398 "l2cap-le",
399 "Channel %#.4x: Got remote channel ID %#.4x",
400 local_cid(),
401 remote_cid());
402
403 remote_config_ = LeChannelConfig{.mtu = rsp.mtu(),
404 .mps = rsp.mps(),
405 .initial_credits = rsp.initial_credits()};
406 state_.exchanged_connection_response = true;
407 set_opened();
408 }
409
CompleteInboundConnection(LeChannelConfig remote_config,LowEnergyCommandHandler::LeCreditBasedConnectionResponder * responder)410 void LeDynamicChannel::CompleteInboundConnection(
411 LeChannelConfig remote_config,
412 LowEnergyCommandHandler::LeCreditBasedConnectionResponder* responder) {
413 remote_config_ = remote_config;
414 responder->Send(local_cid(),
415 local_config_.mtu,
416 local_config_.mps,
417 local_config_.initial_credits,
418 LECreditBasedConnectionResult::kSuccess);
419 state_.exchanged_connection_response = true;
420 set_opened();
421 TriggerOpenCallback();
422 }
423
424 } // namespace bt::l2cap::internal
425