chromium/chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.cc

// 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 "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager_impl.h"

#include <string>

#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 "chromeos/ash/components/nearby/common/connections_manager/nearby_connections_manager.h"
#include "chromeos/ash/components/nearby/presence/conversions/nearby_presence_conversions.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 "components/cross_device/nearby/nearby_features.h"
#include "crypto/random.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/network_change_notifier.h"

namespace {

const char kFastAdvertisementServiceUuid[] =
    "0000fef3-0000-1000-8000-00805f9b34fb";
const nearby::connections::mojom::Strategy kStrategy =
    nearby::connections::mojom::Strategy::kP2pPointToPoint;

// Timeout for initiating a connection to a remote device.
constexpr base::TimeDelta kInitiateNearbyConnectionTimeout = base::Seconds(60);

// Whether or not WifiLan is supported for advertising. Support as
// a bandwidth upgrade medium is behind a feature flag.
constexpr bool kIsWifiLanAdvertisingSupported = false;

bool ShouldUseInternet(NearbyConnectionsManager::DataUsage data_usage,
                       NearbyConnectionsManager::PowerLevel power_level) {
  // We won't use internet if the user requested we don't.
  if (data_usage == NearbyConnectionsManager::DataUsage::kOffline) {
    return false;
  }

  // We won't use internet in a low power mode.
  if (power_level == NearbyConnectionsManager::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::NEARBY_INFRA)
        << __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 == NearbyConnectionsManager::DataUsage::kWifiOnly &&
      net::NetworkChangeNotifier::GetConnectionCost() ==
          net::NetworkChangeNotifier::CONNECTION_COST_METERED) {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __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(NearbyConnectionsManager::DataUsage data_usage,
                        NearbyConnectionsManager::PowerLevel power_level) {
  return base::FeatureList::IsEnabled(features::kNearbySharingWebRtc) &&
         ShouldUseInternet(data_usage, power_level);
}

bool ShouldEnableWifiLan(NearbyConnectionsManager::DataUsage data_usage,
                         NearbyConnectionsManager::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 ";
  }
  if (mediums.wifi_direct) {
    ss << "wifidirect ";
  }
  ss << "}";

  return ss.str();
}

}  // 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,
    NearbyConnectionsManager::PowerLevel power_level,
    NearbyConnectionsManager::DataUsage data_usage,
    ConnectionsCallback callback) {
  DCHECK(listener);
  DCHECK(!incoming_connection_listener_);

  raw_ptr<nearby::connections::mojom::NearbyConnections> nearby_connections =
      GetNearbyConnections();
  if (!nearby_connections) {
    std::move(callback).Run(ConnectionsStatus::kError);
    return;
  }

  bool is_high_power =
      power_level == NearbyConnectionsManager::PowerLevel::kHighPower;
  bool use_ble = features::IsNearbyBleV2Enabled() || !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,
                         NearbyConnectionsManager::PowerLevel::kHighPower),
      /*wifi_lan=*/
      ShouldEnableWifiLan(data_usage,
                          NearbyConnectionsManager::PowerLevel::kHighPower) &&
          kIsWifiLanAdvertisingSupported,
      /*wifi_direct=*/
      base::FeatureList::IsEnabled(features::kNearbySharingWifiDirect));
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __func__ << ": " << "is_high_power=" << (is_high_power ? "yes" : "no")
      << "use_ble=" << (use_ble ? "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;

  // We pass in the Fast Advertisement service UUID when we want Nearby
  // Connections to advertise a Fast Advertisement (AKA low-power, contacts
  // mode advertising). For BLE v1, this is always the case. When BLE v2 is
  // enabled, we only pass in the UUID when in low-power mode.
  // TODO (b/327451380): Replace this with a bool in the NC layer.
  std::optional<::device::BluetoothUUID> fast_advertisement_service_uuid =
      features::IsNearbyBleV2Enabled() && is_high_power
          ? std::nullopt
          : std::make_optional(
                device::BluetoothUUID(kFastAdvertisementServiceUuid));

  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=*/
                              fast_advertisement_service_uuid),
      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::InjectBluetoothEndpoint(
    const std::string& service_id,
    const std::string& endpoint_id,
    const std::vector<uint8_t> endpoint_info,
    const std::vector<uint8_t> remote_bluetooth_mac_address,
    ConnectionsCallback callback) {
  nearby::connections::mojom::NearbyConnections* nearby_connections =
      GetNearbyConnections();
  if (!nearby_connections) {
    CD_LOG(ERROR, Feature::NS)
        << __func__ << " Nearby Connections cannot be retrieved.";
    std::move(callback).Run(ConnectionsStatus::kError);
    return;
  }

  if (endpoint_id.length() != 4) {
    CD_LOG(ERROR, Feature::NS)
        << __func__ << " endpoint ID must be length 4. Actual size: "
        << base::NumberToString(endpoint_id.length());
    std::move(callback).Run(ConnectionsStatus::kError);
    return;
  }

  if (endpoint_info.size() == 0 || endpoint_info.size() > 130) {
    CD_LOG(ERROR, Feature::NS)
        << __func__
        << " endpoint info must have size >0 and <131. Actual size: "
        << base::NumberToString(endpoint_info.size());
    std::move(callback).Run(ConnectionsStatus::kError);
    return;
  }

  if (remote_bluetooth_mac_address.size() != 6) {
    CD_LOG(ERROR, Feature::NS)
        << __func__
        << " bluetooth mac address size must be 6 bytes. Actual size: "
        << base::NumberToString(remote_bluetooth_mac_address.size());
    std::move(callback).Run(ConnectionsStatus::kError);
    return;
  }

  nearby_connections->InjectBluetoothEndpoint(
      service_id, endpoint_id, endpoint_info, remote_bluetooth_mac_address,
      std::move(callback));
}

