| // 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_service_manager.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/att/error.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gatt/remote_service.h" |
| |
| namespace bt::gatt::internal { |
| |
| RemoteServiceManager::ServiceListRequest::ServiceListRequest( |
| ServiceListCallback callback, const std::vector<UUID>& uuids) |
| : callback_(std::move(callback)), uuids_(uuids) { |
| BT_DEBUG_ASSERT(callback_); |
| } |
| |
| void RemoteServiceManager::ServiceListRequest::Complete( |
| att::Result<> status, const ServiceMap& services) { |
| TRACE_DURATION("bluetooth", |
| "gatt::RemoteServiceManager::ServiceListRequest::Complete"); |
| |
| ServiceList result; |
| |
| if (status.is_error() || services.empty()) { |
| callback_(status, std::move(result)); |
| return; |
| } |
| |
| for (const auto& iter : services) { |
| auto& svc = iter.second; |
| auto pred = [&svc](const UUID& uuid) { return svc->uuid() == uuid; }; |
| if (uuids_.empty() || |
| std::find_if(uuids_.begin(), uuids_.end(), pred) != uuids_.end()) { |
| result.push_back(iter.second->GetWeakPtr()); |
| } |
| } |
| |
| callback_(status, std::move(result)); |
| } |
| |
| RemoteServiceManager::RemoteServiceManager(std::unique_ptr<Client> client) |
| : client_(std::move(client)), initialized_(false), weak_self_(this) { |
| BT_DEBUG_ASSERT(client_); |
| |
| client_->SetNotificationHandler( |
| fit::bind_member<&RemoteServiceManager::OnNotification>(this)); |
| } |
| |
| RemoteServiceManager::~RemoteServiceManager() { |
| client_->SetNotificationHandler({}); |
| services_.clear(); |
| |
| // Resolve all pending requests with an error. |
| att::Result<> status = ToResult(HostError::kFailed); |
| |
| auto pending = std::move(pending_list_services_requests_); |
| while (!pending.empty()) { |
| // This copies |services|. |
| pending.front().Complete(status, services_); |
| pending.pop(); |
| } |
| } |
| |
| void RemoteServiceManager::Initialize(att::ResultFunction<> cb, |
| fit::callback<void(uint16_t)> mtu_cb, |
| std::vector<UUID> services) { |
| auto self = weak_self_.GetWeakPtr(); |
| |
| auto init_cb = [self, |
| user_init_cb = std::move(cb)](att::Result<> status) mutable { |
| TRACE_DURATION("bluetooth", |
| "gatt::RemoteServiceManager::Initialize::init_cb"); |
| bt_log(DEBUG, "gatt", "RemoteServiceManager initialization complete"); |
| |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| self->initialized_ = true; |
| |
| if (status.is_ok() && self->svc_watcher_) { |
| // Notify all discovered services here. |
| TRACE_DURATION("bluetooth", "gatt::RemoteServiceManager::svc_watcher_"); |
| ServiceList added; |
| std::transform( |
| self->services_.begin(), |
| self->services_.end(), |
| std::back_inserter(added), |
| [](ServiceMap::value_type& svc) { return svc.second->GetWeakPtr(); }); |
| self->svc_watcher_(/*removed=*/{}, std::move(added), /*modified=*/{}); |
| } |
| |
| // Notify pending ListService() requests. |
| while (!self->pending_list_services_requests_.empty()) { |
| self->pending_list_services_requests_.front().Complete(status, |
| self->services_); |
| self->pending_list_services_requests_.pop(); |
| } |
| |
| user_init_cb(status); |
| }; |
| |
| client_->ExchangeMTU([self, |
| init_cb = std::move(init_cb), |
| mtu_cb = std::move(mtu_cb), |
| services = std::move(services)]( |
| att::Result<uint16_t> mtu_result) mutable { |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| // Support for the MTU exchange is optional, so if the peer indicated they |
| // don't support it, we continue with initialization. |
| if (mtu_result.is_ok() || |
| mtu_result.error_value().is(att::ErrorCode::kRequestNotSupported)) { |
| bt_is_error(mtu_result, DEBUG, "gatt", "MTU exchange not supported"); |
| mtu_cb(mtu_result.value_or(att::kLEMinMTU)); |
| } else { |
| bt_log(INFO, |
| "gatt", |
| "MTU exchange failed: %s", |
| bt_str(mtu_result.error_value())); |
| init_cb(fit::error(mtu_result.error_value())); |
| return; |
| } |
| |
| self->InitializeGattProfileService([self, |
| init_cb = std::move(init_cb), |
| services = std::move(services)]( |
| att::Result<> status) mutable { |
| if (status == ToResult(HostError::kNotFound)) { |
| // The GATT Profile service's Service Changed characteristic is |
| // optional. Its absence implies that the set of GATT services on the |
| // server is fixed, so the kNotFound error can be safely ignored. |
| bt_log(DEBUG, |
| "gatt", |
| "GATT Profile service not found. Assuming services are fixed."); |
| } else if (status.is_error()) { |
| init_cb(status); |
| return; |
| } |
| |
| self->DiscoverServices( |
| std::move(services), |
| [self, init_cb = std::move(init_cb)](att::Result<> status) mutable { |
| if (status.is_error()) { |
| init_cb(status); |
| return; |
| } |
| |
| // Handle Service Changed notifications received during service |
| // discovery. Skip notifying the service watcher callback as it will |
| // be notified in the init_cb callback. We handle Service Changed |
| // notifications before notifying the service watcher and init_cb in |
| // order to reduce the likelihood that returned services are |
| // instantly invalidated by Service Changed notifications. It is |
| // likely that bonded peers will send a Service Changed notification |
| // upon connection to indicate that services changed since the last |
| // connection, and such notifications will probably be received |
| // before service discovery completes. (Core Spec v5.3, Vol 3, Part |
| // G, Sec 2.5.2) |
| self->MaybeHandleNextServiceChangedNotification( |
| [self, init_cb = std::move(init_cb)]() mutable { |
| init_cb(fit::ok()); |
| }); |
| }); |
| }); |
| }); |
| } |
| |
| RemoteService* RemoteServiceManager::GattProfileService() { |
| auto service_iter = |
| std::find_if(services_.begin(), services_.end(), [](auto& s) { |
| return s.second->uuid() == types::kGenericAttributeService; |
| }); |
| return service_iter == services_.end() ? nullptr : service_iter->second.get(); |
| } |
| |
| void RemoteServiceManager::ConfigureServiceChangedNotifications( |
| RemoteService* gatt_profile_service, att::ResultFunction<> callback) { |
| auto self = weak_self_.GetWeakPtr(); |
| gatt_profile_service->DiscoverCharacteristics( |
| [self, callback = std::move(callback)]( |
| att::Result<> status, |
| const CharacteristicMap& characteristics) mutable { |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (bt_is_error( |
| status, |
| WARN, |
| "gatt", |
| "Error discovering GATT Profile service characteristics")) { |
| callback(status); |
| return; |
| } |
| |
| RemoteService* gatt_profile_service = self->GattProfileService(); |
| BT_ASSERT(gatt_profile_service); |
| |
| auto svc_changed_char_iter = std::find_if( |
| characteristics.begin(), |
| characteristics.end(), |
| [](CharacteristicMap::const_reference c) { |
| const CharacteristicData& data = c.second.first; |
| return data.type == types::kServiceChangedCharacteristic; |
| }); |
| |
| // The Service Changed characteristic is optional, and its absence |
| // implies that the set of GATT services on the server is fixed. |
| if (svc_changed_char_iter == characteristics.end()) { |
| callback(ToResult(HostError::kNotFound)); |
| return; |
| } |
| |
| const bt::gatt::CharacteristicHandle svc_changed_char_handle = |
| svc_changed_char_iter->first; |
| |
| auto notification_cb = [self](const ByteBuffer& value, |
| bool /*maybe_truncated*/) { |
| // The Client's Bearer may outlive this object. |
| if (self.is_alive()) { |
| self->OnServiceChangedNotification(value); |
| } |
| }; |
| |
| // Don't save handler_id as notifications never need to be disabled. |
| auto status_cb = [self, callback = std::move(callback)]( |
| att::Result<> status, IdType /*handler_id*/) { |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| // If the Service Changed characteristic exists, notification support |
| // is mandatory (Core Spec v5.2, Vol 3, Part G, Sec 7.1). |
| if (bt_is_error(status, |
| WARN, |
| "gatt", |
| "Enabling notifications of Service Changed " |
| "characteristic failed")) { |
| callback(status); |
| return; |
| } |
| |
| callback(fit::ok()); |
| }; |
| |
| gatt_profile_service->EnableNotifications(svc_changed_char_handle, |
| std::move(notification_cb), |
| std::move(status_cb)); |
| }); |
| } |
| |
| void RemoteServiceManager::InitializeGattProfileService( |
| att::ResultFunction<> callback) { |
| auto self = weak_self_.GetWeakPtr(); |
| DiscoverGattProfileService([self, callback = std::move(callback)]( |
| att::Result<> status) mutable { |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (status.is_error()) { |
| callback(status); |
| return; |
| } |
| |
| RemoteService* gatt_svc = self->GattProfileService(); |
| BT_ASSERT(gatt_svc); |
| self->ConfigureServiceChangedNotifications( |
| gatt_svc, [self, callback = std::move(callback)](att::Result<> status) { |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| callback(status); |
| }); |
| }); |
| } |
| |
| void RemoteServiceManager::DiscoverGattProfileService( |
| att::ResultFunction<> callback) { |
| auto self = weak_self_.GetWeakPtr(); |
| auto status_cb = [self, |
| callback = std::move(callback)](att::Result<> status) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (bt_is_error( |
| status, WARN, "gatt", "Error discovering GATT Profile service")) { |
| callback(status); |
| return; |
| } |
| |
| // The GATT Profile service is optional, and its absence implies that the |
| // set of GATT services on the server is fixed. |
| if (self->services_.empty()) { |
| callback(ToResult(HostError::kNotFound)); |
| return; |
| } |
| |
| // At most one instance of the GATT Profile service may exist (Core Spec |
| // v5.2, Vol 3, Part G, Sec 7). |
| if (self->services_.size() > 1) { |
| bt_log(WARN, |
| "gatt", |
| "Discovered (%zu) GATT Profile services, expected 1", |
| self->services_.size()); |
| callback(ToResult(HostError::kFailed)); |
| return; |
| } |
| |
| UUID uuid = self->services_.begin()->second->uuid(); |
| // The service UUID is filled in by Client based on the service discovery |
| // request, so it should be the same as the requested UUID. |
| BT_ASSERT(uuid == types::kGenericAttributeService); |
| |
| callback(fit::ok()); |
| }; |
| DiscoverServicesOfKind(ServiceKind::PRIMARY, |
| {types::kGenericAttributeService}, |
| std::move(status_cb)); |
| } |
| |
| void RemoteServiceManager::AddService(const ServiceData& service_data) { |
| att::Handle handle = service_data.range_start; |
| auto iter = services_.find(handle); |
| if (iter != services_.end()) { |
| // The GATT Profile service is discovered before general service discovery, |
| // so it may be discovered twice. |
| if (iter->second->uuid() != types::kGenericAttributeService) { |
| bt_log(WARN, |
| "gatt", |
| "found duplicate service attribute handle! (%#.4x)", |
| handle); |
| } |
| return; |
| } |
| |
| auto svc = |
| std::make_unique<RemoteService>(service_data, client_->GetWeakPtr()); |
| if (!svc) { |
| bt_log(DEBUG, "gatt", "failed to allocate RemoteService"); |
| return; |
| } |
| |
| services_[handle] = std::move(svc); |
| } |
| |
| void RemoteServiceManager::DiscoverServicesOfKind( |
| ServiceKind kind, |
| std::vector<UUID> service_uuids, |
| att::ResultFunction<> status_cb) { |
| auto self = weak_self_.GetWeakPtr(); |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| // The Client's Bearer may outlive this object. |
| if (self.is_alive()) { |
| self->AddService(service_data); |
| } |
| }; |
| |
| if (!service_uuids.empty()) { |
| client_->DiscoverServicesWithUuids(kind, |
| std::move(svc_cb), |
| std::move(status_cb), |
| std::move(service_uuids)); |
| } else { |
| client_->DiscoverServices(kind, std::move(svc_cb), std::move(status_cb)); |
| } |
| } |
| |
| void RemoteServiceManager::DiscoverServices(std::vector<UUID> service_uuids, |
| att::ResultFunction<> status_cb) { |
| auto self = weak_self_.GetWeakPtr(); |
| auto status_cb_wrapper = |
| [self, status_cb = std::move(status_cb)](att::Result<> status) { |
| TRACE_DURATION( |
| "bluetooth", |
| "gatt::RemoteServiceManager::DiscoverServices::status_cb_wrapper"); |
| |
| // The Client's Bearer may outlive this object. |
| if (!self.is_alive()) { |
| status_cb(ToResult(HostError::kFailed)); |
| return; |
| } |
| |
| // Service discovery support is mandatory for servers (v5.0, Vol 3, Part |
| // G, 4.2). |
| if (bt_is_error(status, TRACE, "gatt", "failed to discover services")) { |
| self->services_.clear(); |
| } |
| |
| status_cb(status); |
| }; |
| |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| // The Client's Bearer may outlive this object. |
| if (self.is_alive()) { |
| self->AddService(service_data); |
| } |
| }; |
| |
| DiscoverPrimaryAndSecondaryServicesInRange(std::move(service_uuids), |
| att::kHandleMin, |
| att::kHandleMax, |
| std::move(svc_cb), |
| std::move(status_cb_wrapper)); |
| } |
| |
| void RemoteServiceManager::DiscoverPrimaryAndSecondaryServicesInRange( |
| std::vector<UUID> service_uuids, |
| att::Handle start, |
| att::Handle end, |
| ServiceCallback service_cb, |
| att::ResultFunction<> status_cb) { |
| auto self = weak_self_.GetWeakPtr(); |
| auto primary_discov_cb = [self, |
| service_uuids, |
| start, |
| end, |
| svc_cb = service_cb.share(), |
| status_cb = std::move(status_cb)]( |
| att::Result<> status) mutable { |
| if (!self.is_alive() || status.is_error()) { |
| status_cb(status); |
| return; |
| } |
| |
| auto secondary_discov_cb = [cb = std::move(status_cb)]( |
| att::Result<> status) mutable { |
| // Not all GATT servers support the "secondary service" group type. We |
| // suppress the "Unsupported Group Type" error code and simply report no |
| // services instead of treating it as a fatal condition (errors propagated |
| // up the stack from here will cause the connection to be terminated). |
| if (status == ToResult(att::ErrorCode::kUnsupportedGroupType)) { |
| bt_log(DEBUG, |
| "gatt", |
| "peer does not support secondary services; ignoring ATT error"); |
| status = fit::ok(); |
| } |
| |
| cb(status); |
| }; |
| |
| if (!service_uuids.empty()) { |
| self->client_->DiscoverServicesWithUuidsInRange( |
| ServiceKind::SECONDARY, |
| start, |
| end, |
| std::move(svc_cb), |
| std::move(secondary_discov_cb), |
| std::move(service_uuids)); |
| } else { |
| self->client_->DiscoverServicesInRange(ServiceKind::SECONDARY, |
| start, |
| end, |
| std::move(svc_cb), |
| std::move(secondary_discov_cb)); |
| } |
| }; |
| |
| if (!service_uuids.empty()) { |
| client_->DiscoverServicesWithUuidsInRange(ServiceKind::PRIMARY, |
| start, |
| end, |
| std::move(service_cb), |
| std::move(primary_discov_cb), |
| std::move(service_uuids)); |
| } else { |
| client_->DiscoverServicesInRange(ServiceKind::PRIMARY, |
| start, |
| end, |
| std::move(service_cb), |
| std::move(primary_discov_cb)); |
| } |
| } |
| |
| void RemoteServiceManager::ListServices(const std::vector<UUID>& uuids, |
| ServiceListCallback callback) { |
| ServiceListRequest request(std::move(callback), uuids); |
| if (initialized_) { |
| request.Complete(fit::ok(), services_); |
| } else { |
| pending_list_services_requests_.push(std::move(request)); |
| } |
| } |
| |
| RemoteService::WeakPtr RemoteServiceManager::FindService(att::Handle handle) { |
| auto iter = services_.find(handle); |
| return iter == services_.end() ? RemoteService::WeakPtr() |
| : iter->second->GetWeakPtr(); |
| } |
| |
| void RemoteServiceManager::OnNotification(bool /*indication*/, |
| att::Handle value_handle, |
| const ByteBuffer& value, |
| bool maybe_truncated) { |
| if (services_.empty()) { |
| bt_log(DEBUG, "gatt", "ignoring notification from unknown service"); |
| return; |
| } |
| |
| // Find the service that |value_handle| belongs to. |
| auto iter = services_.upper_bound(value_handle); |
| if (iter != services_.begin()) |
| --iter; |
| |
| // If |value_handle| is within the previous service then we found it. |
| auto& svc = iter->second; |
| BT_DEBUG_ASSERT(value_handle >= svc->handle()); |
| |
| if (svc->info().range_end >= value_handle) { |
| svc->HandleNotification(value_handle, value, maybe_truncated); |
| } |
| } |
| |
| void RemoteServiceManager::OnServiceChangedNotification( |
| const ByteBuffer& buffer) { |
| bt_log(DEBUG, "gatt", "received service changed notification"); |
| |
| if (buffer.size() != sizeof(ServiceChangedCharacteristicValue)) { |
| bt_log(WARN, |
| "gatt", |
| "service changed notification value malformed; ignoring (size: %zu)", |
| buffer.size()); |
| return; |
| } |
| |
| ServiceChangedCharacteristicValue value; |
| value.range_start_handle = |
| le16toh(buffer.ReadMember< |
| &ServiceChangedCharacteristicValue::range_start_handle>()); |
| value.range_end_handle = le16toh( |
| buffer |
| .ReadMember<&ServiceChangedCharacteristicValue::range_end_handle>()); |
| if (value.range_start_handle > value.range_end_handle) { |
| bt_log( |
| WARN, |
| "gatt", |
| "service changed notification value malformed; ignoring (start > end)"); |
| return; |
| } |
| |
| queued_service_changes_.push(value); |
| |
| // Bonded devices may send service changed notifications upon connection if |
| // services changed while the device was disconnected (Core Spec v5.3, Vol 3, |
| // Part G, Sec 7.1). These notifications may be received during the initial |
| // service discovery procedure. Queue the service changes and process them as |
| // the last step of initialization. |
| if (!initialized_) { |
| bt_log(DEBUG, |
| "gatt", |
| "Received service changed notification before RemoteServiceManager " |
| "initialization " |
| "complete; queueing."); |
| return; |
| } |
| |
| MaybeHandleNextServiceChangedNotification(); |
| } |
| |
| void RemoteServiceManager::MaybeHandleNextServiceChangedNotification( |
| fit::callback<void()> on_complete) { |
| if (on_complete) { |
| service_changes_complete_callbacks_.push_back(std::move(on_complete)); |
| } |
| |
| if (current_service_change_.has_value()) { |
| return; |
| } |
| |
| if (queued_service_changes_.empty()) { |
| for (auto& cb : service_changes_complete_callbacks_) { |
| cb(); |
| } |
| service_changes_complete_callbacks_.clear(); |
| return; |
| } |
| |
| bt_log(DEBUG, "gatt", "handling next Service Changed notification"); |
| |
| current_service_change_ = ServiceChangedState{ |
| .value = queued_service_changes_.front(), .services = {}}; |
| queued_service_changes_.pop(); |
| |
| auto self = weak_self_.GetWeakPtr(); |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| if (self.is_alive()) { |
| BT_ASSERT(self->current_service_change_.has_value()); |
| // gatt::Client verifies that service discovery results are in the |
| // requested range. |
| BT_ASSERT(service_data.range_start >= |
| self->current_service_change_->value.range_start_handle); |
| BT_ASSERT(service_data.range_start <= |
| self->current_service_change_->value.range_end_handle); |
| self->current_service_change_->services.emplace(service_data.range_start, |
| service_data); |
| } |
| }; |
| |
| att::ResultFunction<> status_cb = [self](att::Result<> status) mutable { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (!bt_is_error( |
| status, |
| WARN, |
| "gatt", |
| "service discovery for service changed notification failed")) { |
| BT_ASSERT(self->current_service_change_.has_value()); |
| self->ProcessServiceChangedDiscoveryResults( |
| self->current_service_change_.value()); |
| } |
| |
| self->current_service_change_.reset(); |
| self->MaybeHandleNextServiceChangedNotification(); |
| }; |
| |
| DiscoverPrimaryAndSecondaryServicesInRange( |
| /*service_uuids=*/{}, |
| self->current_service_change_->value.range_start_handle, |
| self->current_service_change_->value.range_end_handle, |
| std::move(svc_cb), |
| std::move(status_cb)); |
| } |
| |
| void RemoteServiceManager::ProcessServiceChangedDiscoveryResults( |
| const ServiceChangedState& service_changed) { |
| std::vector<ServiceMap::iterator> removed_iters; |
| std::vector<ServiceData> added_data; |
| std::vector<std::pair<ServiceMap::iterator, ServiceData>> |
| modified_iters_and_data; |
| CalculateServiceChanges( |
| service_changed, removed_iters, added_data, modified_iters_and_data); |
| |
| bt_log(INFO, |
| "gatt", |
| "service changed notification added %zu, removed %zu, and modified " |
| "%zu services", |
| added_data.size(), |
| removed_iters.size(), |
| modified_iters_and_data.size()); |
| |
| std::vector<att::Handle> removed_service_handles; |
| for (ServiceMap::iterator& service_iter : removed_iters) { |
| removed_service_handles.push_back(service_iter->first); |
| service_iter->second->set_service_changed(true); |
| services_.erase(service_iter); |
| } |
| |
| ServiceList modified_services; |
| modified_services.reserve(modified_iters_and_data.size()); |
| for (auto& [service_iter, new_service_data] : modified_iters_and_data) { |
| if (service_iter->second->uuid() == types::kGenericAttributeService) { |
| // The specification is ambiguous about what to do if the GATT Profile |
| // Service changes, but it implies that it means the Database Hash or |
| // Server Supported Features values have changed. At the very least, the |
| // Service Changed Characteristic is not supposed to change if the server |
| // is bonded with any client. We don't want to reset the service and |
| // potentially miss notifications until characteristics have been |
| // rediscovered. See Core Spec v5.3, Vol 3, Part G, Sec 7.1. |
| bt_log(INFO, |
| "gatt", |
| "GATT Profile Service changed; assuming same characteristics " |
| "(server values probably " |
| "changed)"); |
| modified_services.push_back(service_iter->second->GetWeakPtr()); |
| continue; |
| } |
| |
| // Destroy the old service and replace with a new service in order to easily |
| // cancel ongoing procedures and ensure clients handle service change. |
| service_iter->second->set_service_changed(true); |
| service_iter->second.reset(); |
| |
| auto new_service = std::make_unique<RemoteService>(new_service_data, |
| client_->GetWeakPtr()); |
| BT_ASSERT(new_service->handle() == service_iter->first); |
| modified_services.push_back(new_service->GetWeakPtr()); |
| service_iter->second = std::move(new_service); |
| } |
| |
| ServiceList added_services; |
| added_services.reserve(added_data.size()); |
| for (ServiceData service_data : added_data) { |
| auto service = |
| std::make_unique<RemoteService>(service_data, client_->GetWeakPtr()); |
| added_services.push_back(service->GetWeakPtr()); |
| auto [_, inserted] = |
| services_.try_emplace(service->handle(), std::move(service)); |
| BT_ASSERT_MSG(inserted, |
| "service with handle (%#.4x) already exists", |
| service->handle()); |
| } |
| |
| // Skip notifying the service watcher callback during initialization as it |
| // will be notified in the init_cb callback. |
| if (initialized_) { |
| svc_watcher_(std::move(removed_service_handles), |
| std::move(added_services), |
| std::move(modified_services)); |
| } |
| } |
| |
| void RemoteServiceManager::CalculateServiceChanges( |
| const ServiceChangedState& service_changed, |
| std::vector<ServiceMap::iterator>& removed_services, |
| std::vector<ServiceData>& added_services, |
| std::vector<std::pair<ServiceMap::iterator, ServiceData>>& |
| modified_services) { |
| // iterator to first service greater than or equal to the start of the |
| // affected range. |
| auto services_iter = |
| services_.lower_bound(service_changed.value.range_start_handle); |
| // iterator to first service greater than the end of the affected range. |
| auto services_end = |
| services_.upper_bound(service_changed.value.range_end_handle); |
| auto new_services_iter = service_changed.services.begin(); |
| auto new_services_end = service_changed.services.end(); |
| |
| // Iterate through the lists of services and calculate the difference. Both |
| // the old and new services are stored in ordered maps, so we can iterate |
| // through both linearly in one pass. |
| while (services_iter != services_end && |
| new_services_iter != new_services_end) { |
| if (services_iter->first < new_services_iter->first) { |
| removed_services.push_back(services_iter); |
| services_iter++; |
| } else if (services_iter->first == new_services_iter->first) { |
| if (services_iter->second->uuid() == new_services_iter->second.type) { |
| // Assume service with same handle & UUID has been modified, since all |
| // services in the Service Change range must be affected, by definition: |
| // "The Service Changed Characteristic Value [...] indicates the |
| // beginning and ending Attribute Handles affected by an addition, |
| // removal, or modification to a GATT-based service on the server" (Core |
| // Spec v5.3, Vol 3, Part G, Sec 7.1). |
| modified_services.emplace_back(services_iter, |
| new_services_iter->second); |
| } else { |
| // A new service has been added with the same handle but different type. |
| removed_services.push_back(services_iter); |
| added_services.push_back(new_services_iter->second); |
| } |
| services_iter++; |
| new_services_iter++; |
| } else { |
| added_services.push_back(new_services_iter->second); |
| new_services_iter++; |
| } |
| } |
| |
| // Remaining old services must have been removed. |
| while (services_iter != services_end) { |
| removed_services.push_back(services_iter); |
| services_iter++; |
| } |
| |
| // Remaining new services must have been added. |
| if (new_services_iter != new_services_end) { |
| std::transform( |
| new_services_iter, |
| new_services_end, |
| std::back_inserter(added_services), |
| [](const std::map<att::Handle, ServiceData>::value_type& value) { |
| return value.second; |
| }); |
| } |
| } |
| |
| } // namespace bt::gatt::internal |