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/remote_characteristic.h"
16
17 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
18 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
19 #include "pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
20 #include "pw_bluetooth_sapphire/internal/host/gatt/client.h"
21
22 namespace bt::gatt {
23
PendingNotifyRequest(ValueCallback value_cb,NotifyStatusCallback status_cb)24 RemoteCharacteristic::PendingNotifyRequest::PendingNotifyRequest(
25 ValueCallback value_cb, NotifyStatusCallback status_cb)
26 : value_callback(std::move(value_cb)),
27 status_callback(std::move(status_cb)) {
28 BT_DEBUG_ASSERT(value_callback);
29 BT_DEBUG_ASSERT(status_callback);
30 }
31
RemoteCharacteristic(Client::WeakPtr client,const CharacteristicData & info)32 RemoteCharacteristic::RemoteCharacteristic(Client::WeakPtr client,
33 const CharacteristicData& info)
34 : info_(info),
35 discovery_error_(false),
36 ccc_handle_(att::kInvalidHandle),
37 ext_prop_handle_(att::kInvalidHandle),
38 next_notify_handler_id_(1u),
39 client_(std::move(client)),
40 weak_self_(this) {
41 BT_DEBUG_ASSERT(client_.is_alive());
42 }
43
~RemoteCharacteristic()44 RemoteCharacteristic::~RemoteCharacteristic() {
45 ResolvePendingNotifyRequests(ToResult(HostError::kFailed));
46
47 // Clear the CCC if we have enabled notifications and destructor was not
48 // called as a result of a Service Changed notification.
49 if (!notify_handlers_.empty()) {
50 notify_handlers_.clear();
51 // Don't disable notifications if the service changed as this characteristic
52 // may no longer exist, may have been changed, or may have moved. If the
53 // characteristic is still valid, the server may continue to send
54 // notifications, but they will be ignored until a new handler is
55 // registered.
56 if (!service_changed_) {
57 DisableNotificationsInternal();
58 }
59 }
60 }
61
UpdateDataWithExtendedProperties(ExtendedProperties ext_props)62 void RemoteCharacteristic::UpdateDataWithExtendedProperties(
63 ExtendedProperties ext_props) {
64 // |CharacteristicData| is an immutable snapshot into the data associated with
65 // this Characteristic. Update |info_| with the most recent snapshot - the
66 // only new member is the recently read |ext_props|.
67 info_ = CharacteristicData(info_.properties,
68 ext_props,
69 info_.handle,
70 info_.value_handle,
71 info_.type);
72 }
73
DiscoverDescriptors(att::Handle range_end,att::ResultFunction<> callback)74 void RemoteCharacteristic::DiscoverDescriptors(att::Handle range_end,
75 att::ResultFunction<> callback) {
76 BT_DEBUG_ASSERT(client_.is_alive());
77 BT_DEBUG_ASSERT(callback);
78 BT_DEBUG_ASSERT(range_end >= info().value_handle);
79
80 discovery_error_ = false;
81 descriptors_.clear();
82
83 if (info().value_handle == range_end) {
84 callback(fit::ok());
85 return;
86 }
87
88 auto self = weak_self_.GetWeakPtr();
89 auto desc_cb = [self](const DescriptorData& desc) {
90 if (!self.is_alive())
91 return;
92
93 if (self->discovery_error_)
94 return;
95
96 if (desc.type == types::kClientCharacteristicConfig) {
97 if (self->ccc_handle_ != att::kInvalidHandle) {
98 bt_log(
99 DEBUG, "gatt", "characteristic has more than one CCC descriptor!");
100 self->discovery_error_ = true;
101 return;
102 }
103 self->ccc_handle_ = desc.handle;
104 } else if (desc.type == types::kCharacteristicExtProperties) {
105 if (self->ext_prop_handle_ != att::kInvalidHandle) {
106 bt_log(DEBUG,
107 "gatt",
108 "characteristic has more than one Extended Prop descriptor!");
109 self->discovery_error_ = true;
110 return;
111 }
112
113 // If the characteristic properties has the ExtendedProperties bit set,
114 // then update the handle.
115 if (self->properties() & Property::kExtendedProperties) {
116 self->ext_prop_handle_ = desc.handle;
117 } else {
118 bt_log(DEBUG, "gatt", "characteristic extended properties not set");
119 }
120 }
121
122 // As descriptors must be strictly increasing, this emplace should always
123 // succeed
124 auto [_unused, success] =
125 self->descriptors_.try_emplace(DescriptorHandle(desc.handle), desc);
126 BT_DEBUG_ASSERT(success);
127 };
128
129 auto status_cb = [self,
130 cb = std::move(callback)](att::Result<> status) mutable {
131 if (!self.is_alive()) {
132 cb(ToResult(HostError::kFailed));
133 return;
134 }
135
136 if (self->discovery_error_) {
137 status = ToResult(HostError::kFailed);
138 }
139
140 if (status.is_error()) {
141 self->descriptors_.clear();
142 cb(status);
143 return;
144 }
145
146 // If the characteristic contains the ExtendedProperties descriptor, perform
147 // a Read operation to get the extended properties before notifying the
148 // callback.
149 if (self->ext_prop_handle_ != att::kInvalidHandle) {
150 auto read_cb = [self, cb = std::move(cb)](att::Result<> status,
151 const ByteBuffer& data,
152 bool /*maybe_truncated*/) {
153 if (status.is_error()) {
154 cb(status);
155 return;
156 }
157
158 // The ExtendedProperties descriptor value is a |uint16_t| representing
159 // the ExtendedProperties bitfield. If the retrieved |data| is
160 // malformed, respond with an error and return early.
161 if (data.size() != sizeof(uint16_t)) {
162 cb(ToResult(HostError::kPacketMalformed));
163 return;
164 }
165
166 auto ext_props = le16toh(data.To<uint16_t>());
167 self->UpdateDataWithExtendedProperties(ext_props);
168
169 cb(status);
170 };
171
172 self->client_->ReadRequest(self->ext_prop_handle_, std::move(read_cb));
173 return;
174 }
175
176 cb(status);
177 };
178
179 client_->DiscoverDescriptors(info().value_handle + 1,
180 range_end,
181 std::move(desc_cb),
182 std::move(status_cb));
183 }
184
EnableNotifications(ValueCallback value_callback,NotifyStatusCallback status_callback)185 void RemoteCharacteristic::EnableNotifications(
186 ValueCallback value_callback, NotifyStatusCallback status_callback) {
187 BT_DEBUG_ASSERT(client_.is_alive());
188 BT_DEBUG_ASSERT(value_callback);
189 BT_DEBUG_ASSERT(status_callback);
190
191 if (!(info().properties & (Property::kNotify | Property::kIndicate))) {
192 bt_log(DEBUG, "gatt", "characteristic does not support notifications");
193 status_callback(ToResult(HostError::kNotSupported), kInvalidId);
194 return;
195 }
196
197 // If notifications are already enabled then succeed right away.
198 if (!notify_handlers_.empty()) {
199 BT_DEBUG_ASSERT(pending_notify_reqs_.empty());
200
201 IdType id = next_notify_handler_id_++;
202 notify_handlers_[id] = std::move(value_callback);
203 status_callback(fit::ok(), id);
204 return;
205 }
206
207 pending_notify_reqs_.emplace(std::move(value_callback),
208 std::move(status_callback));
209
210 // If there are other pending requests to enable notifications then we'll wait
211 // until the descriptor write completes.
212 if (pending_notify_reqs_.size() > 1u)
213 return;
214
215 // It is possible for some characteristics that support notifications or
216 // indications to not have a CCC descriptor. Such characteristics do not need
217 // to be directly configured to consider notifications to have been enabled.
218 if (ccc_handle_ == att::kInvalidHandle) {
219 bt_log(TRACE,
220 "gatt",
221 "notications enabled without characteristic configuration");
222 ResolvePendingNotifyRequests(fit::ok());
223 return;
224 }
225
226 StaticByteBuffer<2> ccc_value;
227 ccc_value.SetToZeros();
228
229 // Enable indications if supported. Otherwise enable notifications.
230 if (info().properties & Property::kIndicate) {
231 ccc_value[0] = static_cast<uint8_t>(kCCCIndicationBit);
232 } else {
233 ccc_value[0] = static_cast<uint8_t>(kCCCNotificationBit);
234 }
235
236 auto self = weak_self_.GetWeakPtr();
237 auto ccc_write_cb = [self](att::Result<> status) {
238 bt_log(DEBUG, "gatt", "CCC write status (enable): %s", bt_str(status));
239 if (self.is_alive()) {
240 self->ResolvePendingNotifyRequests(status);
241 }
242 };
243
244 client_->WriteRequest(ccc_handle_, ccc_value, std::move(ccc_write_cb));
245 }
246
DisableNotifications(IdType handler_id)247 bool RemoteCharacteristic::DisableNotifications(IdType handler_id) {
248 BT_DEBUG_ASSERT(client_.is_alive());
249
250 auto handler_iter = notify_handlers_.find(handler_id);
251 if (handler_iter == notify_handlers_.end()) {
252 bt_log(TRACE, "gatt", "notify handler not found (id: %lu)", handler_id);
253 return false;
254 }
255
256 // Don't modify handlers map while handlers are being notified.
257 if (notifying_handlers_) {
258 handlers_pending_disable_.push_back(handler_id);
259 return true;
260 }
261 notify_handlers_.erase(handler_iter);
262
263 if (!notify_handlers_.empty())
264 return true;
265
266 DisableNotificationsInternal();
267 return true;
268 }
269
DisableNotificationsInternal()270 void RemoteCharacteristic::DisableNotificationsInternal() {
271 if (ccc_handle_ == att::kInvalidHandle) {
272 // Nothing to do.
273 return;
274 }
275
276 if (!client_.is_alive()) {
277 bt_log(TRACE, "gatt", "client bearer invalid!");
278 return;
279 }
280
281 // Disable notifications.
282 StaticByteBuffer<2> ccc_value;
283 ccc_value.SetToZeros();
284
285 auto ccc_write_cb = [](att::Result<> status) {
286 bt_log(DEBUG, "gatt", "CCC write status (disable): %s", bt_str(status));
287 };
288
289 // We send the request without handling the status as there is no good way to
290 // recover from failing to disable notifications. If the peer continues to
291 // send notifications, they will be dropped as no handlers are registered.
292 client_->WriteRequest(ccc_handle_, ccc_value, std::move(ccc_write_cb));
293 }
294
ResolvePendingNotifyRequests(att::Result<> status)295 void RemoteCharacteristic::ResolvePendingNotifyRequests(att::Result<> status) {
296 // Don't iterate requests as callbacks can add new requests.
297 while (!pending_notify_reqs_.empty()) {
298 auto req = std::move(pending_notify_reqs_.front());
299 pending_notify_reqs_.pop();
300
301 IdType id = kInvalidId;
302
303 if (status.is_ok()) {
304 id = next_notify_handler_id_++;
305 // Add handler to map before calling status callback in case callback
306 // removes the handler.
307 notify_handlers_[id] = std::move(req.value_callback);
308 }
309
310 req.status_callback(status, id);
311 }
312 }
313
HandleNotification(const ByteBuffer & value,bool maybe_truncated)314 void RemoteCharacteristic::HandleNotification(const ByteBuffer& value,
315 bool maybe_truncated) {
316 BT_DEBUG_ASSERT(client_.is_alive());
317
318 notifying_handlers_ = true;
319 for (auto& iter : notify_handlers_) {
320 auto& handler = iter.second;
321 handler(value, maybe_truncated);
322 }
323 notifying_handlers_ = false;
324
325 // If handlers disabled themselves when notified, remove them from the map.
326 for (IdType handler_id : handlers_pending_disable_) {
327 notify_handlers_.erase(handler_id);
328 }
329 handlers_pending_disable_.clear();
330 }
331
332 } // namespace bt::gatt
333