• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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