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