| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/data_element.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sdp/sdp.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" |
| |
| namespace bt::sdp { |
| namespace { |
| |
| template <std::size_t N> |
| DynamicByteBuffer make_dynamic_byte_buffer(std::array<uint8_t, N> bytes) { |
| DynamicByteBuffer ret(N); |
| ret.Write(bytes.data(), N); |
| return ret; |
| } |
| |
| TEST(DataElementTest, CreateIsNull) { |
| DataElement elem; |
| EXPECT_EQ(DataElement::Type::kNull, elem.type()); |
| EXPECT_TRUE(elem.Get<std::nullptr_t>()); |
| EXPECT_EQ(nullptr, *elem.Get<std::nullptr_t>()); |
| |
| StaticByteBuffer expected(0x00); |
| DynamicByteBuffer buf(1); |
| EXPECT_EQ(1u, elem.Write(&buf)); |
| EXPECT_TRUE(ContainersEqual(expected, buf)); |
| } |
| |
| TEST(DataElementTest, SetAndGet) { |
| DataElement elem; |
| |
| elem.Set(uint8_t{5}); |
| |
| EXPECT_TRUE(elem.Get<uint8_t>()); |
| EXPECT_EQ(5u, *elem.Get<uint8_t>()); |
| EXPECT_FALSE(elem.Get<int16_t>()); |
| |
| elem.Set(std::string("Fuchsiađź’–")); |
| |
| EXPECT_FALSE(elem.Get<uint8_t>()); |
| EXPECT_TRUE(elem.Get<std::string>()); |
| EXPECT_EQ(std::string("Fuchsiađź’–"), *elem.Get<std::string>()); |
| } |
| |
| TEST(DataElementTest, Read) { |
| StaticByteBuffer buf( |
| 0x25, // Type (4: String) & Size (5: in an additional byte) = 0b00100 101 |
| 0x0B, // Bytes |
| 'F', |
| 'u', |
| 'c', |
| 'h', |
| 's', |
| 'i', |
| 'a', |
| 0xF0, |
| 0x9F, |
| 0x92, |
| 0x96, // String |
| 0xDE, |
| 0xAD, |
| 0xBE, |
| 0xEF // Extra data (shouldn't be parsed) |
| ); |
| |
| DataElement elem; |
| EXPECT_EQ(13u, DataElement::Read(&elem, buf)); |
| |
| EXPECT_EQ(DataElement::Type::kString, elem.type()); |
| EXPECT_EQ(std::string("Fuchsiađź’–"), *elem.Get<std::string>()); |
| |
| // Invalid - 0xDE: 0x11011 110 = 37 (invalid) + 6 (2 following byte size) |
| EXPECT_EQ(0u, DataElement::Read(&elem, buf.view(13))); |
| |
| // elem shouldn't have been touched |
| EXPECT_EQ(DataElement::Type::kString, elem.type()); |
| EXPECT_EQ(std::string("Fuchsiađź’–"), *elem.Get<std::string>()); |
| } |
| |
| TEST(DataElementTest, ReadInvalidType) { |
| StaticByteBuffer buf( |
| 0xFD, // Type (Invalid) & Size (5: in an additional byte) = 0b11111 101 |
| 0x0B, // Bytes |
| 'F', |
| 'u', |
| 'c', |
| 'h', |
| 's', |
| 'i', |
| 'a', |
| 0xF0, |
| 0x9F, |
| 0x92, |
| 0x96 // String |
| ); |
| |
| DataElement elem; |
| EXPECT_EQ(0u, DataElement::Read(&elem, buf)); |
| } |
| |
| TEST(DataElementTest, ReadUUID) { |
| StaticByteBuffer buf( |
| 0x19, // Type (3: UUID) & Size (1: two bytes) = 0b00011 001 |
| 0x01, |
| 0x00 // L2CAP |
| ); |
| |
| DataElement elem; |
| EXPECT_EQ(3u, DataElement::Read(&elem, buf)); |
| EXPECT_EQ(DataElement::Type::kUuid, elem.type()); |
| EXPECT_EQ(UUID(uint16_t{0x0100}), *elem.Get<UUID>()); |
| |
| StaticByteBuffer buf2( |
| 0x1A, // Type (3: UUID) & Size (2: four bytes) = 0b00011 010 |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04); |
| |
| EXPECT_EQ(5u, DataElement::Read(&elem, buf2)); |
| EXPECT_EQ(DataElement::Type::kUuid, elem.type()); |
| EXPECT_EQ(UUID(uint32_t{0x01020304}), *elem.Get<UUID>()); |
| |
| StaticByteBuffer buf3( |
| 0x1B, // Type (3: UUID) & Size (3: eight bytes) = 0b00011 011 |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04, |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04, |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04, |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04); |
| |
| EXPECT_EQ(0u, DataElement::Read(&elem, buf3)); |
| |
| StaticByteBuffer buf4( |
| 0x1C, // Type (3: UUID) & Size (3: eight bytes) = 0b00011 100 |
| 0x01, |
| 0x02, |
| 0x03, |
| 0x04, |
| 0x05, |
| 0x06, |
| 0x07, |
| 0x08, |
| 0x09, |
| 0x0A, |
| 0x0B, |
| 0x0C, |
| 0x0D, |
| 0x0E, |
| 0x0F, |
| 0x10); |
| |
| EXPECT_EQ(17u, DataElement::Read(&elem, buf4)); |
| EXPECT_EQ(DataElement::Type::kUuid, elem.type()); |
| // UInt128 in UUID is little-endian |
| EXPECT_EQ(UUID({0x10, |
| 0x0F, |
| 0x0E, |
| 0x0D, |
| 0x0C, |
| 0x0B, |
| 0x0A, |
| 0x09, |
| 0x08, |
| 0x07, |
| 0x06, |
| 0x05, |
| 0x04, |
| 0x03, |
| 0x02, |
| 0x01}), |
| *elem.Get<UUID>()); |
| } |
| |
| TEST(DataElementTest, Write) { |
| // This represents a plausible attribute_lists parameter of a |
| // SDP_ServiceSearchAttributeResponse PDU for an SPP service. |
| std::vector<DataElement> attribute_list; |
| |
| // SerialPort from Assigned Numbers |
| std::vector<DataElement> service_class_list; |
| service_class_list.emplace_back(DataElement(UUID(uint16_t{0x1101}))); |
| DataElement service_class_value(std::move(service_class_list)); |
| attribute_list.emplace_back(DataElement(kServiceClassIdList)); |
| attribute_list.emplace_back(std::move(service_class_value)); |
| |
| // Protocol Descriptor List |
| |
| std::vector<DataElement> protocol_list_value; |
| |
| // ( L2CAP, PSM=RFCOMM ) |
| std::vector<DataElement> protocol_l2cap; |
| protocol_l2cap.emplace_back(DataElement(protocol::kL2CAP)); |
| protocol_l2cap.emplace_back(DataElement(uint16_t{0x0003})); // RFCOMM |
| |
| protocol_list_value.emplace_back(DataElement(std::move(protocol_l2cap))); |
| |
| // ( RFCOMM, CHANNEL=1 ) |
| std::vector<DataElement> protocol_rfcomm; |
| protocol_rfcomm.push_back(DataElement(protocol::kRFCOMM)); |
| protocol_rfcomm.push_back(DataElement(uint8_t{1})); // Server Channel = 1 |
| |
| protocol_list_value.emplace_back(DataElement(std::move(protocol_rfcomm))); |
| |
| attribute_list.emplace_back(DataElement(kProtocolDescriptorList)); |
| attribute_list.emplace_back(DataElement(std::move(protocol_list_value))); |
| |
| // Bluetooth Profile Descriptor List |
| std::vector<DataElement> profile_sequence_list; |
| std::vector<DataElement> spp_sequence; |
| spp_sequence.push_back(DataElement(UUID(uint16_t{0x1101}))); |
| spp_sequence.push_back(DataElement(uint16_t{0x0102})); |
| |
| profile_sequence_list.emplace_back(std::move(spp_sequence)); |
| |
| attribute_list.push_back(DataElement(kBluetoothProfileDescriptorList)); |
| attribute_list.push_back((DataElement(std::move(profile_sequence_list)))); |
| |
| // A custom attribute that has a uint64_t in it. |
| attribute_list.emplace_back(DataElement(UUID(uint16_t(0xF00D)))); |
| attribute_list.emplace_back(DataElement(uint64_t(0xB0BAC0DECAFEFACE))); |
| |
| DataElement attribute_lists_elem(std::move(attribute_list)); |
| |
| // clang-format off |
| StaticByteBuffer expected( |
| 0x35, 0x35, // Sequence uint8 41 bytes |
| 0x09, // uint16_t type |
| UpperBits(kServiceClassIdList), LowerBits(kServiceClassIdList), |
| 0x35, 0x03, // Sequence uint8 3 bytes |
| 0x19, // UUID (16 bits) |
| 0x11, 0x01, // Serial Port from assigned numbers |
| 0x09, // uint16_t type |
| UpperBits(kProtocolDescriptorList), LowerBits(kProtocolDescriptorList), |
| 0x35, 0x0F, // Sequence uint8 15 bytes |
| 0x35, 0x06, // Sequence uint8 6 bytes |
| 0x19, // Type: UUID (16 bits) |
| 0x01, 0x00, // L2CAP UUID |
| 0x09, // Type: uint16_t |
| 0x00, 0x03, // RFCOMM PSM |
| 0x35, 0x05, // Sequence uint8 5 bytes |
| 0x19, // Type: UUID (16 bits) |
| 0x00, 0x03, // RFCOMM UUID |
| 0x08, // Type: uint8_t |
| 0x01, // RFCOMM Channel 1 |
| 0x09, // uint16_t type |
| UpperBits(kBluetoothProfileDescriptorList), |
| LowerBits(kBluetoothProfileDescriptorList), |
| 0x35, 0x08, // Sequence uint8 8 bytes |
| 0x35, 0x06, // Sequence uint8 6 bytes |
| 0x19, // Type: UUID (16 bits) |
| 0x11, 0x01, // 0x1101 (SPP) |
| 0x09, // Type: uint16_t |
| 0x01, 0x02, // v1.2 |
| 0x19, // Type: UUID (16 bits) |
| 0xF0, 0x0D, // Custom attribute ID, |
| 0x0B, // uint64_t type |
| 0xB0, 0xBA, 0xC0, 0xDE, 0xCA, 0xFE, 0xFA, 0xCE // Data for uint64_t |
| ); |
| // clang-format on |
| |
| DynamicByteBuffer block(55); |
| |
| size_t written = attribute_lists_elem.Write(&block); |
| |
| EXPECT_EQ(expected.size(), written); |
| EXPECT_EQ(written, attribute_lists_elem.WriteSize()); |
| EXPECT_TRUE(ContainersEqual(expected, block)); |
| } |
| |
| TEST(DataElementTest, ReadSequence) { |
| // clang-format off |
| StaticByteBuffer buf( |
| 0x35, 0x08, // Sequence with 1 byte length (8) |
| 0x09, 0x00, 0x01, // uint16_t: 1 |
| 0x0A, 0x00, 0x00, 0x00, 0x02 // uint32_t: 2 |
| ); |
| // clang-format on |
| |
| DataElement elem; |
| EXPECT_EQ(buf.size(), DataElement::Read(&elem, buf)); |
| EXPECT_EQ(DataElement::Type::kSequence, elem.type()); |
| auto* it = elem.At(0); |
| EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type()); |
| EXPECT_EQ(1u, *it->Get<uint16_t>()); |
| |
| it = elem.At(1); |
| EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type()); |
| EXPECT_EQ(2u, *it->Get<uint32_t>()); |
| } |
| |
| TEST(DataElementTest, ReadNestedSequence) { |
| StaticByteBuffer buf(0x35, |
| 0x1C, // Sequence uint8 28 bytes |
| // Sequence 0 |
| 0x35, |
| 0x08, // Sequence uint8 8 bytes |
| 0x09, |
| 0x00, |
| 0x00, // Element: uint16_t (0) |
| 0x0A, |
| 0xFE, |
| 0xED, |
| 0xBE, |
| 0xEF, // Element: uint32_t (0xFEEDBEEF) |
| // Sequence 1 |
| 0x35, |
| 0x10, // Sequence uint8 16 bytes |
| 0x09, |
| 0x00, |
| 0x00, // Element: uint16_t (0) |
| 0x0A, |
| 0xFE, |
| 0xDB, |
| 0xAC, |
| 0x01, // Element: uint32_t (0xFEDBAC01) |
| 0x09, |
| 0x00, |
| 0x01, // Handle: uint16_t (1 = kServiceClassIdList) |
| 0x35, |
| 0x03, |
| 0x19, |
| 0x11, |
| 0x01 // Element: Sequence (3) { UUID(0x1101) } |
| ); |
| |
| DataElement elem; |
| EXPECT_EQ(buf.size(), DataElement::Read(&elem, buf)); |
| EXPECT_EQ(DataElement::Type::kSequence, elem.type()); |
| auto* outer_it = elem.At(0); |
| EXPECT_EQ(DataElement::Type::kSequence, outer_it->type()); |
| |
| auto* it = outer_it->At(0); |
| EXPECT_EQ(0u, *it->Get<uint16_t>()); |
| |
| it = outer_it->At(1); |
| EXPECT_EQ(0xfeedbeef, *it->Get<uint32_t>()); |
| |
| outer_it = elem.At(1); |
| EXPECT_EQ(DataElement::Type::kSequence, outer_it->type()); |
| |
| it = outer_it->At(0); |
| EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type()); |
| EXPECT_EQ(0u, *it->Get<uint16_t>()); |
| |
| it = outer_it->At(1); |
| EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type()); |
| EXPECT_EQ(0xfedbac01, *it->Get<uint32_t>()); |
| |
| it = outer_it->At(2); |
| EXPECT_EQ(DataElement::Type::kUnsignedInt, it->type()); |
| EXPECT_EQ(1u, *it->Get<uint16_t>()); |
| |
| it = outer_it->At(3); |
| EXPECT_EQ(DataElement::Type::kSequence, it->type()); |
| |
| auto inner_it = it->At(0); |
| EXPECT_EQ(DataElement::Type::kUuid, inner_it->type()); |
| } |
| |
| TEST(DataElementTest, ToString) { |
| EXPECT_EQ("Null", DataElement().ToString()); |
| EXPECT_EQ("Boolean(true)", DataElement(true).ToString()); |
| EXPECT_EQ("UnsignedInt:1(27)", DataElement(uint8_t{27}).ToString()); |
| EXPECT_EQ("SignedInt:4(-54321)", DataElement(int32_t{-54321}).ToString()); |
| EXPECT_EQ("UUID(00000100-0000-1000-8000-00805f9b34fb)", |
| DataElement(protocol::kL2CAP).ToString()); |
| EXPECT_EQ("String(fuchsiađź’–)", |
| DataElement(std::string("fuchsiađź’–")).ToString()); |
| // This test and the following one print invalid unicode strings by replacing |
| // nonASCII characters |
| // with '.'. Somewhat confusingly, individual bytes of invalid unicode |
| // sequences can be valid |
| // ASCII bytes. In particular, the '\x28' in the invalid unicode sequences |
| // below is a perfectly valid '(' in ASCII, so it prints as that. |
| EXPECT_EQ( |
| "String(ABC.(XYZ)", |
| DataElement(std::string("ABC\xc3\x28XYZ")).ToString()); // Invalid UTF8. |
| EXPECT_EQ("String(ABC.(XYZ....)", |
| DataElement(std::string("ABC\xc3\x28XYZđź’–")) |
| .ToString()); // Invalid UTF8 means the whole |
| // string must be treated as ASCII. |
| DataElement elem; |
| elem.SetUrl(std::string("http://example.com")); |
| EXPECT_EQ("Url(http://example.com)", elem.ToString()); |
| std::vector<DataElement> strings; |
| strings.emplace_back(std::string("hello")); |
| strings.emplace_back(std::string("sapphiređź”·")); |
| EXPECT_EQ("Sequence { String(hello) String(sapphiređź”·) }", |
| DataElement(std::move(strings)).ToString()); |
| DataElement alts; |
| strings.clear(); |
| strings.emplace_back(std::string("hello")); |
| strings.emplace_back(std::string("sapphiređź”·")); |
| alts.SetAlternative(std::move(strings)); |
| EXPECT_EQ("Alternatives { String(hello) String(sapphiređź”·) }", |
| alts.ToString()); |
| } |
| |
| TEST(DataElementTest, SetAndGetUrl) { |
| DataElement elem; |
| elem.SetUrl(std::string("https://foobar.dev")); |
| |
| EXPECT_FALSE(elem.Get<std::string>()); |
| EXPECT_EQ(DataElement::Type::kUrl, elem.type()); |
| EXPECT_EQ(std::string("https://foobar.dev"), *elem.GetUrl()); |
| } |
| |
| TEST(DataElementTest, SetInvalidUrlStringIsNoOp) { |
| DataElement elem; |
| EXPECT_EQ(DataElement::Type::kNull, elem.type()); |
| elem.SetUrl(std::string("https://foobarđź”·.dev")); |
| |
| EXPECT_FALSE(elem.GetUrl()); |
| EXPECT_EQ(DataElement::Type::kNull, elem.type()); |
| } |
| |
| TEST(DataElementTest, ReadUrlFromBuffer) { |
| StaticByteBuffer buf( |
| 0x45, // Type (8: URL) & Size (5: in an additional byte) = 0b01000 101 |
| 0x0B, // 11 Bytes |
| 'F', |
| 'u', |
| 'c', |
| 'h', |
| 's', |
| 'i', |
| 'a', |
| '.', |
| 'd', |
| 'e', |
| 'v', // URL String |
| 0xDE, |
| 0xAD, |
| 0xBE, |
| 0xEF // Extra data (shouldn't be parsed) |
| ); |
| |
| DataElement read_elem; |
| EXPECT_EQ(13u, DataElement::Read(&read_elem, buf)); |
| |
| EXPECT_EQ(DataElement::Type::kUrl, read_elem.type()); |
| EXPECT_EQ(std::string("Fuchsia.dev"), *read_elem.GetUrl()); |
| } |
| |
| TEST(DataElementTest, WriteUrlToBuffer) { |
| DataElement url_elem; |
| url_elem.SetUrl(std::string("Test.com")); |
| |
| auto expected = StaticByteBuffer( |
| 0x45, // Type (8: URL) & Size (5: in an additional byte) = 0b01000 101 |
| 0x08, // 8 Bytes |
| 'T', |
| 'e', |
| 's', |
| 't', |
| '.', |
| 'c', |
| 'o', |
| 'm' // URL String |
| ); |
| |
| DynamicByteBuffer write_buf(10); |
| |
| size_t written = url_elem.Write(&write_buf); |
| |
| EXPECT_EQ(expected.size(), written); |
| EXPECT_EQ(written, url_elem.WriteSize()); |
| EXPECT_TRUE(ContainersEqual(expected, write_buf)); |
| } |
| |
| TEST(DataElementTest, SetAndGetStrings) { |
| auto buffer_set_string = make_dynamic_byte_buffer<10>( |
| {'s', 'e', 't', ' ', 's', 't', 'r', 'i', 'n', 'g'}); |
| std::string string_set_string("set string"); |
| |
| auto buffer_set_buffer = make_dynamic_byte_buffer<10>( |
| {'s', 'e', 't', ' ', 'b', 'u', 'f', 'f', 'e', 'r'}); |
| std::string string_set_buffer("set buffer"); |
| |
| DataElement elem_set_string; |
| elem_set_string.Set(string_set_string); |
| |
| EXPECT_EQ(elem_set_string.Get<std::string>(), string_set_string); |
| EXPECT_EQ(elem_set_string.Get<DynamicByteBuffer>(), buffer_set_string); |
| |
| DataElement elem_set_buffer; |
| elem_set_buffer.Set(string_set_buffer); |
| |
| EXPECT_EQ(elem_set_buffer.Get<DynamicByteBuffer>(), buffer_set_buffer); |
| EXPECT_EQ(elem_set_buffer.Get<std::string>(), string_set_buffer); |
| } |
| |
| } // namespace |
| } // namespace bt::sdp |