Cleanup OutgoingTargetsManager.

PiperOrigin-RevId: 811928178
This commit is contained in:
Francis Tsui
2025-09-26 13:09:08 -07:00
committed by Copybara-Service
parent 482d7f773d
commit 6922eea95f
5 changed files with 565 additions and 135 deletions
+19 -2
View File
@@ -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",
],
)
+23 -58
View File
@@ -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);
}
}
+75 -40
View File
@@ -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 <stdint.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#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<void(const ShareTarget&)>
share_target_discovered_callback,
absl::AnyInvocable<void(const ShareTarget&)> share_target_updated_callback,
absl::AnyInvocable<void(const ShareTarget&)> share_target_lost_callback)
absl::AnyInvocable<void(const ShareTarget&)> share_target_lost_callback,
std::function<void(OutgoingShareSession& session,
const TransferMetadata& metadata)>
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<NearbyShareDecryptedPublicCertificate> 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<NearbyShareDecryptedPublicCertificate> 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<ShareTarget> 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<ThreadTimer>(
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<NearbyShareDecryptedPublicCertificate> certificate,
absl::AnyInvocable<void(OutgoingShareSession& session,
const TransferMetadata& metadata)>
transfer_update_callback) {
std::optional<NearbyShareDecryptedPublicCertificate> 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();
}
+60 -35
View File
@@ -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 <stddef.h>
#include <stdint.h>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#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<void(const ShareTarget&)>
share_target_discovered_callback,
absl::AnyInvocable<void(const ShareTarget&)>
share_target_updated_callback,
absl::AnyInvocable<void(const ShareTarget&)> share_target_lost_callback);
absl::AnyInvocable<void(const ShareTarget&)> share_target_lost_callback,
std::function<void(OutgoingShareSession& session,
const TransferMetadata& metadata)>
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<NearbyShareDecryptedPublicCertificate> 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<void(const ShareTarget&)> 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<NearbyShareDecryptedPublicCertificate> 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<NearbyShareDecryptedPublicCertificate> certificate,
absl::AnyInvocable<void(OutgoingShareSession& session,
const TransferMetadata& metadata)>
transfer_update_callback);
std::optional<NearbyShareDecryptedPublicCertificate> certificate);
void ForEachShareTarget(
absl::AnyInvocable<void(const ShareTarget&)> callback);
private:
// Returns the share target if it has been removed, std::nullopt otherwise.
std::optional<ShareTarget> RemoveOutgoingShareTargetWithEndpointId(
absl::string_view endpoint_id);
@@ -113,9 +134,13 @@ class OutgoingTargetsManager {
TaskRunner& service_thread_;
NearbyConnectionsManager& connections_manager_;
analytics::AnalyticsRecorder& analytics_recorder_;
absl::AnyInvocable<void(const ShareTarget&)>
share_target_discovered_callback_;
absl::AnyInvocable<void(const ShareTarget&)> share_target_updated_callback_;
absl::AnyInvocable<void(const ShareTarget&)> share_target_lost_callback_;
std::function<void(OutgoingShareSession& session,
const TransferMetadata& metadata)>
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_;
+388
View File
@@ -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 <optional>
#include <string>
#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<void(const ShareTarget&)>
share_target_discovered_callback_;
testing::MockFunction<void(const ShareTarget&)>
share_target_updated_callback_;
testing::MockFunction<void(const ShareTarget&)> share_target_lost_callback_;
testing::MockFunction<void(OutgoingShareSession&, const TransferMetadata&)>
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