chromium/chromeos/ash/components/network/cellular_policy_handler.cc

// Copyright 2021 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/network/cellular_policy_handler.h"

#include <optional>

#include "base/containers/contains.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/value_iterators.h"
#include "chromeos/ash/components/dbus/hermes/hermes_euicc_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_manager_client.h"
#include "chromeos/ash/components/dbus/hermes/hermes_profile_client.h"
#include "chromeos/ash/components/network/cellular_esim_installer.h"
#include "chromeos/ash/components/network/cellular_esim_profile_handler.h"
#include "chromeos/ash/components/network/cellular_esim_profile_waiter.h"
#include "chromeos/ash/components/network/cellular_utils.h"
#include "chromeos/ash/components/network/managed_cellular_pref_handler.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/metrics/cellular_network_metrics_logger.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_profile_handler.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/policy_util.h"
#include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

using ash::cellular_setup::mojom::ProfileInstallMethod;

namespace ash {

namespace {

constexpr int kInstallRetryLimit = 3;
constexpr base::TimeDelta kInstallRetryDelay = base::Days(1);

// The timeout that is provided when waiting for the properties of discovered
// pending profiles to be set before choosing a profile to install.
constexpr base::TimeDelta kCellularESimProfileWaiterDelay = base::Seconds(30);

constexpr net::BackoffEntry::Policy kRetryBackoffPolicy = {
    0,               // Number of initial errors to ignore.
    5 * 60 * 1000,   // Initial delay of 5 minutes in ms.
    2.0,             // Factor by which the waiting time will be multiplied.
    0,               // Fuzzing percentage.
    60 * 60 * 1000,  // Maximum delay of 1 hour in ms.
    -1,              // Never discard the entry.
    true,            // Use initial delay.
};

// Timeout waiting for EUICC to become available in Hermes.
constexpr base::TimeDelta kEuiccWaitTime = base::Minutes(3);
// Timeout waiting for cellular device to become available.
constexpr base::TimeDelta kCellularDeviceWaitTime = base::Seconds(30);

// Returns the activation code for the first eSIM profile whose properties are
// available and whose state is pending, if any.
std::optional<std::string> GetFirstActivationCode(
    const dbus::ObjectPath& euicc_path,
    const std::vector<dbus::ObjectPath>& profile_paths) {
  HermesEuiccClient::Properties* euicc_properties =
      HermesEuiccClient::Get()->GetProperties(euicc_path);
  DCHECK(euicc_properties);

  for (const auto& profile_path : profile_paths) {
    HermesProfileClient::Properties* profile_properties =
        HermesProfileClient::Get()->GetProperties(profile_path);
    if (!profile_properties) {
      NET_LOG(ERROR)
          << "Failed to get profile properties for available profile";
      continue;
    }
    if (profile_properties->state().value() !=
        hermes::profile::State::kPending) {
      NET_LOG(ERROR) << "Expected available profile to have state "
                     << hermes::profile::State::kPending << ", has "
                     << profile_properties->state().value();
      continue;
    }
    const std::string& activation_code =
        profile_properties->activation_code().value();
    if (activation_code.empty()) {
      NET_LOG(ERROR) << "Expected available profile to have an activation code";
      continue;
    }
    return activation_code;
  }
  return std::nullopt;
}

}  // namespace

CellularPolicyHandler::InstallPolicyESimRequest::InstallPolicyESimRequest(
    policy_util::SmdxActivationCode activation_code,
    const base::Value::Dict& onc_config)
    : activation_code(std::move(activation_code)),
      onc_config(onc_config.Clone()),
      retry_backoff(&kRetryBackoffPolicy) {}

CellularPolicyHandler::InstallPolicyESimRequest::~InstallPolicyESimRequest() =
    default;

CellularPolicyHandler::CellularPolicyHandler() = default;

CellularPolicyHandler::~CellularPolicyHandler() {
  OnShuttingDown();
}

void CellularPolicyHandler::Init(
    CellularESimProfileHandler* cellular_esim_profile_handler,
    CellularESimInstaller* cellular_esim_installer,
    CellularInhibitor* cellular_inhibitor,
    NetworkProfileHandler* network_profile_handler,
    NetworkStateHandler* network_state_handler,
    ManagedCellularPrefHandler* managed_cellular_pref_handler,
    ManagedNetworkConfigurationHandler* managed_network_configuration_handler) {
  cellular_esim_profile_handler_ = cellular_esim_profile_handler;
  cellular_esim_installer_ = cellular_esim_installer;
  cellular_inhibitor_ = cellular_inhibitor;
  network_profile_handler_ = network_profile_handler;
  network_state_handler_ = network_state_handler;
  managed_cellular_pref_handler_ = managed_cellular_pref_handler;
  managed_network_configuration_handler_ =
      managed_network_configuration_handler;

  hermes_observation_.Observe(HermesManagerClient::Get());
  cellular_esim_profile_handler_observation_.Observe(
      cellular_esim_profile_handler);
  network_state_handler_observer_.Observe(network_state_handler_.get());
}

void CellularPolicyHandler::InstallESim(const base::Value::Dict& onc_config) {
  std::optional<policy_util::SmdxActivationCode> activation_code =
      policy_util::GetSmdxActivationCodeFromONC(onc_config);

  if (!activation_code.has_value()) {
    return;
  }

  NET_LOG(EVENT) << "Queueing a policy eSIM profile installation request with "
                 << "activation code found in the provided ONC configuration: "
                 << activation_code->ToString();

  PushRequestAndProcess(std::make_unique<InstallPolicyESimRequest>(
      std::move(activation_code.value()), onc_config));
}

void CellularPolicyHandler::OnAvailableEuiccListChanged() {
  ResumeInstallIfNeeded();
}

void CellularPolicyHandler::OnESimProfileListUpdated() {
  ResumeInstallIfNeeded();
}

void CellularPolicyHandler::DeviceListChanged() {
  ResumeInstallIfNeeded();
}

void CellularPolicyHandler::OnShuttingDown() {
  if (!network_state_handler_) {
    return;
  }
  network_state_handler_observer_.Reset();
  network_state_handler_ = nullptr;
}

void CellularPolicyHandler::ResumeInstallIfNeeded() {
  if (!is_installing_ || !wait_timer_.IsRunning()) {
    return;
  }
  wait_timer_.Stop();
  AttemptInstallESim();
}

void CellularPolicyHandler::ProcessRequests() {
  if (remaining_install_requests_.empty()) {
    need_refresh_profile_list_ = true;
    return;
  }

  // Another install request is already underway; wait until it has completed
  // before starting a new request.
  if (is_installing_)
    return;

  is_installing_ = true;
  NET_LOG(EVENT) << "Installing policy eSIM profile: "
                 << GetCurrentActivationCode().ToString();
  AttemptInstallESim();
}

void CellularPolicyHandler::ScheduleRetryAndProcessRequests(
    std::unique_ptr<InstallPolicyESimRequest> request,
    InstallRetryReason reason) {
  if (reason != InstallRetryReason::kInternalError &&
      request->retry_backoff.failure_count() >= kInstallRetryLimit) {
    NET_LOG(ERROR) << "Failed to install policy eSIM profile: "
                   << request->activation_code.ToErrorString();
    ProcessRequests();
    return;
  }

  request->retry_backoff.InformOfRequest(/*succeeded=*/false);

  // Force a delay of |kInstallRetryDelay| when we fail for any reason other
  // than an internal failure, e.g. failure to inhibit, to reduce frequent
  // retries due to errors that are unlikely to be resolved quickly, e.g. an
  // invalid activation code.
  if (reason == InstallRetryReason::kOther) {
    request->retry_backoff.SetCustomReleaseTime(base::TimeTicks::Now() +
                                                kInstallRetryDelay);
  }

  const base::TimeDelta retry_delay =
      request->retry_backoff.GetTimeUntilRelease();

  NET_LOG(ERROR) << "Failed to install policy eSIM profile. Retrying in "
                 << retry_delay << ": "
                 << request->activation_code.ToErrorString();

  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&CellularPolicyHandler::PushRequestAndProcess,
                     weak_ptr_factory_.GetWeakPtr(), std::move(request)),
      retry_delay);

  ProcessRequests();
}