void NearbyConnectionsManagerImpl::StartDiscovery(
    DiscoveryListener* listener,
    NearbyConnectionsManager::DataUsage data_usage,
    ConnectionsCallback callback) {
  DCHECK(listener);
  DCHECK(!discovery_listener_);

  raw_ptr<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,
                         NearbyConnectionsManager::PowerLevel::kHighPower),
      /*wifi_lan=*/
      ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower) &&
          ::features::IsNearbyMdnsEnabled(),
      /*wifi_direct=*/
      base::FeatureList::IsEnabled(features::kNearbySharingWifiDirect));
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __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::NEARBY_INFRA)
            << __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,
    NearbyConnectionsManager::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,
      /*web_rtc=*/ShouldEnableWebRtc(data_usage, PowerLevel::kHighPower),
      /*wifi_lan=*/ShouldEnableWifiLan(data_usage, PowerLevel::kHighPower),
      /*wifi_direct=*/
      base::FeatureList::IsEnabled(features::kNearbySharingWifiDirect));
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __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::NEARBY_INFRA)
      << "Failed to connect to the remote shareTarget: Timed out.";
  Disconnect(endpoint_id);
}

void NearbyConnectionsManagerImpl::OnConnectionTimedOutV3(
    const std::string& endpoint_id) {
  if (base::Contains(endpoint_id_to_presence_device_map_, endpoint_id)) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << __func__
        << "(V3) Failed to connect to the remote shareTarget: Timed out.";
    DisconnectV3(*endpoint_id_to_presence_device_map_.at(endpoint_id).get());
  } else {
    CD_LOG(WARNING, Feature::NEARBY_INFRA)
        << __func__ << "Timed out, but no endpoint_id in PresenceDevice map.";
  }
}

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::NEARBY_INFRA)
        << "Failed to connect to the remote shareTarget: "
        << ConnectionsStatusToString(status);
    Disconnect(endpoint_id);
    return;
  }

  // TODO(crbug/1111458): Support TransferManager.
}

