Update BLE advertisement format

PiperOrigin-RevId: 778351891
This commit is contained in:
Guogang Li
2025-07-01 23:13:28 -07:00
committed by Copybara-Service
parent 2df88546be
commit cf92356d02
3 changed files with 131 additions and 64 deletions
@@ -27,6 +27,7 @@
#include "internal/platform/byte_array.h"
#include "internal/platform/logging.h"
#include "internal/platform/stream_reader.h"
#include "internal/platform/stream_writer.h"
namespace nearby {
namespace connections {
@@ -35,6 +36,7 @@ namespace mediums {
namespace {
constexpr uint8_t kPsmBitmask = 0x01;
constexpr uint8_t kRxInstantConnectionAdvBitmask = 0x02;
bool HasField(uint8_t field_mask, uint8_t psm_bit) {
return (field_mask & psm_bit) > 0;
@@ -125,6 +127,9 @@ absl::StatusOr<BleAdvertisement> BleAdvertisement::CreateBleAdvertisement(
bool fast_advertisement =
static_cast<bool>((*version_byte & kFastAdvertisementFlagBitmask) >> 1);
bool is_second_profile =
static_cast<bool>(*version_byte & kSecondProfileBitmask);
// The next 3 bytes are supposed to be the service_id_hash if not fast
// advertisement.
ByteArray service_id_hash;
@@ -171,6 +176,7 @@ absl::StatusOr<BleAdvertisement> BleAdvertisement::CreateBleAdvertisement(
ble_advertisement.version_ = version;
ble_advertisement.socket_version_ = socket_version;
ble_advertisement.fast_advertisement_ = fast_advertisement;
ble_advertisement.is_second_profile_ = is_second_profile;
ble_advertisement.service_id_hash_ = service_id_hash;
ble_advertisement.data_ = data;
@@ -183,25 +189,40 @@ absl::StatusOr<BleAdvertisement> BleAdvertisement::CreateBleAdvertisement(
"Cannot deserialize BleAdvertisement: device_token.");
}
ble_advertisement.device_token_ = *device_token_bytes;
} else {
// No device token no more optional field.
return ble_advertisement;
}
// Extra fields, for backward compatible reason, put this field in the end of
// this advertisement. That means it must support device token if there's any
// extra field. E.g. If iOS or other platform wants to use extra fields, need
// to put a random or empty device token in the advertisement.
int extra_fields_byte_number =
kExtraFieldsMaskLength + BleAdvertisementHeader::kPsmValueByteLength;
if (stream_reader.IsAvailable(extra_fields_byte_number)) {
auto extra_fields_bytes = stream_reader.ReadBytes(extra_fields_byte_number);
if (!extra_fields_bytes.has_value()) {
return absl::InvalidArgumentError(
"Cannot deserialize BleAdvertisement: extra_field.");
if (stream_reader.IsAvailable(kExtraFieldsMaskLength)) {
uint8_t extra_fields_mask = stream_reader.ReadUint8().value_or(0);
if (extra_fields_mask & kPsmBitmask) {
auto psm_value = stream_reader.ReadUint16();
if (!psm_value.has_value()) {
return absl::InvalidArgumentError(
"Cannot deserialize BleAdvertisement: psm_value.");
}
ble_advertisement.psm_ = *psm_value;
}
if (extra_fields_mask & kRxInstantConnectionAdvBitmask) {
auto rx_instant_connection_adv_byte = stream_reader.ReadUint8();
if (!rx_instant_connection_adv_byte.has_value()) {
return absl::InvalidArgumentError(
"Cannot deserialize BleAdvertisement: "
"rx_instant_connection_adv_byte.");
}
auto rx_instant_connection_adv_data =
stream_reader.ReadBytes(*rx_instant_connection_adv_byte);
if (!rx_instant_connection_adv_data.has_value()) {
return absl::InvalidArgumentError(
"Cannot deserialize BleAdvertisement: "
"rx_instant_connection_adv_data.");
}
ble_advertisement.rx_instant_connection_adv_ =
*rx_instant_connection_adv_data;
}
BleExtraFields extra_fields{*extra_fields_bytes};
ble_advertisement.psm_ = extra_fields.GetPsm();
}
return ble_advertisement;
}
@@ -211,6 +232,8 @@ BleAdvertisement::operator ByteArray() const {
return ByteArray{};
}
StreamWriter stream_writer;
// The first 3 bits are the Version.
char version_byte = (static_cast<char>(version_) << 5) & kVersionBitmask;
// The next 3 bits are the Socket version. 2 bits left are reserved.
@@ -219,37 +242,34 @@ BleAdvertisement::operator ByteArray() const {
// The next 1 bit is the fast advertisement flag. 1 bit left is reserved.
version_byte |= (static_cast<char>(fast_advertisement_ ? 1 : 0) << 1) &
kFastAdvertisementFlagBitmask;
version_byte |=
(static_cast<char>(is_second_profile_ ? 1 : 0)) & kSecondProfileBitmask;
stream_writer.WriteUint8(version_byte);
// Serialize Data size bytes
ByteArray data_size_bytes{static_cast<size_t>(
fast_advertisement_ ? kFastDataSizeLength : kDataSizeLength)};
auto *data_size_bytes_write_ptr = data_size_bytes.data();
SerializeDataSize(fast_advertisement_, data_size_bytes_write_ptr,
data_.size());
if (fast_advertisement_) {
stream_writer.WriteUint8(data_.size());
stream_writer.WriteBytes(std::string(data_));
stream_writer.WriteBytes(std::string(device_token_));
} else {
stream_writer.WriteBytes(std::string(service_id_hash_));
stream_writer.WriteUint32(data_.size());
stream_writer.WriteBytes(std::string(data_));
stream_writer.WriteBytes(std::string(device_token_));
}
// For Extra fields, there's no space for legacy fast advertisement, use
// ByteArrayWithExtraField() to get the new advertisement bytes for extended
// advertising.
// clang-format on
if (fast_advertisement_) {
std::string out =
absl::StrCat(std::string(1, version_byte), std::string(data_size_bytes),
std::string(data_), std::string(device_token_));
return ByteArray{std::move(out)};
} else {
std::string out = absl::StrCat(
std::string(1, version_byte), std::string(service_id_hash_),
std::string(data_size_bytes), std::string(data_),
std::string(device_token_));
return ByteArray{std::move(out)};
}
// clang-format on
return ByteArray{stream_writer.GetData()};
}
ByteArray BleAdvertisement::ByteArrayWithExtraField() const {
ByteArray advertisement_bytes = ByteArray(*this);
ByteArray extra_fields_bytes = ByteArray(BleExtraFields(psm_));
ByteArray extra_fields_bytes =
ByteArray(BleExtraFields(psm_, rx_instant_connection_adv_));
std::string advertisement_with_extra_fields_bytes = absl::StrCat(
std::string(advertisement_bytes), std::string(extra_fields_bytes));
@@ -264,7 +284,8 @@ bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const {
this->GetServiceIdHash() == rhs.GetServiceIdHash() &&
this->GetData() == rhs.GetData() &&
this->GetDeviceToken() == rhs.GetDeviceToken() &&
this->GetPsm() == rhs.GetPsm();
this->GetPsm() == rhs.GetPsm() &&
this->GetRxInstantConnectionAdv() == rhs.GetRxInstantConnectionAdv();
}
bool BleAdvertisement::IsSupportedVersion(Version version) {
@@ -276,23 +297,9 @@ bool BleAdvertisement::IsSupportedSocketVersion(SocketVersion socket_version) {
socket_version <= SocketVersion::kV2;
}
void BleAdvertisement::SerializeDataSize(bool fast_advertisement,
char *data_size_bytes_write_ptr,
size_t data_size) const {
// Get a raw representation of the data size bytes in memory.
char *data_size_bytes = reinterpret_cast<char *>(&data_size);
const int data_size_length =
fast_advertisement ? kFastDataSizeLength : kDataSizeLength;
// Append these raw bytes to advertisement bytes, keeping in mind that we need
// to convert from Little Endian to Big Endian in the process.
for (int i = 0; i < data_size_length; ++i) {
data_size_bytes_write_ptr[i] = data_size_bytes[data_size_length - i - 1];
}
}
BleAdvertisement::BleExtraFields::BleExtraFields(int psm) : psm_(psm) {}
BleAdvertisement::BleExtraFields::BleExtraFields(
int psm, const ByteArray &rx_instant_connection_adv)
: psm_(psm), rx_instant_connection_adv_(rx_instant_connection_adv) {}
BleAdvertisement::BleExtraFields::BleExtraFields(
const ByteArray &ble_extra_fields_bytes) {
@@ -313,22 +320,39 @@ BleAdvertisement::BleExtraFields::BleExtraFields(
stream_reader.IsAvailable(BleAdvertisementHeader::kPsmValueByteLength)) {
psm_ = stream_reader.ReadUint16().value_or(0);
}
if (HasField(mask_byte, kRxInstantConnectionAdvBitmask) &&
stream_reader.IsAvailable(kRxInstantConnectionAdvSizeLength)) {
auto rx_instant_connection_adv_byte = stream_reader.ReadUint8();
auto rx_instant_connection_adv_data =
stream_reader.ReadBytes(*rx_instant_connection_adv_byte);
if (rx_instant_connection_adv_data.has_value()) {
rx_instant_connection_adv_ = *rx_instant_connection_adv_data;
}
}
}
BleAdvertisement::BleExtraFields::operator ByteArray() const {
if (psm_ == BleAdvertisementHeader::kDefaultPsmValue) {
if (psm_ == BleAdvertisementHeader::kDefaultPsmValue &&
rx_instant_connection_adv_.Empty()) {
return ByteArray{};
}
ByteArray psm_byte{BleAdvertisementHeader::kPsmValueByteLength};
char *data = psm_byte.data();
// Save the PSM value in network byte order.
data[0] = (psm_ >> 8) & 0xFF;
data[1] = psm_ & 0xFF;
StreamWriter stream_writer;
stream_writer.WriteUint8(
(psm_ != BleAdvertisementHeader::kDefaultPsmValue ? kPsmBitmask : 0) |
(rx_instant_connection_adv_.Empty() ? 0
: kRxInstantConnectionAdvBitmask));
if (psm_ != BleAdvertisementHeader::kDefaultPsmValue) {
stream_writer.WriteUint16(psm_);
}
std::string out =
absl::StrCat(std::string(1, kPsmBitmask), std::string(psm_byte));
return ByteArray{std::move(out)};
if (!rx_instant_connection_adv_.Empty()) {
stream_writer.WriteUint8(rx_instant_connection_adv_.size());
stream_writer.WriteBytes(std::string(rx_instant_connection_adv_));
}
return ByteArray{stream_writer.GetData()};
}
} // namespace mediums
@@ -66,6 +66,7 @@ class BleAdvertisement {
static constexpr int kVersionBitmask = 0x0E0;
static constexpr int kSocketVersionBitmask = 0x01C;
static constexpr int kFastAdvertisementFlagBitmask = 0x002;
static constexpr int kSecondProfileBitmask = 0x001;
static constexpr int kDataSizeLength = 4; // Length of one int.
static constexpr int kFastDataSizeLength = 1; // Length of one byte.
static constexpr int kMinAdvertisementLength =
@@ -114,6 +115,7 @@ class BleAdvertisement {
Version GetVersion() const { return version_; }
SocketVersion GetSocketVersion() const { return socket_version_; }
bool IsFastAdvertisement() const { return fast_advertisement_; }
bool IsSecondProfile() const { return is_second_profile_; }
ByteArray GetServiceIdHash() const { return service_id_hash_; }
ByteArray &GetData() & { return data_; }
const ByteArray &GetData() const & { return data_; }
@@ -122,6 +124,12 @@ class BleAdvertisement {
ByteArray GetDeviceToken() const { return device_token_; }
int GetPsm() const { return psm_; }
void SetPsm(int psm) { psm_ = psm; }
ByteArray GetRxInstantConnectionAdv() const {
return rx_instant_connection_adv_;
}
void SetRxInstantConnectionAdv(const ByteArray &rx_instant_connection_adv) {
rx_instant_connection_adv_ = rx_instant_connection_adv;
}
std::string ToReadableString() const {
return absl::StrFormat(
"BleAdvertisement { version=%d, socket_version=%d, "
@@ -142,21 +150,30 @@ class BleAdvertisement {
// e.g. [BIT_MASK][X_FIELD(2 Bytes)][LENGTH(2 Bytes) + Y_FIELD(n Bytes)]
//
// Below is the current fields
// [BIT_MASK][PSM_VALUE(2 Bytes)]
// [BIT_MASK][PSM_VALUE(2 Bytes)][RX_INSTANT_CONNECTION_ADV(1 Byte length +
// 1~N Bytes data)]
//
// The PSM (protocol service multiplexer) value is used for create data
// connection on L2CAP socket. It only exists when remote device supports
// L2CAP socket feature.
class BleExtraFields {
public:
explicit BleExtraFields(int psm);
static constexpr int kRxInstantConnectionAdvSizeLength = 1;
explicit BleExtraFields(int psm,
const ByteArray &rx_instant_connection_adv);
explicit BleExtraFields(const ByteArray &ble_extra_fields_bytes);
explicit operator ByteArray() const;
int GetPsm() const { return psm_; }
ByteArray GetRxInstantConnectionAdv() const {
return rx_instant_connection_adv_;
}
private:
int psm_ = BleAdvertisementHeader::kDefaultPsmValue;
ByteArray rx_instant_connection_adv_;
};
void DoInitialize(bool fast_advertisement, Version version,
@@ -165,9 +182,6 @@ class BleAdvertisement {
const ByteArray &device_token, int psm);
static bool IsSupportedVersion(Version version);
static bool IsSupportedSocketVersion(SocketVersion socket_version);
void SerializeDataSize(bool fast_advertisement,
char *data_size_bytes_write_ptr,
size_t data_size) const;
int ComputeAdvertisementLength(int data_length, int total_optional_length,
bool fast_advertisement) const {
// The advertisement length is the minimum length + the length of the data +
@@ -181,10 +195,12 @@ class BleAdvertisement {
Version version_{Version::kUndefined};
SocketVersion socket_version_{SocketVersion::kUndefined};
bool fast_advertisement_ = false;
bool is_second_profile_ = false;
ByteArray service_id_hash_;
ByteArray data_;
ByteArray device_token_;
int psm_ = BleAdvertisementHeader::kDefaultPsmValue;
ByteArray rx_instant_connection_adv_;
};
} // namespace mediums
@@ -15,6 +15,8 @@
#include "connections/implementation/mediums/ble_v2/ble_advertisement.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <string>
#include "gmock/gmock.h"
@@ -22,6 +24,7 @@
#include "gtest/gtest.h"
#include "absl/hash/hash_testing.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h"
#include "internal/platform/byte_array.h"
@@ -573,6 +576,30 @@ TEST(BleAdvertisementTest, Hash) {
}));
}
TEST(BleAdvertisementTest, TestExtraFields) {
ByteArray advertisement_data{
std::string("\x48\xfc\x9f\x5e\x00\x00\x00\x33\x23\xfc"
"\x9f\x5e\x36\x58\x42\x54\x22\x22\x96\xe6"
"\xe2\x0f\xc4\x4a\x61\x87\xfe\xf9\xbb\xc4"
"\x98\x4b\x1c\x8a\x10\x6e\x65\x61\x72\x62"
"\x79\x27\x73\x20\x50\x69\x78\x65\x6c\x20"
"\x37\x0c\xc4\x13\x2e\xdc\x8d\x00\x00\xbf"
"\xf9\x02\x3f\xa0\x17\x79\x15\xfa\x9c\x97"
"\xb8\x5a\x3b\x1f\xd9\x54\x8e\x13\xe2\xf5"
"\xb7\x0b\xb0\x91\xaf\x7b\x17\x4c\x6b\x6f"
"\x67\x3b\xda\xc9\x1c\x10\x28\x84\x15\x05"
"\x61\x00\x00\x84\x15\x06\x61\x00\x00\x84"
"\x15\x04\x7f\x00\x00\x84\x15\x07\x61\x00"
"\x00\x83\x15\x01\x16\x44",
126)};
absl::StatusOr<BleAdvertisement> ble_advertisement =
BleAdvertisement::CreateBleAdvertisement(advertisement_data);
EXPECT_EQ(ble_advertisement->GetPsm(), 0);
EXPECT_FALSE(ble_advertisement->IsFastAdvertisement());
EXPECT_TRUE(ble_advertisement.ok());
EXPECT_EQ(ble_advertisement->ByteArrayWithExtraField(), advertisement_data);
}
} // namespace
} // namespace mediums
} // namespace connections