void CellularPolicyHandler::PushRequestAndProcess(
    std::unique_ptr<InstallPolicyESimRequest> request) {
  remaining_install_requests_.push_back(std::move(request));
  ProcessRequests();
}

void CellularPolicyHandler::PopRequest() {
  remaining_install_requests_.pop_front();
  is_installing_ = false;
  if (remaining_install_requests_.empty()) {
    const NetworkProfile* profile =
        cellular_utils::GetCellularProfile(network_profile_handler_);
    DCHECK(profile);

    managed_network_configuration_handler_->OnCellularPoliciesApplied(*profile);
  }
}

void CellularPolicyHandler::PopAndProcessRequests() {
  PopRequest();
  ProcessRequests();
}

void CellularPolicyHandler::AttemptInstallESim() {
  DCHECK(is_installing_);

  const DeviceState* cellular_device =
      network_state_handler_->GetDeviceStateByType(
          NetworkTypePattern::Cellular());
  if (!cellular_device) {
    // Cellular device may not be ready. Wait for DeviceListChanged notification
    // before continuing with installation.
    NET_LOG(EVENT)
        << "Waiting for the cellular device to become available to install "
        << "policy eSIM profile: " << GetCurrentActivationCode().ToString();
    wait_timer_.Start(FROM_HERE, kCellularDeviceWaitTime,
                      base::BindOnce(&CellularPolicyHandler::OnWaitTimeout,
                                     weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  std::optional<dbus::ObjectPath> euicc_path =
      cellular_utils::GetCurrentEuiccPath();
  if (!euicc_path) {
    // Hermes may not be ready and available EUICC list is empty. Wait for
    // AvailableEuiccListChanged notification to continue with installation.
    NET_LOG(EVENT) << "Waiting for EUICC to be found to install policy eSIM "
                   << "profile: " << GetCurrentActivationCode().ToString();
    wait_timer_.Start(FROM_HERE, kEuiccWaitTime,
                      base::BindOnce(&CellularPolicyHandler::OnWaitTimeout,
                                     weak_ptr_factory_.GetWeakPtr()));
    return;
  }

  base::Value::Dict new_shill_properties = GetNewShillProperties();
  // If iccid is found in policy onc, the installation will be skipped because
  // it indicates that the eSIM profile has already been installed before using
  // the same SM-DP+ or SM-DS.
  const std::optional<std::string> iccid = GetIccidFromPolicyONC();
  if (iccid) {
    const std::optional<dbus::ObjectPath> profile_path =
        FindExistingMatchingESimProfile(*iccid);
    if (profile_path) {
      NET_LOG(EVENT) << "Found an existing installed profile that matches the "
                     << "policy eSIM installation request. Configuring a Shill "
                     << "service for the profile: "
                     << GetCurrentActivationCode().ToString();
      cellular_esim_installer_->ConfigureESimService(
          std::move(new_shill_properties), *euicc_path, *profile_path,
          base::BindOnce(&CellularPolicyHandler::OnConfigureESimService,
                         weak_ptr_factory_.GetWeakPtr()));
      return;
    }

    NET_LOG(EVENT) << "Skip installation because iccid is found in the policy"
                   << " ONC, this indicates that the eSIM profile has already"
                   << " been installed.";
    PopAndProcessRequests();
    return;
  }

  if (!HasNonCellularInternetConnectivity()) {
    NET_LOG(ERROR)
        << "Failed to install the policy eSIM profile due to missing a "
        << "non-cellular internet connection: "
        << GetCurrentActivationCode().ToErrorString();
    auto current_request = std::move(remaining_install_requests_.front());
    PopRequest();
    ScheduleRetryAndProcessRequests(
        std::move(current_request),
        InstallRetryReason::kMissingNonCellularConnectivity);
    return;
  }

  if (need_refresh_profile_list_) {
    // Profile list for current EUICC may not have been refreshed, so explicitly
    // refresh profile list before processing installation requests.
    cellular_esim_profile_handler_->RefreshProfileListAndRestoreSlot(
        *euicc_path,
        base::BindOnce(&CellularPolicyHandler::OnRefreshProfileList,
                       weak_ptr_factory_.GetWeakPtr(), *euicc_path,
                       std::move(new_shill_properties)));
    return;
  }

  PerformInstallESim(*euicc_path, std::move(new_shill_properties));
}

void CellularPolicyHandler::PerformInstallESim(
    const dbus::ObjectPath& euicc_path,
    base::Value::Dict new_shill_properties) {
  NET_LOG(EVENT) << "Installing policy eSIM profile ("
                 << GetCurrentActivationCode().ToString() << ") and inhibiting "
                 << "cellular device to request available profiles for SM-DX "
                    "activation code.";

  // Confirmation codes are not required when installing policy eSIM profiles.
  cellular_inhibitor_->InhibitCellularScanning(
      CellularInhibitor::InhibitReason::kRequestingAvailableProfiles,
      base::BindOnce(&CellularPolicyHandler::OnInhibitedForRefreshSmdxProfiles,
                     weak_ptr_factory_.GetWeakPtr(), euicc_path,
                     std::move(new_shill_properties)));
}

void CellularPolicyHandler::OnRefreshProfileList(
    const dbus::ObjectPath& euicc_path,
    base::Value::Dict new_shill_properties,
    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
  if (!inhibit_lock) {
    NET_LOG(ERROR) << "Failed to refresh the profile list due to an inhibit "
                   << "error, path: " << euicc_path.value();
  } else {
    need_refresh_profile_list_ = false;
    // Reset the |inhibit_lock| so that the device will no longer be inhibited
    // with |kRefreshingProfileList| and can become inhibited with the correct
    // reason e.g., |kInstallingProfile|.
    inhibit_lock.reset();
  }

  PerformInstallESim(euicc_path, std::move(new_shill_properties));
}

void CellularPolicyHandler::OnConfigureESimService(
    std::optional<dbus::ObjectPath> service_path) {
  DCHECK(is_installing_);

  auto current_request = std::move(remaining_install_requests_.front());
  PopRequest();
  if (!service_path) {
    ScheduleRetryAndProcessRequests(std::move(current_request),
                                    InstallRetryReason::kOther);
    return;
  }

  NET_LOG(EVENT) << "Successfully configured a Shill service for the existing "
                 << "profile: " << current_request->activation_code.ToString();

  current_request->retry_backoff.InformOfRequest(/*succeeded=*/true);
  const std::string* iccid =
      policy_util::GetIccidFromONC(current_request->onc_config);
  DCHECK(iccid);

  const std::string* name =
      current_request->onc_config.FindString(::onc::network_config::kName);
  DCHECK(name);
  managed_cellular_pref_handler_->AddESimMetadata(
      *iccid, *name, current_request->activation_code);
  ProcessRequests();
}

void CellularPolicyHandler::OnInhibitedForRefreshSmdxProfiles(
    const dbus::ObjectPath& euicc_path,
    base::Value::Dict new_shill_properties,
    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock) {
  // The cellular device must be inhibited before refreshing SM-DX profiles. If
  // we fail to receive a lock consider this installation attempt a failure.
  if (!inhibit_lock) {
    NET_LOG(ERROR)
        << "Failed to inhibit cellular for refreshing SM-DX profiles";
    auto current_request = std::move(remaining_install_requests_.front());

    if (current_request->activation_code.type() ==
        policy_util::SmdxActivationCode::Type::SMDS) {
      CellularNetworkMetricsLogger::LogSmdsScanResult(
          current_request->activation_code.value(),
          /*result=*/std::nullopt);
    }
    PopRequest();
    ScheduleRetryAndProcessRequests(std::move(current_request),
                                    InstallRetryReason::kInternalError);
    return;
  }

  HermesEuiccClient::Get()->RefreshSmdxProfiles(
      euicc_path, GetCurrentActivationCode().value(),
      /*restore_slot=*/true,
      base::BindOnce(&CellularPolicyHandler::OnRefreshSmdxProfiles,
                     weak_ptr_factory_.GetWeakPtr(), euicc_path,
                     std::move(new_shill_properties), std::move(inhibit_lock),
                     base::TimeTicks::Now()));
}

void CellularPolicyHandler::OnRefreshSmdxProfiles(
    const dbus::ObjectPath& euicc_path,
    base::Value::Dict new_shill_properties,
    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
    base::TimeTicks start_time,
    HermesResponseStatus status,
    const std::vector<dbus::ObjectPath>& profile_paths) {
  DCHECK(inhibit_lock);

  auto& current_request = remaining_install_requests_.front();

  const bool is_smds =
      remaining_install_requests_.front()->activation_code.type() ==
      policy_util::SmdxActivationCode::Type::SMDS;
  if (is_smds) {
    CellularNetworkMetricsLogger::LogSmdsScanResult(
        current_request->activation_code.value(), status);
    CellularNetworkMetricsLogger::LogSmdsScanProfileCount(
        profile_paths.size(),
        CellularNetworkMetricsLogger::SmdsScanMethod::kViaPolicy);
    CellularNetworkMetricsLogger::LogSmdsScanDuration(
        base::TimeTicks::Now() - start_time,
        status == HermesResponseStatus::kSuccess,
        current_request->activation_code.ToString());
  }

  std::unique_ptr<CellularESimProfileWaiter> waiter =
      std::make_unique<CellularESimProfileWaiter>();
  for (const auto& profile_path : profile_paths) {
    waiter->RequirePendingProfile(profile_path);
  }

  auto on_success =
      base::BindOnce(&CellularPolicyHandler::CompleteRefreshSmdxProfiles,
                     weak_ptr_factory_.GetWeakPtr(), euicc_path,
                     std::move(new_shill_properties), std::move(inhibit_lock),
                     status, profile_paths);
  auto on_shutdown =
      base::BindOnce(&CellularPolicyHandler::PopAndProcessRequests,
                     weak_ptr_factory_.GetWeakPtr());

  waiter->Wait(std::move(on_success), std::move(on_shutdown));

  if (waiter->waiting()) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(
            [](std::unique_ptr<CellularESimProfileWaiter> waiter) {
              waiter.reset();
            },
            std::move(waiter)),
        kCellularESimProfileWaiterDelay);
  }
}

void CellularPolicyHandler::CompleteRefreshSmdxProfiles(
    const dbus::ObjectPath& euicc_path,
    base::Value::Dict new_shill_properties,
    std::unique_ptr<CellularInhibitor::InhibitLock> inhibit_lock,
    HermesResponseStatus status,
    const std::vector<dbus::ObjectPath>& profile_paths) {
  DCHECK(inhibit_lock);

  auto& current_request = remaining_install_requests_.front();
  const bool is_smds = current_request->activation_code.type() ==
                       policy_util::SmdxActivationCode::Type::SMDS;

  // Scanning SM-DP+ or SM-DS servers will return both a result and available
  // profiles, with the number of profiles that can be returned dependent on
  // which type of server we are scanning. An error being returned indicates
  // there was an issue when performing the scan, but since it does not
  // invalidate the returned profile(s) we simply log the error, capture the
  // error in our metrics, and continue.
  NET_LOG(EVENT) << "HermesEuiccClient::RefreshSmdxProfiles returned with "
                 << "result code " << status << " when called for policy eSIM "
                 << "profile: " << current_request->activation_code.ToString();

  // The activation code will already be known in the SM-DP+ case, but for the
  // sake of using the same flow both both SM-DP+ and SM-DS we choose an
  // activation code from the results in both cases.
  const std::optional<std::string> activation_code =
      GetFirstActivationCode(euicc_path, profile_paths);

  if (!activation_code.has_value()) {
    CellularNetworkMetricsLogger::LogESimPolicyInstallNoAvailableProfiles(
        is_smds
            ? CellularNetworkMetricsLogger::ESimPolicyInstallMethod::kViaSmds
            : CellularNetworkMetricsLogger::ESimPolicyInstallMethod::kViaSmdp);

    NET_LOG(ERROR) << "Failed to find an available profile that matches the "
                   << "activation code provided by policy: "
                   << current_request->activation_code.ToString();
    auto failed_request = std::move(remaining_install_requests_.front());
    PopRequest();
    // Do not retry if there are no profiles available.
    if (status == HermesResponseStatus::kSuccess) {
      ProcessRequests();
    } else {
      ScheduleRetryAndProcessRequests(
          std::move(failed_request), HermesResponseStatusToRetryReason(status));
    }
    return;
  }

  const bool is_initial_install =
      current_request->retry_backoff.failure_count() == 0;
  if (is_initial_install) {
    CellularNetworkMetricsLogger::LogESimPolicyInstallMethod(
        is_smds
            ? CellularNetworkMetricsLogger::ESimPolicyInstallMethod::kViaSmds
            : CellularNetworkMetricsLogger::ESimPolicyInstallMethod::kViaSmdp);
  }

  // Confirmation codes are not required when installing policy eSIM profiles
  // using an activation code.
  cellular_esim_installer_->InstallProfileFromActivationCode(
      activation_code.value(),
      /*confirmation_code=*/std::string(), euicc_path,
      std::move(new_shill_properties),
      base::BindOnce(
          &CellularPolicyHandler::OnESimProfileInstallAttemptComplete,
          weak_ptr_factory_.GetWeakPtr()),
      is_initial_install,
      is_smds ? ProfileInstallMethod::kViaSmds
              : ProfileInstallMethod::kViaActivationCodeAfterSmds);
}

void CellularPolicyHandler::OnESimProfileInstallAttemptComplete(
    HermesResponseStatus status,
    std::optional<dbus::ObjectPath> profile_path,
    std::optional<std::string> service_path) {
  DCHECK(is_installing_);

  auto current_request = std::move(remaining_install_requests_.front());
  PopRequest();

  const bool has_error = status != HermesResponseStatus::kSuccess;
  const bool was_installed = profile_path.has_value();

  if (has_error && !was_installed) {
    if (!base::Contains(kHermesUserErrorCodes, status)) {
      NET_LOG(ERROR)
          << "Failed to install the policy eSIM profile due to a non-user "
          << "error: " << status << ". Scheduling another attempt: "
          << current_request->activation_code.ToString();
      ScheduleRetryAndProcessRequests(
          std::move(current_request),
          HermesResponseStatusToRetryReason(status));
    } else {
      NET_LOG(ERROR)
          << "Failed to install the policy eSIM profile due to a user error: "
          << status << ". Will not schedule another attempt: "
          << current_request->activation_code.ToString();
      ProcessRequests();
    }
    return;
  }

  if (has_error) {
    NET_LOG(ERROR)
        << "Successfully installed policy eSIM profile but failed to "
        << "subsequently enable or connect to the profile: " << status << ". "
        << "Writing the profile information to device prefs and will not "
        << "schedule another attempt: "
        << current_request->activation_code.ToString();
  } else {
    NET_LOG(EVENT) << "Successfully installed policy eSIM profile: "
                   << current_request->activation_code.ToString();
  }

  current_request->retry_backoff.InformOfRequest(/*succeeded=*/true);
  HermesProfileClient::Properties* profile_properties =
      HermesProfileClient::Get()->GetProperties(*profile_path);

  const std::string* name =
      current_request->onc_config.FindString(::onc::network_config::kName);
  DCHECK(name);
  managed_cellular_pref_handler_->AddESimMetadata(
      profile_properties->iccid().value(), *name,
      current_request->activation_code,
      /*sync_stub_networks=*/false);

  managed_network_configuration_handler_->NotifyPolicyAppliedToNetwork(
      *service_path);

  ProcessRequests();
}

void CellularPolicyHandler::OnWaitTimeout() {
  NET_LOG(ERROR) << "Timed out when waiting for the EUICC or profile list.";

  auto current_request = std::move(remaining_install_requests_.front());
  PopRequest();
  ScheduleRetryAndProcessRequests(std::move(current_request),
                                  InstallRetryReason::kInternalError);
}

base::Value::Dict CellularPolicyHandler::GetNewShillProperties() {
  const NetworkProfile* profile =
      cellular_utils::GetCellularProfile(network_profile_handler_);
  DCHECK(profile);

  const std::string* guid =
      remaining_install_requests_.front()->onc_config.FindString(
          ::onc::network_config::kGUID);
  DCHECK(guid);

  return policy_util::CreateShillConfiguration(
      *profile, *guid, /*global_policy=*/nullptr,
      &(remaining_install_requests_.front()->onc_config),
      /*user_settings=*/nullptr);
}

const policy_util::SmdxActivationCode&
CellularPolicyHandler::GetCurrentActivationCode() const {
  DCHECK(is_installing_);
  return remaining_install_requests_.front()->activation_code;
}

std::optional<std::string> CellularPolicyHandler::GetIccidFromPolicyONC() {
  const std::string* iccid = policy_util::GetIccidFromONC(
      remaining_install_requests_.front()->onc_config);
  if (!iccid || iccid->empty()) {
    return std::nullopt;
  }
  return *iccid;
}

std::optional<dbus::ObjectPath>
CellularPolicyHandler::FindExistingMatchingESimProfile(
    const std::string& iccid) {
  for (CellularESimProfile esim_profile :
       cellular_esim_profile_handler_->GetESimProfiles()) {
    if (esim_profile.iccid() == iccid) {
      return esim_profile.path();
    }
  }
  return std::nullopt;
}

bool CellularPolicyHandler::HasNonCellularInternetConnectivity() {
  const NetworkState* default_network =
      network_state_handler_->DefaultNetwork();
  return default_network && default_network->type() != shill::kTypeCellular &&
         default_network->IsOnline();
}

CellularPolicyHandler::InstallRetryReason
CellularPolicyHandler::HermesResponseStatusToRetryReason(
    HermesResponseStatus status) const {
  if (base::Contains(kHermesInternalErrorCodes, status)) {
    return CellularPolicyHandler::InstallRetryReason::kInternalError;
  }
  if (base::Contains(kHermesUserErrorCodes, status)) {
    return CellularPolicyHandler::InstallRetryReason::kUserError;
  }
  return CellularPolicyHandler::InstallRetryReason::kOther;
}

}  // namespace ash