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