From 6922eea95fa8f26df747702f5d3d184c6bd0ff7c Mon Sep 17 00:00:00 2001 From: Francis Tsui Date: Fri, 26 Sep 2025 13:07:26 -0700 Subject: [PATCH] Cleanup OutgoingTargetsManager. PiperOrigin-RevId: 811928178 --- sharing/BUILD | 21 +- sharing/nearby_sharing_service_impl.cc | 81 ++--- sharing/outgoing_targets_manager.cc | 115 ++++--- sharing/outgoing_targets_manager.h | 95 ++++-- sharing/outgoing_targets_manager_test.cc | 388 +++++++++++++++++++++++ 5 files changed, 565 insertions(+), 135 deletions(-) create mode 100644 sharing/outgoing_targets_manager_test.cc diff --git a/sharing/BUILD b/sharing/BUILD index 278a100c..f49fcec5 100644 --- a/sharing/BUILD +++ b/sharing/BUILD @@ -269,16 +269,15 @@ cc_library( ":thread_timer", ":transfer_metadata", ":types", - "//internal/flags:nearby_flags", "//internal/platform:types", "//proto:sharing_enums_cc_proto", "//sharing/analytics", "//sharing/certificates", - "//sharing/flags/generated:generated_flags", "//sharing/internal/public:logging", "//sharing/proto:enums_cc_proto", "//sharing/proto:share_cc_proto", "//sharing/proto:wire_format_cc_proto", + "@com_google_absl//absl/base:nullability", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/functional:any_invocable", "@com_google_absl//absl/strings", @@ -940,3 +939,21 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "outgoing_targets_manager_test", + srcs = ["outgoing_targets_manager_test.cc"], + deps = [ + ":outgoing_targets_manager", + ":share_session", + ":test_support", + ":transfer_metadata", + ":types", + "//internal/platform/implementation:platform_impl", + "//internal/test", + "//sharing/analytics", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/strings:string_view", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sharing/nearby_sharing_service_impl.cc b/sharing/nearby_sharing_service_impl.cc index c0e480f0..128ce961 100644 --- a/sharing/nearby_sharing_service_impl.cc +++ b/sharing/nearby_sharing_service_impl.cc @@ -107,6 +107,7 @@ namespace nearby::sharing { namespace { using BlockedVendorId = ::nearby::sharing::Advertisement::BlockedVendorId; +using ::absl::Milliseconds; using ::location::nearby::proto::sharing::OSType; using ::location::nearby::proto::sharing::ResponseToIntroduction; using ::location::nearby::proto::sharing::SessionStatus; @@ -239,9 +240,13 @@ NearbySharingServiceImpl::NearbySharingServiceImpl( outgoing_targets_manager_( context->GetClock(), service_thread_.get(), nearby_connections_manager_.get(), &analytics_recorder_, + absl::bind_front( + &NearbySharingServiceImpl::NotifyShareTargetDiscovered, this), absl::bind_front(&NearbySharingServiceImpl::NotifyShareTargetUpdated, this), absl::bind_front(&NearbySharingServiceImpl::NotifyShareTargetLost, + this), + absl::bind_front(&NearbySharingServiceImpl::OnOutgoingTransferUpdate, this)) { CHECK(nearby_connections_manager_); CHECK(analytics_recorder); @@ -1608,10 +1613,11 @@ void NearbySharingServiceImpl::HandleEndpointLost( discovered_advertisements_to_retry_map_.erase(endpoint_id); discovered_advertisements_retried_set_.erase(endpoint_id); - outgoing_targets_manager_.MoveToDiscoveryCache(std::string(endpoint_id), - NearbyFlags::GetInstance().GetInt64Flag( - config_package_nearby::nearby_sharing_feature:: - kDiscoveryCacheLostExpiryMs)); + outgoing_targets_manager_.OnShareTargetLost( + std::string(endpoint_id), + Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( + config_package_nearby::nearby_sharing_feature:: + kDiscoveryCacheLostExpiryMs))); FinishEndpointDiscoveryEvent(); } @@ -1703,53 +1709,8 @@ void NearbySharingServiceImpl::OnOutgoingDecryptedCertificate( return; } LogShareTargetDiscovered(*share_target); - if (outgoing_targets_manager_.FindDuplicateInOutgoingShareTargets( - endpoint_id, *share_target)) { - outgoing_targets_manager_.DeduplicateInOutgoingShareTarget( - *share_target, endpoint_id, std::move(certificate)); - FinishEndpointDiscoveryEvent(); - return; - } - bool in_discovery_cache = - outgoing_targets_manager_.FindDuplicateInDiscoveryCache(endpoint_id, - *share_target); - VLOG(1) << __func__ << ": Adding (endpoint_id=" << endpoint_id - << ", share_target_id=" << share_target->id - << ") to outgoing share target map"; - outgoing_targets_manager_.CreateOutgoingShareSession( - *share_target, endpoint_id, std::move(certificate), - absl::bind_front(&NearbySharingServiceImpl::OnOutgoingTransferUpdate, - this)); - if (in_discovery_cache) { - NotifyShareTargetUpdated(*share_target); - - LOG(INFO) - << __func__ - << ": [Dedupped] Reported NotifyShareTargetUpdated to all surfaces " - "for share_target: " - << share_target->ToString(); - FinishEndpointDiscoveryEvent(); - return; - } - - // Update the endpoint id for the share target. - LOG(INFO) << __func__ << ": An endpoint: " << endpoint_id - << " has been discovered, with an advertisement " - "containing a valid share target with id: " - << share_target->id; - - // Notifies the user that we discovered a device. - VLOG(1) << __func__ << ": There are " - << (foreground_send_surface_map_.size() + - background_send_surface_map_.size()) - << " discovery callbacks be called."; - - NotifyShareTargetDiscovered(*share_target); - - VLOG(1) << __func__ << ": NotifyShareTargetDiscovered: share_target: " - << share_target->ToString() << " endpoint_id=" << endpoint_id - << " to all send surfaces."; - + outgoing_targets_manager_.OnShareTargetDiscovered( + *share_target, endpoint_id, std::move(certificate)); FinishEndpointDiscoveryEvent(); } @@ -2070,7 +2031,10 @@ void NearbySharingServiceImpl::StartScanning() { is_scanning_ = true; InvalidateReceiveSurfaceState(); - outgoing_targets_manager_.DisableAllOutgoingShareTargets(); + outgoing_targets_manager_.AllTargetsLost( + Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( + config_package_nearby::nearby_sharing_feature:: + kDiscoveryCacheLostExpiryMs))); discovered_advertisements_to_retry_map_.clear(); discovered_advertisements_retried_set_.clear(); @@ -3093,21 +3057,22 @@ void NearbySharingServiceImpl::UnregisterShareTarget(int64_t share_target_id) { // Find the endpoint id that matches the given share target. OutgoingShareSession* session = outgoing_targets_manager_.GetOutgoingShareSession(share_target_id); + absl::Duration cache_retention = + Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( + config_package_nearby::nearby_sharing_feature:: + kUnregisterTargetDiscoveryCacheLostExpiryMs)); if (session != nullptr) { LOG(INFO) << __func__ << ": [Dedupped] Move the endpoint " << session->endpoint_id() << " to discovery_cache."; - outgoing_targets_manager_.MoveToDiscoveryCache( - session->endpoint_id(), - NearbyFlags::GetInstance().GetInt64Flag( - config_package_nearby::nearby_sharing_feature:: - kUnregisterTargetDiscoveryCacheLostExpiryMs)); + outgoing_targets_manager_.OnShareTargetLost( + session->endpoint_id(), cache_retention); } else { // Be careful not to clear out the share session map if a new session // was started during the cancellation delay. if (!is_scanning_ && !is_transferring_) { LOG(INFO) << "Cannot find session for target " << share_target_id << " clearing all outgoing sessions."; - outgoing_targets_manager_.DisableAllOutgoingShareTargets(); + outgoing_targets_manager_.AllTargetsLost(cache_retention); } } diff --git a/sharing/outgoing_targets_manager.cc b/sharing/outgoing_targets_manager.cc index 655c670c..b9e473f5 100644 --- a/sharing/outgoing_targets_manager.cc +++ b/sharing/outgoing_targets_manager.cc @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,22 +16,22 @@ #include +#include #include #include #include #include +#include "absl/base/nullability.h" #include "absl/functional/any_invocable.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" -#include "internal/flags/nearby_flags.h" #include "internal/platform/clock.h" #include "internal/platform/task_runner.h" #include "proto/sharing_enums.pb.h" #include "sharing/analytics/analytics_recorder.h" #include "sharing/certificates/nearby_share_decrypted_public_certificate.h" -#include "sharing/flags/generated/nearby_sharing_feature_flags.h" #include "sharing/internal/public/logging.h" #include "sharing/nearby_connections_manager.h" #include "sharing/outgoing_share_session.h" @@ -45,20 +45,71 @@ namespace nearby::sharing { OutgoingTargetsManager::OutgoingTargetsManager( - Clock* clock, TaskRunner* service_thread, - NearbyConnectionsManager* connections_manager, + Clock* absl_nonnull clock, TaskRunner* absl_nonnull service_thread, + NearbyConnectionsManager* absl_nonnull connections_manager, analytics::AnalyticsRecorder* analytics_recorder, + absl::AnyInvocable + share_target_discovered_callback, absl::AnyInvocable share_target_updated_callback, - absl::AnyInvocable share_target_lost_callback) + absl::AnyInvocable share_target_lost_callback, + std::function + transfer_update_callback) : clock_(*clock), service_thread_(*service_thread), connections_manager_(*connections_manager), analytics_recorder_(*analytics_recorder), + share_target_discovered_callback_( + std::move(share_target_discovered_callback)), share_target_updated_callback_(std::move(share_target_updated_callback)), - share_target_lost_callback_(std::move(share_target_lost_callback)) {} + share_target_lost_callback_(std::move(share_target_lost_callback)), + transfer_update_callback_(std::move(transfer_update_callback)) {} +void OutgoingTargetsManager::OnShareTargetDiscovered( + ShareTarget share_target, absl::string_view endpoint_id, + std::optional certificate) { + if (FindDuplicateInOutgoingShareTargets(endpoint_id, share_target)) { + if (DeduplicateInOutgoingShareTarget(share_target, endpoint_id, + std::move(certificate))) { + share_target_updated_callback_(share_target); -void OutgoingTargetsManager::DeduplicateInOutgoingShareTarget( + LOG(INFO) << __func__ + << ": [Dedupped] NotifyShareTargetUpdated to all surfaces " + "for share_target: " + << share_target.ToString(); + } + return; + } + bool in_discovery_cache = + FindDuplicateInDiscoveryCache(endpoint_id, share_target); + VLOG(1) << __func__ << ": Adding (endpoint_id=" << endpoint_id + << ", share_target_id=" << share_target.id + << ") to outgoing share target map"; + CreateOutgoingShareSession(share_target, endpoint_id, std::move(certificate)); + if (in_discovery_cache) { + share_target_updated_callback_(share_target); + + LOG(INFO) + << __func__ + << ": [Dedupped] Reported NotifyShareTargetUpdated to all surfaces " + "for share_target: " + << share_target.ToString(); + return; + } + // Update the endpoint id for the share target. + LOG(INFO) << __func__ << ": An endpoint: " << endpoint_id + << " has been discovered, with an advertisement " + "containing a valid share target with id: " + << share_target.id; + + share_target_discovered_callback_(share_target); + + VLOG(1) << __func__ << ": NotifyShareTargetDiscovered: share_target: " + << share_target.ToString() << " endpoint_id=" << endpoint_id + << " to all send surfaces."; +} + +bool OutgoingTargetsManager::DeduplicateInOutgoingShareTarget( const ShareTarget& share_target, absl::string_view endpoint_id, std::optional certificate) { // TODO(b/343764269): may need to update last_outgoing_metadata_ if the @@ -70,22 +121,16 @@ void OutgoingTargetsManager::DeduplicateInOutgoingShareTarget( if (session_it == outgoing_share_session_map_.end()) { LOG(WARNING) << __func__ << ": share_target.id=" << share_target.id << " not found in outgoing share session map."; - return; + return false; } if (session_it->second.IsConnected()) { LOG(INFO) << __func__ << ": share_target.id=" << share_target.id << " is connected, not updating outgoing_share_session_map_."; - return; + return false; } session_it->second.UpdateSessionForDedup(share_target, std::move(certificate), endpoint_id); - - share_target_updated_callback_(share_target); - - LOG(INFO) << __func__ - << ": [Dedupped] NotifyShareTargetUpdated to all surfaces " - "for share_target: " - << share_target.ToString(); + return true; } bool OutgoingTargetsManager::FindDuplicateInDiscoveryCache( @@ -190,10 +235,10 @@ OutgoingTargetsManager::RemoveOutgoingShareTargetWithEndpointId( // Pass endpoint_id by value here since we remove entries from the // outgoing_share_target_map_ in this function, and some callers like -// DisableAllOutgoingShareTargets pass the map item key as the endpoint_id. +// AllTargetsLost pass the map item key as the endpoint_id. // This prevents the endpoint_id from being invalidated in this function. -void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, - uint64_t expiry_ms) { +void OutgoingTargetsManager::OnShareTargetLost(std::string endpoint_id, + absl::Duration retention) { std::optional share_target_opt = RemoveOutgoingShareTargetWithEndpointId(endpoint_id); if (!share_target_opt.has_value()) { @@ -205,8 +250,7 @@ void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, cache_entry.share_target.receive_disabled = true; cache_entry.expiry_timer = std::make_unique( service_thread_, absl::StrCat("discovery_cache_timeout_", endpoint_id), - absl::Milliseconds(expiry_ms), - [this, expiry_ms, endpoint_id = std::string(endpoint_id)]() { + retention, [this, retention, endpoint_id]() { auto cache_node = discovery_cache_.extract(endpoint_id); if (cache_node.empty()) { LOG(WARNING) << "Trying to remove endpoint_id: " << endpoint_id @@ -216,14 +260,9 @@ void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, ShareTarget& share_target = cache_node.mapped().share_target; LOG(INFO) << ": Removing (endpoint_id=" << endpoint_id << ", share_target.id=" << share_target.id - << ") from discovery_cache after " << expiry_ms << "ms"; + << ") from discovery_cache after " << retention; share_target_lost_callback_(share_target); - - VLOG(1) << "discovery_cache entry: " << endpoint_id << " timeout after " - << expiry_ms << "ms" - << ": [Dedupped] NotifyShareTargetLost to all surfaces for " - << "share_target: " << share_target.ToString(); }); // Send ShareTarget update to set receive disabled to true. share_target_updated_callback_(cache_entry.share_target); @@ -235,15 +274,12 @@ void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, void OutgoingTargetsManager::CreateOutgoingShareSession( const ShareTarget& share_target, absl::string_view endpoint_id, - std::optional certificate, - absl::AnyInvocable - transfer_update_callback) { + std::optional certificate) { outgoing_share_target_map_.insert_or_assign(endpoint_id, share_target); auto [it_out, inserted] = outgoing_share_session_map_.try_emplace( share_target.id, &clock_, service_thread_, &connections_manager_, analytics_recorder_, std::string(endpoint_id), share_target, - std::move(transfer_update_callback)); + transfer_update_callback_); if (!inserted) { LOG(WARNING) << __func__ << ": share_target.id=" << share_target.id << " already exists in outgoing share session map. This " @@ -266,13 +302,10 @@ OutgoingShareSession* OutgoingTargetsManager::GetOutgoingShareSession( return &it->second; } -void OutgoingTargetsManager::DisableAllOutgoingShareTargets() { +void OutgoingTargetsManager::AllTargetsLost(absl::Duration retention) { VLOG(1) << "Move all outgoing share targets to discovery cache."; while (!outgoing_share_target_map_.empty()) { - MoveToDiscoveryCache(outgoing_share_target_map_.begin()->first, - NearbyFlags::GetInstance().GetInt64Flag( - config_package_nearby::nearby_sharing_feature:: - kUnregisterTargetDiscoveryCacheLostExpiryMs)); + OnShareTargetLost(outgoing_share_target_map_.begin()->first, retention); } DCHECK(outgoing_share_target_map_.empty()); DCHECK(outgoing_share_session_map_.empty()); @@ -280,8 +313,10 @@ void OutgoingTargetsManager::DisableAllOutgoingShareTargets() { void OutgoingTargetsManager::Cleanup() { while (!outgoing_share_target_map_.empty()) { - RemoveOutgoingShareTargetWithEndpointId( - outgoing_share_target_map_.begin()->first); + // Latch endpoint_id here since RemoveOutgoingShareTargetWithEndpointId() + // will remove the entry from the map. + std::string endpoint_id = outgoing_share_target_map_.begin()->first; + RemoveOutgoingShareTargetWithEndpointId(endpoint_id); } discovery_cache_.clear(); } diff --git a/sharing/outgoing_targets_manager.h b/sharing/outgoing_targets_manager.h index f9ad525a..5ddf0115 100644 --- a/sharing/outgoing_targets_manager.h +++ b/sharing/outgoing_targets_manager.h @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,13 +18,16 @@ #include #include +#include #include #include #include +#include "absl/base/nullability.h" #include "absl/container/flat_hash_map.h" #include "absl/functional/any_invocable.h" #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "internal/platform/clock.h" #include "internal/platform/task_runner.h" #include "proto/sharing_enums.pb.h" @@ -39,64 +42,82 @@ #include "sharing/transfer_metadata.h" namespace nearby::sharing { -class NearbyShareContactManager; - -namespace NearbySharingServiceUnitTests { -class NearbySharingServiceImplTest_CreateShareTarget_Test; -class NearbySharingServiceImplTest_RemoveIncomingPayloads_Test; -}; // namespace NearbySharingServiceUnitTests +// Manages outgoing share targets and outgoing share sessions. +// +// This class is thread-compatible. All methods must be called on the service +// thread. +// +// Each discovered share target has a corresponding outgoing share session. +// When the share target is lost, the share target is moved to the +// discovery cache and the share session is destroyed. The lost share target +// is reported as receive_disabled. After a retention period, the share target +// is removed from the discovery cache. +// Newly discovered share targets that match share targets in the discovery +// cache are merged with the discovery cache entry and a new share session is +// created. The share target is then removed from the discovery cache and +// reported as receive_enabled. class OutgoingTargetsManager { public: OutgoingTargetsManager( - Clock* clock, TaskRunner* service_thread, - NearbyConnectionsManager* connections_manager, - analytics::AnalyticsRecorder* analytics_recorder, + Clock* absl_nonnull clock, TaskRunner* absl_nonnull service_thread, + NearbyConnectionsManager* absl_nonnull connections_manager, + analytics::AnalyticsRecorder* absl_nonnull analytics_recorder, + absl::AnyInvocable + share_target_discovered_callback, absl::AnyInvocable share_target_updated_callback, - absl::AnyInvocable share_target_lost_callback); + absl::AnyInvocable share_target_lost_callback, + std::function + transfer_update_callback); + // Remove all outgoing share targets and outgoing share sessions. + // Share targets callbacks will not be called. + // Any connected sessions will be disconnected. void Cleanup(); OutgoingShareSession* GetOutgoingShareSession(int64_t share_target_id); - // Update the entry in outgoing_share_session_map_ with the new share target - // and OnShareTargetUpdated is called. - void DeduplicateInOutgoingShareTarget( - const ShareTarget& share_target, absl::string_view endpoint_id, + void OnShareTargetDiscovered( + ShareTarget share_target, absl::string_view endpoint_id, std::optional certificate); - // Looks for a duplicate of the share target in the discovery cache. - // If found, the share target is removed from the discovery cache and its - // id is copied into `share_target`. - // Returns true if the duplicate is found. - bool FindDuplicateInDiscoveryCache(absl::string_view endpoint_id, - ShareTarget& share_target); + // Move the endpoint to the discovery cache and report the share target as + // receive_disabled. + // `retention` is the time to keep the share target in the discovery cache. + void OnShareTargetLost(std::string endpoint_id, absl::Duration retention); + // Call OnShareTargetLost() on all known share targets. + void AllTargetsLost(absl::Duration retention); + + void ForEachShareTarget( + absl::AnyInvocable callback); + + private: // Looks for a duplicate of the share target in the outgoing share // target map. The share target's id is changed to match an existing target if // available. Returns true if the duplicate is found. bool FindDuplicateInOutgoingShareTargets(absl::string_view endpoint_id, ShareTarget& share_target); - // Move the endpoint to the discovery cache with the given expiry time. - void MoveToDiscoveryCache(std::string endpoint_id, uint64_t expiry_ms); + // Update the entry in outgoing_share_session_map_ with the new share target. + // Returns true if the share target was updated. + bool DeduplicateInOutgoingShareTarget( + const ShareTarget& share_target, absl::string_view endpoint_id, + std::optional certificate); - // Move all outgoing share targets to the discovery cache so that they will be - // reported as receive_disabled. - void DisableAllOutgoingShareTargets(); + // Looks for a duplicate of the share target in the discovery cache. + // If found, the share target is removed from the discovery cache and its + // id is copied into `share_target`. + // Returns true if the duplicate is found. + bool FindDuplicateInDiscoveryCache(absl::string_view endpoint_id, + ShareTarget& share_target); void CreateOutgoingShareSession( const ShareTarget& share_target, absl::string_view endpoint_id, - std::optional certificate, - absl::AnyInvocable - transfer_update_callback); + std::optional certificate); - void ForEachShareTarget( - absl::AnyInvocable callback); - - private: // Returns the share target if it has been removed, std::nullopt otherwise. std::optional RemoveOutgoingShareTargetWithEndpointId( absl::string_view endpoint_id); @@ -113,9 +134,13 @@ class OutgoingTargetsManager { TaskRunner& service_thread_; NearbyConnectionsManager& connections_manager_; analytics::AnalyticsRecorder& analytics_recorder_; + absl::AnyInvocable + share_target_discovered_callback_; absl::AnyInvocable share_target_updated_callback_; absl::AnyInvocable share_target_lost_callback_; - + std::function + transfer_update_callback_; // A map of endpoint id to ShareTarget, where each ShareTarget entry // directly corresponds to a OutgoingShareSession entry in // outgoing_share_target_info_map_; diff --git a/sharing/outgoing_targets_manager_test.cc b/sharing/outgoing_targets_manager_test.cc new file mode 100644 index 00000000..985b0bd0 --- /dev/null +++ b/sharing/outgoing_targets_manager_test.cc @@ -0,0 +1,388 @@ +// Copyright 2025 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 "sharing/outgoing_targets_manager.h" + +#include +#include + +#include "gmock/gmock.h" +#include "protobuf-matchers/protocol-buffer-matchers.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" +#include "internal/test/fake_clock.h" +#include "internal/test/fake_task_runner.h" +#include "sharing/analytics/analytics_recorder.h" +#include "sharing/fake_nearby_connections_manager.h" +#include "sharing/outgoing_share_session.h" +#include "sharing/share_target.h" +#include "sharing/transfer_metadata.h" + +namespace nearby::sharing { +namespace { +using ::absl::Seconds; +using ::testing::InSequence; + +class OutgoingTargetsManagerTest : public ::testing::Test { + protected: + OutgoingTargetsManagerTest() + : service_thread_(&clock_, /*count=*/1), + analytics_recorder_(/*vendor_id=*/0, + /*event_logger=*/nullptr), + outgoing_targets_manager_( + &clock_, &service_thread_, &connections_manager_, + &analytics_recorder_, + share_target_discovered_callback_.AsStdFunction(), + share_target_updated_callback_.AsStdFunction(), + share_target_lost_callback_.AsStdFunction(), + transfer_update_callback_.AsStdFunction()) {} + + FakeClock clock_; + FakeTaskRunner service_thread_; + FakeNearbyConnectionsManager connections_manager_; + analytics::AnalyticsRecorder analytics_recorder_; + testing::MockFunction + share_target_discovered_callback_; + testing::MockFunction + share_target_updated_callback_; + testing::MockFunction share_target_lost_callback_; + testing::MockFunction + transfer_update_callback_; + OutgoingTargetsManager outgoing_targets_manager_; +}; + +TEST_F(OutgoingTargetsManagerTest, onShareTargetDiscoveredNewTarget) { + constexpr int kShareTargetId = 1234; + constexpr absl::string_view kEndpointId = "endpoint_id"; + ShareTarget target; + target.id = kShareTargetId; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call).Times(0); + EXPECT_CALL(share_target_lost_callback_, Call).Times(0); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId, /*certificate=*/std::nullopt); + + OutgoingShareSession* session = + outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session, nullptr); + EXPECT_EQ(session->share_target(), target); + EXPECT_EQ(session->endpoint_id(), kEndpointId); + + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, target); + }); + EXPECT_TRUE(has_targets); +} + +TEST_F(OutgoingTargetsManagerTest, onShareTargetLost) { + constexpr int kShareTargetId = 1234; + constexpr absl::string_view kEndpointId = "endpoint_id"; + ShareTarget target; + target.id = kShareTargetId; + ShareTarget disabled_target = target; + disabled_target.receive_disabled = true; + { + InSequence s; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, disabled_target); + }); + EXPECT_CALL(share_target_lost_callback_, Call).Times(1); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + } + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId, /*certificate=*/std::nullopt); + EXPECT_NE(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + + outgoing_targets_manager_.OnShareTargetLost(std::string(kEndpointId), + Seconds(10)); + + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, disabled_target); + }); + EXPECT_TRUE(has_targets); + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + + // Retention timer expired. + clock_.FastForward(Seconds(10)); + service_thread_.Sync(); + + has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { has_targets = true; }); + EXPECT_FALSE(has_targets); +} + +TEST_F(OutgoingTargetsManagerTest, onShareTargeDedupNoLossByEndpointId) { + constexpr int kShareTargetId = 1234; + constexpr int kShareTargetId2 = kShareTargetId + 100; + constexpr absl::string_view kEndpointId = "endpoint_id"; + ShareTarget target; + target.id = kShareTargetId; + target.device_name = "device_name"; + ShareTarget target2 = target; + target2.id = kShareTargetId2; + target2.device_name = "device_name_2"; + ShareTarget merged_target = target; + merged_target.device_name = "device_name_2"; + { + InSequence s; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_CALL(share_target_lost_callback_, Call).Times(0); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + } + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId, /*certificate=*/std::nullopt); + EXPECT_NE(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + + outgoing_targets_manager_.OnShareTargetDiscovered( + target2, kEndpointId, /*certificate=*/std::nullopt); + + // Make sure share session is not created for new target id. + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId2), + nullptr); + // Make sure share session is created for original target id. + OutgoingShareSession* session2 = + outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId); + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); +} + +TEST_F(OutgoingTargetsManagerTest, onShareTargeDedupNoLossByDeviceId) { + constexpr int kShareTargetId = 1234; + constexpr int kShareTargetId2 = kShareTargetId + 100; + constexpr absl::string_view kEndpointId1 = "endpoint_id"; + constexpr absl::string_view kEndpointId2 = "endpoint_id_2"; + ShareTarget target; + target.id = kShareTargetId; + target.device_id = "device_id"; + target.device_name = "device_name"; + ShareTarget target2 = target; + target2.id = kShareTargetId2; + target2.device_name = "device_name_2"; + ShareTarget merged_target = target; + merged_target.device_name = "device_name_2"; + { + InSequence s; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_CALL(share_target_lost_callback_, Call).Times(0); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + } + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId1, /*certificate=*/std::nullopt); + EXPECT_NE(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + + // Discover a new target with the same device id, but different endpoint id. + outgoing_targets_manager_.OnShareTargetDiscovered( + target2, kEndpointId2, /*certificate=*/std::nullopt); + + // Make sure share session is not created for new target id. + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId2), + nullptr); + // Make sure share session is created for original target id. + OutgoingShareSession* session2 = + outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId2); + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); +} + +TEST_F(OutgoingTargetsManagerTest, onShareTargeDedupByEndpointId) { + constexpr int kShareTargetId = 1234; + constexpr int kShareTargetId2 = kShareTargetId + 100; + constexpr absl::string_view kEndpointId = "endpoint_id"; + ShareTarget target; + target.id = kShareTargetId; + target.device_name = "device_name"; + ShareTarget disabled_target = target; + disabled_target.receive_disabled = true; + ShareTarget target2 = target; + target2.id = kShareTargetId2; + target2.device_name = "device_name_2"; + ShareTarget merged_target = target; + merged_target.device_name = "device_name_2"; + { + InSequence s; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, disabled_target); + }) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_CALL(share_target_lost_callback_, Call).Times(0); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + } + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId, /*certificate=*/std::nullopt); + EXPECT_NE(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + outgoing_targets_manager_.OnShareTargetLost(std::string(kEndpointId), + Seconds(10)); + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + + outgoing_targets_manager_.OnShareTargetDiscovered( + target2, kEndpointId, /*certificate=*/std::nullopt); + + // Make sure share session is not created for new target id. + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId2), + nullptr); + // Make sure share session is created for original target id. + OutgoingShareSession* session2 = + outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId); + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); + + // Retention timer expired. + clock_.FastForward(Seconds(10)); + service_thread_.Sync(); + + // Make sure share session is not removed after retention timer expired. + session2 = outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId); + has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); +} + +TEST_F(OutgoingTargetsManagerTest, onShareTargeDedupByDeviceId) { + constexpr int kShareTargetId = 1234; + constexpr int kShareTargetId2 = kShareTargetId + 100; + constexpr absl::string_view kEndpointId1 = "endpoint_id"; + constexpr absl::string_view kEndpointId2 = "endpoint_id_2"; + ShareTarget target; + target.id = kShareTargetId; + target.device_id = "device_id"; + target.device_name = "device_name"; + ShareTarget disabled_target = target; + disabled_target.receive_disabled = true; + ShareTarget target2 = target; + target2.id = kShareTargetId2; + target2.device_name = "device_name_2"; + ShareTarget merged_target = target; + merged_target.device_name = "device_name_2"; + { + InSequence s; + EXPECT_CALL(share_target_discovered_callback_, Call).Times(1); + EXPECT_CALL(share_target_updated_callback_, Call) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, disabled_target); + }) + .WillOnce([&](const ShareTarget& share_target) { + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_CALL(share_target_lost_callback_, Call).Times(0); + EXPECT_CALL(transfer_update_callback_, Call).Times(0); + } + outgoing_targets_manager_.OnShareTargetDiscovered( + target, kEndpointId1, /*certificate=*/std::nullopt); + EXPECT_NE(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId), + nullptr); + outgoing_targets_manager_.OnShareTargetLost(std::string(kEndpointId1), + Seconds(10)); + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(1234), nullptr); + + // Discover a new target with the same device id, but different endpoint id. + outgoing_targets_manager_.OnShareTargetDiscovered( + target2, kEndpointId2, /*certificate=*/std::nullopt); + + // Make sure share session is not created for new target id. + EXPECT_EQ(outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId2), + nullptr); + // Make sure share session is created for original target id. + OutgoingShareSession* session2 = + outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId2); + bool has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); + + // Retention timer expired. + clock_.FastForward(Seconds(10)); + service_thread_.Sync(); + + // Make sure share session is not removed after retention timer expired. + session2 = outgoing_targets_manager_.GetOutgoingShareSession(kShareTargetId); + EXPECT_NE(session2, nullptr); + EXPECT_EQ(session2->share_target(), merged_target); + EXPECT_EQ(session2->endpoint_id(), kEndpointId2); + has_targets = false; + outgoing_targets_manager_.ForEachShareTarget( + [&](const ShareTarget& share_target) { + has_targets = true; + EXPECT_EQ(share_target, merged_target); + }); + EXPECT_TRUE(has_targets); +} + +} // namespace +} // namespace nearby::sharing