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