xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/gatt/local_service_manager.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 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/internal/host/gatt/local_service_manager.h"
16 
17 #include <pw_bytes/endian.h>
18 
19 #include <algorithm>
20 #include <cinttypes>
21 
22 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
24 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
25 
26 namespace bt::gatt {
27 namespace {
28 
29 // Adds characteristic definition attributes to |grouping| for |chrc|. Returns
30 // the characteristic value handle.
InsertCharacteristicAttributes(att::AttributeGrouping * grouping,const Characteristic & chrc,att::Attribute::ReadHandler read_handler,att::Attribute::WriteHandler write_handler)31 att::Handle InsertCharacteristicAttributes(
32     att::AttributeGrouping* grouping,
33     const Characteristic& chrc,
34     att::Attribute::ReadHandler read_handler,
35     att::Attribute::WriteHandler write_handler) {
36   PW_DCHECK(grouping);
37   PW_DCHECK(!grouping->complete());
38   PW_DCHECK(read_handler);
39   PW_DCHECK(write_handler);
40 
41   // Characteristic Declaration (Vol 3, Part G, 3.3.1).
42   auto* decl_attr = grouping->AddAttribute(
43       types::kCharacteristicDeclaration,
44       att::AccessRequirements(/*encryption=*/false,
45                               /*authentication=*/false,
46                               /*authorization=*/false),  // read (no security)
47       att::AccessRequirements());                        // write (not allowed)
48   PW_DCHECK(decl_attr);
49 
50   // Characteristic Value Declaration (Vol 3, Part G, 3.3.2)
51   auto* value_attr = grouping->AddAttribute(
52       chrc.type(), chrc.read_permissions(), chrc.write_permissions());
53   PW_DCHECK(value_attr);
54 
55   value_attr->set_read_handler(std::move(read_handler));
56   value_attr->set_write_handler(std::move(write_handler));
57 
58   size_t uuid_size = chrc.type().CompactSize(/*allow_32bit=*/false);
59   PW_DCHECK(uuid_size == 2 || uuid_size == 16);
60 
61   // The characteristic declaration value contains:
62   // 1 octet: properties
63   // 2 octets: value handle
64   // 2 or 16 octets: UUID
65   DynamicByteBuffer decl_value(3 + uuid_size);
66   decl_value[0] = chrc.properties();
67   decl_value[1] = static_cast<uint8_t>(value_attr->handle());
68   decl_value[2] = static_cast<uint8_t>(value_attr->handle() >> 8);
69 
70   auto uuid_view = decl_value.mutable_view(3);
71   chrc.type().ToBytes(&uuid_view, /*allow_32bit=*/false);
72   decl_attr->SetValue(decl_value);
73 
74   return value_attr->handle();
75 }
76 
77 // Adds a characteristic descriptor declaration to |grouping| for |desc|.
InsertDescriptorAttribute(att::AttributeGrouping * grouping,const UUID & type,const att::AccessRequirements & read_reqs,const att::AccessRequirements & write_reqs,att::Attribute::ReadHandler read_handler,att::Attribute::WriteHandler write_handler)78 void InsertDescriptorAttribute(att::AttributeGrouping* grouping,
79                                const UUID& type,
80                                const att::AccessRequirements& read_reqs,
81                                const att::AccessRequirements& write_reqs,
82                                att::Attribute::ReadHandler read_handler,
83                                att::Attribute::WriteHandler write_handler) {
84   PW_DCHECK(grouping);
85   PW_DCHECK(!grouping->complete());
86   PW_DCHECK(read_handler);
87   PW_DCHECK(write_handler);
88 
89   // There is no special declaration attribute type for descriptors.
90   auto* attr = grouping->AddAttribute(type, read_reqs, write_reqs);
91   PW_DCHECK(attr);
92 
93   attr->set_read_handler(std::move(read_handler));
94   attr->set_write_handler(std::move(write_handler));
95 }
96 
97 // Returns false if the given service hierarchy contains repeating identifiers.
98 // Returns the number of attributes that will be in the service attribute group
99 // (exluding the service declaration) in |out_attrs|.
ValidateService(const Service & service,size_t * out_attr_count)100 bool ValidateService(const Service& service, size_t* out_attr_count) {
101   PW_DCHECK(out_attr_count);
102 
103   size_t attr_count = 0u;
104   std::unordered_set<IdType> ids;
105   for (const auto& chrc_ptr : service.characteristics()) {
106     if (ids.count(chrc_ptr->id()) != 0u) {
107       bt_log(TRACE, "gatt", "server: repeated ID: %" PRIu64, chrc_ptr->id());
108       return false;
109     }
110 
111     ids.insert(chrc_ptr->id());
112 
113     // +1: Characteristic Declaration (Vol 3, Part G, 3.3.1)
114     // +1: Characteristic Value Declaration (Vol 3, Part G, 3.3.2)
115     attr_count += 2;
116 
117     // Increment the count for the CCC descriptor if the characteristic supports
118     // notifications or indications.
119     if ((chrc_ptr->properties() & Property::kNotify) ||
120         (chrc_ptr->properties() & Property::kIndicate)) {
121       attr_count++;
122     }
123 
124     for (const auto& desc_ptr : chrc_ptr->descriptors()) {
125       if (ids.count(desc_ptr->id()) != 0u) {
126         bt_log(TRACE, "gatt", "server: repeated ID: %" PRIu64, desc_ptr->id());
127         return false;
128       }
129 
130       // Reject descriptors with types that are internally managed by us.
131       if (desc_ptr->type() == types::kClientCharacteristicConfig ||
132           desc_ptr->type() == types::kCharacteristicExtProperties ||
133           desc_ptr->type() == types::kServerCharacteristicConfig) {
134         bt_log(TRACE,
135                "gatt",
136                "server: disallowed descriptor type: %s",
137                desc_ptr->type().ToString().c_str());
138         return false;
139       }
140 
141       ids.insert(desc_ptr->id());
142 
143       // +1: Characteristic Descriptor Declaration (Vol 3, Part G, 3.3.3)
144       attr_count++;
145     }
146     if (chrc_ptr->extended_properties()) {
147       attr_count++;
148     }
149   }
150 
151   *out_attr_count = attr_count;
152 
153   return true;
154 }
155 
156 }  // namespace
157 
158 class LocalServiceManager::ServiceData final {
159  public:
ServiceData(IdType id,att::AttributeGrouping * grouping,Service * service,ReadHandler && read_handler,WriteHandler && write_handler,ClientConfigCallback && ccc_callback)160   ServiceData(IdType id,
161               att::AttributeGrouping* grouping,
162               Service* service,
163               ReadHandler&& read_handler,
164               WriteHandler&& write_handler,
165               ClientConfigCallback&& ccc_callback)
166       : id_(id),
167         read_handler_(std::forward<ReadHandler>(read_handler)),
168         write_handler_(std::forward<WriteHandler>(write_handler)),
169         ccc_callback_(std::forward<ClientConfigCallback>(ccc_callback)),
170         weak_self_(this) {
171     PW_DCHECK(read_handler_);
172     PW_DCHECK(write_handler_);
173     PW_DCHECK(ccc_callback_);
174     PW_DCHECK(grouping);
175 
176     start_handle_ = grouping->start_handle();
177     end_handle_ = grouping->end_handle();
178 
179     // Sort characteristics by UUID size (see Vol 3, Part G, 3.3.1).
180     auto chrcs = service->ReleaseCharacteristics();
181     std::sort(chrcs.begin(),
182               chrcs.end(),
183               [](const auto& chrc_ptr1, const auto& chrc_ptr2) {
184                 return chrc_ptr1->type().CompactSize(/*allow_32bit=*/false) <
185                        chrc_ptr2->type().CompactSize(/*allow_32bit=*/false);
186               });
187     for (auto& chrc : chrcs) {
188       AddCharacteristic(grouping, std::move(chrc));
189     }
190   }
191 
id() const192   inline IdType id() const { return id_; }
start_handle() const193   inline att::Handle start_handle() const { return start_handle_; }
end_handle() const194   inline att::Handle end_handle() const { return end_handle_; }
195 
GetCharacteristicConfig(IdType chrc_id,PeerId peer_id,ClientCharacteristicConfig * out_config)196   bool GetCharacteristicConfig(IdType chrc_id,
197                                PeerId peer_id,
198                                ClientCharacteristicConfig* out_config) {
199     PW_DCHECK(out_config);
200 
201     auto iter = chrc_configs_.find(chrc_id);
202     if (iter == chrc_configs_.end())
203       return false;
204 
205     uint16_t value = iter->second.Get(peer_id);
206     out_config->handle = iter->second.handle();
207     out_config->notify = value & kCCCNotificationBit;
208     out_config->indicate = value & kCCCIndicationBit;
209 
210     return true;
211   }
212 
213   // Clean up our knoweledge of the diconnecting peer.
DisconnectClient(PeerId peer_id)214   void DisconnectClient(PeerId peer_id) {
215     for (auto& id_config_pair : chrc_configs_) {
216       id_config_pair.second.Erase(peer_id);
217     }
218   }
219 
220  private:
221   class CharacteristicConfig {
222    public:
CharacteristicConfig(att::Handle handle)223     explicit CharacteristicConfig(att::Handle handle) : handle_(handle) {}
224     CharacteristicConfig(CharacteristicConfig&&) = default;
225     CharacteristicConfig& operator=(CharacteristicConfig&&) = default;
226 
227     // The characteristic handle.
handle() const228     att::Handle handle() const { return handle_; }
229 
Get(PeerId peer_id)230     uint16_t Get(PeerId peer_id) {
231       auto iter = client_states_.find(peer_id);
232 
233       // If a configuration doesn't exist for |peer_id| then return the default
234       // value.
235       if (iter == client_states_.end())
236         return 0;
237 
238       return iter->second;
239     }
240 
Set(PeerId peer_id,uint16_t value)241     void Set(PeerId peer_id, uint16_t value) {
242       client_states_[peer_id] = value;
243     }
244 
Erase(PeerId peer_id)245     void Erase(PeerId peer_id) { client_states_.erase(peer_id); }
246 
247    private:
248     att::Handle handle_;
249     std::unordered_map<PeerId, uint16_t> client_states_;
250 
251     BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(CharacteristicConfig);
252   };
253 
254   // Called when a read request is performed on a CCC descriptor belonging to
255   // the characteristic identified by |chrc_id|.
OnReadCCC(IdType chrc_id,PeerId peer_id,att::Handle,uint16_t,att::Attribute::ReadResultCallback result_cb)256   void OnReadCCC(IdType chrc_id,
257                  PeerId peer_id,
258                  att::Handle,
259                  uint16_t,
260                  att::Attribute::ReadResultCallback result_cb) {
261     uint16_t value = 0;
262     auto iter = chrc_configs_.find(chrc_id);
263     if (iter != chrc_configs_.end()) {
264       value = iter->second.Get(peer_id);
265     }
266 
267     value = pw::bytes::ConvertOrderTo(cpp20::endian::little, value);
268     result_cb(
269         fit::ok(),
270         BufferView(reinterpret_cast<const uint8_t*>(&value), sizeof(value)));
271   }
272 
273   // Called when a write request is performed on a CCC descriptor belonging to
274   // the characteristic identified by |chrc_id|.
OnWriteCCC(IdType chrc_id,uint8_t chrc_props,PeerId peer_id,att::Handle handle,uint16_t offset,const ByteBuffer & value,att::Attribute::WriteResultCallback result_cb)275   void OnWriteCCC(IdType chrc_id,
276                   uint8_t chrc_props,
277                   PeerId peer_id,
278                   att::Handle handle,
279                   uint16_t offset,
280                   const ByteBuffer& value,
281                   att::Attribute::WriteResultCallback result_cb) {
282     if (offset != 0u) {
283       result_cb(fit::error(att::ErrorCode::kInvalidOffset));
284       return;
285     }
286 
287     if (value.size() != sizeof(uint16_t)) {
288       result_cb(fit::error(att::ErrorCode::kInvalidAttributeValueLength));
289       return;
290     }
291 
292     uint16_t ccc_value = pw::bytes::ConvertOrderFrom(cpp20::endian::little,
293                                                      value.To<uint16_t>());
294     if (ccc_value > (kCCCNotificationBit | kCCCIndicationBit)) {
295       result_cb(fit::error(att::ErrorCode::kInvalidPDU));
296       return;
297     }
298 
299     bool notify = ccc_value & kCCCNotificationBit;
300     bool indicate = ccc_value & kCCCIndicationBit;
301 
302     if ((notify && !(chrc_props & Property::kNotify)) ||
303         (indicate && !(chrc_props & Property::kIndicate))) {
304       result_cb(fit::error(att::ErrorCode::kWriteNotPermitted));
305       return;
306     }
307 
308     auto iter = chrc_configs_.find(chrc_id);
309     if (iter == chrc_configs_.end()) {
310       auto result_pair =
311           chrc_configs_.emplace(chrc_id, CharacteristicConfig(handle));
312       iter = result_pair.first;
313     }
314 
315     // Send a reply back.
316     result_cb(fit::ok());
317 
318     uint16_t current_value = iter->second.Get(peer_id);
319     iter->second.Set(peer_id, ccc_value);
320 
321     if (current_value != ccc_value) {
322       ccc_callback_(id_, chrc_id, peer_id, notify, indicate);
323     }
324   }
325 
AddCharacteristic(att::AttributeGrouping * grouping,CharacteristicPtr chrc)326   void AddCharacteristic(att::AttributeGrouping* grouping,
327                          CharacteristicPtr chrc) {
328     // Set up the characteristic callbacks.
329     // TODO(armansito): Consider tracking a transaction timeout here
330     // (fxbug.dev/42142121).
331     IdType id = chrc->id();
332     uint8_t props = chrc->properties();
333     uint16_t ext_props = chrc->extended_properties();
334     auto self = weak_self_.GetWeakPtr();
335 
336     auto read_handler = [self, id, props](PeerId peer_id,
337                                           att::Handle,
338                                           uint16_t offset,
339                                           auto result_cb) {
340       if (!self.is_alive()) {
341         result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
342         return;
343       }
344 
345       // ATT permissions checks passed if we got here; also check the
346       // characteristic property.
347       if (!(props & Property::kRead)) {
348         // TODO(armansito): Return kRequestNotSupported?
349         result_cb(fit::error(att::ErrorCode::kReadNotPermitted), BufferView());
350         return;
351       }
352 
353       self->read_handler_(peer_id, self->id_, id, offset, std::move(result_cb));
354     };
355 
356     auto write_handler = [self, id, props](PeerId peer_id,
357                                            att::Handle,
358                                            uint16_t offset,
359                                            const auto& value,
360                                            auto result_cb) {
361       if (!self.is_alive()) {
362         if (result_cb)
363           result_cb(fit::error(att::ErrorCode::kUnlikelyError));
364         return;
365       }
366 
367       // If |result_cb| was provided, then this is a write request and the
368       // characteristic must support the "write" procedure.
369       if (result_cb && !(props & Property::kWrite)) {
370         // TODO(armansito): Return kRequestNotSupported?
371         result_cb(fit::error(att::ErrorCode::kWriteNotPermitted));
372         return;
373       }
374 
375       if (!result_cb && !(props & Property::kWriteWithoutResponse))
376         return;
377 
378       self->write_handler_(
379           peer_id, self->id_, id, offset, value, std::move(result_cb));
380     };
381 
382     att::Handle chrc_handle = InsertCharacteristicAttributes(
383         grouping, *chrc, std::move(read_handler), std::move(write_handler));
384 
385     if (props & Property::kNotify || props & Property::kIndicate) {
386       AddCCCDescriptor(grouping, *chrc, chrc_handle);
387     }
388 
389     if (ext_props) {
390       auto* decl_attr = grouping->AddAttribute(
391           types::kCharacteristicExtProperties,
392           att::AccessRequirements(
393               /*encryption=*/false,
394               /*authentication=*/false,
395               /*authorization=*/false),  // read (no security)
396           att::AccessRequirements());    // write (not allowed)
397       PW_DCHECK(decl_attr);
398       decl_attr->SetValue(StaticByteBuffer(
399           (uint8_t)(ext_props & 0x00FF), (uint8_t)((ext_props & 0xFF00) >> 8)));
400     }
401 
402     // TODO(armansito): Inject a SCC descriptor if the characteristic has the
403     // broadcast property and if we ever support configured broadcasts.
404 
405     // Sort descriptors by UUID size. This is not required by the specification
406     // but we do this to return as many descriptors as possible in a ATT Find
407     // Information response.
408     auto descs = chrc->ReleaseDescriptors();
409     std::sort(descs.begin(),
410               descs.end(),
411               [](const auto& desc_ptr1, const auto& desc_ptr2) {
412                 return desc_ptr1->type().CompactSize(/*allow_32bit=*/false) <
413                        desc_ptr2->type().CompactSize(/*allow_32bit=*/false);
414               });
415     for (auto& desc : descs) {
416       AddDescriptor(grouping, std::move(desc));
417     }
418   }
419 
AddDescriptor(att::AttributeGrouping * grouping,DescriptorPtr desc)420   void AddDescriptor(att::AttributeGrouping* grouping, DescriptorPtr desc) {
421     auto self = weak_self_.GetWeakPtr();
422     auto read_handler = [self, id = desc->id()](PeerId peer_id,
423                                                 att::Handle,
424                                                 uint16_t offset,
425                                                 auto result_cb) {
426       if (!self.is_alive()) {
427         result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
428         return;
429       }
430 
431       self->read_handler_(peer_id, self->id_, id, offset, std::move(result_cb));
432     };
433 
434     auto write_handler = [self, id = desc->id()](PeerId peer_id,
435                                                  att::Handle,
436                                                  uint16_t offset,
437                                                  const auto& value,
438                                                  auto result_cb) {
439       // Descriptors cannot be written using the "write without response"
440       // procedure.
441       if (!result_cb)
442         return;
443 
444       if (!self.is_alive()) {
445         result_cb(fit::error(att::ErrorCode::kUnlikelyError));
446         return;
447       }
448 
449       self->write_handler_(
450           peer_id, self->id_, id, offset, value, std::move(result_cb));
451     };
452 
453     InsertDescriptorAttribute(grouping,
454                               desc->type(),
455                               desc->read_permissions(),
456                               desc->write_permissions(),
457                               std::move(read_handler),
458                               std::move(write_handler));
459   }
460 
AddCCCDescriptor(att::AttributeGrouping * grouping,const Characteristic & chrc,att::Handle chrc_handle)461   void AddCCCDescriptor(att::AttributeGrouping* grouping,
462                         const Characteristic& chrc,
463                         att::Handle chrc_handle) {
464     PW_DCHECK(chrc.update_permissions().allowed());
465 
466     // Readable with no authentication or authorization (Vol 3, Part G,
467     // 3.3.3.3). We let the service determine the encryption permission.
468     att::AccessRequirements read_reqs(
469         chrc.update_permissions().encryption_required(),
470         /*authentication=*/false,
471         /*authorization=*/false);
472 
473     IdType id = chrc.id();
474     auto self = weak_self_.GetWeakPtr();
475 
476     auto read_handler = [self, id, chrc_handle](const auto& peer_id,
477                                                 att::Handle,
478                                                 uint16_t offset,
479                                                 auto result_cb) {
480       if (!self.is_alive()) {
481         result_cb(fit::error(att::ErrorCode::kUnlikelyError), BufferView());
482         return;
483       }
484 
485       self->OnReadCCC(id, peer_id, chrc_handle, offset, std::move(result_cb));
486     };
487 
488     auto write_handler = [self, id, chrc_handle, props = chrc.properties()](
489                              const auto& peer_id,
490                              att::Handle,
491                              uint16_t offset,
492                              const auto& value,
493                              auto result_cb) {
494       if (!self.is_alive()) {
495         result_cb(fit::error(att::ErrorCode::kUnlikelyError));
496         return;
497       }
498 
499       self->OnWriteCCC(
500           id, props, peer_id, chrc_handle, offset, value, std::move(result_cb));
501     };
502 
503     // The write permission is determined by the service.
504     InsertDescriptorAttribute(grouping,
505                               types::kClientCharacteristicConfig,
506                               read_reqs,
507                               chrc.update_permissions(),
508                               std::move(read_handler),
509                               std::move(write_handler));
510   }
511 
512   IdType id_;
513   att::Handle start_handle_;
514   att::Handle end_handle_;
515   ReadHandler read_handler_;
516   WriteHandler write_handler_;
517   ClientConfigCallback ccc_callback_;
518 
519   // Characteristic configuration states.
520   // TODO(armansito): Add a mechanism to persist client configuration for bonded
521   // devices.
522   std::unordered_map<IdType, CharacteristicConfig> chrc_configs_;
523 
524   WeakSelf<ServiceData> weak_self_;
525 
526   BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(ServiceData);
527 };
528 
LocalServiceManager()529 LocalServiceManager::LocalServiceManager()
530     : WeakSelf(this),
531       db_(std::make_unique<att::Database>()),
532       next_service_id_(1ull) {
533   PW_DCHECK(db_);
534 }
535 
536 LocalServiceManager::~LocalServiceManager() = default;
537 
RegisterService(ServicePtr service,ReadHandler read_handler,WriteHandler write_handler,ClientConfigCallback ccc_callback)538 IdType LocalServiceManager::RegisterService(ServicePtr service,
539                                             ReadHandler read_handler,
540                                             WriteHandler write_handler,
541                                             ClientConfigCallback ccc_callback) {
542   PW_DCHECK(service);
543   PW_DCHECK(read_handler);
544   PW_DCHECK(write_handler);
545   PW_DCHECK(ccc_callback);
546 
547   if (services_.find(next_service_id_) != services_.end()) {
548     bt_log(TRACE, "gatt", "server: Ran out of service IDs");
549     return kInvalidId;
550   }
551 
552   size_t attr_count;
553   if (!ValidateService(*service, &attr_count))
554     return kInvalidId;
555 
556   // GATT does not support 32-bit UUIDs.
557   const BufferView service_decl_value =
558       service->type().CompactView(/*allow_32bit=*/false);
559 
560   // TODO(armansito): Cluster services with 16-bit and 128-bit together inside
561   // |db_| (Vol 3, Part G, 3.1).
562 
563   att::AttributeGrouping* grouping = db_->NewGrouping(
564       service->primary() ? types::kPrimaryService : types::kSecondaryService,
565       attr_count,
566       service_decl_value);
567   if (!grouping) {
568     bt_log(DEBUG,
569            "gatt",
570            "server: Failed to allocate attribute grouping for service");
571     return kInvalidId;
572   }
573 
574   // Creating a ServiceData will populate the attribute grouping.
575   auto service_data = std::make_unique<ServiceData>(next_service_id_,
576                                                     grouping,
577                                                     service.get(),
578                                                     std::move(read_handler),
579                                                     std::move(write_handler),
580                                                     std::move(ccc_callback));
581   PW_DCHECK(grouping->complete());
582   grouping->set_active(true);
583 
584   // TODO(armansito): Handle potential 64-bit unsigned overflow?
585   IdType id = next_service_id_++;
586 
587   services_[id] = std::move(service_data);
588   if (service_changed_callback_) {
589     service_changed_callback_(
590         id, grouping->start_handle(), grouping->end_handle());
591   }
592 
593   return id;
594 }
595 
UnregisterService(IdType service_id)596 bool LocalServiceManager::UnregisterService(IdType service_id) {
597   auto iter = services_.find(service_id);
598   if (iter == services_.end())
599     return false;
600 
601   const att::Handle start_handle = iter->second->start_handle();
602   const att::Handle end_handle = iter->second->end_handle();
603   db_->RemoveGrouping(start_handle);
604   services_.erase(iter);
605 
606   if (service_changed_callback_) {
607     service_changed_callback_(service_id, start_handle, end_handle);
608   }
609   return true;
610 }
611 
GetCharacteristicConfig(IdType service_id,IdType chrc_id,PeerId peer_id,ClientCharacteristicConfig * out_config)612 bool LocalServiceManager::GetCharacteristicConfig(
613     IdType service_id,
614     IdType chrc_id,
615     PeerId peer_id,
616     ClientCharacteristicConfig* out_config) {
617   PW_DCHECK(out_config);
618 
619   auto iter = services_.find(service_id);
620   if (iter == services_.end())
621     return false;
622 
623   return iter->second->GetCharacteristicConfig(chrc_id, peer_id, out_config);
624 }
625 
DisconnectClient(PeerId peer_id)626 void LocalServiceManager::DisconnectClient(PeerId peer_id) {
627   for (auto& id_service_pair : services_) {
628     id_service_pair.second->DisconnectClient(peer_id);
629   }
630 }
631 
632 }  // namespace bt::gatt
633