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