xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/l2cap/le_dynamic_channel.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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