blob: f0cf866e0b2811aad70c290efe9b934a8e098582 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "sco_connection_manager.h"
#include <lib/async/default.h>
#include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/sco_connection.h"
namespace bt::sco {
namespace {
hci_spec::SynchronousConnectionParameters ConnectionParametersToLe(
hci_spec::SynchronousConnectionParameters params) {
params.transmit_bandwidth = htole32(params.transmit_bandwidth);
params.receive_bandwidth = htole32(params.receive_bandwidth);
params.transmit_coding_format.company_id = htole16(params.transmit_coding_format.company_id);
params.transmit_coding_format.vendor_codec_id =
htole16(params.transmit_coding_format.vendor_codec_id);
params.receive_coding_format.company_id = htole16(params.receive_coding_format.company_id);
params.receive_coding_format.vendor_codec_id =
htole16(params.receive_coding_format.vendor_codec_id);
params.transmit_codec_frame_size_bytes = htole16(params.transmit_codec_frame_size_bytes);
params.receive_codec_frame_size_bytes = htole16(params.receive_codec_frame_size_bytes);
params.input_bandwidth = htole32(params.input_bandwidth);
params.output_bandwidth = htole32(params.output_bandwidth);
params.input_coding_format.company_id = htole16(params.input_coding_format.company_id);
params.input_coding_format.vendor_codec_id = htole16(params.input_coding_format.vendor_codec_id);
params.output_coding_format.company_id = htole16(params.output_coding_format.company_id);
params.output_coding_format.vendor_codec_id =
htole16(params.output_coding_format.vendor_codec_id);
params.max_latency_ms = htole16(params.max_latency_ms);
params.packet_types = htole16(params.packet_types);
return params;
}
constexpr uint16_t kScoTransportPacketTypes =
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kHv1) |
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kHv2) |
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kHv3);
constexpr uint16_t kEscoTransportPacketTypes =
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv3) |
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv4) |
static_cast<uint16_t>(hci_spec::ScoPacketTypeBits::kEv5);
bool ConnectionParametersSupportScoTransport(
const hci_spec::SynchronousConnectionParameters& params) {
return params.packet_types & kScoTransportPacketTypes;
}
bool ConnectionParametersSupportEscoTransport(
const hci_spec::SynchronousConnectionParameters& params) {
return params.packet_types & kEscoTransportPacketTypes;
}
} // namespace
ScoConnectionManager::ScoConnectionManager(PeerId peer_id, hci_spec::ConnectionHandle acl_handle,
DeviceAddress peer_address, DeviceAddress local_address,
fxl::WeakPtr<hci::Transport> transport)
: next_req_id_(0u),
peer_id_(peer_id),
local_address_(local_address),
peer_address_(peer_address),
acl_handle_(acl_handle),
transport_(std::move(transport)),
weak_ptr_factory_(this) {
ZX_ASSERT(transport_);
AddEventHandler(hci_spec::kSynchronousConnectionCompleteEventCode,
fit::bind_member<&ScoConnectionManager::OnSynchronousConnectionComplete>(this));
AddEventHandler(hci_spec::kConnectionRequestEventCode,
fit::bind_member<&ScoConnectionManager::OnConnectionRequest>(this));
}
ScoConnectionManager::~ScoConnectionManager() {
// Remove all event handlers
for (auto handler_id : event_handler_ids_) {
transport_->command_channel()->RemoveEventHandler(handler_id);
}
// Close all connections. Close may remove the connection from the map, so we can't use an
// iterator, which would be invalidated by the removal.
while (connections_.size() > 0) {
auto pair = connections_.begin();
hci_spec::ConnectionHandle handle = pair->first;
ScoConnection* conn = pair->second.get();
conn->Close();
// Make sure we erase the connection if Close doesn't so the loop terminates.
connections_.erase(handle);
}
if (queued_request_) {
CancelRequestWithId(queued_request_->id);
}
if (in_progress_request_) {
bt_log(DEBUG, "gap-sco", "ScoConnectionManager destroyed while request in progress");
// Clear in_progress_request_ before calling callback to prevent calls to
// CompleteRequest() during execution of the callback (e.g. due to destroying the
// RequestHandle).
ConnectionRequest request = std::move(in_progress_request_.value());
in_progress_request_.reset();
request.callback(fitx::error(HostError::kCanceled));
}
}
ScoConnectionManager::RequestHandle ScoConnectionManager::OpenConnection(
hci_spec::SynchronousConnectionParameters parameters, OpenConnectionCallback callback) {
return QueueRequest(/*initiator=*/true, {parameters},
[cb = std::move(callback)](ConnectionResult result) mutable {
// Convert result type.
if (result.is_error()) {
cb(fitx::error(result.take_error()));
return;
}
cb(fitx::ok(result.value().first));
});
}
ScoConnectionManager::RequestHandle ScoConnectionManager::AcceptConnection(
std::vector<hci_spec::SynchronousConnectionParameters> parameters,
AcceptConnectionCallback callback) {
return QueueRequest(/*initiator=*/false, std::move(parameters), std::move(callback));
}
hci::CommandChannel::EventHandlerId ScoConnectionManager::AddEventHandler(
const hci_spec::EventCode& code, hci::CommandChannel::EventCallback cb) {
auto self = weak_ptr_factory_.GetWeakPtr();
auto event_id = transport_->command_channel()->AddEventHandler(
code, [self, callback = std::move(cb)](const auto& event) {
if (self) {
return callback(event);
}
return hci::CommandChannel::EventCallbackResult::kRemove;
});
ZX_ASSERT(event_id);
event_handler_ids_.push_back(event_id);
return event_id;
}
hci::CommandChannel::EventCallbackResult ScoConnectionManager::OnSynchronousConnectionComplete(
const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kSynchronousConnectionCompleteEventCode);
const auto& params = event.params<hci_spec::SynchronousConnectionCompleteEventParams>();
DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr);
// Ignore events from other peers.
if (addr != peer_address_) {
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto status = event.ToResult();
if (bt_is_error(
status, INFO, "gap-sco",
"SCO connection failed to be established; trying next parameters if available (peer: %s)",
bt_str(peer_id_))) {
// A request must be in progress for this event to be generated.
CompleteRequestOrTryNextParameters(fitx::error(HostError::kFailed));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
// The controller should only report SCO and eSCO link types (other values are reserved).
auto link_type = params.link_type;
if (link_type != hci_spec::LinkType::kSCO && link_type != hci_spec::LinkType::kExtendedSCO) {
bt_log(ERROR, "gap-sco", "Received SynchronousConnectionComplete event with invalid link type");
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto connection_handle = letoh16(params.connection_handle);
auto link = std::make_unique<hci::ScoConnection>(connection_handle, local_address_, peer_address_,
transport_);
if (!in_progress_request_) {
bt_log(ERROR, "gap-sco", "Unexpected SCO connection complete, disconnecting (peer: %s)",
bt_str(peer_id_));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
fit::closure deactivated_cb = [this, connection_handle] {
ZX_ASSERT(connections_.erase(connection_handle));
};
hci_spec::SynchronousConnectionParameters conn_params =
in_progress_request_->parameters[in_progress_request_->current_param_index];
auto conn = std::make_unique<ScoConnection>(std::move(link), std::move(deactivated_cb),
conn_params, transport_->sco_data_channel());
fxl::WeakPtr<ScoConnection> conn_weak = conn->GetWeakPtr();
auto [_, success] = connections_.try_emplace(connection_handle, std::move(conn));
ZX_ASSERT_MSG(success, "SCO connection already exists with handle %#.4x (peer: %s)",
connection_handle, bt_str(peer_id_));
CompleteRequest(
fitx::ok(std::make_pair(std::move(conn_weak), in_progress_request_->current_param_index)));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult ScoConnectionManager::OnConnectionRequest(
const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci_spec::kConnectionRequestEventCode);
const auto& params = event.params<hci_spec::ConnectionRequestEventParams>();
// Ignore requests for other link types.
if (params.link_type != hci_spec::LinkType::kSCO &&
params.link_type != hci_spec::LinkType::kExtendedSCO) {
return hci::CommandChannel::EventCallbackResult::kContinue;
}
// Ignore requests from other peers.
DeviceAddress addr(DeviceAddress::Type::kBREDR, params.bd_addr);
if (addr != peer_address_) {
return hci::CommandChannel::EventCallbackResult::kContinue;
}
if (!in_progress_request_ || in_progress_request_->initiator) {
bt_log(INFO, "sco", "reject unexpected %s connection request (peer: %s)",
hci_spec::LinkTypeToString(params.link_type).c_str(), bt_str(peer_id_));
SendRejectConnectionCommand(params.bd_addr, hci_spec::StatusCode::kConnectionRejectedBadBdAddr);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
// Skip to the next parameters that support the requested link type. The controller rejects
// parameters that don't include packet types for the requested link type.
if ((params.link_type == hci_spec::LinkType::kSCO && !FindNextParametersThatSupportSco()) ||
(params.link_type == hci_spec::LinkType::kExtendedSCO &&
!FindNextParametersThatSupportEsco())) {
bt_log(DEBUG, "sco",
"in progress request parameters don't support the requested transport (%s); rejecting",
hci_spec::LinkTypeToString(params.link_type).c_str());
// The controller will send an HCI Synchronous Connection Complete event, so the request will be
// completed then.
SendRejectConnectionCommand(params.bd_addr,
hci_spec::StatusCode::kConnectionRejectedLimitedResources);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bt_log(INFO, "sco", "accepting incoming %s connection from %s (peer: %s)",
hci_spec::LinkTypeToString(params.link_type).c_str(), bt_str(params.bd_addr),
bt_str(peer_id_));
auto accept = hci::CommandPacket::New(
hci_spec::kEnhancedAcceptSynchronousConnectionRequest,
sizeof(hci_spec::EnhancedAcceptSynchronousConnectionRequestCommandParams));
auto accept_params =
accept->mutable_payload<hci_spec::EnhancedAcceptSynchronousConnectionRequestCommandParams>();
accept_params->bd_addr = params.bd_addr;
accept_params->connection_parameters = ConnectionParametersToLe(
in_progress_request_->parameters[in_progress_request_->current_param_index]);
SendCommandWithStatusCallback(std::move(accept), [self = weak_ptr_factory_.GetWeakPtr(),
peer_id = peer_id_](hci::Result<> status) {
if (!self || status.is_ok()) {
return;
}
bt_is_error(
status, WARN, "sco",
"enhanced accept SCO connection command failed, waiting for connection complete (peer: %s",
bt_str(peer_id));
// Do not complete the request here. Wait for HCI_Synchronous_Connection_Complete event,
// which should be received after Connection_Accept_Timeout with status
// kConnectionAcceptTimeoutExceeded.
});
in_progress_request_->received_request = true;
return hci::CommandChannel::EventCallbackResult::kContinue;
}
bool ScoConnectionManager::FindNextParametersThatSupportSco() {
ZX_ASSERT(in_progress_request_);
while (in_progress_request_->current_param_index < in_progress_request_->parameters.size()) {
hci_spec::SynchronousConnectionParameters& params =
in_progress_request_->parameters[in_progress_request_->current_param_index];
if (ConnectionParametersSupportScoTransport(params)) {
return true;
}
in_progress_request_->current_param_index++;
}
return false;
}
bool ScoConnectionManager::FindNextParametersThatSupportEsco() {
ZX_ASSERT(in_progress_request_);
while (in_progress_request_->current_param_index < in_progress_request_->parameters.size()) {
hci_spec::SynchronousConnectionParameters& params =
in_progress_request_->parameters[in_progress_request_->current_param_index];
if (ConnectionParametersSupportEscoTransport(params)) {
return true;
}
in_progress_request_->current_param_index++;
}
return false;
}
ScoConnectionManager::RequestHandle ScoConnectionManager::QueueRequest(
bool initiator, std::vector<hci_spec::SynchronousConnectionParameters> params,
ConnectionCallback cb) {
ZX_ASSERT(cb);
if (params.empty()) {
cb(fitx::error(HostError::kInvalidParameters));
return RequestHandle([]() {});
}
if (queued_request_) {
CancelRequestWithId(queued_request_->id);
}
auto req_id = next_req_id_++;
queued_request_ = {req_id, initiator, /*received_request_arg=*/false, std::move(params),
std::move(cb)};
TryCreateNextConnection();
return RequestHandle([req_id, self = weak_ptr_factory_.GetWeakPtr()]() {
if (self) {
self->CancelRequestWithId(req_id);
}
});
}
void ScoConnectionManager::TryCreateNextConnection() {
// Cancel an in-progress responder request that hasn't received a connection request event yet.
if (in_progress_request_) {
CancelRequestWithId(in_progress_request_->id);
}
if (in_progress_request_ || !queued_request_) {
return;
}
in_progress_request_ = std::move(queued_request_);
queued_request_.reset();
if (in_progress_request_->initiator) {
bt_log(DEBUG, "gap-sco", "Initiating SCO connection (peer: %s)", bt_str(peer_id_));
hci_spec::EnhancedSetupSynchronousConnectionCommandParams command;
command.connection_handle = htole16(acl_handle_);
command.connection_parameters = ConnectionParametersToLe(
in_progress_request_->parameters[in_progress_request_->current_param_index]);
auto packet =
hci::CommandPacket::New(hci_spec::kEnhancedSetupSynchronousConnection, sizeof(command));
*packet->mutable_payload<decltype(command)>() = command;
auto status_cb = [self = weak_ptr_factory_.GetWeakPtr()](hci::Result<> status) {
if (!self || status.is_ok()) {
return;
}
bt_is_error(status, WARN, "sco", "SCO setup connection command failed");
self->CompleteRequest(fitx::error(HostError::kFailed));
};
SendCommandWithStatusCallback(std::move(packet), std::move(status_cb));
}
}
void ScoConnectionManager::CompleteRequestOrTryNextParameters(ConnectionResult result) {
ZX_ASSERT(in_progress_request_);
// Multiple parameter attempts are not supported for initiator requests.
if (result.is_ok() || in_progress_request_->initiator) {
CompleteRequest(std::move(result));
return;
}
// Check if all accept request parameters have been exhausted.
if (in_progress_request_->current_param_index + 1 >= in_progress_request_->parameters.size()) {
bt_log(DEBUG, "sco", "all accept SCO parameters exhausted");
CompleteRequest(fitx::error(HostError::kParametersRejected));
return;
}
// If a request was queued after the connection request event (blocking cancelation at that time),
// cancel the current request.
if (queued_request_) {
CompleteRequest(fitx::error(HostError::kCanceled));
return;
}
// Wait for the next inbound connection request and accept it with the next parameters.
in_progress_request_->received_request = false;
in_progress_request_->current_param_index++;
}
void ScoConnectionManager::CompleteRequest(ConnectionResult result) {
ZX_ASSERT(in_progress_request_);
bt_log(INFO, "gap-sco",
"Completing SCO connection request (initiator: %d, success: %d, peer: %s)",
in_progress_request_->initiator, result.is_ok(), bt_str(peer_id_));
// Clear in_progress_request_ before calling callback to prevent additional calls to
// CompleteRequest() during execution of the callback (e.g. due to destroying the RequestHandle).
ConnectionRequest request = std::move(in_progress_request_.value());
in_progress_request_.reset();
request.callback(std::move(result));
TryCreateNextConnection();
}
void ScoConnectionManager::SendCommandWithStatusCallback(
std::unique_ptr<hci::CommandPacket> command_packet, hci::ResultFunction<> cb) {
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToResult());
};
}
transport_->command_channel()->SendCommand(std::move(command_packet), std::move(command_cb));
}
void ScoConnectionManager::SendRejectConnectionCommand(DeviceAddressBytes addr,
hci_spec::StatusCode reason) {
// The reject command has a small range of allowed reasons (the controller sends "Invalid HCI
// Command Parameters" for other reasons).
ZX_ASSERT_MSG(reason == hci_spec::StatusCode::kConnectionRejectedLimitedResources ||
reason == hci_spec::StatusCode::kConnectionRejectedSecurity ||
reason == hci_spec::StatusCode::kConnectionRejectedBadBdAddr,
"Tried to send invalid reject reason: %s",
hci_spec::StatusCodeToString(reason).c_str());
auto reject =
hci::CommandPacket::New(hci_spec::kRejectSynchronousConnectionRequest,
sizeof(hci_spec::RejectSynchronousConnectionRequestCommandParams));
auto reject_params =
reject->mutable_payload<hci_spec::RejectSynchronousConnectionRequestCommandParams>();
reject_params->bd_addr = addr;
reject_params->reason = reason;
transport_->command_channel()->SendCommand(std::move(reject), nullptr,
hci_spec::kCommandStatusEventCode);
}
void ScoConnectionManager::CancelRequestWithId(ScoRequestId id) {
// Cancel queued request if id matches.
if (queued_request_ && queued_request_->id == id) {
bt_log(INFO, "gap-sco", "Cancelling queued SCO request (id: %zu)", id);
// Clear queued_request_ before calling callback to prevent calls to
// CancelRequestWithId() during execution of the callback (e.g. due to destroying the
// RequestHandle).
ConnectionRequest request = std::move(queued_request_.value());
queued_request_.reset();
request.callback(fitx::error(HostError::kCanceled));
return;
}
// Cancel in progress request if it is a responder request that hasn't received a connection
// request yet.
if (in_progress_request_ && in_progress_request_->id == id && !in_progress_request_->initiator &&
!in_progress_request_->received_request) {
bt_log(INFO, "gap-sco", "Cancelling in progress SCO request (id: %zu)", id);
CompleteRequest(fitx::error(HostError::kCanceled));
}
}
} // namespace bt::sco