xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/fuchsia/host/fidl/gatt2_client_server.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt2_client_server.h"
16 
17 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
18 
19 namespace fb = fuchsia::bluetooth;
20 namespace fbg = fuchsia::bluetooth::gatt2;
21 
22 namespace bthost {
23 
24 namespace {
25 
RemoteServiceToFidlServiceInfo(const bt::gatt::RemoteService::WeakPtr & svc)26 fbg::ServiceInfo RemoteServiceToFidlServiceInfo(
27     const bt::gatt::RemoteService::WeakPtr& svc) {
28   fbg::ServiceInfo out;
29   out.set_handle(fbg::ServiceHandle{svc->handle()});
30   auto kind = svc->info().kind == bt::gatt::ServiceKind::PRIMARY
31                   ? fbg::ServiceKind::PRIMARY
32                   : fbg::ServiceKind::SECONDARY;
33   out.set_kind(kind);
34   out.set_type(fb::Uuid{svc->uuid().value()});
35   return out;
36 }
37 
38 }  // namespace
39 
Gatt2ClientServer(bt::gatt::PeerId peer_id,bt::gatt::GATT::WeakPtr weak_gatt,fidl::InterfaceRequest<fbg::Client> request,fit::callback<void ()> error_cb)40 Gatt2ClientServer::Gatt2ClientServer(
41     bt::gatt::PeerId peer_id,
42     bt::gatt::GATT::WeakPtr weak_gatt,
43     fidl::InterfaceRequest<fbg::Client> request,
44     fit::callback<void()> error_cb)
45     : GattServerBase(std::move(weak_gatt), /*impl=*/this, std::move(request)),
46       peer_id_(peer_id),
47       server_error_cb_(std::move(error_cb)),
48       weak_self_(this) {
49   set_error_handler([this](zx_status_t) {
50     if (server_error_cb_) {
51       server_error_cb_();
52     }
53   });
54 
55   // It is safe to bind |this| to the callback because the service watcher is
56   // unregistered in the destructor.
57   service_watcher_id_ = gatt()->RegisterRemoteServiceWatcherForPeer(
58       peer_id_, [this](auto removed, auto added, auto modified) {
59         // Ignore results before the initial call to ListServices() completes to
60         // avoid redundant notifications.
61         if (!list_services_complete_) {
62           bt_log(TRACE,
63                  "fidl",
64                  "ignoring service watcher update before ListServices() result "
65                  "received");
66           return;
67         }
68         OnWatchServicesResult(removed, added, modified);
69       });
70 }
71 
~Gatt2ClientServer()72 Gatt2ClientServer::~Gatt2ClientServer() {
73   PW_CHECK(gatt()->UnregisterRemoteServiceWatcher(service_watcher_id_));
74 }
75 
OnWatchServicesResult(const std::vector<bt::att::Handle> & removed,const bt::gatt::ServiceList & added,const bt::gatt::ServiceList & modified)76 void Gatt2ClientServer::OnWatchServicesResult(
77     const std::vector<bt::att::Handle>& removed,
78     const bt::gatt::ServiceList& added,
79     const bt::gatt::ServiceList& modified) {
80   // Accumulate all removed services and send in next result.
81   if (!next_watch_services_result_.has_value()) {
82     next_watch_services_result_.emplace();
83   }
84   next_watch_services_result_->removed.insert(removed.begin(), removed.end());
85 
86   // Remove any stale updated services (to avoid sending an invalid one to the
87   // client).
88   for (bt::att::Handle handle : removed) {
89     next_watch_services_result_->updated.erase(handle);
90   }
91 
92   // Replace any existing updated services with same handle and add new updates.
93   for (const bt::gatt::RemoteService::WeakPtr& svc : added) {
94     next_watch_services_result_->updated[svc->handle()] = svc;
95   }
96   for (const bt::gatt::RemoteService::WeakPtr& svc : modified) {
97     next_watch_services_result_->updated[svc->handle()] = svc;
98   }
99 
100   bt_log(TRACE,
101          "fidl",
102          "next watch services result: (removed: %zu, updated: %zu) (peer: %s)",
103          next_watch_services_result_->removed.size(),
104          next_watch_services_result_->updated.size(),
105          bt_str(peer_id_));
106 
107   TrySendNextWatchServicesResult();
108 }
109 
TrySendNextWatchServicesResult()110 void Gatt2ClientServer::TrySendNextWatchServicesResult() {
111   if (!watch_services_request_ || !next_watch_services_result_) {
112     return;
113   }
114 
115   std::vector<fbg::Handle> fidl_removed;
116   std::transform(
117       next_watch_services_result_->removed.begin(),
118       next_watch_services_result_->removed.end(),
119       std::back_inserter(fidl_removed),
120       [](const bt::att::Handle& handle) { return fbg::Handle{handle}; });
121 
122   // Don't filter removed services by UUID because we don't know the UUIDs of
123   // these services currently.
124   // TODO(fxbug.dev/42111895): Filter removed services by UUID.
125 
126   std::vector<fbg::ServiceInfo> fidl_updated;
127   for (const ServiceMap::value_type& svc_pair :
128        next_watch_services_result_->updated) {
129     // Filter updated services by UUID.
130     // NOTE: If clients change UUIDs they are requesting across requests, they
131     // won't receive existing service with the new UUIDs, only new ones
132     if (prev_watch_services_uuids_.empty() ||
133         prev_watch_services_uuids_.count(svc_pair.second->uuid()) == 1) {
134       fidl_updated.push_back(RemoteServiceToFidlServiceInfo(svc_pair.second));
135     }
136   }
137 
138   next_watch_services_result_.reset();
139 
140   // Skip sending results that are empty after filtering services by UUID.
141   if (fidl_removed.empty() && fidl_updated.empty()) {
142     bt_log(TRACE,
143            "fidl",
144            "skipping service watcher update without matching UUIDs (peer: %s)",
145            bt_str(peer_id_));
146     return;
147   }
148 
149   // TODO(fxbug.dev/42165836): Use measure-tape to verify response fits in FIDL
150   // channel before sending. This is only an issue for peers with very large
151   // databases.
152   bt_log(TRACE,
153          "fidl",
154          "notifying WatchServices() callback (removed: %zu, updated: %zu, "
155          "peer: %s)",
156          fidl_removed.size(),
157          fidl_updated.size(),
158          bt_str(peer_id_));
159   watch_services_request_.value()(std::move(fidl_updated),
160                                   std::move(fidl_removed));
161   watch_services_request_.reset();
162 }
163 
164 // TODO(fxbug.dev/42165818): Do not send privileged services (e.g. Generic
165 // Attribute Profile Service) to clients.
WatchServices(std::vector<fb::Uuid> fidl_uuids,WatchServicesCallback callback)166 void Gatt2ClientServer::WatchServices(std::vector<fb::Uuid> fidl_uuids,
167                                       WatchServicesCallback callback) {
168   std::unordered_set<bt::UUID> uuids;
169   std::transform(fidl_uuids.begin(),
170                  fidl_uuids.end(),
171                  std::inserter(uuids, uuids.begin()),
172                  [](const fb::Uuid& uuid) { return bt::UUID(uuid.value); });
173 
174   // If the UUID filter list is changed between requests, perform a fresh
175   // ListServices() call to ensure existing services that match the new UUIDs
176   // are reported to the client. Dropping the old watch_services_request_ with
177   // no new results.
178   if (uuids != prev_watch_services_uuids_) {
179     bt_log(DEBUG,
180            "fidl",
181            "WatchServices: UUIDs changed from previous call (peer: %s)",
182            bt_str(peer_id_));
183     list_services_complete_ = false;
184     // Clear old watch service results as we're about to get a fresh list of
185     // services.
186     next_watch_services_result_.reset();
187     prev_watch_services_uuids_ = uuids;
188     if (watch_services_request_) {
189       watch_services_request_.value()({}, {});
190       watch_services_request_.reset();
191     }
192   }
193 
194   // Only allow 1 callback at a time. Close the server if this is violated.
195   if (watch_services_request_) {
196     bt_log(WARN,
197            "fidl",
198            "%s: call received while previous call is still pending",
199            __FUNCTION__);
200     binding()->Close(ZX_ERR_ALREADY_BOUND);
201     server_error_cb_();
202     return;
203   }
204 
205   watch_services_request_.emplace(std::move(callback));
206 
207   auto self = weak_self_.GetWeakPtr();
208 
209   // Return a complete service snapshot on the first call, or on calls that use
210   // a new UUID filter list.
211   if (!list_services_complete_) {
212     std::vector<bt::UUID> uuids_vector(uuids.begin(), uuids.end());
213     gatt()->ListServices(
214         peer_id_,
215         std::move(uuids_vector),
216         [self](bt::att::Result<> status,
217                const bt::gatt::ServiceList& services) {
218           if (!self.is_alive()) {
219             return;
220           }
221           if (bt_is_error(status,
222                           INFO,
223                           "fidl",
224                           "WatchServices: ListServices failed (peer: %s)",
225                           bt_str(self->peer_id_))) {
226             self->binding()->Close(ZX_ERR_CONNECTION_RESET);
227             self->server_error_cb_();
228             return;
229           }
230 
231           bt_log(DEBUG,
232                  "fidl",
233                  "WatchServices: ListServices complete (peer: %s)",
234                  bt_str(self->peer_id_));
235 
236           PW_CHECK(self->watch_services_request_);
237           self->list_services_complete_ = true;
238           self->OnWatchServicesResult(
239               /*removed=*/{}, /*added=*/services, /*modified=*/{});
240         });
241     return;
242   }
243 
244   TrySendNextWatchServicesResult();
245 }
246 
ConnectToService(fbg::ServiceHandle handle,fidl::InterfaceRequest<fbg::RemoteService> request)247 void Gatt2ClientServer::ConnectToService(
248     fbg::ServiceHandle handle,
249     fidl::InterfaceRequest<fbg::RemoteService> request) {
250   bt_log(DEBUG, "fidl", "%s: (handle: 0x%lX)", __FUNCTION__, handle.value);
251 
252   if (!fidl_helpers::IsFidlGattServiceHandleValid(handle)) {
253     request.Close(ZX_ERR_INVALID_ARGS);
254     return;
255   }
256   bt::att::Handle service_handle = static_cast<bt::att::Handle>(handle.value);
257 
258   // Only allow clients to have 1 RemoteService per service at a time to prevent
259   // race conditions between multiple RemoteService clients modifying a service,
260   // and to simplify implementation. A client shouldn't need more than 1
261   // RemoteService per service at a time, but if they really need to, they can
262   // create multiple Client instances.
263   if (services_.count(service_handle) == 1) {
264     request.Close(ZX_ERR_ALREADY_EXISTS);
265     return;
266   }
267 
268   // Mark this connection as in progress.
269   services_.try_emplace(service_handle, nullptr);
270 
271   bt::gatt::RemoteService::WeakPtr service =
272       gatt()->FindService(peer_id_, service_handle);
273   if (!service.is_alive()) {
274     bt_log(INFO,
275            "fidl",
276            "service not found (peer: %s, handle: %#.4x)",
277            bt_str(peer_id_),
278            service_handle);
279     services_.erase(service_handle);
280     request.Close(ZX_ERR_NOT_FOUND);
281     return;
282   }
283   PW_CHECK(service_handle == service->handle());
284 
285   // This removed handler may be called long after the service is removed from
286   // the service map or this server is destroyed, since removed handlers are not
287   // unregistered. If the FIDL client connects->disconnects->connects, it is
288   // possible for this handler to be called twice (the second call should then
289   // do nothing).
290   auto self = weak_self_.GetWeakPtr();
291   fit::closure removed_handler = [self, service_handle] {
292     if (!self.is_alive()) {
293       return;
294     }
295     bt_log(DEBUG,
296            "fidl",
297            "service removed (peer: %s, handle: %#.4x)",
298            bt_str(self->peer_id_),
299            service_handle);
300     auto svc_iter = self->services_.find(service_handle);
301     if (svc_iter == self->services_.end()) {
302       bt_log(TRACE,
303              "fidl",
304              "ignoring service removed callback for already removed service "
305              "(peer: %s, handle: "
306              "%#.4x)",
307              bt_str(self->peer_id_),
308              service_handle);
309       return;
310     }
311     svc_iter->second->Close(ZX_ERR_CONNECTION_RESET);
312     self->services_.erase(svc_iter);
313   };
314 
315   // The only reason RemoteService::AddRemovedHandler() can fail is if the
316   // service is already shut down, but that should not be possible in this
317   // synchronous callback (the service would not have been returned in the first
318   // place).
319   PW_CHECK(service->AddRemovedHandler(std::move(removed_handler)),
320            "adding service removed handler failed (service may be shut "
321            "down) (peer: %s, "
322            "handle: %#.4x)",
323            bt_str(peer_id_),
324            service_handle);
325 
326   std::unique_ptr<Gatt2RemoteServiceServer> remote_service_server =
327       std::make_unique<Gatt2RemoteServiceServer>(
328           std::move(service), gatt(), peer_id_, std::move(request));
329 
330   // Even if there is already an error, this handler won't be called until the
331   // next yield to the event loop.
332   remote_service_server->set_error_handler(
333       [self, service_handle](zx_status_t status) {
334         bt_log(TRACE,
335                "fidl",
336                "FIDL channel error (peer: %s, handle: %#.4x)",
337                bt_str(self->peer_id_),
338                service_handle);
339         self->services_.erase(service_handle);
340       });
341 
342   // Error handler should not have been called yet.
343   PW_CHECK(services_.count(service_handle) == 1);
344   services_[service_handle] = std::move(remote_service_server);
345 }
346 
347 }  // namespace bthost
348