From eb3ae063f50c29e04e301dad37e2bb05e5131d94 Mon Sep 17 00:00:00 2001 From: Francis Tsui Date: Wed, 24 Sep 2025 18:38:51 -0700 Subject: [PATCH] Move OutgoingTargetsManager to separate files. PiperOrigin-RevId: 811122178 --- sharing/BUILD | 28 +++ sharing/nearby_sharing_service_impl.cc | 254 --------------------- sharing/nearby_sharing_service_impl.h | 84 +------ sharing/outgoing_targets_manager.cc | 301 +++++++++++++++++++++++++ sharing/outgoing_targets_manager.h | 134 +++++++++++ 5 files changed, 464 insertions(+), 337 deletions(-) create mode 100644 sharing/outgoing_targets_manager.cc create mode 100644 sharing/outgoing_targets_manager.h diff --git a/sharing/BUILD b/sharing/BUILD index f15bd7d0..278a100c 100644 --- a/sharing/BUILD +++ b/sharing/BUILD @@ -260,6 +260,33 @@ cc_library( ], ) +cc_library( + name = "outgoing_targets_manager", + srcs = ["outgoing_targets_manager.cc"], + hdrs = ["outgoing_targets_manager.h"], + deps = [ + ":share_session", + ":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/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:string_view", + "@com_google_absl//absl/time", + ], +) + cc_library( name = "nearby_sharing_service", srcs = [ @@ -314,6 +341,7 @@ cc_library( ":incoming_frame_reader", ":nearby_connection_impl", ":nearby_sharing_decoder", + ":outgoing_targets_manager", ":paired_key_verification_runner", ":share_session", ":thread_timer", diff --git a/sharing/nearby_sharing_service_impl.cc b/sharing/nearby_sharing_service_impl.cc index bc27aff3..c0e480f0 100644 --- a/sharing/nearby_sharing_service_impl.cc +++ b/sharing/nearby_sharing_service_impl.cc @@ -37,7 +37,6 @@ #include "absl/random/random.h" #include "absl/status/statusor.h" #include "absl/strings/escaping.h" -#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" @@ -3036,204 +3035,6 @@ IncomingShareSession& NearbySharingServiceImpl::CreateIncomingShareSession( return it->second; } -void 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 - // deduped target id matches the one in last_outgoing_metadata_. - // But since we do not modify the share target of a connected session, it may - // not happen. - - auto session_it = outgoing_share_session_map_.find(share_target.id); - 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; - } - if (session_it->second.IsConnected()) { - LOG(INFO) << __func__ << ": share_target.id=" << share_target.id - << " is connected, not updating outgoing_share_session_map_."; - return; - } - 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(); -} - -bool OutgoingTargetsManager::FindDuplicateInDiscoveryCache( - absl::string_view endpoint_id, ShareTarget& share_target) { - auto it = discovery_cache_.find(endpoint_id); - if (it != discovery_cache_.end()) { - // If endpoint info changes for an endpoint ID, NC will send a rediscovery - // event for the same endpoint id. - LOG(INFO) << __func__ - << ": [Dedupped] Found duplicate endpoint_id: " << endpoint_id - << ", share_target.id changed from: " << share_target.id << " to " - << it->second.share_target.id; - share_target.id = it->second.share_target.id; - discovery_cache_.erase(it); - return true; - } - - for (auto it = discovery_cache_.begin(); it != discovery_cache_.end(); ++it) { - if (it->second.share_target.device_id == share_target.device_id) { - LOG(INFO) << __func__ - << ": [Dedupped] Found duplicate device_id, share_target.id " - "changed from: " - << share_target.id << " to " << it->second.share_target.id - << ". New endpoint_id: " << endpoint_id; - // Share targets in discovery cache have receive_disabled set to true. - // Copy only the id field from cache entry, - share_target.id = it->second.share_target.id; - discovery_cache_.erase(it); - return true; - } - } - return false; -} - -bool OutgoingTargetsManager::FindDuplicateInOutgoingShareTargets( - absl::string_view endpoint_id, ShareTarget& share_target) { - // If the duplicate is found, share_target.id needs to be updated to the old - // "discovered" share_target_id so NotifyShareTargetUpdated matches a target - // that was discovered before. - - auto it = outgoing_share_target_map_.find(endpoint_id); - if (it != outgoing_share_target_map_.end()) { - // If endpoint info changes for an endpoint ID, NC will send a rediscovery - // event for the same endpoint id. - LOG(INFO) << __func__ - << ": [Dedupped] Found duplicate endpoint_id: " << endpoint_id - << " in outgoing_share_target_map, share_target.id changed from: " - << share_target.id << " to " << it->second.id; - share_target.id = it->second.id; - it->second = share_target; - return true; - } - - for (auto it = outgoing_share_target_map_.begin(); - it != outgoing_share_target_map_.end(); ++it) { - if (it->second.device_id == share_target.device_id) { - LOG(INFO) - << __func__ - << ": [Dedupped] Found duplicate device_id, endpoint ID " - "changed from: " - << it->first << " to " << endpoint_id - << " in outgoing_share_target_map, share_target.id changed from: " - << share_target.id << " to " << it->second.id; - share_target.id = it->second.id; - outgoing_share_target_map_.erase(it); - outgoing_share_target_map_.insert_or_assign(endpoint_id, share_target); - return true; - } - } - return false; -} - -std::optional -OutgoingTargetsManager::RemoveOutgoingShareTargetWithEndpointId( - absl::string_view endpoint_id) { - VLOG(1) << __func__ << ":Outgoing connection to " << endpoint_id - << " disconnected"; - auto target_node = outgoing_share_target_map_.extract(endpoint_id); - if (target_node.empty()) { - LOG(WARNING) << __func__ << ": endpoint_id=" << endpoint_id - << " not found in outgoing share target map."; - return std::nullopt; - } - ShareTarget& share_target = target_node.mapped(); - VLOG(1) << __func__ << ": Removing (endpoint_id=" << endpoint_id - << ", share_target.id=" << target_node.mapped().id - << ") from outgoing share target map"; - - // Do not destroy the session until it has been removed from the map. - // Session destruction can trigger callbacks that traverses the map and it - // cannot access the map while it is being modified. - auto session_node = - outgoing_share_session_map_.extract(target_node.mapped().id); - if (!session_node.empty()) { - session_node.mapped().OnDisconnect(); - } else { - LOG(WARNING) << __func__ << ": share_target.id=" << target_node.mapped().id - << " not found in outgoing share session map."; - } - return share_target; -} - -// 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. -// This prevents the endpoint_id from being invalidated in this function. -void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, - uint64_t expiry_ms) { - std::optional share_target_opt = - RemoveOutgoingShareTargetWithEndpointId(endpoint_id); - if (!share_target_opt.has_value()) { - return; - } - DiscoveryCacheEntry cache_entry; - cache_entry.share_target = std::move(share_target_opt.value()); - // Entries in Discovery Cache are all receive disabled. - 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)]() { - auto cache_node = discovery_cache_.extract(endpoint_id); - if (cache_node.empty()) { - LOG(WARNING) << "Trying to remove endpoint_id: " << endpoint_id - << " from discovery_cache, but cannot find it"; - return; - } - 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"; - - 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); - auto [it, inserted] = - discovery_cache_.insert_or_assign(endpoint_id, std::move(cache_entry)); - LOG(INFO) << "[Dedupped] added to discovery_cache: " << endpoint_id << " by " - << (inserted ? "insert" : "assign"); -} - -void OutgoingTargetsManager::CreateOutgoingShareSession( - const ShareTarget& share_target, absl::string_view endpoint_id, - std::optional certificate, - absl::AnyInvocable - transfer_update_callback) { - 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)); - if (!inserted) { - LOG(WARNING) << __func__ << ": share_target.id=" << share_target.id - << " already exists in outgoing share session map. This " - "should NOT happen"; - } else { - auto& session = it_out->second; - if (certificate.has_value()) { - session.set_certificate(std::move(*certificate)); - } - } -} - ShareSession* NearbySharingServiceImpl::GetShareSession( int64_t share_target_id) { ShareSession* result = GetIncomingShareSession(share_target_id); @@ -3253,16 +3054,6 @@ IncomingShareSession* NearbySharingServiceImpl::GetIncomingShareSession( return &it->second; } -OutgoingShareSession* OutgoingTargetsManager::GetOutgoingShareSession( - int64_t share_target_id) { - auto it = outgoing_share_session_map_.find(share_target_id); - if (it == outgoing_share_session_map_.end()) { - return nullptr; - } - - return &it->second; -} - std::optional> NearbySharingServiceImpl::GetBluetoothMacAddressForShareTarget( OutgoingShareSession& session) { @@ -3277,18 +3068,6 @@ NearbySharingServiceImpl::GetBluetoothMacAddressForShareTarget( return GetBluetoothMacAddressFromCertificate(*certificate); } -void OutgoingTargetsManager::DisableAllOutgoingShareTargets() { - 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)); - } - DCHECK(outgoing_share_target_map_.empty()); - DCHECK(outgoing_share_session_map_.empty()); -} - void NearbySharingServiceImpl::UnregisterShareTarget(int64_t share_target_id) { LOG(INFO) << __func__ << ": Unregister share target " << share_target_id; @@ -3544,37 +3323,4 @@ void NearbySharingServiceImpl::UpdateFilePathsInProgress( << ": Update file paths in progress: " << update_file_paths; } -OutgoingTargetsManager::OutgoingTargetsManager( - Clock* clock, TaskRunner* service_thread, - NearbyConnectionsManager* connections_manager, - analytics::AnalyticsRecorder* analytics_recorder, - absl::AnyInvocable share_target_updated_callback, - absl::AnyInvocable share_target_lost_callback) - : clock_(*clock), - service_thread_(*service_thread), - connections_manager_(*connections_manager), - analytics_recorder_(*analytics_recorder), - share_target_updated_callback_(std::move(share_target_updated_callback)), - share_target_lost_callback_(std::move(share_target_lost_callback)) {} - -void OutgoingTargetsManager::Cleanup() { - while (!outgoing_share_target_map_.empty()) { - RemoveOutgoingShareTargetWithEndpointId( - outgoing_share_target_map_.begin()->first); - } - discovery_cache_.clear(); -} - -void OutgoingTargetsManager::ForEachShareTarget( - absl::AnyInvocable callback) { - // All share targets in discovery_cache have received_disabled set to true, - // send them to new send surface in discovered events.. - for (const auto& [endpoint_id, discovery_cache_entry] : discovery_cache_) { - callback(discovery_cache_entry.share_target); - } - for (const auto& [endpoint_id, share_target] : outgoing_share_target_map_) { - callback(share_target); - } -} - } // namespace nearby::sharing diff --git a/sharing/nearby_sharing_service_impl.h b/sharing/nearby_sharing_service_impl.h index b6dd0654..57e12904 100644 --- a/sharing/nearby_sharing_service_impl.h +++ b/sharing/nearby_sharing_service_impl.h @@ -63,6 +63,7 @@ #include "sharing/nearby_sharing_service_extension.h" #include "sharing/nearby_sharing_settings.h" #include "sharing/outgoing_share_session.h" +#include "sharing/outgoing_targets_manager.h" #include "sharing/paired_key_verification_runner.h" #include "sharing/proto/enums.pb.h" #include "sharing/proto/wire_format.pb.h" @@ -83,89 +84,6 @@ class NearbySharingServiceImplTest_CreateShareTarget_Test; class NearbySharingServiceImplTest_RemoveIncomingPayloads_Test; }; // namespace NearbySharingServiceUnitTests -class OutgoingTargetsManager { - public: - OutgoingTargetsManager( - Clock* clock, TaskRunner* service_thread, - NearbyConnectionsManager* connections_manager, - analytics::AnalyticsRecorder* analytics_recorder, - absl::AnyInvocable - share_target_updated_callback, - absl::AnyInvocable share_target_lost_callback); - - 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, - 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); - - // 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); - - // Returns the share target if it has been removed, std::nullopt otherwise. - std::optional RemoveOutgoingShareTargetWithEndpointId( - absl::string_view endpoint_id); - - // Move the endpoint to the discovery cache with the given expiry time. - void MoveToDiscoveryCache(std::string endpoint_id, uint64_t expiry_ms); - - // Move all outgoing share targets to the discovery cache so that they will be - // reported as receive_disabled. - void DisableAllOutgoingShareTargets(); - - void CreateOutgoingShareSession( - const ShareTarget& share_target, absl::string_view endpoint_id, - std::optional certificate, - absl::AnyInvocable - transfer_update_callback); - - void ForEachShareTarget( - absl::AnyInvocable callback); - - private: - // Cache a recently lost share target to be re-discovered. - // Purged after expiry_timer. - struct DiscoveryCacheEntry { - // If needed, we can add "state" field to model "Tomb" state. - std::unique_ptr expiry_timer; - ShareTarget share_target; - }; - - Clock& clock_; - TaskRunner& service_thread_; - NearbyConnectionsManager& connections_manager_; - analytics::AnalyticsRecorder& analytics_recorder_; - absl::AnyInvocable share_target_updated_callback_; - absl::AnyInvocable share_target_lost_callback_; - - // A map of endpoint id to ShareTarget, where each ShareTarget entry - // directly corresponds to a OutgoingShareSession entry in - // outgoing_share_target_info_map_; - absl::flat_hash_map outgoing_share_target_map_; - // A map of ShareTarget id to OutgoingShareSession. This lets us know which - // endpoint and public certificate are related to the outgoing share target. - absl::flat_hash_map - outgoing_share_session_map_; - // A map of Endpoint id to DiscoveryCacheEntry. - // All ShareTargets in discovery cache have received_disabled set to true. - absl::flat_hash_map discovery_cache_; -}; - // All methods should be called from the same sequence that created the service. class NearbySharingServiceImpl : public NearbySharingService, diff --git a/sharing/outgoing_targets_manager.cc b/sharing/outgoing_targets_manager.cc new file mode 100644 index 00000000..655c670c --- /dev/null +++ b/sharing/outgoing_targets_manager.cc @@ -0,0 +1,301 @@ +// Copyright 2022-2023 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 +#include +#include + +#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" +#include "sharing/proto/encrypted_metadata.pb.h" +#include "sharing/proto/enums.pb.h" +#include "sharing/proto/wire_format.pb.h" +#include "sharing/share_target.h" +#include "sharing/thread_timer.h" +#include "sharing/transfer_metadata.h" + +namespace nearby::sharing { + +OutgoingTargetsManager::OutgoingTargetsManager( + Clock* clock, TaskRunner* service_thread, + NearbyConnectionsManager* connections_manager, + analytics::AnalyticsRecorder* analytics_recorder, + absl::AnyInvocable share_target_updated_callback, + absl::AnyInvocable share_target_lost_callback) + : clock_(*clock), + service_thread_(*service_thread), + connections_manager_(*connections_manager), + analytics_recorder_(*analytics_recorder), + share_target_updated_callback_(std::move(share_target_updated_callback)), + share_target_lost_callback_(std::move(share_target_lost_callback)) {} + + +void 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 + // deduped target id matches the one in last_outgoing_metadata_. + // But since we do not modify the share target of a connected session, it may + // not happen. + + auto session_it = outgoing_share_session_map_.find(share_target.id); + 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; + } + if (session_it->second.IsConnected()) { + LOG(INFO) << __func__ << ": share_target.id=" << share_target.id + << " is connected, not updating outgoing_share_session_map_."; + return; + } + 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(); +} + +bool OutgoingTargetsManager::FindDuplicateInDiscoveryCache( + absl::string_view endpoint_id, ShareTarget& share_target) { + auto it = discovery_cache_.find(endpoint_id); + if (it != discovery_cache_.end()) { + // If endpoint info changes for an endpoint ID, NC will send a rediscovery + // event for the same endpoint id. + LOG(INFO) << __func__ + << ": [Dedupped] Found duplicate endpoint_id: " << endpoint_id + << ", share_target.id changed from: " << share_target.id << " to " + << it->second.share_target.id; + share_target.id = it->second.share_target.id; + discovery_cache_.erase(it); + return true; + } + + for (auto it = discovery_cache_.begin(); it != discovery_cache_.end(); ++it) { + if (it->second.share_target.device_id == share_target.device_id) { + LOG(INFO) << __func__ + << ": [Dedupped] Found duplicate device_id, share_target.id " + "changed from: " + << share_target.id << " to " << it->second.share_target.id + << ". New endpoint_id: " << endpoint_id; + // Share targets in discovery cache have receive_disabled set to true. + // Copy only the id field from cache entry, + share_target.id = it->second.share_target.id; + discovery_cache_.erase(it); + return true; + } + } + return false; +} + +bool OutgoingTargetsManager::FindDuplicateInOutgoingShareTargets( + absl::string_view endpoint_id, ShareTarget& share_target) { + // If the duplicate is found, share_target.id needs to be updated to the old + // "discovered" share_target_id so NotifyShareTargetUpdated matches a target + // that was discovered before. + + auto it = outgoing_share_target_map_.find(endpoint_id); + if (it != outgoing_share_target_map_.end()) { + // If endpoint info changes for an endpoint ID, NC will send a rediscovery + // event for the same endpoint id. + LOG(INFO) << __func__ + << ": [Dedupped] Found duplicate endpoint_id: " << endpoint_id + << " in outgoing_share_target_map, share_target.id changed from: " + << share_target.id << " to " << it->second.id; + share_target.id = it->second.id; + it->second = share_target; + return true; + } + + for (auto it = outgoing_share_target_map_.begin(); + it != outgoing_share_target_map_.end(); ++it) { + if (it->second.device_id == share_target.device_id) { + LOG(INFO) + << __func__ + << ": [Dedupped] Found duplicate device_id, endpoint ID " + "changed from: " + << it->first << " to " << endpoint_id + << " in outgoing_share_target_map, share_target.id changed from: " + << share_target.id << " to " << it->second.id; + share_target.id = it->second.id; + outgoing_share_target_map_.erase(it); + outgoing_share_target_map_.insert_or_assign(endpoint_id, share_target); + return true; + } + } + return false; +} + +std::optional +OutgoingTargetsManager::RemoveOutgoingShareTargetWithEndpointId( + absl::string_view endpoint_id) { + VLOG(1) << __func__ << ":Outgoing connection to " << endpoint_id + << " disconnected"; + auto target_node = outgoing_share_target_map_.extract(endpoint_id); + if (target_node.empty()) { + LOG(WARNING) << __func__ << ": endpoint_id=" << endpoint_id + << " not found in outgoing share target map."; + return std::nullopt; + } + ShareTarget& share_target = target_node.mapped(); + VLOG(1) << __func__ << ": Removing (endpoint_id=" << endpoint_id + << ", share_target.id=" << target_node.mapped().id + << ") from outgoing share target map"; + + // Do not destroy the session until it has been removed from the map. + // Session destruction can trigger callbacks that traverses the map and it + // cannot access the map while it is being modified. + auto session_node = + outgoing_share_session_map_.extract(target_node.mapped().id); + if (!session_node.empty()) { + session_node.mapped().OnDisconnect(); + } else { + LOG(WARNING) << __func__ << ": share_target.id=" << target_node.mapped().id + << " not found in outgoing share session map."; + } + return share_target; +} + +// 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. +// This prevents the endpoint_id from being invalidated in this function. +void OutgoingTargetsManager::MoveToDiscoveryCache(std::string endpoint_id, + uint64_t expiry_ms) { + std::optional share_target_opt = + RemoveOutgoingShareTargetWithEndpointId(endpoint_id); + if (!share_target_opt.has_value()) { + return; + } + DiscoveryCacheEntry cache_entry; + cache_entry.share_target = std::move(share_target_opt.value()); + // Entries in Discovery Cache are all receive disabled. + 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)]() { + auto cache_node = discovery_cache_.extract(endpoint_id); + if (cache_node.empty()) { + LOG(WARNING) << "Trying to remove endpoint_id: " << endpoint_id + << " from discovery_cache, but cannot find it"; + return; + } + 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"; + + 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); + auto [it, inserted] = + discovery_cache_.insert_or_assign(endpoint_id, std::move(cache_entry)); + LOG(INFO) << "[Dedupped] added to discovery_cache: " << endpoint_id << " by " + << (inserted ? "insert" : "assign"); +} + +void OutgoingTargetsManager::CreateOutgoingShareSession( + const ShareTarget& share_target, absl::string_view endpoint_id, + std::optional certificate, + absl::AnyInvocable + transfer_update_callback) { + 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)); + if (!inserted) { + LOG(WARNING) << __func__ << ": share_target.id=" << share_target.id + << " already exists in outgoing share session map. This " + "should NOT happen"; + } else { + auto& session = it_out->second; + if (certificate.has_value()) { + session.set_certificate(std::move(*certificate)); + } + } +} + +OutgoingShareSession* OutgoingTargetsManager::GetOutgoingShareSession( + int64_t share_target_id) { + auto it = outgoing_share_session_map_.find(share_target_id); + if (it == outgoing_share_session_map_.end()) { + return nullptr; + } + + return &it->second; +} + +void OutgoingTargetsManager::DisableAllOutgoingShareTargets() { + 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)); + } + DCHECK(outgoing_share_target_map_.empty()); + DCHECK(outgoing_share_session_map_.empty()); +} + +void OutgoingTargetsManager::Cleanup() { + while (!outgoing_share_target_map_.empty()) { + RemoveOutgoingShareTargetWithEndpointId( + outgoing_share_target_map_.begin()->first); + } + discovery_cache_.clear(); +} + +void OutgoingTargetsManager::ForEachShareTarget( + absl::AnyInvocable callback) { + // All share targets in discovery_cache have received_disabled set to true, + // send them to new send surface in discovered events.. + for (const auto& [endpoint_id, discovery_cache_entry] : discovery_cache_) { + callback(discovery_cache_entry.share_target); + } + for (const auto& [endpoint_id, share_target] : outgoing_share_target_map_) { + callback(share_target); + } +} + +} // namespace nearby::sharing diff --git a/sharing/outgoing_targets_manager.h b/sharing/outgoing_targets_manager.h new file mode 100644 index 00000000..f9ad525a --- /dev/null +++ b/sharing/outgoing_targets_manager.h @@ -0,0 +1,134 @@ +// Copyright 2022-2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_SHARING_OUTGOING_TARGETS_MANAGER_H_ +#define THIRD_PARTY_NEARBY_SHARING_OUTGOING_TARGETS_MANAGER_H_ + +#include +#include + +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.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/nearby_connections_manager.h" +#include "sharing/outgoing_share_session.h" +#include "sharing/proto/enums.pb.h" +#include "sharing/proto/wire_format.pb.h" +#include "sharing/share_target.h" +#include "sharing/thread_timer.h" +#include "sharing/transfer_metadata.h" + +namespace nearby::sharing { +class NearbyShareContactManager; + +namespace NearbySharingServiceUnitTests { +class NearbySharingServiceImplTest_CreateShareTarget_Test; +class NearbySharingServiceImplTest_RemoveIncomingPayloads_Test; +}; // namespace NearbySharingServiceUnitTests + +class OutgoingTargetsManager { + public: + OutgoingTargetsManager( + Clock* clock, TaskRunner* service_thread, + NearbyConnectionsManager* connections_manager, + analytics::AnalyticsRecorder* analytics_recorder, + absl::AnyInvocable + share_target_updated_callback, + absl::AnyInvocable share_target_lost_callback); + + 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, + 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); + + // 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); + + // Move all outgoing share targets to the discovery cache so that they will be + // reported as receive_disabled. + void DisableAllOutgoingShareTargets(); + + void CreateOutgoingShareSession( + const ShareTarget& share_target, absl::string_view endpoint_id, + std::optional certificate, + absl::AnyInvocable + transfer_update_callback); + + 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); + + // Cache a recently lost share target to be re-discovered. + // Purged after expiry_timer. + struct DiscoveryCacheEntry { + // If needed, we can add "state" field to model "Tomb" state. + std::unique_ptr expiry_timer; + ShareTarget share_target; + }; + + Clock& clock_; + TaskRunner& service_thread_; + NearbyConnectionsManager& connections_manager_; + analytics::AnalyticsRecorder& analytics_recorder_; + absl::AnyInvocable share_target_updated_callback_; + absl::AnyInvocable share_target_lost_callback_; + + // A map of endpoint id to ShareTarget, where each ShareTarget entry + // directly corresponds to a OutgoingShareSession entry in + // outgoing_share_target_info_map_; + absl::flat_hash_map outgoing_share_target_map_; + // A map of ShareTarget id to OutgoingShareSession. This lets us know which + // endpoint and public certificate are related to the outgoing share target. + absl::flat_hash_map + outgoing_share_session_map_; + // A map of Endpoint id to DiscoveryCacheEntry. + // All ShareTargets in discovery cache have received_disabled set to true. + absl::flat_hash_map discovery_cache_; +}; + +} // namespace nearby::sharing + +#endif // THIRD_PARTY_NEARBY_SHARING_OUTGOING_TARGETS_MANAGER_H_