diff --git a/connections/implementation/mediums/BUILD b/connections/implementation/mediums/BUILD index 3cb5f8ab..bfc2e333 100644 --- a/connections/implementation/mediums/BUILD +++ b/connections/implementation/mediums/BUILD @@ -21,7 +21,6 @@ cc_library( "bluetooth_classic.cc", "bluetooth_radio.cc", "mediums.cc", - "uuid.cc", "webrtc_stub.cc", "wifi_lan.cc", ], @@ -30,9 +29,7 @@ cc_library( "ble_v2.h", "bluetooth_classic.h", "bluetooth_radio.h", - "lost_entity_tracker.h", "mediums.h", - "uuid.h", "webrtc_stub.h", "wifi_lan.h", ], @@ -64,10 +61,13 @@ cc_library( name = "utils", srcs = [ "utils.cc", + "uuid.cc", "webrtc_peer_id.cc", ], hdrs = [ + "lost_entity_tracker.h", "utils.h", + "uuid.h", "webrtc_peer_id.h", "webrtc_socket_stub.h", ], @@ -75,12 +75,14 @@ cc_library( visibility = [ "//connections/implementation:__pkg__", "//connections/implementation/mediums:__pkg__", + "//connections/implementation/mediums/ble_v2:__subpackages__", "//connections/implementation/mediums/webrtc:__pkg__", ], deps = [ "//connections/implementation/proto:offline_wire_formats_cc_proto", "//internal/platform:base", "//internal/platform:types", + "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/strings", ], ) diff --git a/connections/implementation/mediums/ble_v2.cc b/connections/implementation/mediums/ble_v2.cc index 696c6b9d..b6cef336 100644 --- a/connections/implementation/mediums/ble_v2.cc +++ b/connections/implementation/mediums/ble_v2.cc @@ -22,6 +22,7 @@ #include "absl/strings/str_cat.h" #include "connections/implementation/mediums/ble_v2/ble_advertisement.h" #include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h" +#include "connections/implementation/mediums/ble_v2/ble_utils.h" #include "connections/implementation/mediums/ble_v2/bloom_filter.h" #include "connections/implementation/mediums/bluetooth_radio.h" #include "connections/implementation/mediums/utils.h" @@ -29,7 +30,6 @@ #include "internal/platform/byte_array.h" #include "internal/platform/logging.h" #include "internal/platform/mutex_lock.h" -#include "internal/platform/prng.h" namespace location { namespace nearby { @@ -43,33 +43,6 @@ using ::location::nearby::api::ble_v2::PowerMode; constexpr int kMaxAdvertisementLength = 512; constexpr int kDummyServiceIdLength = 128; -constexpr absl::string_view kCopresenceServiceUuid = - "0000FEF3-0000-1000-8000-00805F9B34FB"; - -// These two values make up the base UUID we use when advertising a slot. -// The base is an all zero Version-3 name-based UUID. To turn this into an -// advertisement slot UUID, we simply OR the least significant bits with the -// slot number. -// -// More info about the format can be found here: -// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) -constexpr std::int64_t kAdvertisementUuidMsb = 0x0000000000003000; -constexpr std::int64_t kAdvertisementUuidLsb = 0x8000000000000000; - -ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes) { - return Utils::Sha256Hash( - advertisement_bytes, - mediums::BleAdvertisementHeader::kAdvertisementHashLength); -} - -ByteArray GenerateHash(const std::string& source, size_t size) { - return Utils::Sha256Hash(source, size); -} - -ByteArray GenerateDeviceToken() { - return Utils::Sha256Hash(std::to_string(Prng().NextUint32()), - mediums::BleAdvertisement::kDeviceTokenLength); -} } // namespace @@ -122,14 +95,14 @@ bool BleV2::StartAdvertising( // Wrap the connections advertisement to the medium advertisement. const bool is_fast_advertisement = !fast_advertisement_service_uuid.empty(); - ByteArray service_id_hash{GenerateHash( - service_id, mediums::BleAdvertisement::kServiceIdHashLength)}; + ByteArray service_id_hash = mediums::bleutils::GenerateHash( + service_id, mediums::BleAdvertisement::kServiceIdHashLength); ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{ mediums::BleAdvertisement::Version::kV2, mediums::BleAdvertisement::SocketVersion::kV2, /* service_id_hash= */ is_fast_advertisement ? ByteArray{} : service_id_hash, - advertisement_bytes, GenerateDeviceToken()}}; + advertisement_bytes, mediums::bleutils::GenerateDeviceToken()}}; if (medium_advertisement_bytes.Empty()) { NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not wrap a " "connection advertisement to medium advertisement."; @@ -202,9 +175,10 @@ bool BleV2::StartAdvertising( scan_response_data.tx_power_level = BleAdvertisementData::kUnspecifiedTxPowerLevel; scan_response_data.service_uuids.insert( - std::string(kCopresenceServiceUuid)); + std::string(mediums::bleutils::kCopresenceServiceUuid)); scan_response_data.service_data.insert( - {std::string(kCopresenceServiceUuid), advertisement_header_bytes}); + {std::string(mediums::bleutils::kCopresenceServiceUuid), + advertisement_header_bytes}); } if (!medium_.StartAdvertising(advertising_data, scan_response_data, @@ -243,10 +217,10 @@ bool BleV2::StopAdvertising(const std::string& service_id) { // TODO(b/213835576): Check the BLE Connections is off. We set the fake // value for the time being till connections is implemented. bool no_incoming_ble_sockets = true; - // Restart the BLE advertisement if there is still an advertiser. - if (!subscribed_gatt_characteristics_.empty()) { + // Set the value of characteristic to empty if there is still an advertiser. + if (!hosted_gatt_characteristics_.empty()) { ByteArray empty_value = {}; - for (const auto& characteristic : subscribed_gatt_characteristics_) { + for (const auto& characteristic : hosted_gatt_characteristics_) { if (!gatt_server_->UpdateCharacteristic(characteristic, empty_value)) { NEARBY_LOGS(ERROR) << "Failed to clear characteristic uuid=" << characteristic.uuid @@ -254,7 +228,7 @@ bool BleV2::StopAdvertising(const std::string& service_id) { << service_id; } } - subscribed_gatt_characteristics_.clear(); + hosted_gatt_characteristics_.clear(); } else if (no_incoming_ble_sockets) { // Otherwise, if we aren't restarting the BLE advertisement, then shutdown // the gatt server if it's not in use. @@ -318,7 +292,8 @@ bool BleV2::StartScanning(const std::string& service_id, PowerLevel power_level, scanned_service_ids_.insert(service_id); // TODO(b/213835576): We should re-start scanning once the power level is // changed. - std::vector service_uuids{std::string(kCopresenceServiceUuid)}; + std::vector service_uuids{ + std::string(mediums::bleutils::kCopresenceServiceUuid)}; if (!medium_.StartScanning( service_uuids, PowerLevelToPowerMode(power_level), { @@ -393,20 +368,14 @@ bool BleV2::StartAdvertisementGattServerLocked( std::unique_ptr gatt_server = medium_.StartGattServer({ .characteristic_subscription_cb = - [this](const ServerGattConnection& connection, - const GattCharacteristic& characteristic) { - MutexLock lock(&mutex_); - subscribed_gatt_characteristics_.insert(characteristic); + [](const ServerGattConnection& connection, + const GattCharacteristic& characteristic) { + // TODO(b/213835576): Impl or remove. }, .characteristic_unsubscription_cb = - [this](const ServerGattConnection& connection, - const GattCharacteristic& characteristic) { - MutexLock lock(&mutex_); - const auto char_it = - subscribed_gatt_characteristics_.find(characteristic); - if (char_it != subscribed_gatt_characteristics_.end()) { - subscribed_gatt_characteristics_.erase(char_it); - } + [](const ServerGattConnection& connection, + const GattCharacteristic& characteristic) { + // TODO(b/213835576): Impl or remove. }, }); if (!gatt_server || !gatt_server->IsValid()) { @@ -436,21 +405,21 @@ bool BleV2::GenerateAdvertisementCharacteristic( GattCharacteristic::Property::kRead}; absl::optional gatt_characteristic = - gatt_server.CreateCharacteristic(std::string(kCopresenceServiceUuid), - GenerateAdvertisementUuid(slot), - permissions, properties); - + gatt_server.CreateCharacteristic( + std::string(mediums::bleutils::kCopresenceServiceUuid), + mediums::bleutils::GenerateAdvertisementUuid(slot), permissions, + properties); if (!gatt_characteristic.has_value()) { NEARBY_LOGS(INFO) << "Unable to create and add a characterstic to the gatt " "server for the advertisement."; return false; } - - if (!gatt_server.UpdateCharacteristic(*gatt_characteristic, + if (!gatt_server.UpdateCharacteristic(gatt_characteristic.value(), gatt_advertisement)) { NEARBY_LOGS(INFO) << "Unable to write a value to the GATT characteristic."; return false; } + hosted_gatt_characteristics_.insert(gatt_characteristic.value()); return true; } @@ -478,7 +447,7 @@ ByteArray BleV2::CreateAdvertisementHeader() { bloom_filter.Add(dummy_service_id); ByteArray advertisement_hash = - GenerateAdvertisementHash(dummy_service_id_bytes); + mediums::bleutils::GenerateAdvertisementHash(dummy_service_id_bytes); for (const auto& item : gatt_advertisements_) { const std::string& service_id = item.second.first; const ByteArray& gatt_advertisement = item.second.second; @@ -489,8 +458,8 @@ ByteArray BleV2::CreateAdvertisementHeader() { std::string advertisement_bodies = absl::StrCat( advertisement_hash.AsStringView(), gatt_advertisement.AsStringView()); - advertisement_hash = - GenerateAdvertisementHash(ByteArray(std::move(advertisement_bodies))); + advertisement_hash = mediums::bleutils::GenerateAdvertisementHash( + ByteArray(std::move(advertisement_bodies))); } return ByteArray(mediums::BleAdvertisementHeader( @@ -500,10 +469,6 @@ ByteArray BleV2::CreateAdvertisementHeader() { advertisement_hash, /*psm=*/0)); } -std::string BleV2::GenerateAdvertisementUuid(int slot) { - return std::string(Uuid(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot)); -} - PowerMode BleV2::PowerLevelToPowerMode(PowerLevel power_level) { switch (power_level) { case PowerLevel::kHighPower: diff --git a/connections/implementation/mediums/ble_v2.h b/connections/implementation/mediums/ble_v2.h index 5c6fa6ac..de90fbba 100644 --- a/connections/implementation/mediums/ble_v2.h +++ b/connections/implementation/mediums/ble_v2.h @@ -124,7 +124,7 @@ class BleV2 final { absl::flat_hash_map> gatt_advertisements_ ABSL_GUARDED_BY(mutex_); absl::flat_hash_set - subscribed_gatt_characteristics_ ABSL_GUARDED_BY(mutex_); + hosted_gatt_characteristics_ ABSL_GUARDED_BY(mutex_); absl::flat_hash_set scanned_service_ids_ ABSL_GUARDED_BY(mutex_); }; diff --git a/connections/implementation/mediums/ble_v2/BUILD b/connections/implementation/mediums/ble_v2/BUILD index 5044fb84..185db4f3 100644 --- a/connections/implementation/mediums/ble_v2/BUILD +++ b/connections/implementation/mediums/ble_v2/BUILD @@ -20,6 +20,7 @@ cc_library( "ble_advertisement.cc", "ble_advertisement_header.cc", "ble_packet.cc", + "ble_utils.cc", "bloom_filter.cc", ], hdrs = [ @@ -28,6 +29,7 @@ cc_library( "ble_advertisement_header.h", "ble_packet.h", "ble_peripheral.h", + "ble_utils.h", "bloom_filter.h", "discovered_peripheral_callback.h", ], @@ -37,6 +39,7 @@ cc_library( ], deps = [ "//connections:core_types", + "//connections/implementation/mediums:utils", "//internal/platform:base", "//internal/platform:logging", "//internal/platform:types", @@ -58,6 +61,7 @@ cc_test( "ble_advertisement_test.cc", "ble_packet_test.cc", "ble_peripheral_test.cc", + "ble_utils_test.cc", "bloom_filter_test.cc", ], deps = [ @@ -66,6 +70,7 @@ cc_test( "//internal/platform:comm", "//internal/platform/implementation/g3", # buildcleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/hash:hash_testing", "@com_google_absl//absl/time", "@com_google_googletest//:gtest_main", ], diff --git a/connections/implementation/mediums/ble_v2/advertisement_read_result.cc b/connections/implementation/mediums/ble_v2/advertisement_read_result.cc index 82578c0c..e1036391 100644 --- a/connections/implementation/mediums/ble_v2/advertisement_read_result.cc +++ b/connections/implementation/mediums/ble_v2/advertisement_read_result.cc @@ -32,36 +32,28 @@ const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{ .max_backoff_duration = absl::Minutes(5), }; -// Adds a successfully read advertisement for the specified slot to this read -// result. This is fundamentally different from RecordLastReadStatus() because -// we can report a read failure, but still manage to read some advertisements. -void AdvertisementReadResult::AddAdvertisement(std::int32_t slot, +void AdvertisementReadResult::AddAdvertisement(int slot, const ByteArray& advertisement) { MutexLock lock(&mutex_); - // Blindly remove from the advertisements map to make sure any existing - // key-value pair is destroyed. - advertisements_.emplace(slot, advertisement); + // Replace if existed. + advertisements_.insert_or_assign(slot, advertisement); } -// Determines whether or not an advertisement was successfully read at the -// specified slot. -bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const { +bool AdvertisementReadResult::HasAdvertisement(int slot) const { MutexLock lock(&mutex_); return advertisements_.contains(slot); } -// Retrieves all raw advertisements that were successfully read. std::vector AdvertisementReadResult::GetAdvertisements() const { MutexLock lock(&mutex_); std::vector all_advertisements; - all_advertisements.reserve(advertisements_.size()); - for (const auto& item : advertisements_) { - all_advertisements.emplace_back(&item.second); - } + std::transform(advertisements_.begin(), advertisements_.end(), + std::back_inserter(all_advertisements), + [](auto& kv) { return &kv.second; }); return all_advertisements; } @@ -73,7 +65,7 @@ AdvertisementReadResult::EvaluateRetryStatus() const { MutexLock lock(&mutex_); // Check if we have already succeeded reading this advertisement. - if (status_ == Status::kSuccess) { + if (result_.has_value() && *result_ == Result::kSuccess) { return RetryStatus::kPreviouslySucceeded; } @@ -85,9 +77,6 @@ AdvertisementReadResult::EvaluateRetryStatus() const { return RetryStatus::kRetry; } -// Records the status of the latest read, and updates the next backoff -// duration for subsequent reads. Be sure to also call -// AddAdvertisement() if any advertisements were read. void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { MutexLock lock(&mutex_); @@ -101,7 +90,7 @@ void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { } else { // Determine whether or not we were already failing before. If we were, we // should increase the backoff duration. - if (status_ == Status::kFailure) { + if (result_.has_value() && *result_ == Result::kFailure) { // Use exponential backoff to determine the next backoff duration. This // simply involves multiplying our current backoff duration by some // multiplier. @@ -119,11 +108,9 @@ void AdvertisementReadResult::RecordLastReadStatus(bool is_success) { } // Update the internal result. - status_ = is_success ? Status::kSuccess : Status::kFailure; + result_ = is_success ? Result::kSuccess : Result::kFailure; } -// Returns how much time has passed since we last tried reading from an -// advertisement GATT server. absl::Duration AdvertisementReadResult::GetDurationSinceRead() const { MutexLock lock(&mutex_); return GetDurationSinceReadLocked(); diff --git a/connections/implementation/mediums/ble_v2/advertisement_read_result.h b/connections/implementation/mediums/ble_v2/advertisement_read_result.h index 712f56e3..d1b01bd1 100644 --- a/connections/implementation/mediums/ble_v2/advertisement_read_result.h +++ b/connections/implementation/mediums/ble_v2/advertisement_read_result.h @@ -30,17 +30,10 @@ namespace nearby { namespace connections { namespace mediums { -// Representation of a GATT advertisement read result. This object helps us -// determine whether or not we need to retry GATT reads. +// Representation of a GATT/L2CAP advertisement read result. This object helps +// us determine whether or not we need to retry GATT reads. class AdvertisementReadResult { public: - // We need a long enough duration such that we always trigger a read - // retry AND we always connect to it without delay. The former case - // helps us initialize an AdvertisementReadResult so that we - // unconditionally try reading on the first sighting. And the latter - // case helps us connect immediately when we initialize a dummy read - // result for fast advertisements (which don't use the GATT server). - struct Config { // How much to multiply the backoff duration by with every failure to read // from the advertisement GATT server. This should never be below 1! @@ -54,10 +47,11 @@ class AdvertisementReadResult { }; static const Config kDefaultConfig; + explicit AdvertisementReadResult(const Config& config = kDefaultConfig) : config_(config) {} - ~AdvertisementReadResult() = default; + // Indicator for deciding if we should retry reading the advertisement. enum class RetryStatus { kUnknown = 0, kRetry = 1, @@ -65,20 +59,38 @@ class AdvertisementReadResult { kTooSoon = 3, }; - void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement) + // Adds a successfully read advertisement for the specified slot to this read + // result. This is fundamentally different from RecordLastReadStatus() + // because we can report a read failure, but still manage to read some + // advertisements. + void AddAdvertisement(int slot, const ByteArray& advertisement) ABSL_LOCKS_EXCLUDED(mutex_); - bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_); + + // Determines whether or not an advertisement was successfully read at the + // specified slot. + bool HasAdvertisement(int slot) const ABSL_LOCKS_EXCLUDED(mutex_); + + // Retrieves all raw advertisements that were successfully read. std::vector GetAdvertisements() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Determines what stage we're in for retrying a read from an advertisement + // GATT/L2CAP server. RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_); + + // Records the status of the latest read, and updates the next backoff + // duration for subsequent reads. Be sure to also call AddAdvertisement(...) + // if any advertisements were read. void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns how much time has passed since we last tried reading from an + // advertisement GATT server. absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_); private: - enum class Status { - kUnknown = 0, + enum class Result { + kFailure = 0, kSuccess = 1, - kFailure = 2, }; absl::Duration GetDurationSinceReadLocked() const @@ -87,13 +99,21 @@ class AdvertisementReadResult { mutable Mutex mutex_; // Maps slot numbers to the GATT advertisement found in that slot. - absl::flat_hash_map advertisements_ - ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map advertisements_ ABSL_GUARDED_BY(mutex_); Config config_; - absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_); - absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_); - Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown; + absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_) = + config_.base_backoff_duration; + + // We need a long enough duration such that we always trigger a read retry AND + // we always connect to it without delay. The former case helps us initialize + // an AdvertisementReadResult so that we unconditionally try reading on the + // first sighting. And the latter case helps us connect immediately when we + // initialize a dummy read result for fast advertisements (which don't use the + // GATT server). + absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_) = + SystemClock::ElapsedRealtime() - config_.max_backoff_duration; + absl::optional result_ ABSL_GUARDED_BY(mutex_); }; } // namespace mediums diff --git a/connections/implementation/mediums/ble_v2/advertisement_read_result_test.cc b/connections/implementation/mediums/ble_v2/advertisement_read_result_test.cc index 94b79ac7..de76380e 100644 --- a/connections/implementation/mediums/ble_v2/advertisement_read_result_test.cc +++ b/connections/implementation/mediums/ble_v2/advertisement_read_result_test.cc @@ -41,7 +41,7 @@ TEST(AdvertisementReadResultTest, AdvertisementExists) { AdvertisementReadResult advertisement_read_result(test_config); advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); - std::int32_t slot = 6; + int slot = 6; advertisement_read_result.AddAdvertisement(slot, ByteArray(kAdvertisementBytes)); @@ -52,7 +52,7 @@ TEST(AdvertisementReadResultTest, AdvertisementNonExistent) { AdvertisementReadResult advertisement_read_result(test_config); advertisement_read_result.RecordLastReadStatus(/* is_success= */ true); - std::int32_t slot = 6; + int slot = 6; EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot)); } @@ -114,7 +114,7 @@ TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) { advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); // Record an absurd amount of failures so we hit the maximum backoff duration. - for (std::int32_t i = 0; i < 1000; i++) { + for (int i = 0; i < 1000; ++i) { advertisement_read_result.RecordLastReadStatus(/* is_success= */ false); } diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement.cc b/connections/implementation/mediums/ble_v2/ble_advertisement.cc index 000c1387..50608b14 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement.cc @@ -192,27 +192,12 @@ BleAdvertisement::operator ByteArray() const { bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const { return this->GetVersion() == rhs.GetVersion() && this->GetSocketVersion() == rhs.GetSocketVersion() && + this->IsFastAdvertisement() == rhs.IsFastAdvertisement() && this->GetServiceIdHash() == rhs.GetServiceIdHash() && this->GetData() == rhs.GetData() && this->GetDeviceToken() == rhs.GetDeviceToken(); } -bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const { - if (this->GetVersion() != rhs.GetVersion()) { - return this->GetVersion() < rhs.GetVersion(); - } - if (this->GetSocketVersion() != rhs.GetSocketVersion()) { - return this->GetSocketVersion() < rhs.GetSocketVersion(); - } - if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) { - return this->GetServiceIdHash() < rhs.GetServiceIdHash(); - } - if (this->GetDeviceToken() != rhs.GetDeviceToken()) { - return this->GetDeviceToken() < rhs.GetDeviceToken(); - } - return this->GetData() < rhs.GetData(); -} - bool BleAdvertisement::IsSupportedVersion(Version version) const { return version >= Version::kV1 && version <= Version::kV2; } diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement.h b/connections/implementation/mediums/ble_v2/ble_advertisement.h index 4cde9418..92c04a2e 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement.h +++ b/connections/implementation/mediums/ble_v2/ble_advertisement.h @@ -57,6 +57,15 @@ class BleAdvertisement { static constexpr int kServiceIdHashLength = 3; static constexpr int kDeviceTokenLength = 2; + // Hashable + bool operator==(const BleAdvertisement &rhs) const; + template + friend H AbslHashValue(H h, const BleAdvertisement &b) { + return H::combine(std::move(h), b.version_, b.socket_version_, + b.fast_advertisement_, b.service_id_hash_, b.data_, + b.device_token_); + } + BleAdvertisement() = default; BleAdvertisement(Version version, SocketVersion socket_version, const ByteArray &service_id_hash, const ByteArray &data, @@ -69,9 +78,6 @@ class BleAdvertisement { ~BleAdvertisement() = default; explicit operator ByteArray() const; - // Operator overloads when comparing BleAdvertisement. - bool operator==(const BleAdvertisement &rhs) const; - bool operator<(const BleAdvertisement &rhs) const; bool IsValid() const { return IsSupportedVersion(version_); } Version GetVersion() const { return version_; } diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc b/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc index 1587f991..23ea7ab1 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_header.cc @@ -136,6 +136,16 @@ BleAdvertisementHeader::operator ByteArray() const { return ByteArray(std::move(out)); } +bool BleAdvertisementHeader::operator==( + const BleAdvertisementHeader &rhs) const { + return GetVersion() == rhs.GetVersion() && + IsExtendedAdvertisement() == rhs.IsExtendedAdvertisement() && + GetNumSlots() == rhs.GetNumSlots() && + GetServiceIdBloomFilter() == rhs.GetServiceIdBloomFilter() && + GetAdvertisementHash() == rhs.GetAdvertisementHash() && + GetPsmValue() == rhs.GetPsmValue(); +} + } // namespace mediums } // namespace connections } // namespace nearby diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_header.h b/connections/implementation/mediums/ble_v2/ble_advertisement_header.h index b3f83554..532042a2 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_header.h +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_header.h @@ -54,6 +54,15 @@ class BleAdvertisementHeader { static constexpr int kAdvertisementHashLength = 4; static constexpr int kServiceIdBloomFilterLength = 10; + // Hashable + bool operator==(const BleAdvertisementHeader &rhs) const; + template + friend H AbslHashValue(H h, const BleAdvertisementHeader &b) { + return H::combine(std::move(h), b.version_, b.extended_advertisement_, + b.num_slots_, b.service_id_bloom_filter_, + b.advertisement_hash_, b.psm_); + } + BleAdvertisementHeader() = default; BleAdvertisementHeader(Version version, bool extended_advertisement, int num_slots, diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_header_test.cc b/connections/implementation/mediums/ble_v2/ble_advertisement_header_test.cc index 82f7200e..bac9c92a 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_header_test.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_header_test.cc @@ -15,6 +15,7 @@ #include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h" #include "gtest/gtest.h" +#include "absl/hash/hash_testing.h" #include "internal/platform/base64_utils.h" namespace location { @@ -214,6 +215,23 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) { EXPECT_FALSE(short_ble_advertisement_header.IsValid()); } +TEST(BleAdvertisementHeaderTest, Hash) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ + BleAdvertisementHeader(), + BleAdvertisementHeader(kVersion, false, 0, + ByteArray(std::string(kServiceIDBloomFilter)), + ByteArray(std::string(kAdvertisementHash)), 0), + BleAdvertisementHeader( + kVersion, true, 2, + ByteArray("\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15"), + ByteArray("\x0E\x0F\x0G\x0H"), 1), + BleAdvertisementHeader( + kVersion, false, 4, + ByteArray("\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29"), + ByteArray("\x0i\x0j\x0k\x0l"), 2), + })); +} + } // namespace } // namespace mediums } // namespace connections diff --git a/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc b/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc index dbdb531b..632e5280 100644 --- a/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc +++ b/connections/implementation/mediums/ble_v2/ble_advertisement_test.cc @@ -17,6 +17,7 @@ #include #include "gtest/gtest.h" +#include "absl/hash/hash_testing.h" namespace location { namespace nearby { @@ -448,6 +449,21 @@ TEST(BleAdvertisementTest, EXPECT_FALSE(corrupted_ble_advertisement.IsValid()); } +TEST(BleAdvertisementTest, Hash) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ + BleAdvertisement(), + BleAdvertisement(kVersion, kSocketVersion, ByteArray{}, + ByteArray(std::string(kFastData)), + ByteArray(std::string(kDeviceToken))), + BleAdvertisement( + kVersion, kSocketVersion, ByteArray(std::string(kServiceIDHashBytes)), + ByteArray(std::string(kData)), ByteArray(std::string(kDeviceToken))), + BleAdvertisement(kVersion, BleAdvertisement::SocketVersion::kV1, + ByteArray{}, ByteArray(std::string(kFastData)), + ByteArray(std::string(kDeviceToken))), + })); +} + } // namespace } // namespace mediums } // namespace connections diff --git a/connections/implementation/mediums/ble_v2/ble_peripheral.h b/connections/implementation/mediums/ble_v2/ble_peripheral.h index e8c05287..cd9ba571 100644 --- a/connections/implementation/mediums/ble_v2/ble_peripheral.h +++ b/connections/implementation/mediums/ble_v2/ble_peripheral.h @@ -30,7 +30,8 @@ namespace mediums { class BlePeripheral { public: BlePeripheral() = default; - explicit BlePeripheral(const ByteArray& id) : id_(id) {} + explicit BlePeripheral(const ByteArray& id) : BlePeripheral(id, 0) {} + BlePeripheral(const ByteArray& id, int psm) : id_(id), psm_(psm) {} BlePeripheral(const BlePeripheral&) = default; BlePeripheral& operator=(const BlePeripheral&) = default; BlePeripheral(BlePeripheral&&) = default; @@ -39,11 +40,17 @@ class BlePeripheral { bool IsValid() const { return !id_.Empty(); } ByteArray GetId() const { return id_; } + int GetPsm() const { return psm_; } private: // A unique identifier for this peripheral. It is the BLE advertisement it - // was found on. + // was found on, or even simply the BLE MAC address. ByteArray id_; + + // 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. + int psm_; }; } // namespace mediums diff --git a/connections/implementation/mediums/ble_v2/ble_peripheral_test.cc b/connections/implementation/mediums/ble_v2/ble_peripheral_test.cc index 56b4b664..daa4fa97 100644 --- a/connections/implementation/mediums/ble_v2/ble_peripheral_test.cc +++ b/connections/implementation/mediums/ble_v2/ble_peripheral_test.cc @@ -23,14 +23,27 @@ namespace mediums { namespace { constexpr absl::string_view kId{"AB12"}; +constexpr int kDefaultPsm = 0; TEST(BlePeripheralTest, ConstructionWorks) { ByteArray id{std::string(kId)}; - BlePeripheral ble_peripheral{id}; + BlePeripheral ble_peripheral(id); EXPECT_TRUE(ble_peripheral.IsValid()); EXPECT_EQ(id, ble_peripheral.GetId()); + EXPECT_EQ(kDefaultPsm, ble_peripheral.GetPsm()); +} + +TEST(BlePeripheralTest, ConstructionWorksWithPsm) { + ByteArray id{std::string(kId)}; + int psm = 1; + + BlePeripheral ble_peripheral(id, psm); + + EXPECT_TRUE(ble_peripheral.IsValid()); + EXPECT_EQ(id, ble_peripheral.GetId()); + EXPECT_EQ(psm, ble_peripheral.GetPsm()); } TEST(BlePeripheralTest, ConstructionEmptyFails) { diff --git a/connections/implementation/mediums/ble_v2/ble_utils.cc b/connections/implementation/mediums/ble_v2/ble_utils.cc new file mode 100644 index 00000000..55a08823 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_utils.cc @@ -0,0 +1,71 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "connections/implementation/mediums/ble_v2/ble_utils.h" + +#include + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace bleutils { + +namespace { + +// These two values make up the base UUID we use when advertising a slot. +// The base is an all zero Version-3 name-based UUID. To turn this into an +// advertisement slot UUID, we simply OR the least significant bits with the +// slot number. +// +// More info about the format can be found here: +// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based) +constexpr std::int64_t kAdvertisementUuidMsb = 0x0000000000003000; +constexpr std::int64_t kAdvertisementUuidLsb = 0x8000000000000000; + +} // namespace + +const absl::string_view kCopresenceServiceUuid = + "0000FEF3-0000-1000-8000-00805F9B34FB"; + +ByteArray GenerateHash(const std::string& source, size_t size) { + return Utils::Sha256Hash(source, size); +} + +ByteArray GenerateServiceIdHash(const std::string& service_id) { + return Utils::Sha256Hash(service_id, BlePacket::kServiceIdHashLength); +} + +ByteArray GenerateDeviceToken() { + return Utils::Sha256Hash(std::to_string(Prng().NextUint32()), + mediums::BleAdvertisement::kDeviceTokenLength); +} + +ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes) { + return Utils::Sha256Hash(advertisement_bytes, + BleAdvertisementHeader::kAdvertisementHashLength); +} + +std::string GenerateAdvertisementUuid(int slot) { + if (slot < 0) { + return {}; + } + return std::string(Uuid(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot)); +} + +} // namespace bleutils +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/connections/implementation/mediums/ble_v2/ble_utils.h b/connections/implementation/mediums/ble_v2/ble_utils.h new file mode 100644 index 00000000..0b5a2f75 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_utils.h @@ -0,0 +1,47 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_ +#define CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_ + +#include + +#include "connections/implementation/mediums/ble_v2//ble_advertisement.h" +#include "connections/implementation/mediums/ble_v2/ble_advertisement_header.h" +#include "connections/implementation/mediums/ble_v2/ble_packet.h" +#include "connections/implementation/mediums/utils.h" +#include "connections/implementation/mediums/uuid.h" +#include "internal/platform/prng.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace bleutils { + +ABSL_CONST_INIT extern const absl::string_view kCopresenceServiceUuid; + +ByteArray GenerateHash(const std::string& source, size_t size); +ByteArray GenerateServiceIdHash(const std::string& service_id); +ByteArray GenerateDeviceToken(); +ByteArray GenerateAdvertisementHash(const ByteArray& advertisement_bytes); +std::string GenerateAdvertisementUuid(int slot); + +} // namespace bleutils +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location + +#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_UTILS_H_ diff --git a/connections/implementation/mediums/ble_v2/ble_utils_test.cc b/connections/implementation/mediums/ble_v2/ble_utils_test.cc new file mode 100644 index 00000000..95950362 --- /dev/null +++ b/connections/implementation/mediums/ble_v2/ble_utils_test.cc @@ -0,0 +1,98 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "connections/implementation/mediums/ble_v2/ble_utils.h" + +#include + +#include "gtest/gtest.h" + +namespace location { +namespace nearby { +namespace connections { +namespace mediums { +namespace bleutils { + +namespace { + +TEST(BleUtilsTest, CanGenerateHash) { + std::string service_id = {"service_id"}; + + ByteArray generated_bytes = GenerateHash(service_id, 4); + + // Can generate a non-empty byte array. + EXPECT_FALSE(generated_bytes.Empty()); + + generated_bytes = GenerateHash(service_id, 0); + + // Cannot generate a non-empty byte array for size 0. + EXPECT_TRUE(generated_bytes.Empty()); +} + +TEST(BleUtilsTest, CanGenerateServiceIdHash) { + std::string service_id = {"service_id"}; + + ByteArray generated_bytes = GenerateServiceIdHash(service_id); + + // Can generate a non-empty byte array. + EXPECT_FALSE(generated_bytes.Empty()); +} + +TEST(BleUtilsTest, CanGenerateDeviceToken) { + ByteArray generated_bytes = GenerateDeviceToken(); + + // Can generate a non-empty byte array. + EXPECT_FALSE(generated_bytes.Empty()); +} + +TEST(BleUtilsTest, CanGenerateAdvertisementHash) { + ByteArray empty_advertisement_bytes = {}; + ByteArray non_empty_advertisement_bytes("abcd"); + ByteArray generated_bytes_1 = + GenerateAdvertisementHash(empty_advertisement_bytes); + + ByteArray generated_bytes_2 = + GenerateAdvertisementHash(non_empty_advertisement_bytes); + + // Can generate a non-empty byte array. + EXPECT_FALSE(generated_bytes_1.Empty()); + EXPECT_FALSE(generated_bytes_2.Empty()); +} + +TEST(BleUtilsTest, CanGenerateAdvertisementUuid) { + std::string generated_string = GenerateAdvertisementUuid(0); + + EXPECT_EQ("00000000-0000-3000-8000-000000000000", generated_string); + + generated_string = GenerateAdvertisementUuid(1); + + EXPECT_EQ("00000000-0000-3000-8000-000000000001", generated_string); + + generated_string = GenerateAdvertisementUuid(10); + + EXPECT_EQ("00000000-0000-3000-8000-00000000000a", generated_string); + + generated_string = GenerateAdvertisementUuid(-1); + + // Can't generate an advertisement uuid for slot < 0. The result is empty. + EXPECT_TRUE(generated_string.empty()); +} + +} // namespace + +} // namespace bleutils +} // namespace mediums +} // namespace connections +} // namespace nearby +} // namespace location diff --git a/internal/platform/BUILD b/internal/platform/BUILD index a7ad448b..0c8adc30 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -197,6 +197,7 @@ cc_test( ":test_util", "//internal/platform/implementation/g3", # build_cleaner: keep "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/hash:hash_testing", "@com_google_googletest//:gtest_main", ], ) diff --git a/internal/platform/byte_array.h b/internal/platform/byte_array.h index 221961bd..fdafb690 100644 --- a/internal/platform/byte_array.h +++ b/internal/platform/byte_array.h @@ -99,6 +99,12 @@ class ByteArray { return absl::string_view(data(), size()); } + // Hashable + template + friend H AbslHashValue(H h, const ByteArray& m) { + return H::combine(std::move(h), m.data_); + } + private: std::string data_; }; diff --git a/internal/platform/byte_array_test.cc b/internal/platform/byte_array_test.cc index 366a3199..5b05978e 100644 --- a/internal/platform/byte_array_test.cc +++ b/internal/platform/byte_array_test.cc @@ -17,6 +17,7 @@ #include #include "gtest/gtest.h" +#include "absl/hash/hash_testing.h" namespace { @@ -95,4 +96,13 @@ TEST(ByteArrayTest, CreateFromAbslStringReturnsTheSame) { EXPECT_EQ(bytes.AsStringView(), kTestString); } +TEST(ByteArrayTest, Hash) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ + ByteArray(), + ByteArray("12345"), + ByteArray("ABCDE"), + ByteArray("A1B2Z"), + })); +} + } // namespace