blob: 301803a838bf6d0b8c1d5895c28bbd2346a26420 [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 <zircon/status.h>
#include "helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/l2cap/types.h"
#include "src/connectivity/bluetooth/core/bt-host/sdp/status.h"
using fuchsia::bluetooth::ErrorCode;
using fuchsia::bluetooth::Status;
namespace fidlbredr = fuchsia::bluetooth::bredr;
using fidlbredr::DataElement;
using fidlbredr::Profile;
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::ChannelMode::kBasic;
break;
case fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION:
params.mode = bt::l2cap::ChannelMode::kEnhancedRetransmission;
break;
default:
ZX_PANIC("FIDL channel parameter contains invalid mode");
}
}
if (fidl.has_max_rx_sdu_size()) {
params.max_rx_sdu_size = fidl.max_rx_sdu_size();
}
return params;
}
fidlbredr::ChannelMode ChannelModeToFidl(bt::l2cap::ChannelMode mode) {
switch (mode) {
case bt::l2cap::ChannelMode::kBasic:
return fidlbredr::ChannelMode::BASIC;
break;
case bt::l2cap::ChannelMode::kEnhancedRetransmission:
return fidlbredr::ChannelMode::ENHANCED_RETRANSMISSION;
break;
default:
ZX_PANIC("Could not convert channel parameter mode to unsupported FIDL mode");
}
}
fidlbredr::Channel ChannelSocketToFidlChannel(bt::l2cap::ChannelSocket chan_sock) {
fidlbredr::Channel chan;
if (!chan_sock) {
return chan;
}
chan.set_socket(std::move(chan_sock.socket));
chan.set_channel_mode(ChannelModeToFidl(chan_sock.params->mode));
chan.set_max_tx_sdu_size(chan_sock.params->max_tx_sdu_size);
return chan;
}
bool FidlToDataElement(const fidlbredr::DataElement& fidl, bt::sdp::DataElement* out) {
ZX_DEBUG_ASSERT(out);
switch (fidl.Which()) {
case fidlbredr::DataElement::Tag::kInt8:
out->Set(fidl.int8());
break;
case fidlbredr::DataElement::Tag::kInt16:
out->Set(fidl.int16());
break;
case fidlbredr::DataElement::Tag::kInt32:
out->Set(fidl.int32());
break;
case fidlbredr::DataElement::Tag::kInt64:
out->Set(fidl.int64());
break;
case fidlbredr::DataElement::Tag::kUint8:
out->Set(fidl.uint8());
break;
case fidlbredr::DataElement::Tag::kUint16:
out->Set(fidl.uint16());
break;
case fidlbredr::DataElement::Tag::kUint32:
out->Set(fidl.uint32());
break;
case fidlbredr::DataElement::Tag::kUint64:
out->Set(fidl.uint64());
break;
case fidlbredr::DataElement::Tag::kStr:
out->Set(fidl.str());
break;
case fidlbredr::DataElement::Tag::kB:
out->Set(fidl.b());
break;
case fidlbredr::DataElement::Tag::kUuid:
out->Set(fidl_helpers::UuidFromFidl(fidl.uuid()));
break;
case fidlbredr::DataElement::Tag::kSequence: {
std::vector<bt::sdp::DataElement> seq;
for (const auto& fidl_elem : fidl.sequence()) {
bt::sdp::DataElement it;
if (!FidlToDataElement(*fidl_elem, &it)) {
return false;
}
seq.emplace_back(std::move(it));
}
out->Set(std::move(seq));
break;
}
case fidlbredr::DataElement::Tag::kAlternatives: {
std::vector<bt::sdp::DataElement> alts;
for (const auto& fidl_elem : fidl.alternatives()) {
bt::sdp::DataElement it;
if (!FidlToDataElement(*fidl_elem, &it)) {
return false;
}
alts.emplace_back(std::move(it));
}
out->SetAlternative(std::move(alts));
break;
}
default:
// Types not handled: Null datatype (never used) and Url data type (not supported by Set)
bt_log(WARN, "profile_server", "Encountered FidlToDataElement type not handled.");
return false;
}
return true;
}
fidlbredr::DataElementPtr DataElementToFidl(const bt::sdp::DataElement* in) {
auto elem = fidlbredr::DataElement::New();
bt_log(SPEW, "sdp", "DataElementToFidl: %s", in->ToString().c_str());
ZX_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, "profile_server", "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, "profile_server", "no 128-bit integer support in FIDL yet");
return nullptr;
}
return elem;
}
case bt::sdp::DataElement::Type::kUuid: {
auto uuid = in->Get<bt::UUID>();
ZX_DEBUG_ASSERT(uuid);
elem->set_uuid(fidl_helpers::UuidToFidl(*uuid));
return elem;
}
case bt::sdp::DataElement::Type::kString: {
elem->set_str(*in->Get<std::string>());
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: {
bt_log(INFO, "profile_server", "no support for Url types in DataElement yet");
return nullptr;
}
case bt::sdp::DataElement::Type::kNull: {
bt_log(INFO, "profile_server", "no support for null DataElement types in FIDL");
return nullptr;
}
}
}
fidlbredr::ProtocolDescriptorPtr DataElementToProtocolDescriptor(const bt::sdp::DataElement* in) {
auto desc = fidlbredr::ProtocolDescriptor::New();
if (in->type() != bt::sdp::DataElement::Type::kSequence) {
return nullptr;
}
const auto protocol_uuid = in->At(0)->Get<bt::UUID>();
if (!protocol_uuid) {
return nullptr;
}
desc->protocol = 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;
}
void AddProtocolDescriptorList(
bt::sdp::ServiceRecord* rec, bt::sdp::ServiceRecord::ProtocolListId id,
const ::std::vector<fidlbredr::ProtocolDescriptor>& descriptor_list) {
bt_log(SPEW, "profile_server", "ProtocolDescriptorList %d", id);
for (auto& descriptor : descriptor_list) {
bt::sdp::DataElement protocol_params;
if (descriptor.params.size() > 1) {
std::vector<bt::sdp::DataElement> params;
for (auto& fidl_param : descriptor.params) {
bt::sdp::DataElement bt_param;
FidlToDataElement(fidl_param, &bt_param);
params.emplace_back(std::move(bt_param));
}
protocol_params.Set(std::move(params));
} else if (descriptor.params.size() == 1) {
FidlToDataElement(descriptor.params.front(), &protocol_params);
}
bt_log(SPEW, "profile_server", "%d : %s", fidl::ToUnderlying(descriptor.protocol),
protocol_params.ToString().c_str());
rec->AddProtocolDescriptor(id, bt::UUID(static_cast<uint16_t>(descriptor.protocol)),
std::move(protocol_params));
}
}
} // namespace
ProfileServer::ProfileServer(fxl::WeakPtr<bt::gap::Adapter> adapter,
fidl::InterfaceRequest<Profile> request)
: ServerBase(this, std::move(request)),
advertised_total_(0),
searches_total_(0),
adapter_(adapter),
weak_ptr_factory_(this) {}
ProfileServer::~ProfileServer() {
if (adapter()) {
// Unregister anything that we have registered.
auto sdp = adapter()->sdp_server();
for (const auto& it : current_advertised_) {
sdp->UnregisterService(it.second.registration_handle);
}
auto conn_manager = adapter()->bredr_connection_manager();
for (const auto& it : searches_) {
conn_manager->RemoveServiceSearch(it.second.search_id);
}
}
}
void ProfileServer::Advertise(
std::vector<fidlbredr::ServiceDefinition> definitions,
fidlbredr::SecurityRequirements requirements, fidlbredr::ChannelParameters parameters,
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::ConnectionReceiver> receiver) {
// TODO: check that the service definition is valid for useful error messages
std::vector<bt::sdp::ServiceRecord> registering;
for (auto& definition : definitions) {
bt::sdp::ServiceRecord rec;
std::vector<bt::UUID> classes;
if (!definition.has_service_class_uuids()) {
bt_log(INFO, "profile_server", "Advertised service contains no Service UUIDs");
// Dropping receiver as we didn't register.
return;
}
for (auto& uuid : definition.service_class_uuids()) {
bt::UUID btuuid = fidl_helpers::UuidFromFidl(uuid);
bt_log(SPEW, "profile_server", "Setting Service Class UUID %s", bt_str(btuuid));
classes.emplace_back(std::move(btuuid));
}
rec.SetServiceClassUUIDs(classes);
if (definition.has_protocol_descriptor_list()) {
AddProtocolDescriptorList(&rec, bt::sdp::ServiceRecord::kPrimaryProtocolList,
definition.protocol_descriptor_list());
}
if (definition.has_additional_protocol_descriptor_lists()) {
size_t protocol_list_id = 1;
for (const auto& descriptor_list : definition.additional_protocol_descriptor_lists()) {
AddProtocolDescriptorList(&rec, protocol_list_id, descriptor_list);
protocol_list_id++;
}
}
if (definition.has_profile_descriptors()) {
for (const auto& profile : definition.profile_descriptors()) {
bt_log(SPEW, "profile_server", "Adding Profile %#hx v%d.%d", profile.profile_id,
profile.major_version, profile.minor_version);
rec.AddProfile(bt::UUID(uint16_t(profile.profile_id)), profile.major_version,
profile.minor_version);
}
}
if (definition.has_information()) {
for (const auto& info : definition.information()) {
if (!info.has_language()) {
bt_log(INFO, "profile_server", "Adding information to service definition: no language!");
// Dropping the receiver as it's not registered.
return;
}
std::string language = info.language();
std::string name, description, provider;
if (info.has_name()) {
name = info.name();
}
if (info.has_description()) {
description = info.description();
}
if (info.has_provider()) {
provider = info.provider();
}
bt_log(SPEW, "profile_server", "Adding Info (%s): (%s, %s, %s)", language.c_str(),
name.c_str(), description.c_str(), provider.c_str());
rec.AddInfo(language, name, description, provider);
}
}
if (definition.has_additional_attributes()) {
for (const auto& attribute : definition.additional_attributes()) {
bt::sdp::DataElement elem;
FidlToDataElement(attribute.element, &elem);
bt_log(SPEW, "profile_server", "Adding attribute %#x : %s", attribute.id,
elem.ToString().c_str());
rec.SetAttribute(attribute.id, std::move(elem));
}
}
registering.emplace_back(std::move(rec));
}
ZX_DEBUG_ASSERT(adapter());
auto sdp = adapter()->sdp_server();
ZX_DEBUG_ASSERT(sdp);
uint64_t next = advertised_total_ + 1;
auto registration_handle = sdp->RegisterService(
std::move(registering), FidlToChannelParameters(parameters),
[this, next](auto chan_sock, auto handle, const auto& protocol_list) {
OnChannelConnected(next, std::move(chan_sock), handle, std::move(protocol_list));
});
if (!registration_handle) {
return;
};
auto receiverptr = receiver.Bind();
receiverptr.set_error_handler(
[this, next](zx_status_t status) { OnConnectionReceiverError(next, status); });
current_advertised_.try_emplace(next, std::move(receiverptr), registration_handle);
advertised_total_ = next;
}
void ProfileServer::Search(
fidlbredr::ServiceClassProfileIdentifier service_uuid, std::vector<uint16_t> attr_ids,
fidl::InterfaceHandle<fuchsia::bluetooth::bredr::SearchResults> results) {
bt::UUID search_uuid(static_cast<uint32_t>(service_uuid));
std::unordered_set<bt::sdp::AttributeId> attributes(attr_ids.begin(), attr_ids.end());
if (!attr_ids.empty()) {
// Always request the ProfileDescriptor for the event
attributes.insert(bt::sdp::kBluetoothProfileDescriptorList);
}
ZX_DEBUG_ASSERT(adapter());
auto next = searches_total_ + 1;
auto search_id = adapter()->bredr_connection_manager()->AddServiceSearch(
search_uuid, std::move(attributes),
[this, next](auto id, const auto& attrs) { OnServiceFound(next, id, attrs); });
if (!search_id) {
return;
}
auto results_ptr = 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, uint16_t psm,
fidlbredr::ChannelParameters parameters, ConnectCallback callback) {
bt::PeerId id{peer_id.value};
auto connected_cb = [cb = callback.share()](auto chan_sock) {
if (!chan_sock) {
bt_log(SPEW, "profile_server", "Channel socket is empty, returning failed.");
cb(fit::error(fuchsia::bluetooth::ErrorCode::FAILED));
return;
}
cb(fit::ok(ChannelSocketToFidlChannel(std::move(chan_sock))));
};
ZX_DEBUG_ASSERT(adapter());
bool connecting = adapter()->bredr_connection_manager()->OpenL2capChannel(
id, psm, FidlToChannelParameters(parameters), std::move(connected_cb),
async_get_default_dispatcher());
if (!connecting) {
callback(fit::error(fuchsia::bluetooth::ErrorCode::NOT_FOUND));
}
}
void ProfileServer::OnChannelConnected(uint64_t ad_id, bt::l2cap::ChannelSocket chan_sock,
bt::hci::ConnectionHandle handle,
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;
}
ZX_DEBUG_ASSERT(adapter());
auto id = adapter()->bredr_connection_manager()->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);
ZX_ASSERT(prot_seq);
fidlbredr::ProtocolDescriptorPtr desc = DataElementToProtocolDescriptor(prot_seq);
ZX_ASSERT(desc);
fuchsia::bluetooth::PeerId peer_id{id.value()};
std::vector<fidlbredr::ProtocolDescriptor> list;
list.emplace_back(std::move(*desc));
it->second.receiver->Connected(peer_id, ChannelSocketToFidlChannel(std::move(chan_sock)),
std::move(list));
}
void ProfileServer::OnConnectionReceiverError(uint64_t ad_id, zx_status_t status) {
bt_log(SPEW, "profile_server", "Connection receiver closed, ending advertisement %lu", ad_id);
auto it = current_advertised_.find(ad_id);
if (it == current_advertised_.end() || !adapter()) {
return;
}
adapter()->sdp_server()->UnregisterService(it->second.registration_handle);
current_advertised_.erase(it);
}
void ProfileServer::OnSearchResultError(uint64_t search_id, zx_status_t status) {
bt_log(SPEW, "profile_server", "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()) {
return;
}
adapter()->bredr_connection_manager()->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 = fidlbredr::Attribute::New();
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), []() {});
}
} // namespace bthost