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