blob: 5df5180ef01972648f699f83aa47e79e341e1f2f [file] [log] [blame]
// 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