blob: d7c486ace1934fdfe218560ec81180921cb5b0d6 [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"
namespace bt::sco {
namespace {
hci::SynchronousConnectionParameters ConnectionParametersToLe(
hci::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;
}
} // namespace
ScoConnectionManager::ScoConnectionManager(PeerId peer_id, hci::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_(transport),
weak_ptr_factory_(this) {
ZX_ASSERT(transport_);
// register event handlers
AddEventHandler(hci::kSynchronousConnectionCompleteEventCode,
fit::bind_member(this, &ScoConnectionManager::OnSynchronousConnectionComplete));
AddEventHandler(hci::kConnectionRequestEventCode,
fit::bind_member(this, &ScoConnectionManager::OnConnectionRequest));
}
ScoConnectionManager::~ScoConnectionManager() {
// Remove all event handlers
for (auto handler_id : event_handler_ids_) {
transport_->command_channel()->RemoveEventHandler(handler_id);
}
// Close all connections
for (auto [handle, conn] : connections_) {
conn->Close();
}
if (in_progress_request_) {
bt_log(DEBUG, "gap-sco", "ScoConnectionManager destroyed while request in progress");
in_progress_request_.reset();
}
}
ScoConnectionManager::RequestHandle ScoConnectionManager::OpenConnection(
hci::SynchronousConnectionParameters params, ConnectionCallback callback) {
return QueueRequest(/*initiator=*/true, params, std::move(callback));
}
ScoConnectionManager::RequestHandle ScoConnectionManager::AcceptConnection(
hci::SynchronousConnectionParameters params, ConnectionCallback callback) {
return QueueRequest(/*initiator=*/false, params, std::move(callback));
}
hci::CommandChannel::EventHandlerId ScoConnectionManager::AddEventHandler(
const hci::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::kSynchronousConnectionCompleteEventCode);
const auto& params = event.params<hci::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.ToStatus();
if (bt_is_error(status, INFO, "gap-sco", "SCO connection failed to be established (peer: %s)",
bt_str(peer_id_))) {
if (in_progress_request_) {
CompleteRequest(fit::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::LinkType::kSCO && link_type != hci::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 = hci::Connection::CreateSCO(link_type, connection_handle, local_address_,
peer_address_, transport_);
if (!in_progress_request_) {
bt_log(WARN, "gap-sco", "Unexpected SCO connection complete, disconnecting (peer: %s)",
bt_str(peer_id_));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto conn = ScoConnection::Create(std::move(link), /*deactivated_cb=*/[this, connection_handle] {
ZX_ASSERT(connections_.erase(connection_handle));
});
auto [_, success] = connections_.try_emplace(connection_handle, conn);
ZX_ASSERT_MSG(success, "SCO connection already exists with handle %#.4x (peer: %s)",
connection_handle, bt_str(peer_id_));
CompleteRequest(fit::ok(std::move(conn)));
return hci::CommandChannel::EventCallbackResult::kContinue;
}
hci::CommandChannel::EventCallbackResult ScoConnectionManager::OnConnectionRequest(
const hci::EventPacket& event) {
ZX_ASSERT(event.event_code() == hci::kConnectionRequestEventCode);
const auto& params = event.params<hci::ConnectionRequestEventParams>();
// Ignore requests for other link types.
if (params.link_type != hci::LinkType::kSCO && params.link_type != hci::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, "gap-sco", "reject unexpected SCO connection request (peer: %s)",
bt_str(peer_id_));
auto reject =
hci::CommandPacket::New(hci::kRejectSynchronousConnectionRequest,
sizeof(hci::RejectSynchronousConnectionRequestCommandParams));
auto reject_params =
reject->mutable_payload<hci::RejectSynchronousConnectionRequestCommandParams>();
reject_params->bd_addr = params.bd_addr;
reject_params->reason = hci::StatusCode::kConnectionRejectedBadBdAddr;
transport_->command_channel()->SendCommand(std::move(reject), nullptr,
hci::kCommandStatusEventCode);
return hci::CommandChannel::EventCallbackResult::kContinue;
}
auto accept =
hci::CommandPacket::New(hci::kEnhancedAcceptSynchronousConnectionRequest,
sizeof(hci::EnhancedAcceptSynchronousConnectionRequestCommandParams));
auto accept_params =
accept->mutable_payload<hci::EnhancedAcceptSynchronousConnectionRequestCommandParams>();
accept_params->bd_addr = params.bd_addr;
accept_params->connection_parameters = ConnectionParametersToLe(in_progress_request_->parameters);
SendCommandWithStatusCallback(
std::move(accept), [self = weak_ptr_factory_.GetWeakPtr()](hci::Status status) {
if (!self || status.is_success()) {
return;
}
bt_is_error(status, DEBUG, "sco", "SCO accept connection command failed");
self->CompleteRequest(fit::error(HostError::kFailed));
});
in_progress_request_->received_request = true;
return hci::CommandChannel::EventCallbackResult::kContinue;
}
ScoConnectionManager::RequestHandle ScoConnectionManager::QueueRequest(
bool initiator, hci::SynchronousConnectionParameters params, ConnectionCallback cb) {
ZX_ASSERT(cb);
// Cancel the current request.
queued_request_.reset();
auto req_id = next_req_id_++;
queued_request_ = {
.id = req_id, .initiator = initiator, .parameters = params, .callback = 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::EnhancedSetupSynchronousConnectionCommandParams command;
command.connection_handle = htole16(acl_handle_);
command.connection_parameters = ConnectionParametersToLe(in_progress_request_->parameters);
auto packet =
hci::CommandPacket::New(hci::kEnhancedSetupSynchronousConnection, sizeof(command));
*packet->mutable_payload<decltype(command)>() = command;
auto status_cb = [self = weak_ptr_factory_.GetWeakPtr()](hci::Status status) {
if (!self || status.is_success()) {
return;
}
bt_is_error(status, DEBUG, "sco", "SCO setup connection command failed");
self->CompleteRequest(fit::error(HostError::kFailed));
};
SendCommandWithStatusCallback(std::move(packet), std::move(status_cb));
}
}
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_));
in_progress_request_->callback(std::move(result));
in_progress_request_.reset();
TryCreateNextConnection();
}
void ScoConnectionManager::SendCommandWithStatusCallback(
std::unique_ptr<hci::CommandPacket> command_packet, hci::StatusCallback cb) {
hci::CommandChannel::CommandCallback command_cb;
if (cb) {
command_cb = [cb = std::move(cb)](auto, const hci::EventPacket& event) {
cb(event.ToStatus());
};
}
transport_->command_channel()->SendCommand(std::move(command_packet), std::move(command_cb));
}
void ScoConnectionManager::CancelRequestWithId(ScoRequestId id) {
// Cancel queued request if id matches.
if (queued_request_ && queued_request_->id == id) {
bt_log(TRACE, "gap-sco", "Cancelling queued request (id: %zu)", id);
queued_request_.reset();
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(TRACE, "gap-sco", "Cancelling in progress request (id: %zu)", id);
CompleteRequest(fit::error(HostError::kCanceled));
}
}
} // namespace bt::sco