| // 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 "gatt_server_server.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/gap/low_energy_connection_manager.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gatt/connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gatt/gatt_defs.h" |
| #include "src/connectivity/bluetooth/core/bt-host/gatt/server.h" |
| |
| using fuchsia::bluetooth::ErrorCode; |
| using fuchsia::bluetooth::Status; |
| using GattErrorCode = fuchsia::bluetooth::gatt::ErrorCode; |
| |
| using fuchsia::bluetooth::gatt::Characteristic; |
| using fuchsia::bluetooth::gatt::Descriptor; |
| using fuchsia::bluetooth::gatt::LocalService; |
| using fuchsia::bluetooth::gatt::LocalServiceDelegate; |
| using fuchsia::bluetooth::gatt::LocalServiceDelegatePtr; |
| using fuchsia::bluetooth::gatt::SecurityRequirementsPtr; |
| using fuchsia::bluetooth::gatt::ServiceInfo; |
| |
| namespace bthost { |
| namespace { |
| |
| bt::att::ErrorCode GattErrorCodeFromFidl(GattErrorCode error_code, |
| bool is_read) { |
| switch (error_code) { |
| case GattErrorCode::NO_ERROR: |
| return bt::att::ErrorCode::kNoError; |
| case GattErrorCode::INVALID_OFFSET: |
| return bt::att::ErrorCode::kInvalidOffset; |
| case GattErrorCode::INVALID_VALUE_LENGTH: |
| return bt::att::ErrorCode::kInvalidAttributeValueLength; |
| case GattErrorCode::NOT_PERMITTED: |
| if (is_read) |
| return bt::att::ErrorCode::kReadNotPermitted; |
| return bt::att::ErrorCode::kWriteNotPermitted; |
| default: |
| break; |
| } |
| return bt::att::ErrorCode::kUnlikelyError; |
| } |
| |
| bt::att::AccessRequirements ParseSecurityRequirements( |
| const SecurityRequirementsPtr& reqs) { |
| if (!reqs) { |
| return bt::att::AccessRequirements(); |
| } |
| return bt::att::AccessRequirements(reqs->encryption_required, |
| reqs->authentication_required, |
| reqs->authorization_required); |
| } |
| |
| // Carries a either a successful Result or an error message that can be sent as |
| // a FIDL response. |
| template <typename Result, typename Error = std::string, |
| typename = std::enable_if_t<!std::is_same<Result, Error>::value>> |
| struct MaybeResult final { |
| explicit MaybeResult(Result&& result) |
| : result(std::forward<Result>(result)) {} |
| |
| explicit MaybeResult(Error&& error) : error(std::forward<Error>(error)) {} |
| |
| bool is_error() const { return static_cast<bool>(!result); } |
| |
| Result result; |
| Error error; |
| }; |
| |
| using DescriptorResult = MaybeResult<bt::gatt::DescriptorPtr>; |
| DescriptorResult NewDescriptor(const Descriptor& fidl_desc) { |
| auto read_reqs = ParseSecurityRequirements(fidl_desc.permissions->read); |
| auto write_reqs = ParseSecurityRequirements(fidl_desc.permissions->write); |
| |
| bt::UUID type; |
| if (!bt::StringToUuid(fidl_desc.type, &type)) { |
| return DescriptorResult("Invalid descriptor UUID"); |
| } |
| |
| return DescriptorResult(std::make_unique<bt::gatt::Descriptor>( |
| fidl_desc.id, type, read_reqs, write_reqs)); |
| } |
| |
| using CharacteristicResult = MaybeResult<bt::gatt::CharacteristicPtr>; |
| CharacteristicResult NewCharacteristic(const Characteristic& fidl_chrc) { |
| uint8_t props = fidl_chrc.properties & 0xFF; |
| uint16_t ext_props = (fidl_chrc.properties & 0xFF00) >> 8; |
| |
| if (!fidl_chrc.permissions) { |
| return CharacteristicResult("Characteristic permissions missing"); |
| } |
| |
| bool supports_update = (props & bt::gatt::Property::kNotify) || |
| (props & bt::gatt::Property::kIndicate); |
| if (supports_update != static_cast<bool>(fidl_chrc.permissions->update)) { |
| return CharacteristicResult( |
| supports_update ? "Characteristic update permission required" |
| : "Characteristic update permission must be null"); |
| } |
| |
| auto read_reqs = ParseSecurityRequirements(fidl_chrc.permissions->read); |
| auto write_reqs = ParseSecurityRequirements(fidl_chrc.permissions->write); |
| auto update_reqs = ParseSecurityRequirements(fidl_chrc.permissions->update); |
| |
| bt::UUID type; |
| if (!bt::StringToUuid(fidl_chrc.type, &type)) { |
| return CharacteristicResult("Invalid characteristic UUID"); |
| } |
| |
| auto chrc = std::make_unique<bt::gatt::Characteristic>( |
| fidl_chrc.id, type, props, ext_props, read_reqs, write_reqs, update_reqs); |
| if (fidl_chrc.descriptors && !fidl_chrc.descriptors->empty()) { |
| for (const auto& fidl_desc : *fidl_chrc.descriptors) { |
| auto desc_result = NewDescriptor(fidl_desc); |
| if (desc_result.is_error()) { |
| return CharacteristicResult(std::move(desc_result.error)); |
| } |
| |
| chrc->AddDescriptor(std::move(desc_result.result)); |
| } |
| } |
| |
| return CharacteristicResult(std::move(chrc)); |
| } |
| |
| } // namespace |
| |
| // Implements the gatt::LocalService FIDL interface. Instances of this class are |
| // only created by a GattServerServer. |
| class GattServerServer::LocalServiceImpl |
| : public GattServerBase<fuchsia::bluetooth::gatt::LocalService> { |
| public: |
| LocalServiceImpl(GattServerServer* owner, uint64_t id, |
| LocalServiceDelegatePtr delegate, |
| ::fidl::InterfaceRequest<LocalService> request) |
| : GattServerBase(owner->gatt(), this, std::move(request)), |
| owner_(owner), |
| id_(id), |
| delegate_(std::move(delegate)) { |
| ZX_DEBUG_ASSERT(owner_); |
| ZX_DEBUG_ASSERT(delegate_); |
| } |
| |
| // The destructor removes the GATT service |
| ~LocalServiceImpl() override { |
| CleanUp(); |
| |
| // Do not notify the owner in this case. If we got here it means that |
| // |owner_| deleted us. |
| } |
| |
| // Returns the current delegate. Returns nullptr if the delegate was |
| // disconnected (e.g. due to a call to RemoveService()). |
| LocalServiceDelegate* delegate() { return delegate_.get(); } |
| |
| private: |
| // fuchsia::bluetooth::gatt::Service overrides: |
| void RemoveService() override { |
| CleanUp(); |
| owner_->RemoveService(id_); |
| } |
| |
| void NotifyValue(uint64_t characteristic_id, ::std::string peer_id, |
| ::std::vector<uint8_t> value, bool confirm) override { |
| auto id = fidl_helpers::PeerIdFromString(std::move(peer_id)); |
| if (id) { |
| gatt()->SendNotification(id_, characteristic_id, *id, std::move(value), |
| confirm); |
| } |
| } |
| |
| // Unregisters the underlying service if it is still active. |
| void CleanUp() { |
| delegate_ = nullptr; // Closes the delegate handle. |
| gatt()->UnregisterService(id_); |
| } |
| |
| // |owner_| owns this instance and is expected to outlive it. |
| GattServerServer* owner_; // weak |
| uint64_t id_; |
| |
| // The delegate connection for the corresponding service instance. This gets |
| // cleared when the service is unregistered (via RemoveService() or |
| // destruction). |
| LocalServiceDelegatePtr delegate_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LocalServiceImpl); |
| }; |
| |
| GattServerServer::GattServerServer( |
| fbl::RefPtr<bt::gatt::GATT> gatt, |
| fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Server> request) |
| : GattServerBase(gatt, this, std::move(request)), weak_ptr_factory_(this) {} |
| |
| GattServerServer::~GattServerServer() { |
| // This will remove all of our services from their adapter. |
| services_.clear(); |
| } |
| |
| void GattServerServer::RemoveService(uint64_t id) { |
| if (services_.erase(id)) { |
| bt_log(TRACE, "bt-host", "service removed (id: %lu)", id); |
| } else { |
| bt_log(TRACE, "bt-host", "service id not found: %lu", id); |
| } |
| } |
| |
| void GattServerServer::PublishService( |
| ServiceInfo service_info, |
| fidl::InterfaceHandle<LocalServiceDelegate> delegate, |
| fidl::InterfaceRequest<LocalService> service_iface, |
| PublishServiceCallback callback) { |
| if (!delegate) { |
| auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, |
| "A delegate is required"); |
| callback(std::move(error)); |
| return; |
| } |
| |
| if (!service_iface) { |
| auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, |
| "Service interface is required"); |
| callback(std::move(error)); |
| return; |
| } |
| |
| bt::UUID service_type; |
| if (!bt::StringToUuid(service_info.type, &service_type)) { |
| auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, |
| "Invalid service UUID"); |
| callback(std::move(error)); |
| return; |
| } |
| |
| // Process the FIDL service tree. |
| auto service = |
| std::make_unique<bt::gatt::Service>(service_info.primary, service_type); |
| if (service_info.characteristics) { |
| for (const auto& fidl_chrc : *service_info.characteristics) { |
| auto chrc_result = NewCharacteristic(fidl_chrc); |
| if (chrc_result.is_error()) { |
| auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS, |
| chrc_result.error); |
| callback(std::move(error)); |
| return; |
| } |
| |
| service->AddCharacteristic(std::move(chrc_result.result)); |
| } |
| } |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| |
| // Set up event handlers. |
| auto read_handler = [self](auto svc_id, auto id, auto offset, |
| auto responder) mutable { |
| if (self) { |
| self->OnReadRequest(svc_id, id, offset, std::move(responder)); |
| } else { |
| responder(bt::att::ErrorCode::kUnlikelyError, bt::BufferView()); |
| } |
| }; |
| auto write_handler = [self](auto svc_id, auto id, auto offset, |
| const auto& value, auto responder) mutable { |
| if (self) { |
| self->OnWriteRequest(svc_id, id, offset, value, std::move(responder)); |
| } else { |
| responder(bt::att::ErrorCode::kUnlikelyError); |
| } |
| }; |
| auto ccc_callback = [self](auto svc_id, auto id, bt::gatt::PeerId peer_id, |
| bool notify, bool indicate) { |
| if (self) |
| self->OnCharacteristicConfig(svc_id, id, peer_id, notify, indicate); |
| }; |
| |
| auto id_cb = [self, delegate = std::move(delegate), |
| service_iface = std::move(service_iface), |
| callback = std::move(callback)](bt::gatt::IdType id) mutable { |
| if (!self) |
| return; |
| |
| if (!id) { |
| // TODO(armansito): Report a more detailed string if registration |
| // fails due to duplicate ids. |
| auto error = fidl_helpers::NewFidlError(ErrorCode::FAILED, |
| "Failed to publish service"); |
| callback(std::move(error)); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(self->services_.find(id) == self->services_.end()); |
| |
| // This will be called if either the delegate or the service connection |
| // closes. |
| auto connection_error_cb = [self, id](zx_status_t status) { |
| bt_log(TRACE, "bt-host", "removing GATT service (id: %lu)", id); |
| if (self) |
| self->RemoveService(id); |
| }; |
| |
| auto delegate_ptr = delegate.Bind(); |
| delegate_ptr.set_error_handler(connection_error_cb); |
| |
| auto service_server = std::make_unique<LocalServiceImpl>( |
| self.get(), id, std::move(delegate_ptr), std::move(service_iface)); |
| service_server->set_error_handler(connection_error_cb); |
| self->services_[id] = std::move(service_server); |
| |
| callback(Status()); |
| }; |
| |
| gatt()->RegisterService(std::move(service), std::move(id_cb), |
| std::move(read_handler), std::move(write_handler), |
| std::move(ccc_callback)); |
| } |
| |
| void GattServerServer::OnReadRequest(bt::gatt::IdType service_id, |
| bt::gatt::IdType id, uint16_t offset, |
| bt::gatt::ReadResponder responder) { |
| auto iter = services_.find(service_id); |
| if (iter == services_.end()) { |
| responder(bt::att::ErrorCode::kUnlikelyError, bt::BufferView()); |
| return; |
| } |
| |
| auto cb = [responder = std::move(responder)](fidl::VectorPtr<uint8_t> value, |
| auto error_code) { |
| responder(GattErrorCodeFromFidl(error_code, true /* is_read */), |
| bt::BufferView(value->data(), value->size())); |
| }; |
| |
| auto* delegate = iter->second->delegate(); |
| ZX_DEBUG_ASSERT(delegate); |
| delegate->OnReadValue(id, offset, std::move(cb)); |
| } |
| |
| void GattServerServer::OnWriteRequest(bt::gatt::IdType service_id, |
| bt::gatt::IdType id, uint16_t offset, |
| const bt::ByteBuffer& value, |
| bt::gatt::WriteResponder responder) { |
| auto iter = services_.find(service_id); |
| if (iter == services_.end()) { |
| responder(bt::att::ErrorCode::kUnlikelyError); |
| return; |
| } |
| |
| auto fidl_value = fidl::To<fidl::VectorPtr<uint8_t>>(value); |
| auto* delegate = iter->second->delegate(); |
| ZX_DEBUG_ASSERT(delegate); |
| |
| if (!responder) { |
| delegate->OnWriteWithoutResponse(id, offset, std::move(fidl_value)); |
| return; |
| } |
| |
| auto cb = [responder = std::move(responder)](auto error_code) { |
| responder(GattErrorCodeFromFidl(error_code, false /* is_read */)); |
| }; |
| |
| delegate->OnWriteValue(id, offset, std::move(fidl_value), std::move(cb)); |
| } |
| |
| void GattServerServer::OnCharacteristicConfig(bt::gatt::IdType service_id, |
| bt::gatt::IdType chrc_id, |
| bt::gatt::PeerId peer_id, |
| bool notify, bool indicate) { |
| auto iter = services_.find(service_id); |
| if (iter != services_.end()) { |
| auto* delegate = iter->second->delegate(); |
| ZX_DEBUG_ASSERT(delegate); |
| delegate->OnCharacteristicConfiguration(chrc_id, peer_id.ToString(), notify, |
| indicate); |
| } |
| } |
| |
| } // namespace bthost |