blob: e939da32df9906d262331695a6b3fd420d8043f3 [file] [log] [blame]
// 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/public/pw_bluetooth_sapphire/internal/host/common/advertising_data.h"
#include <limits>
#include <variant>
#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/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/random.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/uuid.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/test_helpers.h"
#include "src/connectivity/bluetooth/lib/cpp-string/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(AdvertisingDataTest, MakeEmpty) {
AdvertisingData data;
EXPECT_EQ(0u, data.CalculateBlockSize());
}
TEST(AdvertisingDataTest, CopyLeavesNoRemnants) {
AdvertisingData data;
data.SetFlags(0x4);
data.SetTxPower(4);
data.SetAppearance(0x4567);
EXPECT_TRUE(data.SetLocalName("fuchsia"));
EXPECT_TRUE(data.AddUri("http://fuchsia.cl"));
StaticByteBuffer bytes(0x01, 0x02, 0x03);
EXPECT_TRUE(data.SetManufacturerData(0x0123, bytes.view()));
auto service_uuid = UUID(kId1As16);
EXPECT_TRUE(data.AddServiceUuid(service_uuid));
StaticByteBuffer service_bytes(0x01, 0x02);
EXPECT_TRUE(data.SetServiceData(service_uuid, service_bytes.view()));
AdvertisingData new_data;
new_data.SetTxPower(1);
new_data.Copy(&data);
EXPECT_EQ(1, data.tx_power().value());
EXPECT_FALSE(data.appearance().has_value());
EXPECT_FALSE(data.local_name().has_value());
EXPECT_FALSE(data.flags().has_value());
EXPECT_EQ(0u, data.uris().size());
EXPECT_EQ(0u, data.manufacturer_data_ids().size());
EXPECT_EQ(0u, data.service_uuids().size());
EXPECT_EQ(0u, data.service_data_uuids().size());
}
TEST(AdvertisingDataTest, EncodeKnownURI) {
AdvertisingData data;
EXPECT_TRUE(data.AddUri("https://abc.xyz"));
StaticByteBuffer bytes(
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(AdvertisingDataTest, EncodeUnknownURI) {
AdvertisingData data;
EXPECT_TRUE(data.AddUri("flubs:xyz"));
StaticByteBuffer bytes(
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(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).To<uint16_t>());
};
EXPECT_TRUE(uuids.find(to_uuid(block, 2)) != uuids.end());
EXPECT_TRUE(uuids.find(to_uuid(block, 4)) != uuids.end());
}
TEST(AdvertisingDataTest, ParseBlock) {
StaticByteBuffer bytes(
// 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);
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), data);
EXPECT_EQ(3u, data->service_uuids().size());
EXPECT_TRUE(data->local_name());
EXPECT_EQ("Test💖", data->local_name()->name);
EXPECT_TRUE(data->tx_power());
EXPECT_EQ(-113, *(data->tx_power()));
}
TEST(AdvertisingDataTest, ParseBlockUnknownDataType) {
AdvertisingData expected_ad;
constexpr uint8_t lower_byte = 0x12, upper_byte = 0x22;
constexpr 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)));
StaticByteBuffer bytes{// 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};
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), data);
// 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(AdvertisingDataTest, ParseBlockNameTooLong) {
// A block with a name of exactly kMaxNameLength (==248) bytes should be
// parsed correctly.
{
StaticByteBuffer<2> leading_bytes{kMaxNameLength + 1,
DataType::kCompleteLocalName};
auto bytes = DynamicByteBuffer(kMaxNameLength + 2);
bytes.Write(leading_bytes);
DynamicByteBuffer name(kMaxNameLength);
name.Fill('a');
bytes.Write(name, /*pos=*/2);
AdvertisingData::ParseResult result = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), result);
EXPECT_EQ(result->local_name()->name, std::string(kMaxNameLength, 'a'));
}
// Repeat previous test with shortened name.
{
auto leading_bytes =
StaticByteBuffer<2>{kMaxNameLength + 1, DataType::kShortenedLocalName};
auto bytes = DynamicByteBuffer(kMaxNameLength + 2);
bytes.Write(leading_bytes);
DynamicByteBuffer name(kMaxNameLength);
name.Fill('a');
bytes.Write(name, /*pos=*/2);
AdvertisingData::ParseResult result = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), result);
EXPECT_EQ(result->local_name()->name, std::string(kMaxNameLength, 'a'));
}
// A block with a name of kMaxNameLength+1 (==249) bytes should be rejected.
{
StaticByteBuffer<2> leading_bytes{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);
AdvertisingData::ParseResult result = AdvertisingData::FromBytes(bytes);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kLocalNameTooLong,
result.error_value());
}
// Repeat previous test with shortened name.
{
auto leading_bytes =
StaticByteBuffer<2>{kMaxNameLength + 2, DataType::kShortenedLocalName};
auto bytes = DynamicByteBuffer(kMaxNameLength + 3);
bytes.Write(leading_bytes);
DynamicByteBuffer name(kMaxNameLength + 1);
name.Fill('a');
bytes.Write(name, /*pos=*/2);
AdvertisingData::ParseResult result = AdvertisingData::FromBytes(bytes);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kLocalNameTooLong,
result.error_value());
}
}
TEST(AdvertisingDataTest, ManufacturerZeroLength) {
StaticByteBuffer bytes(
// 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());
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), data);
EXPECT_EQ(1u, data->manufacturer_data_ids().count(0x1234));
EXPECT_EQ(0u, data->manufacturer_data(0x1234).size());
}
TEST(AdvertisingDataTest, ServiceData) {
// A typical Eddystone-URL beacon advertisement
// to "https://fuchsia.cl"
StaticByteBuffer bytes(
// 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());
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), data);
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)));
}
// Per CSS v9 Part A 1.1.1, "A packet or data block shall not contain more than
// one instance for each Service UUID data size". We enforce this by failing to
// parse AdvertisingData with UUIDs of a particular size which exceed the amount
// that can fit in one TLV field.
TEST(AdvertisingDataTest, TooManyUuidsOfSizeRejected) {
// Space for the maximum # of 16 bit UUIDs + length + type fields.
const uint64_t kMaxAllowed16BitUuidsSize =
(2 + kMax16BitUuids * UUIDElemSize::k16Bit);
// Space for one more UUID + type and length fields
const uint64_t kExpectedBuffSize =
kMaxAllowed16BitUuidsSize + (2 + UUIDElemSize::k16Bit);
DynamicByteBuffer bytes(kExpectedBuffSize);
uint64_t offset = 0;
// Write first TLV field with maximum # of UUIDs
bytes.Write(StaticByteBuffer{
kMax16BitUuids * UUIDElemSize::k16Bit + 1, // Size byte
static_cast<uint8_t>(DataType::kComplete16BitServiceUuids) // Type byte
});
offset += 2;
for (uint16_t i = 0; i < kMax16BitUuids; ++i) {
UUID uuid(static_cast<uint16_t>(i + 'a'));
bytes.Write(uuid.CompactView(), offset);
offset += uuid.CompactSize();
}
// Verify that we successfully parse an AD with the maximum amount of 16 bit
// UUIDs
AdvertisingData::ParseResult adv_result = AdvertisingData::FromBytes(
bytes.view(/*pos=*/0, /*size=*/kMaxAllowed16BitUuidsSize));
ASSERT_EQ(fit::ok(), adv_result);
EXPECT_EQ(kMax16BitUuids, adv_result->service_uuids().size());
// Write second Complete 16 bit Service UUIDs TLV field with one more UUID
bytes.Write(
StaticByteBuffer{
UUIDElemSize::k16Bit + 1, // Size byte
static_cast<uint8_t>(
DataType::kComplete16BitServiceUuids) // Type byte
},
offset);
offset += 2;
UUID uuid(static_cast<uint16_t>(kMax16BitUuids + 'a'));
bytes.Write(uuid.CompactView(), offset);
adv_result = AdvertisingData::FromBytes(bytes);
ASSERT_TRUE(adv_result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kUuidsMalformed,
adv_result.error_value());
}
TEST(AdvertisingDataTest, Missing) {
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(DynamicByteBuffer());
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kMissing, result.error_value());
}
TEST(AdvertisingDataTest, InvalidTlvFormat) {
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(StaticByteBuffer(0x03));
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kInvalidTlvFormat,
result.error_value());
}
TEST(AdvertisingDataTest, TxPowerLevelMalformed) {
StaticByteBuffer service_data{/*length=*/0x01,
static_cast<uint8_t>(DataType::kTxPowerLevel)};
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(service_data);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kTxPowerLevelMalformed,
result.error_value());
}
TEST(AdvertisingDataTest, UuidsMalformed) {
StaticByteBuffer service_data{
0x02, // Length
static_cast<uint8_t>(DataType::kComplete16BitServiceUuids),
0x12 // The length of a valid 16-bit UUID byte array be a multiple of 2
// (and 1 % 2 == 1).
};
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(service_data);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kUuidsMalformed, result.error_value());
}
TEST(AdvertisingDataTest, ManufacturerSpecificDataTooSmall) {
StaticByteBuffer service_data{
0x02, // Length
static_cast<uint8_t>(DataType::kManufacturerSpecificData),
0x12 // Manufacturer-specific data must be at least 2 bytes
};
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(service_data);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kManufacturerSpecificDataTooSmall,
result.error_value());
}
TEST(AdvertisingDataTest, DecodeServiceDataWithIncompleteUuid) {
StaticByteBuffer service_data(
0x02, // Length
static_cast<uint8_t>(DataType::kServiceData16Bit), // Data type
0xAA // First byte of incomplete UUID
);
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(service_data);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kServiceDataTooSmall,
result.error_value());
}
TEST(AdvertisingDataTest, AppearanceMalformed) {
StaticByteBuffer service_data{
0x02, // Length
static_cast<uint8_t>(DataType::kAppearance),
0x12 // Appearance is supposed to be 2 bytes
};
AdvertisingData::ParseResult result =
AdvertisingData::FromBytes(service_data);
ASSERT_TRUE(result.is_error());
EXPECT_EQ(AdvertisingData::ParseError::kAppearanceMalformed,
result.error_value());
}
TEST(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
StaticByteBuffer bytes(0x01, 0x02, 0x03, 0x04);
StaticByteBuffer same(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(AdvertisingDataTest, Copy) {
UUID gatt(kGattUuid);
UUID eddy(kEddystoneUuid);
StaticByteBuffer<kRandomDataSize> rand_data;
random_generator()->Get(rand_data.mutable_subspan());
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());
StaticByteBuffer bytes(0x01, 0x02, 0x03);
EXPECT_TRUE(source.SetManufacturerData(0x0123, bytes.view()));
EXPECT_TRUE(ContainersEqual(rand_data, dest.manufacturer_data(0x0123)));
}
TEST(AdvertisingDataTest, Move) {
UUID gatt(kGattUuid);
UUID eddy(kEddystoneUuid);
StaticByteBuffer<kRandomDataSize> rand_data;
random_generator()->Get(rand_data.mutable_subspan());
UUID heart_rate_uuid(kHeartRateServiceUuid);
int8_t tx_power = 18; // arbitrary TX power
uint16_t appearance = 0x4567; // arbitrary appearance value
uint8_t flags = 0x48; // arbitrary flags value
AdvertisingData source;
EXPECT_TRUE(source.SetLocalName("test"));
source.SetFlags(flags);
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()->name);
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)));
EXPECT_EQ(flags, dest.flags().value());
};
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(AdvertisingDataTest, Flags) {
// A zero-byte flags is allowed, and sets the flags field to zeroes.
StaticByteBuffer flags_empty(0x01, DataType::kFlags);
// Extra bytes are accepted but ignored.
StaticByteBuffer flags_extra(0x04, DataType::kFlags, 0x03, 0x42, 0x49);
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(flags_empty);
ASSERT_EQ(fit::ok(), data);
ASSERT_TRUE(data->flags().has_value());
ASSERT_EQ(0x00, data->flags().value());
data = AdvertisingData::FromBytes(flags_extra);
ASSERT_EQ(fit::ok(), data);
ASSERT_TRUE(data->flags().has_value());
ASSERT_EQ(0x03, data->flags().value());
}
TEST(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;
StaticByteBuffer bytes(
// 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);
AdvertisingData::ParseResult data = AdvertisingData::FromBytes(bytes);
ASSERT_EQ(fit::ok(), data);
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(AdvertisingDataTest, WriteBlockSuccess) {
AdvertisingData data;
data.SetTxPower(4);
data.SetAppearance(0x4567);
EXPECT_TRUE(data.SetLocalName("fuchsia"));
StaticByteBuffer bytes(0x01, 0x02, 0x03);
EXPECT_TRUE(data.SetManufacturerData(0x0123, bytes.view()));
auto service_uuid = UUID(kId1As16);
StaticByteBuffer service_bytes(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));
StaticByteBuffer expected_buf(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(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(AdvertisingDataTest, WriteBlockWithFlagsSuccess) {
AdvertisingData data;
data.SetTxPower(4);
data.SetAppearance(0x4567);
EXPECT_TRUE(data.SetLocalName("fuchsia"));
StaticByteBuffer bytes(0x01, 0x02, 0x03);
EXPECT_TRUE(data.SetManufacturerData(0x0123, bytes.view()));
auto service_uuid = UUID(kId1As16);
StaticByteBuffer service_bytes(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));
StaticByteBuffer expected_buf(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(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(
bt_lib_cpp_string::StringPrintf("i: %du UUID: %s", i, bt_str(next)));
if (i >= n_uuids) {
return next;
}
EXPECT_TRUE(input.AddServiceUuid(next));
}
}
TEST(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()->name);
EXPECT_FALSE(data.SetLocalName(too_long_name));
EXPECT_EQ(name_that_fits, data.local_name()->name);
}
// Write the data out to ensure no assertions are triggered
DynamicByteBuffer block(data.CalculateBlockSize());
EXPECT_TRUE(data.WriteBlock(&block, std::nullopt));
}
// Tests that setting a complete local name overwrites an existing shortened
// local name and that setting a shortened local name has no effect if a
// complete local name is currently stored.
TEST(AdvertisingDataTest, CompleteLocalNameFavored) {
AdvertisingData data;
std::string short_name = "short";
std::string complete_name = "complete";
EXPECT_TRUE(data.SetLocalName(short_name, /*is_complete=*/false));
EXPECT_EQ(short_name, data.local_name()->name);
EXPECT_TRUE(data.SetLocalName(complete_name, /*is_complete=*/true));
EXPECT_EQ(complete_name, data.local_name()->name);
EXPECT_FALSE(data.SetLocalName(short_name, /*is_complete=*/false));
EXPECT_EQ(complete_name, data.local_name()->name);
}
// 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(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