diff --git a/connections/implementation/mediums/ble_v2/BUILD b/connections/implementation/mediums/ble_v2/BUILD index e6631f01..59723d58 100644 --- a/connections/implementation/mediums/ble_v2/BUILD +++ b/connections/implementation/mediums/ble_v2/BUILD @@ -141,6 +141,7 @@ cc_test( "//internal/platform:uuid", "//internal/platform/implementation:comm", "//internal/platform/implementation/g3", # buildcleaner: keep + "//internal/test", "//proto/mediums:ble_frames_cc_proto", "@com_github_protobuf_matchers//protobuf-matchers", "@com_google_absl//absl/base:core_headers", diff --git a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.cc b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.cc index 75218c25..6d398c1a 100644 --- a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.cc +++ b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.cc @@ -59,6 +59,8 @@ namespace mediums { namespace { constexpr int kGattThreadCount = 1; constexpr absl::Duration kInstantLostAdvertisementTimeout = absl::Seconds(60); +constexpr absl::Duration kExtendedAdvertisementHeaderDelay = absl::Seconds(3); +constexpr absl::Duration kAdvertisementHeaderExpiry = absl::Seconds(15); } // namespace DiscoveredPeripheralTracker::DiscoveredPeripheralTracker( @@ -127,6 +129,11 @@ void DiscoveredPeripheralTracker::StartTracking( .include_dct_advertisement = include_dct_advertisement, .pcp = pcp}; + if (service_id_infos_.empty()) { + medium_start_scanning_time_ = SystemClock::ElapsedRealtime(); + VLOG(1) << "Medium start scanning time: " << medium_start_scanning_time_; + } + // Replace if key exists. service_id_infos_.insert_or_assign(service_id, std::move(service_id_info)); @@ -177,8 +184,7 @@ void DiscoveredPeripheralTracker::ProcessFoundBleAdvertisement( } if (IsSkippableGattAdvertisement(advertisement_data)) { - LOG(INFO) - << "Ignore GATT advertisement and wait for extended advertisement."; + VLOG(1) << "Ignore GATT advertisement and wait for extended advertisement."; return; } @@ -330,8 +336,21 @@ bool DiscoveredPeripheralTracker::IsSkippableGattAdvertisement( BleAdvertisementHeader advertisement_header( ExtractAdvertisementHeaderBytes(advertisement_data)); - return advertisement_header.IsValid() && - advertisement_header.IsSupportExtendedAdvertisement(); + if (is_read_gatt_for_extended_advertisement_enabled_) { + if (!advertisement_header.IsValid()) { + return false; + } + + if (advertisement_header.IsSupportExtendedAdvertisement() && + (SystemClock::ElapsedRealtime() - medium_start_scanning_time_) < + kExtendedAdvertisementHeaderDelay) { + return true; + } + return false; + } else { + return advertisement_header.IsValid() && + advertisement_header.IsSupportExtendedAdvertisement(); + } } void DiscoveredPeripheralTracker::ClearGattAdvertisement( @@ -749,46 +768,67 @@ void DiscoveredPeripheralTracker::HandleAdvertisementHeader( } // Determine whether or not we need to read a fresh GATT advertisement. - if (ShouldReadRawAdvertisementFromServer(advertisement_header)) { - // Determine whether or not we need to read a fresh GATT advertisement. - if (is_fetching_in_thread_) { - VLOG(1) << ": Handle GATT advertisement header with hash " - << absl::BytesToHexString( - advertisement_header.GetAdvertisementHash().AsStringView()) - << " in thread"; + if (is_fetching_in_thread_) { + VLOG(1) << ": Handle GATT advertisement header with hash " + << absl::BytesToHexString( + advertisement_header.GetAdvertisementHash().AsStringView()) + << " in thread"; - if (!fetching_advertisements_.insert(advertisement_header).second) { - VLOG(1) << ": Ignore the advertisement header due to it " - "is already in fetching."; - return; - } + if (executor_ == nullptr) { + // The situation happens when flag value changed + executor_ = std::make_unique(kGattThreadCount); + } - if (executor_ == nullptr) { - // The situation happens when flag value changed - executor_ = std::make_unique(kGattThreadCount); - } + if (!ShouldReadRawAdvertisementFromServer(advertisement_header)) { + UpdateCommonStateForFoundBleAdvertisement(advertisement_header); + return; + } - if (is_read_gatt_for_extended_advertisement_enabled_) { - { - MutexLock lock(&task_mutex_); - gatt_fetch_tasks_.push_back({ - .peripheral = peripheral, - .advertisement_header = advertisement_header, - .advertisement_fetcher = std::move(advertisement_fetcher), - }); - cond_.Notify(); + if (is_read_gatt_for_extended_advertisement_enabled_) { + MutexLock lock(&task_mutex_); + if (advertisement_header.IsSupportExtendedAdvertisement() && + is_extended_advertisement_available_) { + for (auto& item : gatt_extended_fetch_tasks_) { + if (item.advertisement_header == advertisement_header) { + item.scheduled_time = SystemClock::ElapsedRealtime(); + UpdateCommonStateForFoundBleAdvertisement(advertisement_header); + return; + } } + + gatt_extended_fetch_tasks_.push_back({ + .peripheral = peripheral, + .advertisement_header = advertisement_header, + .advertisement_fetcher = std::move(advertisement_fetcher), + .scheduled_time = SystemClock::ElapsedRealtime(), + }); } else { - executor_->Execute([this, peripheral, advertisement_header, - advertisement_fetcher = - std::move(advertisement_fetcher)]() mutable { - FetchRawAdvertisementsInThread(peripheral, advertisement_header, - std::move(advertisement_fetcher)); + for (auto& item : gatt_fetch_tasks_) { + if (item.advertisement_header == advertisement_header) { + item.scheduled_time = SystemClock::ElapsedRealtime(); + UpdateCommonStateForFoundBleAdvertisement(advertisement_header); + return; + } + } + + gatt_fetch_tasks_.push_back({ + .peripheral = peripheral, + .advertisement_header = advertisement_header, + .advertisement_fetcher = std::move(advertisement_fetcher), + .scheduled_time = SystemClock::ElapsedRealtime(), }); } - - return; + cond_.Notify(); } else { + executor_->Execute( + [this, peripheral, advertisement_header, + advertisement_fetcher = std::move(advertisement_fetcher)]() mutable { + FetchRawAdvertisementsInThread(peripheral, advertisement_header, + std::move(advertisement_fetcher)); + }); + } + } else { + if (ShouldReadRawAdvertisementFromServer(advertisement_header)) { std::vector gatt_advertisement_bytes_list = FetchRawAdvertisements(peripheral, advertisement_header, std::move(advertisement_fetcher)); @@ -869,7 +909,7 @@ bool DiscoveredPeripheralTracker::ShouldReadRawAdvertisementFromServer( << ", but we have already read its GATT advertisement."; return false; case AdvertisementReadResult::RetryStatus::kTooSoon: - LOG(INFO) + VLOG(1) << "Received advertisement header with hash " << absl::BytesToHexString( advertisement_header.GetAdvertisementHash().AsStringView()) @@ -922,7 +962,10 @@ void DiscoveredPeripheralTracker::FetchRawAdvertisementsInThread( if (!IsInterestingAdvertisementHeader(advertisement_header)) { LOG(INFO) << ": Ignore to read raw advertisement from server due to it " "is not interesting header now."; - fetching_advertisements_.erase(advertisement_header); + return; + } + + if (!ShouldReadRawAdvertisementFromServer(advertisement_header)) { return; } @@ -943,7 +986,6 @@ void DiscoveredPeripheralTracker::FetchRawAdvertisementsInThread( LOG(WARNING) << ": Ignore the fetched GATT advertisement from server due to it " "is not interesting header now."; - fetching_advertisements_.erase(advertisement_header); return; } @@ -952,9 +994,9 @@ void DiscoveredPeripheralTracker::FetchRawAdvertisementsInThread( std::vector gatt_advertisement_bytes_list = it.first->second->GetAdvertisements(); - if (gatt_advertisement_bytes_list.empty() || - !IsInterestingAdvertisementHeader(advertisement_header)) { - fetching_advertisements_.erase(advertisement_header); + if (gatt_advertisement_bytes_list.empty()) { + VLOG(1) << ": Ignore the fetched GATT advertisement from server due to " + "it is empty."; return; } @@ -962,7 +1004,6 @@ void DiscoveredPeripheralTracker::FetchRawAdvertisementsInThread( gatt_advertisement_bytes_list, /*service_uuid=*/{}); UpdateCommonStateForFoundBleAdvertisement(advertisement_header); - fetching_advertisements_.erase(advertisement_header); VLOG(1) << ": Completed to handle GATT advertisement header with hash " << absl::BytesToHexString( advertisement_header.GetAdvertisementHash().AsStringView()) @@ -979,17 +1020,37 @@ void DiscoveredPeripheralTracker::GattFetchingLoop() { gatt_fetch_tasks_.clear(); return; } - while (gatt_fetch_tasks_.empty() && !shutting_down_) { + while (gatt_fetch_tasks_.empty() && gatt_extended_fetch_tasks_.empty() && + !shutting_down_) { cond_.Wait(); } if (shutting_down_) { gatt_fetch_tasks_.clear(); + gatt_extended_fetch_tasks_.clear(); return; } - task = std::move(gatt_fetch_tasks_.front()); - gatt_fetch_tasks_.pop_front(); + // Non-extended advertisements are prioritized over extended + // advertisements. + if (!gatt_fetch_tasks_.empty()) { + task = std::move(gatt_fetch_tasks_.front()); + gatt_fetch_tasks_.pop_front(); + } else if (!gatt_extended_fetch_tasks_.empty()) { + task = std::move(gatt_extended_fetch_tasks_.front()); + gatt_extended_fetch_tasks_.pop_front(); + } + } + + // Check if the task is expired. + if (SystemClock::ElapsedRealtime() - task.scheduled_time > + kAdvertisementHeaderExpiry) { + VLOG(1) << "GATT advertisement with hash: " + << absl::BytesToHexString( + task.advertisement_header.GetAdvertisementHash() + .AsStringView()) + << " is expired, skip to fetch raw advertisement."; + continue; } FetchRawAdvertisementsInThread(task.peripheral, task.advertisement_header, @@ -1001,7 +1062,7 @@ void DiscoveredPeripheralTracker::UpdateCommonStateForFoundBleAdvertisement( const BleAdvertisementHeader& advertisement_header) { const auto ga_it = gatt_advertisements_.find(advertisement_header); if (ga_it == gatt_advertisements_.end()) { - LOG(INFO) + VLOG(1) << "No GATT advertisements found for advertisement header with hash " << absl::BytesToHexString( advertisement_header.GetAdvertisementHash().AsStringView()); diff --git a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h index 72cbdd27..1c5e22a5 100644 --- a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h +++ b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker.h @@ -158,6 +158,7 @@ class DiscoveredPeripheralTracker { BleV2Peripheral peripheral; BleAdvertisementHeader advertisement_header; AdvertisementFetcher advertisement_fetcher; + absl::Time scheduled_time; }; // Clears stale data from any previous sessions. @@ -309,6 +310,9 @@ class DiscoveredPeripheralTracker { bool is_read_gatt_for_extended_advertisement_enabled_ = true; bool is_extended_advertisement_available_; + absl::Time medium_start_scanning_time_ ABSL_GUARDED_BY(mutex_) = + absl::InfiniteFuture(); + // ------------ SERVICE ID MAPS ------------ // Entries in these maps all follow the same lifecycle. Entries are added in // StartTracking, and removed in StopTracking. @@ -351,10 +355,6 @@ class DiscoveredPeripheralTracker { absl::flat_hash_map gatt_advertisement_infos_ ABSL_GUARDED_BY(mutex_); - // Tracks the advertisements in GATT fetching. - absl::flat_hash_set fetching_advertisements_ - ABSL_GUARDED_BY(mutex_); - std::unique_ptr executor_ ABSL_GUARDED_BY(mutex_) = nullptr; @@ -362,6 +362,8 @@ class DiscoveredPeripheralTracker { AtomicBoolean shutting_down_{false}; ConditionVariable cond_{&task_mutex_}; std::deque gatt_fetch_tasks_ ABSL_GUARDED_BY(task_mutex_); + std::deque gatt_extended_fetch_tasks_ + ABSL_GUARDED_BY(task_mutex_); // Maps an advertisement header's hash with the time it's reported lost. // Ignores subsequent discovery events for the same advertisement header. diff --git a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker_test.cc b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker_test.cc index 702519cc..a20dc810 100644 --- a/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker_test.cc +++ b/connections/implementation/mediums/ble_v2/discovered_peripheral_tracker_test.cc @@ -17,8 +17,10 @@ #include #include #include +#include #include #include +#include #include #include "gmock/gmock.h" @@ -51,6 +53,7 @@ #include "internal/platform/mutex.h" #include "internal/platform/mutex_lock.h" #include "internal/platform/uuid.h" +#include "internal/test/fake_clock.h" namespace nearby { namespace connections { @@ -66,6 +69,7 @@ constexpr absl::string_view kData = "\x04\x02\x00"; constexpr absl::string_view kData2 = "\x07\x00\x07"; constexpr absl::string_view kDeviceToken = "\x04\x20"; constexpr absl::string_view kDeviceName = "device"; +constexpr absl::Duration kDefaultGattFetchDelay = absl::Milliseconds(200); ByteArray CreateFastBleAdvertisement(const ByteArray& data, const ByteArray& device_token) { @@ -141,6 +145,24 @@ ByteArray CreateBleAdvertisementHeader(const ByteArray& advertisement_hash, service_ids); } +ByteArray CreateExtendedBleAdvertisementHeader( + const ByteArray& advertisement_hash, + std::vector& service_ids) { + BloomFilter service_id_bloom_filter( + std::make_unique>()); + + for (const std::string& service_id : service_ids) { + service_id_bloom_filter.Add(service_id); + } + + return ByteArray(BleAdvertisementHeader( + BleAdvertisementHeader::Version::kV2, + /*extended_advertisement=*/true, + /*num_slots=*/service_ids.size(), ByteArray(service_id_bloom_filter), + advertisement_hash, BleAdvertisementHeader::kDefaultPsmValue)); +} + ByteArray GenerateRandomAdvertisementHash() { ByteArray random_advertisement_hash = Utils::GenerateRandomBytes( BleAdvertisementHeader::kAdvertisementHashByteLength); @@ -179,13 +201,18 @@ class DiscoveredPeripheralTrackerTest config_package_nearby::nearby_connections_feature:: kEnableGattQueryInThread, std::get<0>(GetParam())); + bool enable_read_gatt_for_extended_advertisement = std::get<1>(GetParam()); NearbyFlags::GetInstance().OverrideBoolFlagValue( config_package_nearby::nearby_connections_feature:: kEnableReadGattForExtendedAdvertisement, - std::get<1>(GetParam())); - MediumEnvironment::Instance().Start(); + enable_read_gatt_for_extended_advertisement); + EnvironmentConfig config{ + .webrtc_enabled = false, + .use_simulated_clock = enable_read_gatt_for_extended_advertisement}; + MediumEnvironment::Instance().Start(config); discovered_peripheral_tracker_ = - std::make_unique(); + std::make_unique( + enable_read_gatt_for_extended_advertisement); adapter_peripheral_ = std::make_unique(); adapter_central_ = std::make_unique(); ble_peripheral_ = std::make_unique(*adapter_peripheral_); @@ -216,6 +243,20 @@ class DiscoveredPeripheralTrackerTest GetAdvertisementFetcher(fetch_latch, advertisement_bytes_list)); } + // Simulates to see an extended advertisement, which don't need to fetch + // advertisement from GATT. + void FindExtendedAdvertisement( + const api::ble_v2::BleAdvertisementData& advertisement_data, + CountDownLatch& fetch_latch) { + BleV2Peripheral peripheral = CreateBlePeripheral(); + DiscoveredPeripheralTracker::AdvertisementFetcher placeholder_fetcher = + [](BleV2Peripheral, int, int, const std::vector&, + mediums::AdvertisementReadResult&) {}; + discovered_peripheral_tracker_->ProcessFoundBleAdvertisement( + peripheral, advertisement_data, std::move(placeholder_fetcher)); + fetch_latch.CountDown(); + } + // Simulates to see a regular advertisement. void FindAdvertisement( const api::ble_v2::BleAdvertisementData& advertisement_data, @@ -228,15 +269,16 @@ class DiscoveredPeripheralTrackerTest GetAdvertisementFetcher(fetch_latch, advertisement_bytes_list)); } - void FindAdvertisementWithSlowFetcher( + void FindAdvertisementWithDelay( const api::ble_v2::BleAdvertisementData& advertisement_data, const std::vector& advertisement_bytes_list, - CountDownLatch& fetch_latch) { + CountDownLatch& fetch_latch, absl::Duration delay) { BleV2Peripheral peripheral = CreateBlePeripheral(); discovered_peripheral_tracker_->ProcessFoundBleAdvertisement( peripheral, advertisement_data, - GetSlowAdvertisementFetcher(fetch_latch, advertisement_bytes_list)); + GetAdvertisementFetcherWithDelay(fetch_latch, advertisement_bytes_list, + delay)); } int GetFetchAdvertisementCallbackCount() const { @@ -278,10 +320,12 @@ class DiscoveredPeripheralTrackerTest }; } - DiscoveredPeripheralTracker::AdvertisementFetcher GetSlowAdvertisementFetcher( + DiscoveredPeripheralTracker::AdvertisementFetcher + GetAdvertisementFetcherWithDelay( CountDownLatch& fetch_latch, - const std::vector& advertisement_bytes_list) { - return [this, &fetch_latch, advertisement_bytes_list]( + const std::vector& advertisement_bytes_list, + absl::Duration delay) { + return [this, &fetch_latch, advertisement_bytes_list, delay]( BleV2Peripheral peripheral, int num_slots, int psm, const std::vector& interesting_service_ids, mediums::AdvertisementReadResult& advertisement_read_result) { @@ -289,7 +333,7 @@ class DiscoveredPeripheralTrackerTest fetch_count_++; int slot = 0; // In real environment, the GATT fetch may run about 3-5 seconds. - absl::SleepFor(absl::Milliseconds(200)); + absl::SleepFor(delay); for (const auto& advertisement_bytes : advertisement_bytes_list) { advertisement_read_result.AddAdvertisement(slot++, advertisement_bytes); } @@ -1539,11 +1583,11 @@ TEST_P(DiscoveredPeripheralTrackerTest, FetchGattAdvertisementInThread) { {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); } - FindAdvertisementWithSlowFetcher(advertisement_data, {advertisement_bytes}, - fetch_latch); + FindAdvertisementWithDelay(advertisement_data, {advertisement_bytes}, + fetch_latch, kDefaultGattFetchDelay); // We should receive a client callback of a peripheral discovery. - fetch_latch.Await(kWaitDuration); + EXPECT_TRUE(fetch_latch.Await(kWaitDuration).ok()); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1); } @@ -1586,13 +1630,13 @@ TEST_P(DiscoveredPeripheralTrackerTest, {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); } - FindAdvertisementWithSlowFetcher(advertisement_data, {advertisement_bytes}, - fetch_latch); + FindAdvertisementWithDelay(advertisement_data, {advertisement_bytes}, + fetch_latch, kDefaultGattFetchDelay); // We should receive a client callback of a peripheral discovery. absl::SleepFor(absl::Milliseconds(20)); discovered_peripheral_tracker_->StopTracking(std::string(kServiceIdA)); - fetch_latch.Await(kWaitDuration); + EXPECT_TRUE(fetch_latch.Await(kWaitDuration).ok()); EXPECT_FALSE(found_latch.Await(kWaitDuration).result()); EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1); } @@ -1638,8 +1682,8 @@ TEST_P(DiscoveredPeripheralTrackerTest, {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); } - FindAdvertisementWithSlowFetcher(advertisement_data, {advertisement_bytes}, - fetch_latch); + FindAdvertisementWithDelay(advertisement_data, {advertisement_bytes}, + fetch_latch, kDefaultGattFetchDelay); api::ble_v2::BleAdvertisementData advertisement_data_2{}; if (!advertisement_header_bytes_2.Empty()) { @@ -1647,13 +1691,331 @@ TEST_P(DiscoveredPeripheralTrackerTest, {bleutils::kCopresenceServiceUuid, advertisement_header_bytes_2}); } - FindAdvertisementWithSlowFetcher(advertisement_data_2, - {advertisement_bytes_2}, fetch_latch); + FindAdvertisementWithDelay(advertisement_data_2, {advertisement_bytes_2}, + fetch_latch, kDefaultGattFetchDelay); + + // We should receive a client callback of a peripheral discovery. + EXPECT_TRUE(fetch_latch.Await(kWaitDuration).ok()); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 2); +} + +TEST_P(DiscoveredPeripheralTrackerTest, + GattAdvertisementGotEarlierThanExtendedAdvertisement) { + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableGattQueryInThread) || + !NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableReadGattForExtendedAdvertisement)) { + return; + } + + std::vector service_ids = {std::string(kServiceIdA)}; + ByteArray advertisement_hash = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_bytes = + CreateExtendedBleAdvertisementHeader(advertisement_hash, service_ids); + ByteArray advertisement_bytes = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string(kData)), + ByteArray(std::string(kDeviceToken))); + CountDownLatch found_latch(1); + CountDownLatch fetch_latch(2); + + discovered_peripheral_tracker_->StartTracking( + std::string(kServiceIdA), false, Pcp::kP2pPointToPoint, + { + .peripheral_discovered_cb = + [&found_latch](BleV2Peripheral peripheral, + const std::string& service_id, + const ByteArray& advertisement_bytes, + bool fast_advertisement) { + EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData))); + EXPECT_FALSE(fast_advertisement); + found_latch.CountDown(); + }, + }, + bleutils::kCopresenceServiceUuid); + + // 1. First receive a GATT advertisement data, it should be skipped. + api::ble_v2::BleAdvertisementData advertisement_data{}; + if (!advertisement_header_bytes.Empty()) { + advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); + } + + FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch); + + // 2. Receive extended advertisement data first. + api::ble_v2::BleAdvertisementData extended_advertisement_data{}; + extended_advertisement_data.is_extended_advertisement = true; + extended_advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_bytes}); + + FindExtendedAdvertisement(extended_advertisement_data, fetch_latch); // We should receive a client callback of a peripheral discovery. fetch_latch.Await(kWaitDuration); EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); - EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 2); + EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0); +} + +TEST_P(DiscoveredPeripheralTrackerTest, + OnlyGattAdvertisementReceivedOnDeviceWithExtended) { + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableGattQueryInThread) || + !NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableReadGattForExtendedAdvertisement)) { + return; + } + + std::optional fake_clock = + MediumEnvironment::Instance().GetSimulatedClock(); + + std::vector service_ids = {std::string(kServiceIdA)}; + ByteArray advertisement_hash = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_bytes = + CreateExtendedBleAdvertisementHeader(advertisement_hash, service_ids); + ByteArray advertisement_bytes = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string(kData)), + ByteArray(std::string(kDeviceToken))); + CountDownLatch found_latch(1); + CountDownLatch fetch_latch(2); + + discovered_peripheral_tracker_->StartTracking( + std::string(kServiceIdA), false, Pcp::kP2pPointToPoint, + { + .peripheral_discovered_cb = + [&found_latch](BleV2Peripheral peripheral, + const std::string& service_id, + const ByteArray& advertisement_bytes, + bool fast_advertisement) { + EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData))); + EXPECT_FALSE(fast_advertisement); + found_latch.CountDown(); + }, + }, + bleutils::kCopresenceServiceUuid); + + // 1. First receive a GATT advertisement data, it should be skipped. + api::ble_v2::BleAdvertisementData advertisement_data{}; + if (!advertisement_header_bytes.Empty()) { + advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); + } + + FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch); + + // 2. Receive GATT advertisement data again after 4 seconds, it should access + // GATT server. + (*fake_clock)->FastForward(absl::Seconds(4)); + FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch); + + // We should receive a client callback of a peripheral discovery. + fetch_latch.Await(kWaitDuration); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1); +} + +TEST_P(DiscoveredPeripheralTrackerTest, SkipExpiredGattAdvertisement) { + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableGattQueryInThread) || + !NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableReadGattForExtendedAdvertisement)) { + return; + } + + std::optional fake_clock = + MediumEnvironment::Instance().GetSimulatedClock(); + + std::vector service_ids = {std::string(kServiceIdA)}; + ByteArray advertisement_hash = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_bytes = + CreateExtendedBleAdvertisementHeader(advertisement_hash, service_ids); + ByteArray advertisement_bytes = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string(kData)), + ByteArray(std::string(kDeviceToken))); + CountDownLatch found_latch(1); + CountDownLatch fetch_latch(2); + + discovered_peripheral_tracker_->StartTracking( + std::string(kServiceIdA), false, Pcp::kP2pPointToPoint, + { + .peripheral_discovered_cb = + [&found_latch](BleV2Peripheral peripheral, + const std::string& service_id, + const ByteArray& advertisement_bytes, + bool fast_advertisement) { + EXPECT_EQ(advertisement_bytes, ByteArray(std::string(kData))); + EXPECT_FALSE(fast_advertisement); + found_latch.CountDown(); + }, + }, + bleutils::kCopresenceServiceUuid); + + // 1. First receive a GATT advertisement data, it should be skipped. + api::ble_v2::BleAdvertisementData advertisement_data{}; + if (!advertisement_header_bytes.Empty()) { + advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); + } + + FindAdvertisementWithDelay(advertisement_data, {advertisement_bytes}, + fetch_latch, kDefaultGattFetchDelay); + + // 2. The GATT advertisement will be expired after 20 seconds. + FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch); + (*fake_clock)->FastForward(absl::Seconds(20)); + + // We should receive a client callback of a peripheral discovery. + fetch_latch.Await(kWaitDuration); + EXPECT_FALSE(found_latch.Await(kWaitDuration).result()); + EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 0); +} + +TEST_P(DiscoveredPeripheralTrackerTest, + DiscoveredOnceWhenGattAndExtendedAdvertisementReceived) { + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableGattQueryInThread) || + !NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableReadGattForExtendedAdvertisement)) { + return; + } + + std::optional fake_clock = + MediumEnvironment::Instance().GetSimulatedClock(); + + std::vector service_ids = {std::string(kServiceIdA)}; + ByteArray advertisement_hash = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_bytes = + CreateExtendedBleAdvertisementHeader(advertisement_hash, service_ids); + ByteArray advertisement_bytes = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string(kData)), + ByteArray(std::string(kDeviceToken))); + CountDownLatch found_latch(1); + CountDownLatch fetch_latch(2); + std::atomic callback_count = 0; + + discovered_peripheral_tracker_->StartTracking( + std::string(kServiceIdA), false, Pcp::kP2pPointToPoint, + { + .peripheral_discovered_cb = + [&found_latch, &callback_count]( + BleV2Peripheral peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, + bool fast_advertisement) { + ++callback_count; + found_latch.CountDown(); + }, + }, + bleutils::kCopresenceServiceUuid); + (*fake_clock)->FastForward(absl::Seconds(4)); + + // 1. Received extended advertisement. + api::ble_v2::BleAdvertisementData extended_advertisement_data{}; + extended_advertisement_data.is_extended_advertisement = true; + extended_advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_bytes}); + + FindExtendedAdvertisement(extended_advertisement_data, fetch_latch); + + // 2. Received GATT advertisement. + api::ble_v2::BleAdvertisementData advertisement_data{}; + if (!advertisement_header_bytes.Empty()) { + advertisement_data.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_bytes}); + } + + FindAdvertisement(advertisement_data, {advertisement_bytes}, fetch_latch); + + EXPECT_TRUE(fetch_latch.Await(kWaitDuration).ok()); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + EXPECT_EQ(GetFetchAdvertisementCallbackCount(), 1); + EXPECT_EQ(callback_count, 1); +} + +TEST_P(DiscoveredPeripheralTrackerTest, + FindGattAdvertisementInHigherPriorityThanExtendedGattAdvertisement) { + if (!NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableGattQueryInThread) || + !NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableReadGattForExtendedAdvertisement)) { + return; + } + + std::optional fake_clock = + MediumEnvironment::Instance().GetSimulatedClock(); + std::vector service_ids = {std::string(kServiceIdA)}; + ByteArray advertisement_hash_a = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_a = + CreateBleAdvertisementHeader(advertisement_hash_a, service_ids); + ByteArray advertisement_a = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string("advertisement_a")), + ByteArray(std::string(kDeviceToken))); + ByteArray advertisement_hash_b = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_b = + CreateExtendedBleAdvertisementHeader(advertisement_hash_b, service_ids); + ByteArray advertisement_b = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string("advertisement_b")), + ByteArray(std::string(kDeviceToken))); + ByteArray advertisement_hash_c = GenerateRandomAdvertisementHash(); + ByteArray advertisement_header_c = + CreateBleAdvertisementHeader(advertisement_hash_c, service_ids); + ByteArray advertisement_c = CreateBleAdvertisement( + std::string(kServiceIdA), ByteArray(std::string("advertisement_c")), + ByteArray(std::string(kDeviceToken))); + CountDownLatch found_latch(3); + CountDownLatch fetch_latch(3); + std::vector discovered_peripheral_order; + + discovered_peripheral_tracker_->StartTracking( + std::string(kServiceIdA), false, Pcp::kP2pPointToPoint, + { + .peripheral_discovered_cb = + [&found_latch, &discovered_peripheral_order]( + BleV2Peripheral peripheral, const std::string& service_id, + const ByteArray& advertisement_bytes, + bool fast_advertisement) { + discovered_peripheral_order.push_back( + std::string(advertisement_bytes)); + found_latch.CountDown(); + }, + }, + bleutils::kCopresenceServiceUuid); + (*fake_clock)->FastForward(absl::Seconds(4)); + + // 1. Find peripheral A with GATT advertisement. + api::ble_v2::BleAdvertisementData advertisement_data_a{}; + advertisement_data_a.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_a}); + FindAdvertisementWithDelay(advertisement_data_a, {advertisement_a}, + fetch_latch, kDefaultGattFetchDelay); + + // 2. Find peripheral B with extended advertisement. + api::ble_v2::BleAdvertisementData advertisement_data_b{}; + advertisement_data_b.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_b}); + FindAdvertisement(advertisement_data_b, {advertisement_b}, fetch_latch); + + // 3. Find peripheral C with GATT advertisement. + api::ble_v2::BleAdvertisementData advertisement_data_c{}; + advertisement_data_c.service_data.insert( + {bleutils::kCopresenceServiceUuid, advertisement_header_c}); + FindAdvertisement(advertisement_data_c, {advertisement_c}, fetch_latch); + + EXPECT_TRUE(fetch_latch.Await(kWaitDuration).ok()); + EXPECT_TRUE(found_latch.Await(kWaitDuration).result()); + ASSERT_EQ(discovered_peripheral_order.size(), 3); + EXPECT_EQ(discovered_peripheral_order[0], "advertisement_a"); + EXPECT_EQ(discovered_peripheral_order[1], "advertisement_c"); + EXPECT_EQ(discovered_peripheral_order[2], "advertisement_b"); } INSTANTIATE_TEST_SUITE_P(