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/gatt_server_server.h"
16
17 #include <pw_assert/check.h>
18
19 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/uuid.h"
22 #include "pw_bluetooth_sapphire/internal/host/gap/low_energy_connection_manager.h"
23 #include "pw_bluetooth_sapphire/internal/host/gatt/connection.h"
24 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
25 #include "pw_bluetooth_sapphire/internal/host/gatt/server.h"
26
27 using fuchsia::bluetooth::ErrorCode;
28 using fuchsia::bluetooth::Status;
29 using GattErrorCode = fuchsia::bluetooth::gatt::ErrorCode;
30
31 using fuchsia::bluetooth::gatt::Characteristic;
32 using fuchsia::bluetooth::gatt::Descriptor;
33 using fuchsia::bluetooth::gatt::LocalService;
34 using fuchsia::bluetooth::gatt::LocalServiceDelegate;
35 using fuchsia::bluetooth::gatt::LocalServiceDelegatePtr;
36 using fuchsia::bluetooth::gatt::SecurityRequirementsPtr;
37 using fuchsia::bluetooth::gatt::ServiceInfo;
38
39 namespace bthost {
40 namespace {
41
GattStatusFromFidl(GattErrorCode error_code,bool is_read)42 fit::result<bt::att::ErrorCode> GattStatusFromFidl(GattErrorCode error_code,
43 bool is_read) {
44 switch (error_code) {
45 case GattErrorCode::NO_ERROR:
46 return fit::ok();
47 case GattErrorCode::INVALID_OFFSET:
48 return fit::error(bt::att::ErrorCode::kInvalidOffset);
49 case GattErrorCode::INVALID_VALUE_LENGTH:
50 return fit::error(bt::att::ErrorCode::kInvalidAttributeValueLength);
51 case GattErrorCode::NOT_PERMITTED:
52 if (is_read)
53 return fit::error(bt::att::ErrorCode::kReadNotPermitted);
54 return fit::error(bt::att::ErrorCode::kWriteNotPermitted);
55 default:
56 break;
57 }
58 return fit::error(bt::att::ErrorCode::kUnlikelyError);
59 }
60
ParseSecurityRequirements(const SecurityRequirementsPtr & reqs)61 bt::att::AccessRequirements ParseSecurityRequirements(
62 const SecurityRequirementsPtr& reqs) {
63 if (!reqs) {
64 return bt::att::AccessRequirements();
65 }
66 return bt::att::AccessRequirements(reqs->encryption_required,
67 reqs->authentication_required,
68 reqs->authorization_required);
69 }
70
71 // Carries a either a successful Result or an error message that can be sent as
72 // a FIDL response.
73 template <typename Result,
74 typename Error = std::string,
75 typename = std::enable_if_t<!std::is_same<Result, Error>::value>>
76 struct MaybeResult final {
MaybeResultbthost::__anon6e6ebf1e0111::MaybeResult77 explicit MaybeResult(Result&& result)
78 : result(std::forward<Result>(result)) {}
79
MaybeResultbthost::__anon6e6ebf1e0111::MaybeResult80 explicit MaybeResult(Error&& error) : error(std::forward<Error>(error)) {}
81
is_errorbthost::__anon6e6ebf1e0111::MaybeResult82 bool is_error() const { return static_cast<bool>(!result); }
83
84 Result result;
85 Error error;
86 };
87
88 using DescriptorResult = MaybeResult<bt::gatt::DescriptorPtr>;
NewDescriptor(const Descriptor & fidl_desc)89 DescriptorResult NewDescriptor(const Descriptor& fidl_desc) {
90 auto read_reqs = ParseSecurityRequirements(fidl_desc.permissions->read);
91 auto write_reqs = ParseSecurityRequirements(fidl_desc.permissions->write);
92
93 bt::UUID type;
94 if (!bt::StringToUuid(fidl_desc.type, &type)) {
95 return DescriptorResult("Invalid descriptor UUID");
96 }
97
98 return DescriptorResult(std::make_unique<bt::gatt::Descriptor>(
99 fidl_desc.id, type, read_reqs, write_reqs));
100 }
101
102 using CharacteristicResult = MaybeResult<bt::gatt::CharacteristicPtr>;
NewCharacteristic(const Characteristic & fidl_chrc)103 CharacteristicResult NewCharacteristic(const Characteristic& fidl_chrc) {
104 uint8_t props = fidl_chrc.properties & 0xFF;
105 uint16_t ext_props = (fidl_chrc.properties & 0xFF00) >> 8;
106
107 if (!fidl_chrc.permissions) {
108 return CharacteristicResult("Characteristic permissions missing");
109 }
110
111 bool supports_update = (props & bt::gatt::Property::kNotify) ||
112 (props & bt::gatt::Property::kIndicate);
113 if (supports_update != static_cast<bool>(fidl_chrc.permissions->update)) {
114 return CharacteristicResult(
115 supports_update ? "Characteristic update permission required"
116 : "Characteristic update permission must be null");
117 }
118
119 auto read_reqs = ParseSecurityRequirements(fidl_chrc.permissions->read);
120 auto write_reqs = ParseSecurityRequirements(fidl_chrc.permissions->write);
121 auto update_reqs = ParseSecurityRequirements(fidl_chrc.permissions->update);
122
123 bt::UUID type;
124 if (!bt::StringToUuid(fidl_chrc.type, &type)) {
125 return CharacteristicResult("Invalid characteristic UUID");
126 }
127
128 auto chrc = std::make_unique<bt::gatt::Characteristic>(
129 fidl_chrc.id, type, props, ext_props, read_reqs, write_reqs, update_reqs);
130 if (fidl_chrc.descriptors && !fidl_chrc.descriptors->empty()) {
131 for (const auto& fidl_desc : *fidl_chrc.descriptors) {
132 auto desc_result = NewDescriptor(fidl_desc);
133 if (desc_result.is_error()) {
134 return CharacteristicResult(std::move(desc_result.error));
135 }
136
137 chrc->AddDescriptor(std::move(desc_result.result));
138 }
139 }
140
141 return CharacteristicResult(std::move(chrc));
142 }
143
144 } // namespace
145
146 // Implements the gatt::LocalService FIDL interface. Instances of this class are
147 // only created by a GattServerServer.
148 class GattServerServer::LocalServiceImpl
149 : public GattServerBase<fuchsia::bluetooth::gatt::LocalService> {
150 public:
LocalServiceImpl(GattServerServer * owner,uint64_t id,LocalServiceDelegatePtr delegate,::fidl::InterfaceRequest<LocalService> request)151 LocalServiceImpl(GattServerServer* owner,
152 uint64_t id,
153 LocalServiceDelegatePtr delegate,
154 ::fidl::InterfaceRequest<LocalService> request)
155 : GattServerBase(owner->gatt(), this, std::move(request)),
156 owner_(owner),
157 id_(id),
158 delegate_(std::move(delegate)) {
159 PW_DCHECK(owner_);
160 PW_DCHECK(delegate_);
161 }
162
163 // The destructor removes the GATT service
~LocalServiceImpl()164 ~LocalServiceImpl() override {
165 CleanUp();
166
167 // Do not notify the owner in this case. If we got here it means that
168 // |owner_| deleted us.
169 }
170
171 // Returns the current delegate. Returns nullptr if the delegate was
172 // disconnected (e.g. due to a call to RemoveService()).
delegate()173 LocalServiceDelegate* delegate() { return delegate_.get(); }
174
175 private:
176 // fuchsia::bluetooth::gatt::Service overrides:
RemoveService()177 void RemoveService() override {
178 CleanUp();
179 owner_->RemoveService(id_);
180 }
181
NotifyValue(uint64_t characteristic_id,::std::string peer_id,::std::vector<uint8_t> value,bool confirm)182 void NotifyValue(uint64_t characteristic_id,
183 ::std::string peer_id,
184 ::std::vector<uint8_t> value,
185 bool confirm) override {
186 auto id = fidl_helpers::PeerIdFromString(std::move(peer_id));
187 if (id) {
188 bt::gatt::IndicationCallback indication_cb = nullptr;
189 if (confirm) {
190 indication_cb = [](bt::att::Result<> result) {
191 bt_log(DEBUG, "fidl", "indication result: %s", bt_str(result));
192 };
193 }
194 gatt()->SendUpdate(id_,
195 characteristic_id,
196 *id,
197 std::move(value),
198 std::move(indication_cb));
199 }
200 }
201
202 // Unregisters the underlying service if it is still active.
CleanUp()203 void CleanUp() {
204 delegate_ = nullptr; // Closes the delegate handle.
205 gatt()->UnregisterService(id_);
206 }
207
208 // |owner_| owns this instance and is expected to outlive it.
209 GattServerServer* owner_; // weak
210 uint64_t id_;
211
212 // The delegate connection for the corresponding service instance. This gets
213 // cleared when the service is unregistered (via RemoveService() or
214 // destruction).
215 LocalServiceDelegatePtr delegate_;
216
217 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LocalServiceImpl);
218 };
219
GattServerServer(bt::gatt::GATT::WeakPtr gatt,fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Server> request)220 GattServerServer::GattServerServer(
221 bt::gatt::GATT::WeakPtr gatt,
222 fidl::InterfaceRequest<fuchsia::bluetooth::gatt::Server> request)
223 : GattServerBase(gatt, this, std::move(request)), weak_self_(this) {}
224
~GattServerServer()225 GattServerServer::~GattServerServer() {
226 // This will remove all of our services from their adapter.
227 services_.clear();
228 }
229
RemoveService(uint64_t id)230 void GattServerServer::RemoveService(uint64_t id) {
231 if (services_.erase(id)) {
232 bt_log(DEBUG, "fidl", "%s: service removed (id: %lu)", __FUNCTION__, id);
233 } else {
234 bt_log(WARN, "fidl", "%s: service id not found: %lu", __FUNCTION__, id);
235 }
236 }
237
PublishService(ServiceInfo service_info,fidl::InterfaceHandle<LocalServiceDelegate> delegate,fidl::InterfaceRequest<LocalService> service_iface,PublishServiceCallback callback)238 void GattServerServer::PublishService(
239 ServiceInfo service_info,
240 fidl::InterfaceHandle<LocalServiceDelegate> delegate,
241 fidl::InterfaceRequest<LocalService> service_iface,
242 PublishServiceCallback callback) {
243 if (!delegate) {
244 bt_log(WARN, "fidl", "%s: missing service delegate", __FUNCTION__);
245 auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
246 "A delegate is required");
247 callback(std::move(error));
248 return;
249 }
250
251 if (!service_iface) {
252 bt_log(WARN, "fidl", "%s: missing service interface request", __FUNCTION__);
253 auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
254 "Service interface is required");
255 callback(std::move(error));
256 return;
257 }
258
259 bt::UUID service_type;
260 if (!bt::StringToUuid(service_info.type, &service_type)) {
261 bt_log(WARN,
262 "fidl",
263 "%s: invalid service UUID %s",
264 __FUNCTION__,
265 service_info.type.c_str());
266 auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
267 "Invalid service UUID");
268 callback(std::move(error));
269 return;
270 }
271
272 // Process the FIDL service tree.
273 auto service =
274 std::make_unique<bt::gatt::Service>(service_info.primary, service_type);
275 if (service_info.characteristics) {
276 for (const auto& fidl_chrc : *service_info.characteristics) {
277 auto chrc_result = NewCharacteristic(fidl_chrc);
278 if (chrc_result.is_error()) {
279 auto error = fidl_helpers::NewFidlError(ErrorCode::INVALID_ARGUMENTS,
280 chrc_result.error);
281 callback(std::move(error));
282 return;
283 }
284
285 service->AddCharacteristic(std::move(chrc_result.result));
286 }
287 }
288
289 auto self = weak_self_.GetWeakPtr();
290
291 // Set up event handlers.
292 auto read_handler = [self](bt::PeerId /*ignore*/,
293 auto svc_id,
294 auto id,
295 auto offset,
296 auto responder) mutable {
297 if (self.is_alive()) {
298 self->OnReadRequest(svc_id, id, offset, std::move(responder));
299 } else {
300 responder(fit::error(bt::att::ErrorCode::kUnlikelyError),
301 bt::BufferView());
302 }
303 };
304 auto write_handler = [self](bt::PeerId /*ignore*/,
305 auto svc_id,
306 auto id,
307 auto offset,
308 const auto& value,
309 auto responder) mutable {
310 if (self.is_alive()) {
311 self->OnWriteRequest(svc_id, id, offset, value, std::move(responder));
312 } else {
313 responder(fit::error(bt::att::ErrorCode::kUnlikelyError));
314 }
315 };
316 auto ccc_callback = [self](auto svc_id,
317 auto id,
318 bt::gatt::PeerId peer_id,
319 bool notify,
320 bool indicate) {
321 if (self.is_alive())
322 self->OnCharacteristicConfig(svc_id, id, peer_id, notify, indicate);
323 };
324
325 auto id_cb = [self,
326 delegate = std::move(delegate),
327 service_iface = std::move(service_iface),
328 callback = std::move(callback)](bt::gatt::IdType id) mutable {
329 if (!self.is_alive())
330 return;
331
332 if (!id) {
333 // TODO(armansito): Report a more detailed string if registration
334 // fails due to duplicate ids.
335 auto error = fidl_helpers::NewFidlError(ErrorCode::FAILED,
336 "Failed to publish service");
337 callback(std::move(error));
338 return;
339 }
340
341 PW_DCHECK(self->services_.find(id) == self->services_.end());
342
343 // This will be called if either the delegate or the service connection
344 // closes.
345 auto connection_error_cb = [self, id](zx_status_t status) {
346 bt_log(DEBUG, "bt-host", "removing GATT service (id: %lu)", id);
347 if (self.is_alive())
348 self->RemoveService(id);
349 };
350
351 auto delegate_ptr = delegate.Bind();
352 delegate_ptr.set_error_handler(connection_error_cb);
353
354 auto service_server = std::make_unique<LocalServiceImpl>(
355 &self.get(), id, std::move(delegate_ptr), std::move(service_iface));
356 service_server->set_error_handler(connection_error_cb);
357 self->services_[id] = std::move(service_server);
358
359 callback(Status());
360 };
361
362 gatt()->RegisterService(std::move(service),
363 std::move(id_cb),
364 std::move(read_handler),
365 std::move(write_handler),
366 std::move(ccc_callback));
367 }
368
OnReadRequest(bt::gatt::IdType service_id,bt::gatt::IdType id,uint16_t offset,bt::gatt::ReadResponder responder)369 void GattServerServer::OnReadRequest(bt::gatt::IdType service_id,
370 bt::gatt::IdType id,
371 uint16_t offset,
372 bt::gatt::ReadResponder responder) {
373 auto iter = services_.find(service_id);
374 if (iter == services_.end()) {
375 bt_log(
376 WARN, "fidl", "%s: unknown service id %lu", __FUNCTION__, service_id);
377 responder(fit::error(bt::att::ErrorCode::kUnlikelyError), bt::BufferView());
378 return;
379 }
380
381 auto cb = [responder = std::move(responder)](
382 fidl::VectorPtr<uint8_t> optional_value,
383 auto error_code) mutable {
384 std::vector<uint8_t> value;
385 if (optional_value.has_value()) {
386 value = std::move(optional_value.value());
387 }
388 responder(GattStatusFromFidl(error_code, /*is_read=*/true),
389 bt::BufferView(value.data(), value.size()));
390 };
391
392 auto* delegate = iter->second->delegate();
393 PW_DCHECK(delegate);
394 delegate->OnReadValue(id, offset, std::move(cb));
395 }
396
OnWriteRequest(bt::gatt::IdType service_id,bt::gatt::IdType id,uint16_t offset,const bt::ByteBuffer & value,bt::gatt::WriteResponder responder)397 void GattServerServer::OnWriteRequest(bt::gatt::IdType service_id,
398 bt::gatt::IdType id,
399 uint16_t offset,
400 const bt::ByteBuffer& value,
401 bt::gatt::WriteResponder responder) {
402 auto iter = services_.find(service_id);
403 if (iter == services_.end()) {
404 bt_log(
405 WARN, "fidl", "%s: unknown service id %lu", __FUNCTION__, service_id);
406 responder(fit::error(bt::att::ErrorCode::kUnlikelyError));
407 return;
408 }
409
410 auto fidl_value = fidl::To<std::vector<uint8_t>>(value);
411 auto* delegate = iter->second->delegate();
412 PW_DCHECK(delegate);
413
414 if (!responder) {
415 delegate->OnWriteWithoutResponse(id, offset, std::move(fidl_value));
416 return;
417 }
418
419 auto cb = [responder = std::move(responder)](auto error_code) mutable {
420 responder(GattStatusFromFidl(error_code, /*is_read=*/false));
421 };
422
423 delegate->OnWriteValue(id, offset, std::move(fidl_value), std::move(cb));
424 }
425
OnCharacteristicConfig(bt::gatt::IdType service_id,bt::gatt::IdType chrc_id,bt::gatt::PeerId peer_id,bool notify,bool indicate)426 void GattServerServer::OnCharacteristicConfig(bt::gatt::IdType service_id,
427 bt::gatt::IdType chrc_id,
428 bt::gatt::PeerId peer_id,
429 bool notify,
430 bool indicate) {
431 auto iter = services_.find(service_id);
432 if (iter == services_.end()) {
433 bt_log(
434 WARN, "fidl", "%s: unknown service id %lu", __FUNCTION__, service_id);
435 return;
436 }
437
438 auto* delegate = iter->second->delegate();
439 PW_DCHECK(delegate);
440 delegate->OnCharacteristicConfiguration(
441 chrc_id, peer_id.ToString(), notify, indicate);
442 }
443
444 } // namespace bthost
445