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