• 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/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