| // 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 |