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