| // Copyright 2017 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 "local_service_manager.h" |
| |
| #include <endian.h> |
| #include <zircon/assert.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gatt/gatt_defs.h" |
| #include "src/lib/fxl/memory/weak_ptr.h" |
| |
| namespace bt::gatt { |
| namespace { |
| |
| // Adds characteristic definition attributes to |grouping| for |chrc|. Returns |
| // the characteristic value handle. |
| att::Handle InsertCharacteristicAttributes(att::AttributeGrouping* grouping, |
| const Characteristic& chrc, |
| att::Attribute::ReadHandler read_handler, |
| att::Attribute::WriteHandler write_handler) { |
| ZX_DEBUG_ASSERT(grouping); |
| ZX_DEBUG_ASSERT(!grouping->complete()); |
| ZX_DEBUG_ASSERT(read_handler); |
| ZX_DEBUG_ASSERT(write_handler); |
| |
| // Characteristic Declaration (Vol 3, Part G, 3.3.1). |
| auto* decl_attr = |
| grouping->AddAttribute(types::kCharacteristicDeclaration, |
| att::AccessRequirements(false, false, false), // read (no security) |
| att::AccessRequirements()); // write (not allowed) |
| ZX_DEBUG_ASSERT(decl_attr); |
| |
| // Characteristic Value Declaration (Vol 3, Part G, 3.3.2) |
| auto* value_attr = |
| grouping->AddAttribute(chrc.type(), chrc.read_permissions(), chrc.write_permissions()); |
| ZX_DEBUG_ASSERT(value_attr); |
| |
| value_attr->set_read_handler(std::move(read_handler)); |
| value_attr->set_write_handler(std::move(write_handler)); |
| |
| size_t uuid_size = chrc.type().CompactSize(false /* allow_32bit */); |
| ZX_DEBUG_ASSERT(uuid_size == 2 || uuid_size == 16); |
| |
| // The characteristic declaration value contains: |
| // 1 octet: properties |
| // 2 octets: value handle |
| // 2 or 16 octets: UUID |
| DynamicByteBuffer decl_value(3 + uuid_size); |
| decl_value[0] = chrc.properties(); |
| decl_value[1] = static_cast<uint8_t>(value_attr->handle()); |
| decl_value[2] = static_cast<uint8_t>(value_attr->handle() >> 8); |
| |
| auto uuid_view = decl_value.mutable_view(3); |
| chrc.type().ToBytes(&uuid_view, false /* allow_32bit */); |
| decl_attr->SetValue(decl_value); |
| |
| return value_attr->handle(); |
| } |
| |
| // Adds a characteristic descriptor declaration to |grouping| for |desc|. |
| void InsertDescriptorAttribute(att::AttributeGrouping* grouping, const UUID& type, |
| const att::AccessRequirements& read_reqs, |
| const att::AccessRequirements& write_reqs, |
| att::Attribute::ReadHandler read_handler, |
| att::Attribute::WriteHandler write_handler) { |
| ZX_DEBUG_ASSERT(grouping); |
| ZX_DEBUG_ASSERT(!grouping->complete()); |
| ZX_DEBUG_ASSERT(read_handler); |
| ZX_DEBUG_ASSERT(write_handler); |
| |
| // There is no special declaration attribute type for descriptors. |
| auto* attr = grouping->AddAttribute(type, read_reqs, write_reqs); |
| ZX_DEBUG_ASSERT(attr); |
| |
| attr->set_read_handler(std::move(read_handler)); |
| attr->set_write_handler(std::move(write_handler)); |
| } |
| |
| // Returns false if the given service hierarchy contains repeating identifiers. |
| // Returns the number of attributes that will be in the service attribute group |
| // (exluding the service declaration) in |out_attrs|. |
| bool ValidateService(const Service& service, size_t* out_attr_count) { |
| ZX_DEBUG_ASSERT(out_attr_count); |
| |
| size_t attr_count = 0u; |
| std::unordered_set<IdType> ids; |
| for (const auto& chrc_ptr : service.characteristics()) { |
| if (ids.count(chrc_ptr->id()) != 0u) { |
| bt_log(TRACE, "gatt", "server: repeated ID: %lu", chrc_ptr->id()); |
| return false; |
| } |
| |
| ids.insert(chrc_ptr->id()); |
| |
| // +1: Characteristic Declaration (Vol 3, Part G, 3.3.1) |
| // +1: Characteristic Value Declaration (Vol 3, Part G, 3.3.2) |
| attr_count += 2; |
| |
| // Increment the count for the CCC descriptor if the characteristic supports |
| // notifications or indications. |
| if ((chrc_ptr->properties() & Property::kNotify) || |
| (chrc_ptr->properties() & Property::kIndicate)) { |
| attr_count++; |
| } |
| |
| for (const auto& desc_ptr : chrc_ptr->descriptors()) { |
| if (ids.count(desc_ptr->id()) != 0u) { |
| bt_log(TRACE, "gatt", "server: repeated ID: %lu", desc_ptr->id()); |
| return false; |
| } |
| |
| // Reject descriptors with types that are internally managed by us. |
| if (desc_ptr->type() == types::kClientCharacteristicConfig || |
| desc_ptr->type() == types::kCharacteristicExtProperties || |
| desc_ptr->type() == types::kServerCharacteristicConfig) { |
| bt_log(TRACE, "gatt", "server: disallowed descriptor type: %s", |
| desc_ptr->type().ToString().c_str()); |
| return false; |
| } |
| |
| ids.insert(desc_ptr->id()); |
| |
| // +1: Characteristic Descriptor Declaration (Vol 3, Part G, 3.3.3) |
| attr_count++; |
| } |
| if (chrc_ptr->extended_properties()) { |
| attr_count++; |
| } |
| } |
| |
| *out_attr_count = attr_count; |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| class LocalServiceManager::ServiceData final { |
| public: |
| ServiceData(IdType id, att::AttributeGrouping* grouping, Service* service, |
| ReadHandler&& read_handler, WriteHandler&& write_handler, |
| ClientConfigCallback&& ccc_callback) |
| : id_(id), |
| read_handler_(std::forward<ReadHandler>(read_handler)), |
| write_handler_(std::forward<WriteHandler>(write_handler)), |
| ccc_callback_(std::forward<ClientConfigCallback>(ccc_callback)), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(read_handler_); |
| ZX_DEBUG_ASSERT(write_handler_); |
| ZX_DEBUG_ASSERT(ccc_callback_); |
| ZX_DEBUG_ASSERT(grouping); |
| |
| start_handle_ = grouping->start_handle(); |
| end_handle_ = grouping->end_handle(); |
| |
| // Sort characteristics by UUID size (see Vol 3, Part G, 3.3.1). |
| auto chrcs = service->ReleaseCharacteristics(); |
| std::sort(chrcs.begin(), chrcs.end(), [](const auto& chrc_ptr1, const auto& chrc_ptr2) { |
| return chrc_ptr1->type().CompactSize(false /* allow_32bit */) < |
| chrc_ptr2->type().CompactSize(false /* allow_32bit */); |
| }); |
| for (auto& chrc : chrcs) { |
| AddCharacteristic(grouping, std::move(chrc)); |
| } |
| } |
| |
| inline IdType id() const { return id_; } |
| inline att::Handle start_handle() const { return start_handle_; } |
| inline att::Handle end_handle() const { return end_handle_; } |
| |
| bool GetCharacteristicConfig(IdType chrc_id, PeerId peer_id, |
| ClientCharacteristicConfig* out_config) { |
| ZX_DEBUG_ASSERT(out_config); |
| |
| auto iter = chrc_configs_.find(chrc_id); |
| if (iter == chrc_configs_.end()) |
| return false; |
| |
| uint16_t value = iter->second.Get(peer_id); |
| out_config->handle = iter->second.handle(); |
| out_config->notify = value & kCCCNotificationBit; |
| out_config->indicate = value & kCCCIndicationBit; |
| |
| return true; |
| } |
| |
| // Clean up our knoweledge of the diconnecting peer. |
| void DisconnectClient(PeerId peer_id) { |
| for (auto& id_config_pair : chrc_configs_) { |
| id_config_pair.second.Erase(peer_id); |
| } |
| } |
| |
| private: |
| class CharacteristicConfig { |
| public: |
| explicit CharacteristicConfig(att::Handle handle) : handle_(handle) {} |
| CharacteristicConfig(CharacteristicConfig&&) = default; |
| CharacteristicConfig& operator=(CharacteristicConfig&&) = default; |
| |
| // The characteristic handle. |
| att::Handle handle() const { return handle_; } |
| |
| uint16_t Get(PeerId peer_id) { |
| auto iter = client_states_.find(peer_id); |
| |
| // If a configuration doesn't exist for |peer_id| then return the default |
| // value. |
| if (iter == client_states_.end()) |
| return 0; |
| |
| return iter->second; |
| } |
| |
| void Set(PeerId peer_id, uint16_t value) { client_states_[peer_id] = value; } |
| |
| void Erase(PeerId peer_id) { client_states_.erase(peer_id); } |
| |
| private: |
| att::Handle handle_; |
| std::unordered_map<PeerId, uint16_t> client_states_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CharacteristicConfig); |
| }; |
| |
| // Called when a read request is performed on a CCC descriptor belonging to |
| // the characteristic identified by |chrc_id|. |
| void OnReadCCC(IdType chrc_id, PeerId peer_id, att::Handle handle, uint16_t offset, |
| const ReadResponder& result_cb) { |
| uint16_t value = 0; |
| auto iter = chrc_configs_.find(chrc_id); |
| if (iter != chrc_configs_.end()) { |
| value = iter->second.Get(peer_id); |
| } |
| |
| value = htole16(value); |
| result_cb(att::ErrorCode::kNoError, |
| BufferView(reinterpret_cast<const uint8_t*>(&value), sizeof(value))); |
| } |
| |
| // Called when a write request is performed on a CCC descriptor belonging to |
| // the characteristic identified by |chrc_id|. |
| void OnWriteCCC(IdType chrc_id, uint8_t chrc_props, PeerId peer_id, att::Handle handle, |
| uint16_t offset, const ByteBuffer& value, const WriteResponder& result_cb) { |
| if (offset != 0u) { |
| result_cb(att::ErrorCode::kInvalidOffset); |
| return; |
| } |
| |
| if (value.size() != sizeof(uint16_t)) { |
| result_cb(att::ErrorCode::kInvalidAttributeValueLength); |
| return; |
| } |
| |
| uint16_t ccc_value = le16toh(value.As<uint16_t>()); |
| if (ccc_value > (kCCCNotificationBit | kCCCIndicationBit)) { |
| result_cb(att::ErrorCode::kInvalidPDU); |
| return; |
| } |
| |
| bool notify = ccc_value & kCCCNotificationBit; |
| bool indicate = ccc_value & kCCCIndicationBit; |
| |
| if ((notify && !(chrc_props & Property::kNotify)) || |
| (indicate && !(chrc_props & Property::kIndicate))) { |
| result_cb(att::ErrorCode::kWriteNotPermitted); |
| return; |
| } |
| |
| auto iter = chrc_configs_.find(chrc_id); |
| if (iter == chrc_configs_.end()) { |
| auto result_pair = chrc_configs_.emplace(chrc_id, CharacteristicConfig(handle)); |
| iter = result_pair.first; |
| } |
| |
| // Send a reply back. |
| result_cb(att::ErrorCode::kNoError); |
| |
| uint16_t current_value = iter->second.Get(peer_id); |
| iter->second.Set(peer_id, ccc_value); |
| |
| if (current_value != ccc_value) { |
| ccc_callback_(id_, chrc_id, peer_id, notify, indicate); |
| } |
| } |
| |
| void AddCharacteristic(att::AttributeGrouping* grouping, CharacteristicPtr chrc) { |
| // Set up the characteristic callbacks. |
| // TODO(armansito): Consider tracking a transaction timeout here (fxbug.dev/636). |
| IdType id = chrc->id(); |
| uint8_t props = chrc->properties(); |
| uint16_t ext_props = chrc->extended_properties(); |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| |
| auto read_handler = [self, id, props](const auto& peer_id, att::Handle handle, uint16_t offset, |
| auto result_cb) { |
| if (!self) { |
| result_cb(att::ErrorCode::kUnlikelyError, BufferView()); |
| return; |
| } |
| |
| // ATT permissions checks passed if we got here; also check the |
| // characteristic property. |
| if (!(props & Property::kRead)) { |
| // TODO(armansito): Return kRequestNotSupported? |
| result_cb(att::ErrorCode::kReadNotPermitted, BufferView()); |
| return; |
| } |
| |
| self->read_handler_(self->id_, id, offset, std::move(result_cb)); |
| }; |
| |
| auto write_handler = [self, id, props](const auto& peer_id, att::Handle handle, uint16_t offset, |
| const auto& value, auto result_cb) { |
| if (!self) { |
| if (result_cb) |
| result_cb(att::ErrorCode::kUnlikelyError); |
| return; |
| } |
| |
| // If |result_cb| was provided, then this is a write request and the |
| // characteristic must support the "write" procedure. |
| if (result_cb && !(props & Property::kWrite)) { |
| // TODO(armansito): Return kRequestNotSupported? |
| result_cb(att::ErrorCode::kWriteNotPermitted); |
| return; |
| } |
| |
| if (!result_cb && !(props & Property::kWriteWithoutResponse)) |
| return; |
| |
| self->write_handler_(self->id_, id, offset, value, std::move(result_cb)); |
| }; |
| |
| att::Handle chrc_handle = InsertCharacteristicAttributes( |
| grouping, *chrc, std::move(read_handler), std::move(write_handler)); |
| |
| if (props & Property::kNotify || props & Property::kIndicate) { |
| AddCCCDescriptor(grouping, *chrc, chrc_handle); |
| } |
| |
| if (ext_props) { |
| auto* decl_attr = grouping->AddAttribute( |
| types::kCharacteristicExtProperties, |
| att::AccessRequirements(false, false, false), // read (no security) |
| att::AccessRequirements()); // write (not allowed) |
| ZX_DEBUG_ASSERT(decl_attr); |
| decl_attr->SetValue(CreateStaticByteBuffer((uint8_t)(ext_props & 0x00FF), |
| (uint8_t)((ext_props & 0xFF00) >> 8))); |
| } |
| |
| // TODO(armansito): Inject a SCC descriptor if the characteristic has the |
| // broadcast property and if we ever support configured broadcasts. |
| |
| // Sort descriptors by UUID size. This is not required by the specification |
| // but we do this to return as many descriptors as possible in a ATT Find |
| // Information response. |
| auto descs = chrc->ReleaseDescriptors(); |
| std::sort(descs.begin(), descs.end(), [](const auto& desc_ptr1, const auto& desc_ptr2) { |
| return desc_ptr1->type().CompactSize(false /* allow_32bit */) < |
| desc_ptr2->type().CompactSize(false /* allow_32bit */); |
| }); |
| for (auto& desc : descs) { |
| AddDescriptor(grouping, std::move(desc)); |
| } |
| } |
| |
| void AddDescriptor(att::AttributeGrouping* grouping, DescriptorPtr desc) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto read_handler = [self, id = desc->id()](const auto& peer_id, att::Handle handle, |
| uint16_t offset, auto result_cb) { |
| if (!self) { |
| result_cb(att::ErrorCode::kUnlikelyError, BufferView()); |
| return; |
| } |
| |
| self->read_handler_(self->id_, id, offset, std::move(result_cb)); |
| }; |
| |
| auto write_handler = [self, id = desc->id()](const auto& peer_id, att::Handle handle, |
| uint16_t offset, const auto& value, |
| auto result_cb) { |
| // Descriptors cannot be written using the "write without response" |
| // procedure. |
| if (!result_cb) |
| return; |
| |
| if (!self) { |
| result_cb(att::ErrorCode::kUnlikelyError); |
| return; |
| } |
| |
| self->write_handler_(self->id_, id, offset, value, std::move(result_cb)); |
| }; |
| |
| InsertDescriptorAttribute(grouping, desc->type(), desc->read_permissions(), |
| desc->write_permissions(), std::move(read_handler), |
| std::move(write_handler)); |
| } |
| |
| void AddCCCDescriptor(att::AttributeGrouping* grouping, const Characteristic& chrc, |
| att::Handle chrc_handle) { |
| ZX_DEBUG_ASSERT(chrc.update_permissions().allowed()); |
| |
| // Readable with no authentication or authorization (Vol 3, Part G, |
| // 3.3.3.3). We let the service determine the encryption permission. |
| att::AccessRequirements read_reqs(chrc.update_permissions().encryption_required(), false, |
| false); |
| |
| IdType id = chrc.id(); |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| |
| auto read_handler = [self, id, chrc_handle](const auto& peer_id, att::Handle handle, |
| uint16_t offset, auto result_cb) { |
| if (!self) { |
| result_cb(att::ErrorCode::kUnlikelyError, BufferView()); |
| return; |
| } |
| |
| self->OnReadCCC(id, peer_id, chrc_handle, offset, std::move(result_cb)); |
| }; |
| |
| auto write_handler = [self, id, chrc_handle, props = chrc.properties()]( |
| const auto& peer_id, att::Handle handle, uint16_t offset, |
| const auto& value, auto result_cb) { |
| if (!self) { |
| result_cb(att::ErrorCode::kUnlikelyError); |
| return; |
| } |
| |
| self->OnWriteCCC(id, props, peer_id, chrc_handle, offset, value, std::move(result_cb)); |
| }; |
| |
| // The write permission is determined by the service. |
| InsertDescriptorAttribute(grouping, types::kClientCharacteristicConfig, read_reqs, |
| chrc.update_permissions(), std::move(read_handler), |
| std::move(write_handler)); |
| } |
| |
| IdType id_; |
| att::Handle start_handle_; |
| att::Handle end_handle_; |
| ReadHandler read_handler_; |
| WriteHandler write_handler_; |
| ClientConfigCallback ccc_callback_; |
| |
| // Characteristic configuration states. |
| // TODO(armansito): Add a mechanism to persist client configuration for bonded |
| // devices. |
| std::unordered_map<IdType, CharacteristicConfig> chrc_configs_; |
| |
| fxl::WeakPtrFactory<ServiceData> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ServiceData); |
| }; |
| |
| LocalServiceManager::LocalServiceManager() : db_(att::Database::Create()), next_service_id_(1ull) { |
| ZX_DEBUG_ASSERT(db_); |
| } |
| |
| LocalServiceManager::~LocalServiceManager() {} |
| |
| IdType LocalServiceManager::RegisterService(ServicePtr service, ReadHandler read_handler, |
| WriteHandler write_handler, |
| ClientConfigCallback ccc_callback) { |
| ZX_DEBUG_ASSERT(service); |
| ZX_DEBUG_ASSERT(read_handler); |
| ZX_DEBUG_ASSERT(write_handler); |
| ZX_DEBUG_ASSERT(ccc_callback); |
| |
| if (services_.find(next_service_id_) != services_.end()) { |
| bt_log(TRACE, "gatt", "server: Ran out of service IDs"); |
| return kInvalidId; |
| } |
| |
| size_t attr_count; |
| if (!ValidateService(*service, &attr_count)) |
| return kInvalidId; |
| |
| // GATT does not support 32-bit UUIDs. |
| const BufferView service_decl_value = service->type().CompactView(false /* allow_32bit */); |
| |
| // TODO(armansito): Cluster services with 16-bit and 128-bit together inside |
| // |db_| (Vol 3, Part G, 3.1). |
| |
| att::AttributeGrouping* grouping = |
| db_->NewGrouping(service->primary() ? types::kPrimaryService : types::kSecondaryService, |
| attr_count, service_decl_value); |
| if (!grouping) { |
| bt_log(DEBUG, "gatt", "server: Failed to allocate attribute grouping for service"); |
| return kInvalidId; |
| } |
| |
| // Creating a ServiceData will populate the attribute grouping. |
| auto service_data = std::make_unique<ServiceData>( |
| next_service_id_, grouping, service.get(), std::move(read_handler), std::move(write_handler), |
| std::move(ccc_callback)); |
| ZX_DEBUG_ASSERT(grouping->complete()); |
| grouping->set_active(true); |
| |
| // TODO(armansito): Handle potential 64-bit unsigned overflow? |
| IdType id = next_service_id_++; |
| |
| services_[id] = std::move(service_data); |
| if (service_changed_callback_) { |
| service_changed_callback_(id, grouping->start_handle(), grouping->end_handle()); |
| } |
| |
| return id; |
| } |
| |
| bool LocalServiceManager::UnregisterService(IdType service_id) { |
| auto iter = services_.find(service_id); |
| if (iter == services_.end()) |
| return false; |
| |
| const att::Handle start_handle = iter->second->start_handle(); |
| const att::Handle end_handle = iter->second->end_handle(); |
| db_->RemoveGrouping(start_handle); |
| services_.erase(iter); |
| |
| if (service_changed_callback_) { |
| service_changed_callback_(service_id, start_handle, end_handle); |
| } |
| return true; |
| } |
| |
| bool LocalServiceManager::GetCharacteristicConfig(IdType service_id, IdType chrc_id, PeerId peer_id, |
| ClientCharacteristicConfig* out_config) { |
| ZX_DEBUG_ASSERT(out_config); |
| |
| auto iter = services_.find(service_id); |
| if (iter == services_.end()) |
| return false; |
| |
| return iter->second->GetCharacteristicConfig(chrc_id, peer_id, out_config); |
| } |
| |
| void LocalServiceManager::DisconnectClient(PeerId peer_id) { |
| for (auto& id_service_pair : services_) { |
| id_service_pair.second->DisconnectClient(peer_id); |
| } |
| } |
| |
| } // namespace bt::gatt |