blob: 04f23219b4b0a128faf3abbf1b0978fa1996a63f [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 "helpers.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.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::DataElementType;
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.type) {
case DataElementType::NOTHING:
out->Set(nullptr);
return true;
case DataElementType::UNSIGNED_INTEGER: {
if (!fidl.data.is_integer()) {
return false;
}
if (fidl.size == 1) {
out->Set(uint8_t(fidl.data.integer()));
} else if (fidl.size == 2) {
out->Set(uint16_t(fidl.data.integer()));
} else if (fidl.size == 4) {
out->Set(uint32_t(fidl.data.integer()));
} else if (fidl.size == 8) {
out->Set(uint64_t(fidl.data.integer()));
} else {
return false;
}
return true;
}
case DataElementType::SIGNED_INTEGER: {
if (!fidl.data.is_integer()) {
return false;
}
if (fidl.size == 1) {
out->Set(int8_t(fidl.data.integer()));
} else if (fidl.size == 2) {
out->Set(int16_t(fidl.data.integer()));
} else if (fidl.size == 4) {
out->Set(int32_t(fidl.data.integer()));
} else if (fidl.size == 8) {
out->Set(int64_t(fidl.data.integer()));
} else {
return false;
}
return true;
}
case DataElementType::UUID: {
if (!fidl.data.is_uuid()) {
return false;
}
bt::UUID uuid;
bool success = StringToUuid(fidl.data.uuid(), &uuid);
if (!success) {
return false;
}
out->Set(uuid);
return true;
}
case DataElementType::STRING: {
if (!fidl.data.is_str()) {
return false;
}
out->Set(fidl.data.str());
return true;
}
case DataElementType::BOOLEAN: {
if (!fidl.data.is_b()) {
return false;
}
out->Set(fidl.data.b());
return true;
}
case DataElementType::SEQUENCE: {
if (!fidl.data.is_sequence()) {
return false;
}
bool success = true;
std::vector<bt::sdp::DataElement> elems;
for (const auto& fidl_elem : fidl.data.sequence()) {
bt::sdp::DataElement it;
success = FidlToDataElement(*fidl_elem, &it);
if (!success) {
return false;
}
elems.emplace_back(std::move(it));
}
out->Set(std::move(elems));
return true;
}
default:
return false;
}
}
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::kNull:
elem->type = DataElementType::NOTHING;
return elem;
case bt::sdp::DataElement::Type::kUnsignedInt: {
elem->type = DataElementType::UNSIGNED_INTEGER;
auto size = in->size();
if (size == bt::sdp::DataElement::Size::kOneByte) {
elem->data.set_integer(*in->Get<uint8_t>());
} else if (size == bt::sdp::DataElement::Size::kTwoBytes) {
elem->data.set_integer(*in->Get<uint16_t>());
} else if (size == bt::sdp::DataElement::Size::kFourBytes) {
elem->data.set_integer(*in->Get<uint32_t>());
} else if (size == bt::sdp::DataElement::Size::kEightBytes) {
elem->data.set_integer(*in->Get<uint64_t>());
} else {
// TODO: handle 128-bit integers
bt_log(DEBUG, "profile_server", "no 128-bit integer type yet");
return nullptr;
}
return elem;
}
case bt::sdp::DataElement::Type::kSignedInt: {
elem->type = DataElementType::SIGNED_INTEGER;
auto size = in->size();
if (size == bt::sdp::DataElement::Size::kOneByte) {
elem->data.set_integer(*in->Get<int8_t>());
} else if (size == bt::sdp::DataElement::Size::kTwoBytes) {
elem->data.set_integer(*in->Get<int16_t>());
} else if (size == bt::sdp::DataElement::Size::kFourBytes) {
elem->data.set_integer(*in->Get<int32_t>());
} else if (size == bt::sdp::DataElement::Size::kEightBytes) {
elem->data.set_integer(*in->Get<int64_t>());
} else {
// TODO: handle 128-bit integers
bt_log(DEBUG, "profile_server", "no 128-bit integer type yet");
return nullptr;
}
return elem;
}
case bt::sdp::DataElement::Type::kUuid: {
elem->type = DataElementType::UUID;
auto uuid = in->Get<bt::UUID>();
ZX_DEBUG_ASSERT(uuid);
elem->data.uuid() = uuid->ToString();
return elem;
}
case bt::sdp::DataElement::Type::kString: {
elem->type = DataElementType::STRING;
elem->data.str() = *in->Get<std::string>();
return elem;
}
case bt::sdp::DataElement::Type::kBoolean: {
elem->type = DataElementType::BOOLEAN;
elem->data.set_b(*in->Get<bool>());
return elem;
}
case bt::sdp::DataElement::Type::kSequence: {
elem->type = DataElementType::SEQUENCE;
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->data.set_sequence(std::move(elems));
return elem;
}
case bt::sdp::DataElement::Type::kAlternative: {
elem->type = DataElementType::ALTERNATIVE;
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->data.set_sequence(std::move(elems));
return elem;
}
case bt::sdp::DataElement::Type::kUrl: {
ZX_PANIC("not implemented");
break;
}
}
}
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;
}
fidlbredr::ProfileDescriptorPtr DataElementToProfileDescriptor(const bt::sdp::DataElement* in) {
auto desc = fidlbredr::ProfileDescriptor::New();
if (in->type() != bt::sdp::DataElement::Type::kSequence) {
return nullptr;
}
const bt::sdp::DataElement* profile_desc = in->At(0);
const bt::sdp::DataElement* profile_elem = profile_desc->At(0);
if (!profile_elem) {
return nullptr;
}
const auto profile_uuid = profile_elem->Get<bt::UUID>();
if (!profile_uuid) {
return nullptr;
}
desc->profile_id = fidlbredr::ServiceClassProfileIdentifier(*profile_uuid->As16Bit());
const bt::sdp::DataElement* version_elem = profile_desc->At(1);
if (!version_elem) {
return nullptr;
}
const auto version = version_elem->Get<uint16_t>();
if (!version) {
return nullptr;
}
desc->major_version = static_cast<uint8_t>(*version >> 8);
desc->minor_version = static_cast<uint8_t>(*version & 0xFF);
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)),
last_service_id_(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 : registered_) {
sdp->UnregisterService(it.second);
}
for (const auto& search_id : searches_) {
adapter()->bredr_connection_manager()->RemoveServiceSearch(search_id);
}
}
}
void ProfileServer::AddService(fidlbredr::ServiceDefinition definition,
fidlbredr::SecurityLevel sec_level,
fidlbredr::ChannelParameters parameters,
AddServiceCallback callback) {
// TODO: check that the service definition is valid for useful error messages
bt::sdp::ServiceRecord rec;
std::vector<bt::UUID> classes;
for (auto& uuid_str : definition.service_class_uuids) {
bt::UUID uuid;
bt_log(SPEW, "profile_server", "Setting Service Class UUID %s", uuid_str.c_str());
bool success = bt::StringToUuid(uuid_str, &uuid);
if (!success) {
callback(
fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, "Service class UUIDs not valid"),
0);
return;
};
classes.emplace_back(std::move(uuid));
}
rec.SetServiceClassUUIDs(classes);
AddProtocolDescriptorList(&rec, bt::sdp::ServiceRecord::kPrimaryProtocolList,
definition.protocol_descriptors);
size_t protocol_list_id = 1;
if (definition.additional_protocol_descriptors.has_value()) {
for (const auto& descriptor_list : *definition.additional_protocol_descriptors) {
AddProtocolDescriptorList(&rec, protocol_list_id, descriptor_list);
protocol_list_id++;
}
}
for (const auto& profile : definition.profile_descriptors) {
bt_log(SPEW, "profile_server", "Adding Profile %#x 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);
}
for (const auto& info : definition.information) {
bt_log(SPEW, "profile_server", "Adding Info (%s): (%s, %s, %s)", info.language.c_str(),
info.name.value_or("").c_str(), info.description.value_or("").c_str(),
info.provider.value_or("").c_str());
rec.AddInfo(info.language, info.name.value_or(""), info.description.value_or(""),
info.provider.value_or(""));
}
if (definition.additional_attributes.has_value()) {
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));
}
}
uint64_t next = last_service_id_ + 1;
ZX_DEBUG_ASSERT(adapter());
auto sdp = adapter()->sdp_server();
ZX_DEBUG_ASSERT(sdp);
auto handle = sdp->RegisterService(
std::move(rec), 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 (!handle) {
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
"Service definition was not valid"),
0);
return;
};
registered_.emplace(next, handle);
last_service_id_ = next;
callback(fidl_helpers::StatusToFidlDeprecated(bt::sdp::Status()), next);
}
void ProfileServer::RemoveService(uint64_t service_id) {
auto it = registered_.find(service_id);
if (it == registered_.end()) {
bt_log(INFO, "profile_server", "RemoveService with unused id %lu", service_id);
return;
}
ZX_DEBUG_ASSERT(adapter());
auto server = adapter()->sdp_server();
ZX_DEBUG_ASSERT(server);
bool removed = server->UnregisterService(it->second);
ZX_DEBUG_ASSERT(removed);
registered_.erase(it);
}
void ProfileServer::AddSearch(fidlbredr::ServiceClassProfileIdentifier service_uuid,
std::vector<uint16_t> attr_ids) {
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 search_id = adapter()->bredr_connection_manager()->AddServiceSearch(
search_uuid, std::move(attributes),
[this](auto id, const auto& attrs) { OnServiceFound(id, attrs); });
if (search_id) {
searches_.emplace_back(search_id);
}
}
void ProfileServer::ConnectL2cap(std::string peer_id, uint16_t channel,
fidlbredr::ChannelParameters parameters,
ConnectL2capCallback callback) {
auto dev_id = fidl_helpers::PeerIdFromString(peer_id);
if (!dev_id.has_value()) {
callback(fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, "invalid device ID"),
fidlbredr::Channel());
return;
}
auto connected_cb = [cb = callback.share()](auto chan_sock) {
cb(fidl_helpers::StatusToFidlDeprecated(bt::sdp::Status()),
ChannelSocketToFidlChannel(std::move(chan_sock)));
};
ZX_DEBUG_ASSERT(adapter());
bool connecting = adapter()->bredr_connection_manager()->OpenL2capChannel(
*dev_id, channel, FidlToChannelParameters(parameters), std::move(connected_cb),
async_get_default_dispatcher());
if (!connecting) {
callback(fidl_helpers::NewFidlError(ErrorCode::NOT_FOUND,
"Remote device not found - is it connected?"),
fidlbredr::Channel());
}
}
void ProfileServer::OnChannelConnected(uint64_t service_id, bt::l2cap::ChannelSocket chan_sock,
bt::hci::ConnectionHandle handle,
const bt::sdp::DataElement& protocol_list) {
ZX_DEBUG_ASSERT(adapter());
auto id = adapter()->bredr_connection_manager()->GetPeerId(handle);
const auto* prot_seq = protocol_list.At(1);
// If there isn't a second-level protocol, return the l2cap protocol
if (!prot_seq) {
prot_seq = protocol_list.At(0);
}
ZX_ASSERT(prot_seq);
fidlbredr::ProtocolDescriptorPtr desc = DataElementToProtocolDescriptor(prot_seq);
ZX_ASSERT(desc);
binding()->events().OnConnected(id.ToString(), service_id,
ChannelSocketToFidlChannel(std::move(chan_sock)),
std::move(*desc));
}
void ProfileServer::OnServiceFound(
bt::PeerId peer_id, const std::map<bt::sdp::AttributeId, bt::sdp::DataElement>& attributes) {
// Convert ProfileDescriptor Attribute
auto it = attributes.find(bt::sdp::kBluetoothProfileDescriptorList);
if (it == attributes.end()) {
bt_log(WARN, "profile_server",
"Found service on %s didn't contain profile descriptor, dropping", bt_str(peer_id));
return;
}
fidlbredr::ProfileDescriptorPtr desc = DataElementToProfileDescriptor(&it->second);
// 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));
}
binding()->events().OnServiceFound(peer_id.ToString(), std::move(*desc), std::move(fidl_attrs));
}
} // namespace bthost