blob: 5ff14de6b79a105c34ba0cebeec4c5b4608a0970 [file] [log] [blame]
// Copyright 2021 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 "gatt2_client_server.h"
#include "src/connectivity/bluetooth/core/bt-host/fidl/helpers.h"
namespace fb = fuchsia::bluetooth;
namespace fbg = fuchsia::bluetooth::gatt2;
namespace bthost {
namespace {
fbg::ServiceInfo RemoteServiceToFidlServiceInfo(const bt::gatt::RemoteService::WeakPtr& svc) {
fbg::ServiceInfo out;
out.set_handle(fbg::ServiceHandle{svc->handle()});
auto kind = svc->info().kind == bt::gatt::ServiceKind::PRIMARY ? fbg::ServiceKind::PRIMARY
: fbg::ServiceKind::SECONDARY;
out.set_kind(kind);
out.set_type(fb::Uuid{svc->uuid().value()});
return out;
}
} // namespace
Gatt2ClientServer::Gatt2ClientServer(bt::gatt::PeerId peer_id, bt::gatt::GATT::WeakPtr weak_gatt,
fidl::InterfaceRequest<fbg::Client> request,
fit::callback<void()> error_cb)
: GattServerBase(std::move(weak_gatt), /*impl=*/this, std::move(request)),
peer_id_(peer_id),
server_error_cb_(std::move(error_cb)),
weak_self_(this) {
set_error_handler([this](zx_status_t) {
if (server_error_cb_) {
server_error_cb_();
}
});
// It is safe to bind |this| to the callback because the service watcher is unregistered in the
// destructor.
service_watcher_id_ = gatt()->RegisterRemoteServiceWatcherForPeer(
peer_id_, [this](auto removed, auto added, auto modified) {
// Ignore results before the initial call to ListServices() completes to avoid redundant
// notifications.
if (!list_services_complete_) {
bt_log(TRACE, "fidl",
"ignoring service watcher update before ListServices() result received");
return;
}
OnWatchServicesResult(removed, added, modified);
});
}
Gatt2ClientServer::~Gatt2ClientServer() {
BT_ASSERT(gatt()->UnregisterRemoteServiceWatcher(service_watcher_id_));
}
void Gatt2ClientServer::OnWatchServicesResult(const std::vector<bt::att::Handle>& removed,
const bt::gatt::ServiceList& added,
const bt::gatt::ServiceList& modified) {
// Accumulate all removed services and send in next result.
if (!next_watch_services_result_.has_value()) {
next_watch_services_result_.emplace();
}
next_watch_services_result_->removed.insert(removed.begin(), removed.end());
// Remove any stale updated services (to avoid sending an invalid one to the client).
for (bt::att::Handle handle : removed) {
next_watch_services_result_->updated.erase(handle);
}
// Replace any existing updated services with same handle and add new updates.
for (const bt::gatt::RemoteService::WeakPtr& svc : added) {
next_watch_services_result_->updated[svc->handle()] = svc;
}
for (const bt::gatt::RemoteService::WeakPtr& svc : modified) {
next_watch_services_result_->updated[svc->handle()] = svc;
}
bt_log(TRACE, "fidl", "next watch services result: (removed: %zu, updated: %zu) (peer: %s)",
next_watch_services_result_->removed.size(), next_watch_services_result_->updated.size(),
bt_str(peer_id_));
TrySendNextWatchServicesResult();
}
void Gatt2ClientServer::TrySendNextWatchServicesResult() {
if (!watch_services_request_ || !next_watch_services_result_) {
return;
}
std::vector<fbg::Handle> fidl_removed;
std::transform(next_watch_services_result_->removed.begin(),
next_watch_services_result_->removed.end(), std::back_inserter(fidl_removed),
[](const bt::att::Handle& handle) { return fbg::Handle{handle}; });
// Don't filter removed services by UUID because we don't know the UUIDs of these services
// currently.
// TODO(https://fxbug.dev/42111895): Filter removed services by UUID.
std::vector<fbg::ServiceInfo> fidl_updated;
for (const ServiceMap::value_type& svc_pair : next_watch_services_result_->updated) {
// Filter updated services by UUID.
// NOTE: If clients change UUIDs they are requesting across requests, they won't receive
// existing service with the new UUIDs, only new ones
if (prev_watch_services_uuids_.empty() ||
prev_watch_services_uuids_.count(svc_pair.second->uuid()) == 1) {
fidl_updated.push_back(RemoteServiceToFidlServiceInfo(svc_pair.second));
}
}
next_watch_services_result_.reset();
// Skip sending results that are empty after filtering services by UUID.
if (fidl_removed.empty() && fidl_updated.empty()) {
bt_log(TRACE, "fidl", "skipping service watcher update without matching UUIDs (peer: %s)",
bt_str(peer_id_));
return;
}
// TODO(https://fxbug.dev/42165836): Use measure-tape to verify response fits in FIDL channel
// before sending. This is only an issue for peers with very large databases.
bt_log(TRACE, "fidl", "notifying WatchServices() callback (removed: %zu, updated: %zu, peer: %s)",
fidl_removed.size(), fidl_updated.size(), bt_str(peer_id_));
watch_services_request_.value()(std::move(fidl_updated), std::move(fidl_removed));
watch_services_request_.reset();
}
// TODO(https://fxbug.dev/42165818): Do not send privileged services (e.g. Generic Attribute Profile
// Service) to clients.
void Gatt2ClientServer::WatchServices(std::vector<fb::Uuid> fidl_uuids,
WatchServicesCallback callback) {
std::unordered_set<bt::UUID> uuids;
std::transform(fidl_uuids.begin(), fidl_uuids.end(), std::inserter(uuids, uuids.begin()),
[](const fb::Uuid& uuid) { return bt::UUID(uuid.value); });
// If the UUID filter list is changed between requests, perform a fresh ListServices() call to
// ensure existing services that match the new UUIDs are reported to the client.
// Dropping the old watch_services_request_ with no new results.
if (uuids != prev_watch_services_uuids_) {
bt_log(DEBUG, "fidl", "WatchServices: UUIDs changed from previous call (peer: %s)",
bt_str(peer_id_));
list_services_complete_ = false;
// Clear old watch service results as we're about to get a fresh list of services.
next_watch_services_result_.reset();
prev_watch_services_uuids_ = uuids;
if (watch_services_request_) {
watch_services_request_.value()({}, {});
watch_services_request_.reset();
}
}
// Only allow 1 callback at a time. Close the server if this is violated.
if (watch_services_request_) {
bt_log(WARN, "fidl", "%s: call received while previous call is still pending", __FUNCTION__);
binding()->Close(ZX_ERR_ALREADY_BOUND);
server_error_cb_();
return;
}
watch_services_request_.emplace(std::move(callback));
auto self = weak_self_.GetWeakPtr();
// Return a complete service snapshot on the first call, or on calls that use a new UUID filter
// list.
if (!list_services_complete_) {
std::vector<bt::UUID> uuids_vector(uuids.begin(), uuids.end());
gatt()->ListServices(
peer_id_, std::move(uuids_vector),
[self](bt::att::Result<> status, const bt::gatt::ServiceList& services) {
if (!self.is_alive()) {
return;
}
if (bt_is_error(status, INFO, "fidl", "WatchServices: ListServices failed (peer: %s)",
bt_str(self->peer_id_))) {
self->binding()->Close(ZX_ERR_CONNECTION_RESET);
self->server_error_cb_();
return;
}
bt_log(DEBUG, "fidl", "WatchServices: ListServices complete (peer: %s)",
bt_str(self->peer_id_));
BT_ASSERT(self->watch_services_request_);
self->list_services_complete_ = true;
self->OnWatchServicesResult(/*removed=*/{}, /*added=*/services, /*modified=*/{});
});
return;
}
TrySendNextWatchServicesResult();
}
void Gatt2ClientServer::ConnectToService(fbg::ServiceHandle handle,
fidl::InterfaceRequest<fbg::RemoteService> request) {
bt_log(DEBUG, "fidl", "%s: (handle: 0x%lX)", __FUNCTION__, handle.value);
if (!fidl_helpers::IsFidlGattServiceHandleValid(handle)) {
request.Close(ZX_ERR_INVALID_ARGS);
return;
}
bt::att::Handle service_handle = static_cast<bt::att::Handle>(handle.value);
// Only allow clients to have 1 RemoteService per service at a time to prevent race conditions
// between multiple RemoteService clients modifying a service, and to simplify implementation.
// A client shouldn't need more than 1 RemoteService per service at a time, but if they really
// need to, they can create multiple Client instances.
if (services_.count(service_handle) == 1) {
request.Close(ZX_ERR_ALREADY_EXISTS);
return;
}
// Mark this connection as in progress.
services_.try_emplace(service_handle, nullptr);
bt::gatt::RemoteService::WeakPtr service = gatt()->FindService(peer_id_, service_handle);
if (!service.is_alive()) {
bt_log(INFO, "fidl", "service not found (peer: %s, handle: %#.4x)", bt_str(peer_id_),
service_handle);
services_.erase(service_handle);
request.Close(ZX_ERR_NOT_FOUND);
return;
}
BT_ASSERT(service_handle == service->handle());
// This removed handler may be called long after the service is removed from the service map or
// this server is destroyed, since removed handlers are not unregistered. If the FIDL client
// connects->disconnects->connects, it is possible for this handler to be called twice (the
// second call should then do nothing).
auto self = weak_self_.GetWeakPtr();
fit::closure removed_handler = [self, service_handle] {
if (!self.is_alive()) {
return;
}
bt_log(DEBUG, "fidl", "service removed (peer: %s, handle: %#.4x)", bt_str(self->peer_id_),
service_handle);
auto svc_iter = self->services_.find(service_handle);
if (svc_iter == self->services_.end()) {
bt_log(TRACE, "fidl",
"ignoring service removed callback for already removed service (peer: %s, handle: "
"%#.4x)",
bt_str(self->peer_id_), service_handle);
return;
}
svc_iter->second->Close(ZX_ERR_CONNECTION_RESET);
self->services_.erase(svc_iter);
};
// The only reason RemoteService::AddRemovedHandler() can fail is if the service is already
// shut down, but that should not be possible in this synchronous callback (the service
// would not have been returned in the first place).
BT_ASSERT_MSG(service->AddRemovedHandler(std::move(removed_handler)),
"adding service removed handler failed (service may be shut down) (peer: %s, "
"handle: %#.4x)",
bt_str(peer_id_), service_handle);
std::unique_ptr<Gatt2RemoteServiceServer> remote_service_server =
std::make_unique<Gatt2RemoteServiceServer>(std::move(service), gatt(), peer_id_,
std::move(request));
// Even if there is already an error, this handler won't be called until the next yield to
// the event loop.
remote_service_server->set_error_handler([self, service_handle](zx_status_t status) {
bt_log(TRACE, "fidl", "FIDL channel error (peer: %s, handle: %#.4x)", bt_str(self->peer_id_),
service_handle);
self->services_.erase(service_handle);
});
// Error handler should not have been called yet.
BT_ASSERT(services_.count(service_handle) == 1);
services_[service_handle] = std::move(remote_service_server);
}
} // namespace bthost