void NearbyConnectionsManagerImpl::OnConnectionRequestedV3(
    nearby::presence::PresenceDevice remote_device,
    ConnectionsStatus status) {
  if (status != ConnectionsStatus::kSuccess) {
    CD_LOG(ERROR, Feature::NEARBY_INFRA)
        << "Failed to connect (v3) to remote device with result: "
        << ConnectionsStatusToString(status);
    DisconnectV3(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::NEARBY_INFRA)
                << __func__ << ": Disconnecting from endpoint " << endpoint_id
                << " attempted over Nearby Connections with result: "
                << ConnectionsStatusToString(status);
          },
          endpoint_id));

  OnDisconnected(endpoint_id);
  CD_LOG(INFO, Feature::NEARBY_INFRA) << "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::NEARBY_INFRA)
                << __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::NEARBY_INFRA)
                << __func__ << ": Cancelling payload to id " << payload_id
                << " attempted over Nearby Connections with result: "
                << ConnectionsStatusToString(status);
          },
          payload_id));
  CD_LOG(INFO, Feature::NEARBY_INFRA) << "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();
}

void NearbyConnectionsManagerImpl::ClearIncomingPayloadWithId(
    int64_t payload_id) {
  auto payload_found = incoming_payloads_.find(payload_id);
  if (payload_found != incoming_payloads_.end()) {
    std::vector<PayloadPtr> payload_found_container;
    payload_found_container.push_back(std::move(payload_found->second));
    file_handler_.ReleaseFilePayloads(std::move(payload_found_container));
    incoming_payloads_.erase(payload_found);
  }
  payload_status_listeners_.erase(payload_id);
}

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, WifiLan, and
  // WifiDirect.
  if (!base::FeatureList::IsEnabled(features::kNearbySharingWebRtc) &&
      !base::FeatureList::IsEnabled(features::kNearbySharingWifiLan) &&
      !base::FeatureList::IsEnabled(features::kNearbySharingWifiDirect)) {
    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::NEARBY_INFRA)
                << __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(
    nearby::presence::PresenceDevice remote_presence_device,
    NearbyConnectionsManager::DataUsage data_usage,
    NearbyConnectionCallback callback) {
  raw_ptr<nearby::connections::mojom::NearbyConnections> nearby_connections =
      GetNearbyConnections();
  CHECK(nearby_connections);

  const std::string& endpoint_id = remote_presence_device.GetEndpointId();
  endpoint_id_to_presence_device_map_.emplace(
      endpoint_id, std::make_unique<nearby::presence::PresenceDevice>(
                       std::move(remote_presence_device)));

  // 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),
      /*wifi_direct=*/
      base::FeatureList::IsEnabled(features::kNearbySharingWifiDirect));
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
      << __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(endpoint_id, std::move(callback));

  auto timeout_timer = std::make_unique<base::OneShotTimer>();
  timeout_timer->Start(
      FROM_HERE, kInitiateNearbyConnectionTimeout,
      base::BindOnce(&NearbyConnectionsManagerImpl::OnConnectionTimedOutV3,
                     weak_ptr_factory_.GetWeakPtr(), endpoint_id));
  connect_timeout_timers_v3_.emplace(endpoint_id, std::move(timeout_timer));

  endpoint_id_to_connect_v3_start_time_.emplace(endpoint_id,
                                                base::TimeTicks::Now());

  auto presence_device =
      *endpoint_id_to_presence_device_map_.at(endpoint_id).get();

  nearby_connections->RequestConnectionV3(
      service_id_,
      ash::nearby::presence::BuildPresenceMojomDevice(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(), presence_device));
}

void NearbyConnectionsManagerImpl::DisconnectV3(
    nearby::presence::PresenceDevice remote_presence_device) {
  if (!process_reference_) {
    return;
  }

  process_reference_->GetNearbyConnections()->DisconnectFromDeviceV3(
      service_id_,
      ash::nearby::presence::BuildPresenceMojomDevice(remote_presence_device),
      base::BindOnce([](ConnectionsStatus status) {
        CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
            << __func__ << ": Disconnect (V3) from device "
            << "attempted over Nearby Connections with result: "
            << ConnectionsStatusToString(status);
      }));

  // `OnDisconnectedV3()` 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.
  OnDisconnectedV3(remote_presence_device.GetEndpointId());
}

base::WeakPtr<NearbyConnectionsManager>
NearbyConnectionsManagerImpl::GetWeakPtr() {
  return weak_ptr_factory_.GetWeakPtr();
}

