| // Copyright 2017 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/common/advertising_data.h" |
| |
| #include <limits> |
| #include <variant> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| |
| namespace bt { |
| namespace { |
| |
| constexpr uint16_t kGattUuid = 0x1801; |
| constexpr uint16_t kEddystoneUuid = 0xFEAA; |
| constexpr uint16_t kHeartRateServiceUuid = 0x180D; |
| |
| constexpr uint16_t kId1As16 = 0x0212; |
| constexpr uint16_t kId2As16 = 0x1122; |
| |
| constexpr size_t kRandomDataSize = 100; |
| |
| TEST(GAP_AdvertisingDataTest, MakeEmpty) { |
| AdvertisingData data; |
| |
| EXPECT_EQ(0u, data.CalculateBlockSize()); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, EncodeKnownURI) { |
| AdvertisingData data; |
| EXPECT_TRUE(data.AddUri("https://abc.xyz")); |
| |
| auto bytes = |
| CreateStaticByteBuffer(0x0B, 0x24, 0x17, '/', '/', 'a', 'b', 'c', '.', 'x', 'y', 'z'); |
| |
| EXPECT_EQ(bytes.size(), data.CalculateBlockSize()); |
| DynamicByteBuffer block(data.CalculateBlockSize()); |
| data.WriteBlock(&block, std::nullopt); |
| EXPECT_TRUE(ContainersEqual(bytes, block)); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, EncodeUnknownURI) { |
| AdvertisingData data; |
| EXPECT_TRUE(data.AddUri("flubs:xyz")); |
| |
| auto bytes = |
| CreateStaticByteBuffer(0x0B, 0x24, 0x01, 'f', 'l', 'u', 'b', 's', ':', 'x', 'y', 'z'); |
| |
| size_t block_size = data.CalculateBlockSize(); |
| EXPECT_EQ(bytes.size(), block_size); |
| DynamicByteBuffer block(block_size); |
| data.WriteBlock(&block, std::nullopt); |
| EXPECT_TRUE(ContainersEqual(bytes, block)); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, CompressServiceUUIDs) { |
| AdvertisingData data; |
| std::unordered_set<UUID> uuids{UUID(kId1As16), UUID(kId2As16)}; |
| for (auto& uuid : uuids) { |
| SCOPED_TRACE(bt_str(uuid)); |
| EXPECT_TRUE(data.AddServiceUuid(uuid)); |
| } |
| |
| uint8_t expected_block_size = 1 // length byte |
| + 1 // type byte |
| + (sizeof(uint16_t) * 2); // 2 16-bit UUIDs |
| EXPECT_EQ(expected_block_size, data.CalculateBlockSize()); |
| |
| StaticByteBuffer expected_header{expected_block_size - 1, DataType::kIncomplete16BitServiceUuids}; |
| |
| DynamicByteBuffer block(expected_block_size); |
| data.WriteBlock(&block, std::nullopt); |
| |
| EXPECT_TRUE(ContainersEqual(expected_header, block.view(/*pos=*/0, /*size=*/2))); |
| auto to_uuid = [](const ByteBuffer& b, size_t pos) { |
| return UUID(b.view(pos, /*size=*/2).As<uint16_t>()); |
| }; |
| EXPECT_TRUE(uuids.find(to_uuid(block, 2)) != uuids.end()); |
| EXPECT_TRUE(uuids.find(to_uuid(block, 4)) != uuids.end()); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, ParseBlock) { |
| auto bytes = CreateStaticByteBuffer( |
| // Complete 16-bit UUIDs |
| 0x05, 0x03, 0x12, 0x02, 0x22, 0x11, |
| // Incomplete list of 32-bit UUIDs |
| 0x05, 0x04, 0x34, 0x12, 0x34, 0x12, |
| // Local name |
| 0x09, 0x09, 'T', 'e', 's', 't', 0xF0, 0x9F, 0x92, 0x96, |
| // TX Power |
| 0x02, 0x0A, 0x8F); |
| |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| |
| EXPECT_EQ(3u, data->service_uuids().size()); |
| EXPECT_TRUE(data->local_name()); |
| EXPECT_EQ("Test💖", *(data->local_name())); |
| EXPECT_TRUE(data->tx_power()); |
| EXPECT_EQ(-113, *(data->tx_power())); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, ParseBlockUnknownDataType) { |
| AdvertisingData expected_ad; |
| uint8_t lower_byte = 0x12, upper_byte = 0x22; |
| uint16_t uuid_value = (upper_byte << 8) + lower_byte; |
| // The only field present in the expected AD is one complete 16-bit UUID. |
| EXPECT_TRUE(expected_ad.AddServiceUuid(UUID(uuid_value))); |
| |
| auto bytes = StaticByteBuffer{ |
| // Complete 16-bit UUIDs |
| 0x03, 0x03, lower_byte, upper_byte, |
| // 0x40, the second octet, is not a recognized DataType (see common/supplement_data.h). |
| 0x05, 0x40, 0x34, 0x12, 0x34, 0x12}; |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| |
| // The second field of `bytes` was valid (in that its length byte matched its length), but its |
| // Data Type was unknown, so it should be ignored (i.e. the only field in the `data` should be the |
| // single 16-bit UUID, matching expected AD). |
| EXPECT_EQ(expected_ad, *data); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, ParseBlockNameTooLong) { |
| // A block with a name of exactly kMaxNameLength (==248) bytes should be parsed correctly. |
| { |
| auto leading_bytes = StaticByteBuffer<2>{kMaxNameLength + 1, DataType::kCompleteLocalName}; |
| auto bytes = DynamicByteBuffer(kMaxNameLength + 2); |
| bytes.Write(leading_bytes); |
| DynamicByteBuffer name(kMaxNameLength); |
| name.Fill('a'); |
| bytes.Write(name, /*pos=*/2); |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| EXPECT_EQ(data->local_name(), std::string(kMaxNameLength, 'a')); |
| } |
| // A block with a name of kMaxNameLength+1 (==249) bytes should be rejected. |
| { |
| auto leading_bytes = StaticByteBuffer<2>{kMaxNameLength + 2, DataType::kCompleteLocalName}; |
| auto bytes = DynamicByteBuffer(kMaxNameLength + 3); |
| bytes.Write(leading_bytes); |
| DynamicByteBuffer name(kMaxNameLength + 1); |
| name.Fill('a'); |
| bytes.Write(name, /*pos=*/2); |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_FALSE(data.has_value()); |
| } |
| } |
| |
| TEST(GAP_AdvertisingDataTest, ManufacturerZeroLength) { |
| auto bytes = CreateStaticByteBuffer( |
| // Complete 16-bit UUIDs |
| 0x05, 0x03, 0x12, 0x02, 0x22, 0x11, |
| // Manufacturer Data with no data |
| 0x03, 0xFF, 0x34, 0x12); |
| |
| EXPECT_EQ(0u, AdvertisingData().manufacturer_data_ids().size()); |
| |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| |
| EXPECT_EQ(1u, data->manufacturer_data_ids().count(0x1234)); |
| EXPECT_EQ(0u, data->manufacturer_data(0x1234).size()); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, ServiceData) { |
| // A typical Eddystone-URL beacon advertisement |
| // to "https://fuchsia.cl" |
| auto bytes = CreateStaticByteBuffer( |
| // Complete 16-bit UUIDs, 0xFEAA |
| 0x03, 0x03, 0xAA, 0xFE, |
| // Eddystone Service (0xFEAA) Data: |
| 0x10, 0x16, 0xAA, 0xFE, |
| 0x10, // Eddystone-Uri type |
| 0xEE, // TX Power level -18dBm |
| 0x03, // "https://" |
| 'f', 'u', 'c', 'h', 's', 'i', 'a', '.', 'c', 'l'); |
| |
| EXPECT_EQ(0u, AdvertisingData().service_data_uuids().size()); |
| |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| |
| UUID eddystone(uint16_t{0xFEAA}); |
| |
| EXPECT_EQ(1u, data->service_data_uuids().size()); |
| EXPECT_EQ(13u, data->service_data(eddystone).size()); |
| |
| EXPECT_TRUE(ContainersEqual(bytes.view(8), data->service_data(eddystone))); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, DecodeServiceDataWithIncompleteUuid) { |
| auto service_data = |
| StaticByteBuffer(0x02, // Length |
| static_cast<uint8_t>(DataType::kServiceData16Bit), // Data type |
| 0xAA // First byte of incomplete UUID |
| ); |
| |
| EXPECT_FALSE(AdvertisingData::FromBytes(service_data)); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, Equality) { |
| AdvertisingData one, two; |
| |
| UUID gatt(kGattUuid); |
| UUID eddy(kEddystoneUuid); |
| |
| // Service UUIDs |
| EXPECT_EQ(two, one); |
| EXPECT_TRUE(one.AddServiceUuid(gatt)); |
| EXPECT_NE(two, one); |
| EXPECT_TRUE(two.AddServiceUuid(gatt)); |
| EXPECT_EQ(two, one); |
| |
| // Even when the bytes are the same but from different places |
| auto bytes = CreateStaticByteBuffer(0x01, 0x02, 0x03, 0x04); |
| auto same = CreateStaticByteBuffer(0x01, 0x02, 0x03, 0x04); |
| EXPECT_TRUE(two.SetManufacturerData(0x0123, bytes.view())); |
| EXPECT_NE(two, one); |
| EXPECT_TRUE(one.SetManufacturerData(0x0123, same.view())); |
| EXPECT_EQ(two, one); |
| |
| // When TX Power is different |
| two.SetTxPower(-34); |
| EXPECT_NE(two, one); |
| one.SetTxPower(-30); |
| EXPECT_NE(two, one); |
| one.SetTxPower(-34); |
| EXPECT_EQ(two, one); |
| |
| // Even if the fields were added in different orders |
| AdvertisingData three, four; |
| EXPECT_TRUE(three.AddServiceUuid(eddy)); |
| EXPECT_TRUE(three.AddServiceUuid(gatt)); |
| EXPECT_NE(three, four); |
| |
| EXPECT_TRUE(four.AddServiceUuid(gatt)); |
| EXPECT_TRUE(four.AddServiceUuid(eddy)); |
| EXPECT_EQ(three, four); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, Copy) { |
| UUID gatt(kGattUuid); |
| UUID eddy(kEddystoneUuid); |
| StaticByteBuffer<kRandomDataSize> rand_data; |
| rand_data.FillWithRandomBytes(); |
| |
| AdvertisingData source; |
| EXPECT_TRUE(source.AddUri("http://fuchsia.cl")); |
| EXPECT_TRUE(source.AddUri("https://ru.st")); |
| EXPECT_TRUE(source.SetManufacturerData(0x0123, rand_data.view())); |
| EXPECT_TRUE(source.AddServiceUuid(gatt)); |
| EXPECT_TRUE(source.AddServiceUuid(eddy)); |
| |
| AdvertisingData dest; |
| source.Copy(&dest); |
| |
| EXPECT_EQ(source, dest); |
| |
| // Modifying the source shouldn't mess with the copy |
| EXPECT_TRUE(source.SetLocalName("fuchsia")); |
| EXPECT_FALSE(dest.local_name()); |
| |
| auto bytes = CreateStaticByteBuffer(0x01, 0x02, 0x03); |
| EXPECT_TRUE(source.SetManufacturerData(0x0123, bytes.view())); |
| EXPECT_TRUE(ContainersEqual(rand_data, dest.manufacturer_data(0x0123))); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, Move) { |
| UUID gatt(kGattUuid); |
| UUID eddy(kEddystoneUuid); |
| StaticByteBuffer<kRandomDataSize> rand_data; |
| rand_data.FillWithRandomBytes(); |
| |
| UUID heart_rate_uuid(kHeartRateServiceUuid); |
| int8_t tx_power = 18; // arbitrary TX power |
| uint16_t appearance = 0x4567; // arbitrary appearance value |
| AdvertisingData source; |
| EXPECT_TRUE(source.SetLocalName("test")); |
| source.SetTxPower(tx_power); |
| source.SetAppearance(appearance); |
| EXPECT_TRUE(source.AddUri("http://fuchsia.cl")); |
| EXPECT_TRUE(source.AddUri("https://ru.st")); |
| EXPECT_TRUE(source.SetManufacturerData(0x0123, rand_data.view())); |
| EXPECT_TRUE(source.AddServiceUuid(gatt)); |
| EXPECT_TRUE(source.AddServiceUuid(eddy)); |
| EXPECT_TRUE(source.SetServiceData(heart_rate_uuid, rand_data.view())); |
| |
| auto verify_advertising_data = [&](const AdvertisingData& dest, const char* type) { |
| SCOPED_TRACE(type); |
| // Dest should have the data we set. |
| EXPECT_EQ("test", dest.local_name().value()); |
| EXPECT_EQ(tx_power, dest.tx_power().value()); |
| EXPECT_EQ(appearance, dest.appearance().value()); |
| EXPECT_EQ(std::unordered_set<std::string>({"http://fuchsia.cl", "https://ru.st"}), dest.uris()); |
| EXPECT_TRUE(ContainersEqual(rand_data, dest.manufacturer_data(0x0123))); |
| EXPECT_EQ(std::unordered_set<UUID>({gatt, eddy}), dest.service_uuids()); |
| EXPECT_TRUE(ContainersEqual(rand_data, dest.service_data(heart_rate_uuid))); |
| }; |
| |
| AdvertisingData move_constructed(std::move(source)); |
| |
| // source should be empty. |
| EXPECT_EQ(AdvertisingData(), source); |
| verify_advertising_data(move_constructed, "move_constructed"); |
| |
| AdvertisingData move_assigned{}; |
| move_assigned = std::move(move_constructed); |
| EXPECT_EQ(AdvertisingData(), move_constructed); |
| verify_advertising_data(move_assigned, "move_assigned"); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, Uris) { |
| // The encoding scheme is represented by the first UTF-8 code-point in the URI string. Per |
| // https://www.bluetooth.com/specifications/assigned-numbers/uri-scheme-name-string-mapping/, |
| // 0xBA is the highest code point corresponding to an encoding scheme. However, 0xBA > 0x7F, so |
| // representing too-large encoding schemes (i.e. code-points > 0xBA) in UTF-8 requires two bytes. |
| const uint8_t kLargestKnownSchemeByte1 = 0xC2, kLargestKnownSchemeByte2 = 0xBA; |
| // These bytes represent the (valid) UTF-8 code point for the (unknown encoding scheme) U+00BB. |
| const uint8_t kUnknownSchemeByte1 = 0xC2, kUnknownSchemeByte2 = 0xBB; |
| auto bytes = CreateStaticByteBuffer( |
| // Uri: "https://abc.xyz" |
| 0x0B, DataType::kURI, 0x17, '/', '/', 'a', 'b', 'c', '.', 'x', 'y', 'z', |
| // Empty URI should be ignored: |
| 0x01, DataType::kURI, |
| // Uri: "flubs:abc" |
| 0x0B, DataType::kURI, 0x01, 'f', 'l', 'u', 'b', 's', ':', 'a', 'b', 'c', |
| // Uri: "ms-settings-cloudstorage:flub" |
| 0x07, DataType::kURI, kLargestKnownSchemeByte1, kLargestKnownSchemeByte2, 'f', 'l', 'u', 'b', |
| // Invalid URI should be ignored - UTF-8 U+00BB doesn't correspond to an encoding scheme. |
| 0x07, DataType::kURI, kUnknownSchemeByte1, kUnknownSchemeByte2, 'f', 'l', 'u', 'b', |
| // Invalid URI should be ignored - UTF-8 U+0000 doesn't correspond to an encoding scheme. |
| 0x03, DataType::kURI, 0x00, 0x00); |
| |
| std::optional<AdvertisingData> data = AdvertisingData::FromBytes(bytes); |
| ASSERT_TRUE(data.has_value()); |
| |
| auto uris = data->uris(); |
| EXPECT_EQ(3u, uris.size()); |
| |
| EXPECT_TRUE(std::find(uris.begin(), uris.end(), "https://abc.xyz") != uris.end()); |
| EXPECT_TRUE(std::find(uris.begin(), uris.end(), "flubs:abc") != uris.end()); |
| EXPECT_TRUE(std::find(uris.begin(), uris.end(), "ms-settings-cloudstorage:flub") != uris.end()); |
| } |
| |
| // Tests writing a fully populated |AdvertisingData| to |
| // an output buffer succeeds. |
| TEST(GAP_AdvertisingDataTest, WriteBlockSuccess) { |
| AdvertisingData data; |
| |
| data.SetTxPower(4); |
| data.SetAppearance(0x4567); |
| EXPECT_TRUE(data.SetLocalName("fuchsia")); |
| |
| auto bytes = CreateStaticByteBuffer(0x01, 0x02, 0x03); |
| EXPECT_TRUE(data.SetManufacturerData(0x0123, bytes.view())); |
| |
| auto service_uuid = UUID(kId1As16); |
| auto service_bytes = CreateStaticByteBuffer(0x01, 0x02); |
| EXPECT_TRUE(data.AddServiceUuid(service_uuid)); |
| EXPECT_TRUE(data.SetServiceData(service_uuid, service_bytes.view())); |
| |
| EXPECT_TRUE(data.AddUri("http://fuchsia.cl")); |
| |
| DynamicByteBuffer write_buf(data.CalculateBlockSize()); |
| EXPECT_TRUE(data.WriteBlock(&write_buf, std::nullopt)); |
| |
| auto expected_buf = CreateStaticByteBuffer( |
| 0x02, 0x0a, 0x04, // tx_power_level_: 4 |
| 0x03, 0x19, 0x67, 0x45, // appearance_: 0x4567 |
| 0x08, 0x09, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, // local_name_: "fuchsia" |
| 0x06, 0xff, 0x23, 0x01, 0x01, 0x02, 0x03, // manufacturer_data_ |
| 0x05, 0x16, 0x12, 0x02, 0x01, 0x02, // service_data_ |
| 0x0e, 0x24, 0x16, 0x2f, 0x2f, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x2e, 0x63, 0x6c, |
| 0x03, 0x02, 0x12, 0x02); // uris_ |
| EXPECT_TRUE(ContainersEqual(expected_buf, write_buf)); |
| } |
| |
| // Tests writing |AdvertisingData| to an output buffer that |
| // is too small fails gracefully and returns early. |
| TEST(GAP_AdvertisingDataTest, WriteBlockSmallBufError) { |
| AdvertisingData data; |
| |
| data.SetTxPower(4); |
| data.SetAppearance(0x4567); |
| EXPECT_TRUE(data.SetLocalName("fuchsia")); |
| |
| DynamicByteBuffer write_buf(data.CalculateBlockSize() - 1); |
| // The buffer is too small. No write should occur, and should return false. |
| EXPECT_FALSE(data.WriteBlock(&write_buf, std::nullopt)); |
| } |
| |
| // Tests writing a fully populated |AdvertisingData| with provided flags to |
| // an output buffer succeeds. |
| TEST(GAP_AdvertisingDataTest, WriteBlockWithFlagsSuccess) { |
| AdvertisingData data; |
| |
| data.SetTxPower(4); |
| data.SetAppearance(0x4567); |
| EXPECT_TRUE(data.SetLocalName("fuchsia")); |
| |
| auto bytes = CreateStaticByteBuffer(0x01, 0x02, 0x03); |
| EXPECT_TRUE(data.SetManufacturerData(0x0123, bytes.view())); |
| |
| auto service_uuid = UUID(kId1As16); |
| auto service_bytes = CreateStaticByteBuffer(0x01, 0x02); |
| EXPECT_TRUE(data.AddServiceUuid(service_uuid)); |
| EXPECT_TRUE(data.SetServiceData(service_uuid, service_bytes.view())); |
| |
| EXPECT_TRUE(data.AddUri("http://fuchsia.cl")); |
| |
| DynamicByteBuffer write_buf(data.CalculateBlockSize(/*include_flags=*/true)); |
| EXPECT_TRUE(data.WriteBlock(&write_buf, AdvFlag::kLEGeneralDiscoverableMode)); |
| |
| auto expected_buf = CreateStaticByteBuffer( |
| 0x02, 0x01, 0x02, // flags: 2 |
| 0x02, 0x0a, 0x04, // tx_power_level_: 4 |
| 0x03, 0x19, 0x67, 0x45, // appearance_: 0x4567 |
| 0x08, 0x09, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, // local_name_: "fuchsia" |
| 0x06, 0xff, 0x23, 0x01, 0x01, 0x02, 0x03, // manufacturer_data_ |
| 0x05, 0x16, 0x12, 0x02, 0x01, 0x02, // service_data_ |
| 0x0e, 0x24, 0x16, 0x2f, 0x2f, 0x66, 0x75, 0x63, 0x68, 0x73, 0x69, 0x61, 0x2e, 0x63, 0x6c, |
| 0x03, 0x02, 0x12, 0x02); // uris_ |
| EXPECT_TRUE(ContainersEqual(expected_buf, write_buf)); |
| } |
| |
| TEST(GAP_AdvertisingDataTest, WriteBlockWithFlagsBufError) { |
| AdvertisingData data; |
| |
| data.SetTxPower(6); |
| EXPECT_TRUE(data.SetLocalName("Fuchsia")); |
| data.SetAppearance(0x1234); |
| |
| DynamicByteBuffer write_buf(data.CalculateBlockSize(/*include_flags=*/true) - 1); |
| EXPECT_FALSE(data.WriteBlock(&write_buf, AdvFlag::kLEGeneralDiscoverableMode)); |
| } |
| |
| // Adds `n_(consecutively_increasing)_uuids` to `input` and returns the "next" UUID in the sequence. |
| // UUIDs may wrap around - this is OK, as we only care that they are all distinct. |
| UUID AddNDistinctUuids(AdvertisingData& input, |
| std::variant<uint16_t, uint32_t, UInt128> starting_uuid, uint8_t n_uuids) { |
| UUID next; |
| for (uint8_t i = 0; true; ++i) { |
| std::visit( |
| [&](auto arg) { |
| using T = std::decay_t<decltype(arg)>; |
| if constexpr (std::is_same_v<T, UInt128>) { |
| arg[0] += i; |
| next = UUID(arg); |
| } else { |
| next = UUID(static_cast<T>(arg + i)); |
| } |
| }, |
| starting_uuid); |
| SCOPED_TRACE(fxl::StringPrintf("i: %du UUID: %s", i, bt_str(next))); |
| if (i >= n_uuids) { |
| return next; |
| } |
| EXPECT_TRUE(input.AddServiceUuid(next)); |
| } |
| } |
| |
| TEST(GAP_AdvertisingDataTest, SetFieldsWithTooLongParameters) { |
| AdvertisingData data; |
| { |
| // Use the https URI encoding scheme. This prefix will be compressed to one byte when encoded. |
| std::string uri = "https:"; |
| uri += std::string(kMaxEncodedUriLength - 1, '.'); |
| EXPECT_TRUE(data.AddUri(uri)); |
| uri += '.'; |
| EXPECT_FALSE(data.AddUri(uri)); |
| } |
| // Attempt to set slightly too long service data. |
| { |
| UUID two_byte_uuid{kHeartRateServiceUuid}; |
| DynamicByteBuffer long_data(kMaxEncodedServiceDataLength - 1); |
| long_data.Fill(0xAB); |
| EXPECT_FALSE(data.SetServiceData(two_byte_uuid, long_data)); |
| // An empty DynamicByteBuffer represents unset service data per the header. |
| EXPECT_TRUE(ContainersEqual(DynamicByteBuffer(), data.service_data(two_byte_uuid))); |
| // Now use a view that is just small enough to fit when encoded |
| BufferView view = long_data.view(/*pos=*/0, /*size=*/long_data.size() - 1); |
| EXPECT_TRUE(data.SetServiceData(two_byte_uuid, view)); |
| EXPECT_TRUE(ContainersEqual(view, data.service_data(two_byte_uuid))); |
| } |
| // Attempt to set slightly too long manufacturer data. |
| { |
| uint16_t manufacturer_id{0xABBA}; |
| DynamicByteBuffer long_data(kMaxManufacturerDataLength + 1); |
| long_data.Fill(0xAB); |
| EXPECT_FALSE(data.SetManufacturerData(manufacturer_id, long_data.view())); |
| // An empty DynamicByteBuffer represents unset service data per the header. |
| EXPECT_TRUE(ContainersEqual(DynamicByteBuffer(), data.manufacturer_data(manufacturer_id))); |
| // Now use a view that is just small enough to fit when encoded |
| BufferView view = long_data.view(/*pos=*/0, /*size=*/long_data.size() - 1); |
| EXPECT_TRUE(data.SetManufacturerData(manufacturer_id, view)); |
| EXPECT_TRUE(ContainersEqual(view, data.manufacturer_data(manufacturer_id))); |
| } |
| // Ensure that service UUIDs are truncated when they do not fit. |
| { |
| uint16_t starting_16bit_uuid = 0x0001; |
| UUID should_fail = AddNDistinctUuids( |
| data, std::variant<uint16_t, uint32_t, UInt128>{starting_16bit_uuid}, kMax16BitUuids); |
| EXPECT_FALSE(data.AddServiceUuid(should_fail)); |
| EXPECT_TRUE(data.service_uuids().find(should_fail) == data.service_uuids().end()); |
| |
| // This value must not fit in a 16 bit number in order to count as a "32 bit" UUID |
| uint32_t starting_32bit_uuid = std::numeric_limits<uint16_t>::max() + 1; |
| should_fail = AddNDistinctUuids( |
| data, std::variant<uint16_t, uint32_t, UInt128>{starting_32bit_uuid}, kMax32BitUuids); |
| EXPECT_FALSE(data.AddServiceUuid(should_fail)); |
| EXPECT_TRUE(data.service_uuids().find(should_fail) == data.service_uuids().end()); |
| |
| UInt128 starting_128bit_uuid = {0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, |
| 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB}; |
| should_fail = AddNDistinctUuids( |
| data, std::variant<uint16_t, uint32_t, UInt128>{starting_128bit_uuid}, kMax128BitUuids); |
| EXPECT_FALSE(data.AddServiceUuid(should_fail)); |
| EXPECT_TRUE(data.service_uuids().find(should_fail) == data.service_uuids().end()); |
| } |
| // Ensures names exceeding kMaxNameLength are rejected. |
| { |
| std::string name_that_fits(kMaxNameLength, 'a'); |
| std::string too_long_name(kMaxNameLength + 1, 'b'); |
| EXPECT_TRUE(data.SetLocalName(name_that_fits)); |
| EXPECT_EQ(name_that_fits, data.local_name()); |
| EXPECT_FALSE(data.SetLocalName(too_long_name)); |
| EXPECT_EQ(name_that_fits, data.local_name()); |
| } |
| // Write the data out to ensure no assertions are triggered |
| DynamicByteBuffer block(data.CalculateBlockSize()); |
| EXPECT_TRUE(data.WriteBlock(&block, std::nullopt)); |
| } |
| |
| // Tests that even when the maximum number of distinct UUIDs for a certain size have been added to |
| // an AD, we do not reject additional UUIDs that are duplicates of already-added UUIDs. |
| TEST(GAP_AdvertisingDataTest, AddDuplicateServiceUuidsWhenFullSucceeds) { |
| AdvertisingData data; |
| uint16_t starting_16bit_uuid = 0x0001; |
| UUID should_fail = AddNDistinctUuids( |
| data, std::variant<uint16_t, uint32_t, UInt128>{starting_16bit_uuid}, kMax16BitUuids); |
| // Verify that adding another distinct UUID fails - i.e. we are at the limit. |
| EXPECT_FALSE(data.AddServiceUuid(should_fail)); |
| EXPECT_TRUE(data.service_uuids().find(should_fail) == data.service_uuids().end()); |
| // Verify that we are notified of success when adding an existing UUID |
| EXPECT_TRUE(data.AddServiceUuid(UUID(starting_16bit_uuid))); |
| } |
| } // namespace |
| } // namespace bt |