| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/nearby_sharing/nearby_connections_manager_impl.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/files/file_util.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/unguessable_token.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_features.h" |
| #include "chrome/browser/nearby_sharing/constants.h" |
| #include "chrome/browser/nearby_sharing/public/cpp/nearby_connections_manager.h" |
| #include "chromeos/ash/components/nearby/presence/conversions/proto_conversions.h" |
| #include "chromeos/ash/services/nearby/public/mojom/nearby_connections_types.mojom.h" |
| #include "chromeos/ash/services/nearby/public/mojom/nearby_presence.mojom.h" |
| #include "components/cross_device/logging/logging.h" |
| #include "crypto/random.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "net/base/network_change_notifier.h" |
| |
| namespace { |
| |
| using NearbyPresenceService = ash::nearby::presence::NearbyPresenceService; |
| |
| const char kFastAdvertisementServiceUuid[] = |
| "0000fef3-0000-1000-8000-00805f9b34fb"; |
| const nearby::connections::mojom::Strategy kStrategy = |
| nearby::connections::mojom::Strategy::kP2pPointToPoint; |
| |
| bool ShouldUseInternet(DataUsage data_usage, PowerLevel power_level) { |
| // We won't use internet if the user requested we don't. |
| if (data_usage == DataUsage::kOffline) { |
| return false; |
| } |
| |
| // We won't use internet in a low power mode. |
| if (power_level == PowerLevel::kLowPower) { |
| return false; |
| } |
| |
| net::NetworkChangeNotifier::ConnectionType connection_type = |
| net::NetworkChangeNotifier::GetConnectionType(); |
| |
| // Verify that this network has an internet connection. |
| if (connection_type == net::NetworkChangeNotifier::CONNECTION_NONE) { |
| CD_LOG(VERBOSE, Feature::NC) << __func__ << ": No internet connection."; |
| return false; |
| } |
| |
| // If the user wants to limit Wi-Fi, then don't use it on metered networks. |
| if (data_usage == DataUsage::kWifiOnly && |
| net::NetworkChangeNotifier::GetConnectionCost() == |
| net::NetworkChangeNotifier::CONNECTION_COST_METERED) { |
| CD_LOG(VERBOSE, Feature::NC) << __func__ << ": Do not use internet with " |
| << data_usage << " and a metered connection."; |
| return false; |
| } |
| |
| // We're online, the user hasn't disabled Wi-Fi, let's use it! |
| return true; |
| } |
| |
| bool ShouldEnableWebRtc(DataUsage data_usage, PowerLevel power_level) { |
| return base::FeatureList::IsEnabled(features::kNearbySharingWebRtc) && |
| ShouldUseInternet(data_usage, power_level); |
| } |
| |
| bool ShouldEnableWifiLan(DataUsage data_usage, PowerLevel power_level) { |
| if (!base::FeatureList::IsEnabled(features::kNearbySharingWifiLan)) { |
| return false; |
| } |
| |
| // WifiLan only works if both devices are using the same router. We can't |
| // guarantee this, but at least check that we are using Wi-Fi or ethernet. |
| // TODO(https://crbug.com/1261238): Test if WifiLan can work if both devices |
| // are connected to the router without an internet connection. If so, return |
| // true if connection_type == net::NetworkChangeNotifier::CONNECTION_NONE. |
| net::NetworkChangeNotifier::ConnectionType connection_type = |
| net::NetworkChangeNotifier::GetConnectionType(); |
| bool is_connection_wifi_or_ethernet = |
| connection_type == net::NetworkChangeNotifier::CONNECTION_WIFI || |
| connection_type == net::NetworkChangeNotifier::CONNECTION_ETHERNET; |
| |
| return ShouldUseInternet(data_usage, power_level) && |
| is_connection_wifi_or_ethernet; |
| } |
| |
| std::string MediumSelectionToString( |
| const nearby::connections::mojom::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 "; |
| } |
| ss << "}"; |
| |
| return ss.str(); |
| } |
| |
| // TODO(b/311040986): Migrate these to a conversions class. This requires moving |
| // NearbyPresenceService::PresenceDevice and NearbyPresenceService::Action out |
| // of NearbyPresenceService to avoid a dependency cycle. |
| ash::nearby::presence::mojom::ActionType ConvertPresenceServiceActionToMojom( |
| NearbyPresenceService::Action action) { |
| switch (action) { |
| case NearbyPresenceService::Action::kActiveUnlock: |
| return ash::nearby::presence::mojom::ActionType::kActiveUnlockAction; |
| case NearbyPresenceService::Action::kNearbyShare: |
| return ash::nearby::presence::mojom::ActionType::kNearbyShareAction; |
| case NearbyPresenceService::Action::kInstantTethering: |
| return ash::nearby::presence::mojom::ActionType::kInstantTetheringAction; |
| case NearbyPresenceService::Action::kPhoneHub: |
| return ash::nearby::presence::mojom::ActionType::kPhoneHubAction; |
| case NearbyPresenceService::Action::kPresenceManager: |
| return ash::nearby::presence::mojom::ActionType::kPresenceManagerAction; |
| case NearbyPresenceService::Action::kFinder: |
| return ash::nearby::presence::mojom::ActionType::kFinderAction; |
| case NearbyPresenceService::Action::kFastPairSass: |
| return ash::nearby::presence::mojom::ActionType::kFastPairSassAction; |
| case NearbyPresenceService::Action::kTapToTransfer: |
| return ash::nearby::presence::mojom::ActionType::kTapToTransferAction; |
| case NearbyPresenceService::Action::kLast: |
| return ash::nearby::presence::mojom::ActionType::kLastAction; |
| } |
| } |
| |
| ash::nearby::presence::mojom::MetadataPtr MetadataToMojom( |
| ::nearby::internal::Metadata metadata) { |
| return ash::nearby::presence::mojom::Metadata::New( |
| ash::nearby::presence::proto::DeviceTypeToMojom(metadata.device_type()), |
| metadata.account_name(), metadata.device_name(), metadata.user_name(), |
| metadata.device_profile_url(), |
| std::vector<uint8_t>(metadata.bluetooth_mac_address().begin(), |
| metadata.bluetooth_mac_address().end())); |
| } |
| |
| ash::nearby::presence::mojom::PresenceDevicePtr |
| BuildPresenceMojomFromServiceDevice( |
| NearbyPresenceService::PresenceDevice device) { |
| std::vector<ash::nearby::presence::mojom::ActionType> actions; |
| for (auto action : device.GetActions()) { |
| actions.push_back(ConvertPresenceServiceActionToMojom(action)); |
| } |
| |
| return ash::nearby::presence::mojom::PresenceDevice::New( |
| device.GetEndpointId(), std::move(actions), device.GetStableId(), |
| MetadataToMojom(device.GetMetadata())); |
| } |
| |
| } // namespace |
| |
| NearbyConnectionsManagerImpl::NearbyConnectionsManagerImpl( |
| ash::nearby::NearbyProcessManager* process_manager, |
| const std::string& service_id) |
| : process_manager_(process_manager), service_id_(service_id) { |
| DCHECK(process_manager_); |
| } |
| |
| NearbyConnectionsManagerImpl::~NearbyConnectionsManagerImpl() { |
| ClearIncomingPayloads(); |
| } |
| |
| void NearbyConnectionsManagerImpl::Shutdown() { |
| Reset(); |
| } |
| |
| void NearbyConnectionsManagerImpl::StartAdvertising( |
| std::vector<uint8_t> endpoint_info, |
| IncomingConnectionListener* listener, |
| PowerLevel power_level, |
| DataUsage data_usage, |
| ConnectionsCallback callback) { |
| DCHECK(listener); |
| DCHECK(!incoming_connection_listener_); |
| |
| nearby::connections::mojom::NearbyConnections* nearby_connections = |
| GetNearbyConnections(); |
| if (!nearby_connections) { |
| std::move(callback).Run(ConnectionsStatus::kError); |
| return; |
| } |
| |
| bool is_high_power = power_level == PowerLevel::kHighPower; |
| bool use_ble = !is_high_power; |
| auto allowed_mediums = MediumSelection::New( |
| /*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 or not to allow WebRTC |
| // upgrades from this advertisement. |
| ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower), |
| /*wifi_lan=*/ |
| ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower) && |
| kIsWifiLanAdvertisingSupported); |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": " << "is_high_power=" << (is_high_power ? "yes" : "no") |
| << ", data_usage=" << data_usage |
| << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums); |
| |
| mojo::PendingRemote<ConnectionLifecycleListener> lifecycle_listener; |
| connection_lifecycle_listeners_.Add( |
| this, lifecycle_listener.InitWithNewPipeAndPassReceiver()); |
| |
| // Only auto-upgrade bandwidth if advertising at high-visibility. |
| // This acts as a privacy safeguard when advertising in the background. |
| // Bandwidth upgrades may expose stable identifiers, and so they're |
| // only safe to expose after we've verified the sender's identity. |
| // Once we have verified their identity, we will manually trigger |
| // a bandwidth upgrade. This isn't a concern in the foreground |
| // because high-visibility already leaks the device name. |
| bool auto_upgrade_bandwidth = is_high_power; |
| |
| incoming_connection_listener_ = listener; |
| nearby_connections->StartAdvertising( |
| service_id_, endpoint_info, |
| AdvertisingOptions::New( |
| kStrategy, std::move(allowed_mediums), auto_upgrade_bandwidth, |
| /*enforce_topology_constraints=*/true, |
| /*enable_bluetooth_listening=*/use_ble, |
| /*enable_webrtc_listening=*/ |
| ShouldEnableWebRtc(data_usage, power_level), |
| /*fast_advertisement_service_uuid=*/ |
| device::BluetoothUUID(kFastAdvertisementServiceUuid)), |
| std::move(lifecycle_listener), std::move(callback)); |
| } |
| |
| void NearbyConnectionsManagerImpl::StopAdvertising( |
| ConnectionsCallback callback) { |
| incoming_connection_listener_ = nullptr; |
| |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| process_reference_->GetNearbyConnections()->StopAdvertising( |
| service_id_, std::move(callback)); |
| } |
| |
| void NearbyConnectionsManagerImpl::StartDiscovery( |
| DiscoveryListener* listener, |
| DataUsage data_usage, |
| ConnectionsCallback callback) { |
| DCHECK(listener); |
| DCHECK(!discovery_listener_); |
| |
| nearby::connections::mojom::NearbyConnections* nearby_connections = |
| GetNearbyConnections(); |
| if (!nearby_connections) { |
| std::move(callback).Run(ConnectionsStatus::kError); |
| return; |
| } |
| |
| auto allowed_mediums = MediumSelection::New( |
| /*bluetooth=*/true, |
| /*ble=*/true, |
| /*webrtc=*/ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower), |
| /*wifi_lan=*/ |
| ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower) && |
| kIsWifiLanDiscoverySupported); |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": " << "data_usage=" << data_usage |
| << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums); |
| |
| discovery_listener_ = listener; |
| nearby_connections->StartDiscovery( |
| service_id_, |
| DiscoveryOptions::New( |
| kStrategy, std::move(allowed_mediums), |
| device::BluetoothUUID(kFastAdvertisementServiceUuid), |
| /*is_out_of_band_connection=*/false), |
| endpoint_discovery_listener_.BindNewPipeAndPassRemote(), |
| std::move(callback)); |
| } |
| |
| void NearbyConnectionsManagerImpl::StopDiscovery() { |
| discovered_endpoints_.clear(); |
| discovery_listener_ = nullptr; |
| endpoint_discovery_listener_.reset(); |
| |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| process_reference_->GetNearbyConnections()->StopDiscovery( |
| service_id_, base::BindOnce([](ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ |
| << ": Stop discovery attempted over Nearby " |
| "Connections with result: " |
| << ConnectionsStatusToString(status); |
| })); |
| } |
| |
| void NearbyConnectionsManagerImpl::Connect( |
| std::vector<uint8_t> endpoint_info, |
| const std::string& endpoint_id, |
| std::optional<std::vector<uint8_t>> bluetooth_mac_address, |
| DataUsage data_usage, |
| NearbyConnectionCallback callback) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| std::move(callback).Run(nullptr); |
| return; |
| } |
| |
| if (bluetooth_mac_address && bluetooth_mac_address->size() != 6) { |
| bluetooth_mac_address.reset(); |
| } |
| |
| auto allowed_mediums = MediumSelection::New( |
| /*bluetooth=*/true, |
| /*ble=*/false, ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower), |
| /*wifi_lan=*/ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower)); |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": " << "data_usage=" << data_usage |
| << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums); |
| |
| mojo::PendingRemote<ConnectionLifecycleListener> lifecycle_listener; |
| connection_lifecycle_listeners_.Add( |
| this, lifecycle_listener.InitWithNewPipeAndPassReceiver()); |
| |
| auto result = |
| pending_outgoing_connections_.emplace(endpoint_id, std::move(callback)); |
| DCHECK(result.second); |
| |
| auto timeout_timer = std::make_unique<base::OneShotTimer>(); |
| timeout_timer->Start( |
| FROM_HERE, kInitiateNearbyConnectionTimeout, |
| base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionTimedOut, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| connect_timeout_timers_.emplace(endpoint_id, std::move(timeout_timer)); |
| |
| process_reference_->GetNearbyConnections()->RequestConnection( |
| service_id_, endpoint_info, endpoint_id, |
| ConnectionOptions::New(std::move(allowed_mediums), |
| std::move(bluetooth_mac_address), |
| /*keep_alive_interval_millis=*/std::nullopt, |
| /*keep_alive_timeout_millis=*/std::nullopt), |
| std::move(lifecycle_listener), |
| base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionRequested, |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionTimedOut( |
| const std::string& endpoint_id) { |
| CD_LOG(ERROR, Feature::NC) |
| << "Failed to connect to the remote shareTarget: Timed out."; |
| Disconnect(endpoint_id); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionRequested( |
| const std::string& endpoint_id, |
| ConnectionsStatus status) { |
| auto it = pending_outgoing_connections_.find(endpoint_id); |
| if (it == pending_outgoing_connections_.end()) { |
| return; |
| } |
| |
| if (status != ConnectionsStatus::kSuccess) { |
| CD_LOG(ERROR, Feature::NC) |
| << "Failed to connect to the remote shareTarget: " |
| << ConnectionsStatusToString(status); |
| Disconnect(endpoint_id); |
| return; |
| } |
| |
| // TODO(crbug/1111458): Support TransferManager. |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionRequestedV3( |
| NearbyPresenceService::PresenceDevice remote_device, |
| ConnectionsStatus status) { |
| if (status != ConnectionsStatus::kSuccess) { |
| CD_LOG(ERROR, Feature::NC) |
| << "Failed to connect (v3) to remote device with result: " |
| << ConnectionsStatusToString(status); |
| DisconnectV3(std::move(remote_device)); |
| return; |
| } |
| } |
| |
| void NearbyConnectionsManagerImpl::Disconnect(const std::string& endpoint_id) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| process_reference_->GetNearbyConnections()->DisconnectFromEndpoint( |
| service_id_, endpoint_id, |
| base::BindOnce( |
| [](const std::string& endpoint_id, ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Disconnecting from endpoint " << endpoint_id |
| << " attempted over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| }, |
| endpoint_id)); |
| |
| OnDisconnected(endpoint_id); |
| CD_LOG(INFO, Feature::NC) << "Disconnected from " << endpoint_id; |
| } |
| |
| void NearbyConnectionsManagerImpl::Send( |
| const std::string& endpoint_id, |
| PayloadPtr payload, |
| base::WeakPtr<PayloadStatusListener> listener) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| if (listener) { |
| RegisterPayloadStatusListener(payload->id, listener); |
| } |
| |
| process_reference_->GetNearbyConnections()->SendPayload( |
| service_id_, {endpoint_id}, std::move(payload), |
| base::BindOnce( |
| [](const std::string& endpoint_id, ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Sending payload to endpoint " << endpoint_id |
| << " attempted over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| }, |
| endpoint_id)); |
| } |
| |
| void NearbyConnectionsManagerImpl::RegisterPayloadStatusListener( |
| int64_t payload_id, |
| base::WeakPtr<PayloadStatusListener> listener) { |
| payload_status_listeners_.insert_or_assign(payload_id, listener); |
| } |
| |
| void NearbyConnectionsManagerImpl::RegisterPayloadPath( |
| int64_t payload_id, |
| const base::FilePath& file_path, |
| ConnectionsCallback callback) { |
| if (!process_reference_) { |
| return; |
| } |
| |
| DCHECK(!file_path.empty()); |
| |
| file_handler_.CreateFile( |
| file_path, base::BindOnce(&NearbyConnectionsManagerImpl::OnFileCreated, |
| weak_ptr_factory_.GetWeakPtr(), payload_id, |
| std::move(callback))); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnFileCreated( |
| int64_t payload_id, |
| ConnectionsCallback callback, |
| NearbyFileHandler::CreateFileResult result) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| process_reference_->GetNearbyConnections()->RegisterPayloadFile( |
| service_id_, payload_id, std::move(result.input_file), |
| std::move(result.output_file), std::move(callback)); |
| } |
| |
| NearbyConnectionsManagerImpl::Payload* |
| NearbyConnectionsManagerImpl::GetIncomingPayload(int64_t payload_id) { |
| auto it = incoming_payloads_.find(payload_id); |
| if (it == incoming_payloads_.end()) { |
| return nullptr; |
| } |
| |
| return it->second.get(); |
| } |
| |
| void NearbyConnectionsManagerImpl::Cancel(int64_t payload_id) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| auto it = payload_status_listeners_.find(payload_id); |
| if (it != payload_status_listeners_.end()) { |
| base::WeakPtr<PayloadStatusListener> 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 (listener) { |
| listener->OnStatusUpdate( |
| PayloadTransferUpdate::New(payload_id, PayloadStatus::kCanceled, |
| /*total_bytes=*/0, |
| /*bytes_transferred=*/0), |
| /*upgraded_medium=*/std::nullopt); |
| } |
| } |
| |
| process_reference_->GetNearbyConnections()->CancelPayload( |
| service_id_, payload_id, |
| base::BindOnce( |
| [](int64_t payload_id, ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Cancelling payload to id " << payload_id |
| << " attempted over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| }, |
| payload_id)); |
| CD_LOG(INFO, Feature::NC) << "Cancelling payload: " << payload_id; |
| } |
| |
| void NearbyConnectionsManagerImpl::ClearIncomingPayloads() { |
| std::vector<PayloadPtr> payloads; |
| for (auto& it : incoming_payloads_) { |
| payloads.push_back(std::move(it.second)); |
| payload_status_listeners_.erase(it.first); |
| } |
| |
| file_handler_.ReleaseFilePayloads(std::move(payloads)); |
| incoming_payloads_.clear(); |
| } |
| |
| std::optional<std::string> NearbyConnectionsManagerImpl::GetAuthenticationToken( |
| const std::string& endpoint_id) { |
| auto it = connection_info_map_.find(endpoint_id); |
| if (it == connection_info_map_.end()) { |
| return std::nullopt; |
| } |
| |
| return it->second->authentication_token; |
| } |
| |
| std::optional<std::vector<uint8_t>> |
| NearbyConnectionsManagerImpl::GetRawAuthenticationToken( |
| const std::string& endpoint_id) { |
| 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::RegisterBandwidthUpgradeListener( |
| base::WeakPtr<BandwidthUpgradeListener> listener) { |
| CHECK(!bandwidth_upgrade_listener_); |
| bandwidth_upgrade_listener_ = listener; |
| } |
| |
| void NearbyConnectionsManagerImpl::UpgradeBandwidth( |
| const std::string& endpoint_id) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| // The only bandwidth upgrade mediums at this point are WebRTC and WifiLan. |
| if (!base::FeatureList::IsEnabled(features::kNearbySharingWebRtc) && |
| !base::FeatureList::IsEnabled(features::kNearbySharingWifiLan)) { |
| return; |
| } |
| |
| requested_bwu_endpoint_ids_.emplace(endpoint_id); |
| process_reference_->GetNearbyConnections()->InitiateBandwidthUpgrade( |
| service_id_, endpoint_id, |
| base::BindOnce( |
| [](const std::string& endpoint_id, ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Bandwidth upgrade attempted to endpoint " |
| << endpoint_id << "over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| base::UmaHistogramBoolean( |
| "Nearby.Share.Medium.InitiateBandwidthUpgradeResult", |
| status == ConnectionsStatus::kSuccess); |
| }, |
| endpoint_id)); |
| } |
| |
| void NearbyConnectionsManagerImpl::ConnectV3( |
| NearbyPresenceService::PresenceDevice remote_presence_device, |
| DataUsage data_usage, |
| NearbyConnectionCallback callback) { |
| nearby::connections::mojom::NearbyConnections* nearby_connections = |
| GetNearbyConnections(); |
| CHECK(nearby_connections); |
| |
| // TODO(b/287340241): Enable BLE connections as an allowed medium. |
| auto allowed_mediums = MediumSelection::New( |
| /*bluetooth=*/true, |
| /*ble=*/false, ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower), |
| /*wifi_lan=*/ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower)); |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": " << "data_usage=" << data_usage |
| << ", allowed_mediums=" << MediumSelectionToString(*allowed_mediums); |
| |
| mojo::PendingRemote<ConnectionListenerV3> connection_listener_v3; |
| connection_listener_v3s_.Add( |
| this, connection_listener_v3.InitWithNewPipeAndPassReceiver()); |
| |
| pending_outgoing_connections_.emplace(remote_presence_device.GetEndpointId(), |
| std::move(callback)); |
| |
| auto timeout_timer = std::make_unique<base::OneShotTimer>(); |
| timeout_timer->Start( |
| FROM_HERE, kInitiateNearbyConnectionTimeout, |
| base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionTimedOut, |
| weak_ptr_factory_.GetWeakPtr(), |
| remote_presence_device.GetEndpointId())); |
| connect_timeout_timers_.emplace(remote_presence_device.GetEndpointId(), |
| std::move(timeout_timer)); |
| |
| nearby_connections->RequestConnectionV3( |
| service_id_, BuildPresenceMojomFromServiceDevice(remote_presence_device), |
| ConnectionOptions::New(std::move(allowed_mediums), |
| /*bluetooth_mac_address=*/std::nullopt, |
| /*keep_alive_interval_millis=*/std::nullopt, |
| /*keep_alive_timeout_millis=*/std::nullopt), |
| std::move(connection_listener_v3), |
| base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionRequestedV3, |
| weak_ptr_factory_.GetWeakPtr(), remote_presence_device)); |
| } |
| |
| void NearbyConnectionsManagerImpl::DisconnectV3( |
| NearbyPresenceService::PresenceDevice remote_presence_device) { |
| if (!process_reference_) { |
| return; |
| } |
| |
| ash::nearby::presence::mojom::PresenceDevicePtr presence_device_mojom = |
| BuildPresenceMojomFromServiceDevice(remote_presence_device); |
| |
| process_reference_->GetNearbyConnections()->DisconnectFromDeviceV3( |
| service_id_, presence_device_mojom.Clone(), |
| base::BindOnce([](ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Disconnect (V3) from device " |
| << "attempted over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| })); |
| |
| // `OnDisconnected()` is called because if the remote_presence_device hasn't |
| // been connected yet, ConnectionListenerV3 is not notified of the |
| // disconnection event. Directly calling here will ensure the cleanup. |
| OnDisconnected(std::move(presence_device_mojom)); |
| } |
| |
| base::WeakPtr<NearbyConnectionsManager> |
| NearbyConnectionsManagerImpl::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnNearbyProcessStopped( |
| ash::nearby::NearbyProcessManager::NearbyProcessShutdownReason) { |
| CD_LOG(VERBOSE, Feature::NC) << __func__; |
| process_reference_.reset(); |
| Reset(); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnEndpointFound( |
| const std::string& endpoint_id, |
| DiscoveredEndpointInfoPtr info) { |
| if (!discovery_listener_) { |
| CD_LOG(INFO, Feature::NC) << "Ignoring discovered endpoint " |
| << base::HexEncode(info->endpoint_info) |
| << " because we're no longer " |
| "in discovery mode"; |
| return; |
| } |
| |
| auto result = discovered_endpoints_.insert(endpoint_id); |
| if (!result.second) { |
| CD_LOG(INFO, Feature::NC) << "Ignoring discovered endpoint " |
| << base::HexEncode(info->endpoint_info) |
| << " because we've already " |
| "reported this endpoint"; |
| return; |
| } |
| |
| discovery_listener_->OnEndpointDiscovered(endpoint_id, info->endpoint_info); |
| CD_LOG(INFO, Feature::NC) |
| << "Discovered " << base::HexEncode(info->endpoint_info) |
| << " over Nearby Connections"; |
| } |
| |
| void NearbyConnectionsManagerImpl::OnEndpointLost( |
| const std::string& endpoint_id) { |
| if (!discovered_endpoints_.erase(endpoint_id)) { |
| CD_LOG(INFO, Feature::NC) << "Ignoring lost endpoint " << endpoint_id |
| << " because we haven't reported this endpoint"; |
| return; |
| } |
| |
| if (!discovery_listener_) { |
| CD_LOG(INFO, Feature::NC) << "Ignoring lost endpoint " << endpoint_id |
| << " because we're no longer in discovery mode"; |
| return; |
| } |
| |
| discovery_listener_->OnEndpointLost(endpoint_id); |
| CD_LOG(INFO, Feature::NC) |
| << "Endpoint " << endpoint_id << " lost over Nearby Connections"; |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionInitiated( |
| const std::string& endpoint_id, |
| ConnectionInfoPtr info) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| bool is_incoming_connection = info->is_incoming_connection; |
| const std::vector<uint8_t>& endpoint_info = info->endpoint_info; |
| auto result = connection_info_map_.emplace(endpoint_id, std::move(info)); |
| DCHECK(result.second); |
| |
| mojo::PendingRemote<PayloadListener> payload_listener; |
| payload_listeners_.Add(this, |
| payload_listener.InitWithNewPipeAndPassReceiver()); |
| |
| if (is_incoming_connection && incoming_connection_listener_) { |
| incoming_connection_listener_->OnIncomingConnectionInitiated(endpoint_id, |
| endpoint_info); |
| } |
| |
| process_reference_->GetNearbyConnections()->AcceptConnection( |
| service_id_, endpoint_id, std::move(payload_listener), |
| base::BindOnce( |
| [](const std::string& endpoint_id, ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Accept connection attempted to endpoint " |
| << endpoint_id << " over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| }, |
| endpoint_id)); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionAccepted( |
| const std::string& endpoint_id) { |
| 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( |
| endpoint_id, std::make_unique<NearbyConnectionImpl>( |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| DCHECK(result.second); |
| incoming_connection_listener_->OnIncomingConnectionAccepted( |
| endpoint_id, it->second->endpoint_info, result.first->second.get()); |
| } else { |
| auto pending_it = pending_outgoing_connections_.find(endpoint_id); |
| if (pending_it == pending_outgoing_connections_.end()) { |
| Disconnect(endpoint_id); |
| return; |
| } |
| |
| auto result = connections_.emplace( |
| endpoint_id, std::make_unique<NearbyConnectionImpl>( |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| DCHECK(result.second); |
| std::move(pending_it->second) |
| .Run( |
| /*nearby_connection=*/result.first->second.get()); |
| pending_outgoing_connections_.erase(pending_it); |
| connect_timeout_timers_.erase(endpoint_id); |
| } |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionRejected( |
| const std::string& endpoint_id, |
| Status status) { |
| connection_info_map_.erase(endpoint_id); |
| |
| auto it = pending_outgoing_connections_.find(endpoint_id); |
| if (it != pending_outgoing_connections_.end()) { |
| std::move(it->second).Run(/*nearby_connection=*/nullptr); |
| pending_outgoing_connections_.erase(it); |
| connect_timeout_timers_.erase(endpoint_id); |
| } |
| |
| // TODO(crbug/1111458): Support TransferManager. |
| } |
| |
| void NearbyConnectionsManagerImpl::OnDisconnected( |
| const std::string& endpoint_id) { |
| connection_info_map_.erase(endpoint_id); |
| |
| auto it = pending_outgoing_connections_.find(endpoint_id); |
| if (it != pending_outgoing_connections_.end()) { |
| std::move(it->second).Run(nullptr); |
| pending_outgoing_connections_.erase(it); |
| connect_timeout_timers_.erase(endpoint_id); |
| } |
| |
| // TODO(b/326139109): Refactor to add a check for `endpoint_id` to ensure that |
| // it exists in either `pending_outgoing_connections_` or `connections_`. |
| // Otherwise we should `ReportBadMessage()`. |
| |
| // Destroying the NearbyConnectionImpl object may start a chain of callbacks |
| // that can delete this NearbyConnectionsManagerImpl object. This may result |
| // in a crash (see b/303675257). Update the |connections_| map, but wait to |
| // destroy the connection object until after we're done modifying internal |
| // state in OnDisconnected() by letting the connection go out of scope. |
| std::unique_ptr<NearbyConnectionImpl> connection = |
| std::move(connections_[endpoint_id]); |
| connections_.erase(endpoint_id); |
| |
| if (base::Contains(requested_bwu_endpoint_ids_, endpoint_id)) { |
| base::UmaHistogramBoolean( |
| "Nearby.Share.Medium.RequestedBandwidthUpgradeResult", |
| base::Contains(current_upgraded_mediums_, endpoint_id)); |
| } |
| requested_bwu_endpoint_ids_.erase(endpoint_id); |
| on_bandwidth_changed_endpoint_ids_.erase(endpoint_id); |
| current_upgraded_mediums_.erase(endpoint_id); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnBandwidthChanged( |
| const std::string& endpoint_id, |
| Medium medium) { |
| // `OnBandwidthChanged` is always called for the first Medium we connected to. |
| // This is not guaranteed to be a specific Medium, but is usually Bluetooth. |
| // This may or may not be preceded by a call to `UpgradeBandwidth`. It's not |
| // useful to record this first Medium since no Bandwidth Upgrade occurred, so |
| // we ignore it. |
| if (!base::Contains(on_bandwidth_changed_endpoint_ids_, endpoint_id)) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Initial call with medium=" << medium |
| << "; endpoint_id=" << endpoint_id; |
| on_bandwidth_changed_endpoint_ids_.emplace(endpoint_id); |
| } else { |
| CD_LOG(VERBOSE, Feature::NC) << __func__ << ": Changed to medium=" << medium |
| << "; endpoint_id=" << endpoint_id; |
| base::UmaHistogramEnumeration("Nearby.Share.Medium.ChangedToMedium", |
| medium); |
| current_upgraded_mediums_.insert_or_assign(endpoint_id, medium); |
| // Only propagate this event on actual bandwidth upgrades. |
| if (bandwidth_upgrade_listener_) { |
| bandwidth_upgrade_listener_->OnBandwidthUpgrade(endpoint_id, medium); |
| } |
| } |
| // TODO(crbug/1111458): Support TransferManager. |
| } |
| |
| void NearbyConnectionsManagerImpl::OnPayloadReceived( |
| const std::string& endpoint_id, |
| PayloadPtr payload) { |
| auto result = incoming_payloads_.emplace(payload->id, std::move(payload)); |
| DCHECK(result.second); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnPayloadTransferUpdate( |
| const std::string& endpoint_id, |
| PayloadTransferUpdatePtr update) { |
| // TODO(https://crbug.com/1177088): Determine if we should attempt to bind to |
| // process. |
| if (!process_reference_) { |
| return; |
| } |
| |
| // 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. |
| auto listener_it = payload_status_listeners_.find(update->payload_id); |
| if (listener_it != payload_status_listeners_.end()) { |
| base::WeakPtr<PayloadStatusListener> listener = listener_it->second; |
| switch (update->status) { |
| case PayloadStatus::kInProgress: |
| break; |
| case PayloadStatus::kSuccess: |
| case PayloadStatus::kCanceled: |
| case PayloadStatus::kFailure: |
| payload_status_listeners_.erase(update->payload_id); |
| break; |
| } |
| // Note: The listener might be invalidated, for example, if it is shared |
| // with another payload in the same transfer. |
| if (listener) { |
| listener->OnStatusUpdate(std::move(update), |
| GetUpgradedMedium(endpoint_id)); |
| } |
| return; |
| } |
| |
| // If this is an incoming payload that we have not registered for, then we'll |
| // treat it as a control frame (eg. IntroductionFrame) and forward it to the |
| // associated NearbyConnection. |
| auto payload_it = incoming_payloads_.find(update->payload_id); |
| if (payload_it == incoming_payloads_.end()) { |
| return; |
| } |
| |
| if (!payload_it->second->content->is_bytes()) { |
| CD_LOG(WARNING, Feature::NC) |
| << "Received unknown payload of file type. Cancelling."; |
| process_reference_->GetNearbyConnections()->CancelPayload( |
| service_id_, payload_it->first, base::DoNothing()); |
| return; |
| } |
| |
| if (update->status != PayloadStatus::kSuccess) { |
| return; |
| } |
| |
| auto connections_it = connections_.find(endpoint_id); |
| if (connections_it == connections_.end()) { |
| return; |
| } |
| |
| CD_LOG(INFO, Feature::NC) |
| << "Writing incoming byte message to NearbyConnection."; |
| connections_it->second->WriteMessage( |
| payload_it->second->content->get_bytes()->bytes); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionInitiated( |
| PresenceDevicePtr remote_device, |
| InitialConnectionInfoV3Ptr info) { |
| if (!process_reference_) { |
| return; |
| } |
| |
| if (info->authentication_status == |
| nearby::connections::mojom::AuthenticationStatus::kSuccess) { |
| mojo::PendingRemote<PayloadListenerV3> payload_listener; |
| payload_listener_v3s_.Add( |
| this, payload_listener.InitWithNewPipeAndPassReceiver()); |
| |
| process_reference_->GetNearbyConnections()->AcceptConnectionV3( |
| service_id_, std::move(remote_device), std::move(payload_listener), |
| base::BindOnce([](ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Accept connection (V3) attempted to device " |
| << " over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| })); |
| } else { |
| process_reference_->GetNearbyConnections()->RejectConnectionV3( |
| service_id_, std::move(remote_device), |
| base::BindOnce([](ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ << ": Reject connection (V3) attempted to device " |
| << " over Nearby Connections with result: " |
| << ConnectionsStatusToString(status); |
| })); |
| } |
| } |
| |
| void NearbyConnectionsManagerImpl::OnConnectionResult( |
| PresenceDevicePtr remote_device, |
| Status status) { |
| CD_LOG(INFO, Feature::NC) |
| << __func__ << ": OnConnectionResult result=" << status; |
| |
| const std::string& endpoint_id = remote_device->endpoint_id; |
| auto it = pending_outgoing_connections_.find(endpoint_id); |
| |
| if (it == pending_outgoing_connections_.end()) { |
| connection_listener_v3s_.ReportBadMessage( |
| base::StringPrintf("OnConnectionResult() received endpoint_id=%s which " |
| "does not exist in connections V3", |
| endpoint_id.c_str())); |
| return; |
| } |
| |
| if (status == Status::kSuccess) { |
| auto result = connections_v3_.emplace( |
| endpoint_id, std::make_unique<NearbyConnectionImpl>( |
| weak_ptr_factory_.GetWeakPtr(), endpoint_id)); |
| std::move(it->second) |
| .Run( |
| /*nearby_connection=*/result.first->second.get()); |
| } else { |
| std::move(it->second).Run(/*nearby_connection=*/nullptr); |
| } |
| |
| pending_outgoing_connections_.erase(it); |
| connect_timeout_timers_.erase(endpoint_id); |
| } |
| |
| void NearbyConnectionsManagerImpl::OnDisconnected( |
| PresenceDevicePtr remote_device) { |
| const std::string& endpoint_id = remote_device->endpoint_id; |
| |
| auto it = pending_outgoing_connections_.find(endpoint_id); |
| if (it != pending_outgoing_connections_.end()) { |
| std::move(it->second).Run(/*nearby_connection=*/nullptr); |
| pending_outgoing_connections_.erase(it); |
| connect_timeout_timers_.erase(endpoint_id); |
| } else { |
| // Destroying the NearbyConnectionImpl object may start a chain of callbacks |
| // that can delete this NearbyConnectionsManagerImpl object. This may result |
| // in a crash (see b/303675257). Update the |connections_v3_| map, but wait |
| // to destroy the connection object until after we're done modifying |
| // internal state in OnDisconnected() by letting the connection go out of |
| // scope. |
| std::unique_ptr<NearbyConnectionImpl> connection; |
| auto active_connections_it = connections_v3_.find(endpoint_id); |
| |
| // The specified endpoint_id was not in pending connections, and thus must |
| // be in active `connections_v3_`. If not, report a bad message from Nearby. |
| if (active_connections_it == connections_v3_.end()) { |
| connection_listener_v3s_.ReportBadMessage( |
| base::StringPrintf("OnDisconnected() received endpoint_id=%s which " |
| "does not exist in pending connections or " |
| "connections V3", |
| endpoint_id.c_str())); |
| } else { |
| connection = std::move(active_connections_it->second); |
| connections_v3_.erase(active_connections_it); |
| } |
| } |
| |
| // TODO(b/325534442): Emit to V3 version of the metric |
| // RequestedBandwidthUpgradeResult, and updated BWU-related maps. See older |
| // OnDisconnected() method. |
| } |
| |
| nearby::connections::mojom::NearbyConnections* |
| NearbyConnectionsManagerImpl::GetNearbyConnections() { |
| if (!process_reference_) { |
| process_reference_ = process_manager_->GetNearbyProcessReference( |
| base::BindOnce(&NearbyConnectionsManagerImpl::OnNearbyProcessStopped, |
| base::Unretained(this))); |
| |
| if (!process_reference_) { |
| CD_LOG(WARNING, Feature::NC) |
| << __func__ << "Failed to get a reference to the nearby process."; |
| return nullptr; |
| } |
| } |
| |
| nearby::connections::mojom::NearbyConnections* nearby_connections = |
| process_reference_->GetNearbyConnections().get(); |
| |
| if (!nearby_connections) { |
| CD_LOG(WARNING, Feature::NC) |
| << __func__ |
| << "Failed to get a nearby connections from process reference."; |
| } |
| |
| return nearby_connections; |
| } |
| |
| void NearbyConnectionsManagerImpl::Reset() { |
| if (process_reference_) { |
| process_reference_->GetNearbyConnections()->StopAllEndpoints( |
| service_id_, base::BindOnce([](ConnectionsStatus status) { |
| CD_LOG(VERBOSE, Feature::NC) |
| << __func__ |
| << ": Stop all endpoints attempted over Nearby " |
| "Connections with result: " |
| << ConnectionsStatusToString(status); |
| })); |
| } |
| process_reference_.reset(); |
| discovered_endpoints_.clear(); |
| payload_status_listeners_.clear(); |
| ClearIncomingPayloads(); |
| connections_.clear(); |
| connections_v3_.clear(); |
| connection_info_map_.clear(); |
| discovery_listener_ = nullptr; |
| incoming_connection_listener_ = nullptr; |
| endpoint_discovery_listener_.reset(); |
| connect_timeout_timers_.clear(); |
| requested_bwu_endpoint_ids_.clear(); |
| on_bandwidth_changed_endpoint_ids_.clear(); |
| current_upgraded_mediums_.clear(); |
| |
| for (auto& entry : pending_outgoing_connections_) { |
| std::move(entry.second).Run(/*connection=*/nullptr); |
| } |
| |
| pending_outgoing_connections_.clear(); |
| } |
| |
| std::optional<nearby::connections::mojom::Medium> |
| NearbyConnectionsManagerImpl::GetUpgradedMedium( |
| const std::string& endpoint_id) const { |
| const auto it = current_upgraded_mediums_.find(endpoint_id); |
| if (it == current_upgraded_mediums_.end()) { |
| return std::nullopt; |
| } |
| |
| return it->second; |
| } |