xref: /aosp_15_r20/external/pigweed/pw_bluetooth_sapphire/host/gatt/remote_characteristic.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/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