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