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