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/sdp/data_element.h"
16
17 #include <cpp-string/string_printf.h>
18 #include <endian.h>
19
20 #include <algorithm>
21 #include <set>
22 #include <vector>
23
24 #include "pw_bluetooth_sapphire/internal/host/common/log.h"
25
26 // Returns true if |url| is a valid URI.
IsValidUrl(const std::string & url)27 bool IsValidUrl(const std::string& url) {
28 // Pulled from [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986).
29 // See Section 2.2 for the set of reserved characters.
30 // See Section 2.3 for the set of unreserved characters.
31 constexpr char kValidUrlChars[] =
32 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!#$&'("
33 ")*+,/:;=?@[]";
34 return url.find_first_not_of(kValidUrlChars) == std::string::npos;
35 }
36
37 namespace bt::sdp {
38
39 namespace {
40
41 // Size Descriptor occupies the lowest 3 bits of the header byte.
42 // v5.0, Vol 3, Part B, Sec 3.3.
43 constexpr uint8_t kDataElementSizeTypeMask = 0x07;
44
SizeToSizeType(size_t size)45 DataElement::Size SizeToSizeType(size_t size) {
46 switch (size) {
47 case 1:
48 return DataElement::Size::kOneByte;
49 case 2:
50 return DataElement::Size::kTwoBytes;
51 case 4:
52 return DataElement::Size::kFourBytes;
53 case 8:
54 return DataElement::Size::kEightBytes;
55 case 16:
56 return DataElement::Size::kSixteenBytes;
57 default:
58 BT_PANIC("invalid data element size: %zu", size);
59 }
60 return DataElement::Size::kNextFour;
61 }
62
AggregateSize(const std::vector<DataElement> & aggregate)63 size_t AggregateSize(const std::vector<DataElement>& aggregate) {
64 size_t total_size = 0;
65 for (const auto& elem : aggregate) {
66 total_size += elem.WriteSize();
67 }
68 return total_size;
69 }
70
WriteLength(MutableByteBuffer * buf,size_t length)71 size_t WriteLength(MutableByteBuffer* buf, size_t length) {
72 if (length <= std::numeric_limits<uint8_t>::max()) {
73 uint8_t val = static_cast<uint8_t>(length);
74 buf->Write(&val, sizeof(val));
75 return sizeof(uint8_t);
76 }
77
78 if (length <= std::numeric_limits<uint16_t>::max()) {
79 buf->WriteObj(htobe16(static_cast<uint16_t>(length)));
80 return sizeof(uint16_t);
81 }
82
83 if (length <= std::numeric_limits<uint32_t>::max()) {
84 buf->WriteObj(htobe32(static_cast<uint32_t>(length)));
85 return sizeof(uint32_t);
86 }
87
88 return 0;
89 }
90
91 } // namespace
92
DataElement()93 DataElement::DataElement() : type_(Type::kNull), size_(Size::kOneByte) {}
94
DataElement(const DataElement & other)95 DataElement::DataElement(const DataElement& other)
96 : type_(other.type_), size_(other.size_) {
97 switch (type_) {
98 case Type::kNull:
99 return;
100 case Type::kUnsignedInt:
101 uint_value_ = other.uint_value_;
102 return;
103 case Type::kBoolean:
104 case Type::kSignedInt:
105 int_value_ = other.int_value_;
106 return;
107 case Type::kUuid:
108 uuid_ = other.uuid_;
109 return;
110 case Type::kString:
111 case Type::kUrl:
112 bytes_ = DynamicByteBuffer(other.bytes_);
113 return;
114 case Type::kSequence:
115 case Type::kAlternative:
116 for (const auto& it : other.aggregate_) {
117 aggregate_.emplace_back(DataElement(it));
118 }
119 return;
120 }
121 }
122
123 template <>
Set(uint8_t value)124 void DataElement::Set<uint8_t>(uint8_t value) {
125 type_ = Type::kUnsignedInt;
126 size_ = SizeToSizeType(sizeof(uint8_t));
127 uint_value_ = value;
128 }
129
130 template <>
Set(uint16_t value)131 void DataElement::Set<uint16_t>(uint16_t value) {
132 type_ = Type::kUnsignedInt;
133 size_ = SizeToSizeType(sizeof(uint16_t));
134 uint_value_ = value;
135 }
136
137 template <>
Set(uint32_t value)138 void DataElement::Set<uint32_t>(uint32_t value) {
139 type_ = Type::kUnsignedInt;
140 size_ = SizeToSizeType(sizeof(uint32_t));
141 uint_value_ = value;
142 }
143
144 template <>
Set(uint64_t value)145 void DataElement::Set<uint64_t>(uint64_t value) {
146 type_ = Type::kUnsignedInt;
147 size_ = SizeToSizeType(sizeof(uint64_t));
148 uint_value_ = value;
149 }
150
151 template <>
Set(int8_t value)152 void DataElement::Set<int8_t>(int8_t value) {
153 type_ = Type::kSignedInt;
154 size_ = SizeToSizeType(sizeof(int8_t));
155 int_value_ = value;
156 }
157
158 template <>
Set(int16_t value)159 void DataElement::Set<int16_t>(int16_t value) {
160 type_ = Type::kSignedInt;
161 size_ = SizeToSizeType(sizeof(int16_t));
162 int_value_ = value;
163 }
164
165 template <>
Set(int32_t value)166 void DataElement::Set<int32_t>(int32_t value) {
167 type_ = Type::kSignedInt;
168 size_ = SizeToSizeType(sizeof(int32_t));
169 int_value_ = value;
170 }
171
172 template <>
Set(int64_t value)173 void DataElement::Set<int64_t>(int64_t value) {
174 type_ = Type::kSignedInt;
175 size_ = SizeToSizeType(sizeof(int64_t));
176 int_value_ = value;
177 }
178
179 template <>
Set(bool value)180 void DataElement::Set<bool>(bool value) {
181 type_ = Type::kBoolean;
182 size_ = Size::kOneByte;
183 int_value_ = (value ? 1 : 0);
184 }
185
186 template <>
Set(std::nullptr_t)187 void DataElement::Set<std::nullptr_t>(std::nullptr_t) {
188 type_ = Type::kNull;
189 size_ = Size::kOneByte;
190 }
191
192 template <>
Set(UUID value)193 void DataElement::Set<UUID>(UUID value) {
194 type_ = Type::kUuid;
195 size_ = SizeToSizeType(value.CompactSize());
196 uuid_ = value;
197 }
198
Set(const bt::DynamicByteBuffer & value)199 void DataElement::Set(const bt::DynamicByteBuffer& value) {
200 type_ = Type::kString;
201 SetVariableSize(value.size());
202 bytes_ = DynamicByteBuffer(value);
203 }
204
Set(const std::string & value)205 void DataElement::Set(const std::string& value) {
206 type_ = Type::kString;
207 SetVariableSize(value.size());
208 bytes_ = DynamicByteBuffer(value);
209 }
210
Set(std::vector<DataElement> && value)211 void DataElement::Set(std::vector<DataElement>&& value) {
212 type_ = Type::kSequence;
213 aggregate_ = std::move(value);
214 SetVariableSize(AggregateSize(aggregate_));
215 }
216
SetUrl(const std::string & url)217 void DataElement::SetUrl(const std::string& url) {
218 if (!IsValidUrl(url)) {
219 bt_log(WARN, "sdp", "Invalid URL in SetUrl: %s", url.c_str());
220 return;
221 }
222
223 type_ = Type::kUrl;
224 SetVariableSize(url.size());
225 bytes_ = DynamicByteBuffer(url);
226 }
227
SetAlternative(std::vector<DataElement> && items)228 void DataElement::SetAlternative(std::vector<DataElement>&& items) {
229 type_ = Type::kAlternative;
230 aggregate_ = std::move(items);
231 SetVariableSize(AggregateSize(aggregate_));
232 }
233
234 template <>
Get() const235 std::optional<uint8_t> DataElement::Get<uint8_t>() const {
236 if (type_ == Type::kUnsignedInt && size_ == SizeToSizeType(sizeof(uint8_t))) {
237 return static_cast<uint8_t>(uint_value_);
238 }
239
240 return std::nullopt;
241 }
242
243 template <>
Get() const244 std::optional<uint16_t> DataElement::Get<uint16_t>() const {
245 if (type_ == Type::kUnsignedInt &&
246 size_ == SizeToSizeType(sizeof(uint16_t))) {
247 return static_cast<uint16_t>(uint_value_);
248 }
249
250 return std::nullopt;
251 }
252
253 template <>
Get() const254 std::optional<uint32_t> DataElement::Get<uint32_t>() const {
255 if (type_ == Type::kUnsignedInt &&
256 size_ == SizeToSizeType(sizeof(uint32_t))) {
257 return static_cast<uint32_t>(uint_value_);
258 }
259
260 return std::nullopt;
261 }
262
263 template <>
Get() const264 std::optional<uint64_t> DataElement::Get<uint64_t>() const {
265 if (type_ == Type::kUnsignedInt &&
266 size_ == SizeToSizeType(sizeof(uint64_t))) {
267 return uint_value_;
268 }
269
270 return std::nullopt;
271 }
272
273 template <>
Get() const274 std::optional<int8_t> DataElement::Get<int8_t>() const {
275 if (type_ == Type::kSignedInt && size_ == SizeToSizeType(sizeof(int8_t))) {
276 return static_cast<int8_t>(int_value_);
277 }
278
279 return std::nullopt;
280 }
281
282 template <>
Get() const283 std::optional<int16_t> DataElement::Get<int16_t>() const {
284 if (type_ == Type::kSignedInt && size_ == SizeToSizeType(sizeof(int16_t))) {
285 return static_cast<int16_t>(int_value_);
286 }
287
288 return std::nullopt;
289 }
290
291 template <>
Get() const292 std::optional<int32_t> DataElement::Get<int32_t>() const {
293 if (type_ == Type::kSignedInt && size_ == SizeToSizeType(sizeof(int32_t))) {
294 return static_cast<int32_t>(int_value_);
295 }
296
297 return std::nullopt;
298 ;
299 }
300
301 template <>
Get() const302 std::optional<int64_t> DataElement::Get<int64_t>() const {
303 if (type_ == Type::kSignedInt && size_ == SizeToSizeType(sizeof(int64_t))) {
304 return static_cast<int64_t>(int_value_);
305 }
306
307 return std::nullopt;
308 }
309
310 template <>
Get() const311 std::optional<bool> DataElement::Get<bool>() const {
312 if (type_ != Type::kBoolean) {
313 return std::nullopt;
314 }
315
316 return (int_value_ == 1);
317 }
318
319 template <>
Get() const320 std::optional<std::nullptr_t> DataElement::Get<std::nullptr_t>() const {
321 if (type_ != Type::kNull) {
322 return std::nullopt;
323 }
324
325 return nullptr;
326 }
327
328 template <>
Get() const329 std::optional<bt::DynamicByteBuffer> DataElement::Get<bt::DynamicByteBuffer>()
330 const {
331 if (type_ != Type::kString) {
332 return std::nullopt;
333 }
334
335 return DynamicByteBuffer(bytes_);
336 }
337
338 template <>
Get() const339 std::optional<std::string> DataElement::Get<std::string>() const {
340 if (type_ != Type::kString) {
341 return std::nullopt;
342 }
343
344 return std::string(reinterpret_cast<const char*>(bytes_.data()),
345 bytes_.size());
346 }
347
348 template <>
Get() const349 std::optional<UUID> DataElement::Get<UUID>() const {
350 if (type_ != Type::kUuid) {
351 return std::nullopt;
352 }
353
354 return uuid_;
355 }
356
357 template <>
358 std::optional<std::vector<DataElement>>
Get() const359 DataElement::Get<std::vector<DataElement>>() const {
360 if (type_ != Type::kSequence) {
361 return std::nullopt;
362 }
363
364 std::vector<DataElement> aggregate_copy;
365 for (const auto& it : aggregate_) {
366 aggregate_copy.emplace_back(it.Clone());
367 }
368
369 return aggregate_copy;
370 }
371
GetUrl() const372 std::optional<std::string> DataElement::GetUrl() const {
373 if (type_ != Type::kUrl) {
374 return std::nullopt;
375 }
376
377 return std::string(reinterpret_cast<const char*>(bytes_.data()),
378 bytes_.size());
379 }
380
SetVariableSize(size_t length)381 void DataElement::SetVariableSize(size_t length) {
382 if (length <= std::numeric_limits<uint8_t>::max()) {
383 size_ = Size::kNextOne;
384 } else if (length <= std::numeric_limits<uint16_t>::max()) {
385 size_ = Size::kNextTwo;
386 } else {
387 size_ = Size::kNextFour;
388 }
389 }
390
Read(DataElement * elem,const ByteBuffer & buffer)391 size_t DataElement::Read(DataElement* elem, const ByteBuffer& buffer) {
392 if (buffer.size() == 0) {
393 return 0;
394 }
395 Type type_desc = static_cast<Type>(buffer[0] & kTypeMask);
396 Size size_desc = static_cast<Size>(buffer[0] & kDataElementSizeTypeMask);
397 size_t data_bytes = 0;
398 size_t bytes_read = 1;
399 BufferView cursor = buffer.view(bytes_read);
400 switch (size_desc) {
401 case DataElement::Size::kOneByte:
402 case DataElement::Size::kTwoBytes:
403 case DataElement::Size::kFourBytes:
404 case DataElement::Size::kEightBytes:
405 case DataElement::Size::kSixteenBytes:
406 if (type_desc != Type::kNull) {
407 data_bytes = (1 << static_cast<uint8_t>(size_desc));
408 } else {
409 data_bytes = 0;
410 }
411 break;
412 case DataElement::Size::kNextOne: {
413 if (cursor.size() < sizeof(uint8_t)) {
414 return 0;
415 }
416 data_bytes = cursor.To<uint8_t>();
417 bytes_read += sizeof(uint8_t);
418 break;
419 }
420 case DataElement::Size::kNextTwo: {
421 if (cursor.size() < sizeof(uint16_t)) {
422 return 0;
423 }
424 data_bytes = be16toh(cursor.To<uint16_t>());
425 bytes_read += sizeof(uint16_t);
426 break;
427 }
428 case DataElement::Size::kNextFour: {
429 if (cursor.size() < sizeof(uint32_t)) {
430 return 0;
431 }
432 data_bytes = be32toh(cursor.To<uint32_t>());
433 bytes_read += sizeof(uint32_t);
434 break;
435 }
436 }
437 cursor = buffer.view(bytes_read);
438 if (cursor.size() < data_bytes) {
439 return 0;
440 }
441
442 switch (type_desc) {
443 case Type::kNull: {
444 if (size_desc != Size::kOneByte) {
445 return 0;
446 }
447 elem->Set(nullptr);
448 return bytes_read + data_bytes;
449 }
450 case Type::kBoolean: {
451 if (size_desc != Size::kOneByte) {
452 return 0;
453 }
454 elem->Set(cursor.To<uint8_t>() != 0);
455 return bytes_read + data_bytes;
456 }
457 case Type::kUnsignedInt: {
458 if (size_desc == Size::kOneByte) {
459 elem->Set(cursor.To<uint8_t>());
460 } else if (size_desc == Size::kTwoBytes) {
461 elem->Set(be16toh(cursor.To<uint16_t>()));
462 } else if (size_desc == Size::kFourBytes) {
463 elem->Set(be32toh(cursor.To<uint32_t>()));
464 } else if (size_desc == Size::kEightBytes) {
465 elem->Set(be64toh(cursor.To<uint64_t>()));
466 } else {
467 // TODO(fxbug.dev/42078670): support 128-bit uints
468 // Invalid size.
469 return 0;
470 }
471 return bytes_read + data_bytes;
472 }
473 case Type::kSignedInt: {
474 if (size_desc == Size::kOneByte) {
475 elem->Set(cursor.To<int8_t>());
476 } else if (size_desc == Size::kTwoBytes) {
477 elem->Set(be16toh(cursor.To<int16_t>()));
478 } else if (size_desc == Size::kFourBytes) {
479 elem->Set(be32toh(cursor.To<int32_t>()));
480 } else if (size_desc == Size::kEightBytes) {
481 elem->Set(be64toh(cursor.To<int64_t>()));
482 } else {
483 // TODO(fxbug.dev/42078670): support 128-bit uints
484 // Invalid size.
485 return 0;
486 }
487 return bytes_read + data_bytes;
488 }
489 case Type::kUuid: {
490 if (size_desc == Size::kTwoBytes) {
491 elem->Set(UUID(be16toh(cursor.To<uint16_t>())));
492 } else if (size_desc == Size::kFourBytes) {
493 elem->Set(UUID(be32toh(cursor.To<uint32_t>())));
494 } else if (size_desc == Size::kSixteenBytes) {
495 StaticByteBuffer<16> uuid_bytes;
496 // UUID expects these to be in little-endian order.
497 cursor.Copy(&uuid_bytes, 0, 16);
498 std::reverse(uuid_bytes.mutable_data(), uuid_bytes.mutable_data() + 16);
499 UUID uuid(uuid_bytes);
500 elem->Set(uuid);
501 } else {
502 return 0;
503 }
504 return bytes_read + data_bytes;
505 }
506 case Type::kString: {
507 if (static_cast<uint8_t>(size_desc) < 5) {
508 return 0;
509 }
510 bt::DynamicByteBuffer str(data_bytes);
511 str.Write(cursor.data(), data_bytes);
512 elem->Set(str);
513 return bytes_read + data_bytes;
514 }
515 case Type::kSequence:
516 case Type::kAlternative: {
517 if (static_cast<uint8_t>(size_desc) < 5) {
518 return 0;
519 }
520 BufferView sequence_buf = cursor.view(0, data_bytes);
521 size_t remaining = data_bytes;
522 BT_DEBUG_ASSERT(sequence_buf.size() == data_bytes);
523
524 std::vector<DataElement> seq;
525 while (remaining > 0) {
526 DataElement next;
527 size_t used = Read(&next, sequence_buf.view(data_bytes - remaining));
528 if (used == 0 || used > remaining) {
529 return 0;
530 }
531 seq.push_back(std::move(next));
532 remaining -= used;
533 }
534 BT_DEBUG_ASSERT(remaining == 0);
535 if (type_desc == Type::kAlternative) {
536 elem->SetAlternative(std::move(seq));
537 } else {
538 elem->Set(std::move(seq));
539 }
540 return bytes_read + data_bytes;
541 }
542 case Type::kUrl: {
543 if (static_cast<uint8_t>(size_desc) < 5) {
544 return 0;
545 }
546 std::string str(cursor.data(), cursor.data() + data_bytes);
547 elem->SetUrl(str);
548 return bytes_read + data_bytes;
549 }
550 }
551 return 0;
552 }
553
WriteSize() const554 size_t DataElement::WriteSize() const {
555 switch (type_) {
556 case Type::kNull:
557 return 1;
558 case Type::kBoolean:
559 return 2;
560 case Type::kUnsignedInt:
561 case Type::kSignedInt:
562 case Type::kUuid:
563 return 1 + (1 << static_cast<uint8_t>(size_));
564 case Type::kString:
565 case Type::kUrl:
566 return 1 + (1 << (static_cast<uint8_t>(size_) - 5)) + bytes_.size();
567 case Type::kSequence:
568 case Type::kAlternative:
569 return 1 + (1 << (static_cast<uint8_t>(size_) - 5)) +
570 AggregateSize(aggregate_);
571 }
572 }
573
Write(MutableByteBuffer * buffer) const574 size_t DataElement::Write(MutableByteBuffer* buffer) const {
575 if (buffer->size() < WriteSize()) {
576 bt_log(TRACE,
577 "sdp",
578 "not enough space in buffer (%zu < %zu)",
579 buffer->size(),
580 WriteSize());
581 return 0;
582 }
583
584 uint8_t type_and_size =
585 static_cast<uint8_t>(type_) | static_cast<uint8_t>(size_);
586 buffer->Write(&type_and_size, 1);
587 size_t pos = 1;
588
589 MutableBufferView cursor = buffer->mutable_view(pos);
590
591 switch (type_) {
592 case Type::kNull: {
593 return pos;
594 }
595 case Type::kBoolean: {
596 uint8_t val = int_value_ != 0 ? 1 : 0;
597 cursor.Write(&val, sizeof(val));
598 pos += 1;
599 return pos;
600 }
601 case Type::kUnsignedInt: {
602 if (size_ == Size::kOneByte) {
603 uint8_t val = static_cast<uint8_t>(uint_value_);
604 cursor.Write(reinterpret_cast<uint8_t*>(&val), sizeof(val));
605 pos += sizeof(val);
606 } else if (size_ == Size::kTwoBytes) {
607 cursor.WriteObj(htobe16(static_cast<uint16_t>(uint_value_)));
608 pos += sizeof(uint16_t);
609 } else if (size_ == Size::kFourBytes) {
610 cursor.WriteObj(htobe32(static_cast<uint32_t>(uint_value_)));
611 pos += sizeof(uint32_t);
612 } else if (size_ == Size::kEightBytes) {
613 uint64_t val = htobe64(uint_value_);
614 cursor.Write(reinterpret_cast<uint8_t*>(&val), sizeof(val));
615 pos += sizeof(val);
616 }
617 return pos;
618 }
619 case Type::kSignedInt: {
620 if (size_ == Size::kOneByte) {
621 int8_t val = static_cast<int8_t>(int_value_);
622 cursor.Write(reinterpret_cast<uint8_t*>(&val), sizeof(val));
623 pos += sizeof(val);
624 } else if (size_ == Size::kTwoBytes) {
625 cursor.WriteObj(htobe16(static_cast<int16_t>(int_value_)));
626 pos += sizeof(uint16_t);
627 } else if (size_ == Size::kFourBytes) {
628 cursor.WriteObj(htobe32(static_cast<int32_t>(int_value_)));
629 pos += sizeof(uint32_t);
630 } else if (size_ == Size::kEightBytes) {
631 int64_t val = htobe64(static_cast<int64_t>(int_value_));
632 cursor.Write(reinterpret_cast<uint8_t*>(&val), sizeof(val));
633 pos += sizeof(val);
634 }
635 return pos;
636 }
637 case Type::kUuid: {
638 size_t written = uuid_.ToBytes(&cursor);
639 BT_DEBUG_ASSERT(written);
640 // SDP is big-endian, so reverse.
641 std::reverse(cursor.mutable_data(), cursor.mutable_data() + written);
642 pos += written;
643 return pos;
644 }
645 case Type::kString:
646 case Type::kUrl: {
647 size_t used = WriteLength(&cursor, bytes_.size());
648 BT_DEBUG_ASSERT(used);
649 pos += used;
650 cursor.Write(bytes_.data(), bytes_.size(), used);
651 pos += bytes_.size();
652 return pos;
653 }
654 case Type::kSequence:
655 case Type::kAlternative: {
656 size_t used = WriteLength(&cursor, AggregateSize(aggregate_));
657 BT_DEBUG_ASSERT(used);
658 pos += used;
659 cursor = cursor.mutable_view(used);
660 for (const auto& elem : aggregate_) {
661 used = elem.Write(&cursor);
662 BT_DEBUG_ASSERT(used);
663 pos += used;
664 cursor = cursor.mutable_view(used);
665 }
666 return pos;
667 }
668 }
669 return 0;
670 }
671
At(size_t idx) const672 const DataElement* DataElement::At(size_t idx) const {
673 if ((type_ != Type::kSequence && type_ != Type::kAlternative) ||
674 (idx >= aggregate_.size())) {
675 return nullptr;
676 }
677 return &aggregate_[idx];
678 }
679
ToString() const680 std::string DataElement::ToString() const {
681 switch (type_) {
682 case Type::kNull:
683 return std::string("Null");
684 case Type::kBoolean:
685 return bt_lib_cpp_string::StringPrintf("Boolean(%s)",
686 int_value_ ? "true" : "false");
687 case Type::kUnsignedInt:
688 return bt_lib_cpp_string::StringPrintf(
689 "UnsignedInt:%zu(%lu)", WriteSize() - 1, uint_value_);
690 case Type::kSignedInt:
691 return bt_lib_cpp_string::StringPrintf(
692 "SignedInt:%zu(%ld)", WriteSize() - 1, int_value_);
693 case Type::kUuid:
694 return bt_lib_cpp_string::StringPrintf("UUID(%s)",
695 uuid_.ToString().c_str());
696 case Type::kString:
697 return bt_lib_cpp_string::StringPrintf(
698 "String(%s)", bytes_.Printable(0, bytes_.size()).c_str());
699 case Type::kUrl:
700 return bt_lib_cpp_string::StringPrintf(
701 "Url(%s)", bytes_.Printable(0, bytes_.size()).c_str());
702 case Type::kSequence: {
703 std::string str;
704 for (const auto& it : aggregate_) {
705 str += it.ToString() + " ";
706 }
707 return bt_lib_cpp_string::StringPrintf("Sequence { %s}", str.c_str());
708 }
709 case Type::kAlternative: {
710 std::string str;
711 for (const auto& it : aggregate_) {
712 str += it.ToString() + " ";
713 }
714 return bt_lib_cpp_string::StringPrintf("Alternatives { %s}", str.c_str());
715 }
716 default:
717 bt_log(TRACE,
718 "sdp",
719 "unhandled type (%hhu) in ToString()",
720 static_cast<unsigned char>(type_));
721 // Fallthrough to unknown.
722 }
723
724 return "(unknown)";
725 }
726 } // namespace bt::sdp
727