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