• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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