blob: 2a4dbe02f256eddde1ebe15f9cee67c04c2d64ef [file] [log] [blame]
// Copyright 2018 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 "profile_server.h"
#include <string.h>
#include <zircon/status.h>
#include <zircon/syscalls/clock.h>
#include <cstddef>
#include <memory>
#include "fuchsia/bluetooth/bredr/cpp/fidl.h"
#include "helpers.h"
#include "lib/fidl/cpp/binding.h"
#include "lib/fidl/cpp/interface_ptr.h"
#include "lib/fpromise/result.h"
#include "pw_intrusive_ptr/intrusive_ptr.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/host_error.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/weak_self.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/l2cap/types.h"
#include "zircon/errors.h"
namespace fidlbredr = fuchsia::bluetooth::bredr;
namespace android_emb = pw::bluetooth::vendor::android_hci;
using fidlbredr::DataElement;
using fidlbredr::Profile;
using pw::bluetooth::AclPriority;
using FeaturesBits = pw::bluetooth::Controller::FeaturesBits;
namespace bthost {
namespace {
bt::l2cap::ChannelParameters FidlToChannelParameters(const fidlbredr::ChannelParameters& fidl) {
bt::l2cap::ChannelParameters params;
if (fidl.has_channel_mode()) {
switch (fidl.channel_mode()) {
case fidlbredr::ChannelMode::BASIC:
params.mode = bt::l2cap::RetransmissionAndFlowControlMode::kBasic;
break;
case fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION:
params.mode = bt::l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission;
break;
default:
BT_PANIC("FIDL channel parameter contains invalid mode");
}
}
if (fidl.has_max_rx_sdu_size()) {
params.max_rx_sdu_size = fidl.max_rx_sdu_size();
}
if (fidl.has_flush_timeout()) {
params.flush_timeout = std::chrono::nanoseconds(fidl.flush_timeout());
}
return params;
}
fidlbredr::ChannelMode ChannelModeToFidl(const bt::l2cap::AnyChannelMode& mode) {
if (auto* flow_control_mode = std::get_if<bt::l2cap::RetransmissionAndFlowControlMode>(&mode)) {
switch (*flow_control_mode) {
case bt::l2cap::RetransmissionAndFlowControlMode::kBasic:
return fidlbredr::ChannelMode::BASIC;
break;
case bt::l2cap::RetransmissionAndFlowControlMode::kEnhancedRetransmission:
return fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION;
break;
default:
// Intentionally unhandled, fall through to PANIC.
break;
}
}
BT_PANIC("Could not convert channel parameter mode to unsupported FIDL mode");
}
fidlbredr::ChannelParameters ChannelInfoToFidlChannelParameters(
const bt::l2cap::ChannelInfo& info) {
fidlbredr::ChannelParameters params;
params.set_channel_mode(ChannelModeToFidl(info.mode));
params.set_max_rx_sdu_size(info.max_rx_sdu_size);
if (info.flush_timeout) {
params.set_flush_timeout(info.flush_timeout->count());
}
return params;
}
// NOLINTNEXTLINE(misc-no-recursion)
fidlbredr::DataElementPtr DataElementToFidl(const bt::sdp::DataElement* in) {
auto elem = std::make_unique<fidlbredr::DataElement>();
bt_log(TRACE, "fidl", "DataElementToFidl: %s", in->ToString().c_str());
BT_DEBUG_ASSERT(in);
switch (in->type()) {
case bt::sdp::DataElement::Type::kUnsignedInt: {
switch (in->size()) {
case bt::sdp::DataElement::Size::kOneByte:
elem->set_uint8(*in->Get<uint8_t>());
break;
case bt::sdp::DataElement::Size::kTwoBytes:
elem->set_uint16(*in->Get<uint16_t>());
break;
case bt::sdp::DataElement::Size::kFourBytes:
elem->set_uint32(*in->Get<uint32_t>());
break;
case bt::sdp::DataElement::Size::kEightBytes:
elem->set_uint64(*in->Get<uint64_t>());
break;
default:
bt_log(INFO, "fidl", "no 128-bit integer support in FIDL yet");
return nullptr;
}
return elem;
}
case bt::sdp::DataElement::Type::kSignedInt: {
switch (in->size()) {
case bt::sdp::DataElement::Size::kOneByte:
elem->set_int8(*in->Get<int8_t>());
break;
case bt::sdp::DataElement::Size::kTwoBytes:
elem->set_int16(*in->Get<int16_t>());
break;
case bt::sdp::DataElement::Size::kFourBytes:
elem->set_int32(*in->Get<int32_t>());
break;
case bt::sdp::DataElement::Size::kEightBytes:
elem->set_int64(*in->Get<int64_t>());
break;
default:
bt_log(INFO, "fidl", "no 128-bit integer support in FIDL yet");
return nullptr;
}
return elem;
}
case bt::sdp::DataElement::Type::kUuid: {
auto uuid = in->Get<bt::UUID>();
BT_DEBUG_ASSERT(uuid);
elem->set_uuid(fidl_helpers::UuidToFidl(*uuid));
return elem;
}
case bt::sdp::DataElement::Type::kString: {
auto bytes = in->Get<bt::DynamicByteBuffer>();
BT_DEBUG_ASSERT(bytes);
std::vector<uint8_t> data(bytes->cbegin(), bytes->cend());
elem->set_str(data);
return elem;
}
case bt::sdp::DataElement::Type::kBoolean: {
elem->set_b(*in->Get<bool>());
return elem;
}
case bt::sdp::DataElement::Type::kSequence: {
std::vector<fidlbredr::DataElementPtr> elems;
const bt::sdp::DataElement* it;
for (size_t idx = 0; (it = in->At(idx)); ++idx) {
elems.emplace_back(DataElementToFidl(it));
}
elem->set_sequence(std::move(elems));
return elem;
}
case bt::sdp::DataElement::Type::kAlternative: {
std::vector<fidlbredr::DataElementPtr> elems;
const bt::sdp::DataElement* it;
for (size_t idx = 0; (it = in->At(idx)); ++idx) {
elems.emplace_back(DataElementToFidl(it));
}
elem->set_alternatives(std::move(elems));
return elem;
}
case bt::sdp::DataElement::Type::kUrl: {
elem->set_url(*in->GetUrl());
return elem;
}
case bt::sdp::DataElement::Type::kNull: {
bt_log(INFO, "fidl", "no support for null DataElement types in FIDL");
return nullptr;
}
}
}
fidlbredr::ProtocolDescriptorPtr DataElementToProtocolDescriptor(const bt::sdp::DataElement* in) {
auto desc = std::make_unique<fidlbredr::ProtocolDescriptor>();
if (in->type() != bt::sdp::DataElement::Type::kSequence) {
bt_log(DEBUG, "fidl", "DataElement type is not kSequence (in: %s)", bt_str(*in));
return nullptr;
}
const auto protocol_uuid = in->At(0)->Get<bt::UUID>();
if (!protocol_uuid) {
bt_log(DEBUG, "fidl", "first DataElement in sequence is not type kUUID (in: %s)", bt_str(*in));
return nullptr;
}
desc->protocol = static_cast<fidlbredr::ProtocolIdentifier>(*protocol_uuid->As16Bit());
const bt::sdp::DataElement* it;
for (size_t idx = 1; (it = in->At(idx)); ++idx) {
desc->params.push_back(std::move(*DataElementToFidl(it)));
}
return desc;
}
AclPriority FidlToAclPriority(fidlbredr::A2dpDirectionPriority in) {
switch (in) {
case fidlbredr::A2dpDirectionPriority::SOURCE:
return AclPriority::kSource;
case fidlbredr::A2dpDirectionPriority::SINK:
return AclPriority::kSink;
default:
return AclPriority::kNormal;
}
}
} // namespace
ProfileServer::ProfileServer(bt::gap::Adapter::WeakPtr adapter,
fidl::InterfaceRequest<Profile> request)
: ServerBase(this, std::move(request)),
advertised_total_(0),
searches_total_(0),
adapter_(std::move(adapter)),
weak_self_(this) {}
ProfileServer::~ProfileServer() {
if (adapter().is_alive()) {
// Unregister anything that we have registered.
for (const auto& it : current_advertised_) {
adapter()->bredr()->UnregisterService(it.second.registration_handle);
}
for (const auto& it : searches_) {
adapter()->bredr()->RemoveServiceSearch(it.second.search_id);
}
}
}
void ProfileServer::L2capParametersExt::RequestParameters(
fuchsia::bluetooth::bredr::ChannelParameters requested, RequestParametersCallback callback) {
if (requested.has_flush_timeout()) {
channel_->SetBrEdrAutomaticFlushTimeout(
std::chrono::nanoseconds(requested.flush_timeout()),
[chan = channel_, cb = std::move(callback)](auto result) {
if (result.is_ok()) {
bt_log(DEBUG, "fidl",
"L2capParametersExt::RequestParameters: setting flush timeout succeeded");
} else {
bt_log(INFO, "fidl",
"L2capParametersExt::RequestParameters: setting flush timeout failed");
}
// Return the current parameters even if the request failed.
// TODO(https://fxbug.dev/42152567): set current security requirements in returned channel
// parameters
cb(fidlbredr::L2capParametersExt_RequestParameters_Result::WithResponse(
fidlbredr::L2capParametersExt_RequestParameters_Response(
ChannelInfoToFidlChannelParameters(chan->info()))));
});
return;
}
// No other channel parameters are supported, so just return the current parameters.
// TODO(https://fxbug.dev/42152567): set current security requirements in returned channel
// parameters
callback(fidlbredr::L2capParametersExt_RequestParameters_Result::WithResponse(
fidlbredr::L2capParametersExt_RequestParameters_Response(
ChannelInfoToFidlChannelParameters(channel_->info()))));
}
void ProfileServer::L2capParametersExt::handle_unknown_method(uint64_t ordinal,
bool method_has_response) {
bt_log(WARN, "fidl", "L2capParametersExt: unknown method received");
}
void ProfileServer::AudioOffloadExt::GetSupportedFeatures(GetSupportedFeaturesCallback callback) {
fidlbredr::AudioOffloadExt_GetSupportedFeatures_Response response;
std::vector<fidlbredr::AudioOffloadFeatures>* mutable_audio_offload_features =
response.mutable_audio_offload_features();
const bt::gap::AdapterState& adapter_state = adapter_->state();
if (!adapter_state.IsControllerFeatureSupported(FeaturesBits::kAndroidVendorExtensions)) {
callback(
fidlbredr::AudioOffloadExt_GetSupportedFeatures_Result::WithResponse(std::move(response)));
return;
}
const uint32_t a2dp_offload_capabilities =
adapter_state.android_vendor_capabilities->a2dp_source_offload_capability_mask();
const uint32_t sbc_capability = static_cast<uint32_t>(android_emb::A2dpCodecType::SBC);
const uint32_t aac_capability = static_cast<uint32_t>(android_emb::A2dpCodecType::AAC);
if (a2dp_offload_capabilities & sbc_capability) {
fidlbredr::AudioSbcSupport audio_sbc_support;
mutable_audio_offload_features->push_back(
fidlbredr::AudioOffloadFeatures::WithSbc(std::move(audio_sbc_support)));
}
if (a2dp_offload_capabilities & aac_capability) {
fidlbredr::AudioAacSupport audio_aac_support;
mutable_audio_offload_features->push_back(
fidlbredr::AudioOffloadFeatures::WithAac(std::move(audio_aac_support)));
}
callback(
fidlbredr::AudioOffloadExt_GetSupportedFeatures_Result::WithResponse(std::move(response)));
}
void ProfileServer::AudioOffloadExt::StartAudioOffload(
fidlbredr::AudioOffloadConfiguration audio_offload_configuration,
fidl::InterfaceRequest<fidlbredr::AudioOffloadController> controller) {
auto audio_offload_controller_server =
std::make_unique<AudioOffloadController>(std::move(controller), channel_);
WeakPtr<AudioOffloadController> server_ptr = audio_offload_controller_server->GetWeakPtr();
std::unique_ptr<bt::l2cap::A2dpOffloadManager::Configuration> config =
AudioOffloadConfigFromFidl(audio_offload_configuration);
if (!config) {
bt_log(ERROR, "fidl", "%s: invalid config received", __FUNCTION__);
server_ptr->Close(/*epitaph_value=*/ZX_ERR_NOT_SUPPORTED);
return;
}
auto error_handler = [this, server_ptr](zx_status_t status) {
if (!server_ptr.is_alive()) {
bt_log(ERROR, "fidl", "audio offload controller server was destroyed");
return;
}
bt_log(DEBUG, "fidl", "audio offload controller server closed (reason: %s)",
zx_status_get_string(status));
if (!profile_server_.audio_offload_controller_server_) {
bt_log(WARN, "fidl",
"could not find controller server in audio offload controller error callback");
}
bt::hci::ResultCallback<> stop_cb =
[server_ptr](fit::result<bt::Error<pw::bluetooth::emboss::StatusCode>> result) {
if (result.is_error()) {
bt_log(ERROR, "fidl", "stopping audio offload failed in error handler: %s",
bt_str(result));
server_ptr->Close(/*epitaph_value=*/ZX_ERR_UNAVAILABLE);
return;
}
bt_log(ERROR, "fidl", "stopping audio offload complete: %s", bt_str(result));
};
channel_->StopA2dpOffload(std::move(stop_cb));
};
audio_offload_controller_server->set_error_handler(error_handler);
profile_server_.audio_offload_controller_server_ = std::move(audio_offload_controller_server);
auto callback = [this,
server_ptr](fit::result<bt::Error<pw::bluetooth::emboss::StatusCode>> result) {
if (!server_ptr.is_alive()) {
bt_log(ERROR, "fidl", "audio offload controller server was destroyed");
return;
}
if (result.is_error()) {
bt_log(ERROR, "fidl", "StartAudioOffload failed: %s", bt_str(result));
auto host_error = result.error_value().host_error();
if (host_error == bt::HostError::kInProgress) {
server_ptr->Close(/*epitaph_value=*/ZX_ERR_ALREADY_BOUND);
} else if (host_error == bt::HostError::kFailed) {
server_ptr->Close(/*epitaph_value=*/ZX_ERR_INTERNAL);
} else {
server_ptr->Close(/*epitaph_value=*/ZX_ERR_UNAVAILABLE);
}
profile_server_.audio_offload_controller_server_ = nullptr;
return;
}
// Send OnStarted event to tell Rust Profiles that we've finished offloading
server_ptr->SendOnStartedEvent();
};
channel_->StartA2dpOffload(*config, std::move(callback));
}
std::unique_ptr<bt::l2cap::A2dpOffloadManager::Configuration>
ProfileServer::AudioOffloadExt::AudioOffloadConfigFromFidl(
fidlbredr::AudioOffloadConfiguration& audio_offload_configuration) {
auto codec = fidl_helpers::FidlToCodecType(audio_offload_configuration.codec());
if (!codec.has_value()) {
bt_log(WARN, "fidl", "%s: invalid codec", __FUNCTION__);
return nullptr;
}
std::unique_ptr<bt::l2cap::A2dpOffloadManager::Configuration> config =
std::make_unique<bt::l2cap::A2dpOffloadManager::Configuration>();
config->codec = codec.value();
config->max_latency = audio_offload_configuration.max_latency();
config->scms_t_enable =
fidl_helpers::FidlToScmsTEnable(audio_offload_configuration.scms_t_enable());
config->sampling_frequency =
fidl_helpers::FidlToSamplingFrequency(audio_offload_configuration.sampling_frequency());
config->bits_per_sample =
fidl_helpers::FidlToBitsPerSample(audio_offload_configuration.bits_per_sample());
config->channel_mode =
fidl_helpers::FidlToChannelMode(audio_offload_configuration.channel_mode());
config->encoded_audio_bit_rate = audio_offload_configuration.encoded_bit_rate();
if (audio_offload_configuration.encoder_settings().is_sbc()) {
if (audio_offload_configuration.sampling_frequency() ==
fuchsia::bluetooth::bredr::AudioSamplingFrequency::HZ_88200 ||
audio_offload_configuration.sampling_frequency() ==
fuchsia::bluetooth::bredr::AudioSamplingFrequency::HZ_96000) {
bt_log(WARN, "fidl", "%s: sbc encoder cannot use sampling frequency %hhu", __FUNCTION__,
static_cast<uint8_t>(audio_offload_configuration.sampling_frequency()));
return nullptr;
}
config->sbc_configuration =
fidl_helpers::FidlToEncoderSettingsSbc(audio_offload_configuration.encoder_settings(),
audio_offload_configuration.sampling_frequency(),
audio_offload_configuration.channel_mode());
} else if (audio_offload_configuration.encoder_settings().is_aac()) {
config->aac_configuration =
fidl_helpers::FidlToEncoderSettingsAac(audio_offload_configuration.encoder_settings(),
audio_offload_configuration.sampling_frequency(),
audio_offload_configuration.channel_mode());
}
return config;
}
void ProfileServer::AudioOffloadExt::handle_unknown_method(uint64_t ordinal,
bool method_has_response) {
bt_log(WARN, "fidl", "AudioOffloadExt: unknown method received");
}
void ProfileServer::AudioOffloadController::handle_unknown_method(uint64_t ordinal,
bool method_has_response) {
bt_log(WARN, "fidl", "AudioOffloadController: unknown method received");
}
ProfileServer::ScoConnectionServer::ScoConnectionServer(
fidl::InterfaceRequest<fuchsia::bluetooth::bredr::ScoConnection> request,
bt::sco::ScoConnection::WeakPtr connection)
: ServerBase(this, std::move(request)), connection_(std::move(connection)) {
binding()->set_error_handler([this](zx_status_t) { Close(ZX_ERR_CANCELED); });
}
ProfileServer::ScoConnectionServer::~ScoConnectionServer() {
if (connection_.is_alive()) {
connection_->Deactivate();
}
}
void ProfileServer::ScoConnectionServer::Activate(fit::callback<void()> on_closed) {
on_closed_ = std::move(on_closed);
auto rx_callback = [this] { TryRead(); };
auto closed_cb = [this] { Close(ZX_ERR_PEER_CLOSED); };
connection_->Activate(std::move(rx_callback), std::move(closed_cb));
}
void ProfileServer::ScoConnectionServer::Read(ReadCallback callback) {
if (connection_->parameters().view().input_data_path().Read() !=
pw::bluetooth::emboss::ScoDataPath::HCI) {
bt_log(WARN, "fidl", "%s called for an offloaded SCO connection", __func__);
Close(ZX_ERR_IO_NOT_PRESENT);
return;
}
if (read_cb_) {
bt_log(WARN, "fidl", "%s called when a read callback was already present", __func__);
Close(ZX_ERR_BAD_STATE);
return;
}
read_cb_ = std::move(callback);
TryRead();
}
void ProfileServer::ScoConnectionServer::Write(std::vector<uint8_t> data, WriteCallback callback) {
if (connection_->parameters().view().output_data_path().Read() !=
pw::bluetooth::emboss::ScoDataPath::HCI) {
bt_log(WARN, "fidl", "%s called for a non-HCI SCO connection", __func__);
Close(ZX_ERR_IO_NOT_PRESENT);
return;
}
auto buffer = std::make_unique<bt::DynamicByteBuffer>(data.size());
buffer->Write(data.data(), data.size());
if (!connection_->Send(std::move(buffer))) {
bt_log(WARN, "fidl", "%s: failed to send SCO packet", __func__);
Close(ZX_ERR_IO);
return;
}
callback();
}
void ProfileServer::ScoConnectionServer::TryRead() {
if (!read_cb_) {
return;
}
std::unique_ptr<bt::hci::ScoDataPacket> packet = connection_->Read();
if (!packet) {
return;
}
std::vector<uint8_t> payload;
fuchsia::bluetooth::bredr::RxPacketStatus status =
fidl_helpers::ScoPacketStatusToFidl(packet->packet_status_flag());
if (packet->packet_status_flag() !=
bt::hci_spec::SynchronousDataPacketStatusFlag::kNoDataReceived) {
payload = packet->view().payload_data().ToVector();
}
read_cb_(status, std::move(payload));
}
void ProfileServer::ScoConnectionServer::Close(zx_status_t epitaph) {
connection_->Deactivate();
connection_.reset();
binding()->Close(epitaph);
if (on_closed_) {
on_closed_();
}
}
void ProfileServer::Advertise(fuchsia::bluetooth::bredr::ProfileAdvertiseRequest request,
AdvertiseCallback callback) {
if (!request.has_services() || !request.has_receiver()) {
callback(fidlbredr::Profile_Advertise_Result::WithErr(
fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS));
return;
}
if (!request.has_parameters()) {
request.set_parameters(fidlbredr::ChannelParameters());
}
std::vector<bt::sdp::ServiceRecord> registering;
for (auto& definition : request.services()) {
auto rec = fidl_helpers::ServiceDefinitionToServiceRecord(definition);
// Drop the receiver on error.
if (rec.is_error()) {
bt_log(WARN, "fidl", "%s: Failed to create service record from service defintion",
__FUNCTION__);
callback(fpromise::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS));
return;
}
registering.emplace_back(std::move(rec.value()));
}
BT_ASSERT(adapter().is_alive());
BT_ASSERT(adapter()->bredr());
uint64_t next = advertised_total_ + 1;
auto registration_handle = adapter()->bredr()->RegisterService(
std::move(registering), FidlToChannelParameters(request.parameters()),
[this, next](auto channel, const auto& protocol_list) {
OnChannelConnected(next, std::move(channel), std::move(protocol_list));
});
if (!registration_handle) {
bt_log(WARN, "fidl", "%s: Failed to register service", __FUNCTION__);
callback(fpromise::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS));
return;
};
fidlbredr::ConnectionReceiverPtr receiver = request.mutable_receiver()->Bind();
// Monitor events on the `ConnectionReceiver`. Remove the service if the FIDL client revokes the
// service registration.
receiver.events().OnRevoke = [this, ad_id = next]() {
bt_log(DEBUG, "fidl", "Connection receiver revoked. Ending service advertisement %lu", ad_id);
OnConnectionReceiverClosed(ad_id);
};
// Errors on the `ConnectionReceiver` will result in service unregistration.
receiver.set_error_handler([this, ad_id = next](zx_status_t status) {
bt_log(DEBUG, "fidl",
"Connection receiver closed with error: %s. Ending service advertisement %lu",
zx_status_get_string(status), ad_id);
OnConnectionReceiverClosed(ad_id);
});
current_advertised_.try_emplace(next, std::move(receiver), registration_handle);
advertised_total_ = next;
// TODO(https://fxbug.dev/330590954): Return the services that were registered by the SDP server.
fuchsia::bluetooth::bredr::Profile_Advertise_Response result;
std::vector<fuchsia::bluetooth::bredr::ServiceDefinition> registered_definitions;
result.set_services(std::move(registered_definitions));
callback(fuchsia::bluetooth::bredr::Profile_Advertise_Result::WithResponse(std::move(result)));
}
void ProfileServer::Search(::fuchsia::bluetooth::bredr::ProfileSearchRequest request) {
if (!request.has_results() || !request.has_service_uuid()) {
bt_log(WARN, "fidl", "%s: missing parameter", __FUNCTION__);
return;
}
bt::UUID search_uuid(static_cast<uint32_t>(request.service_uuid()));
std::unordered_set<bt::sdp::AttributeId> attributes;
if (request.has_attr_ids() && !request.attr_ids().empty()) {
attributes.insert(request.attr_ids().begin(), request.attr_ids().end());
// Always request the ProfileDescriptor for the event
attributes.insert(bt::sdp::kBluetoothProfileDescriptorList);
}
BT_DEBUG_ASSERT(adapter().is_alive());
auto next = searches_total_ + 1;
auto search_id = adapter()->bredr()->AddServiceSearch(
search_uuid, std::move(attributes),
[this, next](auto id, const auto& attrs) { OnServiceFound(next, id, attrs); });
if (!search_id) {
return;
}
auto results_ptr = request.mutable_results()->Bind();
results_ptr.set_error_handler(
[this, next](zx_status_t status) { OnSearchResultError(next, status); });
searches_.try_emplace(next, std::move(results_ptr), search_id);
searches_total_ = next;
}
void ProfileServer::Connect(fuchsia::bluetooth::PeerId peer_id,
fidlbredr::ConnectParameters connection, ConnectCallback callback) {
bt::PeerId id{peer_id.value};
// Anything other than L2CAP is not supported by this server.
if (!connection.is_l2cap()) {
bt_log(WARN, "fidl", "%s: non-l2cap connections are not supported (is_rfcomm: %d, peer: %s)",
__FUNCTION__, connection.is_rfcomm(), bt_str(id));
callback(fpromise::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS));
return;
}
// The L2CAP parameters must include a PSM. ChannelParameters are optional.
auto l2cap_params = std::move(connection.l2cap());
if (!l2cap_params.has_psm()) {
bt_log(WARN, "fidl", "%s: missing l2cap psm (peer: %s)", __FUNCTION__, bt_str(id));
callback(fpromise::error(fuchsia::bluetooth::ErrorCode::INVALID_ARGUMENTS));
return;
}
uint16_t psm = l2cap_params.psm();
fidlbredr::ChannelParameters parameters = std::move(*l2cap_params.mutable_parameters());
auto connected_cb = [self = weak_self_.GetWeakPtr(), cb = callback.share(),
id](bt::l2cap::Channel::WeakPtr chan) {
if (!chan.is_alive()) {
bt_log(INFO, "fidl", "Connect: Channel socket is empty, returning failed. (peer: %s)",
bt_str(id));
cb(fpromise::error(fuchsia::bluetooth::ErrorCode::FAILED));
return;
}
if (!self.is_alive()) {
cb(fpromise::error(fuchsia::bluetooth::ErrorCode::FAILED));
return;
}
auto fidl_chan = self->ChannelToFidl(std::move(chan));
cb(fpromise::ok(std::move(fidl_chan)));
};
BT_DEBUG_ASSERT(adapter().is_alive());
adapter()->bredr()->OpenL2capChannel(
id, psm, fidl_helpers::FidlToBrEdrSecurityRequirements(parameters),
FidlToChannelParameters(parameters), std::move(connected_cb));
}
void ProfileServer::ConnectSco(::fuchsia::bluetooth::bredr::ProfileConnectScoRequest request) {
if (!request.has_receiver()) {
bt_log(WARN, "fidl", "%s missing receiver", __FUNCTION__);
return;
}
auto client = request.mutable_receiver()->Bind();
if (!request.has_peer_id() || !request.has_initiator() || !request.has_params() ||
request.params().empty()) {
client->Error(fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
return;
}
bt::PeerId peer_id(request.peer_id().value);
if (request.initiator() && request.params().size() != 1u) {
bt_log(WARN, "fidl", "%s: too many parameters in initiator request (peer: %s)", __FUNCTION__,
bt_str(peer_id));
client->Error(fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
return;
}
auto params_result = fidl_helpers::FidlToScoParametersVector(request.params());
if (params_result.is_error()) {
bt_log(WARN, "fidl", "%s: invalid parameters (peer: %s)", __FUNCTION__, bt_str(peer_id));
client->Error(fidlbredr::ScoErrorCode::INVALID_ARGUMENTS);
return;
}
auto params = params_result.value();
pw::IntrusivePtr<ScoRequest> sco_request = pw::MakeRefCounted<ScoRequest>();
client.set_error_handler(
[sco_request](zx_status_t status) { sco_request->request_handle.reset(); });
sco_request->receiver = std::move(client);
sco_request->parameters = std::move(*request.mutable_params());
if (request.initiator()) {
auto callback = [self = weak_self_.GetWeakPtr(), sco_request](
bt::sco::ScoConnectionManager::OpenConnectionResult result) mutable {
// The connection may complete after this server is destroyed.
if (!self.is_alive()) {
// Prevent leaking connections.
if (result.is_ok()) {
result.value()->Deactivate();
}
return;
}
// Convert result type.
if (result.is_error()) {
self->OnScoConnectionResult(std::move(sco_request), result.take_error());
return;
}
self->OnScoConnectionResult(
std::move(sco_request),
fit::ok(std::make_pair(std::move(result.value()), /*parameter index=*/0u)));
};
sco_request->request_handle =
adapter()->bredr()->OpenScoConnection(peer_id, params.front(), std::move(callback));
return;
}
auto callback = [self = weak_self_.GetWeakPtr(), sco_request](
bt::sco::ScoConnectionManager::AcceptConnectionResult result) mutable {
// The connection may complete after this server is destroyed.
if (!self.is_alive()) {
// Prevent leaking connections.
if (result.is_ok()) {
result.value().first->Deactivate();
}
return;
}
self->OnScoConnectionResult(std::move(sco_request), std::move(result));
};
sco_request->request_handle =
adapter()->bredr()->AcceptScoConnection(peer_id, params, std::move(callback));
}
void ProfileServer::handle_unknown_method(uint64_t ordinal, bool method_has_response) {
bt_log(WARN, "fidl", "ProfileServer: unknown method received");
}
void ProfileServer::OnChannelConnected(uint64_t ad_id, bt::l2cap::Channel::WeakPtr channel,
const bt::sdp::DataElement& protocol_list) {
auto it = current_advertised_.find(ad_id);
if (it == current_advertised_.end()) {
// The receiver has disappeared, do nothing.
return;
}
BT_DEBUG_ASSERT(adapter().is_alive());
auto handle = channel->link_handle();
auto id = adapter()->bredr()->GetPeerId(handle);
// The protocol that is connected should be L2CAP, because that is the only thing that
// we can connect. We can't say anything about what the higher level protocols will be.
auto prot_seq = protocol_list.At(0);
BT_ASSERT(prot_seq);
fidlbredr::ProtocolDescriptorPtr desc = DataElementToProtocolDescriptor(prot_seq);
BT_ASSERT(desc);
fuchsia::bluetooth::PeerId peer_id{id.value()};
std::vector<fidlbredr::ProtocolDescriptor> list;
list.emplace_back(std::move(*desc));
auto fidl_chan = ChannelToFidl(std::move(channel));
it->second.receiver->Connected(peer_id, std::move(fidl_chan), std::move(list));
}
void ProfileServer::OnConnectionReceiverClosed(uint64_t ad_id) {
auto it = current_advertised_.find(ad_id);
if (it == current_advertised_.end() || !adapter().is_alive()) {
return;
}
adapter()->bredr()->UnregisterService(it->second.registration_handle);
current_advertised_.erase(it);
}
void ProfileServer::OnSearchResultError(uint64_t search_id, zx_status_t status) {
bt_log(DEBUG, "fidl", "Search result closed, ending search %lu reason %s", search_id,
zx_status_get_string(status));
auto it = searches_.find(search_id);
if (it == searches_.end() || !adapter().is_alive()) {
return;
}
adapter()->bredr()->RemoveServiceSearch(it->second.search_id);
searches_.erase(it);
}
void ProfileServer::OnServiceFound(
uint64_t search_id, bt::PeerId peer_id,
const std::map<bt::sdp::AttributeId, bt::sdp::DataElement>& attributes) {
auto search_it = searches_.find(search_id);
if (search_it == searches_.end()) {
// Search was de-registered.
return;
}
// Convert ProfileDescriptor Attribute
auto it = attributes.find(bt::sdp::kProtocolDescriptorList);
fidl::VectorPtr<fidlbredr::ProtocolDescriptor> descriptor_list;
if (it != attributes.end()) {
std::vector<fidlbredr::ProtocolDescriptor> list;
size_t idx = 0;
auto* sdp_list_element = it->second.At(idx);
while (sdp_list_element != nullptr) {
fidlbredr::ProtocolDescriptorPtr desc = DataElementToProtocolDescriptor(sdp_list_element);
if (!desc) {
break;
}
list.push_back(std::move(*desc));
sdp_list_element = it->second.At(++idx);
}
descriptor_list = std::move(list);
}
// Add the rest of the attributes
std::vector<fidlbredr::Attribute> fidl_attrs;
for (const auto& it : attributes) {
auto attr = std::make_unique<fidlbredr::Attribute>();
attr->id = it.first;
attr->element = std::move(*DataElementToFidl(&it.second));
fidl_attrs.emplace_back(std::move(*attr));
}
fuchsia::bluetooth::PeerId fidl_peer_id{peer_id.value()};
search_it->second.results->ServiceFound(fidl_peer_id, std::move(descriptor_list),
std::move(fidl_attrs), [](auto) {});
}
void ProfileServer::OnScoConnectionResult(
pw::IntrusivePtr<ScoRequest> request, // NOLINT(performance-unnecessary-value-param)
bt::sco::ScoConnectionManager::AcceptConnectionResult result) {
auto receiver = std::move(request->receiver);
if (result.is_error()) {
if (!receiver.is_bound()) {
return;
}
bt_log(INFO, "fidl", "%s: SCO connection failed (status: %s)", __FUNCTION__,
bt::HostErrorToString(result.error_value()).c_str());
fidlbredr::ScoErrorCode fidl_error = fidlbredr::ScoErrorCode::FAILURE;
if (result.error_value() == bt::HostError::kCanceled) {
fidl_error = fidlbredr::ScoErrorCode::CANCELLED;
}
if (result.error_value() == bt::HostError::kParametersRejected) {
fidl_error = fidlbredr::ScoErrorCode::PARAMETERS_REJECTED;
}
receiver->Error(fidl_error);
return;
}
bt::sco::ScoConnection::WeakPtr connection = std::move(result.value().first);
const uint16_t max_tx_data_size = connection->max_tx_sdu_size();
fidl::InterfaceHandle<fidlbredr::ScoConnection> sco_handle;
std::unique_ptr<ScoConnectionServer> sco_server =
std::make_unique<ScoConnectionServer>(sco_handle.NewRequest(), connection);
auto [server_iter, _] = sco_connection_servers_.emplace(&connection.get(), std::move(sco_server));
// Activate after adding the connection to the map in case on_closed is called synchronously.
auto on_closed = [this, conn = &connection.get()] {
auto iter = sco_connection_servers_.find(conn);
BT_ASSERT(iter != sco_connection_servers_.end());
sco_connection_servers_.erase(iter);
};
server_iter->second->Activate(std::move(on_closed));
if (!receiver.is_bound()) {
return;
}
size_t parameter_index = result.value().second;
BT_ASSERT_MSG(parameter_index < request->parameters.size(),
"parameter_index (%zu) >= request->parameters.size() (%zu)", parameter_index,
request->parameters.size());
fidlbredr::ScoConnectionParameters parameters = std::move(request->parameters[parameter_index]);
parameters.set_max_tx_data_size(max_tx_data_size);
receiver->Connected(std::move(sco_handle), std::move(parameters));
}
void ProfileServer::OnAudioDirectionExtError(AudioDirectionExt* ext_server, zx_status_t status) {
bt_log(DEBUG, "fidl", "audio direction ext server closed (reason: %s)",
zx_status_get_string(status));
auto handle = audio_direction_ext_servers_.extract(ext_server->unique_id());
if (handle.empty()) {
bt_log(WARN, "fidl", "could not find ext server in audio direction ext error callback");
}
}
fidl::InterfaceHandle<fidlbredr::AudioDirectionExt> ProfileServer::BindAudioDirectionExtServer(
bt::l2cap::Channel::WeakPtr channel) {
fidl::InterfaceHandle<fidlbredr::AudioDirectionExt> client;
bt::l2cap::Channel::UniqueId unique_id = channel->unique_id();
auto audio_direction_ext_server =
std::make_unique<AudioDirectionExt>(client.NewRequest(), std::move(channel));
AudioDirectionExt* server_ptr = audio_direction_ext_server.get();
audio_direction_ext_server->set_error_handler(
[this, server_ptr](zx_status_t status) { OnAudioDirectionExtError(server_ptr, status); });
audio_direction_ext_servers_[unique_id] = std::move(audio_direction_ext_server);
return client;
}
void ProfileServer::OnL2capParametersExtError(L2capParametersExt* ext_server, zx_status_t status) {
bt_log(DEBUG, "fidl", "fidl parameters ext server closed (reason: %s)",
zx_status_get_string(status));
auto handle = l2cap_parameters_ext_servers_.extract(ext_server->unique_id());
if (handle.empty()) {
bt_log(WARN, "fidl", "could not find ext server in l2cap parameters ext error callback");
}
}
fidl::InterfaceHandle<fidlbredr::L2capParametersExt> ProfileServer::BindL2capParametersExtServer(
bt::l2cap::Channel::WeakPtr channel) {
fidl::InterfaceHandle<fidlbredr::L2capParametersExt> client;
bt::l2cap::Channel::UniqueId unique_id = channel->unique_id();
auto l2cap_parameters_ext_server =
std::make_unique<L2capParametersExt>(client.NewRequest(), std::move(channel));
L2capParametersExt* server_ptr = l2cap_parameters_ext_server.get();
l2cap_parameters_ext_server->set_error_handler(
[this, server_ptr](zx_status_t status) { OnL2capParametersExtError(server_ptr, status); });
l2cap_parameters_ext_servers_[unique_id] = std::move(l2cap_parameters_ext_server);
return client;
}
void ProfileServer::OnAudioOffloadExtError(AudioOffloadExt* ext_server, zx_status_t status) {
bt_log(DEBUG, "fidl", "audio offload ext server closed (reason: %s)",
zx_status_get_string(status));
auto handle = audio_offload_ext_servers_.extract(ext_server->unique_id());
if (handle.empty()) {
bt_log(WARN, "fidl", "could not find ext server in audio offload ext error callback");
}
}
fidl::InterfaceHandle<fidlbredr::AudioOffloadExt> ProfileServer::BindAudioOffloadExtServer(
bt::l2cap::Channel::WeakPtr channel) {
fidl::InterfaceHandle<fidlbredr::AudioOffloadExt> client;
bt::l2cap::Channel::UniqueId unique_id = channel->unique_id();
std::unique_ptr<bthost::ProfileServer::AudioOffloadExt> audio_offload_ext_server =
std::make_unique<AudioOffloadExt>(*this, client.NewRequest(), std::move(channel), adapter_);
AudioOffloadExt* server_ptr = audio_offload_ext_server.get();
audio_offload_ext_server->set_error_handler(
[this, server_ptr](zx_status_t status) { OnAudioOffloadExtError(server_ptr, status); });
audio_offload_ext_servers_[unique_id] = std::move(audio_offload_ext_server);
return client;
}
fuchsia::bluetooth::bredr::Channel ProfileServer::ChannelToFidl(
bt::l2cap::Channel::WeakPtr channel) {
BT_ASSERT(channel.is_alive());
fidlbredr::Channel fidl_chan;
fidl_chan.set_channel_mode(ChannelModeToFidl(channel->mode()));
fidl_chan.set_max_tx_sdu_size(channel->max_tx_sdu_size());
if (channel->info().flush_timeout) {
fidl_chan.set_flush_timeout(channel->info().flush_timeout->count());
}
if (adapter()->state().IsControllerFeatureSupported(FeaturesBits::kSetAclPriorityCommand)) {
fidl_chan.set_ext_direction(BindAudioDirectionExtServer(channel));
}
if (adapter()->state().IsControllerFeatureSupported(FeaturesBits::kAndroidVendorExtensions) &&
adapter()->state().android_vendor_capabilities->a2dp_source_offload_capability_mask()) {
fidl_chan.set_ext_audio_offload(BindAudioOffloadExtServer(channel));
}
fidl_chan.set_ext_l2cap(BindL2capParametersExtServer(channel));
auto closed_cb = [this, unique_id = channel->unique_id()]() {
l2cap_parameters_ext_servers_.erase(unique_id);
audio_direction_ext_servers_.erase(unique_id);
audio_offload_ext_servers_.erase(unique_id);
audio_offload_controller_server_ = nullptr;
};
auto sock = l2cap_socket_factory_.MakeSocketForChannel(std::move(channel), std::move(closed_cb));
fidl_chan.set_socket(std::move(sock));
return fidl_chan;
}
void ProfileServer::AudioDirectionExt::SetPriority(
fuchsia::bluetooth::bredr::A2dpDirectionPriority priority, SetPriorityCallback callback) {
channel_->RequestAclPriority(FidlToAclPriority(priority),
[cb = std::move(callback)](auto result) {
if (result.is_ok()) {
cb(fpromise::ok());
return;
}
bt_log(DEBUG, "fidl", "ACL priority request failed");
cb(fpromise::error(fuchsia::bluetooth::ErrorCode::FAILED));
});
}
void ProfileServer::AudioDirectionExt::handle_unknown_method(uint64_t ordinal,
bool method_has_response) {
bt_log(WARN, "fidl", "AudioDirectionExt: unknown method received");
}
} // namespace bthost