xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/fuchsia/host/fidl/gatt2_server_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_server_server.h"
16 
17 #include <functional>
18 #include <utility>
19 
20 #include "fuchsia/bluetooth/cpp/fidl.h"
21 #include "fuchsia/bluetooth/gatt2/cpp/fidl.h"
22 #include "lib/fidl/cpp/interface_ptr.h"
23 #include "lib/fit/function.h"
24 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/gatt2_server_ids.h"
25 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
26 #include "pw_bluetooth_sapphire/internal/host/att/att.h"
27 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
28 #include "pw_bluetooth_sapphire/internal/host/common/uuid.h"
29 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
30 #include "pw_bluetooth_sapphire/internal/host/gatt/server.h"
31 
32 namespace fbt = fuchsia::bluetooth;
33 namespace btg = bt::gatt;
34 
35 using fuchsia::bluetooth::gatt2::AttributePermissions;
36 using fuchsia::bluetooth::gatt2::Characteristic;
37 using fuchsia::bluetooth::gatt2::Descriptor;
38 using fgatt2Err = fuchsia::bluetooth::gatt2::Error;
39 using fuchsia::bluetooth::gatt2::Handle;
40 using fuchsia::bluetooth::gatt2::INITIAL_VALUE_CHANGED_CREDITS;
41 using fuchsia::bluetooth::gatt2::LocalService;
42 using fuchsia::bluetooth::gatt2::LocalService_ReadValue_Result;
43 using fuchsia::bluetooth::gatt2::LocalService_WriteValue_Result;
44 using fuchsia::bluetooth::gatt2::LocalServicePeerUpdateRequest;
45 using fuchsia::bluetooth::gatt2::LocalServiceWriteValueRequest;
46 using fuchsia::bluetooth::gatt2::PublishServiceError;
47 using fuchsia::bluetooth::gatt2::SecurityRequirements;
48 using fuchsia::bluetooth::gatt2::ServiceInfo;
49 using fuchsia::bluetooth::gatt2::ServiceKind;
50 using fuchsia::bluetooth::gatt2::ValueChangedParameters;
51 
52 namespace bthost {
53 
Gatt2ServerServer(btg::GATT::WeakPtr gatt,fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::Server> request)54 Gatt2ServerServer::Gatt2ServerServer(
55     btg::GATT::WeakPtr gatt,
56     fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::Server> request)
57     : GattServerBase(std::move(gatt), this, std::move(request)),
58       weak_self_(this) {}
59 
~Gatt2ServerServer()60 Gatt2ServerServer::~Gatt2ServerServer() {
61   // Remove all services from the local GATT host.
62   for (const auto& iter : services_) {
63     gatt()->UnregisterService(iter.first.value());
64   }
65 }
66 
RemoveService(InternalServiceId id)67 void Gatt2ServerServer::RemoveService(InternalServiceId id) {
68   // Close the connection to the FIDL LocalService.
69   services_.erase(id);
70   // Unregister the service from the local GATT host. Don't remove the ID from
71   // service_id_mapping_ to prevent service ID reuse.
72   gatt()->UnregisterService(id.value());
73 }
74 
PublishService(ServiceInfo info,fidl::InterfaceHandle<LocalService> service,PublishServiceCallback callback)75 void Gatt2ServerServer::PublishService(
76     ServiceInfo info,
77     fidl::InterfaceHandle<LocalService> service,
78     PublishServiceCallback callback) {
79   if (!info.has_handle()) {
80     bt_log(WARN, "fidl", "%s: `info` missing required `handle`", __FUNCTION__);
81     callback(fpromise::error(PublishServiceError::INVALID_SERVICE_HANDLE));
82     return;
83   }
84   if (!info.has_type()) {
85     bt_log(
86         WARN, "fidl", "%s: `info` missing required `type` UUID", __FUNCTION__);
87     callback(fpromise::error(PublishServiceError::INVALID_UUID));
88     return;
89   }
90   if (!info.has_characteristics()) {
91     bt_log(WARN,
92            "fidl",
93            "%s: `info` missing required `characteristics`",
94            __FUNCTION__);
95     callback(fpromise::error(PublishServiceError::INVALID_CHARACTERISTICS));
96     return;
97   }
98 
99   ClientServiceId client_svc_id(info.handle().value);
100   if (service_id_mapping_.count(client_svc_id) != 0) {
101     bt_log(WARN,
102            "fidl",
103            "%s: cannot publish service with already-used `handle`",
104            __FUNCTION__);
105     callback(fpromise::error(PublishServiceError::INVALID_SERVICE_HANDLE));
106     return;
107   }
108 
109   bt::UUID service_type(info.type().value);
110   // The default value for kind is PRIMARY if not present.
111   bool primary = info.has_kind() ? info.kind() == ServiceKind::PRIMARY : true;
112 
113   // Process the FIDL service tree.
114   auto gatt_svc = std::make_unique<btg::Service>(primary, service_type);
115   for (const auto& fidl_chrc : info.characteristics()) {
116     btg::CharacteristicPtr maybe_chrc =
117         fidl_helpers::Gatt2CharacteristicFromFidl(fidl_chrc);
118     if (!maybe_chrc) {
119       callback(fpromise::error(PublishServiceError::INVALID_CHARACTERISTICS));
120       return;
121     }
122 
123     gatt_svc->AddCharacteristic(std::move(maybe_chrc));
124   }
125 
126   auto self = weak_self_.GetWeakPtr();
127   auto id_cb = [self,
128                 service_handle = std::move(service),
129                 client_svc_id,
130                 callback = std::move(callback)](btg::IdType raw_id) mutable {
131     if (!self.is_alive()) {
132       return;
133     }
134 
135     if (raw_id == bt::gatt::kInvalidId) {
136       bt_log(INFO,
137              "bt-host",
138              "internal error publishing service (handle: %lu)",
139              client_svc_id.value());
140       callback(fpromise::error(PublishServiceError::UNLIKELY_ERROR));
141       return;
142     }
143     InternalServiceId internal_id(raw_id);
144     auto error_handler =
145         [self, client_svc_id, internal_id](zx_status_t status) {
146           bt_log(INFO,
147                  "bt-host",
148                  "LocalService shut down, removing GATT service (id: %lu)",
149                  client_svc_id.value());
150           self->RemoveService(internal_id);
151         };
152 
153     fidl::InterfacePtr<LocalService> service_ptr = service_handle.Bind();
154     service_ptr.set_error_handler(error_handler);
155     service_ptr.events().OnSuppressDiscovery = [self, internal_id]() {
156       self->OnSuppressDiscovery(internal_id);
157     };
158     service_ptr.events().OnNotifyValue =
159         [self, internal_id](ValueChangedParameters update) {
160           self->OnNotifyValue(internal_id, std::move(update));
161         };
162     service_ptr.events().OnIndicateValue = [self, internal_id](
163                                                ValueChangedParameters update,
164                                                zx::eventpair confirm) {
165       self->OnIndicateValue(internal_id, std::move(update), std::move(confirm));
166     };
167     self->services_.emplace(internal_id,
168                             Service{.local_svc_ptr = std::move(service_ptr)});
169     self->service_id_mapping_[client_svc_id] = internal_id;
170     callback(fpromise::ok());
171   };
172 
173   gatt()->RegisterService(
174       std::move(gatt_svc),
175       std::move(id_cb),
176       fit::bind_member<&Gatt2ServerServer::OnReadRequest>(this),
177       fit::bind_member<&Gatt2ServerServer::OnWriteRequest>(this),
178       fit::bind_member<&Gatt2ServerServer::OnClientCharacteristicConfiguration>(
179           this));
180 }
181 
OnReadRequest(bt::PeerId peer_id,bt::gatt::IdType service_id,btg::IdType id,uint16_t offset,btg::ReadResponder responder)182 void Gatt2ServerServer::OnReadRequest(bt::PeerId peer_id,
183                                       bt::gatt::IdType service_id,
184                                       btg::IdType id,
185                                       uint16_t offset,
186                                       btg::ReadResponder responder) {
187   auto svc_iter = services_.find(InternalServiceId(service_id));
188   // GATT must only send read requests for registered services.
189   PW_CHECK(svc_iter != services_.end());
190 
191   auto cb = [responder = std::move(responder)](
192                 LocalService_ReadValue_Result res) mutable {
193     if (res.is_err()) {
194       responder(fit::error(fidl_helpers::Gatt2ErrorCodeFromFidl(res.err())),
195                 bt::BufferView());
196       return;
197     }
198     responder(fit::ok(), bt::BufferView(res.response().value));
199   };
200   svc_iter->second.local_svc_ptr->ReadValue(
201       fbt::PeerId{peer_id.value()}, Handle{id}, offset, std::move(cb));
202 }
203 
OnWriteRequest(bt::PeerId peer_id,bt::gatt::IdType service_id,btg::IdType id,uint16_t offset,const bt::ByteBuffer & value,btg::WriteResponder responder)204 void Gatt2ServerServer::OnWriteRequest(bt::PeerId peer_id,
205                                        bt::gatt::IdType service_id,
206                                        btg::IdType id,
207                                        uint16_t offset,
208                                        const bt::ByteBuffer& value,
209                                        btg::WriteResponder responder) {
210   auto svc_iter = services_.find(InternalServiceId(service_id));
211   // GATT must only send write requests for registered services.
212   PW_CHECK(svc_iter != services_.end());
213 
214   auto cb = [responder = std::move(responder)](
215                 LocalService_WriteValue_Result result) mutable {
216     // If this was a Write Without Response request, the response callback will
217     // be null.
218     if (responder) {
219       fit::result<bt::att::ErrorCode> rsp = fit::ok();
220       if (!result.is_response()) {
221         rsp = fit::error(fidl_helpers::Gatt2ErrorCodeFromFidl(result.err()));
222       }
223       responder(rsp);
224     }
225   };
226 
227   LocalServiceWriteValueRequest params;
228   params.set_peer_id(fbt::PeerId{peer_id.value()});
229   params.set_handle(Handle{id});
230   params.set_offset(offset);
231   params.set_value(value.ToVector());
232   svc_iter->second.local_svc_ptr->WriteValue(std::move(params), std::move(cb));
233 }
234 
OnClientCharacteristicConfiguration(bt::gatt::IdType service_id,bt::gatt::IdType chrc_id,bt::PeerId peer_id,bool notify,bool indicate)235 void Gatt2ServerServer::OnClientCharacteristicConfiguration(
236     bt::gatt::IdType service_id,
237     bt::gatt::IdType chrc_id,
238     bt::PeerId peer_id,
239     bool notify,
240     bool indicate) {
241   auto svc_iter = services_.find(InternalServiceId(service_id));
242   // GATT must only send CCC updates for registered services.
243   PW_CHECK(svc_iter != services_.end());
244 
245   auto cb = []() {
246     bt_log(TRACE, "fidl", "characteristic configuration acknowledged");
247   };
248   svc_iter->second.local_svc_ptr->CharacteristicConfiguration(
249       fbt::PeerId{peer_id.value()}, Handle{chrc_id}, notify, indicate, cb);
250 }
251 
OnSuppressDiscovery(InternalServiceId service_id)252 void Gatt2ServerServer::OnSuppressDiscovery(InternalServiceId service_id) {
253   // TODO(fxbug.dev/42180948): This event is not yet supported
254   bt_log(ERROR,
255          "fidl",
256          "%s not supported - see https://fxbug.dev/42180948",
257          __FUNCTION__);
258 }
259 
ValidateValueChangedEvent(InternalServiceId service_id,const fuchsia::bluetooth::gatt2::ValueChangedParameters & update,const char * update_type)260 bool Gatt2ServerServer::ValidateValueChangedEvent(
261     InternalServiceId service_id,
262     const fuchsia::bluetooth::gatt2::ValueChangedParameters& update,
263     const char* update_type) {
264   auto iter = services_.find(service_id);
265   // It is impossible for clients to send events to a closed service.
266   PW_CHECK(iter != services_.end());
267   // Subtract credit before validating parameters so that credits aren't
268   // permanently lost from the client's perspective.
269   SubtractCredit(iter->second);
270 
271   if (!update.has_handle()) {
272     bt_log(WARN, "fidl", "ValueChangedParameters missing required `handle`");
273     return false;
274   }
275   if (!update.has_value()) {
276     bt_log(WARN, "fidl", "ValueChangedParameters missing required `value`");
277     return false;
278   }
279   return true;
280 }
281 
OnNotifyValue(InternalServiceId service_id,fuchsia::bluetooth::gatt2::ValueChangedParameters update)282 void Gatt2ServerServer::OnNotifyValue(
283     InternalServiceId service_id,
284     fuchsia::bluetooth::gatt2::ValueChangedParameters update) {
285   if (!ValidateValueChangedEvent(service_id, update, "notification")) {
286     RemoveService(service_id);
287     return;
288   }
289   bt::gatt::IndicationCallback indicate_cb = nullptr;
290   if (!update.has_peer_ids() || update.peer_ids().empty()) {
291     gatt()->UpdateConnectedPeers(service_id.value(),
292                                  update.handle().value,
293                                  update.value(),
294                                  /*indicate_cb=*/nullptr);
295     return;
296   }
297   for (auto peer_id : update.peer_ids()) {
298     gatt()->SendUpdate(service_id.value(),
299                        update.handle().value,
300                        bt::PeerId(peer_id.value),
301                        update.value(),
302                        /*indicate_cb=*/nullptr);
303   }
304 }
305 
OnIndicateValue(InternalServiceId service_id,fuchsia::bluetooth::gatt2::ValueChangedParameters update,zx::eventpair confirmation)306 void Gatt2ServerServer::OnIndicateValue(
307     InternalServiceId service_id,
308     fuchsia::bluetooth::gatt2::ValueChangedParameters update,
309     zx::eventpair confirmation) {
310   if (!ValidateValueChangedEvent(service_id, update, "indication")) {
311     RemoveService(service_id);
312     return;
313   }
314 
315   if (!update.has_peer_ids() || update.peer_ids().empty()) {
316     auto indicate_cb =
317         [confirm = std::move(confirmation)](bt::att::Result<> status) mutable {
318           if (status.is_error()) {
319             bt_log(WARN, "fidl", "indication failed: %s", bt_str(status));
320             return;
321           }
322           confirm.signal_peer(/*clear_mask=*/0, ZX_EVENTPAIR_SIGNALED);
323         };
324     gatt()->UpdateConnectedPeers(service_id.value(),
325                                  update.handle().value,
326                                  update.value(),
327                                  std::move(indicate_cb));
328     return;
329   }
330 
331   bt::att::ResultFunction<> shared_result_fn =
332       [pending = update.peer_ids().size(),
333        confirm = std::move(confirmation)](bt::att::Result<> res) mutable {
334         if (!confirm.is_valid()) {
335           // An error was already signaled.
336           return;
337         }
338         if (res.is_error()) {
339           bt_log(INFO,
340                  "fidl",
341                  "failed to indicate some peers: %s",
342                  bt_str(res.error_value()));
343           confirm.reset();  // signals ZX_EVENTPAIR_PEER_CLOSED
344           return;
345         }
346         pending--;
347         if (pending == 0) {
348           confirm.signal_peer(/*clear_mask=*/0, ZX_EVENTPAIR_SIGNALED);
349         }
350       };
351 
352   for (auto peer_id : update.peer_ids()) {
353     gatt()->SendUpdate(service_id.value(),
354                        update.handle().value,
355                        bt::PeerId(peer_id.value),
356                        update.value(),
357                        shared_result_fn.share());
358   }
359 }
360 
SubtractCredit(Service & svc)361 void Gatt2ServerServer::SubtractCredit(Service& svc) {
362   // It is impossible for clients to violate the credit system from the server's
363   // perspective because new credits are granted before the count reaches 0
364   // (excessive events will fill the FIDL channel and eventually crash the
365   // client).
366   PW_CHECK(svc.credits > 0);
367   svc.credits--;
368   if (svc.credits <= REFRESH_CREDITS_AT) {
369     // Static cast OK because current_credits > 0 and we never add more than
370     // INITIAL_VALUE_CHANGED_CREDITS.
371     uint8_t credits_to_add =
372         static_cast<uint8_t>(INITIAL_VALUE_CHANGED_CREDITS - svc.credits);
373     svc.local_svc_ptr->ValueChangedCredit(credits_to_add);
374     svc.credits = INITIAL_VALUE_CHANGED_CREDITS;
375   }
376 }
377 
378 }  // namespace bthost
379