// 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/nearby_connections_manager_impl.h" #include #include #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/functional/bind_front.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "absl/types/span.h" #include "internal/base/file_path.h" #include "internal/flags/nearby_flags.h" #include "internal/platform/device_info.h" #include "internal/platform/mutex_lock.h" #include "internal/platform/task_runner.h" #include "sharing/advertisement.h" #include "sharing/common/nearby_share_enums.h" #include "sharing/constants.h" #include "sharing/flags/generated/nearby_sharing_feature_flags.h" #include "sharing/internal/base/encode.h" #include "sharing/internal/public/connectivity_manager.h" #include "sharing/internal/public/context.h" #include "sharing/internal/public/logging.h" #include "sharing/nearby_connection_impl.h" #include "sharing/nearby_connections_manager.h" #include "sharing/nearby_connections_service.h" #include "sharing/nearby_connections_types.h" #include "sharing/transfer_manager.h" namespace nearby::sharing { namespace { using ::nearby::sharing::proto::DataUsage; constexpr char kServiceId[] = "NearbySharing"; constexpr char kFastAdvertisementServiceUuid[] = "0000fef3-0000-1000-8000-00805f9b34fb"; constexpr Strategy kStrategy = Strategy::kP2pPointToPoint; const uint8_t kMinimumAdvertisementSize = /* Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits)= */ 1 + Advertisement::kSaltSize + Advertisement::kMetadataEncryptionKeyHashByteSize; bool ShouldUseInternet(ConnectivityManager& connectivity_manager, DataUsage data_usage, PowerLevel power_level) { // We won't use the internet if the user requested we don't. if (data_usage == DataUsage::OFFLINE_DATA_USAGE) return false; // We won't use the internet in a low power mode. if (power_level == PowerLevel::kLowPower) return false; ConnectivityManager::ConnectionType connection_type = connectivity_manager.GetConnectionType(); // Verify that this network has an internet connection. if (connection_type == ConnectivityManager::ConnectionType::kNone) { VLOG(1) << __func__ << ": No internet connection."; return false; } if (data_usage == DataUsage::WIFI_ONLY_DATA_USAGE && connection_type != ConnectivityManager::ConnectionType::kWifi) { return false; } // We're online, the user hasn't disabled Wi-Fi, let's use it! return true; } bool ShouldEnableWebRtc(ConnectivityManager& connectivity_manager, DataUsage data_usage, PowerLevel power_level) { return NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_sharing_feature:: kEnableMediumWebRtc) && ShouldUseInternet(connectivity_manager, data_usage, power_level); } bool ShouldEnableWifiLan(ConnectivityManager& connectivity_manager) { if (!NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_sharing_feature:: kEnableMediumWifiLan)) { return false; } ConnectivityManager::ConnectionType connection_type = connectivity_manager.GetConnectionType(); bool is_connection_wifi_or_ethernet = connection_type == ConnectivityManager::ConnectionType::kWifi || connection_type == ConnectivityManager::ConnectionType::kEthernet; return is_connection_wifi_or_ethernet; } // Temporarily fix to get around wifi hotspot issues for HP Aero with Realtek. // See b/380191431 for more details. bool ShouldEnableWifiHotspot(ConnectivityManager& connectivity_manager) { if (connectivity_manager.IsHPRealtekDevice()) { LOG(WARNING) << __func__ << ": Disable wifi hotspot for HP with Realtek"; return false; } return true; } bool ShouldEnableBleForTransfers() { return NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_sharing_feature::kEnableBleForTransfer); } std::string MediumSelectionToString(const MediumSelection& mediums) { std::stringstream ss; ss << "{"; if (mediums.bluetooth) ss << "bluetooth "; if (mediums.ble) ss << "ble "; if (mediums.web_rtc) ss << "webrtc "; if (mediums.wifi_lan) ss << "wifilan "; if (mediums.wifi_hotspot) ss << "wifihotspot "; ss << "}"; return ss.str(); } std::string PayloadStatusToString(PayloadStatus status) { switch (status) { case PayloadStatus::kSuccess: return "Success"; case PayloadStatus::kFailure: return "Failure"; case PayloadStatus::kInProgress: return "In Progress"; case PayloadStatus::kCanceled: return "Canceled"; } } } // namespace NearbyConnectionsManagerImpl::NearbyConnectionsManagerImpl( TaskRunner* connections_callback_task_runner, Context* context, ConnectivityManager& connectivity_manager, nearby::DeviceInfo& device_info, std::unique_ptr nearby_connections_service) : connections_callback_task_runner_(connections_callback_task_runner), context_(context), connectivity_manager_(connectivity_manager), device_info_(device_info), nearby_connections_service_(std::move(nearby_connections_service)) {} NearbyConnectionsManagerImpl::~NearbyConnectionsManagerImpl() { ClearIncomingPayloads(); } void NearbyConnectionsManagerImpl::Shutdown() { Reset(); } void NearbyConnectionsManagerImpl::StartAdvertising( std::vector endpoint_info, IncomingConnectionListener* listener, PowerLevel power_level, DataUsage data_usage, bool use_stable_endpoint_id, bool force_new_endpoint_id, ConnectionsCallback callback) { DCHECK(listener); DCHECK(!incoming_connection_listener_); if (!nearby_connections_service_) { std::move(callback)(ConnectionsStatus::kError); return; } bool is_high_power = power_level == PowerLevel::kHighPower; bool use_ble = true; MediumSelection allowed_mediums = MediumSelection( /*bluetooth=*/is_high_power, /*ble=*/use_ble, // Using kHighPower here rather than power_level to signal that power // level isn't a factor when deciding whether to allow WebRTC // upgrades from this advertisement. ShouldEnableWebRtc(connectivity_manager_, data_usage, PowerLevel::kHighPower), ShouldEnableWifiLan(connectivity_manager_), ShouldEnableWifiHotspot(connectivity_manager_)); VLOG(1) << __func__ << ": " << "is_high_power=" << (is_high_power ? "yes" : "no") << ", data_usage=" << static_cast(data_usage) << ", allowed_mediums=" << MediumSelectionToString(allowed_mediums); // Nearby Sharing manually controls Wi-Fi/Bluetooth upgrade. Frequent // Bluetooth connection drops were observed during upgrades for Bluetooth // transfers. Android has similar logic to handle upgrades, please check // b/161880863 for more information. bool auto_upgrade_bandwidth = false; incoming_connection_listener_ = listener; NearbyConnectionsService::ConnectionListener connection_listener; connection_listener.initiated_cb = [this](absl::string_view endpoint_id, const ConnectionInfo& connection_info) { OnConnectionInitiated(endpoint_id, connection_info); }; connection_listener.accepted_cb = [this](absl::string_view endpoint_id) { connections_callback_task_runner_->PostTask( [this, endpoint_id = std::string(endpoint_id)]() { OnConnectionAccepted(endpoint_id); }); }; connection_listener.rejected_cb = [&](absl::string_view endpoint_id, Status status) { OnConnectionRejected(endpoint_id, status); }; connection_listener.disconnected_cb = [this](absl::string_view endpoint_id) { connections_callback_task_runner_->PostTask( [this, endpoint_id = std::string(endpoint_id)]() { OnDisconnected(endpoint_id); }); }; connection_listener.bandwidth_changed_cb = [&](absl::string_view endpoint_id, Medium medium) { OnBandwidthChanged(endpoint_id, medium); }; Uuid fast_advertisement_service_uuid; LOG(INFO) << __func__ << ": Nearby Sharing flag kEnableBleV2 is enabled."; // Uses fast advertisement when advertisement data size is less than // kMinimumAdvertisementSize. Nearby Connections will decide whether to use // GATT server with this information. if (endpoint_info.size() > kMinimumAdvertisementSize) { fast_advertisement_service_uuid = Uuid(""); } else { fast_advertisement_service_uuid = Uuid(kFastAdvertisementServiceUuid); } nearby_connections_service_->StartAdvertising( kServiceId, endpoint_info, { .strategy = kStrategy, .allowed_mediums = std::move(allowed_mediums), .auto_upgrade_bandwidth = auto_upgrade_bandwidth, .enforce_topology_constraints = true, .enable_bluetooth_listening = use_ble, .enable_webrtc_listening = ShouldEnableWebRtc( connectivity_manager_, data_usage, power_level), .use_stable_endpoint_id = use_stable_endpoint_id, .force_new_endpoint_id = force_new_endpoint_id, .fast_advertisement_service_uuid = fast_advertisement_service_uuid, }, std::move(connection_listener), std::move(callback)); } void NearbyConnectionsManagerImpl::StopAdvertising( ConnectionsCallback callback) { incoming_connection_listener_ = nullptr; if (!nearby_connections_service_) { std::move(callback)(ConnectionsStatus::kSuccess); return; } nearby_connections_service_->StopAdvertising(kServiceId, std::move(callback)); } void NearbyConnectionsManagerImpl::StartDiscovery( DiscoveryListener* listener, DataUsage data_usage, std::optional alternate_service_uuid, ConnectionsCallback callback) { DCHECK(listener); if (!nearby_connections_service_) { std::move(callback)(ConnectionsStatus::kError); return; } MediumSelection allowed_mediums = MediumSelection( /*bluetooth=*/true, /*ble=*/true, /*web_rtc=*/ ShouldEnableWebRtc(connectivity_manager_, data_usage, PowerLevel::kHighPower), /*wifi_lan=*/ ShouldEnableWifiLan(connectivity_manager_), ShouldEnableWifiHotspot(connectivity_manager_)); VLOG(1) << __func__ << ": " << "data_usage=" << static_cast(data_usage) << ", allowed_mediums=" << MediumSelectionToString(allowed_mediums); discovery_listener_ = listener; NearbyConnectionsService::DiscoveryListener service_discovery_listener; service_discovery_listener.endpoint_found_cb = [&](absl::string_view endpoint_id, const DiscoveredEndpointInfo& info) { OnEndpointFound(endpoint_id, info); }; service_discovery_listener.endpoint_lost_cb = [&](absl::string_view endpoint_id) { OnEndpointLost(endpoint_id); }; nearby_connections_service_->StartDiscovery( kServiceId, { .strategy = kStrategy, .allowed_mediums = std::move(allowed_mediums), .fast_advertisement_service_uuid = Uuid(kFastAdvertisementServiceUuid), .is_out_of_band_connection = false, .alternate_service_uuid = std::move(alternate_service_uuid), }, std::move(service_discovery_listener), std::move(callback)); } void NearbyConnectionsManagerImpl::StopDiscovery() { MutexLock lock(&mutex_); discovered_endpoints_.clear(); discovery_listener_ = nullptr; if (!nearby_connections_service_) { return; } nearby_connections_service_->StopDiscovery( kServiceId, [&](ConnectionsStatus status) { VLOG(1) << __func__ << ": Stop discovery attempted over Nearby " "Connections with result: " << ConnectionsStatusToString(status); }); } void NearbyConnectionsManagerImpl::Connect( std::vector endpoint_info, absl::string_view endpoint_id, std::optional> bluetooth_mac_address, DataUsage data_usage, TransportType transport_type, NearbyConnectionCallback callback) { MutexLock lock(&mutex_); if (!nearby_connections_service_) { callback(endpoint_id, nullptr, Status::kError); return; } if (bluetooth_mac_address.has_value() && bluetooth_mac_address->size() != 6) { bluetooth_mac_address.reset(); } MediumSelection allowed_mediums = MediumSelection( /*bluetooth=*/true, /*ble=*/ShouldEnableBleForTransfers(), ShouldEnableWebRtc(connectivity_manager_, data_usage, PowerLevel::kHighPower), /*wifi_lan=*/ ShouldEnableWifiLan(connectivity_manager_), // Note: this only affects sending since this function is only for // outgoing connections. /*wifi_hotspot=*/ IsTransportTypeFlagsSet(transport_type, TransportType::kHighQuality) && ShouldEnableWifiHotspot(connectivity_manager_)); VLOG(1) << __func__ << ": " << "data_usage=" << static_cast(data_usage) << ", allowed_mediums=" << MediumSelectionToString(allowed_mediums); [[maybe_unused]] auto result = pending_outgoing_connections_.emplace(endpoint_id, std::move(callback)); DCHECK(result.second); auto timeout_timer = context_->CreateTimer(); timeout_timer->Start( absl::ToInt64Milliseconds(kInitiateNearbyConnectionTimeout), /*period=*/0, [this, endpoint_id = std::string(endpoint_id)]() { OnConnectionTimedOut(endpoint_id); }); connect_timeout_timers_.emplace(endpoint_id, std::move(timeout_timer)); NearbyConnectionsService::ConnectionListener connection_listener; connection_listener.initiated_cb = [&](absl::string_view endpoint_id, const ConnectionInfo& connection_info) { OnConnectionInitiated(endpoint_id, connection_info); }; connection_listener.accepted_cb = [this](absl::string_view endpoint_id) { connections_callback_task_runner_->PostTask( [this, endpoint_id = std::string(endpoint_id)]() { OnConnectionAccepted(endpoint_id); }); }; connection_listener.rejected_cb = [&](absl::string_view endpoint_id, Status status) { OnConnectionRejected(endpoint_id, status); }; connection_listener.disconnected_cb = [this](absl::string_view endpoint_id) { connections_callback_task_runner_->PostTask( [this, endpoint_id = std::string(endpoint_id)]() { OnDisconnected(endpoint_id); }); }; connection_listener.bandwidth_changed_cb = [&](absl::string_view endpoint_id, Medium medium) { OnBandwidthChanged(endpoint_id, medium); }; nearby_connections_service_->RequestConnection( kServiceId, endpoint_info, endpoint_id, { .allowed_mediums = std::move(allowed_mediums), .remote_bluetooth_mac_address = std::move(bluetooth_mac_address), .non_disruptive_hotspot_mode = IsTransportTypeFlagsSet( transport_type, TransportType::kHighQualityNonDisruptive), }, std::move(connection_listener), [this, endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { MutexLock lock(&mutex_); if (status != ConnectionsStatus::kSuccess) { transfer_managers_.erase(endpoint_id); } OnConnectionRequested(endpoint_id, status); }); // Setup transfer manager. if (IsTransportTypeFlagsSet(transport_type, TransportType::kHighQuality)) { transfer_managers_[endpoint_id] = std::make_unique(context_, endpoint_id); } } void NearbyConnectionsManagerImpl::OnConnectionTimedOut( absl::string_view endpoint_id) { MutexLock lock(&mutex_); LOG(ERROR) << "Failed to connect to the remote shareTarget: Timed out."; if (pending_outgoing_connections_.contains(endpoint_id)) { auto it = connection_info_map_.find(endpoint_id); if (it != connection_info_map_.end()) { it->second.connection_layer_status = Status::kTimeout; } } Disconnect(endpoint_id); } void NearbyConnectionsManagerImpl::OnConnectionRequested( absl::string_view endpoint_id, ConnectionsStatus status) { MutexLock lock(&mutex_); auto it = pending_outgoing_connections_.find(endpoint_id); if (it == pending_outgoing_connections_.end()) return; if (status != ConnectionsStatus::kSuccess) { LOG(ERROR) << "Failed to connect to the remote shareTarget: " << ConnectionsStatusToString(status); auto info_it = connection_info_map_.find(endpoint_id); if (info_it != connection_info_map_.end()) { info_it->second.connection_layer_status = status; } Disconnect(endpoint_id); return; } } void NearbyConnectionsManagerImpl::Disconnect(absl::string_view endpoint_id) { MutexLock lock(&mutex_); if (!pending_outgoing_connections_.contains(endpoint_id) && !connection_info_map_.contains(endpoint_id)) { LOG(WARNING) << "No connection for endpoint " << endpoint_id; return; } if (disconnecting_endpoints_.contains(endpoint_id)) { LOG(INFO) << "Another Disconnecting is running for endpoint_id " << endpoint_id; return; } disconnecting_endpoints_.insert(std::string(endpoint_id)); nearby_connections_service_->DisconnectFromEndpoint( kServiceId, endpoint_id, [&, endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { VLOG(1) << __func__ << ": Disconnecting from endpoint " << endpoint_id << " attempted over Nearby Connections with result: " << ConnectionsStatusToString(status); connections_callback_task_runner_->PostTask([this, endpoint_id]() { OnDisconnected(endpoint_id); { MutexLock lock(&mutex_); disconnecting_endpoints_.erase(endpoint_id); } }); LOG(INFO) << "Disconnected from " << endpoint_id; }); } void NearbyConnectionsManagerImpl::Send( absl::string_view endpoint_id, std::unique_ptr payload, std::weak_ptr listener) { MutexLock lock(&mutex_); if (listener.lock()) { RegisterPayloadStatusListener(payload->id, listener); } if (transfer_managers_.contains(endpoint_id) && payload->content.is_file()) { VLOG(1) << __func__ << ": Send payload " << payload->id << " to " << endpoint_id << " to transfer manager. payload is file: " << payload->content.is_file() << ", is bytes " << payload->content.is_bytes(); transfer_managers_.at(endpoint_id) ->Send([&, endpoint_id = std::string(endpoint_id), payload_copy = *payload]() { VLOG(1) << __func__ << ": Send payload " << payload_copy.id << " to " << endpoint_id; auto sent_payload = std::make_unique(payload_copy); SendWithoutDelay(endpoint_id, std::move(sent_payload)); }); transfer_managers_.at(endpoint_id)->StartTransfer(); return; } SendWithoutDelay(endpoint_id, std::move(payload)); } void NearbyConnectionsManagerImpl::SendWithoutDelay( absl::string_view endpoint_id, std::unique_ptr payload) { VLOG(1) << __func__ << ": Send payload " << payload->id << " to " << endpoint_id; nearby_connections_service_->SendPayload( kServiceId, {std::string(endpoint_id)}, std::move(payload), [endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { VLOG(1) << __func__ << ": Sending payload to endpoint " << endpoint_id << " attempted over Nearby Connections with result: " << ConnectionsStatusToString(status); }); } void NearbyConnectionsManagerImpl::RegisterPayloadStatusListener( int64_t payload_id, std::weak_ptr listener) { MutexLock lock(&mutex_); payload_status_listeners_.insert_or_assign(payload_id, listener); } const Payload* NearbyConnectionsManagerImpl::GetIncomingPayload( int64_t payload_id) const { MutexLock lock(&mutex_); const auto it = incoming_payloads_.find(payload_id); if (it == incoming_payloads_.end()) return nullptr; return &it->second; } void NearbyConnectionsManagerImpl::Cancel(int64_t payload_id) { MutexLock lock(&mutex_); auto it = payload_status_listeners_.find(payload_id); if (it != payload_status_listeners_.end()) { std::weak_ptr listener = it->second; payload_status_listeners_.erase(payload_id); // Note: The listener might be invalidated, for example, if it is shared // with another payload in the same transfer. if (auto status_listener = listener.lock()) { status_listener->OnStatusUpdate(std::make_unique( payload_id, PayloadStatus::kCanceled, /*total_bytes=*/0, /*bytes_transferred=*/0)); } } nearby_connections_service_->CancelPayload( kServiceId, payload_id, [&, payload_id](ConnectionsStatus status) { VLOG(1) << __func__ << ": Cancelling payload to id " << payload_id << " attempted over Nearby Connections with result: " << ConnectionsStatusToString(status); }); LOG(INFO) << "Cancelling payload: " << payload_id; } void NearbyConnectionsManagerImpl::ClearIncomingPayloads() { MutexLock lock(&mutex_); std::vector payloads; for (auto& it : incoming_payloads_) { payloads.push_back(std::move(it.second)); payload_status_listeners_.erase(it.first); } incoming_payloads_.clear(); } std::optional> NearbyConnectionsManagerImpl::GetRawAuthenticationToken( absl::string_view endpoint_id) { MutexLock lock(&mutex_); auto it = connection_info_map_.find(endpoint_id); if (it == connection_info_map_.end()) return std::nullopt; return it->second.raw_authentication_token; } void NearbyConnectionsManagerImpl::UpgradeBandwidth( absl::string_view endpoint_id) { MutexLock lock(&mutex_); // The only bandwidth upgrade mediums at this point are WebRTC and WifiLan. if (!NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_sharing_feature:: kEnableMediumWifiLan) && !NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_sharing_feature::kEnableMediumWebRtc)) { return; } requested_bwu_endpoint_ids_.emplace(endpoint_id); nearby_connections_service_->InitiateBandwidthUpgrade( kServiceId, endpoint_id, [&, endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { VLOG(1) << __func__ << ": Bandwidth upgrade attempted to endpoint " << endpoint_id << "over Nearby Connections with result: " << ConnectionsStatusToString(status); }); } void NearbyConnectionsManagerImpl::OnEndpointFound( absl::string_view endpoint_id, const DiscoveredEndpointInfo& info) { MutexLock lock(&mutex_); if (!discovery_listener_) { VLOG(1) << "Ignoring discovered endpoint " << nearby::utils::HexEncode(info.endpoint_info) << " because we're no longer " "in discovery mode"; return; } auto result = discovered_endpoints_.insert(std::string(endpoint_id)); if (!result.second) { VLOG(1) << "Ignoring discovered endpoint " << nearby::utils::HexEncode(info.endpoint_info) << " because we've already " "reported this endpoint"; return; } discovery_listener_->OnEndpointDiscovered(endpoint_id, info.endpoint_info); LOG(INFO) << "Discovered " << nearby::utils::HexEncode(info.endpoint_info) << " over Nearby Connections"; } void NearbyConnectionsManagerImpl::OnEndpointLost( absl::string_view endpoint_id) { MutexLock lock(&mutex_); if (!discovered_endpoints_.erase(endpoint_id)) { VLOG(1) << "Ignoring lost endpoint " << endpoint_id << " because we haven't reported this endpoint"; return; } if (!discovery_listener_) { VLOG(1) << "Ignoring lost endpoint " << endpoint_id << " because we're no longer in discovery mode"; return; } discovery_listener_->OnEndpointLost(endpoint_id); LOG(INFO) << "Endpoint " << endpoint_id << " lost over Nearby Connections"; } void NearbyConnectionsManagerImpl::OnConnectionInitiated( absl::string_view endpoint_id, const ConnectionInfo& info) { MutexLock lock(&mutex_); [[maybe_unused]] auto result = connection_info_map_.emplace(endpoint_id, std::move(info)); DCHECK(result.second); NearbyConnectionsService::PayloadListener payload_listener; payload_listener.payload_cb = [this](absl::string_view endpoint_id, Payload payload) { OnPayloadReceived(endpoint_id, payload); }; payload_listener.payload_progress_cb = absl::bind_front( &NearbyConnectionsManagerImpl::OnPayloadTransferUpdate, this); nearby_connections_service_->AcceptConnection( kServiceId, endpoint_id, std::move(payload_listener), [endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { VLOG(1) << __func__ << ": Accept connection attempted to endpoint " << endpoint_id << " over Nearby Connections with result: " << ConnectionsStatusToString(status); }); } void NearbyConnectionsManagerImpl::OnConnectionAccepted( absl::string_view endpoint_id) { MutexLock lock(&mutex_); auto it = connection_info_map_.find(endpoint_id); if (it == connection_info_map_.end()) return; if (it->second.is_incoming_connection) { if (!incoming_connection_listener_) { // Not in advertising mode. Disconnect(endpoint_id); return; } auto result = connections_.emplace( std::string(endpoint_id), std::make_unique(device_info_)); DCHECK(result.second); incoming_connection_listener_->OnIncomingConnection( endpoint_id, it->second.endpoint_info, result.first->second.get()); } else { auto it = pending_outgoing_connections_.find(endpoint_id); if (it == pending_outgoing_connections_.end()) { Disconnect(endpoint_id); return; } auto result = connections_.emplace( endpoint_id, std::make_unique(device_info_)); DCHECK(result.second); std::move(it->second)(endpoint_id, result.first->second.get(), Status::kSuccess); pending_outgoing_connections_.erase(it); connect_timeout_timers_.erase(endpoint_id); } } void NearbyConnectionsManagerImpl::OnConnectionRejected( absl::string_view endpoint_id, Status status) { MutexLock lock(&mutex_); connection_info_map_.erase(endpoint_id); auto it = pending_outgoing_connections_.find(endpoint_id); if (it != pending_outgoing_connections_.end()) { std::move(it->second)(endpoint_id, nullptr, status); pending_outgoing_connections_.erase(it); connect_timeout_timers_.erase(endpoint_id); } } void NearbyConnectionsManagerImpl::OnDisconnected( absl::string_view endpoint_id) { MutexLock lock(&mutex_); // Remove transfer manager. if (transfer_managers_.contains(endpoint_id)) { transfer_managers_[endpoint_id]->CancelTransfer(); transfer_managers_.erase(endpoint_id); } Status connection_layer_status = Status::kUnknown; auto info_it = connection_info_map_.find(endpoint_id); if (info_it != connection_info_map_.end()) { connection_layer_status = info_it->second.connection_layer_status; connection_info_map_.erase(info_it); } auto it = pending_outgoing_connections_.find(endpoint_id); if (it != pending_outgoing_connections_.end()) { std::move(it->second)(endpoint_id, nullptr, connection_layer_status); pending_outgoing_connections_.erase(it); connect_timeout_timers_.erase(endpoint_id); } connections_.erase(endpoint_id); requested_bwu_endpoint_ids_.erase(endpoint_id); current_upgraded_mediums_.erase(endpoint_id); } void NearbyConnectionsManagerImpl::OnBandwidthChanged( absl::string_view endpoint_id, Medium medium) { MutexLock lock(&mutex_); VLOG(1) << __func__ << ": Bandwidth changed to medium=" << static_cast(medium) << "; endpoint_id=" << endpoint_id; if (transfer_managers_.contains(endpoint_id)) { transfer_managers_[endpoint_id]->OnMediumQualityChanged(medium); } current_upgraded_mediums_.insert_or_assign(endpoint_id, medium); // TODO(crbug/1111458): Support TransferManager. } void NearbyConnectionsManagerImpl::OnPayloadReceived( absl::string_view endpoint_id, Payload& payload) { MutexLock lock(&mutex_); VLOG(1) << "Received payload id=" << payload.id; if (NearbyFlags::GetInstance().GetBoolFlag( sharing::config_package_nearby::nearby_sharing_feature:: kDeleteUnexpectedReceivedFileFix)) { if (payload.content.type != PayloadContent::Type::kBytes && !payload_status_listeners_.contains(payload.id)) { LOG(WARNING) << __func__ << ": Received unknown payload. Canceling."; DeleteUnknownFilePayloadAndCancel(payload); return; } if (!incoming_payloads_.contains(payload.id)) { incoming_payloads_.emplace(payload.id, std::move(payload)); return; } LOG(WARNING) << __func__ << ": Payload id already exists. Canceling."; DeleteUnknownFilePayloadAndCancel(payload); } else { [[maybe_unused]] auto result = incoming_payloads_.emplace(payload.id, std::move(payload)); DCHECK(result.second); } } void NearbyConnectionsManagerImpl::DeleteUnknownFilePayloadAndCancel( Payload& payload) { if (payload.content.type == PayloadContent::Type::kFile) { MutexLock lock(&mutex_); file_paths_to_delete_.insert(payload.content.file_payload.file.path); } Cancel(payload.id); } void NearbyConnectionsManagerImpl::ProcessUnknownFilePathsToDelete( PayloadStatus status, PayloadContent::Type type, const FilePath& path) { // Unknown payload comes as kInProgress and kCanceled status with kFile type // from NearbyConnections. Delete it. if ((status == PayloadStatus::kCanceled || status == PayloadStatus::kInProgress) && type == PayloadContent::Type::kFile) { LOG(WARNING) << __func__ << ": Unknown payload has been canceled, removing."; MutexLock lock(&mutex_); file_paths_to_delete_.insert(path); } } std::optional< std::weak_ptr> NearbyConnectionsManagerImpl::GetStatusListenerForId(int64_t payload_id) const { MutexLock lock(&mutex_); auto listener_it = payload_status_listeners_.find(payload_id); if (listener_it == payload_status_listeners_.end()) { return std::nullopt; } return listener_it->second; } NearbyConnectionImpl* NearbyConnectionsManagerImpl::GetConnectionForId( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); auto connection_it = connections_.find(endpoint_id); if (connection_it == connections_.end()) return nullptr; return connection_it->second.get(); } void NearbyConnectionsManagerImpl::RemoveStatusListenerForPayloadId( int64_t payload_id) { MutexLock lock(&mutex_); payload_status_listeners_.erase(payload_id); } void NearbyConnectionsManagerImpl::OnPayloadTransferUpdate( absl::string_view endpoint_id, const PayloadTransferUpdate& update) { VLOG(1) << "Received payload transfer update id=" << update.payload_id << ",status=" << PayloadStatusToString(update.status) << ",total=" << update.total_bytes << ",bytes_transferred=" << update.bytes_transferred; // If this is a payload we've registered for, then forward its status to // the PayloadStatusListener if it still exists. We don't need to do // anything more with the payload. std::optional> listener = GetStatusListenerForId(update.payload_id); if (listener.has_value()) { switch (update.status) { case PayloadStatus::kInProgress: break; case PayloadStatus::kSuccess: case PayloadStatus::kCanceled: case PayloadStatus::kFailure: RemoveStatusListenerForPayloadId(update.payload_id); break; } // Note: The listener might be invalidated, for example, if it is shared // with another payload in the same transfer. if (auto status_listener = listener->lock()) { status_listener->OnStatusUpdate( std::make_unique(update)); } return; } // If this is an incoming payload that we have not registered for, then // we'll treat it as a control frame (e.g. IntroductionFrame) and // forward it to the associated NearbyConnection. auto payload = GetIncomingPayload(update.payload_id); if (payload == nullptr) return; if (!NearbyFlags::GetInstance().GetBoolFlag( sharing::config_package_nearby::nearby_sharing_feature:: kDeleteUnexpectedReceivedFileFix)) { if (payload->content.type != PayloadContent::Type::kBytes) { LOG(WARNING) << "Received unknown payload of file type. Cancelling."; nearby_connections_service_->CancelPayload(kServiceId, payload->id, [](Status status) {}); ProcessUnknownFilePathsToDelete(update.status, payload->content.type, payload->content.file_payload.file.path); return; } } if (update.status != PayloadStatus::kSuccess) return; NearbyConnectionImpl* connection = GetConnectionForId(endpoint_id); if (connection == nullptr) return; VLOG(1) << "Writing incoming byte message to NearbyConnection."; connection->WriteMessage(payload->content.bytes_payload.bytes); } void NearbyConnectionsManagerImpl::Reset() { MutexLock lock(&mutex_); for (auto& [endpoint_id, connection] : connections_) { nearby_connections_service_->DisconnectFromEndpoint( kServiceId, endpoint_id, [endpoint_id = std::string(endpoint_id)](ConnectionsStatus status) { VLOG(1) << __func__ << ": Disconnect from endpoint attempted over Nearby " "Connections with result: " << ConnectionsStatusToString(status); }); } discovered_endpoints_.clear(); payload_status_listeners_.clear(); ClearIncomingPayloads(); connections_.clear(); connection_info_map_.clear(); discovery_listener_ = nullptr; incoming_connection_listener_ = nullptr; connect_timeout_timers_.clear(); requested_bwu_endpoint_ids_.clear(); current_upgraded_mediums_.clear(); for (auto& transfer_manager : transfer_managers_) { transfer_manager.second->CancelTransfer(); } transfer_managers_.clear(); for (auto& entry : pending_outgoing_connections_) std::move(entry.second)(entry.first, /*connection=*/nullptr, Status::kReset); pending_outgoing_connections_.clear(); } std::optional NearbyConnectionsManagerImpl::GetUpgradedMedium( absl::string_view endpoint_id) const { MutexLock lock(&mutex_); const auto it = current_upgraded_mediums_.find(endpoint_id); if (it == current_upgraded_mediums_.end()) return std::nullopt; return it->second; } void NearbyConnectionsManagerImpl::SetCustomSavePath( absl::string_view custom_save_path) { MutexLock lock(&mutex_); nearby_connections_service_->SetCustomSavePath( custom_save_path, [&](Status status) { VLOG(1) << __func__ << ": SetCustomSavePath attempted over Nearby " "Connections with result: " << static_cast(status); }); } absl::flat_hash_set NearbyConnectionsManagerImpl::GetUnknownFilePathsToDelete() { MutexLock lock(&mutex_); return file_paths_to_delete_; } absl::flat_hash_set NearbyConnectionsManagerImpl::GetAndClearUnknownFilePathsToDelete() { MutexLock lock(&mutex_); auto file_paths_to_delete = std::move(file_paths_to_delete_); file_paths_to_delete_.clear(); return file_paths_to_delete; } absl::flat_hash_set NearbyConnectionsManagerImpl::GetUnknownFilePathsToDeleteForTesting() { return GetUnknownFilePathsToDelete(); } void NearbyConnectionsManagerImpl::AddUnknownFilePathsToDeleteForTesting( FilePath file_path) { MutexLock lock(&mutex_); file_paths_to_delete_.insert(file_path); } void NearbyConnectionsManagerImpl::ProcessUnknownFilePathsToDeleteForTesting( PayloadStatus status, PayloadContent::Type type, const FilePath& path) { ProcessUnknownFilePathsToDelete(status, type, path); } void NearbyConnectionsManagerImpl::OnPayloadTransferUpdateForTesting( absl::string_view endpoint_id, const PayloadTransferUpdate& update) { OnPayloadTransferUpdate(endpoint_id, update); } void NearbyConnectionsManagerImpl::OnPayloadReceivedForTesting( absl::string_view endpoint_id, Payload& payload) { OnPayloadReceived(endpoint_id, payload); } std::string NearbyConnectionsManagerImpl::Dump() const { return nearby_connections_service_->Dump(); } } // namespace nearby::sharing