| // 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 "server.h" |
| |
| #include <lib/async/default.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/rfcomm/rfcomm.h" |
| #include "garnet/drivers/bluetooth/lib/sdp/pdu.h" |
| |
| namespace btlib { |
| namespace sdp { |
| |
| using common::BufferView; |
| using common::UUID; |
| |
| namespace { |
| |
| // The VersionNumberList value. (5.0, Vol 3, Part B, 5.2.3) |
| constexpr uint16_t kVersion = 0x0100; // Version 1.0 |
| |
| // The initial ServiceDatabaseState |
| constexpr uint32_t kInitialDbState = 0; |
| |
| // Populates the ServiceDiscoveryService record. |
| ServiceRecord MakeServiceDiscoveryService() { |
| ServiceRecord sdp; |
| sdp.SetHandle(kSDPHandle); |
| |
| // ServiceClassIDList attribute should have the |
| // ServiceDiscoveryServerServiceClassID |
| // See v5.0, Vol 3, Part B, Sec 5.2.2 |
| sdp.SetServiceClassUUIDs({profile::kServiceDiscoveryClass}); |
| |
| // The VersionNumberList attribute. See v5.0, Vol 3, Part B, Sec 5.2.3 |
| // Version 1.0 |
| sdp.SetAttribute(kSDP_VersionNumberList, |
| DataElement(std::vector<DataElement>{kVersion})); |
| |
| // ServiceDatabaseState attribute. Changes when a service gets added or |
| // removed. |
| sdp.SetAttribute(kSDP_ServiceDatabaseState, DataElement(kInitialDbState)); |
| |
| return sdp; |
| } |
| |
| void SendErrorResponse(const fbl::RefPtr<l2cap::Channel>& chan, |
| TransactionId tid, ErrorCode code) { |
| ErrorResponse response(code); |
| chan->Send(response.GetPDU(0 /* ignored */, tid, BufferView())); |
| } |
| |
| // Finds the PSM that is specified in a ProtocolDescriptorList |
| // Returns l2cap::kInvalidPSM if none is found or the list is invalid |
| l2cap::PSM FindProtocolListPSM(const DataElement& protocol_list) { |
| bt_log(SPEW, "sdp", "Trying to find PSM from %s", |
| protocol_list.ToString().c_str()); |
| const auto* l2cap_protocol = protocol_list.At(0); |
| ZX_DEBUG_ASSERT(l2cap_protocol); |
| const auto* prot_uuid = l2cap_protocol->At(0); |
| if (!prot_uuid || prot_uuid->type() != DataElement::Type::kUuid || |
| *prot_uuid->Get<UUID>() != protocol::kL2CAP) { |
| bt_log(SPEW, "sdp", "ProtocolDescriptorList is not valid or not L2CAP"); |
| return l2cap::kInvalidPSM; |
| } |
| |
| const auto* psm_elem = l2cap_protocol->At(1); |
| if (psm_elem && psm_elem->type() == DataElement::Type::kUnsignedInt) { |
| return *psm_elem->Get<uint16_t>(); |
| } else if (psm_elem) { |
| bt_log(SPEW, "sdp", "ProtocolDescriptorList invalid L2CAP parameter type"); |
| return l2cap::kInvalidPSM; |
| } |
| |
| // The PSM is missing, determined by the next protocol. |
| const auto* next_protocol = protocol_list.At(1); |
| if (!next_protocol) { |
| bt_log(SPEW, "sdp", "L2CAP has no PSM and no additional protocol"); |
| return l2cap::kInvalidPSM; |
| } |
| const auto* next_protocol_uuid = next_protocol->At(0); |
| if (!next_protocol_uuid || |
| next_protocol_uuid->type() != DataElement::Type::kUuid) { |
| bt_log(SPEW, "sdp", "L2CAP has no PSM and additional protocol invalid"); |
| return l2cap::kInvalidPSM; |
| } |
| UUID protocol_uuid = *next_protocol_uuid->Get<UUID>(); |
| // When it's RFCOMM, the L2CAP protocol descriptor omits the PSM parameter |
| if (protocol_uuid == protocol::kRFCOMM) { |
| return l2cap::kRFCOMM; |
| } |
| bt_log(SPEW, "sdp", "Can't determine L2CAP PSM from protocol"); |
| return l2cap::kInvalidPSM; |
| } |
| |
| // Writes the RFCOMM channel into a ProtocolDescriptorList |
| DataElement WriteRFCOMMChannel(const DataElement& protocol_list, |
| rfcomm::ServerChannel channel) { |
| return protocol_list.Clone(); |
| } |
| |
| } // namespace |
| |
| Server::Server(fbl::RefPtr<data::Domain> data_domain) |
| : data_domain_(data_domain), |
| next_handle_(kFirstUnreservedHandle), |
| db_state_(0), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(data_domain_); |
| |
| records_.emplace(kSDPHandle, MakeServiceDiscoveryService()); |
| |
| // Register SDP |
| data_domain_->RegisterService( |
| l2cap::kSDP, |
| [self = weak_ptr_factory_.GetWeakPtr()](auto channel) { |
| if (self) |
| self->AddConnection(channel); |
| }, |
| async_get_default_dispatcher()); |
| |
| // SDP and RFCOMM are used by SDP server. |
| psm_to_service_.emplace(l2cap::kSDP, kSDPHandle); |
| psm_to_service_.emplace(l2cap::kRFCOMM, kSDPHandle); |
| } |
| |
| Server::~Server() { data_domain_->UnregisterService(l2cap::kSDP); } |
| |
| bool Server::AddConnection(fbl::RefPtr<l2cap::Channel> channel) { |
| bt_log(TRACE, "sdp", "add connection handle %#.4x", channel->link_handle()); |
| |
| hci::ConnectionHandle handle = channel->link_handle(); |
| auto iter = channels_.find(channel->link_handle()); |
| if (iter != channels_.end()) { |
| bt_log(WARN, "sdp", "handle %#.4x already connected", handle); |
| return false; |
| } |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| bool activated = channel->Activate( |
| [self, handle](const l2cap::SDU& sdu) { |
| if (self) { |
| self->OnRxBFrame(handle, sdu); |
| } |
| }, |
| [self, handle] { |
| if (self) { |
| self->OnChannelClosed(handle); |
| } |
| }, |
| async_get_default_dispatcher()); |
| if (!activated) { |
| bt_log(WARN, "sdp", "failed to activate channel (handle %#.4x)", handle); |
| return false; |
| } |
| self->channels_.emplace(handle, std::move(channel)); |
| return true; |
| } |
| |
| ServiceHandle Server::RegisterService(ServiceRecord record, |
| ConnectCallback conn_cb) { |
| ServiceHandle next = GetNextHandle(); |
| if (!next) { |
| return 0; |
| } |
| |
| record.SetHandle(next); |
| |
| // Services must at least have a ServiceClassIDList (5.0, Vol 3, Part B, 5.1) |
| if (!record.HasAttribute(kServiceClassIdList)) { |
| bt_log(SPEW, "sdp", "new record doesn't have a ServiceClass"); |
| return 0; |
| } |
| // Class ID list is a data element sequence in which each data element is |
| // a UUID representing the service classes that a given service record |
| // conforms to. (5.0, Vol 3, Part B, 5.1.2) |
| const DataElement& class_id_list = record.GetAttribute(kServiceClassIdList); |
| if (class_id_list.type() != DataElement::Type::kSequence) { |
| bt_log(SPEW, "sdp", "class ID list isn't a sequence"); |
| return 0; |
| } |
| size_t idx; |
| const DataElement* elem; |
| for (idx = 0; nullptr != (elem = class_id_list.At(idx)); idx++) { |
| if (elem->type() != DataElement::Type::kUuid) { |
| bt_log(SPEW, "sdp", "class ID list elements are not all UUIDs"); |
| return 0; |
| } |
| } |
| if (idx == 0) { |
| bt_log(SPEW, "sdp", "no elements in the Class ID list (need at least 1)"); |
| return 0; |
| } |
| |
| // All services are placed in the top-level browse group. |
| std::vector<DataElement> browse_list; |
| browse_list.emplace_back(DataElement(kPublicBrowseRootUuid)); |
| record.SetAttribute(kBrowseGroupList, DataElement(std::move(browse_list))); |
| |
| // ProtocolDescriptorList handling: |
| if (record.HasAttribute(kProtocolDescriptorList)) { |
| const auto& primary_list = record.GetAttribute(kProtocolDescriptorList); |
| const auto* primary_protocol = primary_list.At(0); |
| if (!primary_protocol) { |
| bt_log(SPEW, "sdp", "ProtocolDescriptorList is not a sequence"); |
| return 0; |
| } |
| |
| const auto* prot_uuid = primary_protocol->At(0); |
| if (!prot_uuid || prot_uuid->type() != DataElement::Type::kUuid) { |
| bt_log(SPEW, "sdp", "ProtocolDescriptorList is not valid"); |
| return 0; |
| } |
| |
| // We do nothing for primary protocols that are not L2CAP |
| if (*prot_uuid->Get<UUID>() == protocol::kL2CAP) { |
| l2cap::PSM psm = FindProtocolListPSM(primary_list); |
| if (psm == l2cap::kInvalidPSM) { |
| bt_log(SPEW, "sdp", "Couldn't find PSM from ProtocolDescriptorList"); |
| return 0; |
| } |
| |
| if (!conn_cb) { |
| bt_log(SPEW, "sdp", "Connection expected but no conn_cb provided"); |
| return 0; |
| } |
| |
| if (psm == l2cap::kRFCOMM) { |
| const auto* rfcomm_protocol = primary_list.At(1); |
| if (!rfcomm_protocol) { |
| bt_log(SPEW, "sdp", "ProtocolDesciptorList missing RFCOMM protocol"); |
| return 0; |
| } |
| const auto* rfcomm_uuid = rfcomm_protocol->At(0); |
| if (!rfcomm_uuid || rfcomm_uuid->type() != DataElement::Type::kUuid || |
| *rfcomm_uuid->Get<UUID>() != protocol::kRFCOMM) { |
| bt_log(SPEW, "sdp", "L2CAP is RFCOMM, but RFCOMM is not specified"); |
| return 0; |
| } |
| // TODO(NET-1015): allocate an actual RFCOMM channel with RFCOMM |
| rfcomm::ServerChannel rfcomm_channel = 0; |
| record.SetAttribute(kProtocolDescriptorList, |
| WriteRFCOMMChannel(primary_list, rfcomm_channel)); |
| } else if (psm_to_service_.count(psm)) { |
| bt_log(SPEW, "sdp", "L2CAP PSM %#.4x is already allocated", psm); |
| return 0; |
| } else { |
| bt_log(SPEW, "sdp", "Allocating PSM %#.4x for new service", psm); |
| psm_to_service_.emplace(psm, next); |
| data_domain_->RegisterService( |
| psm, |
| [psm, protocol = primary_list.Clone(), |
| conn_cb = std::move(conn_cb)](auto socket, auto handle) mutable { |
| bt_log(SPEW, "sdp", "Channel connected to %#.4x", psm); |
| conn_cb(std::move(socket), handle, std::move(protocol)); |
| }, |
| async_get_default_dispatcher()); |
| auto psm_place = |
| service_to_psms_.emplace(next, std::unordered_set<l2cap::PSM>{psm}); |
| if (!psm_place.second) { |
| psm_place.first->second.insert(psm); |
| } |
| } |
| } |
| } |
| |
| auto placement = records_.emplace(next, std::move(record)); |
| ZX_DEBUG_ASSERT(placement.second); |
| bt_log(SPEW, "sdp", "registered service %#.8x, classes: %s", next, |
| placement.first->second.GetAttribute(kServiceClassIdList) |
| .ToString() |
| .c_str()); |
| return next; |
| } |
| |
| bool Server::UnregisterService(ServiceHandle handle) { |
| if (handle == kSDPHandle || records_.find(handle) == records_.end()) { |
| return false; |
| } |
| bt_log(TRACE, "sdp", "unregistering service (handle: %#.8x)", handle); |
| |
| // Unregister any service callbacks from L2CAP |
| auto psms_it = service_to_psms_.find(handle); |
| if (psms_it != service_to_psms_.end()) { |
| for (const auto& psm : psms_it->second) { |
| data_domain_->UnregisterService(psm); |
| psm_to_service_.erase(psm); |
| } |
| service_to_psms_.erase(psms_it); |
| } |
| |
| records_.erase(handle); |
| return true; |
| } |
| |
| ServiceHandle Server::GetNextHandle() { |
| ServiceHandle initial_next_handle = next_handle_; |
| // We expect most of these to be free. |
| // Safeguard against possibly having to wrap-around and reuse handles. |
| while (records_.count(next_handle_)) { |
| if (next_handle_ == kLastHandle) { |
| bt_log(WARN, "sdp", "service handle wrapped to start"); |
| next_handle_ = kFirstUnreservedHandle; |
| } else { |
| next_handle_++; |
| } |
| if (next_handle_ == initial_next_handle) { |
| return 0; |
| } |
| } |
| return next_handle_++; |
| } |
| |
| ServiceSearchResponse Server::SearchServices( |
| const std::unordered_set<UUID>& pattern) const { |
| ServiceSearchResponse resp; |
| std::vector<ServiceHandle> matched; |
| for (const auto& it : records_) { |
| if (it.second.FindUUID(pattern)) { |
| matched.push_back(it.first); |
| } |
| } |
| bt_log(SPEW, "sdp", "ServiceSearch matched %d records", matched.size()); |
| resp.set_service_record_handle_list(matched); |
| return resp; |
| } |
| |
| ServiceAttributeResponse Server::GetServiceAttributes( |
| ServiceHandle handle, const std::list<AttributeRange>& ranges) const { |
| ServiceAttributeResponse resp; |
| const auto& record = records_.at(handle); |
| for (const auto& range : ranges) { |
| auto attrs = record.GetAttributesInRange(range.start, range.end); |
| for (const auto& attr : attrs) { |
| resp.set_attribute(attr, record.GetAttribute(attr).Clone()); |
| } |
| } |
| bt_log(SPEW, "sdp", "ServiceAttribute %d attributes", |
| resp.attributes().size()); |
| return resp; |
| } |
| |
| ServiceSearchAttributeResponse Server::SearchAllServiceAttributes( |
| const std::unordered_set<UUID>& search_pattern, |
| const std::list<AttributeRange>& attribute_ranges) const { |
| ServiceSearchAttributeResponse resp; |
| for (const auto& it : records_) { |
| const auto& rec = it.second; |
| if (rec.FindUUID(search_pattern)) { |
| for (const auto& range : attribute_ranges) { |
| auto attrs = rec.GetAttributesInRange(range.start, range.end); |
| for (const auto& attr : attrs) { |
| resp.SetAttribute(it.first, attr, rec.GetAttribute(attr).Clone()); |
| } |
| } |
| } |
| } |
| |
| bt_log(SPEW, "sdp", "ServiceSearchAttribute %d records", |
| resp.num_attribute_lists()); |
| return resp; |
| } |
| |
| void Server::OnChannelClosed(const hci::ConnectionHandle& handle) { |
| channels_.erase(handle); |
| } |
| |
| void Server::OnRxBFrame(const hci::ConnectionHandle& handle, |
| const l2cap::SDU& sdu) { |
| uint16_t length = sdu.length(); |
| if (length < sizeof(Header)) { |
| bt_log(TRACE, "sdp", "PDU too short; dropping"); |
| return; |
| } |
| |
| auto it = channels_.find(handle); |
| if (it == channels_.end()) { |
| bt_log(TRACE, "sdp", "can't find peer to respond to; dropping"); |
| return; |
| } |
| l2cap::SDU::Reader reader(&sdu); |
| |
| reader.ReadNext(length, [this, length, chan = it->second.share()]( |
| const common::ByteBuffer& pdu) { |
| ZX_ASSERT(pdu.size() == length); |
| common::PacketView<Header> packet(&pdu); |
| TransactionId tid = betoh16(packet.header().tid); |
| uint16_t param_length = betoh16(packet.header().param_length); |
| |
| if (param_length != (pdu.size() - sizeof(Header))) { |
| bt_log(SPEW, "sdp", "request isn't the correct size (%d != %d)", |
| param_length, pdu.size() - sizeof(Header)); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidSize); |
| return; |
| } |
| |
| packet.Resize(param_length); |
| |
| switch (packet.header().pdu_id) { |
| case kServiceSearchRequest: { |
| ServiceSearchRequest request(packet.payload_data()); |
| if (!request.valid()) { |
| bt_log(TRACE, "sdp", "ServiceSearchRequest not valid"); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRequestSyntax); |
| return; |
| } |
| auto resp = SearchServices(request.service_search_pattern()); |
| chan->Send( |
| resp.GetPDU(request.max_service_record_count(), tid, BufferView())); |
| return; |
| } |
| case kServiceAttributeRequest: { |
| ServiceAttributeRequest request(packet.payload_data()); |
| if (!request.valid()) { |
| bt_log(SPEW, "sdp", "ServiceAttributeRequest not valid"); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRequestSyntax); |
| return; |
| } |
| auto handle = request.service_record_handle(); |
| if (records_.find(handle) == records_.end()) { |
| bt_log(SPEW, "sdp", "ServiceAttributeRequest can't find handle %#.8x", |
| handle); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRecordHandle); |
| return; |
| } |
| auto resp = GetServiceAttributes(handle, request.attribute_ranges()); |
| |
| chan->Send(resp.GetPDU(request.max_attribute_byte_count(), tid, |
| request.ContinuationState())); |
| return; |
| } |
| case kServiceSearchAttributeRequest: { |
| ServiceSearchAttributeRequest request(packet.payload_data()); |
| if (!request.valid()) { |
| bt_log(SPEW, "sdp", "ServiceSearchAttributeRequest not valid"); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRequestSyntax); |
| return; |
| } |
| auto resp = SearchAllServiceAttributes(request.service_search_pattern(), |
| request.attribute_ranges()); |
| chan->Send(resp.GetPDU(request.max_attribute_byte_count(), tid, |
| request.ContinuationState())); |
| return; |
| } |
| case kErrorResponse: { |
| bt_log(SPEW, "sdp", "ErrorResponse isn't allowed as a request"); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRequestSyntax); |
| return; |
| } |
| default: { |
| bt_log(SPEW, "sdp", "unhandled request, returning InvalidRequest"); |
| SendErrorResponse(chan, tid, ErrorCode::kInvalidRequestSyntax); |
| return; |
| } |
| } |
| }); |
| } |
| |
| } // namespace sdp |
| } // namespace btlib |