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