diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement.cc b/connections/implementation/mediums/ble_v2/ble_advertisement.cc index 15690cd1..c024411b 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement.cc @@ -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::CreateBleAdvertisement( bool fast_advertisement = static_cast((*version_byte & kFastAdvertisementFlagBitmask) >> 1); + bool is_second_profile = + static_cast(*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::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::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(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(fast_advertisement_ ? 1 : 0) << 1) & kFastAdvertisementFlagBitmask; + version_byte |= + (static_cast(is_second_profile_ ? 1 : 0)) & kSecondProfileBitmask; + + stream_writer.WriteUint8(version_byte); // Serialize Data size bytes - ByteArray data_size_bytes{static_cast( - 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(&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 diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement.h b/connections/implementation/mediums/ble_v2/ble_advertisement.h index d8e95848..52fbd73e 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement.h +++ b/connections/implementation/mediums/ble_v2/ble_advertisement.h @@ -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 diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc b/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc index c4191a6c..4097d78a 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc @@ -15,6 +15,8 @@ #include "connections/implementation/mediums/ble_v2/ble_advertisement.h" #include +#include +#include #include #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 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