• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/sco/sco_connection_manager.h"
16 
17 #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h"
18 #include "pw_bluetooth_sapphire/internal/host/hci/sco_connection.h"
19 
20 namespace bt::sco {
21 namespace {
22 
ConnectionParametersSupportScoTransport(bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter> & params)23 bool ConnectionParametersSupportScoTransport(
24     bt::StaticPacket<
25         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>& params) {
26   return params.view().packet_types().hv1().Read() ||
27          params.view().packet_types().hv2().Read() ||
28          params.view().packet_types().hv3().Read();
29 }
30 
ConnectionParametersSupportEscoTransport(bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter> & params)31 bool ConnectionParametersSupportEscoTransport(
32     bt::StaticPacket<
33         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>& params) {
34   return params.view().packet_types().ev3().Read() ||
35          params.view().packet_types().ev4().Read() ||
36          params.view().packet_types().ev5().Read();
37 }
38 
39 }  // namespace
40 
ScoConnectionManager(PeerId peer_id,hci_spec::ConnectionHandle acl_handle,DeviceAddress peer_address,DeviceAddress local_address,hci::Transport::WeakPtr transport)41 ScoConnectionManager::ScoConnectionManager(
42     PeerId peer_id,
43     hci_spec::ConnectionHandle acl_handle,
44     DeviceAddress peer_address,
45     DeviceAddress local_address,
46     hci::Transport::WeakPtr transport)
47     : next_req_id_(0u),
48       peer_id_(peer_id),
49       local_address_(local_address),
50       peer_address_(peer_address),
51       acl_handle_(acl_handle),
52       transport_(std::move(transport)),
53       weak_ptr_factory_(this) {
54   BT_ASSERT(transport_.is_alive());
55 
56   AddEventHandler(
57       hci_spec::kSynchronousConnectionCompleteEventCode,
58       fit::bind_member<&ScoConnectionManager::OnSynchronousConnectionComplete>(
59           this));
60   AddEventHandler(
61       hci_spec::kConnectionRequestEventCode,
62       fit::bind_member<&ScoConnectionManager::OnConnectionRequest>(this));
63 }
64 
~ScoConnectionManager()65 ScoConnectionManager::~ScoConnectionManager() {
66   // Remove all event handlers
67   for (auto handler_id : event_handler_ids_) {
68     transport_->command_channel()->RemoveEventHandler(handler_id);
69   }
70 
71   // Close all connections.  Close may remove the connection from the map, so we
72   // can't use an iterator, which would be invalidated by the removal.
73   while (connections_.size() > 0) {
74     auto pair = connections_.begin();
75     hci_spec::ConnectionHandle handle = pair->first;
76     ScoConnection* conn = pair->second.get();
77 
78     conn->Close();
79     // Make sure we erase the connection if Close doesn't so the loop
80     // terminates.
81     connections_.erase(handle);
82   }
83 
84   if (queued_request_) {
85     CancelRequestWithId(queued_request_->id);
86   }
87 
88   if (in_progress_request_) {
89     bt_log(DEBUG,
90            "gap-sco",
91            "ScoConnectionManager destroyed while request in progress");
92     // Clear in_progress_request_ before calling callback to prevent calls to
93     // CompleteRequest() during execution of the callback (e.g. due to
94     // destroying the RequestHandle).
95     ConnectionRequest request = std::move(in_progress_request_.value());
96     in_progress_request_.reset();
97     request.callback(fit::error(HostError::kCanceled));
98   }
99 }
100 
OpenConnection(bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter> parameters,OpenConnectionCallback callback)101 ScoConnectionManager::RequestHandle ScoConnectionManager::OpenConnection(
102     bt::StaticPacket<
103         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
104         parameters,
105     OpenConnectionCallback callback) {
106   return QueueRequest(
107       /*initiator=*/true,
108       {std::move(parameters)},
109       [cb = std::move(callback)](ConnectionResult result) mutable {
110         // Convert result type.
111         if (result.is_error()) {
112           cb(fit::error(result.take_error()));
113           return;
114         }
115         cb(fit::ok(result.value().first));
116       });
117 }
118 
AcceptConnection(std::vector<bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>> parameters,AcceptConnectionCallback callback)119 ScoConnectionManager::RequestHandle ScoConnectionManager::AcceptConnection(
120     std::vector<bt::StaticPacket<
121         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>>
122         parameters,
123     AcceptConnectionCallback callback) {
124   return QueueRequest(
125       /*initiator=*/false, std::move(parameters), std::move(callback));
126 }
127 
AddEventHandler(const hci_spec::EventCode & code,hci::CommandChannel::EventCallbackVariant cb)128 hci::CommandChannel::EventHandlerId ScoConnectionManager::AddEventHandler(
129     const hci_spec::EventCode& code,
130     hci::CommandChannel::EventCallbackVariant cb) {
131   auto self = weak_ptr_factory_.GetWeakPtr();
132   hci::CommandChannel::EventHandlerId event_id = 0;
133   event_id = std::visit(
134       [this, &self, code](auto&& cb) -> hci::CommandChannel::EventHandlerId {
135         using T = std::decay_t<decltype(cb)>;
136         if constexpr (std::is_same_v<T, hci::CommandChannel::EventCallback>) {
137           return transport_->command_channel()->AddEventHandler(
138               code, [self, cb = std::move(cb)](const hci::EventPacket& event) {
139                 if (!self.is_alive()) {
140                   return hci::CommandChannel::EventCallbackResult::kRemove;
141                 }
142                 return cb(event);
143               });
144         } else if constexpr (std::is_same_v<
145                                  T,
146                                  hci::CommandChannel::EmbossEventCallback>) {
147           return transport_->command_channel()->AddEventHandler(
148               code,
149               [self, cb = std::move(cb)](const hci::EmbossEventPacket& event) {
150                 if (!self.is_alive()) {
151                   return hci::CommandChannel::EventCallbackResult::kRemove;
152                 }
153                 return cb(event);
154               });
155         }
156       },
157       std::move(cb));
158   BT_ASSERT(event_id);
159   event_handler_ids_.push_back(event_id);
160   return event_id;
161 }
162 
163 hci::CommandChannel::EventCallbackResult
OnSynchronousConnectionComplete(const hci::EmbossEventPacket & event)164 ScoConnectionManager::OnSynchronousConnectionComplete(
165     const hci::EmbossEventPacket& event) {
166   const auto params = event.view<
167       pw::bluetooth::emboss::SynchronousConnectionCompleteEventView>();
168   DeviceAddress addr(DeviceAddress::Type::kBREDR,
169                      DeviceAddressBytes(params.bd_addr()));
170 
171   // Ignore events from other peers.
172   if (addr != peer_address_) {
173     return hci::CommandChannel::EventCallbackResult::kContinue;
174   }
175 
176   auto status = event.ToResult();
177   if (bt_is_error(status,
178                   INFO,
179                   "gap-sco",
180                   "SCO connection failed to be established; trying next "
181                   "parameters if available (peer: %s)",
182                   bt_str(peer_id_))) {
183     // A request must be in progress for this event to be generated.
184     CompleteRequestOrTryNextParameters(fit::error(HostError::kFailed));
185     return hci::CommandChannel::EventCallbackResult::kContinue;
186   }
187 
188   // The controller should only report SCO and eSCO link types (other values are
189   // reserved).
190   pw::bluetooth::emboss::LinkType link_type = params.link_type().Read();
191   if (link_type != pw::bluetooth::emboss::LinkType::SCO &&
192       link_type != pw::bluetooth::emboss::LinkType::ESCO) {
193     bt_log(
194         ERROR,
195         "gap-sco",
196         "Received SynchronousConnectionComplete event with invalid link type");
197     return hci::CommandChannel::EventCallbackResult::kContinue;
198   }
199 
200   hci_spec::ConnectionHandle connection_handle =
201       params.connection_handle().Read();
202   auto link = std::make_unique<hci::ScoConnection>(
203       connection_handle, local_address_, peer_address_, transport_);
204 
205   if (!in_progress_request_) {
206     bt_log(ERROR,
207            "gap-sco",
208            "Unexpected SCO connection complete, disconnecting (peer: %s)",
209            bt_str(peer_id_));
210     return hci::CommandChannel::EventCallbackResult::kContinue;
211   }
212 
213   fit::closure deactivated_cb = [this, connection_handle] {
214     BT_ASSERT(connections_.erase(connection_handle));
215   };
216   bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>
217       conn_params = in_progress_request_
218                         ->parameters[in_progress_request_->current_param_index];
219   auto conn = std::make_unique<ScoConnection>(std::move(link),
220                                               std::move(deactivated_cb),
221                                               conn_params,
222                                               transport_->sco_data_channel());
223   ScoConnection::WeakPtr conn_weak = conn->GetWeakPtr();
224 
225   auto [_, success] =
226       connections_.try_emplace(connection_handle, std::move(conn));
227   BT_ASSERT_MSG(success,
228                 "SCO connection already exists with handle %#.4x (peer: %s)",
229                 connection_handle,
230                 bt_str(peer_id_));
231 
232   CompleteRequest(fit::ok(std::make_pair(
233       std::move(conn_weak), in_progress_request_->current_param_index)));
234 
235   return hci::CommandChannel::EventCallbackResult::kContinue;
236 }
237 
238 hci::CommandChannel::EventCallbackResult
OnConnectionRequest(const hci::EmbossEventPacket & event)239 ScoConnectionManager::OnConnectionRequest(const hci::EmbossEventPacket& event) {
240   BT_ASSERT(event.event_code() == hci_spec::kConnectionRequestEventCode);
241   auto params = event.view<pw::bluetooth::emboss::ConnectionRequestEventView>();
242 
243   // Ignore requests for other link types.
244   if (params.link_type().Read() != pw::bluetooth::emboss::LinkType::SCO &&
245       params.link_type().Read() != pw::bluetooth::emboss::LinkType::ESCO) {
246     return hci::CommandChannel::EventCallbackResult::kContinue;
247   }
248 
249   // Ignore requests from other peers.
250   DeviceAddress addr(DeviceAddress::Type::kBREDR,
251                      DeviceAddressBytes(params.bd_addr()));
252   if (addr != peer_address_) {
253     return hci::CommandChannel::EventCallbackResult::kContinue;
254   }
255 
256   if (!in_progress_request_ || in_progress_request_->initiator) {
257     bt_log(INFO,
258            "sco",
259            "reject unexpected %s connection request (peer: %s)",
260            hci_spec::LinkTypeToString(params.link_type().Read()),
261            bt_str(peer_id_));
262     SendRejectConnectionCommand(
263         DeviceAddressBytes(params.bd_addr()),
264         pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_BAD_BD_ADDR);
265     return hci::CommandChannel::EventCallbackResult::kContinue;
266   }
267 
268   // Skip to the next parameters that support the requested link type. The
269   // controller rejects parameters that don't include packet types for the
270   // requested link type.
271   if ((params.link_type().Read() == pw::bluetooth::emboss::LinkType::SCO &&
272        !FindNextParametersThatSupportSco()) ||
273       (params.link_type().Read() == pw::bluetooth::emboss::LinkType::ESCO &&
274        !FindNextParametersThatSupportEsco())) {
275     bt_log(DEBUG,
276            "sco",
277            "in progress request parameters don't support the requested "
278            "transport (%s); rejecting",
279            hci_spec::LinkTypeToString(params.link_type().Read()));
280     // The controller will send an HCI Synchronous Connection Complete event, so
281     // the request will be completed then.
282     SendRejectConnectionCommand(DeviceAddressBytes(params.bd_addr()),
283                                 pw::bluetooth::emboss::StatusCode::
284                                     CONNECTION_REJECTED_LIMITED_RESOURCES);
285     return hci::CommandChannel::EventCallbackResult::kContinue;
286   }
287 
288   bt_log(INFO,
289          "sco",
290          "accepting incoming %s connection from %s (peer: %s)",
291          hci_spec::LinkTypeToString(params.link_type().Read()),
292          bt_str(DeviceAddressBytes(params.bd_addr())),
293          bt_str(peer_id_));
294 
295   auto accept = hci::EmbossCommandPacket::New<
296       pw::bluetooth::emboss::
297           EnhancedAcceptSynchronousConnectionRequestCommandWriter>(
298       hci_spec::kEnhancedAcceptSynchronousConnectionRequest);
299   auto view = accept.view_t();
300   view.bd_addr().CopyFrom(params.bd_addr());
301   view.connection_parameters().CopyFrom(
302       in_progress_request_
303           ->parameters[in_progress_request_->current_param_index]
304           .view());
305 
306   SendCommandWithStatusCallback(
307       std::move(accept),
308       [self = weak_ptr_factory_.GetWeakPtr(),
309        peer_id = peer_id_](hci::Result<> status) {
310         if (!self.is_alive() || status.is_ok()) {
311           return;
312         }
313         bt_is_error(status,
314                     WARN,
315                     "sco",
316                     "enhanced accept SCO connection command failed, waiting "
317                     "for connection complete (peer: %s",
318                     bt_str(peer_id));
319         // Do not complete the request here. Wait for
320         // HCI_Synchronous_Connection_Complete event, which should be received
321         // after Connection_Accept_Timeout with status
322         // kConnectionAcceptTimeoutExceeded.
323       });
324 
325   in_progress_request_->received_request = true;
326 
327   return hci::CommandChannel::EventCallbackResult::kContinue;
328 }
329 
FindNextParametersThatSupportSco()330 bool ScoConnectionManager::FindNextParametersThatSupportSco() {
331   BT_ASSERT(in_progress_request_);
332   while (in_progress_request_->current_param_index <
333          in_progress_request_->parameters.size()) {
334     bt::StaticPacket<
335         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>& params =
336         in_progress_request_
337             ->parameters[in_progress_request_->current_param_index];
338     if (ConnectionParametersSupportScoTransport(params)) {
339       return true;
340     }
341     in_progress_request_->current_param_index++;
342   }
343   return false;
344 }
345 
FindNextParametersThatSupportEsco()346 bool ScoConnectionManager::FindNextParametersThatSupportEsco() {
347   BT_ASSERT(in_progress_request_);
348   while (in_progress_request_->current_param_index <
349          in_progress_request_->parameters.size()) {
350     bt::StaticPacket<
351         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>& params =
352         in_progress_request_
353             ->parameters[in_progress_request_->current_param_index];
354     if (ConnectionParametersSupportEscoTransport(params)) {
355       return true;
356     }
357     in_progress_request_->current_param_index++;
358   }
359   return false;
360 }
361 
QueueRequest(bool initiator,std::vector<bt::StaticPacket<pw::bluetooth::emboss::SynchronousConnectionParametersWriter>> params,ConnectionCallback cb)362 ScoConnectionManager::RequestHandle ScoConnectionManager::QueueRequest(
363     bool initiator,
364     std::vector<bt::StaticPacket<
365         pw::bluetooth::emboss::SynchronousConnectionParametersWriter>> params,
366     ConnectionCallback cb) {
367   BT_ASSERT(cb);
368 
369   if (params.empty()) {
370     cb(fit::error(HostError::kInvalidParameters));
371     return RequestHandle([]() {});
372   }
373 
374   if (queued_request_) {
375     CancelRequestWithId(queued_request_->id);
376   }
377 
378   auto req_id = next_req_id_++;
379   queued_request_ = {req_id,
380                      initiator,
381                      /*received_request_arg=*/false,
382                      std::move(params),
383                      std::move(cb)};
384 
385   TryCreateNextConnection();
386 
387   return RequestHandle([req_id, self = weak_ptr_factory_.GetWeakPtr()]() {
388     if (self.is_alive()) {
389       self->CancelRequestWithId(req_id);
390     }
391   });
392 }
393 
TryCreateNextConnection()394 void ScoConnectionManager::TryCreateNextConnection() {
395   // Cancel an in-progress responder request that hasn't received a connection
396   // request event yet.
397   if (in_progress_request_) {
398     CancelRequestWithId(in_progress_request_->id);
399   }
400 
401   if (in_progress_request_ || !queued_request_) {
402     return;
403   }
404 
405   in_progress_request_ = std::move(queued_request_);
406   queued_request_.reset();
407 
408   if (in_progress_request_->initiator) {
409     bt_log(DEBUG,
410            "gap-sco",
411            "Initiating SCO connection (peer: %s)",
412            bt_str(peer_id_));
413 
414     auto packet = hci::EmbossCommandPacket::New<
415         pw::bluetooth::emboss::EnhancedSetupSynchronousConnectionCommandWriter>(
416         hci_spec::kEnhancedSetupSynchronousConnection);
417     auto view = packet.view_t();
418     view.connection_handle().Write(acl_handle_);
419     view.connection_parameters().CopyFrom(
420         in_progress_request_
421             ->parameters[in_progress_request_->current_param_index]
422             .view());
423 
424     auto status_cb = [self = weak_ptr_factory_.GetWeakPtr()](
425                          hci::Result<> status) {
426       if (!self.is_alive() || status.is_ok()) {
427         return;
428       }
429       bt_is_error(status, WARN, "sco", "SCO setup connection command failed");
430       self->CompleteRequest(fit::error(HostError::kFailed));
431     };
432 
433     SendCommandWithStatusCallback(std::move(packet), std::move(status_cb));
434   }
435 }
436 
CompleteRequestOrTryNextParameters(ConnectionResult result)437 void ScoConnectionManager::CompleteRequestOrTryNextParameters(
438     ConnectionResult result) {
439   BT_ASSERT(in_progress_request_);
440 
441   // Multiple parameter attempts are not supported for initiator requests.
442   if (result.is_ok() || in_progress_request_->initiator) {
443     CompleteRequest(std::move(result));
444     return;
445   }
446 
447   // Check if all accept request parameters have been exhausted.
448   if (in_progress_request_->current_param_index + 1 >=
449       in_progress_request_->parameters.size()) {
450     bt_log(DEBUG, "sco", "all accept SCO parameters exhausted");
451     CompleteRequest(fit::error(HostError::kParametersRejected));
452     return;
453   }
454 
455   // If a request was queued after the connection request event (blocking
456   // cancelation at that time), cancel the current request.
457   if (queued_request_) {
458     CompleteRequest(fit::error(HostError::kCanceled));
459     return;
460   }
461 
462   // Wait for the next inbound connection request and accept it with the next
463   // parameters.
464   in_progress_request_->received_request = false;
465   in_progress_request_->current_param_index++;
466 }
467 
CompleteRequest(ConnectionResult result)468 void ScoConnectionManager::CompleteRequest(ConnectionResult result) {
469   BT_ASSERT(in_progress_request_);
470   bt_log(INFO,
471          "gap-sco",
472          "Completing SCO connection request (initiator: %d, success: %d, peer: "
473          "%s)",
474          in_progress_request_->initiator,
475          result.is_ok(),
476          bt_str(peer_id_));
477   // Clear in_progress_request_ before calling callback to prevent additional
478   // calls to CompleteRequest() during execution of the callback (e.g. due to
479   // destroying the RequestHandle).
480   ConnectionRequest request = std::move(in_progress_request_.value());
481   in_progress_request_.reset();
482   request.callback(std::move(result));
483   TryCreateNextConnection();
484 }
485 
SendCommandWithStatusCallback(std::unique_ptr<hci::CommandPacket> command_packet,hci::ResultFunction<> cb)486 void ScoConnectionManager::SendCommandWithStatusCallback(
487     std::unique_ptr<hci::CommandPacket> command_packet,
488     hci::ResultFunction<> cb) {
489   hci::CommandChannel::CommandCallback command_cb;
490   if (cb) {
491     command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
492       cb(event.ToResult());
493     };
494   }
495   transport_->command_channel()->SendCommand(std::move(command_packet),
496                                              std::move(command_cb));
497 }
498 
SendCommandWithStatusCallback(hci::EmbossCommandPacket command_packet,hci::ResultFunction<> cb)499 void ScoConnectionManager::SendCommandWithStatusCallback(
500     hci::EmbossCommandPacket command_packet, hci::ResultFunction<> cb) {
501   hci::CommandChannel::CommandCallback command_cb;
502   if (cb) {
503     command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
504       cb(event.ToResult());
505     };
506   }
507   transport_->command_channel()->SendCommand(std::move(command_packet),
508                                              std::move(command_cb));
509 }
510 
SendRejectConnectionCommand(DeviceAddressBytes addr,pw::bluetooth::emboss::StatusCode reason)511 void ScoConnectionManager::SendRejectConnectionCommand(
512     DeviceAddressBytes addr, pw::bluetooth::emboss::StatusCode reason) {
513   // The reject command has a small range of allowed reasons (the controller
514   // sends "Invalid HCI Command Parameters" for other reasons).
515   BT_ASSERT_MSG(
516       reason == pw::bluetooth::emboss::StatusCode::
517                     CONNECTION_REJECTED_LIMITED_RESOURCES ||
518           reason ==
519               pw::bluetooth::emboss::StatusCode::CONNECTION_REJECTED_SECURITY ||
520           reason == pw::bluetooth::emboss::StatusCode::
521                         CONNECTION_REJECTED_BAD_BD_ADDR,
522       "Tried to send invalid reject reason: %s",
523       hci_spec::StatusCodeToString(reason).c_str());
524 
525   auto reject = hci::EmbossCommandPacket::New<
526       pw::bluetooth::emboss::RejectSynchronousConnectionRequestCommandWriter>(
527       hci_spec::kRejectSynchronousConnectionRequest);
528   auto reject_params = reject.view_t();
529   reject_params.bd_addr().CopyFrom(addr.view());
530   reject_params.reason().Write(reason);
531 
532   transport_->command_channel()->SendCommand(
533       std::move(reject), nullptr, hci_spec::kCommandStatusEventCode);
534 }
535 
CancelRequestWithId(ScoRequestId id)536 void ScoConnectionManager::CancelRequestWithId(ScoRequestId id) {
537   // Cancel queued request if id matches.
538   if (queued_request_ && queued_request_->id == id) {
539     bt_log(INFO, "gap-sco", "Cancelling queued SCO request (id: %zu)", id);
540     // Clear queued_request_ before calling callback to prevent calls to
541     // CancelRequestWithId() during execution of the callback (e.g. due to
542     // destroying the RequestHandle).
543     ConnectionRequest request = std::move(queued_request_.value());
544     queued_request_.reset();
545     request.callback(fit::error(HostError::kCanceled));
546     return;
547   }
548 
549   // Cancel in progress request if it is a responder request that hasn't
550   // received a connection request yet.
551   if (in_progress_request_ && in_progress_request_->id == id &&
552       !in_progress_request_->initiator &&
553       !in_progress_request_->received_request) {
554     bt_log(INFO, "gap-sco", "Cancelling in progress SCO request (id: %zu)", id);
555     CompleteRequest(fit::error(HostError::kCanceled));
556   }
557 }
558 
559 }  // namespace bt::sco
560