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