void NearbyConnectionsManagerImpl::OnNearbyProcessStopped(
    ash::nearby::NearbyProcessManager::NearbyProcessShutdownReason) {
  CD_LOG(VERBOSE, Feature::NEARBY_INFRA) << __func__;
  process_reference_.reset();
  Reset();
}

void NearbyConnectionsManagerImpl::OnEndpointFound(
    const std::string& endpoint_id,
    DiscoveredEndpointInfoPtr info) {
  if (!discovery_listener_) {
    CD_LOG(INFO, Feature::NEARBY_INFRA) << "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::NEARBY_INFRA) << "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::NEARBY_INFRA)
      << "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::NEARBY_INFRA)
        << "Ignoring lost endpoint " << endpoint_id
        << " because we haven't reported this endpoint";
    return;
  }

  if (!discovery_listener_) {
    CD_LOG(INFO, Feature::NEARBY_INFRA)
        << "Ignoring lost endpoint " << endpoint_id
        << " because we're no longer in discovery mode";
    return;
  }

  discovery_listener_->OnEndpointLost(endpoint_id);
  CD_LOG(INFO, Feature::NEARBY_INFRA)
      << "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::NEARBY_INFRA)
                << __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::NEARBY_INFRA)
        << __func__ << ": Initial call with medium=" << medium
        << "; endpoint_id=" << endpoint_id;
    if (bandwidth_upgrade_listener_) {
      bandwidth_upgrade_listener_->OnInitialMedium(endpoint_id, medium);
    }
    on_bandwidth_changed_endpoint_ids_.emplace(endpoint_id);
  } else {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __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::NEARBY_INFRA)
        << "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(VERBOSE, Feature::NEARBY_INFRA)
      << "Writing incoming byte message to NearbyConnection.";
  connections_it->second->WriteMessage(
      payload_it->second->content->get_bytes()->bytes);
}

void NearbyConnectionsManagerImpl::OnConnectionInitiatedV3(
    const std::string& endpoint_id,
    InitialConnectionInfoV3Ptr info) {
  if (!process_reference_) {
    return;
  }

  if (!base::Contains(endpoint_id_to_presence_device_map_, endpoint_id)) {
    CD_LOG(WARNING, Feature::NEARBY_INFRA)
        << __func__ << "Received endpoint_id for device no longer in map.";
    return;
  }

  auto presence_device =
      *endpoint_id_to_presence_device_map_.at(endpoint_id).get();

  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_,
        ash::nearby::presence::BuildPresenceMojomDevice(presence_device),
        std::move(payload_listener),
        base::BindOnce([](ConnectionsStatus status) {
          CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
              << __func__ << ": Accept connection (V3) attempted to device "
              << " over Nearby Connections with result: "
              << ConnectionsStatusToString(status);
        }));
  } else {
    process_reference_->GetNearbyConnections()->RejectConnectionV3(
        service_id_,
        ash::nearby::presence::BuildPresenceMojomDevice(presence_device),
        base::BindOnce([](ConnectionsStatus status) {
          CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
              << __func__ << ": Reject connection (V3) attempted to device "
              << " over Nearby Connections with result: "
              << ConnectionsStatusToString(status);
        }));
  }
}

