mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Update BLE advertisement format
PiperOrigin-RevId: 778351891
This commit is contained in:
committed by
Copybara-Service
parent
2df88546be
commit
cf92356d02
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user