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/client.h"
16
17 #include <pw_bytes/endian.h>
18
19 #include "pw_bluetooth_sapphire/internal/host/att/att.h"
20 #include "pw_bluetooth_sapphire/internal/host/common/assert.h"
21 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
22 #include "pw_bluetooth_sapphire/internal/host/common/macros.h"
23 #include "pw_bluetooth_sapphire/internal/host/common/slab_allocator.h"
24 #include "pw_bluetooth_sapphire/internal/host/common/trace.h"
25 #include "pw_bluetooth_sapphire/internal/host/gatt/gatt_defs.h"
26
27 using bt::HostError;
28
29 namespace bt::gatt {
30 namespace {
31
NewPDU(size_t param_size)32 MutableByteBufferPtr NewPDU(size_t param_size) {
33 auto pdu = NewBuffer(sizeof(att::Header) + param_size);
34 if (!pdu) {
35 bt_log(DEBUG, "att", "out of memory");
36 }
37 return pdu;
38 }
39
40 template <att::UUIDType Format,
41 typename EntryType = att::InformationData<Format>>
ProcessDescriptorDiscoveryResponse(att::Handle range_start,att::Handle range_end,BufferView entries,Client::DescriptorCallback desc_callback,att::Handle * out_last_handle)42 bool ProcessDescriptorDiscoveryResponse(
43 att::Handle range_start,
44 att::Handle range_end,
45 BufferView entries,
46 Client::DescriptorCallback desc_callback,
47 att::Handle* out_last_handle) {
48 PW_DCHECK(out_last_handle);
49
50 if (entries.size() % sizeof(EntryType)) {
51 bt_log(DEBUG, "gatt", "malformed information data list");
52 return false;
53 }
54
55 att::Handle last_handle = range_end;
56 while (entries.size()) {
57 const EntryType& entry = entries.To<EntryType>();
58
59 att::Handle desc_handle =
60 pw::bytes::ConvertOrderFrom(cpp20::endian::little, entry.handle);
61
62 // Stop and report an error if the server erroneously responds with
63 // an attribute outside the requested range.
64 if (desc_handle > range_end || desc_handle < range_start) {
65 bt_log(DEBUG,
66 "gatt",
67 "descriptor handle out of range (handle: %#.4x, "
68 "range: %#.4x - %#.4x)",
69 desc_handle,
70 range_start,
71 range_end);
72 return false;
73 }
74
75 // The handles must be strictly increasing.
76 if (last_handle != range_end && desc_handle <= last_handle) {
77 bt_log(DEBUG, "gatt", "descriptor handles not strictly increasing");
78 return false;
79 }
80
81 last_handle = desc_handle;
82
83 // Notify the handler.
84 desc_callback(DescriptorData(desc_handle, UUID(entry.uuid)));
85
86 entries = entries.view(sizeof(EntryType));
87 }
88
89 *out_last_handle = last_handle;
90 return true;
91 }
92
93 } // namespace
94
95 class Impl final : public Client {
96 public:
Impl(att::Bearer::WeakPtr bearer)97 explicit Impl(att::Bearer::WeakPtr bearer)
98 : att_(std::move(bearer)), weak_self_(this) {
99 PW_DCHECK(att_.is_alive());
100
101 auto handler = [this](auto txn_id, const att::PacketReader& pdu) {
102 PW_DCHECK(pdu.opcode() == att::kNotification ||
103 pdu.opcode() == att::kIndication);
104
105 if (pdu.payload_size() < sizeof(att::NotificationParams)) {
106 // Received a malformed notification. Disconnect the link.
107 bt_log(DEBUG, "gatt", "malformed notification/indication PDU");
108 att_->ShutDown();
109 return;
110 }
111
112 bool is_ind = pdu.opcode() == att::kIndication;
113 const auto& params = pdu.payload<att::NotificationParams>();
114 att::Handle handle =
115 pw::bytes::ConvertOrderFrom(cpp20::endian::little, params.handle);
116 size_t value_size = pdu.payload_size() - sizeof(att::Handle);
117
118 // Auto-confirm indications.
119 if (is_ind) {
120 auto confirmation_pdu = NewPDU(0u);
121 if (confirmation_pdu) {
122 att::PacketWriter(att::kConfirmation, confirmation_pdu.get());
123 att_->Reply(txn_id, std::move(confirmation_pdu));
124 } else {
125 att_->ReplyWithError(
126 txn_id, handle, att::ErrorCode::kInsufficientResources);
127 }
128 }
129
130 bool maybe_truncated = false;
131 // If the value is the max size that fits in the MTU, it may be truncated.
132 if (value_size ==
133 att_->mtu() - sizeof(att::OpCode) - sizeof(att::Handle)) {
134 maybe_truncated = true;
135 }
136
137 // Run the handler
138 if (notification_handler_) {
139 notification_handler_(is_ind,
140 handle,
141 BufferView(params.value, value_size),
142 maybe_truncated);
143 } else {
144 bt_log(
145 TRACE, "gatt", "dropped notification/indication without handler");
146 }
147 };
148
149 not_handler_id_ = att_->RegisterHandler(att::kNotification, handler);
150 ind_handler_id_ = att_->RegisterHandler(att::kIndication, handler);
151 }
152
~Impl()153 ~Impl() override {
154 att_->UnregisterHandler(not_handler_id_);
155 att_->UnregisterHandler(ind_handler_id_);
156 }
157
158 using WeakPtr = WeakSelf<Client>::WeakPtr;
GetWeakPtr()159 WeakPtr GetWeakPtr() override { return weak_self_.GetWeakPtr(); }
160
161 private:
mtu() const162 uint16_t mtu() const override { return att_->mtu(); }
163
ExchangeMTU(MTUCallback mtu_callback)164 void ExchangeMTU(MTUCallback mtu_callback) override {
165 auto pdu = NewPDU(sizeof(att::ExchangeMTURequestParams));
166 if (!pdu) {
167 mtu_callback(fit::error(att::Error(HostError::kOutOfMemory)));
168 return;
169 }
170
171 att::PacketWriter writer(att::kExchangeMTURequest, pdu.get());
172 auto params = writer.mutable_payload<att::ExchangeMTURequestParams>();
173 params->client_rx_mtu =
174 pw::bytes::ConvertOrderTo(cpp20::endian::little, att_->preferred_mtu());
175
176 auto rsp_cb = [this, mtu_cb = std::move(mtu_callback)](
177 att::Bearer::TransactionResult result) mutable {
178 if (result.is_ok()) {
179 const att::PacketReader& rsp = result.value();
180 PW_DCHECK(rsp.opcode() == att::kExchangeMTUResponse);
181
182 if (rsp.payload_size() != sizeof(att::ExchangeMTUResponseParams)) {
183 // Received a malformed response. Disconnect the link.
184 att_->ShutDown();
185
186 mtu_cb(fit::error(att::Error(HostError::kPacketMalformed)));
187 return;
188 }
189
190 const auto& rsp_params = rsp.payload<att::ExchangeMTUResponseParams>();
191 uint16_t server_mtu = pw::bytes::ConvertOrderFrom(
192 cpp20::endian::little, rsp_params.server_rx_mtu);
193
194 // If the minimum value is less than the default MTU, then go with the
195 // default MTU (Vol 3, Part F, 3.4.2.2).
196 uint16_t final_mtu = std::max(
197 att::kLEMinMTU, std::min(server_mtu, att_->preferred_mtu()));
198 att_->set_mtu(final_mtu);
199
200 mtu_cb(fit::ok(final_mtu));
201 return;
202 }
203 const auto& [error, handle] = result.error_value();
204
205 // "If the Error Response is sent by the server with the Error Code
206 // set to Request Not Supported, [...] the default MTU shall be used
207 // (Vol 3, Part G, 4.3.1)"
208 if (error.is(att::ErrorCode::kRequestNotSupported)) {
209 bt_log(
210 DEBUG, "gatt", "peer does not support MTU exchange: using default");
211 att_->set_mtu(att::kLEMinMTU);
212 mtu_cb(fit::error(error));
213 return;
214 }
215
216 bt_log(DEBUG, "gatt", "MTU exchange failed: %s", bt_str(error));
217 mtu_cb(fit::error(error));
218 };
219
220 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
221 }
222
DiscoverServices(ServiceKind kind,ServiceCallback svc_callback,att::ResultFunction<> status_callback)223 void DiscoverServices(ServiceKind kind,
224 ServiceCallback svc_callback,
225 att::ResultFunction<> status_callback) override {
226 DiscoverServicesInRange(kind,
227 att::kHandleMin,
228 att::kHandleMax,
229 std::move(svc_callback),
230 std::move(status_callback));
231 }
232
DiscoverServicesInRange(ServiceKind kind,att::Handle range_start,att::Handle range_end,ServiceCallback svc_callback,att::ResultFunction<> status_callback)233 void DiscoverServicesInRange(ServiceKind kind,
234 att::Handle range_start,
235 att::Handle range_end,
236 ServiceCallback svc_callback,
237 att::ResultFunction<> status_callback) override {
238 PW_CHECK(range_start <= range_end);
239
240 auto pdu = NewPDU(sizeof(att::ReadByGroupTypeRequestParams16));
241 if (!pdu) {
242 status_callback(ToResult(HostError::kOutOfMemory));
243 return;
244 }
245
246 att::PacketWriter writer(att::kReadByGroupTypeRequest, pdu.get());
247 auto* params =
248 writer.mutable_payload<att::ReadByGroupTypeRequestParams16>();
249 params->start_handle =
250 pw::bytes::ConvertOrderTo(cpp20::endian::little, range_start);
251 params->end_handle =
252 pw::bytes::ConvertOrderTo(cpp20::endian::little, range_end);
253 params->type = pw::bytes::ConvertOrderTo(cpp20::endian::little,
254 kind == ServiceKind::PRIMARY
255 ? types::kPrimaryService16
256 : types::kSecondaryService16);
257
258 auto rsp_cb = [this,
259 kind,
260 range_start,
261 range_end,
262 svc_cb = std::move(svc_callback),
263 res_cb = std::move(status_callback)](
264 att::Bearer::TransactionResult result) mutable {
265 if (result.is_error()) {
266 const att::Error& error = result.error_value().first;
267
268 // An Error Response code of "Attribute Not Found" indicates the end of
269 // the procedure (v5.0, Vol 3, Part G, 4.4.1).
270 if (error.is(att::ErrorCode::kAttributeNotFound)) {
271 res_cb(fit::ok());
272 return;
273 }
274
275 res_cb(fit::error(error));
276 return;
277 }
278
279 const att::PacketReader& rsp = result.value();
280 PW_DCHECK(rsp.opcode() == att::kReadByGroupTypeResponse);
281 TRACE_DURATION("bluetooth",
282 "gatt::Client::DiscoverServicesInRange rsp_cb",
283 "size",
284 rsp.size());
285
286 if (rsp.payload_size() < sizeof(att::ReadByGroupTypeResponseParams)) {
287 // Received malformed response. Disconnect the link.
288 bt_log(DEBUG, "gatt", "received malformed Read By Group Type response");
289 att_->ShutDown();
290 res_cb(ToResult(HostError::kPacketMalformed));
291 return;
292 }
293
294 const auto& rsp_params =
295 rsp.payload<att::ReadByGroupTypeResponseParams>();
296 uint8_t entry_length = rsp_params.length;
297
298 // We expect the returned attribute value to be a 16-bit or 128-bit
299 // service UUID.
300 constexpr size_t kAttrDataSize16 =
301 sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType16);
302 constexpr size_t kAttrDataSize128 =
303 sizeof(att::AttributeGroupDataEntry) + sizeof(att::AttributeType128);
304
305 if (entry_length != kAttrDataSize16 && entry_length != kAttrDataSize128) {
306 bt_log(DEBUG, "gatt", "invalid attribute data length");
307 att_->ShutDown();
308 res_cb(ToResult(HostError::kPacketMalformed));
309 return;
310 }
311
312 BufferView attr_data_list(rsp_params.attribute_data_list,
313 rsp.payload_size() - 1);
314 if (attr_data_list.size() % entry_length) {
315 bt_log(DEBUG, "gatt", "malformed attribute data list");
316 att_->ShutDown();
317 res_cb(ToResult(HostError::kPacketMalformed));
318 return;
319 }
320
321 std::optional<att::Handle> last_handle;
322 while (attr_data_list.size()) {
323 att::Handle start = pw::bytes::ConvertOrderFrom(
324 cpp20::endian::little,
325 attr_data_list
326 .ReadMember<&att::AttributeGroupDataEntry::start_handle>());
327 att::Handle end = pw::bytes::ConvertOrderFrom(
328 cpp20::endian::little,
329 attr_data_list
330 .ReadMember<&att::AttributeGroupDataEntry::group_end_handle>());
331
332 if (end < start) {
333 bt_log(DEBUG, "gatt", "received malformed service range values");
334 res_cb(ToResult(HostError::kPacketMalformed));
335 return;
336 }
337
338 if (start < range_start || start > range_end) {
339 bt_log(DEBUG,
340 "gatt",
341 "received service range values outside of requested range");
342 res_cb(ToResult(HostError::kPacketMalformed));
343 return;
344 }
345
346 // "The Attribute Data List is ordered sequentially based on the
347 // attribute handles." (Core Spec v5.3, Vol 3, Part F, Sec 3.4.4.10)
348 if (last_handle.has_value() && start <= last_handle.value()) {
349 bt_log(DEBUG, "gatt", "received services out of order");
350 res_cb(ToResult(HostError::kPacketMalformed));
351 return;
352 }
353
354 // This must succeed as we have performed the appropriate checks above.
355 auto uuid_bytes =
356 attr_data_list.view(offsetof(att::AttributeGroupDataEntry, value),
357 entry_length - (2 * sizeof(att::Handle)));
358 UUID uuid(uuid_bytes);
359
360 ServiceData service(kind, start, end, uuid);
361 last_handle = service.range_end;
362
363 // Notify the handler.
364 svc_cb(service);
365
366 attr_data_list = attr_data_list.view(entry_length);
367 }
368
369 // The procedure is over if we have reached the end of the handle range.
370 if (!last_handle.has_value() || last_handle.value() == range_end) {
371 res_cb(fit::ok());
372 return;
373 }
374
375 // Request the next batch.
376 DiscoverServicesInRange(kind,
377 last_handle.value() + 1,
378 range_end,
379 std::move(svc_cb),
380 std::move(res_cb));
381 };
382
383 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
384 }
385
DiscoverServicesWithUuids(ServiceKind kind,ServiceCallback svc_cb,att::ResultFunction<> status_cb,std::vector<UUID> uuids)386 void DiscoverServicesWithUuids(ServiceKind kind,
387 ServiceCallback svc_cb,
388 att::ResultFunction<> status_cb,
389 std::vector<UUID> uuids) override {
390 DiscoverServicesWithUuidsInRange(kind,
391 att::kHandleMin,
392 att::kHandleMax,
393 std::move(svc_cb),
394 std::move(status_cb),
395 std::move(uuids));
396 }
397
DiscoverServicesWithUuidsInRange(ServiceKind kind,att::Handle range_start,att::Handle range_end,ServiceCallback svc_callback,att::ResultFunction<> status_callback,std::vector<UUID> uuids)398 void DiscoverServicesWithUuidsInRange(ServiceKind kind,
399 att::Handle range_start,
400 att::Handle range_end,
401 ServiceCallback svc_callback,
402 att::ResultFunction<> status_callback,
403 std::vector<UUID> uuids) override {
404 PW_CHECK(range_start <= range_end);
405 PW_CHECK(!uuids.empty());
406 UUID uuid = uuids.back();
407 uuids.pop_back();
408
409 auto recursive_status_cb = [this,
410 range_start,
411 range_end,
412 kind,
413 svc_cb = svc_callback.share(),
414 status_cb = std::move(status_callback),
415 remaining_uuids =
416 std::move(uuids)](auto status) mutable {
417 // Base case
418 if (status.is_error() || remaining_uuids.empty()) {
419 status_cb(status);
420 return;
421 }
422
423 // Recursively discover with the remaining UUIDs.
424 DiscoverServicesWithUuidsInRange(kind,
425 range_start,
426 range_end,
427 std::move(svc_cb),
428 std::move(status_cb),
429 std::move(remaining_uuids));
430 };
431
432 // Discover the last uuid in uuids.
433 DiscoverServicesByUuidInRange(kind,
434 range_start,
435 range_end,
436 std::move(svc_callback),
437 std::move(recursive_status_cb),
438 uuid);
439 }
440
DiscoverServicesByUuidInRange(ServiceKind kind,att::Handle start,att::Handle end,ServiceCallback svc_callback,att::ResultFunction<> status_callback,UUID uuid)441 void DiscoverServicesByUuidInRange(ServiceKind kind,
442 att::Handle start,
443 att::Handle end,
444 ServiceCallback svc_callback,
445 att::ResultFunction<> status_callback,
446 UUID uuid) {
447 size_t uuid_size_bytes = uuid.CompactSize(/* allow 32 bit UUIDs */ false);
448 auto pdu =
449 NewPDU(sizeof(att::FindByTypeValueRequestParams) + uuid_size_bytes);
450 if (!pdu) {
451 status_callback(ToResult(HostError::kOutOfMemory));
452 return;
453 }
454
455 att::PacketWriter writer(att::kFindByTypeValueRequest, pdu.get());
456 auto* params = writer.mutable_payload<att::FindByTypeValueRequestParams>();
457 params->start_handle =
458 pw::bytes::ConvertOrderTo(cpp20::endian::little, start);
459 params->end_handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, end);
460 params->type = pw::bytes::ConvertOrderTo(cpp20::endian::little,
461 kind == ServiceKind::PRIMARY
462 ? types::kPrimaryService16
463 : types::kSecondaryService16);
464 MutableBufferView value_view(params->value, uuid_size_bytes);
465 uuid.ToBytes(&value_view, /* allow 32 bit UUIDs */ false);
466
467 auto rsp_cb = [this,
468 kind,
469 discovery_range_start = start,
470 discovery_range_end = end,
471 svc_cb = std::move(svc_callback),
472 res_cb = std::move(status_callback),
473 uuid](att::Bearer::TransactionResult result) mutable {
474 if (result.is_error()) {
475 const att::Error& error = result.error_value().first;
476
477 // An Error Response code of "Attribute Not Found" indicates the end of
478 // the procedure (v5.0, Vol 3, Part G, 4.4.2).
479 if (error.is(att::ErrorCode::kAttributeNotFound)) {
480 res_cb(fit::ok());
481 return;
482 }
483
484 res_cb(fit::error(error));
485 return;
486 }
487
488 const att::PacketReader& rsp = result.value();
489 PW_DCHECK(rsp.opcode() == att::kFindByTypeValueResponse);
490
491 size_t payload_size = rsp.payload_size();
492 if (payload_size < 1 ||
493 payload_size % sizeof(att::FindByTypeValueResponseParams) != 0) {
494 // Received malformed response. Disconnect the link.
495 bt_log(DEBUG,
496 "gatt",
497 "received malformed Find By Type Value response with size %zu",
498 payload_size);
499 att_->ShutDown();
500 res_cb(ToResult(HostError::kPacketMalformed));
501 return;
502 }
503
504 BufferView handle_list = rsp.payload_data();
505
506 std::optional<att::Handle> last_handle;
507 while (handle_list.size()) {
508 const auto& entry = handle_list.To<att::HandlesInformationList>();
509
510 att::Handle start_handle =
511 pw::bytes::ConvertOrderFrom(cpp20::endian::little, entry.handle);
512 att::Handle end_handle = pw::bytes::ConvertOrderFrom(
513 cpp20::endian::little, entry.group_end_handle);
514
515 if (end_handle < start_handle) {
516 bt_log(DEBUG, "gatt", "received malformed service range values");
517 res_cb(ToResult(HostError::kPacketMalformed));
518 return;
519 }
520
521 if (start_handle < discovery_range_start ||
522 start_handle > discovery_range_end) {
523 bt_log(DEBUG,
524 "gatt",
525 "received service range values outside of requested range");
526 res_cb(ToResult(HostError::kPacketMalformed));
527 return;
528 }
529
530 // "The Handles Information List is ordered sequentially based on the
531 // found attribute handles." (Core Spec v5.3, Vol 3, Part F,
532 // Sec 3.4.3.4)
533 if (last_handle.has_value() && start_handle <= last_handle.value()) {
534 bt_log(DEBUG, "gatt", "received services out of order");
535 res_cb(ToResult(HostError::kPacketMalformed));
536 return;
537 }
538
539 ServiceData service(kind, start_handle, end_handle, uuid);
540
541 // Notify the handler.
542 svc_cb(service);
543
544 // HandlesInformationList is a single element of the list.
545 size_t entry_length = sizeof(att::HandlesInformationList);
546 handle_list = handle_list.view(entry_length);
547
548 last_handle = service.range_end;
549 }
550
551 // The procedure is over if we have reached the end of the handle range.
552 if (!last_handle.has_value() ||
553 last_handle.value() == discovery_range_end) {
554 res_cb(fit::ok());
555 return;
556 }
557
558 // Request the next batch.
559 DiscoverServicesByUuidInRange(kind,
560 last_handle.value() + 1,
561 discovery_range_end,
562 std::move(svc_cb),
563 std::move(res_cb),
564 uuid);
565 };
566
567 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
568 }
569
DiscoverCharacteristics(att::Handle range_start,att::Handle range_end,CharacteristicCallback chrc_callback,att::ResultFunction<> status_callback)570 void DiscoverCharacteristics(att::Handle range_start,
571 att::Handle range_end,
572 CharacteristicCallback chrc_callback,
573 att::ResultFunction<> status_callback) override {
574 PW_CHECK(range_start <= range_end);
575 PW_CHECK(chrc_callback);
576 PW_CHECK(status_callback);
577
578 if (range_start == range_end) {
579 status_callback(fit::ok());
580 return;
581 }
582
583 auto read_by_type_cb = [this,
584 range_end,
585 chrc_cb = std::move(chrc_callback),
586 res_cb = status_callback.share()](
587 ReadByTypeResult result) mutable {
588 TRACE_DURATION("bluetooth",
589 "gatt::Client::DiscoverCharacteristics read_by_type_cb");
590
591 if (result.is_error()) {
592 const auto error = result.error_value().error;
593
594 // An Error Response code of "Attribute Not Found" indicates the end
595 // of the procedure (v5.0, Vol 3, Part G, 4.6.1).
596 if (error.is(att::ErrorCode::kAttributeNotFound)) {
597 res_cb(fit::ok());
598 return;
599 }
600
601 res_cb(fit::error(error));
602 return;
603 }
604
605 auto& attributes = result.value();
606
607 // ReadByTypeRequest() should return an error result if there are no
608 // attributes in a success response.
609 PW_CHECK(!attributes.empty());
610
611 for (auto& char_attr : attributes) {
612 Properties properties = 0u;
613 att::Handle value_handle = 0u;
614 UUID value_uuid;
615
616 // The characteristic declaration value contains:
617 // 1 octet: properties
618 // 2 octets: value handle
619 // 2 or 16 octets: UUID
620 if (char_attr.value.size() ==
621 sizeof(CharacteristicDeclarationAttributeValue<
622 att::UUIDType::k16Bit>)) {
623 auto attr_value = char_attr.value.To<
624 CharacteristicDeclarationAttributeValue<att::UUIDType::k16Bit>>();
625 properties = attr_value.properties;
626 value_handle = pw::bytes::ConvertOrderFrom(cpp20::endian::little,
627 attr_value.value_handle);
628 value_uuid = UUID(attr_value.value_uuid);
629 } else if (char_attr.value.size() ==
630 sizeof(CharacteristicDeclarationAttributeValue<
631 att::UUIDType::k128Bit>)) {
632 auto attr_value =
633 char_attr.value.To<CharacteristicDeclarationAttributeValue<
634 att::UUIDType::k128Bit>>();
635 properties = attr_value.properties;
636 value_handle = pw::bytes::ConvertOrderFrom(cpp20::endian::little,
637 attr_value.value_handle);
638 value_uuid = UUID(attr_value.value_uuid);
639 } else {
640 bt_log(DEBUG,
641 "gatt",
642 "invalid characteristic declaration attribute value size");
643 att_->ShutDown();
644 res_cb(ToResult(HostError::kPacketMalformed));
645 return;
646 }
647
648 // Vol 3, Part G, 3.3: "The Characteristic Value declaration shall
649 // exist immediately following the characteristic declaration."
650 if (value_handle != char_attr.handle + 1) {
651 bt_log(
652 DEBUG, "gatt", "characteristic value doesn't follow declaration");
653 res_cb(ToResult(HostError::kPacketMalformed));
654 return;
655 }
656
657 // Notify the handler. By default, there are no extended properties to
658 // report.
659 chrc_cb(CharacteristicData(properties,
660 /*ext_props=*/std::nullopt,
661 char_attr.handle,
662 value_handle,
663 value_uuid));
664 }
665
666 // The procedure is over if we have reached the end of the handle
667 // range.
668 const auto last_handle = attributes.back().handle;
669 if (last_handle == range_end) {
670 res_cb(fit::ok());
671 return;
672 }
673
674 // Request the next batch.
675 DiscoverCharacteristics(
676 last_handle + 1, range_end, std::move(chrc_cb), std::move(res_cb));
677 };
678
679 ReadByTypeRequest(types::kCharacteristicDeclaration,
680 range_start,
681 range_end,
682 std::move(read_by_type_cb));
683 }
684
DiscoverDescriptors(att::Handle range_start,att::Handle range_end,DescriptorCallback desc_callback,att::ResultFunction<> status_callback)685 void DiscoverDescriptors(att::Handle range_start,
686 att::Handle range_end,
687 DescriptorCallback desc_callback,
688 att::ResultFunction<> status_callback) override {
689 PW_DCHECK(range_start <= range_end);
690 PW_DCHECK(desc_callback);
691 PW_DCHECK(status_callback);
692
693 auto pdu = NewPDU(sizeof(att::FindInformationRequestParams));
694 if (!pdu) {
695 status_callback(ToResult(HostError::kOutOfMemory));
696 return;
697 }
698
699 att::PacketWriter writer(att::kFindInformationRequest, pdu.get());
700 auto* params = writer.mutable_payload<att::FindInformationRequestParams>();
701 params->start_handle =
702 pw::bytes::ConvertOrderTo(cpp20::endian::little, range_start);
703 params->end_handle =
704 pw::bytes::ConvertOrderTo(cpp20::endian::little, range_end);
705
706 auto rsp_cb = [this,
707 range_start,
708 range_end,
709 desc_cb = std::move(desc_callback),
710 res_cb = std::move(status_callback)](
711 att::Bearer::TransactionResult result) mutable {
712 if (result.is_error()) {
713 const att::Error& error = result.error_value().first;
714
715 // An Error Response code of "Attribute Not Found" indicates the end of
716 // the procedure (v5.0, Vol 3, Part G, 4.7.1).
717 if (error.is(att::ErrorCode::kAttributeNotFound)) {
718 res_cb(fit::ok());
719 return;
720 }
721
722 res_cb(fit::error(error));
723 return;
724 }
725 const att::PacketReader& rsp = result.value();
726 PW_DCHECK(rsp.opcode() == att::kFindInformationResponse);
727 TRACE_DURATION("bluetooth",
728 "gatt::Client::DiscoverDescriptors rsp_cb",
729 "size",
730 rsp.size());
731
732 if (rsp.payload_size() < sizeof(att::FindInformationResponseParams)) {
733 bt_log(DEBUG, "gatt", "received malformed Find Information response");
734 att_->ShutDown();
735 res_cb(ToResult(HostError::kPacketMalformed));
736 return;
737 }
738
739 const auto& rsp_params =
740 rsp.payload<att::FindInformationResponseParams>();
741 BufferView entries = rsp.payload_data().view(sizeof(rsp_params.format));
742
743 att::Handle last_handle;
744 bool well_formed;
745 switch (rsp_params.format) {
746 case att::UUIDType::k16Bit:
747 well_formed =
748 ProcessDescriptorDiscoveryResponse<att::UUIDType::k16Bit>(
749 range_start,
750 range_end,
751 entries,
752 desc_cb.share(),
753 &last_handle);
754 break;
755 case att::UUIDType::k128Bit:
756 well_formed =
757 ProcessDescriptorDiscoveryResponse<att::UUIDType::k128Bit>(
758 range_start,
759 range_end,
760 entries,
761 desc_cb.share(),
762 &last_handle);
763 break;
764 default:
765 bt_log(DEBUG, "gatt", "invalid information data format");
766 well_formed = false;
767 break;
768 }
769
770 if (!well_formed) {
771 att_->ShutDown();
772 res_cb(ToResult(HostError::kPacketMalformed));
773 return;
774 }
775
776 // The procedure is over if we have reached the end of the handle range.
777 if (last_handle == range_end) {
778 res_cb(fit::ok());
779 return;
780 }
781
782 // Request the next batch.
783 DiscoverDescriptors(
784 last_handle + 1, range_end, std::move(desc_cb), std::move(res_cb));
785 };
786
787 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
788 }
789
ReadRequest(att::Handle handle,ReadCallback callback)790 void ReadRequest(att::Handle handle, ReadCallback callback) override {
791 auto pdu = NewPDU(sizeof(att::ReadRequestParams));
792 if (!pdu) {
793 callback(ToResult(HostError::kOutOfMemory),
794 BufferView(),
795 /*maybe_truncated=*/false);
796 return;
797 }
798
799 att::PacketWriter writer(att::kReadRequest, pdu.get());
800 auto params = writer.mutable_payload<att::ReadRequestParams>();
801 params->handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
802
803 auto rsp_cb = [this, cb = std::move(callback)](
804 att::Bearer::TransactionResult result) {
805 if (result.is_ok()) {
806 const att::PacketReader& rsp = result.value();
807 PW_DCHECK(rsp.opcode() == att::kReadResponse);
808 bool maybe_truncated =
809 (rsp.payload_size() != att::kMaxAttributeValueLength) &&
810 (rsp.payload_size() == (mtu() - sizeof(rsp.opcode())));
811 cb(fit::ok(), rsp.payload_data(), maybe_truncated);
812 return;
813 }
814 const auto& [error, err_handle] = result.error_value();
815 bt_log(DEBUG,
816 "gatt",
817 "read request failed: %s, handle %#.4x",
818 bt_str(error),
819 err_handle);
820 cb(fit::error(error), BufferView(), /*maybe_truncated=*/false);
821 };
822
823 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
824 }
825
ReadByTypeRequest(const UUID & type,att::Handle start_handle,att::Handle end_handle,ReadByTypeCallback callback)826 void ReadByTypeRequest(const UUID& type,
827 att::Handle start_handle,
828 att::Handle end_handle,
829 ReadByTypeCallback callback) override {
830 size_t type_size = type.CompactSize(/*allow_32bit=*/false);
831 PW_CHECK(type_size == sizeof(uint16_t) || type_size == sizeof(UInt128));
832 auto pdu = NewPDU(type_size == sizeof(uint16_t)
833 ? sizeof(att::ReadByTypeRequestParams16)
834 : sizeof(att::ReadByTypeRequestParams128));
835 if (!pdu) {
836 callback(fit::error(
837 ReadByTypeError{Error(HostError::kOutOfMemory), std::nullopt}));
838 return;
839 }
840
841 att::PacketWriter writer(att::kReadByTypeRequest, pdu.get());
842 if (type_size == sizeof(uint16_t)) {
843 auto params = writer.mutable_payload<att::ReadByTypeRequestParams16>();
844 params->start_handle =
845 pw::bytes::ConvertOrderTo(cpp20::endian::little, start_handle);
846 params->end_handle =
847 pw::bytes::ConvertOrderTo(cpp20::endian::little, end_handle);
848 auto type_view = MutableBufferView(¶ms->type, sizeof(params->type));
849 type.ToBytes(&type_view, /*allow_32bit=*/false);
850 } else {
851 auto params = writer.mutable_payload<att::ReadByTypeRequestParams128>();
852 params->start_handle =
853 pw::bytes::ConvertOrderTo(cpp20::endian::little, start_handle);
854 params->end_handle =
855 pw::bytes::ConvertOrderTo(cpp20::endian::little, end_handle);
856 auto type_view = MutableBufferView(¶ms->type, sizeof(params->type));
857 type.ToBytes(&type_view, /*allow_32bit=*/false);
858 }
859
860 auto rsp_cb = [this, cb = std::move(callback), start_handle, end_handle](
861 att::Bearer::TransactionResult result) {
862 if (result.is_error()) {
863 const auto& [error, handle] = result.error_value();
864 bt_log(DEBUG,
865 "gatt",
866 "read by type request failed: %s, handle %#.4x",
867 bt_str(error),
868 handle);
869 // Only some errors have handles.
870 std::optional<att::Handle> cb_handle =
871 handle ? std::optional(handle) : std::nullopt;
872 cb(fit::error(ReadByTypeError{error, cb_handle}));
873 return;
874 }
875 const att::PacketReader& rsp = result.value();
876 PW_CHECK(rsp.opcode() == att::kReadByTypeResponse);
877 if (rsp.payload_size() < sizeof(att::ReadByTypeResponseParams)) {
878 cb(fit::error(
879 ReadByTypeError{Error(HostError::kPacketMalformed), std::nullopt}));
880 return;
881 }
882
883 const auto& params = rsp.payload<att::ReadByTypeResponseParams>();
884 // The response contains a list of attribute handle-value pairs of uniform
885 // length.
886 const size_t list_size = rsp.payload_size() - sizeof(params.length);
887 const size_t pair_size = params.length;
888
889 // Success response must:
890 // a) Specify valid pair length (at least the size of a handle).
891 // b) Have at least 1 pair (otherwise the Attribute Not Found error should
892 // have been
893 // sent).
894 // c) Have a list size that is evenly divisible by pair size.
895 if (pair_size < sizeof(att::Handle) || list_size < sizeof(att::Handle) ||
896 list_size % pair_size != 0) {
897 cb(fit::error(
898 ReadByTypeError{Error(HostError::kPacketMalformed), std::nullopt}));
899 return;
900 }
901
902 std::vector<ReadByTypeValue> attributes;
903 BufferView attr_list_view(params.attribute_data_list,
904 rsp.payload_size() - sizeof(params.length));
905 while (attr_list_view.size() >= params.length) {
906 const BufferView pair_view = attr_list_view.view(0, pair_size);
907 const att::Handle handle = pw::bytes::ConvertOrderFrom(
908 cpp20::endian::little, pair_view.To<att::Handle>());
909
910 if (handle < start_handle || handle > end_handle) {
911 bt_log(TRACE,
912 "gatt",
913 "client received read by type response with handle outside of "
914 "requested range");
915 cb(fit::error(ReadByTypeError{Error(HostError::kPacketMalformed),
916 std::nullopt}));
917 return;
918 }
919
920 if (!attributes.empty() && attributes.back().handle >= handle) {
921 bt_log(TRACE,
922 "gatt",
923 "client received read by type response with handles in "
924 "non-increasing order");
925 cb(fit::error(ReadByTypeError{Error(HostError::kPacketMalformed),
926 std::nullopt}));
927 return;
928 }
929
930 auto value_view = pair_view.view(sizeof(att::Handle));
931
932 // The value may be truncated if it maxes out the length parameter or
933 // the MTU, whichever is smaller (Core Spec v5.2, Vol 3, Part F,
934 // Sec 3.4.4).
935 const size_t mtu_max_value_size =
936 mtu() - sizeof(att::kReadByTypeResponse) -
937 sizeof(att::ReadByTypeResponseParams) - sizeof(att::Handle);
938 bool maybe_truncated =
939 (value_view.size() ==
940 std::min(static_cast<size_t>(att::kMaxReadByTypeValueLength),
941 mtu_max_value_size));
942
943 attributes.push_back(
944 ReadByTypeValue{handle, value_view, maybe_truncated});
945
946 // Advance list view to next pair (or end of list).
947 attr_list_view = attr_list_view.view(pair_size);
948 }
949 PW_CHECK(attr_list_view.size() == 0);
950
951 cb(fit::ok(std::move(attributes)));
952 };
953
954 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
955 }
956
ReadBlobRequest(att::Handle handle,uint16_t offset,ReadCallback callback)957 void ReadBlobRequest(att::Handle handle,
958 uint16_t offset,
959 ReadCallback callback) override {
960 auto pdu = NewPDU(sizeof(att::ReadBlobRequestParams));
961 if (!pdu) {
962 callback(ToResult(HostError::kOutOfMemory),
963 BufferView(),
964 /*maybe_truncated=*/false);
965 return;
966 }
967
968 att::PacketWriter writer(att::kReadBlobRequest, pdu.get());
969 auto params = writer.mutable_payload<att::ReadBlobRequestParams>();
970 params->handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
971 params->offset = pw::bytes::ConvertOrderTo(cpp20::endian::little, offset);
972
973 auto rsp_cb = [this, offset, cb = std::move(callback)](
974 att::Bearer::TransactionResult result) {
975 if (result.is_ok()) {
976 const att::PacketReader& rsp = result.value();
977 PW_DCHECK(rsp.opcode() == att::kReadBlobResponse);
978 bool maybe_truncated =
979 (static_cast<size_t>(offset) + rsp.payload_size() !=
980 att::kMaxAttributeValueLength) &&
981 (rsp.payload_data().size() == (mtu() - sizeof(att::OpCode)));
982 cb(fit::ok(), rsp.payload_data(), maybe_truncated);
983 return;
984 }
985 const auto& [error, err_handle] = result.error_value();
986 bt_log(DEBUG,
987 "gatt",
988 "read blob request failed: %s, handle: %#.4x",
989 bt_str(error),
990 err_handle);
991 cb(fit::error(error), BufferView(), /*maybe_truncated=*/false);
992 };
993
994 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
995 }
996
WriteRequest(att::Handle handle,const ByteBuffer & value,att::ResultFunction<> callback)997 void WriteRequest(att::Handle handle,
998 const ByteBuffer& value,
999 att::ResultFunction<> callback) override {
1000 const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
1001 if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
1002 bt_log(TRACE, "gatt", "write request payload exceeds MTU");
1003 callback(ToResult(HostError::kPacketMalformed));
1004 return;
1005 }
1006
1007 auto pdu = NewPDU(payload_size);
1008 if (!pdu) {
1009 callback(ToResult(HostError::kOutOfMemory));
1010 return;
1011 }
1012
1013 att::PacketWriter writer(att::kWriteRequest, pdu.get());
1014 auto params = writer.mutable_payload<att::WriteRequestParams>();
1015 params->handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
1016
1017 auto value_view =
1018 writer.mutable_payload_data().mutable_view(sizeof(att::Handle));
1019 value.Copy(&value_view);
1020
1021 auto rsp_cb = [this, cb = std::move(callback)](
1022 att::Bearer::TransactionResult result) {
1023 if (result.is_error()) {
1024 const auto& [error, err_handle] = result.error_value();
1025 bt_log(DEBUG,
1026 "gatt",
1027 "write request failed: %s, handle: %#.2x",
1028 bt_str(error),
1029 err_handle);
1030 cb(fit::error(error));
1031 return;
1032 }
1033 const att::PacketReader& rsp = result.value();
1034 PW_DCHECK(rsp.opcode() == att::kWriteResponse);
1035
1036 if (rsp.payload_size()) {
1037 att_->ShutDown();
1038 cb(ToResult(HostError::kPacketMalformed));
1039 return;
1040 }
1041
1042 cb(fit::ok());
1043 };
1044
1045 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
1046 }
1047
1048 // An internal object for storing the write queue, callback, and reliability
1049 // mode of a long write operation.
1050 struct PreparedWrite {
1051 bt::att::PrepareWriteQueue prep_write_queue;
1052 bt::att::ResultFunction<> callback;
1053 ReliableMode reliable_mode;
1054 };
1055
ExecutePrepareWrites(att::PrepareWriteQueue prep_write_queue,ReliableMode reliable_mode,att::ResultFunction<> callback)1056 void ExecutePrepareWrites(att::PrepareWriteQueue prep_write_queue,
1057 ReliableMode reliable_mode,
1058 att::ResultFunction<> callback) override {
1059 PreparedWrite new_request;
1060 new_request.prep_write_queue = std::move(prep_write_queue);
1061 new_request.callback = std::move(callback);
1062 new_request.reliable_mode = std::move(reliable_mode);
1063 long_write_queue_.push(std::move(new_request));
1064
1065 // If the |long_write_queue| has a pending request, then appending this
1066 // request will be sufficient, otherwise kick off the request.
1067 if (long_write_queue_.size() == 1) {
1068 ProcessWriteQueue(std::move(long_write_queue_.front()));
1069 }
1070 }
1071
ProcessWriteQueue(PreparedWrite prepared_write)1072 void ProcessWriteQueue(PreparedWrite prepared_write) {
1073 if (!prepared_write.prep_write_queue.empty()) {
1074 att::QueuedWrite prep_write_request =
1075 std::move(prepared_write.prep_write_queue.front());
1076 // A copy of the |prep_write_request| is made to pass into the capture
1077 // list for |prep_write_cb|. It will be used to validate the echoed blob.
1078 auto prep_write_copy = att::QueuedWrite(prep_write_request.handle(),
1079 prep_write_request.offset(),
1080 prep_write_request.value());
1081 prepared_write.prep_write_queue.pop();
1082
1083 auto prep_write_cb = [this,
1084 prep_write = std::move(prepared_write),
1085 requested_blob = std::move(prep_write_copy)](
1086 att::Result<> status,
1087 const ByteBuffer& blob) mutable {
1088 // If the write fails, cancel the prep writes and then move on to the
1089 // next long write in the queue. The device will echo the value written
1090 // in the blob, according to the spec (Vol 3, Part G, 4.9.4). The offset
1091 // and value will be verified if the requested |prep_write.mode| is
1092 // enabled (Vol 3, Part G, 4.9.5).
1093
1094 if (prep_write.reliable_mode == ReliableMode::kEnabled) {
1095 if (blob.size() < sizeof(att::PrepareWriteResponseParams)) {
1096 // The response blob is malformed.
1097 status = ToResult(HostError::kNotReliable);
1098 } else {
1099 uint16_t blob_offset = pw::bytes::ConvertOrderFrom(
1100 cpp20::endian::little,
1101 blob.ReadMember<&att::PrepareWriteResponseParams::offset>());
1102 auto blob_value =
1103 blob.view(sizeof(att::PrepareWriteResponseParams));
1104 if ((blob_offset != requested_blob.offset()) ||
1105 !(blob_value == requested_blob.value())) {
1106 status = ToResult(HostError::kNotReliable);
1107 }
1108 }
1109 }
1110
1111 if (status.is_error()) {
1112 auto exec_write_cb = [this,
1113 callback = std::move(prep_write.callback),
1114 prep_write_status =
1115 status](att::Result<>) mutable {
1116 // In this case return the original failure status. This
1117 // effectively overrides the ExecuteWrite status.
1118 callback(prep_write_status);
1119 // Now that this request is complete, remove it from the overall
1120 // queue.
1121 PW_DCHECK(!long_write_queue_.empty());
1122 long_write_queue_.pop();
1123
1124 if (long_write_queue_.size() > 0) {
1125 ProcessWriteQueue(std::move(long_write_queue_.front()));
1126 }
1127 };
1128
1129 ExecuteWriteRequest(att::ExecuteWriteFlag::kCancelAll,
1130 std::move(exec_write_cb));
1131
1132 return;
1133 }
1134
1135 ProcessWriteQueue(std::move(prep_write));
1136 };
1137
1138 PrepareWriteRequest(prep_write_request.handle(),
1139 prep_write_request.offset(),
1140 std::move(prep_write_request.value()),
1141 std::move(prep_write_cb));
1142 }
1143 // End of this write, send and prepare for next item in overall write queue
1144 else {
1145 auto exec_write_cb = [this,
1146 callback = std::move(prepared_write.callback)](
1147 att::Result<> status) mutable {
1148 callback(status);
1149 // Now that this request is complete, remove it from the overall
1150 // queue.
1151 PW_DCHECK(!long_write_queue_.empty());
1152 long_write_queue_.pop();
1153
1154 // If the super queue still has any long writes left to execute,
1155 // initiate them
1156 if (long_write_queue_.size() > 0) {
1157 ProcessWriteQueue(std::move(long_write_queue_.front()));
1158 }
1159 };
1160
1161 ExecuteWriteRequest(att::ExecuteWriteFlag::kWritePending,
1162 std::move(exec_write_cb));
1163 }
1164 }
1165
PrepareWriteRequest(att::Handle handle,uint16_t offset,const ByteBuffer & part_value,PrepareCallback callback)1166 void PrepareWriteRequest(att::Handle handle,
1167 uint16_t offset,
1168 const ByteBuffer& part_value,
1169 PrepareCallback callback) override {
1170 const size_t payload_size =
1171 sizeof(att::PrepareWriteRequestParams) + part_value.size();
1172 if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
1173 bt_log(TRACE, "gatt", "prepare write request payload exceeds MTU");
1174 callback(ToResult(HostError::kPacketMalformed), BufferView());
1175 return;
1176 }
1177
1178 auto pdu = NewPDU(payload_size);
1179 if (!pdu) {
1180 callback(ToResult(HostError::kOutOfMemory), BufferView());
1181 return;
1182 }
1183
1184 att::PacketWriter writer(att::kPrepareWriteRequest, pdu.get());
1185 auto params = writer.mutable_payload<att::PrepareWriteRequestParams>();
1186 params->handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
1187 params->offset = pw::bytes::ConvertOrderTo(cpp20::endian::little, offset);
1188
1189 auto header_size = sizeof(att::Handle) + sizeof(uint16_t);
1190 auto value_view = writer.mutable_payload_data().mutable_view(header_size);
1191 part_value.Copy(&value_view);
1192
1193 auto rsp_cb =
1194 [cb = std::move(callback)](att::Bearer::TransactionResult result) {
1195 if (result.is_ok()) {
1196 const att::PacketReader& rsp = result.value();
1197 PW_DCHECK(rsp.opcode() == att::kPrepareWriteResponse);
1198 cb(fit::ok(), rsp.payload_data());
1199 return;
1200 }
1201 const auto& [error, err_handle] = result.error_value();
1202 bt_log(DEBUG,
1203 "gatt",
1204 "prepare write request failed: %s, handle:"
1205 "%#.4x",
1206 bt_str(error),
1207 err_handle);
1208 cb(fit::error(error), BufferView());
1209 };
1210
1211 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
1212 }
1213
ExecuteWriteRequest(att::ExecuteWriteFlag flag,att::ResultFunction<> callback)1214 void ExecuteWriteRequest(att::ExecuteWriteFlag flag,
1215 att::ResultFunction<> callback) override {
1216 const size_t payload_size = sizeof(att::ExecuteWriteRequestParams);
1217 if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
1218 // This really shouldn't happen because we aren't consuming any actual
1219 // payload here, but just in case...
1220 bt_log(TRACE, "gatt", "execute write request size exceeds MTU");
1221 callback(ToResult(HostError::kPacketMalformed));
1222 return;
1223 }
1224
1225 auto pdu = NewPDU(payload_size);
1226 if (!pdu) {
1227 callback(ToResult(HostError::kOutOfMemory));
1228 return;
1229 }
1230
1231 att::PacketWriter writer(att::kExecuteWriteRequest, pdu.get());
1232 auto params = writer.mutable_payload<att::ExecuteWriteRequestParams>();
1233 params->flags = flag;
1234
1235 auto rsp_cb = [this, cb = std::move(callback)](
1236 att::Bearer::TransactionResult result) {
1237 if (result.is_ok()) {
1238 const att::PacketReader& rsp = result.value();
1239 PW_DCHECK(rsp.opcode() == att::kExecuteWriteResponse);
1240
1241 if (rsp.payload_size()) {
1242 att_->ShutDown();
1243 cb(ToResult(HostError::kPacketMalformed));
1244 return;
1245 }
1246
1247 cb(fit::ok());
1248 return;
1249 }
1250 const att::Error& error = result.error_value().first;
1251 bt_log(DEBUG, "gatt", "execute write request failed: %s", bt_str(error));
1252 cb(fit::error(error));
1253 };
1254
1255 att_->StartTransaction(std::move(pdu), BindCallback(std::move(rsp_cb)));
1256 }
1257
WriteWithoutResponse(att::Handle handle,const ByteBuffer & value,att::ResultFunction<> callback)1258 void WriteWithoutResponse(att::Handle handle,
1259 const ByteBuffer& value,
1260 att::ResultFunction<> callback) override {
1261 const size_t payload_size = sizeof(att::WriteRequestParams) + value.size();
1262 if (sizeof(att::OpCode) + payload_size > att_->mtu()) {
1263 bt_log(DEBUG, "gatt", "write request payload exceeds MTU");
1264 callback(ToResult(HostError::kFailed));
1265 return;
1266 }
1267
1268 auto pdu = NewPDU(payload_size);
1269 if (!pdu) {
1270 callback(ToResult(HostError::kOutOfMemory));
1271 return;
1272 }
1273
1274 att::PacketWriter writer(att::kWriteCommand, pdu.get());
1275 auto params = writer.mutable_payload<att::WriteRequestParams>();
1276 params->handle = pw::bytes::ConvertOrderTo(cpp20::endian::little, handle);
1277
1278 auto value_view =
1279 writer.mutable_payload_data().mutable_view(sizeof(att::Handle));
1280 value.Copy(&value_view);
1281
1282 [[maybe_unused]] bool _ = att_->SendWithoutResponse(std::move(pdu));
1283 callback(fit::ok());
1284 }
1285
SetNotificationHandler(NotificationCallback handler)1286 void SetNotificationHandler(NotificationCallback handler) override {
1287 notification_handler_ = std::move(handler);
1288 }
1289
1290 // Wraps |callback| in a TransactionCallback that only runs if this Client is
1291 // still alive.
BindCallback(att::Bearer::TransactionCallback callback)1292 att::Bearer::TransactionCallback BindCallback(
1293 att::Bearer::TransactionCallback callback) {
1294 return [self = weak_self_.GetWeakPtr(),
1295 cb = std::move(callback)](auto rsp) mutable {
1296 if (self.is_alive()) {
1297 cb(rsp);
1298 }
1299 };
1300 }
1301
1302 att::Bearer::WeakPtr att_;
1303 att::Bearer::HandlerId not_handler_id_;
1304 att::Bearer::HandlerId ind_handler_id_;
1305
1306 NotificationCallback notification_handler_;
1307 // |long_write_queue_| contains long write requests, their
1308 // associated callbacks and reliable write modes.
1309 // Series of PrepareWrites are executed or cancelled at the same time so
1310 // this is used to block while a single series is processed.
1311 //
1312 // While the top element is processed, the |PrepareWriteQueue| and callback
1313 // will be empty and will be popped once the queue is cancelled or executed.
1314 // Following the processing of each queue, the client will automatically
1315 // process the next queue in the |long_write_queue_|.
1316 std::queue<PreparedWrite> long_write_queue_;
1317 WeakSelf<Client> weak_self_;
1318
1319 BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Impl);
1320 };
1321
1322 // static
Create(att::Bearer::WeakPtr bearer)1323 std::unique_ptr<Client> Client::Create(att::Bearer::WeakPtr bearer) {
1324 return std::make_unique<Impl>(std::move(bearer));
1325 }
1326
1327 } // namespace bt::gatt
1328