void NearbyConnectionsManagerImpl::OnConnectionResultV3(
    const std::string& endpoint_id,
    Status status) {
  CD_LOG(INFO, Feature::NEARBY_INFRA) << __func__ << ": result=" << status;

  auto it = pending_outgoing_connections_.find(endpoint_id);
  if (it == pending_outgoing_connections_.end() ||
      !base::Contains(endpoint_id_to_connect_v3_start_time_, endpoint_id)) {
    connection_listener_v3s_.ReportBadMessage(base::StringPrintf(
        "OnConnectionResultV3() 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());

    base::UmaHistogramTimes(
        "Nearby.Connections.V3.ConnectionResult.Success.Latency",
        base::TimeTicks::Now() -
            endpoint_id_to_connect_v3_start_time_.at(endpoint_id));
  } else {
    std::move(it->second).Run(/*nearby_connection=*/nullptr);
  }

  base::UmaHistogramEnumeration("Nearby.Connections.V3.Connection.Result",
                                status);
  pending_outgoing_connections_.erase(it);
  connect_timeout_timers_v3_.erase(endpoint_id);
  endpoint_id_to_connect_v3_start_time_.erase(endpoint_id);
}

void NearbyConnectionsManagerImpl::OnDisconnectedV3(
    const std::string& endpoint_id) {
  endpoint_id_to_presence_device_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_v3_.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("OnDisconnectedV3() 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.

  on_bandwidth_changed_endpoint_ids_.erase(endpoint_id);
  current_upgraded_mediums_v3_.erase(endpoint_id);
}

void NearbyConnectionsManagerImpl::OnBandwidthChangedV3(
    const std::string& endpoint_id,
    BandwidthInfoPtr bandwidth_info) {
  // `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_v3_, endpoint_id)) {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __func__
        << ": (V3) Initial call with medium=" << bandwidth_info->medium
        << " , quality=" << bandwidth_info->quality
        << "; endpoint_id=" << endpoint_id;
    on_bandwidth_changed_endpoint_ids_v3_.emplace(endpoint_id);
  } else {
    CD_LOG(VERBOSE, Feature::NEARBY_INFRA)
        << __func__ << ": (V3) Changed to medium=" << bandwidth_info->medium
        << " , quality=" << bandwidth_info->quality
        << "; endpoint_id=" << endpoint_id;
    base::UmaHistogramEnumeration(
        "Nearby.Connections.V3.Medium.ChangedToMedium", bandwidth_info->medium);
    current_upgraded_mediums_v3_.insert_or_assign(endpoint_id,
                                                  bandwidth_info->medium);

    if (base::Contains(endpoint_id_to_presence_device_map_, endpoint_id) &&
        bandwidth_upgrade_listener_) {
      // TODO(b/337049943): Determine whether to pass back the entire
      // `PresenceDevice` or just the `endpoint_id`.
      bandwidth_upgrade_listener_->OnBandwidthUpgradeV3(
          *endpoint_id_to_presence_device_map_.at(endpoint_id).get(),
          bandwidth_info->medium);
    }
  }
}

void NearbyConnectionsManagerImpl::OnPayloadReceivedV3(
    const std::string& endpoint_id,
    PayloadPtr payload) {
  if (!base::Contains(endpoint_id_to_presence_device_map_, endpoint_id)) {
    CD_LOG(WARNING, Feature::NEARBY_INFRA)
        << __func__ << "Received endpoint_id for device not in map.";
    return;
  }

  incoming_payloads_.emplace(payload->id, std::move(payload));
}

void NearbyConnectionsManagerImpl::OnPayloadTransferUpdateV3(
    const std::string& endpoint_id,
    PayloadTransferUpdatePtr update) {
  if (!base::Contains(endpoint_id_to_presence_device_map_, endpoint_id)) {
    CD_LOG(WARNING, Feature::NEARBY_INFRA)
        << __func__ << "Received endpoint_id for device not in map.";
    return;
  }

  // The implementation of the current v3::PayloadStatusListener operates in the
  // same way as the V1 variant so we can leverage `OnPayloadTransferUpdate()`.
  // This may change in the future.
  OnPayloadTransferUpdate(endpoint_id, std::move(update));
}

raw_ptr<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::NEARBY_INFRA)
          << __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::NEARBY_INFRA)
        << __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::NEARBY_INFRA)
              << __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();
  connect_timeout_timers_v3_.clear();
  requested_bwu_endpoint_ids_.clear();
  on_bandwidth_changed_endpoint_ids_.clear();
  on_bandwidth_changed_endpoint_ids_v3_.clear();
  current_upgraded_mediums_.clear();
  current_upgraded_mediums_v3_.clear();
  endpoint_id_to_connect_v3_start_time_.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 it->second;
  }

  const auto it_v3 = current_upgraded_mediums_v3_.find(endpoint_id);
  if (it_v3 != current_upgraded_mediums_v3_.end()) {
    return it_v3->second;
  }

  return std::nullopt;
}