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