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/generic_attribute_service.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
18 #include "pw_bluetooth_sapphire/internal/host/common/byte_buffer.h"
19 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
20 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
21
22 namespace bt::gatt {
23
GenericAttributeService(LocalServiceManager::WeakPtr local_service_manager,SendIndicationCallback send_indication_callback)24 GenericAttributeService::GenericAttributeService(
25 LocalServiceManager::WeakPtr local_service_manager,
26 SendIndicationCallback send_indication_callback)
27 : local_service_manager_(std::move(local_service_manager)),
28 send_indication_callback_(std::move(send_indication_callback)) {
29 BT_ASSERT(local_service_manager_.is_alive());
30 BT_DEBUG_ASSERT(send_indication_callback_);
31
32 Register();
33 }
34
~GenericAttributeService()35 GenericAttributeService::~GenericAttributeService() {
36 if (local_service_manager_.is_alive() && service_id_ != kInvalidId) {
37 local_service_manager_->UnregisterService(service_id_);
38 }
39 }
40
Register()41 void GenericAttributeService::Register() {
42 const att::AccessRequirements kDisallowed;
43 const att::AccessRequirements kAllowedNoSecurity(/*encryption=*/false,
44 /*authentication=*/false,
45 /*authorization=*/false);
46 CharacteristicPtr service_changed_chr = std::make_unique<Characteristic>(
47 kServiceChangedChrcId, // id
48 types::kServiceChangedCharacteristic, // type
49 Property::kIndicate, // properties
50 0u, // extended_properties
51 kDisallowed, // read
52 kDisallowed, // write
53 kAllowedNoSecurity); // update
54 auto service =
55 std::make_unique<Service>(true, types::kGenericAttributeService);
56 service->AddCharacteristic(std::move(service_changed_chr));
57
58 ClientConfigCallback ccc_callback = [this](IdType service_id,
59 IdType chrc_id,
60 PeerId peer_id,
61 bool notify,
62 bool indicate) {
63 BT_DEBUG_ASSERT(chrc_id == 0u);
64
65 // Discover the handle assigned to this characteristic if necessary.
66 if (svc_changed_handle_ == att::kInvalidHandle) {
67 LocalServiceManager::ClientCharacteristicConfig config;
68 if (!local_service_manager_->GetCharacteristicConfig(
69 service_id, chrc_id, peer_id, &config)) {
70 bt_log(DEBUG,
71 "gatt",
72 "service: Peer has not configured characteristic: %s",
73 bt_str(peer_id));
74 return;
75 }
76 svc_changed_handle_ = config.handle;
77 }
78 SetServiceChangedIndicationSubscription(peer_id, indicate);
79 if (persist_service_changed_ccc_callback_) {
80 ServiceChangedCCCPersistedData persisted = {.notify = notify,
81 .indicate = indicate};
82 persist_service_changed_ccc_callback_(peer_id, persisted);
83 } else {
84 bt_log(WARN,
85 "gatt",
86 "Attempted to persist service changed ccc but no callback found.");
87 }
88 };
89
90 service_id_ =
91 local_service_manager_->RegisterService(std::move(service),
92 NopReadHandler,
93 NopWriteHandler,
94 std::move(ccc_callback));
95 BT_DEBUG_ASSERT(service_id_ != kInvalidId);
96
97 local_service_manager_->set_service_changed_callback(
98 fit::bind_member<&GenericAttributeService::OnServiceChanged>(this));
99 }
100
SetServiceChangedIndicationSubscription(PeerId peer_id,bool indicate)101 void GenericAttributeService::SetServiceChangedIndicationSubscription(
102 PeerId peer_id, bool indicate) {
103 if (indicate) {
104 subscribed_peers_.insert(peer_id);
105 bt_log(DEBUG,
106 "gatt",
107 "service: Service Changed enabled for peer %s",
108 bt_str(peer_id));
109 } else {
110 subscribed_peers_.erase(peer_id);
111 bt_log(DEBUG,
112 "gatt",
113 "service: Service Changed disabled for peer %s",
114 bt_str(peer_id));
115 }
116 }
117
OnServiceChanged(IdType service_id,att::Handle start,att::Handle end)118 void GenericAttributeService::OnServiceChanged(IdType service_id,
119 att::Handle start,
120 att::Handle end) {
121 // Service Changed not yet configured for indication.
122 if (svc_changed_handle_ == att::kInvalidHandle) {
123 return;
124 }
125
126 // Don't send indications for this service's removal.
127 if (service_id_ == service_id) {
128 return;
129 }
130
131 StaticByteBuffer<2 * sizeof(uint16_t)> value;
132
133 value[0] = static_cast<uint8_t>(start);
134 value[1] = static_cast<uint8_t>(start >> 8);
135 value[2] = static_cast<uint8_t>(end);
136 value[3] = static_cast<uint8_t>(end >> 8);
137
138 for (auto peer_id : subscribed_peers_) {
139 bt_log(TRACE,
140 "gatt",
141 "service: indicating peer %s of service(s) changed "
142 "(start: %#.4x, end: %#.4x)",
143 bt_str(peer_id),
144 start,
145 end);
146 send_indication_callback_(
147 service_id_, kServiceChangedChrcId, peer_id, value.view());
148 }
149 }
150
151 } // namespace bt::gatt
152