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_remote_service_server.h"
16
17 #include <pw_assert/check.h>
18
19 #include <utility>
20
21 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/helpers.h"
22 #include "pw_bluetooth_sapphire/fuchsia/host/fidl/measure_tape/hlcpp_measure_tape_for_read_by_type_result.h"
23 #include "pw_bluetooth_sapphire/internal/host/att/att.h"
24 #include "pw_bluetooth_sapphire/internal/host/common/identifier.h"
25
26 namespace fbg = fuchsia::bluetooth::gatt2;
27 namespace measure_fbg = measure_tape::fuchsia::bluetooth::gatt2;
28
29 namespace bthost {
30 namespace {
31
MakeStatusCallback(bt::PeerId peer_id,const char * request_name,fbg::Handle fidl_handle,fit::function<void (fpromise::result<void,fbg::Error>)> callback)32 bt::att::ResultFunction<> MakeStatusCallback(
33 bt::PeerId peer_id,
34 const char* request_name,
35 fbg::Handle fidl_handle,
36 fit::function<void(fpromise::result<void, fbg::Error>)> callback) {
37 return [peer_id, fidl_handle, callback = std::move(callback), request_name](
38 bt::att::Result<> status) {
39 if (bt_is_error(status,
40 INFO,
41 "fidl",
42 "%s: error (peer: %s, handle: 0x%lX)",
43 request_name,
44 bt_str(peer_id),
45 fidl_handle.value)) {
46 callback(fpromise::error(
47 fidl_helpers::AttErrorToGattFidlError(status.error_value())));
48 return;
49 }
50
51 callback(fpromise::ok());
52 };
53 }
54
CharacteristicToFidl(const bt::gatt::CharacteristicData & characteristic,const std::map<bt::gatt::DescriptorHandle,bt::gatt::DescriptorData> & descriptors)55 fbg::Characteristic CharacteristicToFidl(
56 const bt::gatt::CharacteristicData& characteristic,
57 const std::map<bt::gatt::DescriptorHandle, bt::gatt::DescriptorData>&
58 descriptors) {
59 fbg::Characteristic fidl_char;
60 fidl_char.set_handle(fbg::Handle{characteristic.value_handle});
61 fidl_char.set_type(fuchsia::bluetooth::Uuid{characteristic.type.value()});
62
63 // The FIDL property bitfield combines the properties and extended properties
64 // bits. We mask away the kExtendedProperties property.
65 constexpr uint8_t kRemoveExtendedPropertiesMask = 0x7F;
66 fbg::CharacteristicPropertyBits fidl_properties =
67 static_cast<fbg::CharacteristicPropertyBits>(
68 characteristic.properties & kRemoveExtendedPropertiesMask);
69 if (characteristic.extended_properties) {
70 if (*characteristic.extended_properties &
71 bt::gatt::ExtendedProperty::kReliableWrite) {
72 fidl_properties |= fbg::CharacteristicPropertyBits::RELIABLE_WRITE;
73 }
74 if (*characteristic.extended_properties &
75 bt::gatt::ExtendedProperty::kWritableAuxiliaries) {
76 fidl_properties |= fbg::CharacteristicPropertyBits::WRITABLE_AUXILIARIES;
77 }
78 }
79 fidl_char.set_properties(fidl_properties);
80
81 if (!descriptors.empty()) {
82 std::vector<fbg::Descriptor> fidl_descriptors;
83 for (const auto& [handle, data] : descriptors) {
84 fbg::Descriptor fidl_descriptor;
85 fidl_descriptor.set_handle(fbg::Handle{handle.value});
86 fidl_descriptor.set_type(fuchsia::bluetooth::Uuid{data.type.value()});
87 fidl_descriptors.push_back(std::move(fidl_descriptor));
88 }
89 fidl_char.set_descriptors(std::move(fidl_descriptors));
90 }
91
92 return fidl_char;
93 }
94
95 // Returned result is supposed to match Read{Characteristic, Descriptor}Callback
96 // (result type is converted by FIDL move constructor).
97 [[nodiscard]] fpromise::result<::fuchsia::bluetooth::gatt2::ReadValue,
98 ::fuchsia::bluetooth::gatt2::Error>
ReadResultToFidl(bt::PeerId peer_id,fbg::Handle handle,bt::att::Result<> status,const bt::ByteBuffer & value,bool maybe_truncated,const char * request)99 ReadResultToFidl(bt::PeerId peer_id,
100 fbg::Handle handle,
101 bt::att::Result<> status,
102 const bt::ByteBuffer& value,
103 bool maybe_truncated,
104 const char* request) {
105 if (bt_is_error(status,
106 INFO,
107 "fidl",
108 "%s: error (peer: %s, handle: 0x%lX)",
109 request,
110 bt_str(peer_id),
111 handle.value)) {
112 return fpromise::error(
113 fidl_helpers::AttErrorToGattFidlError(status.error_value()));
114 }
115
116 fbg::ReadValue fidl_value;
117 fidl_value.set_handle(handle);
118 fidl_value.set_value(value.ToVector());
119 fidl_value.set_maybe_truncated(maybe_truncated);
120 return fpromise::ok(std::move(fidl_value));
121 }
122
FillInReadOptionsDefaults(fbg::ReadOptions & options)123 void FillInReadOptionsDefaults(fbg::ReadOptions& options) {
124 if (options.is_short_read()) {
125 return;
126 }
127 if (!options.long_read().has_offset()) {
128 options.long_read().set_offset(0);
129 }
130 if (!options.long_read().has_max_bytes()) {
131 options.long_read().set_max_bytes(fbg::MAX_VALUE_LENGTH);
132 }
133 }
134
FillInDefaultWriteOptions(fbg::WriteOptions & options)135 void FillInDefaultWriteOptions(fbg::WriteOptions& options) {
136 if (!options.has_write_mode()) {
137 *options.mutable_write_mode() = fbg::WriteMode::DEFAULT;
138 }
139 if (!options.has_offset()) {
140 *options.mutable_offset() = 0;
141 }
142 }
143
ReliableModeFromFidl(const fbg::WriteMode & mode)144 bt::gatt::ReliableMode ReliableModeFromFidl(const fbg::WriteMode& mode) {
145 return mode == fbg::WriteMode::RELIABLE ? bt::gatt::ReliableMode::kEnabled
146 : bt::gatt::ReliableMode::kDisabled;
147 }
148
149 } // namespace
150
Gatt2RemoteServiceServer(bt::gatt::RemoteService::WeakPtr service,bt::gatt::GATT::WeakPtr gatt,bt::PeerId peer_id,fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::RemoteService> request)151 Gatt2RemoteServiceServer::Gatt2RemoteServiceServer(
152 bt::gatt::RemoteService::WeakPtr service,
153 bt::gatt::GATT::WeakPtr gatt,
154 bt::PeerId peer_id,
155 fidl::InterfaceRequest<fuchsia::bluetooth::gatt2::RemoteService> request)
156 : GattServerBase(std::move(gatt), this, std::move(request)),
157 service_(std::move(service)),
158 peer_id_(peer_id),
159 weak_self_(this) {}
160
~Gatt2RemoteServiceServer()161 Gatt2RemoteServiceServer::~Gatt2RemoteServiceServer() {
162 // Disable all notifications to prevent leaks.
163 for (auto& [_, notifier] : characteristic_notifiers_) {
164 service_->DisableNotifications(notifier.characteristic_handle,
165 notifier.handler_id,
166 /*status_callback=*/[](auto /*status*/) {});
167 }
168 characteristic_notifiers_.clear();
169 }
170
Close(zx_status_t status)171 void Gatt2RemoteServiceServer::Close(zx_status_t status) {
172 binding()->Close(status);
173 }
174
DiscoverCharacteristics(DiscoverCharacteristicsCallback callback)175 void Gatt2RemoteServiceServer::DiscoverCharacteristics(
176 DiscoverCharacteristicsCallback callback) {
177 auto res_cb = [callback = std::move(callback)](
178 bt::att::Result<> status,
179 const bt::gatt::CharacteristicMap& characteristics) {
180 if (status.is_error()) {
181 callback({});
182 return;
183 }
184
185 std::vector<fbg::Characteristic> fidl_characteristics;
186 for (const auto& [_, characteristic] : characteristics) {
187 const auto& [data, descriptors] = characteristic;
188 fidl_characteristics.push_back(CharacteristicToFidl(data, descriptors));
189 }
190 callback(std::move(fidl_characteristics));
191 };
192
193 service_->DiscoverCharacteristics(std::move(res_cb));
194 }
195
ReadByType(::fuchsia::bluetooth::Uuid uuid,ReadByTypeCallback callback)196 void Gatt2RemoteServiceServer::ReadByType(::fuchsia::bluetooth::Uuid uuid,
197 ReadByTypeCallback callback) {
198 service_->ReadByType(
199 fidl_helpers::UuidFromFidl(uuid),
200 [self = weak_self_.GetWeakPtr(),
201 cb = std::move(callback),
202 func = __FUNCTION__](
203 bt::att::Result<> status,
204 std::vector<bt::gatt::RemoteService::ReadByTypeResult> results) {
205 if (!self.is_alive()) {
206 return;
207 }
208
209 if (status == ToResult(bt::HostError::kInvalidParameters)) {
210 bt_log(WARN,
211 "fidl",
212 "%s: called with invalid parameters (peer: %s)",
213 func,
214 bt_str(self->peer_id_));
215 cb(fpromise::error(fbg::Error::INVALID_PARAMETERS));
216 return;
217 } else if (status.is_error()) {
218 cb(fpromise::error(fbg::Error::UNLIKELY_ERROR));
219 return;
220 }
221
222 const size_t kVectorOverhead =
223 sizeof(fidl_message_header_t) + sizeof(fidl_vector_t);
224 const size_t kMaxBytes = ZX_CHANNEL_MAX_MSG_BYTES - kVectorOverhead;
225 size_t bytes_used = 0;
226
227 std::vector<fuchsia::bluetooth::gatt2::ReadByTypeResult> fidl_results;
228 fidl_results.reserve(results.size());
229
230 for (const bt::gatt::RemoteService::ReadByTypeResult& result :
231 results) {
232 fuchsia::bluetooth::gatt2::ReadByTypeResult fidl_result;
233 fidl_result.set_handle(fbg::Handle{result.handle.value});
234 if (result.result.is_ok()) {
235 fbg::ReadValue read_value;
236 read_value.set_handle(fbg::Handle{result.handle.value});
237 read_value.set_value(result.result.value()->ToVector());
238 read_value.set_maybe_truncated(result.maybe_truncated);
239 fidl_result.set_value(std::move(read_value));
240 } else {
241 fidl_result.set_error(fidl_helpers::AttErrorToGattFidlError(
242 bt::att::Error(result.result.error_value())));
243 }
244
245 measure_fbg::Size result_size = measure_fbg::Measure(fidl_result);
246 PW_CHECK(result_size.num_handles == 0);
247 bytes_used += result_size.num_bytes;
248
249 if (bytes_used > kMaxBytes) {
250 cb(fpromise::error(
251 fuchsia::bluetooth::gatt2::Error::TOO_MANY_RESULTS));
252 return;
253 }
254
255 fidl_results.push_back(std::move(fidl_result));
256 }
257
258 cb(fpromise::ok(std::move(fidl_results)));
259 });
260 }
261
ReadCharacteristic(fbg::Handle fidl_handle,fbg::ReadOptions options,ReadCharacteristicCallback callback)262 void Gatt2RemoteServiceServer::ReadCharacteristic(
263 fbg::Handle fidl_handle,
264 fbg::ReadOptions options,
265 ReadCharacteristicCallback callback) {
266 if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
267 callback(fpromise::error(fbg::Error::INVALID_HANDLE));
268 return;
269 }
270 bt::gatt::CharacteristicHandle handle(
271 static_cast<bt::att::Handle>(fidl_handle.value));
272
273 FillInReadOptionsDefaults(options);
274
275 const char* kRequestName = __FUNCTION__;
276 bt::gatt::RemoteService::ReadValueCallback read_cb =
277 [peer_id = peer_id_,
278 fidl_handle,
279 kRequestName,
280 callback = std::move(callback)](bt::att::Result<> status,
281 const bt::ByteBuffer& value,
282 bool maybe_truncated) {
283 callback(ReadResultToFidl(peer_id,
284 fidl_handle,
285 status,
286 value,
287 maybe_truncated,
288 kRequestName));
289 };
290
291 if (options.is_short_read()) {
292 service_->ReadCharacteristic(handle, std::move(read_cb));
293 return;
294 }
295
296 service_->ReadLongCharacteristic(handle,
297 options.long_read().offset(),
298 options.long_read().max_bytes(),
299 std::move(read_cb));
300 }
301
WriteCharacteristic(fbg::Handle fidl_handle,std::vector<uint8_t> value,fbg::WriteOptions options,WriteCharacteristicCallback callback)302 void Gatt2RemoteServiceServer::WriteCharacteristic(
303 fbg::Handle fidl_handle,
304 std::vector<uint8_t> value,
305 fbg::WriteOptions options,
306 WriteCharacteristicCallback callback) {
307 if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
308 callback(fpromise::error(fbg::Error::INVALID_HANDLE));
309 return;
310 }
311 bt::gatt::CharacteristicHandle handle(
312 static_cast<bt::att::Handle>(fidl_handle.value));
313
314 FillInDefaultWriteOptions(options);
315
316 bt::att::ResultFunction<> write_cb = MakeStatusCallback(
317 peer_id_, __FUNCTION__, fidl_handle, std::move(callback));
318
319 if (options.write_mode() == fbg::WriteMode::WITHOUT_RESPONSE) {
320 if (options.offset() != 0) {
321 write_cb(bt::ToResult(bt::HostError::kInvalidParameters));
322 return;
323 }
324 service_->WriteCharacteristicWithoutResponse(
325 handle, std::move(value), std::move(write_cb));
326 return;
327 }
328
329 const uint16_t kMaxShortWriteValueLength =
330 service_->att_mtu() - sizeof(bt::att::OpCode) -
331 sizeof(bt::att::WriteRequestParams);
332 if (options.offset() == 0 &&
333 options.write_mode() == fbg::WriteMode::DEFAULT &&
334 value.size() <= kMaxShortWriteValueLength) {
335 service_->WriteCharacteristic(
336 handle, std::move(value), std::move(write_cb));
337 return;
338 }
339
340 service_->WriteLongCharacteristic(handle,
341 options.offset(),
342 std::move(value),
343 ReliableModeFromFidl(options.write_mode()),
344 std::move(write_cb));
345 }
346
ReadDescriptor(::fuchsia::bluetooth::gatt2::Handle fidl_handle,::fuchsia::bluetooth::gatt2::ReadOptions options,ReadDescriptorCallback callback)347 void Gatt2RemoteServiceServer::ReadDescriptor(
348 ::fuchsia::bluetooth::gatt2::Handle fidl_handle,
349 ::fuchsia::bluetooth::gatt2::ReadOptions options,
350 ReadDescriptorCallback callback) {
351 if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
352 callback(fpromise::error(fbg::Error::INVALID_HANDLE));
353 return;
354 }
355 bt::gatt::DescriptorHandle handle(
356 static_cast<bt::att::Handle>(fidl_handle.value));
357
358 FillInReadOptionsDefaults(options);
359
360 const char* kRequestName = __FUNCTION__;
361 bt::gatt::RemoteService::ReadValueCallback read_cb =
362 [peer_id = peer_id_,
363 fidl_handle,
364 kRequestName,
365 callback = std::move(callback)](bt::att::Result<> status,
366 const bt::ByteBuffer& value,
367 bool maybe_truncated) {
368 callback(ReadResultToFidl(peer_id,
369 fidl_handle,
370 status,
371 value,
372 maybe_truncated,
373 kRequestName));
374 };
375
376 if (options.is_short_read()) {
377 service_->ReadDescriptor(handle, std::move(read_cb));
378 return;
379 }
380
381 service_->ReadLongDescriptor(handle,
382 options.long_read().offset(),
383 options.long_read().max_bytes(),
384 std::move(read_cb));
385 }
386
WriteDescriptor(fbg::Handle fidl_handle,std::vector<uint8_t> value,fbg::WriteOptions options,WriteDescriptorCallback callback)387 void Gatt2RemoteServiceServer::WriteDescriptor(
388 fbg::Handle fidl_handle,
389 std::vector<uint8_t> value,
390 fbg::WriteOptions options,
391 WriteDescriptorCallback callback) {
392 if (!fidl_helpers::IsFidlGattHandleValid(fidl_handle)) {
393 callback(fpromise::error(fbg::Error::INVALID_HANDLE));
394 return;
395 }
396 bt::gatt::DescriptorHandle handle(
397 static_cast<bt::att::Handle>(fidl_handle.value));
398
399 FillInDefaultWriteOptions(options);
400
401 bt::att::ResultFunction<> write_cb = MakeStatusCallback(
402 peer_id_, __FUNCTION__, fidl_handle, std::move(callback));
403
404 // WITHOUT_RESPONSE and RELIABLE write modes are not supported for
405 // descriptors.
406 if (options.write_mode() == fbg::WriteMode::WITHOUT_RESPONSE ||
407 options.write_mode() == fbg::WriteMode::RELIABLE) {
408 write_cb(bt::ToResult(bt::HostError::kInvalidParameters));
409 return;
410 }
411
412 const uint16_t kMaxShortWriteValueLength =
413 service_->att_mtu() - sizeof(bt::att::OpCode) -
414 sizeof(bt::att::WriteRequestParams);
415 if (options.offset() == 0 && value.size() <= kMaxShortWriteValueLength) {
416 service_->WriteDescriptor(handle, std::move(value), std::move(write_cb));
417 return;
418 }
419
420 service_->WriteLongDescriptor(
421 handle, options.offset(), std::move(value), std::move(write_cb));
422 }
423
RegisterCharacteristicNotifier(fbg::Handle fidl_handle,fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle,RegisterCharacteristicNotifierCallback callback)424 void Gatt2RemoteServiceServer::RegisterCharacteristicNotifier(
425 fbg::Handle fidl_handle,
426 fidl::InterfaceHandle<fbg::CharacteristicNotifier> notifier_handle,
427 RegisterCharacteristicNotifierCallback callback) {
428 bt::gatt::CharacteristicHandle char_handle(
429 static_cast<bt::att::Handle>(fidl_handle.value));
430 NotifierId notifier_id = next_notifier_id_++;
431 auto self = weak_self_.GetWeakPtr();
432
433 auto value_cb = [self, notifier_id, fidl_handle](const bt::ByteBuffer& value,
434 bool maybe_truncated) {
435 if (!self.is_alive()) {
436 return;
437 }
438
439 auto notifier_iter = self->characteristic_notifiers_.find(notifier_id);
440 // The lower layers guarantee that the status callback is always invoked
441 // before sending notifications. Notifiers are only removed during
442 // destruction (addressed by previous `self` check) and in the
443 // `DisableNotifications` completion callback in
444 // `OnCharacteristicNotifierError`, so no notifications should be received
445 // after removing a notifier.
446 PW_CHECK(
447 notifier_iter != self->characteristic_notifiers_.end(),
448 "characteristic notification value received after notifier unregistered"
449 "(peer: %s, characteristic: 0x%lX) ",
450 bt_str(self->peer_id_),
451 fidl_handle.value);
452 CharacteristicNotifier& notifier = notifier_iter->second;
453
454 // The `- 1` is needed because there is one unacked notification that we've
455 // already sent to the client aside from the values in the queue.
456 if (notifier.queued_values.size() == kMaxPendingNotifierValues - 1) {
457 bt_log(WARN,
458 "fidl",
459 "GATT CharacteristicNotifier pending values limit reached, "
460 "closing protocol (peer: "
461 "%s, characteristic: %#.2x)",
462 bt_str(self->peer_id_),
463 notifier.characteristic_handle.value);
464 self->OnCharacteristicNotifierError(
465 notifier_id, notifier.characteristic_handle, notifier.handler_id);
466 return;
467 }
468
469 fbg::ReadValue fidl_value;
470 fidl_value.set_handle(fidl_handle);
471 fidl_value.set_value(value.ToVector());
472 fidl_value.set_maybe_truncated(maybe_truncated);
473
474 bt_log(TRACE,
475 "fidl",
476 "Queueing GATT notification value (characteristic: %#.2x)",
477 notifier.characteristic_handle.value);
478 notifier.queued_values.push(std::move(fidl_value));
479
480 self->MaybeNotifyNextValue(notifier_id);
481 };
482
483 auto status_cb = [self,
484 service = service_,
485 char_handle,
486 notifier_id,
487 notifier_handle = std::move(notifier_handle),
488 callback = std::move(callback)](
489 bt::att::Result<> status,
490 bt::gatt::IdType handler_id) mutable {
491 if (!self.is_alive()) {
492 if (status.is_ok()) {
493 // Disable this handler so it doesn't leak.
494 service->DisableNotifications(
495 char_handle, handler_id, [](auto /*status*/) {
496 // There is no notifier to clean up because the server has been
497 // destroyed.
498 });
499 }
500 return;
501 }
502
503 if (status.is_error()) {
504 callback(fpromise::error(
505 fidl_helpers::AttErrorToGattFidlError(status.error_value())));
506 return;
507 }
508
509 CharacteristicNotifier notifier{.handler_id = handler_id,
510 .characteristic_handle = char_handle,
511 .notifier = notifier_handle.Bind()};
512 auto [notifier_iter, emplaced] = self->characteristic_notifiers_.emplace(
513 notifier_id, std::move(notifier));
514 PW_CHECK(emplaced);
515
516 // When the client closes the protocol, unregister the notifier.
517 notifier_iter->second.notifier.set_error_handler(
518 [self, char_handle, handler_id, notifier_id](auto /*status*/) {
519 self->OnCharacteristicNotifierError(
520 notifier_id, char_handle, handler_id);
521 });
522
523 callback(fpromise::ok());
524 };
525
526 service_->EnableNotifications(
527 char_handle, std::move(value_cb), std::move(status_cb));
528 }
529
MaybeNotifyNextValue(NotifierId notifier_id)530 void Gatt2RemoteServiceServer::MaybeNotifyNextValue(NotifierId notifier_id) {
531 auto notifier_iter = characteristic_notifiers_.find(notifier_id);
532 if (notifier_iter == characteristic_notifiers_.end()) {
533 return;
534 }
535 CharacteristicNotifier& notifier = notifier_iter->second;
536
537 if (notifier.queued_values.empty()) {
538 return;
539 }
540
541 if (!notifier.last_value_ack) {
542 return;
543 }
544 notifier.last_value_ack = false;
545
546 fbg::ReadValue value = std::move(notifier.queued_values.front());
547 notifier.queued_values.pop();
548
549 bt_log(DEBUG,
550 "fidl",
551 "Sending GATT notification value (handle: 0x%lX)",
552 value.handle().value);
553 auto self = weak_self_.GetWeakPtr();
554 notifier.notifier->OnNotification(std::move(value), [self, notifier_id]() {
555 if (!self.is_alive()) {
556 return;
557 }
558
559 auto notifier_iter = self->characteristic_notifiers_.find(notifier_id);
560 if (notifier_iter == self->characteristic_notifiers_.end()) {
561 return;
562 }
563 notifier_iter->second.last_value_ack = true;
564 self->MaybeNotifyNextValue(notifier_id);
565 });
566 }
567
OnCharacteristicNotifierError(NotifierId notifier_id,bt::gatt::CharacteristicHandle char_handle,bt::gatt::IdType handler_id)568 void Gatt2RemoteServiceServer::OnCharacteristicNotifierError(
569 NotifierId notifier_id,
570 bt::gatt::CharacteristicHandle char_handle,
571 bt::gatt::IdType handler_id) {
572 auto self = weak_self_.GetWeakPtr();
573 service_->DisableNotifications(
574 char_handle, handler_id, [self, notifier_id](auto /*status*/) {
575 if (!self.is_alive()) {
576 return;
577 }
578 // Clear the notifier regardless of status. Wait until this callback is
579 // called in order to prevent the value callback from being called for
580 // an erased notifier.
581 self->characteristic_notifiers_.erase(notifier_id);
582 });
583 }
584
585 } // namespace bthost
586