| // 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 "remote_service_manager.h" |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-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) { |
| ZX_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); |
| } |
| } |
| |
| callback_(status, std::move(result)); |
| } |
| |
| RemoteServiceManager::RemoteServiceManager(std::unique_ptr<Client> client, |
| async_dispatcher_t* gatt_dispatcher) |
| : gatt_dispatcher_(gatt_dispatcher), |
| client_(std::move(client)), |
| initialized_(false), |
| weak_ptr_factory_(this) { |
| ZX_DEBUG_ASSERT(gatt_dispatcher_); |
| ZX_DEBUG_ASSERT(client_); |
| |
| client_->SetNotificationHandler(fit::bind_member<&RemoteServiceManager::OnNotification>(this)); |
| } |
| |
| RemoteServiceManager::~RemoteServiceManager() { |
| ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid()); |
| |
| client_->SetNotificationHandler({}); |
| ClearServices(); |
| |
| // 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, std::vector<UUID> services) { |
| ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid()); |
| |
| auto self = weak_ptr_factory_.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) { |
| 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; }); |
| 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), services = std::move(services)]( |
| att::Result<> status, uint16_t mtu) mutable { |
| // The Client's Bearer may outlive this object. |
| if (!self) { |
| return; |
| } |
| |
| if (bt_is_error(status, INFO, "gatt", "MTU exchange failed")) { |
| init_cb(status); |
| 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(fitx::ok()); }); |
| }); |
| }); |
| }); |
| } |
| |
| fbl::RefPtr<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; |
| } |
| |
| void RemoteServiceManager::ConfigureServiceChangedNotifications( |
| fbl::RefPtr<RemoteService> gatt_profile_service, att::ResultFunction<> callback) { |
| auto self = weak_ptr_factory_.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) { |
| return; |
| } |
| |
| if (bt_is_error(status, WARN, "gatt", |
| "Error discovering GATT Profile service characteristics")) { |
| callback(status); |
| return; |
| } |
| |
| auto gatt_profile_service = self->GattProfileService(); |
| ZX_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) { |
| 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) { |
| 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(fitx::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_ptr_factory_.GetWeakPtr(); |
| DiscoverGattProfileService([self, callback = std::move(callback)](att::Result<> status) mutable { |
| // The Client's Bearer may outlive this object. |
| if (!self) { |
| return; |
| } |
| |
| if (status.is_error()) { |
| callback(status); |
| return; |
| } |
| |
| fbl::RefPtr<RemoteService> gatt_svc = self->GattProfileService(); |
| ZX_ASSERT(gatt_svc); |
| self->ConfigureServiceChangedNotifications( |
| std::move(gatt_svc), [self, callback = std::move(callback)](att::Result<> status) { |
| // The Client's Bearer may outlive this object. |
| if (!self) { |
| return; |
| } |
| |
| callback(status); |
| }); |
| }); |
| } |
| |
| void RemoteServiceManager::DiscoverGattProfileService(att::ResultFunction<> callback) { |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto status_cb = [self, callback = std::move(callback)](att::Result<> status) { |
| if (!self) { |
| 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. |
| ZX_ASSERT(uuid == types::kGenericAttributeService); |
| |
| callback(fitx::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 = fbl::AdoptRef(new RemoteService(service_data, client_->AsWeakPtr())); |
| 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_ptr_factory_.GetWeakPtr(); |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| // The Client's Bearer may outlive this object. |
| if (self) { |
| 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_ptr_factory_.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) { |
| 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")) { |
| // Clear services that were buffered so far. |
| self->ClearServices(); |
| } |
| |
| status_cb(status); |
| }; |
| |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| // The Client's Bearer may outlive this object. |
| if (self) { |
| 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_ptr_factory_.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 || 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 = fitx::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(fitx::ok(), services_); |
| } else { |
| pending_list_services_requests_.push(std::move(request)); |
| } |
| } |
| |
| fbl::RefPtr<RemoteService> RemoteServiceManager::FindService(att::Handle handle) { |
| auto iter = services_.find(handle); |
| return iter == services_.end() ? nullptr : iter->second; |
| } |
| |
| void RemoteServiceManager::ClearServices() { |
| auto services = std::move(services_); |
| for (auto& iter : services) { |
| iter.second->ShutDown(); |
| } |
| } |
| |
| void RemoteServiceManager::OnNotification(bool /*indication*/, att::Handle value_handle, |
| const ByteBuffer& value, bool maybe_truncated) { |
| ZX_DEBUG_ASSERT(thread_checker_.is_thread_valid()); |
| |
| 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; |
| ZX_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 = |
| letoh16(buffer.ReadMember<&ServiceChangedCharacteristicValue::range_start_handle>()); |
| value.range_end_handle = |
| letoh16(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()}; |
| queued_service_changes_.pop(); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| ServiceCallback svc_cb = [self](const ServiceData& service_data) { |
| if (self) { |
| ZX_ASSERT(self->current_service_change_.has_value()); |
| // gatt::Client verifies that service discovery results are in the requested range. |
| ZX_ASSERT(service_data.range_start >= |
| self->current_service_change_->value.range_start_handle); |
| ZX_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) { |
| return; |
| } |
| |
| if (!bt_is_error(status, WARN, "gatt", |
| "service discovery for service changed notification failed")) { |
| ZX_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->ShutDown(/*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); |
| 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->ShutDown(/*service_changed=*/true); |
| auto new_service = fbl::AdoptRef(new RemoteService(new_service_data, client_->AsWeakPtr())); |
| ZX_ASSERT(new_service->handle() == service_iter->first); |
| service_iter->second = new_service; |
| modified_services.push_back(std::move(new_service)); |
| } |
| |
| ServiceList added_services; |
| added_services.reserve(added_data.size()); |
| for (ServiceData service_data : added_data) { |
| auto service = fbl::AdoptRef(new RemoteService(service_data, client_->AsWeakPtr())); |
| auto [_, inserted] = services_.try_emplace(service->handle(), service); |
| ZX_ASSERT_MSG(inserted, "service with handle (%#.4x) already exists", service->handle()); |
| added_services.push_back(std::move(service)); |
| } |
| |
| // 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 |