chromium/chromeos/ash/services/network_config/cros_network_config.cc

// Copyright 2019 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/services/network_config/cros_network_config.h"

#include <cmath>
#include <optional>
#include <string_view>
#include <vector>

#include "ash/constants/ash_features.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/i18n/time_formatting.h"
#include "base/not_fatal_until.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "chromeos/ash/components/carrier_lock/carrier_lock_manager.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/shill/shill_manager_client.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/cellular_esim_profile_handler.h"
#include "chromeos/ash/components/network/cellular_utils.h"
#include "chromeos/ash/components/network/device_state.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_connection_handler.h"
#include "chromeos/ash/components/network/network_device_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_handler.h"
#include "chromeos/ash/components/network/network_metadata_store.h"
#include "chromeos/ash/components/network/network_name_util.h"
#include "chromeos/ash/components/network/network_profile_handler.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_type_pattern.h"
#include "chromeos/ash/components/network/network_util.h"
#include "chromeos/ash/components/network/onc/onc_translation_tables.h"
#include "chromeos/ash/components/network/policy_util.h"
#include "chromeos/ash/components/network/prohibited_technologies_handler.h"
#include "chromeos/ash/components/network/proxy/ui_proxy_config_service.h"
#include "chromeos/ash/components/network/technology_state_controller.h"
#include "chromeos/ash/components/network/text_message_suppression_state.h"
#include "chromeos/ash/components/network/traffic_counters_handler.h"
#include "chromeos/ash/components/sync_wifi/network_eligibility_checker.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/components/onc/onc_utils.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-shared.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config.mojom.h"
#include "chromeos/services/network_config/public/mojom/cros_network_config_mojom_traits.h"
#include "components/captive_portal/core/captive_portal_detector.h"
#include "components/device_event_log/device_event_log.h"
#include "components/onc/onc_constants.h"
#include "components/user_manager/user_manager.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/ip_address.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace ash::network_config {

namespace {

namespace mojom = ::chromeos::network_config::mojom;
using ::chromeos::network_config::CustomApnListToOnc;
using ::chromeos::network_config::GetApnProperties;
using ::chromeos::network_config::GetBoolean;
using ::chromeos::network_config::GetDictionary;
using ::chromeos::network_config::GetManagedApnList;
using ::chromeos::network_config::GetManagedDictionary;
using ::chromeos::network_config::GetManagedString;
using ::chromeos::network_config::GetRequiredManagedString;
using ::chromeos::network_config::GetString;
using ::chromeos::network_config::ManagedDictionary;
using ::chromeos::network_config::OncApnTypesToMojo;
using ::user_manager::UserManager;

// Error strings from networking_private_api.cc. TODO(1004434): Enumerate
// these in mojo.
const char kErrorAccessToSharedConfig[] = "Error.CannotChangeSharedConfig";
const char kErrorInvalidONCConfiguration[] = "Error.InvalidONCConfiguration";
const char kErrorNetworkUnavailable[] = "Error.NetworkUnavailable";
const char kErrorNotReady[] = "Error.NotReady";
const char kErrorUserIsProhibitedFromConfiguringVpn[] =
    "Error.UserIsProhibitedFromConfiguringVpn";

const char kDefaultCellularProviderName[] = "MobileNetwork";
const char kDefaultCellularProviderCode[] = "000000";

std::string ShillToOnc(const std::string& shill_string,
                       const onc::StringTranslationEntry table[]) {
  std::string onc_string;
  if (!shill_string.empty())
    onc::TranslateStringToONC(table, shill_string, &onc_string);
  return onc_string;
}

mojom::NetworkType NetworkPatternToMojo(NetworkTypePattern type) {
  if (type.Equals(NetworkTypePattern::Cellular()))
    return mojom::NetworkType::kCellular;
  if (type.MatchesPattern(NetworkTypePattern::EthernetOrEthernetEAP()))
    return mojom::NetworkType::kEthernet;
  if (type.Equals(NetworkTypePattern::Tether()))
    return mojom::NetworkType::kTether;
  if (type.Equals(NetworkTypePattern::VPN()))
    return mojom::NetworkType::kVPN;
  if (type.Equals(NetworkTypePattern::WiFi()))
    return mojom::NetworkType::kWiFi;
  NOTREACHED_IN_MIGRATION()
      << "Unsupported network type: " << type.ToDebugString();
  return mojom::NetworkType::kAll;  // Unsupported
}

mojom::NetworkType ShillTypeToMojo(const std::string& shill_type) {
  return NetworkPatternToMojo(NetworkTypePattern::Primitive(shill_type));
}

mojom::NetworkType OncTypeToMojo(const std::string& onc_type) {
  return NetworkPatternToMojo(NetworkTypePattern::Primitive(
      network_util::TranslateONCTypeToShill(onc_type)));
}

NetworkTypePattern MojoTypeToPattern(mojom::NetworkType type) {
  switch (type) {
    case mojom::NetworkType::kAll:
      return NetworkTypePattern::Default();
    case mojom::NetworkType::kCellular:
      return NetworkTypePattern::Cellular();
    case mojom::NetworkType::kEthernet:
      return NetworkTypePattern::Ethernet();
    case mojom::NetworkType::kMobile:
      return NetworkTypePattern::Mobile();
    case mojom::NetworkType::kTether:
      return NetworkTypePattern::Tether();
    case mojom::NetworkType::kVPN:
      return NetworkTypePattern::VPN();
    case mojom::NetworkType::kWireless:
      return NetworkTypePattern::Wireless();
    case mojom::NetworkType::kWiFi:
      return NetworkTypePattern::WiFi();
  }
  NOTREACHED_IN_MIGRATION();
  return NetworkTypePattern::Default();
}

mojom::IPConfigType OncIPConfigTypeToMojo(const std::string& ip_config_type) {
  if (ip_config_type == ::onc::ipconfig::kIPv4)
    return mojom::IPConfigType::kIPv4;
  if (ip_config_type == ::onc::ipconfig::kIPv6)
    return mojom::IPConfigType::kIPv6;
  NOTREACHED_IN_MIGRATION()
      << "Unsupported ONC IPConfig type: " << ip_config_type;
  return mojom::IPConfigType::kIPv4;
}

std::string MojoIPConfigTypeToOnc(mojom::IPConfigType type) {
  switch (type) {
    case mojom::IPConfigType::kIPv4:
      return ::onc::ipconfig::kIPv4;
    case mojom::IPConfigType::kIPv6:
      return ::onc::ipconfig::kIPv6;
  }
  NOTREACHED_IN_MIGRATION() << "Unexpected mojo IPConfig type: " << type;
  return ::onc::ipconfig::kIPv4;
}

std::string MojoNetworkTypeToOnc(mojom::NetworkType type) {
  switch (type) {
    case mojom::NetworkType::kAll:
    case mojom::NetworkType::kMobile:
    case mojom::NetworkType::kWireless:
      break;  // Not supported
    case mojom::NetworkType::kCellular:
      return ::onc::network_type::kCellular;
    case mojom::NetworkType::kEthernet:
      return ::onc::network_type::kEthernet;
    case mojom::NetworkType::kTether:
      return ::onc::network_type::kTether;
    case mojom::NetworkType::kVPN:
      return ::onc::network_type::kVPN;
    case mojom::NetworkType::kWiFi:
      return ::onc::network_type::kWiFi;
  }
  NOTREACHED_IN_MIGRATION() << "Unsupported mojo to ONC type: " << type;
  return std::string();
}

mojom::ConnectionStateType GetMojoConnectionStateType(
    const NetworkState* network) {
  if (network->IsConnectedState()) {
    auto portal_state = network->GetPortalState();
    switch (portal_state) {
      case NetworkState::PortalState::kUnknown:
        return mojom::ConnectionStateType::kConnected;
      case NetworkState::PortalState::kOnline:
        return mojom::ConnectionStateType::kOnline;
      case NetworkState::PortalState::kPortalSuspected:
      case NetworkState::PortalState::kPortal:
      case NetworkState::PortalState::kNoInternet:
        // See PortalState for differentiation of portal states.
        return mojom::ConnectionStateType::kPortal;
    }
  }
  if (network->IsConnectingState())
    return mojom::ConnectionStateType::kConnecting;
  return mojom::ConnectionStateType::kNotConnected;
}

mojom::ConnectionStateType GetConnectionState(const NetworkState* network,
                                              bool technology_enabled) {
  // If a network technology is not enabled, always use NotConnected as the
  // connection state to avoid any edge cases during device enable/disable.
  return technology_enabled ? GetMojoConnectionStateType(network)
                            : mojom::ConnectionStateType::kNotConnected;
}

std::string MojoSecurityTypeToOnc(mojom::SecurityType security_type) {
  switch (security_type) {
    case mojom::SecurityType::kNone:
      return ::onc::wifi::kSecurityNone;
    case mojom::SecurityType::kWep8021x:
      return ::onc::wifi::kWEP_8021X;
    case mojom::SecurityType::kWepPsk:
      return ::onc::wifi::kWEP_PSK;
    case mojom::SecurityType::kWpaEap:
      return ::onc::wifi::kWPA_EAP;
    case mojom::SecurityType::kWpaPsk:
      return ::onc::wifi::kWPA_PSK;
  }
  NOTREACHED_IN_MIGRATION()
      << "Unsupported mojo to ONC type: " << security_type;
  return std::string();
}

mojom::MatchType PasspointMatchTypeToMojo(
    const std::optional<std::string>& match_type) {
  if (!match_type || match_type->empty()) {
    return mojom::MatchType::kNoMatch;
  }
  if (*match_type == shill::kPasspointMatchTypeHome) {
    return mojom::MatchType::kHome;
  }
  if (*match_type == shill::kPasspointMatchTypeRoaming) {
    return mojom::MatchType::kRoaming;
  }
  if (*match_type == shill::kPasspointMatchTypeUnknown) {
    return mojom::MatchType::kUnknown;
  }
  NOTREACHED_IN_MIGRATION()
      << "Unsupported Passpoint match type: " << *match_type;
  return mojom::MatchType::kUnknown;
}

mojom::VpnType OncVpnTypeToMojo(const std::string& onc_vpn_type) {
  if (onc_vpn_type == ::onc::vpn::kIPsec)
    return mojom::VpnType::kIKEv2;
  if (onc_vpn_type == ::onc::vpn::kTypeL2TP_IPsec)
    return mojom::VpnType::kL2TPIPsec;
  if (onc_vpn_type == ::onc::vpn::kOpenVPN)
    return mojom::VpnType::kOpenVPN;
  if (onc_vpn_type == ::onc::vpn::kWireGuard)
    return mojom::VpnType::kWireGuard;
  if (onc_vpn_type == ::onc::vpn::kThirdPartyVpn)
    return mojom::VpnType::kExtension;
  if (onc_vpn_type == ::onc::vpn::kArcVpn)
    return mojom::VpnType::kArc;
  NOTREACHED_IN_MIGRATION() << "Unsupported ONC VPN type: " << onc_vpn_type;
  return mojom::VpnType::kOpenVPN;
}

std::string MojoVpnTypeToOnc(mojom::VpnType mojo_vpn_type) {
  switch (mojo_vpn_type) {
    case mojom::VpnType::kIKEv2:
      return ::onc::vpn::kIPsec;
    case mojom::VpnType::kL2TPIPsec:
      return ::onc::vpn::kTypeL2TP_IPsec;
    case mojom::VpnType::kOpenVPN:
      return ::onc::vpn::kOpenVPN;
    case mojom::VpnType::kWireGuard:
      return ::onc::vpn::kWireGuard;
    case mojom::VpnType::kExtension:
      return ::onc::vpn::kThirdPartyVpn;
    case mojom::VpnType::kArc:
      return ::onc::vpn::kArcVpn;
  }
  NOTREACHED_IN_MIGRATION();
  return ::onc::vpn::kOpenVPN;
}

bool GetIsConfiguredByUser(const std::string& network_guid) {
  if (!NetworkHandler::IsInitialized())
    return false;

  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();

  if (!network_metadata_store)
    return false;

  return network_metadata_store->GetIsCreatedByUser(network_guid);
}

mojom::DeviceStateType GetMojoDeviceStateType(
    NetworkStateHandler::TechnologyState technology_state) {
  switch (technology_state) {
    case NetworkStateHandler::TECHNOLOGY_UNAVAILABLE:
      return mojom::DeviceStateType::kUnavailable;
    case NetworkStateHandler::TECHNOLOGY_UNINITIALIZED:
      return mojom::DeviceStateType::kUninitialized;
    case NetworkStateHandler::TECHNOLOGY_AVAILABLE:
      return mojom::DeviceStateType::kDisabled;
    case NetworkStateHandler::TECHNOLOGY_DISABLING:
      return mojom::DeviceStateType::kDisabling;
    case NetworkStateHandler::TECHNOLOGY_ENABLING:
      return mojom::DeviceStateType::kEnabling;
    case NetworkStateHandler::TECHNOLOGY_ENABLED:
      return mojom::DeviceStateType::kEnabled;
    case NetworkStateHandler::TECHNOLOGY_PROHIBITED:
      return mojom::DeviceStateType::kProhibited;
  }
  NOTREACHED_IN_MIGRATION();
  return mojom::DeviceStateType::kUnavailable;
}

mojom::PortalState GetMojoPortalState(
    const NetworkState::PortalState portal_state) {
  switch (portal_state) {
    case NetworkState::PortalState::kUnknown:
      return mojom::PortalState::kUnknown;
    case NetworkState::PortalState::kOnline:
      return mojom::PortalState::kOnline;
    case NetworkState::PortalState::kPortalSuspected:
      return mojom::PortalState::kPortalSuspected;
    case NetworkState::PortalState::kPortal:
      return mojom::PortalState::kPortal;
    case NetworkState::PortalState::kNoInternet:
      return mojom::PortalState::kNoInternet;
  }
  NOTREACHED_IN_MIGRATION();
  return mojom::PortalState::kUnknown;
}

std::optional<GURL> GetPortalProbeUrl(const NetworkState* network) {
  switch (network->GetPortalState()) {
    case NetworkState::PortalState::kUnknown:
      [[fallthrough]];
    case NetworkState::PortalState::kOnline:
      return std::nullopt;
    case NetworkState::PortalState::kPortalSuspected:
      [[fallthrough]];
    case NetworkState::PortalState::kPortal: {
      const GURL& probe_url = network->probe_url();
      if (probe_url.is_valid())
        return probe_url;
      else
        return GURL(captive_portal::CaptivePortalDetector::kDefaultURL);
    }
    case NetworkState::PortalState::kNoInternet:
      return std::nullopt;
  }
}

mojom::OncSource GetMojoOncSource(const NetworkState* network) {
  ::onc::ONCSource source = network->onc_source();
  switch (source) {
    case ::onc::ONC_SOURCE_UNKNOWN:
    case ::onc::ONC_SOURCE_NONE:
      if (!network->IsInProfile())
        return mojom::OncSource::kNone;
      return network->IsPrivate() ? mojom::OncSource::kUser
                                  : mojom::OncSource::kDevice;
    case ::onc::ONC_SOURCE_USER_IMPORT:
      return mojom::OncSource::kUser;
    case ::onc::ONC_SOURCE_DEVICE_POLICY:
      return mojom::OncSource::kDevicePolicy;
    case ::onc::ONC_SOURCE_USER_POLICY:
      return mojom::OncSource::kUserPolicy;
  }
  NOTREACHED_IN_MIGRATION();
  return mojom::OncSource::kNone;
}

const std::string& GetVpnProviderName(
    const std::vector<mojom::VpnProviderPtr>& vpn_providers,
    const std::string& provider_id) {
  for (const mojom::VpnProviderPtr& provider : vpn_providers) {
    if (provider->provider_id == provider_id)
      return provider->provider_name;
  }
  return base::EmptyString();
}

bool IsVpnProhibited() {
  bool vpn_prohibited = false;
  if (NetworkHandler::IsInitialized()) {
    std::vector<std::string> prohibited_technologies =
        NetworkHandler::Get()
            ->prohibited_technologies_handler()
            ->GetCurrentlyProhibitedTechnologies();
    vpn_prohibited = base::Contains(prohibited_technologies, shill::kTypeVPN);
  }
  return vpn_prohibited;
}

mojom::DeviceStatePropertiesPtr GetVpnState() {
  auto result = mojom::DeviceStateProperties::New();
  result->type = mojom::NetworkType::kVPN;

  result->device_state = IsVpnProhibited() ? mojom::DeviceStateType::kProhibited
                                           : mojom::DeviceStateType::kEnabled;
  return result;
}

mojom::NetworkStatePropertiesPtr NetworkStateToMojo(
    NetworkStateHandler* network_state_handler,
    CellularESimProfileHandler* cellular_esim_profile_handler,
    const std::vector<mojom::VpnProviderPtr>& vpn_providers,
    const NetworkState* network) {
  mojom::NetworkType type = ShillTypeToMojo(network->type());
  if (type == mojom::NetworkType::kAll) {
    NET_LOG(ERROR) << "Unexpected network type: " << network->type()
                   << " GUID: " << network->guid();
    return nullptr;
  }

  auto result = mojom::NetworkStateProperties::New();
  result->type = type;
  result->connectable = network->connectable();
  result->connect_requested = network->connect_requested();
  bool technology_enabled = network->Matches(NetworkTypePattern::VPN()) ||
                            network_state_handler->IsTechnologyEnabled(
                                NetworkTypePattern::Primitive(network->type()));
  result->connection_state = GetConnectionState(network, technology_enabled);
  if (!network->GetError().empty())
    result->error_state = network->GetError();
  result->guid = network->guid();
  result->name =
      network_name_util::GetNetworkName(cellular_esim_profile_handler, network);
  result->portal_state = GetMojoPortalState(network->GetPortalState());
  result->portal_probe_url = GetPortalProbeUrl(network);
  result->priority = network->priority();
  result->prohibited_by_policy = network->blocked_by_policy();
  result->source = GetMojoOncSource(network);
  result->proxy_mode =
      NetworkHandler::HasUiProxyConfigService()
          ? mojom::ProxyMode(
                NetworkHandler::GetUiProxyConfigService()->ProxyModeForNetwork(
                    network))
          : mojom::ProxyMode::kDirect;

  switch (type) {
    case mojom::NetworkType::kCellular: {
      auto cellular = mojom::CellularStateProperties::New();
      cellular->iccid = network->iccid();
      cellular->eid = network->eid();
      cellular->activation_state = network->GetMojoActivationState();
      cellular->network_technology = ShillToOnc(network->network_technology(),
                                                onc::kNetworkTechnologyTable);
      cellular->roaming = network->IndicateRoaming();
      cellular->signal_strength = network->signal_strength();
      if (!network->payment_method().empty() ||
          !network->payment_url().empty()) {
        auto payment_portal = mojom::PaymentPortalProperties::New();
        payment_portal->method = network->payment_method();
        payment_portal->post_data = network->payment_post_data();
        payment_portal->url = network->payment_url();
        cellular->payment_portal = std::move(payment_portal);
      }

      const DeviceState* cellular_device =
          network_state_handler->GetDeviceState(network->device_path());
      bool sim_is_primary =
          cellular_device &&
          cellular_utils::IsSimPrimary(network->iccid(), cellular_device);
      cellular->sim_lock_enabled =
          sim_is_primary && cellular_device->sim_lock_enabled();
      cellular->sim_locked = sim_is_primary && cellular_device->IsSimLocked();
      if (sim_is_primary) {
        cellular->sim_lock_type = cellular_device->sim_lock_type();
      }
      cellular->has_nick_name = network_name_util::HasNickName(
          cellular_esim_profile_handler, network);
      cellular->network_operator = network_name_util::GetServiceProvider(
          cellular_esim_profile_handler, network);
      result->type_state =
          mojom::NetworkTypeStateProperties::NewCellular(std::move(cellular));
      break;
    }
    case mojom::NetworkType::kEthernet: {
      auto ethernet = mojom::EthernetStateProperties::New();
      ethernet->authentication = network->type() == shill::kTypeEthernetEap
                                     ? mojom::AuthenticationType::k8021x
                                     : mojom::AuthenticationType::kNone;
      result->type_state =
          mojom::NetworkTypeStateProperties::NewEthernet(std::move(ethernet));
      break;
    }
    case mojom::NetworkType::kTether: {
      auto tether = mojom::TetherStateProperties::New();
      tether->battery_percentage = network->battery_percentage();
      tether->carrier = network->tether_carrier();
      tether->has_connected_to_host = network->tether_has_connected_to_host();
      tether->signal_strength = network->signal_strength();
      result->type_state =
          mojom::NetworkTypeStateProperties::NewTether(std::move(tether));
      break;
    }
    case mojom::NetworkType::kVPN: {
      auto vpn = mojom::VPNStateProperties::New();
      const NetworkState::VpnProviderInfo* vpn_provider =
          network->vpn_provider();
      if (vpn_provider) {
        vpn->type = OncVpnTypeToMojo(
            ShillToOnc(vpn_provider->type, onc::kVPNTypeTable));
        vpn->provider_id = vpn_provider->id;
        vpn->provider_name =
            GetVpnProviderName(vpn_providers, vpn_provider->id);
      }
      result->type_state =
          mojom::NetworkTypeStateProperties::NewVpn(std::move(vpn));
      break;
    }
    case mojom::NetworkType::kWiFi: {
      auto wifi = mojom::WiFiStateProperties::New();
      wifi->bssid = network->bssid();
      wifi->frequency = network->frequency();
      wifi->hex_ssid = network->GetHexSsid();
      wifi->security = network->GetMojoSecurity();
      wifi->signal_strength = network->signal_strength();
      wifi->ssid = network->name();
      wifi->visible = network->visible();
      wifi->hidden_ssid = network->hidden_ssid();
      wifi->passpoint_id = network->passpoint_id();
      result->type_state =
          mojom::NetworkTypeStateProperties::NewWifi(std::move(wifi));
      break;
    }
    case mojom::NetworkType::kAll:
    case mojom::NetworkType::kMobile:
    case mojom::NetworkType::kWireless:
      NOTREACHED_IN_MIGRATION()
          << "NetworkStateProperties can not be of type: " << type;
      break;
  }
  return result;
}

std::vector<mojom::SIMInfoPtr> CellularSIMInfosToMojo(
    const DeviceState* device) {
  std::vector<mojom::SIMInfoPtr> sim_info_mojos;
  for (const auto& sim_slot :
       cellular_utils::GetSimSlotInfosWithUpdatedEid(device)) {
    auto sim_info_mojo = mojom::SIMInfo::New();
    sim_info_mojo->slot_id = sim_slot.slot_id;
    sim_info_mojo->iccid = sim_slot.iccid;
    sim_info_mojo->eid = sim_slot.eid;
    sim_info_mojo->is_primary = sim_slot.primary;
    sim_info_mojos.push_back(std::move(sim_info_mojo));
  }
  return sim_info_mojos;
}

bool IsCellularConnecting(NetworkStateHandler* network_state_handler) {
  NetworkStateHandler::NetworkStateList cellular_networks;
  network_state_handler->GetVisibleNetworkListByType(
      NetworkTypePattern::Cellular(), &cellular_networks);
  return base::ranges::any_of(cellular_networks,
                              &NetworkState::IsConnectingState);
}

mojom::InhibitReason GetInhibitReason(
    NetworkStateHandler* network_state_handler,
    CellularInhibitor* cellular_inhibitor) {
  if (!cellular_inhibitor)
    return mojom::InhibitReason::kNotInhibited;

  std::optional<CellularInhibitor::InhibitReason> inhibit_reason =
      cellular_inhibitor->GetInhibitReason();
  if (!inhibit_reason) {
    // For devices with EUICC, the UI should be inhibited when a cellular
    // network connection is in progress to prevent additional requests. This is
    // due to complexity in switching slots.
    if (!HermesManagerClient::Get()->GetAvailableEuiccs().empty() &&
        IsCellularConnecting(network_state_handler)) {
      return mojom::InhibitReason::kConnectingToProfile;
    }

    return mojom::InhibitReason::kNotInhibited;
  }

  switch (*inhibit_reason) {
    case CellularInhibitor::InhibitReason::kInstallingProfile:
      return mojom::InhibitReason::kInstallingProfile;
    case CellularInhibitor::InhibitReason::kRenamingProfile:
      return mojom::InhibitReason::kRenamingProfile;
    case CellularInhibitor::InhibitReason::kRemovingProfile:
      return mojom::InhibitReason::kRemovingProfile;
    case CellularInhibitor::InhibitReason::kConnectingToProfile:
      return mojom::InhibitReason::kConnectingToProfile;
    case CellularInhibitor::InhibitReason::kRefreshingProfileList:
      return mojom::InhibitReason::kRefreshingProfileList;
    case CellularInhibitor::InhibitReason::kResettingEuiccMemory:
      return mojom::InhibitReason::kResettingEuiccMemory;
    case CellularInhibitor::InhibitReason::kDisablingProfile:
      return mojom::InhibitReason::kDisablingProfile;
    case CellularInhibitor::InhibitReason::kRequestingAvailableProfiles:
      return mojom::InhibitReason::kRequestingAvailableProfiles;
  }
}

mojom::DeviceStatePropertiesPtr DeviceStateToMojo(
    const DeviceState* device,
    NetworkStateHandler* network_state_handler,
    CellularInhibitor* cellular_inhibitor,
    mojom::DeviceStateType technology_state,
    const std::optional<std::string>& serial_number) {
  mojom::NetworkType type = ShillTypeToMojo(device->type());
  if (type == mojom::NetworkType::kAll) {
    NET_LOG(ERROR) << "Unexpected device type: " << device->type()
                   << " path: " << device->path();
    return nullptr;
  }

  auto result = mojom::DeviceStateProperties::New();
  result->type = type;
  net::IPAddress ipv4_address;
  if (ipv4_address.AssignFromIPLiteral(
          device->GetIpAddressByType(shill::kTypeIPv4))) {
    result->ipv4_address = ipv4_address;
  }
  net::IPAddress ipv6_address;
  if (ipv6_address.AssignFromIPLiteral(
          device->GetIpAddressByType(shill::kTypeIPv6))) {
    result->ipv6_address = ipv6_address;
  }
  result->mac_address =
      network_util::FormattedMacAddress(device->mac_address());
  result->scanning = device->scanning();
  result->device_state = technology_state;
  result->managed_network_available =
      !device->available_managed_network_path().empty();
  result->sim_absent = device->IsSimAbsent();
  if (device->sim_present()) {
    auto sim_lock_status = mojom::SIMLockStatus::New();
    sim_lock_status->lock_type = device->sim_lock_type();
    sim_lock_status->lock_enabled = device->sim_lock_enabled();
    sim_lock_status->retries_left = device->sim_retries_left();
    result->sim_lock_status = std::move(sim_lock_status);
  }
  if (type == mojom::NetworkType::kCellular) {
    result->sim_infos = CellularSIMInfosToMojo(device);
    result->inhibit_reason =
        GetInhibitReason(network_state_handler, cellular_inhibitor);
    result->imei = device->imei();
    if (serial_number && !serial_number->empty()) {
      result->serial = serial_number.value();
    }
    carrier_lock::ModemLockStatus status =
        carrier_lock::CarrierLockManager::GetModemLockStatus();
    if (status == carrier_lock::ModemLockStatus::kCarrierLocked) {
      result->is_carrier_locked = true;
    }
    result->is_flashing = device->flashing();
  }
  return result;
}

std::string GetRequiredString(const base::Value::Dict* dict, const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v) {
    NOTREACHED(base::NotFatalUntil::M127) << "Required key missing: " << key;
    return std::string();
  }
  if (!v->is_string()) {
    NET_LOG(ERROR) << "Expected string, found: " << *v;
    return std::string();
  }
  return v->GetString();
}

int32_t GetInt32(const base::Value::Dict* dict, const char* key) {
  const base::Value* v = dict->Find(key);
  if (v && !v->is_int()) {
    NET_LOG(ERROR) << "Expected int, found: " << *v;
    return 0;
  }
  return v ? v->GetInt() : 0;
}

std::vector<int32_t> GetInt32List(const base::Value::Dict* dict,
                                  const char* key) {
  std::vector<int32_t> result;
  const base::Value* v = dict->Find(key);
  if (v && !v->is_list()) {
    NET_LOG(ERROR) << "Expected list, found: " << *v;
    return result;
  }
  if (v) {
    for (const base::Value& e : v->GetList()) {
      result.push_back(e.GetInt());
    }
  }
  return result;
}

std::optional<std::vector<std::string>> GetStringList(
    const base::Value::Dict* dict,
    const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v) {
    return std::nullopt;
  }
  if (!v->is_list()) {
    NET_LOG(ERROR) << "Expected list, found: " << *v;
    return std::nullopt;
  }
  std::vector<std::string> result;
  for (const base::Value& e : v->GetList()) {
    result.push_back(e.GetString());
  }
  return result;
}

std::vector<std::string> GetRequiredStringList(const base::Value::Dict* dict,
                                               const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v) {
    NOTREACHED_IN_MIGRATION() << "Required key missing: " << key;
    return {};
  }
  if (!v->is_list()) {
    NET_LOG(ERROR) << "Expected list, found: " << *v;
    return {};
  }
  std::vector<std::string> result;
  result.reserve(v->GetList().size());
  for (const base::Value& e : v->GetList()) {
    if (!e.is_string()) {
      NET_LOG(ERROR) << "Expected string, found: " << e;
      break;
    }
    result.push_back(e.GetString());
  }
  return result;
}

void SetString(const char* key,
               const std::optional<std::string>& property,
               base::Value::Dict* dict) {
  if (!property)
    return;
  dict->Set(key, *property);
}

void SetStringIfNotEmpty(const char* key,
                         const std::optional<std::string>& property,
                         base::Value::Dict* dict) {
  if (!property || property->empty())
    return;
  dict->Set(key, *property);
}

void SetStringList(const char* key,
                   const std::optional<std::vector<std::string>>& property,
                   base::Value::Dict* dict) {
  if (!property)
    return;
  base::Value::List list;
  for (const std::string& s : *property)
    list.Append(s);
  dict->Set(key, std::move(list));
}

void SetSubjectAltNameMatch(
    const char* key,
    const std::vector<mojom::SubjectAltNamePtr>* property,
    base::Value::Dict* dict) {
  base::Value::List subject_alt_name_list;
  for (const auto& ptr : *property) {
    std::string type;
    switch (ptr->type) {
      case mojom::SubjectAltName::Type::kEmail:
        type = ::onc::eap_subject_alternative_name_match::kEMAIL;
        break;
      case mojom::SubjectAltName::Type::kDns:
        type = ::onc::eap_subject_alternative_name_match::kDNS;
        break;
      case mojom::SubjectAltName::Type::kUri:
        type = ::onc::eap_subject_alternative_name_match::kURI;
        break;
    }
    base::Value::Dict entry;
    entry.Set(::onc::eap_subject_alternative_name_match::kType, type);
    entry.Set(::onc::eap_subject_alternative_name_match::kValue, ptr->value);
    subject_alt_name_list.Append(std::move(entry));
  }
  dict->Set(key, std::move(subject_alt_name_list));
}

mojom::ManagedStringListPtr GetManagedStringList(const base::Value::Dict* dict,
                                                 const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v)
    return nullptr;
  if (v->is_list()) {
    auto result = mojom::ManagedStringList::New();
    std::vector<std::string> active;
    for (const base::Value& e : v->GetList())
      active.push_back(e.GetString());
    result->active_value = std::move(active);
    return result;
  }
  if (v->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&v->GetDict());
    if (!managed_dict.active_value.is_list()) {
      NET_LOG(ERROR) << "No active or effective value for: " << key;
      return nullptr;
    }
    auto result = mojom::ManagedStringList::New();
    for (const base::Value& e : managed_dict.active_value.GetList())
      result->active_value.push_back(e.GetString());
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none()) {
      result->policy_value = std::vector<std::string>();
      for (const base::Value& e : managed_dict.policy_value.GetList())
        result->policy_value->push_back(e.GetString());
    }
    return result;
  }
  NET_LOG(ERROR) << "Expected list or dictionary, found: " << *v;
  return nullptr;
}

mojom::ManagedBooleanPtr GetManagedBoolean(const base::Value::Dict* dict,
                                           const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v)
    return nullptr;
  if (v->is_bool()) {
    auto result = mojom::ManagedBoolean::New();
    result->active_value = v->GetBool();
    return result;
  }
  if (v->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&v->GetDict());
    if (!managed_dict.active_value.is_bool()) {
      NET_LOG(ERROR) << "No active or effective value for: " << key;
      return nullptr;
    }
    auto result = mojom::ManagedBoolean::New();
    result->active_value = managed_dict.active_value.GetBool();
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none())
      result->policy_value = managed_dict.policy_value.GetBool();
    return result;
  }
  NET_LOG(ERROR) << "Expected bool or dictionary, found: " << *v;
  return nullptr;
}

mojom::ManagedInt32Ptr GetManagedInt32(const base::Value::Dict* dict,
                                       const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v)
    return nullptr;
  if (v->is_int()) {
    auto result = mojom::ManagedInt32::New();
    result->active_value = v->GetInt();
    return result;
  }
  if (v->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&v->GetDict());
    if (!managed_dict.active_value.is_int()) {
      NET_LOG(ERROR) << "No active or effective value for: " << key;
      return nullptr;
    }
    auto result = mojom::ManagedInt32::New();
    result->active_value = managed_dict.active_value.GetInt();
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none())
      result->policy_value = managed_dict.policy_value.GetInt();
    return result;
  }
  NET_LOG(ERROR) << "Expected int or dictionary, found: " << *v;
  return nullptr;
}

mojom::SubjectAltNamePtr GetSubjectAltName(const base::Value::Dict* dict) {
  auto san = mojom::SubjectAltName::New();
  san->value = GetRequiredString(
      dict, ::onc::eap_subject_alternative_name_match::kValue);
  std::string type =
      GetRequiredString(dict, ::onc::eap_subject_alternative_name_match::kType);
  if (type == ::onc::eap_subject_alternative_name_match::kEMAIL) {
    san->type = mojom::SubjectAltName::Type::kEmail;
  } else if (type == ::onc::eap_subject_alternative_name_match::kDNS) {
    san->type = mojom::SubjectAltName::Type::kDns;
  } else if (type == ::onc::eap_subject_alternative_name_match::kURI) {
    san->type = mojom::SubjectAltName::Type::kUri;
  } else {
    NET_LOG(ERROR) << "Unknown subject alternative name type " << type;
    return nullptr;
  }
  return san;
}

mojom::ManagedSubjectAltNameMatchListPtr GetManagedSubjectAltNameMatchList(
    const base::Value::Dict* dict,
    const char* key) {
  auto result = mojom::ManagedSubjectAltNameMatchList::New();
  const base::Value* value = dict->Find(key);
  if (!value)
    return result;

  if (value->is_list()) {
    std::vector<mojom::SubjectAltNamePtr> active;
    for (const base::Value& e : value->GetList()) {
      active.push_back(GetSubjectAltName(&e.GetDict()));
    }
    result->active_value = std::move(active);
    return result;
  }
  if (value->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&value->GetDict());
    if (!managed_dict.active_value.is_list()) {
      NET_LOG(ERROR) << "No active or effective value for WireGuardPeerList";
      return result;
    }
    for (const base::Value& e : managed_dict.active_value.GetList()) {
      result->active_value.push_back(GetSubjectAltName(&e.GetDict()));
    }
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none()) {
      result->policy_value = std::vector<mojom::SubjectAltNamePtr>();
      for (const base::Value& e : managed_dict.policy_value.GetList()) {
        result->policy_value.push_back(GetSubjectAltName(&e.GetDict()));
      }
    }
    return result;
  }
  NET_LOG(ERROR) << "Expected list or dictionary, found: " << *value;
  return result;
}

mojom::IPConfigPropertiesPtr GetIPConfig(const base::Value::Dict* dict) {
  auto ip_config = mojom::IPConfigProperties::New();
  ip_config->gateway = GetString(dict, ::onc::ipconfig::kGateway);
  ip_config->ip_address = GetString(dict, ::onc::ipconfig::kIPAddress);
  ip_config->excluded_routes =
      GetStringList(dict, ::onc::ipconfig::kExcludedRoutes);
  ip_config->included_routes =
      GetStringList(dict, ::onc::ipconfig::kIncludedRoutes);
  ip_config->name_servers = GetStringList(dict, ::onc::ipconfig::kNameServers);
  ip_config->search_domains =
      GetStringList(dict, ::onc::ipconfig::kSearchDomains);
  ip_config->routing_prefix = GetInt32(dict, ::onc::ipconfig::kRoutingPrefix);
  auto type = GetString(dict, ::onc::ipconfig::kType);
  if (!type || type->empty()) {
    // Shill may omit the IP Config type for VPNs. The type should be IPv4.
    ip_config->type = mojom::IPConfigType::kIPv4;
  } else {
    ip_config->type = OncIPConfigTypeToMojo(*type);
  }
  ip_config->web_proxy_auto_discovery_url =
      GetString(dict, ::onc::ipconfig::kWebProxyAutoDiscoveryUrl);
  return ip_config;
}

mojom::ManagedIPConfigPropertiesPtr GetManagedIPConfig(
    const base::Value::Dict* dict) {
  auto ip_config = mojom::ManagedIPConfigProperties::New();
  ip_config->gateway = GetManagedString(dict, ::onc::ipconfig::kGateway);
  ip_config->ip_address = GetManagedString(dict, ::onc::ipconfig::kIPAddress);
  ip_config->name_servers =
      GetManagedStringList(dict, ::onc::ipconfig::kNameServers);
  ip_config->routing_prefix =
      GetManagedInt32(dict, ::onc::ipconfig::kRoutingPrefix);
  // The IPConfig type is not actually mutable, so we convert from an optional
  // managed string to a required unmanaged type enum.
  mojom::ManagedStringPtr managed_type =
      GetManagedString(dict, ::onc::ipconfig::kType);
  mojom::IPConfigType type =
      managed_type ? OncIPConfigTypeToMojo(managed_type->active_value)
                   : mojom::IPConfigType::kIPv4;
  ip_config->type = type;
  ip_config->web_proxy_auto_discovery_url =
      GetManagedString(dict, ::onc::ipconfig::kWebProxyAutoDiscoveryUrl);
  return ip_config;
}

mojom::ManagedProxyLocationPtr GetManagedProxyLocation(
    const base::Value::Dict* dict,
    const char* key) {
  const base::Value::Dict* location_dict = GetDictionary(dict, key);
  if (!location_dict) {
    return nullptr;
  }
  auto proxy_location = mojom::ManagedProxyLocation::New();
  proxy_location->host =
      GetRequiredManagedString(location_dict, ::onc::proxy::kHost);
  proxy_location->port = GetManagedInt32(location_dict, ::onc::proxy::kPort);
  if (!proxy_location->port) {
    NET_LOG(ERROR) << "ProxyLocation: No port: " << *location_dict;
    return nullptr;
  }
  return proxy_location;
}

void SetProxyLocation(const char* key,
                      const mojom::ProxyLocationPtr& location,
                      base::Value::Dict* dict) {
  if (location.is_null()) {
    return;
  }
  base::Value::Dict location_dict;
  location_dict.Set(::onc::proxy::kHost, location->host);
  location_dict.Set(::onc::proxy::kPort, location->port);
  dict->Set(key, std::move(location_dict));
}

mojom::ManagedProxySettingsPtr GetManagedProxySettings(
    const base::Value::Dict* dict) {
  auto proxy_settings = mojom::ManagedProxySettings::New();
  proxy_settings->type = GetRequiredManagedString(dict, ::onc::proxy::kType);
  const base::Value::Dict* manual_dict =
      GetDictionary(dict, ::onc::proxy::kManual);
  if (manual_dict) {
    auto manual_proxy_settings = mojom::ManagedManualProxySettings::New();
    manual_proxy_settings->http_proxy =
        GetManagedProxyLocation(manual_dict, ::onc::proxy::kHttp);
    manual_proxy_settings->secure_http_proxy =
        GetManagedProxyLocation(manual_dict, ::onc::proxy::kHttps);
    manual_proxy_settings->ftp_proxy =
        GetManagedProxyLocation(manual_dict, ::onc::proxy::kFtp);
    manual_proxy_settings->socks =
        GetManagedProxyLocation(manual_dict, ::onc::proxy::kSocks);
    proxy_settings->manual = std::move(manual_proxy_settings);
  }
  proxy_settings->exclude_domains =
      GetManagedStringList(dict, ::onc::proxy::kExcludeDomains);
  proxy_settings->pac = GetManagedString(dict, ::onc::proxy::kPAC);
  return proxy_settings;
}

mojom::ApnState OncApnStateTypeToMojo(const std::string* state) {
  DCHECK(features::IsApnRevampEnabled());
  // State can be empty, because database/modem APNs won't have a state.
  if (!state || state->empty() || *state == ::onc::cellular_apn::kStateEnabled)
    return mojom::ApnState::kEnabled;
  if (*state == ::onc::cellular_apn::kStateDisabled)
    return mojom::ApnState::kDisabled;

  NOTREACHED_IN_MIGRATION() << "Unexpected ONC APN State type: " << state;
  return mojom::ApnState::kEnabled;
}

std::string MojoApnStateTypeToOnc(mojom::ApnState state) {
  DCHECK(features::IsApnRevampEnabled());
  switch (state) {
    case mojom::ApnState::kDisabled:
      return ::onc::cellular_apn::kStateDisabled;
    case mojom::ApnState::kEnabled:
      return ::onc::cellular_apn::kStateEnabled;
  }
  NOTREACHED_IN_MIGRATION() << "Unexpected mojo ApnState type: " << state;
  return ::onc::cellular_apn::kStateEnabled;
}

std::string MojoApnAuthenticationTypeToOnc(
    mojom::ApnAuthenticationType authentication_type) {
  switch (authentication_type) {
    case mojom::ApnAuthenticationType::kAutomatic:
      return ::onc::cellular_apn::kAuthenticationAutomatic;
    case mojom::ApnAuthenticationType::kPap:
      return ::onc::cellular_apn::kAuthenticationPap;
    case mojom::ApnAuthenticationType::kChap:
      return ::onc::cellular_apn::kAuthenticationChap;
  }
  NOTREACHED_IN_MIGRATION()
      << "Unexpected mojo AuthenticationType type: " << authentication_type;
  return ::onc::cellular_apn::kAuthenticationAutomatic;
}

std::string MojoApnIpTypeToOnc(mojom::ApnIpType ip_type) {
  DCHECK(features::IsApnRevampEnabled());
  switch (ip_type) {
    case mojom::ApnIpType::kAutomatic:
      return ::onc::cellular_apn::kIpTypeAutomatic;
    case mojom::ApnIpType::kIpv4:
      return ::onc::cellular_apn::kIpTypeIpv4;
    case mojom::ApnIpType::kIpv6:
      return ::onc::cellular_apn::kIpTypeIpv6;
    case mojom::ApnIpType::kIpv4Ipv6:
      return ::onc::cellular_apn::kIpTypeIpv4Ipv6;
  }
  NOTREACHED_IN_MIGRATION() << "Unexpected mojo ApnIpType type: " << ip_type;
  return ::onc::cellular_apn::kIpTypeAutomatic;
}

std::string MojoApnSourceToOnc(mojom::ApnSource source) {
  DCHECK(features::IsApnRevampEnabled());
  switch (source) {
    case mojom::ApnSource::kModem:
      return ::onc::cellular_apn::kSourceModem;
    case mojom::ApnSource::kModb:
      return ::onc::cellular_apn::kSourceModb;
    case mojom::ApnSource::kUi:
      return ::onc::cellular_apn::kSourceUi;
      // TODO(b/5429735): Add mojom::ApnSource::kAdmin in follow up CL
  }
  NOTREACHED_IN_MIGRATION() << "Unexpected mojo ApnSource: " << source;
  return ::onc::cellular_apn::kSourceModem;
}

std::vector<std::string> MojoApnTypesToOnc(
    const std::vector<mojom::ApnType>& apn_types) {
  DCHECK(features::IsApnRevampEnabled());
  DCHECK(!apn_types.empty());
  std::vector<std::string> apn_types_result;
  apn_types_result.reserve(apn_types.size());
  for (const mojom::ApnType& type : apn_types) {
    switch (type) {
      case mojom::ApnType::kDefault:
        apn_types_result.push_back(::onc::cellular_apn::kApnTypeDefault);
        continue;
      case mojom::ApnType::kAttach:
        apn_types_result.push_back(::onc::cellular_apn::kApnTypeAttach);
        continue;
      case mojom::ApnType::kTether:
        apn_types_result.push_back(::onc::cellular_apn::kApnTypeTether);
        continue;
    }
  }

  return apn_types_result;
}

bool DoesDefaultApnExist(const base::Value::List& apns) {
  for (const base::Value& apn : apns) {
    mojom::ApnPropertiesPtr apn_ptr =
        GetApnProperties(apn.GetDict(), features::IsApnRevampEnabled());
    for (const mojom::ApnType& type : apn_ptr->apn_types) {
      if (type == mojom::ApnType::kDefault) {
        return true;
      }
    }
  }
  return false;
}

std::vector<mojom::FoundNetworkPropertiesPtr> GetFoundNetworksList(
    const base::Value::Dict* dict,
    const char* key) {
  std::vector<mojom::FoundNetworkPropertiesPtr> result;
  const base::Value* v = dict->Find(key);
  if (!v)
    return result;
  if (!v->is_list()) {
    NET_LOG(ERROR) << "Expected list, found: " << *v;
    return result;
  }
  for (const base::Value& entry : v->GetList()) {
    const base::Value::Dict* entry_dict = entry.GetIfDict();
    if (!entry_dict) {
      NET_LOG(ERROR) << "Expected dict, found: " << entry;
      continue;
    }
    auto found_network = mojom::FoundNetworkProperties::New();
    found_network->status =
        GetRequiredString(entry_dict, ::onc::cellular_found_network::kStatus);
    found_network->network_id = GetRequiredString(
        entry_dict, ::onc::cellular_found_network::kNetworkId);
    found_network->technology = GetRequiredString(
        entry_dict, ::onc::cellular_found_network::kTechnology);
    found_network->short_name =
        GetString(entry_dict, ::onc::cellular_found_network::kShortName);
    found_network->long_name =
        GetString(entry_dict, ::onc::cellular_found_network::kLongName);
    result.push_back(std::move(found_network));
  }
  return result;
}

mojom::CellularProviderPropertiesPtr GetCellularProviderProperties(
    const base::Value::Dict* dict,
    const char* key) {
  const base::Value::Dict* provider_dict = dict->FindDict(key);
  if (!provider_dict)
    return nullptr;
  auto provider = mojom::CellularProviderProperties::New();
  auto name = GetString(provider_dict, ::onc::cellular_provider::kName);
  if (!name.has_value() || name->empty()) {
    NET_LOG(ERROR) << "Cellular provider dictionary is missing provider name";
    provider->name = kDefaultCellularProviderName;
  } else {
    provider->name = *name;
  }
  auto code = GetString(provider_dict, ::onc::cellular_provider::kCode);
  if (!code.has_value() || code->empty()) {
    NET_LOG(ERROR) << "Cellular provider dictionary is missing provider code";
    provider->code = kDefaultCellularProviderCode;
  } else {
    provider->code = *code;
  }
  provider->country =
      GetString(provider_dict, ::onc::cellular_provider::kCountry);
  return provider;
}

mojom::ManagedIssuerSubjectPatternPtr GetManagedIssuerSubjectPattern(
    const base::Value::Dict* dict,
    const char* key) {
  const base::Value* pattern_dict_value = dict->Find(key);
  if (!pattern_dict_value) {
    return nullptr;
  }
  const base::Value::Dict* pattern_dict = pattern_dict_value->GetIfDict();
  if (!pattern_dict) {
    NET_LOG(ERROR) << "Expected dictionary, found: " << *pattern_dict_value;
    return nullptr;
  }
  auto pattern = mojom::ManagedIssuerSubjectPattern::New();
  pattern->common_name =
      GetManagedString(pattern_dict, ::onc::client_cert::kCommonName);
  pattern->locality =
      GetManagedString(pattern_dict, ::onc::client_cert::kLocality);
  pattern->organization =
      GetManagedString(pattern_dict, ::onc::client_cert::kOrganization);
  pattern->organizational_unit =
      GetManagedString(pattern_dict, ::onc::client_cert::kOrganizationalUnit);
  return pattern;
}

mojom::ManagedCertificatePatternPtr GetManagedCertificatePattern(
    const base::Value::Dict* dict,
    const char* key) {
  const base::Value* pattern_dict_value = dict->Find(key);
  if (!pattern_dict_value) {
    return nullptr;
  }
  const base::Value::Dict* pattern_dict = pattern_dict_value->GetIfDict();
  if (!pattern_dict) {
    NET_LOG(ERROR) << "Expected dictionary, found: " << *pattern_dict_value;
    return nullptr;
  }
  auto pattern = mojom::ManagedCertificatePattern::New();
  pattern->enrollment_uri =
      GetManagedStringList(pattern_dict, ::onc::client_cert::kEnrollmentURI);
  pattern->issuer =
      GetManagedIssuerSubjectPattern(pattern_dict, ::onc::client_cert::kIssuer);
  pattern->issuer_ca_ref =
      GetManagedStringList(pattern_dict, ::onc::client_cert::kIssuerCARef);
  pattern->subject = GetManagedIssuerSubjectPattern(
      pattern_dict, ::onc::client_cert::kSubject);
  return pattern;
}

mojom::ManagedEAPPropertiesPtr GetManagedEAPProperties(
    const base::Value::Dict* dict,
    const char* key) {
  auto eap = mojom::ManagedEAPProperties::New();
  const base::Value::Dict* eap_dict = dict->FindDict(key);
  if (!eap_dict) {
    return eap;
  }
  eap->anonymous_identity =
      GetManagedString(eap_dict, ::onc::eap::kAnonymousIdentity);
  eap->client_cert_pattern = GetManagedCertificatePattern(
      eap_dict, ::onc::client_cert::kClientCertPattern);
  eap->client_cert_pkcs11_id =
      GetManagedString(eap_dict, ::onc::client_cert::kClientCertPKCS11Id);
  eap->client_cert_provisioning_profile_id = GetManagedString(
      eap_dict, ::onc::client_cert::kClientCertProvisioningProfileId);
  eap->client_cert_ref =
      GetManagedString(eap_dict, ::onc::client_cert::kClientCertRef);
  eap->client_cert_type =
      GetManagedString(eap_dict, ::onc::client_cert::kClientCertType);
  eap->domain_suffix_match =
      GetManagedStringList(eap_dict, ::onc::eap::kDomainSuffixMatch);
  eap->identity = GetManagedString(eap_dict, ::onc::eap::kIdentity);
  eap->inner = GetManagedString(eap_dict, ::onc::eap::kInner);
  eap->outer = GetManagedString(eap_dict, ::onc::eap::kOuter);
  eap->password = GetManagedString(eap_dict, ::onc::eap::kPassword);
  eap->save_credentials =
      GetManagedBoolean(eap_dict, ::onc::eap::kSaveCredentials);
  eap->server_ca_pems =
      GetManagedStringList(eap_dict, ::onc::eap::kServerCAPEMs);
  eap->server_ca_refs =
      GetManagedStringList(eap_dict, ::onc::eap::kServerCARefs);
  eap->subject_alt_name_match = GetManagedSubjectAltNameMatchList(
      eap_dict, ::onc::eap::kSubjectAlternativeNameMatch);
  eap->subject_match = GetManagedString(eap_dict, ::onc::eap::kSubjectMatch);
  eap->tls_version_max = GetManagedString(eap_dict, ::onc::eap::kTLSVersionMax);
  eap->use_proactive_key_caching =
      GetManagedBoolean(eap_dict, ::onc::eap::kUseProactiveKeyCaching);
  eap->use_system_cas = GetManagedBoolean(eap_dict, ::onc::eap::kUseSystemCAs);
  return eap;
}

mojom::ManagedIPSecPropertiesPtr GetManagedIPSecProperties(
    const base::Value::Dict* dict,
    const char* key) {
  auto ipsec = mojom::ManagedIPSecProperties::New();
  const base::Value::Dict* ipsec_dict = dict->FindDict(key);
  if (!ipsec_dict) {
    return ipsec;
  }
  ipsec->authentication_type =
      GetRequiredManagedString(ipsec_dict, ::onc::ipsec::kAuthenticationType);
  ipsec->client_cert_pattern = GetManagedCertificatePattern(
      ipsec_dict, ::onc::client_cert::kClientCertPattern);
  ipsec->client_cert_pkcs11_id =
      GetManagedString(ipsec_dict, ::onc::client_cert::kClientCertPKCS11Id);
  ipsec->client_cert_provisioning_profile_id = GetManagedString(
      ipsec_dict, ::onc::client_cert::kClientCertProvisioningProfileId);
  ipsec->client_cert_ref =
      GetManagedString(ipsec_dict, ::onc::client_cert::kClientCertRef);
  ipsec->client_cert_type =
      GetManagedString(ipsec_dict, ::onc::client_cert::kClientCertType);
  ipsec->eap = GetManagedEAPProperties(ipsec_dict, ::onc::ipsec::kEAP);
  ipsec->group = GetManagedString(ipsec_dict, ::onc::ipsec::kGroup);
  ipsec->ike_version = GetManagedInt32(ipsec_dict, ::onc::ipsec::kIKEVersion);
  ipsec->psk = GetManagedString(ipsec_dict, ::onc::ipsec::kPSK);
  ipsec->save_credentials =
      GetManagedBoolean(ipsec_dict, ::onc::vpn::kSaveCredentials);
  ipsec->server_ca_pems =
      GetManagedStringList(ipsec_dict, ::onc::ipsec::kServerCAPEMs);
  ipsec->server_ca_refs =
      GetManagedStringList(ipsec_dict, ::onc::ipsec::kServerCARefs);
  ipsec->local_identity =
      GetManagedString(ipsec_dict, ::onc::ipsec::kLocalIdentity);
  ipsec->remote_identity =
      GetManagedString(ipsec_dict, ::onc::ipsec::kRemoteIdentity);
  return ipsec;
}

mojom::ManagedL2TPPropertiesPtr GetManagedL2TPProperties(
    const base::Value::Dict* dict,
    const char* key) {
  auto l2tp = mojom::ManagedL2TPProperties::New();
  const base::Value::Dict* l2tp_dict = dict->FindDict(key);
  if (!l2tp_dict)
    return l2tp;
  l2tp->lcp_echo_disabled =
      GetManagedBoolean(l2tp_dict, ::onc::l2tp::kLcpEchoDisabled);
  l2tp->password = GetManagedString(l2tp_dict, ::onc::l2tp::kPassword);
  l2tp->save_credentials =
      GetManagedBoolean(l2tp_dict, ::onc::l2tp::kSaveCredentials);
  l2tp->username = GetManagedString(l2tp_dict, ::onc::l2tp::kUsername);
  return l2tp;
}

mojom::ManagedOpenVPNPropertiesPtr GetManagedOpenVPNProperties(
    const base::Value::Dict* dict,
    const char* key) {
  auto openvpn = mojom::ManagedOpenVPNProperties::New();
  const base::Value::Dict* openvpn_dict = dict->FindDict(key);
  if (!openvpn_dict)
    return openvpn;
  openvpn->auth = GetManagedString(openvpn_dict, ::onc::openvpn::kAuth);
  openvpn->auth_retry =
      GetManagedString(openvpn_dict, ::onc::openvpn::kAuthRetry);
  openvpn->auth_no_cache =
      GetManagedBoolean(openvpn_dict, ::onc::openvpn::kAuthNoCache);
  openvpn->cipher = GetManagedString(openvpn_dict, ::onc::openvpn::kCipher);
  openvpn->client_cert_pkcs11_id =
      GetManagedString(openvpn_dict, ::onc::client_cert::kClientCertPKCS11Id);
  openvpn->client_cert_pattern = GetManagedCertificatePattern(
      openvpn_dict, ::onc::client_cert::kClientCertPattern);
  openvpn->client_cert_provisioning_profile_id = GetManagedString(
      openvpn_dict, ::onc::client_cert::kClientCertProvisioningProfileId);
  openvpn->client_cert_ref =
      GetManagedString(openvpn_dict, ::onc::client_cert::kClientCertRef);
  openvpn->client_cert_type =
      GetManagedString(openvpn_dict, ::onc::client_cert::kClientCertType);
  openvpn->compression_algorithm =
      GetManagedString(openvpn_dict, ::onc::openvpn::kCompressionAlgorithm);
  openvpn->extra_hosts =
      GetManagedStringList(openvpn_dict, ::onc::openvpn::kExtraHosts);
  openvpn->ignore_default_route =
      GetManagedBoolean(openvpn_dict, ::onc::openvpn::kIgnoreDefaultRoute);
  openvpn->key_direction =
      GetManagedString(openvpn_dict, ::onc::openvpn::kKeyDirection);
  openvpn->ns_cert_type =
      GetManagedString(openvpn_dict, ::onc::openvpn::kNsCertType);
  openvpn->password = GetManagedString(openvpn_dict, ::onc::openvpn::kPassword);
  openvpn->port = GetManagedInt32(openvpn_dict, ::onc::openvpn::kPort);
  openvpn->proto = GetManagedString(openvpn_dict, ::onc::openvpn::kProto);
  openvpn->push_peer_info =
      GetManagedBoolean(openvpn_dict, ::onc::openvpn::kPushPeerInfo);
  openvpn->remote_cert_eku =
      GetManagedString(openvpn_dict, ::onc::openvpn::kRemoteCertEKU);
  openvpn->remote_cert_ku =
      GetManagedStringList(openvpn_dict, ::onc::openvpn::kRemoteCertKU);
  openvpn->remote_cert_tls =
      GetManagedString(openvpn_dict, ::onc::openvpn::kRemoteCertTLS);
  openvpn->reneg_sec = GetManagedInt32(openvpn_dict, ::onc::openvpn::kRenegSec);
  openvpn->save_credentials =
      GetManagedBoolean(openvpn_dict, ::onc::vpn::kSaveCredentials);
  openvpn->server_ca_pems =
      GetManagedStringList(openvpn_dict, ::onc::openvpn::kServerCAPEMs);
  openvpn->server_ca_refs =
      GetManagedStringList(openvpn_dict, ::onc::openvpn::kServerCARefs);
  openvpn->server_cert_ref =
      GetManagedString(openvpn_dict, ::onc::openvpn::kServerCertRef);
  openvpn->server_poll_timeout =
      GetManagedInt32(openvpn_dict, ::onc::openvpn::kServerPollTimeout);
  openvpn->shaper = GetManagedInt32(openvpn_dict, ::onc::openvpn::kShaper);
  openvpn->static_challenge =
      GetManagedString(openvpn_dict, ::onc::openvpn::kStaticChallenge);
  openvpn->tls_auth_contents =
      GetManagedString(openvpn_dict, ::onc::openvpn::kTLSAuthContents);
  openvpn->tls_remote =
      GetManagedString(openvpn_dict, ::onc::openvpn::kTLSRemote);
  openvpn->tls_version_min =
      GetManagedString(openvpn_dict, ::onc::openvpn::kTLSVersionMin);
  openvpn->user_authentication_type =
      GetManagedString(openvpn_dict, ::onc::openvpn::kUserAuthenticationType);
  openvpn->username = GetManagedString(openvpn_dict, ::onc::vpn::kUsername);
  openvpn->verb = GetManagedString(openvpn_dict, ::onc::openvpn::kVerb);
  openvpn->verify_hash =
      GetManagedString(openvpn_dict, ::onc::openvpn::kVerifyHash);
  const base::Value::Dict* verify_x509_dict =
      openvpn_dict->FindDict(::onc::openvpn::kVerifyX509);
  if (verify_x509_dict) {
    auto verify_x509 = mojom::ManagedVerifyX509Properties::New();
    verify_x509->name =
        GetManagedString(verify_x509_dict, ::onc::verify_x509::kName);
    verify_x509->type =
        GetManagedString(verify_x509_dict, ::onc::verify_x509::kType);
    openvpn->verify_x509 = std::move(verify_x509);
  }
  return openvpn;
}

mojom::WireGuardPeerPropertiesPtr GetWireGuardPeerProperties(
    const base::Value::Dict* dict) {
  auto peer = mojom::WireGuardPeerProperties::New();
  peer->public_key = GetRequiredString(dict, ::onc::wireguard::kPublicKey);
  peer->preshared_key = GetString(dict, ::onc::wireguard::kPresharedKey);
  peer->allowed_ips = GetString(dict, ::onc::wireguard::kAllowedIPs);
  peer->endpoint = GetString(dict, ::onc::wireguard::kEndpoint);
  peer->persistent_keepalive_interval =
      GetInt32(dict, ::onc::wireguard::kPersistentKeepalive);
  return peer;
}

mojom::ManagedWireGuardPeerListPtr GetManagedWireGuardPeerList(
    const base::Value::Dict* dict,
    const char* key) {
  auto result = mojom::ManagedWireGuardPeerList::New();
  const base::Value* value = dict->Find(key);
  if (!value) {
    return result;
  }
  if (value->is_list()) {
    std::vector<mojom::WireGuardPeerPropertiesPtr> active;
    for (const base::Value& e : value->GetList()) {
      active.push_back(GetWireGuardPeerProperties(&e.GetDict()));
    }
    result->active_value = std::move(active);
    return result;
  }
  if (value->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&value->GetDict());
    if (!managed_dict.active_value.is_list()) {
      NET_LOG(ERROR) << "No active or effective value for WireGuardPeerList";
      return result;
    }
    for (const base::Value& e : managed_dict.active_value.GetList()) {
      result->active_value.push_back(GetWireGuardPeerProperties(&e.GetDict()));
    }
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none()) {
      result->policy_value = std::vector<mojom::WireGuardPeerPropertiesPtr>();
      for (const base::Value& e : managed_dict.policy_value.GetList()) {
        result->policy_value->push_back(
            GetWireGuardPeerProperties(&e.GetDict()));
      }
    }
    return result;
  }
  NET_LOG(ERROR) << "Expected list or dictionary, found: " << *value;
  return result;
}

mojom::ManagedWireGuardPropertiesPtr GetManagedWireGuardProperties(
    const base::Value::Dict* dict,
    const char* key) {
  auto wg = mojom::ManagedWireGuardProperties::New();
  const base::Value::Dict* wg_dict = dict->FindDict(key);
  if (!wg_dict) {
    NET_LOG(ERROR) << "Missing WireGuard properties element";
    return wg;
  }
  wg->ip_addresses =
      GetManagedStringList(wg_dict, ::onc::wireguard::kIPAddresses);
  wg->private_key = GetManagedString(wg_dict, ::onc::wireguard::kPrivateKey);
  wg->public_key = GetManagedString(wg_dict, ::onc::wireguard::kPublicKey);
  wg->peers = GetManagedWireGuardPeerList(wg_dict, ::onc::wireguard::kPeers);
  return wg;
}

mojom::TrafficCounterPropertiesPtr CreateTrafficCounterProperties(
    const base::Value::Dict* properties,
    const std::string& guid) {
  auto traffic_counter_properties = mojom::TrafficCounterProperties::New();
  const std::optional<double> last_reset_time =
      properties->FindDouble(::onc::network_config::kTrafficCounterResetTime);
  if (last_reset_time) {
    traffic_counter_properties->last_reset_time =
        base::Time::FromDeltaSinceWindowsEpoch(
            base::Milliseconds(last_reset_time.value()));
    traffic_counter_properties->friendly_date =
        base::UTF16ToUTF8(base::TimeFormatFriendlyDate(
            traffic_counter_properties->last_reset_time.value()));
  } else {
    traffic_counter_properties->last_reset_time = std::nullopt;
    traffic_counter_properties->friendly_date = std::nullopt;
  }
  traffic_counter_properties->user_specified_reset_day =
      traffic_counters::TrafficCountersHandler::Get()->GetUserSpecifiedResetDay(
          guid);
  return traffic_counter_properties;
}

mojom::ManagedPropertiesPtr ManagedPropertiesToMojo(
    NetworkStateHandler* network_state_handler,
    CellularESimProfileHandler* cellular_esim_profile_handler,
    const NetworkState* network_state,
    const std::vector<mojom::VpnProviderPtr>& vpn_providers,
    const base::Value::Dict* properties) {
  DCHECK(network_state);
  DCHECK(properties);
  std::optional<std::string> onc_type =
      GetString(properties, ::onc::network_config::kType);
  if (!onc_type) {
    NET_LOG(ERROR) << "Malformed ONC dictionary: missing 'Type'";
    return nullptr;
  }
  mojom::NetworkType type = OncTypeToMojo(*onc_type);
  if (type == mojom::NetworkType::kAll) {
    NET_LOG(ERROR) << "Unexpected network type: " << *onc_type;
    return nullptr;
  }
  auto result = mojom::ManagedProperties::New();

  // |network_state| and |properties| guid should be the same.
  std::optional<std::string> guid =
      GetString(properties, ::onc::network_config::kGUID);
  if (!guid) {
    NET_LOG(ERROR) << "Malformed ONC dictionary: missing 'GUID'";
    return nullptr;
  }
  DCHECK_EQ(network_state->guid(), *guid);
  result->guid = network_state->guid();

  // Typed properties (compatible with NetworkStateProperties):
  result->connection_state =
      GetConnectionState(network_state, /*technology_enabled=*/true);
  result->source = GetMojoOncSource(network_state);
  result->type = type;

  // Unmanaged properties
  result->connectable =
      GetBoolean(properties, ::onc::network_config::kConnectable);
  result->error_state =
      GetString(properties, ::onc::network_config::kErrorState);
  const base::Value::List* ip_configs_list =
      properties->FindList(::onc::network_config::kIPConfigs);
  if (ip_configs_list) {
    std::vector<mojom::IPConfigPropertiesPtr> ip_configs;
    for (const base::Value& ip_config : *ip_configs_list) {
      ip_configs.push_back(GetIPConfig(&ip_config.GetDict()));
    }
    result->ip_configs = std::move(ip_configs);
  }
  result->portal_state = GetMojoPortalState(network_state->GetPortalState());
  const base::Value::Dict* saved_ip_config =
      GetDictionary(properties, ::onc::network_config::kSavedIPConfig);
  if (saved_ip_config)
    result->saved_ip_config = GetIPConfig(saved_ip_config);

  // Managed properties
  result->ip_address_config_type = GetRequiredManagedString(
      properties, ::onc::network_config::kIPAddressConfigType);
  result->metered =
      GetManagedBoolean(properties, ::onc::network_config::kMetered);
  result->name = GetManagedString(properties, ::onc::network_config::kName);
  if (result->name->policy_source == mojom::PolicySource::kNone) {
    std::optional<std::string> profile_name =
        network_name_util::GetESimProfileName(cellular_esim_profile_handler,
                                              network_state);
    if (profile_name)
      result->name->active_value = *profile_name;
  }

  result->name_servers_config_type = GetRequiredManagedString(
      properties, ::onc::network_config::kNameServersConfigType);
  result->priority =
      GetManagedInt32(properties, ::onc::network_config::kPriority);

  // Managed dictionaries (not type specific)
  const base::Value::Dict* proxy_settings =
      GetDictionary(properties, ::onc::network_config::kProxySettings);
  if (proxy_settings)
    result->proxy_settings = GetManagedProxySettings(proxy_settings);
  const base::Value::Dict* static_ip_config =
      GetDictionary(properties, ::onc::network_config::kStaticIPConfig);
  if (static_ip_config)
    result->static_ip_config = GetManagedIPConfig(static_ip_config);

  // Type specific dictionaries
  switch (type) {
    case mojom::NetworkType::kCellular: {
      auto cellular = mojom::ManagedCellularProperties::New();
      cellular->activation_state = network_state->GetMojoActivationState();

      const base::Value::Dict* cellular_dict =
          GetDictionary(properties, ::onc::network_config::kCellular);
      if (!cellular_dict) {
        result->type_properties =
            mojom::NetworkTypeManagedProperties::NewCellular(
                std::move(cellular));
        break;
      }
      cellular->auto_connect =
          GetManagedBoolean(cellular_dict, ::onc::cellular::kAutoConnect);
      cellular->selected_apn =
          chromeos::network_config::GetManagedApnProperties(
              cellular_dict, ::onc::cellular::kAPN);
      cellular->apn_list =
          GetManagedApnList(cellular_dict->Find(::onc::cellular::kAPNList),
                            ash::features::IsApnRevampEnabled());
      cellular->allow_roaming =
          GetManagedBoolean(cellular_dict, ::onc::cellular::kAllowRoaming);
      cellular->esn = GetString(cellular_dict, ::onc::cellular::kESN);
      cellular->family = GetString(cellular_dict, ::onc::cellular::kFamily);
      cellular->firmware_revision =
          GetString(cellular_dict, ::onc::cellular::kFirmwareRevision);
      cellular->found_networks =
          GetFoundNetworksList(cellular_dict, ::onc::cellular::kFoundNetworks);
      cellular->hardware_revision =
          GetString(cellular_dict, ::onc::cellular::kHardwareRevision);
      cellular->home_provider = GetCellularProviderProperties(
          cellular_dict, ::onc::cellular::kHomeProvider);
      cellular->eid = GetString(cellular_dict, ::onc::cellular::kEID);
      cellular->iccid = GetString(cellular_dict, ::onc::cellular::kICCID);
      cellular->imei = GetString(cellular_dict, ::onc::cellular::kIMEI);
      const base::Value::Dict* apn_dict =
          GetDictionary(cellular_dict, ::onc::cellular::kLastGoodAPN);
      if (apn_dict) {
        bool is_apn_revamp_enabled = features::IsApnRevampEnabled();
        cellular->last_good_apn =
            GetApnProperties(*apn_dict, is_apn_revamp_enabled);
        if (is_apn_revamp_enabled) {
          const std::optional<std::string> connection_state =
              GetString(properties, ::onc::network_config::kConnectionState);

          // The connected_apn will only be set when the network is connected,
          // and will indicate which APN was used to establish the data
          // connection. The last_good_apn property is set by Shill, and can be
          // present when the network is not connected.
          if (connection_state &&
              *connection_state == ::onc::connection_state::kConnected) {
            cellular->connected_apn = GetApnProperties(*apn_dict, true);
          }
        }
      }
      cellular->manufacturer =
          GetString(cellular_dict, ::onc::cellular::kManufacturer);
      cellular->mdn = GetString(cellular_dict, ::onc::cellular::kMDN);
      cellular->meid = GetString(cellular_dict, ::onc::cellular::kMEID);
      cellular->min = GetString(cellular_dict, ::onc::cellular::kMIN);
      cellular->model_id = GetString(cellular_dict, ::onc::cellular::kModelID);
      cellular->network_technology =
          GetString(cellular_dict, ::onc::cellular::kNetworkTechnology);
      const base::Value::Dict* payment_portal_dict =
          cellular_dict->FindDict(::onc::cellular::kPaymentPortal);
      if (payment_portal_dict) {
        auto payment_portal = mojom::PaymentPortalProperties::New();
        payment_portal->method = GetRequiredString(
            payment_portal_dict, ::onc::cellular_payment_portal::kMethod);
        payment_portal->post_data = GetRequiredString(
            payment_portal_dict, ::onc::cellular_payment_portal::kPostData);
        payment_portal->url = GetString(payment_portal_dict,
                                        ::onc::cellular_payment_portal::kUrl);
        cellular->payment_portal = std::move(payment_portal);
      }
      cellular->roaming_state =
          GetString(cellular_dict, ::onc::cellular::kRoamingState);
      cellular->serving_operator = GetCellularProviderProperties(
          cellular_dict, ::onc::cellular::kServingOperator);
      cellular->signal_strength =
          GetInt32(cellular_dict, ::onc::cellular::kSignalStrength);
      cellular->support_network_scan =
          GetBoolean(cellular_dict, ::onc::cellular::kSupportNetworkScan);

      const DeviceState* cellular_device =
          network_state_handler->GetDeviceState(network_state->device_path());

      // The cellular device only tracks whether the active SIM is locked. To
      // determine whether |network_state| is locked, we check that the SIM is
      // active by comparing the ICCID to the device's ICCID, then we check that
      // the device is in a locked state.
      cellular->sim_locked = cellular_device &&
                             cellular_device->iccid() == cellular->iccid &&
                             cellular_device->IsSimLocked();
      if (cellular->sim_locked) {
        cellular->sim_lock_type = cellular_device->sim_lock_type();
      }
        UserTextMessageSuppressionState state =
            NetworkHandler::Get()
                ->network_metadata_store()
                ->GetUserTextMessageSuppressionState(network_state->guid());
        cellular->allow_text_messages = mojom::ManagedBoolean::New();
        cellular->allow_text_messages->active_value =
            state == UserTextMessageSuppressionState::kAllow;
        PolicyTextMessageSuppressionState policy_state =
            NetworkHandler::Get()
                ->managed_network_configuration_handler()
                ->GetAllowTextMessages();
        if (policy_state != PolicyTextMessageSuppressionState::kUnset) {
          cellular->allow_text_messages->policy_value =
              policy_state == PolicyTextMessageSuppressionState::kAllow;
          // |active_value| should match policy value when policy is enforced.
          cellular->allow_text_messages->active_value =
              cellular->allow_text_messages->policy_value;
          // We manually set the policy source to device enforced as that aligns
          // with the implemented behavior. This field is read in WebUI to
          // determine whether the policy value should be used or not.
          cellular->allow_text_messages->policy_source = ::chromeos::
              network_config::mojom::PolicySource::kDevicePolicyEnforced;
        }

      result->type_properties =
          mojom::NetworkTypeManagedProperties::NewCellular(std::move(cellular));

      if (features::IsTrafficCountersEnabled()) {
        result->traffic_counter_properties =
            CreateTrafficCounterProperties(properties, result->guid);
      }

      break;
    }
    case mojom::NetworkType::kEthernet: {
      auto ethernet = mojom::ManagedEthernetProperties::New();
      const base::Value::Dict* ethernet_dict =
          GetDictionary(properties, ::onc::network_config::kEthernet);
      if (ethernet_dict) {
        ethernet->authentication =
            GetManagedString(ethernet_dict, ::onc::ethernet::kAuthentication);
        ethernet->eap =
            GetManagedEAPProperties(ethernet_dict, ::onc::ethernet::kEAP);
      }
      result->type_properties =
          mojom::NetworkTypeManagedProperties::NewEthernet(std::move(ethernet));
      break;
    }
    case mojom::NetworkType::kTether: {
      // Tether has no managed properties, provide the state properties.
      auto tether = mojom::TetherStateProperties::New();
      tether->battery_percentage = network_state->battery_percentage();
      tether->carrier = network_state->tether_carrier();
      tether->has_connected_to_host =
          network_state->tether_has_connected_to_host();
      tether->signal_strength = network_state->signal_strength();
      result->type_properties =
          mojom::NetworkTypeManagedProperties::NewTether(std::move(tether));
      break;
    }
    case mojom::NetworkType::kVPN: {
      auto vpn = mojom::ManagedVPNProperties::New();
      const base::Value::Dict* vpn_dict =
          GetDictionary(properties, ::onc::network_config::kVPN);
      if (!vpn_dict) {
        result->type_properties =
            mojom::NetworkTypeManagedProperties::NewVpn(std::move(vpn));
        break;
      }
      mojom::ManagedStringPtr managed_type =
          GetManagedString(vpn_dict, ::onc::vpn::kType);
      CHECK(managed_type);
      vpn->type = OncVpnTypeToMojo(managed_type->active_value);

      vpn->auto_connect = GetManagedBoolean(vpn_dict, ::onc::vpn::kAutoConnect);
      vpn->host = GetManagedString(vpn_dict, ::onc::vpn::kHost);

      switch (vpn->type) {
        case mojom::VpnType::kIKEv2:
          vpn->ip_sec = GetManagedIPSecProperties(vpn_dict, ::onc::vpn::kIPsec);
          break;
        case mojom::VpnType::kL2TPIPsec:
          vpn->ip_sec = GetManagedIPSecProperties(vpn_dict, ::onc::vpn::kIPsec);
          vpn->l2tp = GetManagedL2TPProperties(vpn_dict, ::onc::vpn::kL2TP);
          break;
        case mojom::VpnType::kOpenVPN:
          vpn->open_vpn =
              GetManagedOpenVPNProperties(vpn_dict, ::onc::vpn::kOpenVPN);
          break;
        case mojom::VpnType::kWireGuard:
          vpn->wireguard =
              GetManagedWireGuardProperties(vpn_dict, ::onc::vpn::kWireGuard);
          break;
        case mojom::VpnType::kExtension:
        case mojom::VpnType::kArc:
          const base::Value::Dict* third_party_dict =
              vpn_dict->FindDict(::onc::vpn::kThirdPartyVpn);
          if (third_party_dict) {
            vpn->provider_id = GetManagedString(
                third_party_dict, ::onc::third_party_vpn::kExtensionID);
            std::optional<std::string> provider_name = GetString(
                third_party_dict, ::onc::third_party_vpn::kProviderName);
            if (provider_name)
              vpn->provider_name = *provider_name;
            if (vpn->provider_id && vpn->provider_name.empty()) {
              vpn->provider_name = GetVpnProviderName(
                  vpn_providers, vpn->provider_id->active_value);
            }
          } else {
            // Lookup VPN provider details from the NetworkState.
            const NetworkState::VpnProviderInfo* vpn_provider =
                network_state->vpn_provider();
            if (vpn_provider) {
              vpn->provider_id = mojom::ManagedString::New();
              vpn->provider_id->active_value = vpn_provider->id;
              vpn->provider_name =
                  GetVpnProviderName(vpn_providers, vpn_provider->id);
            }
          }
          break;
      }
      result->type_properties =
          mojom::NetworkTypeManagedProperties::NewVpn(std::move(vpn));
      break;
    }
    case mojom::NetworkType::kWiFi: {
      auto wifi = mojom::ManagedWiFiProperties::New();
      wifi->security = network_state->GetMojoSecurity();

      const base::Value::Dict* wifi_dict =
          GetDictionary(properties, ::onc::network_config::kWiFi);
      if (!wifi_dict) {
        result->type_properties =
            mojom::NetworkTypeManagedProperties::NewWifi(std::move(wifi));
        break;
      }
      wifi->allow_gateway_arp_polling =
          GetManagedBoolean(wifi_dict, ::onc::wifi::kAllowGatewayARPPolling);
      wifi->auto_connect =
          GetManagedBoolean(wifi_dict, ::onc::wifi::kAutoConnect);
      wifi->bssid = GetString(wifi_dict, ::onc::wifi::kBSSID);
      wifi->eap = GetManagedEAPProperties(wifi_dict, ::onc::wifi::kEAP);
      wifi->frequency = GetInt32(wifi_dict, ::onc::wifi::kFrequency);
      wifi->frequency_list =
          GetInt32List(wifi_dict, ::onc::wifi::kFrequencyList);
      wifi->hex_ssid = GetManagedString(wifi_dict, ::onc::wifi::kHexSSID);
      wifi->hidden_ssid =
          GetManagedBoolean(wifi_dict, ::onc::wifi::kHiddenSSID);
      wifi->passphrase = GetManagedString(wifi_dict, ::onc::wifi::kPassphrase);
      wifi->ssid = GetRequiredManagedString(wifi_dict, ::onc::wifi::kSSID);
      CHECK(wifi->ssid);
      wifi->signal_strength = GetInt32(wifi_dict, ::onc::wifi::kSignalStrength);
      wifi->is_syncable = sync_wifi::IsEligibleForSync(
          result->guid, result->connectable,
          wifi->hidden_ssid ? wifi->hidden_ssid->active_value : false,
          wifi->security, result->source,
          /*log_result=*/false);
      wifi->is_configured_by_active_user = GetIsConfiguredByUser(result->guid);
      wifi->passpoint_id = GetString(wifi_dict, ::onc::wifi::kPasspointId);
      wifi->passpoint_match_type = PasspointMatchTypeToMojo(
          GetString(wifi_dict, ::onc::wifi::kPasspointMatchType));

      result->type_properties =
          mojom::NetworkTypeManagedProperties::NewWifi(std::move(wifi));

      if (features::IsTrafficCountersForWiFiTestingEnabled()) {
        result->traffic_counter_properties =
            CreateTrafficCounterProperties(properties, result->guid);
      }
      break;
    }
    case mojom::NetworkType::kAll:
    case mojom::NetworkType::kMobile:
    case mojom::NetworkType::kWireless:
      NOTREACHED_IN_MIGRATION()
          << "NetworkStateProperties can not be of type: " << type;
      break;
  }

  return result;
}

bool NetworkTypeCanBeDisabled(mojom::NetworkType type) {
  switch (type) {
    case mojom::NetworkType::kCellular:
    case mojom::NetworkType::kTether:
    case mojom::NetworkType::kWiFi:
      return true;
    case mojom::NetworkType::kAll:
    case mojom::NetworkType::kEthernet:
    case mojom::NetworkType::kMobile:
    case mojom::NetworkType::kVPN:
    case mojom::NetworkType::kWireless:
      return false;
  }
  NOTREACHED_IN_MIGRATION();
  return false;
}

base::Value::Dict GetEAPProperties(const mojom::EAPConfigProperties& eap) {
  base::Value::Dict eap_dict;

  SetString(::onc::eap::kAnonymousIdentity, eap.anonymous_identity, &eap_dict);
  SetString(::onc::client_cert::kClientCertPKCS11Id, eap.client_cert_pkcs11_id,
            &eap_dict);
  SetString(::onc::client_cert::kClientCertType, eap.client_cert_type,
            &eap_dict);
  SetStringList(::onc::eap::kDomainSuffixMatch, eap.domain_suffix_match,
                &eap_dict);
  SetString(::onc::eap::kIdentity, eap.identity, &eap_dict);
  SetString(::onc::eap::kInner, eap.inner, &eap_dict);
  SetString(::onc::eap::kOuter, eap.outer, &eap_dict);
  SetString(::onc::eap::kPassword, eap.password, &eap_dict);
  eap_dict.Set(::onc::eap::kSaveCredentials, eap.save_credentials);
  SetStringList(::onc::eap::kServerCAPEMs, eap.server_ca_pems, &eap_dict);
  SetSubjectAltNameMatch(::onc::eap::kSubjectAlternativeNameMatch,
                         &eap.subject_alt_name_match, &eap_dict);
  SetString(::onc::eap::kSubjectMatch, eap.subject_match, &eap_dict);
  eap_dict.Set(::onc::eap::kUseSystemCAs, eap.use_system_cas);

  return eap_dict;
}

base::Value::Dict MojoApnToOnc(const mojom::ApnProperties& apn_props) {
  base::Value::Dict apn;
  apn.Set(::onc::cellular_apn::kAccessPointName, apn_props.access_point_name);
  apn.Set(::onc::cellular_apn::kAuthentication,
          MojoApnAuthenticationTypeToOnc(apn_props.authentication));
  SetString(::onc::cellular_apn::kLanguage, apn_props.language, &apn);
  SetString(::onc::cellular_apn::kLocalizedName, apn_props.localized_name,
            &apn);
  SetString(::onc::cellular_apn::kName, apn_props.name, &apn);
  SetString(::onc::cellular_apn::kPassword, apn_props.password, &apn);
  SetString(::onc::cellular_apn::kUsername, apn_props.username, &apn);
  SetString(::onc::cellular_apn::kAttach, apn_props.attach, &apn);
  if (features::IsApnRevampEnabled()) {
    SetString(::onc::cellular_apn::kId, apn_props.id, &apn);
    apn.Set(::onc::cellular_apn::kState,
            MojoApnStateTypeToOnc(apn_props.state));
    apn.Set(::onc::cellular_apn::kIpType,
            MojoApnIpTypeToOnc(apn_props.ip_type));
    apn.Set(::onc::cellular_apn::kSource, MojoApnSourceToOnc(apn_props.source));
    base::Value::List apn_types;
    for (const std::string& apn_type : MojoApnTypesToOnc(apn_props.apn_types))
      apn_types.Append(apn_type);
    apn.Set(::onc::cellular_apn::kApnTypes, std::move(apn_types));
  }
  return apn;
}

std::optional<base::Value::Dict> GetOncFromConfigProperties(
    const mojom::ConfigProperties* properties,
    std::optional<std::string> guid) {
  base::Value::Dict onc;

  if (properties->guid && !properties->guid->empty()) {
    if (guid && *guid != *properties->guid) {
      NET_LOG(ERROR) << "GUID does not match: " << *guid
                     << " != " << *properties->guid;
      return std::nullopt;
    }
    SetString(::onc::network_config::kGUID, *properties->guid, &onc);
  }

  // Process |properties->network_type| and set |type|. Configurations have only
  // one type dictionary.
  mojom::NetworkType type = mojom::NetworkType::kAll;  // Invalid type
  base::Value::Dict type_dict;

  if (properties->type_config->is_cellular()) {
    type = mojom::NetworkType::kCellular;
    const mojom::CellularConfigProperties& cellular =
        *properties->type_config->get_cellular();
    if (cellular.apn) {
      type_dict.Set(::onc::cellular::kAPN, MojoApnToOnc(*cellular.apn));
    }
    if (cellular.roaming) {
      type_dict.Set(::onc::cellular::kAllowRoaming,
                    cellular.roaming->allow_roaming);
    }
  } else if (properties->type_config->is_ethernet()) {
    type = mojom::NetworkType::kEthernet;
    const mojom::EthernetConfigProperties& ethernet =
        *properties->type_config->get_ethernet();
    SetString(::onc::ethernet::kAuthentication, ethernet.authentication,
              &type_dict);
    if (ethernet.eap) {
      type_dict.Set(::onc::ethernet::kEAP,
                    GetEAPProperties(*ethernet.eap.get()));
    }
  } else if (properties->type_config->is_vpn()) {
    type = mojom::NetworkType::kVPN;
    const mojom::VPNConfigProperties& vpn = *properties->type_config->get_vpn();
    SetString(::onc::vpn::kHost, vpn.host, &type_dict);
    if (vpn.ip_sec) {
      const mojom::IPSecConfigProperties& ip_sec = *vpn.ip_sec;
      base::Value::Dict ip_sec_dict;
      SetString(::onc::ipsec::kAuthenticationType, ip_sec.authentication_type,
                &ip_sec_dict);
      SetString(::onc::client_cert::kClientCertPKCS11Id,
                ip_sec.client_cert_pkcs11_id, &ip_sec_dict);
      SetString(::onc::client_cert::kClientCertType, ip_sec.client_cert_type,
                &ip_sec_dict);
      SetString(::onc::ipsec::kGroup, ip_sec.group, &ip_sec_dict);
      ip_sec_dict.Set(::onc::ipsec::kIKEVersion, ip_sec.ike_version);
      SetString(::onc::ipsec::kPSK, ip_sec.psk, &ip_sec_dict);
      ip_sec_dict.Set(::onc::l2tp::kSaveCredentials, ip_sec.save_credentials);
      SetStringList(::onc::ipsec::kServerCAPEMs, ip_sec.server_ca_pems,
                    &ip_sec_dict);
      SetStringList(::onc::ipsec::kServerCARefs, ip_sec.server_ca_refs,
                    &ip_sec_dict);
      SetString(::onc::ipsec::kLocalIdentity, ip_sec.local_identity,
                &ip_sec_dict);
      SetString(::onc::ipsec::kRemoteIdentity, ip_sec.remote_identity,
                &ip_sec_dict);
      if (ip_sec.eap) {
        ip_sec_dict.Set(::onc::ipsec::kEAP,
                        GetEAPProperties(*ip_sec.eap.get()));
      }
      type_dict.Set(::onc::vpn::kIPsec, std::move(ip_sec_dict));
    }
    if (vpn.l2tp) {
      const mojom::L2TPConfigProperties& l2tp = *vpn.l2tp;
      base::Value::Dict l2tp_dict;
      l2tp_dict.Set(::onc::l2tp::kLcpEchoDisabled, l2tp.lcp_echo_disabled);
      SetString(::onc::l2tp::kPassword, l2tp.password, &l2tp_dict);
      l2tp_dict.Set(::onc::l2tp::kSaveCredentials, l2tp.save_credentials);
      SetString(::onc::l2tp::kUsername, l2tp.username, &l2tp_dict);
      type_dict.Set(::onc::vpn::kL2TP, std::move(l2tp_dict));
    }
    if (vpn.open_vpn) {
      const mojom::OpenVPNConfigProperties& open_vpn = *vpn.open_vpn;
      base::Value::Dict open_vpn_dict;
      SetString(::onc::client_cert::kClientCertPKCS11Id,
                open_vpn.client_cert_pkcs11_id, &open_vpn_dict);
      SetString(::onc::client_cert::kClientCertType, open_vpn.client_cert_type,
                &open_vpn_dict);
      SetStringList(::onc::openvpn::kExtraHosts, open_vpn.extra_hosts,
                    &open_vpn_dict);
      SetString(::onc::openvpn::kOTP, open_vpn.otp, &open_vpn_dict);
      SetString(::onc::openvpn::kPassword, open_vpn.password, &open_vpn_dict);
      open_vpn_dict.Set(::onc::l2tp::kSaveCredentials,
                        open_vpn.save_credentials);
      SetStringList(::onc::openvpn::kServerCAPEMs, open_vpn.server_ca_pems,
                    &open_vpn_dict);
      SetStringList(::onc::openvpn::kServerCARefs, open_vpn.server_ca_refs,
                    &open_vpn_dict);
      SetString(::onc::vpn::kUsername, open_vpn.username, &open_vpn_dict);
      SetString(::onc::openvpn::kUserAuthenticationType,
                open_vpn.user_authentication_type, &open_vpn_dict);
      type_dict.Set(::onc::vpn::kOpenVPN, std::move(open_vpn_dict));
    }
    if (vpn.wireguard) {
      const mojom::WireGuardConfigProperties& wireguard = *vpn.wireguard;
      base::Value::Dict wireguard_dict;
      SetString(::onc::wireguard::kPrivateKey, wireguard.private_key,
                &wireguard_dict);
      SetStringList(::onc::wireguard::kIPAddresses, wireguard.ip_addresses,
                    &wireguard_dict);

      base::Value::List peer_list;
      if (wireguard.peers) {
        for (auto const& peer : *wireguard.peers) {
          base::Value::Dict peer_dict;
          peer_dict.Set(::onc::wireguard::kPublicKey, peer->public_key);
          SetString(::onc::wireguard::kPresharedKey, peer->preshared_key,
                    &peer_dict);
          SetString(::onc::wireguard::kEndpoint, peer->endpoint, &peer_dict);
          SetString(::onc::wireguard::kAllowedIPs, peer->allowed_ips,
                    &peer_dict);
          if (peer->persistent_keepalive_interval) {
            peer_dict.Set(
                ::onc::wireguard::kPersistentKeepalive,
                base::NumberToString(peer->persistent_keepalive_interval));
          }
          peer_list.Append(std::move(peer_dict));
        }
      }
      wireguard_dict.Set(::onc::wireguard::kPeers, std::move(peer_list));
      wireguard_dict.Set(::onc::vpn::kSaveCredentials, true);
      type_dict.Set(::onc::vpn::kWireGuard, std::move(wireguard_dict));
    }

    if (vpn.type) {
      SetString(::onc::vpn::kType, MojoVpnTypeToOnc(vpn.type->value),
                &type_dict);
    }
  } else if (properties->type_config->is_wifi()) {
    type = mojom::NetworkType::kWiFi;
    const mojom::WiFiConfigProperties& wifi =
        *properties->type_config->get_wifi();
    SetString(::onc::wifi::kPassphrase, wifi.passphrase, &type_dict);
    SetStringIfNotEmpty(::onc::wifi::kSSID, wifi.ssid, &type_dict);
    SetString(::onc::wifi::kPassphrase, wifi.passphrase, &type_dict);

    switch (wifi.hidden_ssid) {
      case mojom::HiddenSsidMode::kDisabled:
        type_dict.Set(::onc::wifi::kHiddenSSID, false);
        break;
      case mojom::HiddenSsidMode::kEnabled:
        type_dict.Set(::onc::wifi::kHiddenSSID, true);
        break;
      case mojom::HiddenSsidMode::kAutomatic:
        // This is expressed to the platform by leaving off kHiddenSSID.
        break;
    }

    SetString(::onc::wifi::kSecurity, MojoSecurityTypeToOnc(wifi.security),
              &type_dict);
    if (wifi.eap) {
      type_dict.Set(::onc::wifi::kEAP, GetEAPProperties(*wifi.eap.get()));
    }
  }

  std::string onc_type = MojoNetworkTypeToOnc(type);
  if (onc_type.empty()) {
    NET_LOG(ERROR) << "Invalid NetworkConfig properties";
    return std::nullopt;
  }
  SetString(::onc::network_config::kType, onc_type, &onc);

  // Process other |properties| members. Order matches the mojo struct.

  if (properties->ip_address_config_type) {
    onc.Set(::onc::network_config::kIPAddressConfigType,
            *properties->ip_address_config_type);
  }
  if (properties->metered) {
    onc.Set(::onc::network_config::kMetered, properties->metered->value);
  }
  SetString(::onc::network_config::kName, properties->name, &onc);
  SetString(::onc::network_config::kNameServersConfigType,
            properties->name_servers_config_type, &onc);

  if (properties->priority) {
    onc.Set(::onc::network_config::kPriority, properties->priority->value);
  }

  if (properties->proxy_settings) {
    const mojom::ProxySettings& proxy = *properties->proxy_settings;
    base::Value::Dict proxy_dict;
    proxy_dict.Set(::onc::proxy::kType, proxy.type);
    if (proxy.manual) {
      const mojom::ManualProxySettings& manual = *proxy.manual;
      base::Value::Dict manual_dict;
      SetProxyLocation(::onc::proxy::kHttp, manual.http_proxy, &manual_dict);
      SetProxyLocation(::onc::proxy::kHttps, manual.secure_http_proxy,
                       &manual_dict);
      SetProxyLocation(::onc::proxy::kFtp, manual.ftp_proxy, &manual_dict);
      SetProxyLocation(::onc::proxy::kSocks, manual.socks, &manual_dict);
      proxy_dict.Set(::onc::proxy::kManual, std::move(manual_dict));
    }
    SetStringList(::onc::proxy::kExcludeDomains, proxy.exclude_domains,
                  &proxy_dict);
    SetString(::onc::proxy::kPAC, proxy.pac, &proxy_dict);
    onc.Set(::onc::network_config::kProxySettings, std::move(proxy_dict));
  }

  if (properties->static_ip_config) {
    const mojom::IPConfigProperties& ip_config = *properties->static_ip_config;
    base::Value::Dict ip_config_dict;
    SetString(::onc::ipconfig::kGateway, ip_config.gateway, &ip_config_dict);
    SetString(::onc::ipconfig::kIPAddress, ip_config.ip_address,
              &ip_config_dict);
    SetStringList(::onc::ipconfig::kNameServers, ip_config.name_servers,
                  &ip_config_dict);
    ip_config_dict.Set(::onc::ipconfig::kRoutingPrefix,
                       ip_config.routing_prefix);
    ip_config_dict.Set(::onc::ipconfig::kType,
                       MojoIPConfigTypeToOnc(ip_config.type));
    SetString(::onc::ipconfig::kWebProxyAutoDiscoveryUrl,
              ip_config.web_proxy_auto_discovery_url, &ip_config_dict);
    onc.Set(::onc::network_config::kStaticIPConfig, std::move(ip_config_dict));
  }

  if (properties->auto_connect) {
    NetworkTypePattern type_pattern = MojoTypeToPattern(type);
    if (type_pattern.Equals(NetworkTypePattern::Cellular()) ||
        type_pattern.Equals(NetworkTypePattern::VPN()) ||
        type_pattern.Equals(NetworkTypePattern::WiFi())) {
      // Note: All type dicts use the same kAutoConnect key.
      type_dict.Set(::onc::wifi::kAutoConnect, properties->auto_connect->value);
    }
  }

  if (!type_dict.empty()) {
    onc.Set(onc_type, std::move(type_dict));
  }

  return onc;
}

mojom::NetworkCertificatePtr GetMojoCert(
    const NetworkCertificateHandler::Certificate& cert,
    mojom::CertificateType type) {
  auto result = mojom::NetworkCertificate::New();
  result->type = type;
  result->hash = cert.hash;
  result->issued_by = cert.issued_by;
  result->issued_to = cert.issued_to;
  result->available_for_network_auth = cert.available_for_network_auth;
  result->device_wide = cert.device_wide;
  if (type == mojom::CertificateType::kServerCA)
    result->pem_or_id = cert.pem;
  if (type == mojom::CertificateType::kUserCert)
    result->pem_or_id = cert.pkcs11_id;
  return result;
}

mojom::TrafficCounterSource ConvertToTrafficCounterSourceEnum(
    const std::string& source) {
  if (source == shill::kTrafficCounterSourceUnknown) {
    return mojom::TrafficCounterSource::kUnknown;
  }
  if (source == shill::kTrafficCounterSourceChrome) {
    return mojom::TrafficCounterSource::kChrome;
  }
  if (source == shill::kTrafficCounterSourceUser) {
    return mojom::TrafficCounterSource::kUser;
  }
  if (source == shill::kTrafficCounterSourceArc) {
    return mojom::TrafficCounterSource::kArc;
  }
  if (source == shill::kTrafficCounterSourceCrosvm) {
    return mojom::TrafficCounterSource::kCrosvm;
  }
  if (source == shill::kTrafficCounterSourcePluginvm) {
    return mojom::TrafficCounterSource::kPluginvm;
  }
  if (source == shill::kTrafficCounterSourceUpdateEngine) {
    return mojom::TrafficCounterSource::kUpdateEngine;
  }
  if (source == shill::kTrafficCounterSourceVpn) {
    return mojom::TrafficCounterSource::kVpn;
  }
  if (source == shill::kTrafficCounterSourceSystem) {
    return mojom::TrafficCounterSource::kSystem;
  }
  if (source == shill::kTrafficCounterSourceBorealisVM) {
    return mojom::TrafficCounterSource::kPluginvm;
  }
  if (source == shill::kTrafficCounterSourceBruschettaVM) {
    return mojom::TrafficCounterSource::kPluginvm;
  }
  if (source == shill::kTrafficCounterSourceCrostiniVM) {
    return mojom::TrafficCounterSource::kPluginvm;
  }
  if (source == shill::kTrafficCounterSourceParallelsVM) {
    return mojom::TrafficCounterSource::kPluginvm;
  }
  if (source == shill::kTrafficCounterSourceTethering) {
    return mojom::TrafficCounterSource::kChrome;
  }
  if (source == shill::kTrafficCounterSourceWiFiDirect) {
    return mojom::TrafficCounterSource::kChrome;
  }
  if (source == shill::kTrafficCounterSourceWiFiLOHS) {
    return mojom::TrafficCounterSource::kChrome;
  }
  NOTREACHED_IN_MIGRATION() << "Unknown traffic counter source: " << source;
  return mojom::TrafficCounterSource::kUnknown;
}

bool GetDnsQueriesMonitoredValue() {
  if (!NetworkHandler::IsInitialized() ||
      !NetworkHandler::Get()->network_metadata_store()) {
    return false;
  }

  NetworkMetadataStore* store = NetworkHandler::Get()->network_metadata_store();
  return store->secure_dns_templates_with_identifiers_active();
}

bool GetReportXdrEventsEnabledValue() {
  if (!NetworkHandler::IsInitialized() ||
      !NetworkHandler::Get()->network_metadata_store()) {
    return false;
  }

  NetworkMetadataStore* store = NetworkHandler::Get()->network_metadata_store();
  return store->report_xdr_events_enabled();
}

// Creates a new APN list by prepending the new |apn| to existing APNs
// associated with the |network_guid|. If |enable_exclusively| is true, the new
// |apn| will be enabled and all other apns will be disabled. Returns nullopt on
// invalid list creation.
std::optional<base::Value::List> CopyExistingAndPrependNewApn(
    const std::string network_guid,
    mojom::ApnPropertiesPtr& apn,
    bool enable_exclusively) {
  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();
  DCHECK(network_metadata_store);

  base::Value::List apn_list;

  if (const base::Value::List* old_custom_apns =
          network_metadata_store->GetCustomApnList(network_guid)) {
    if (old_custom_apns->size() >= mojom::kMaxNumCustomApns) {
      NET_LOG(ERROR) << "Cannot create new custom APN for network: "
                     << network_guid
                     << ". Network already has the max amount allowed: "
                     << mojom::kMaxNumCustomApns;
      return std::nullopt;
    }

    apn_list = old_custom_apns->Clone();
  }

  if (enable_exclusively) {
    apn->state = mojom::ApnState::kEnabled;
    for (base::Value& apn_properties : apn_list) {
      apn_properties.GetDict().Set(::onc::cellular_apn::kState,
                                   ::onc::cellular_apn::kStateDisabled);
    }
  }

  // Set unique Id for custom APNs
  apn->id = base::Token::CreateRandom().ToString();
  // Insert the new custom APN at the beginning of the list to store them by
  // insertion order
  apn_list.Insert(apn_list.begin(), base::Value(MojoApnToOnc(*apn)));

  NET_LOG(USER) << "Setting custom APNs for: " << network_guid << ": "
                << apn_list.size();

  if (!DoesDefaultApnExist(apn_list) &&
      apn->state == mojom::ApnState::kEnabled) {
    NET_LOG(ERROR) << "Cannot create new custom APN in enabled state without a"
                   << "default type if no custom APN with a default type exist"
                   << "yet.";
    return std::nullopt;
  }

  return apn_list;
}

}  // namespace

CrosNetworkConfig::CrosNetworkConfig()
    : CrosNetworkConfig(
          NetworkHandler::Get()->network_state_handler(),
          NetworkHandler::Get()->network_device_handler(),
          NetworkHandler::Get()->cellular_inhibitor(),
          NetworkHandler::Get()->cellular_esim_profile_handler(),
          NetworkHandler::Get()->managed_network_configuration_handler(),
          NetworkHandler::Get()->network_connection_handler(),
          NetworkHandler::Get()->network_certificate_handler(),
          NetworkHandler::Get()->network_profile_handler(),
          NetworkHandler::Get()->technology_state_controller()) {}

CrosNetworkConfig::CrosNetworkConfig(
    NetworkStateHandler* network_state_handler,
    NetworkDeviceHandler* network_device_handler,
    CellularInhibitor* cellular_inhibitor,
    CellularESimProfileHandler* cellular_esim_profile_handler,
    ManagedNetworkConfigurationHandler* network_configuration_handler,
    NetworkConnectionHandler* network_connection_handler,
    NetworkCertificateHandler* network_certificate_handler,
    NetworkProfileHandler* network_profile_handler,
    TechnologyStateController* technology_state_controller)
    : network_state_handler_(network_state_handler),
      network_device_handler_(network_device_handler),
      cellular_inhibitor_(cellular_inhibitor),
      cellular_esim_profile_handler_(cellular_esim_profile_handler),
      network_configuration_handler_(network_configuration_handler),
      network_connection_handler_(network_connection_handler),
      network_certificate_handler_(network_certificate_handler),
      network_profile_handler_(network_profile_handler),
      technology_state_controller_(technology_state_controller) {
  CHECK(network_state_handler);

  // Start observing the `network_state_handler_` so we know to unset our local
  // pointer to it when it's destroyed.
  network_state_handler_observer_.Observe(network_state_handler_.get());

  // Start observing the `network_configuration_handler_` so we know to unset
  // our local pointer to it when it's destroyed.
  if (network_configuration_handler_) {
    network_configuration_handler_->AddObserver(this);
  }

  const std::optional<std::string_view> serial_number =
      system::StatisticsProvider::GetInstance()->GetMachineID();
  if (!serial_number || serial_number->empty()) {
    LOG(WARNING) << "Serial number not set.";
  } else {
    serial_number_ = std::string(serial_number.value());
  }
}

CrosNetworkConfig::~CrosNetworkConfig() {
  if (network_certificate_handler_ &&
      network_certificate_handler_->HasObserver(this)) {
    network_certificate_handler_->RemoveObserver(this);
  }
  if (cellular_inhibitor_ && cellular_inhibitor_->HasObserver(this))
    cellular_inhibitor_->RemoveObserver(this);
  if (network_configuration_handler_ &&
      network_configuration_handler_->HasObserver(this)) {
    network_configuration_handler_->RemoveObserver(this);
  }
}

void CrosNetworkConfig::BindReceiver(
    mojo::PendingReceiver<mojom::CrosNetworkConfig> receiver) {
  NET_LOG(EVENT) << "CrosNetworkConfig::BindReceiver()";
  receivers_.Add(this, std::move(receiver));
}

void CrosNetworkConfig::AddObserver(
    mojo::PendingRemote<mojom::CrosNetworkConfigObserver> observer) {
  if (network_certificate_handler_ &&
      !network_certificate_handler_->HasObserver(this)) {
    network_certificate_handler_->AddObserver(this);
  }
  if (cellular_inhibitor_ && !cellular_inhibitor_->HasObserver(this))
    cellular_inhibitor_->AddObserver(this);
  observers_.Add(std::move(observer));
}

void CrosNetworkConfig::GetNetworkState(const std::string& guid,
                                        GetNetworkStateCallback callback) {
  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (!network) {
    NET_LOG(ERROR) << "Network not found: " << guid;
    std::move(callback).Run(nullptr);
    return;
  }
  if (network->type() == shill::kTypeEthernetEap) {
    NET_LOG(ERROR) << "EthernetEap not supported for GetNetworkState";
    std::move(callback).Run(nullptr);
    return;
  }
  std::move(callback).Run(NetworkStateToMojo(network_state_handler_,
                                             cellular_esim_profile_handler_,
                                             vpn_providers_, network));
}

void CrosNetworkConfig::GetNetworkStateList(
    mojom::NetworkFilterPtr filter,
    GetNetworkStateListCallback callback) {
  NetworkStateHandler::NetworkStateList networks;
  NetworkTypePattern pattern = MojoTypeToPattern(filter->network_type);
  switch (filter->filter) {
    case mojom::FilterType::kActive:
      network_state_handler_->GetActiveNetworkListByType(pattern, &networks);
      if (filter->limit > 0 &&
          static_cast<int>(networks.size()) > filter->limit)
        networks.resize(filter->limit);
      break;
    case mojom::FilterType::kVisible:
      network_state_handler_->GetNetworkListByType(
          pattern, /*configured_only=*/false, /*visible_only=*/true,
          filter->limit, &networks);
      break;
    case mojom::FilterType::kConfigured:
      network_state_handler_->GetNetworkListByType(
          pattern, /*configured_only=*/true, /*visible_only=*/false,
          filter->limit, &networks);
      break;
    case mojom::FilterType::kAll:
      network_state_handler_->GetNetworkListByType(
          pattern, /*configured_only=*/false, /*visible_only=*/false,
          filter->limit, &networks);
      break;
  }
  std::vector<mojom::NetworkStatePropertiesPtr> result;
  for (const NetworkState* network : networks) {
    if (network->type() == shill::kTypeEthernetEap) {
      // EthernetEap is used by Shill to store EAP properties and does not
      // represent a separate network service.
      continue;
    }
    mojom::NetworkStatePropertiesPtr mojo_network = NetworkStateToMojo(
        network_state_handler_, cellular_esim_profile_handler_, vpn_providers_,
        network);
    if (mojo_network)
      result.emplace_back(std::move(mojo_network));
  }
  std::move(callback).Run(std::move(result));
}

void CrosNetworkConfig::GetDeviceStateList(
    GetDeviceStateListCallback callback) {
  NetworkStateHandler::DeviceStateList devices;
  network_state_handler_->GetDeviceList(&devices);
  std::vector<mojom::DeviceStatePropertiesPtr> result;
  for (const DeviceState* device : devices) {
    mojom::DeviceStateType technology_state =
        GetMojoDeviceStateType(network_state_handler_->GetTechnologyState(
            NetworkTypePattern::Primitive(device->type())));
    if (technology_state == mojom::DeviceStateType::kUnavailable) {
      NET_LOG(ERROR) << "Device state unavailable: " << device->name();
      continue;
    }
    mojom::DeviceStatePropertiesPtr mojo_device =
        DeviceStateToMojo(device, network_state_handler_, cellular_inhibitor_,
                          technology_state, serial_number_);
    if (mojo_device)
      result.emplace_back(std::move(mojo_device));
  }

  // Handle VPN state separately because VPN is not considered a device by shill
  // and thus will not be included in the |devices| list returned by network
  // state handler. In the UI code, it is treated as a "device" for consistency.
  // In the UI code, knowing whether a device is prohibited or not is done by
  // checking |device_state| field of the DeviceStateProperties of the
  // corresponding device. A VPN device state is returned if built-in VPN
  // services are prohibited by policy even if no VPN services exist in order to
  // indicate that adding a VPN is prohibited in the UI.
  if (network_state_handler_->FirstNetworkByType(NetworkTypePattern::VPN()) ||
      IsVpnProhibited()) {
    result.emplace_back(GetVpnState());
  }

  std::move(callback).Run(std::move(result));
}

void CrosNetworkConfig::GetManagedProperties(
    const std::string& guid,
    GetManagedPropertiesCallback callback) {
  if (!network_configuration_handler_) {
    NET_LOG(ERROR) << "GetManagedProperties called with no handler";
    std::move(callback).Run(nullptr);
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (!network) {
    NET_LOG(ERROR) << "Network not found: " << guid;
    std::move(callback).Run(nullptr);
    return;
  }

  network_configuration_handler_->GetManagedProperties(
      LoginState::Get()->primary_user_hash(), network->path(),
      base::BindOnce(&CrosNetworkConfig::OnGetManagedProperties,
                     weak_factory_.GetWeakPtr(), std::move(callback), guid));
}

void CrosNetworkConfig::OnGetManagedProperties(
    GetManagedPropertiesCallback callback,
    std::string guid,
    const std::string& service_path,
    std::optional<base::Value::Dict> properties,
    std::optional<std::string> error) {
  if (!properties) {
    NET_LOG(ERROR) << "GetManagedProperties failed for: " << guid
                   << " Error: " << error.value_or("Failed");
    std::move(callback).Run(nullptr);
    return;
  }
  const NetworkState* network_state =
      network_state_handler_->GetNetworkState(service_path);
  if (!network_state) {
    NET_LOG(ERROR) << "Network not found: " << service_path;
    std::move(callback).Run(nullptr);
    return;
  }
  mojom::ManagedPropertiesPtr managed_properties = ManagedPropertiesToMojo(
      network_state_handler_, cellular_esim_profile_handler_, network_state,
      vpn_providers_, &properties.value());

  if (managed_properties->type == mojom::NetworkType::kCellular) {
    std::vector<mojom::ApnPropertiesPtr> custom_apn_list =
        GetCustomApnList(guid);
    if (!custom_apn_list.empty()) {
      managed_properties->type_properties->get_cellular()->custom_apn_list =
          std::move(custom_apn_list);
    }
  }

  // For Ethernet networks with no authentication, check for a separate
  // EthernetEAP configuration.
  const NetworkState* eap_state = nullptr;
  if (managed_properties->type == mojom::NetworkType::kEthernet) {
    mojom::ManagedEthernetPropertiesPtr& ethernet =
        managed_properties->type_properties->get_ethernet();
    if (!ethernet->authentication || ethernet->authentication->active_value ==
                                         ::onc::ethernet::kAuthenticationNone) {
      eap_state = network_state_handler_->GetEAPForEthernet(
          network_state->path(), /*connected_only=*/false);
    }
  }
  if (!eap_state) {
    // No EAP properties, return the managed properties as-is.
    std::move(callback).Run(std::move(managed_properties));
    return;
  }

  // Request the EAP state. On success the EAP state will be applied to
  // |managed_properties| and returned. On failure |managed_properties| will
  // be returned as-is.
  NET_LOG(DEBUG) << "Requesting EAP state for: " + service_path
                 << " from: " << eap_state->path();
  network_configuration_handler_->GetManagedProperties(
      LoginState::Get()->primary_user_hash(), eap_state->path(),
      base::BindOnce(&CrosNetworkConfig::OnGetManagedPropertiesEap,
                     weak_factory_.GetWeakPtr(), std::move(callback),
                     std::move(managed_properties)));
}

void CrosNetworkConfig::OnGetManagedPropertiesEap(
    GetManagedPropertiesCallback callback,
    mojom::ManagedPropertiesPtr managed_properties,
    const std::string& service_path,
    std::optional<base::Value::Dict> eap_properties,
    std::optional<std::string> error) {
  if (eap_properties) {
    // Copy the EAP properties to |managed_properties| before sending.
    const base::Value::Dict* ethernet_dict =
        eap_properties->FindDict(::onc::network_config::kEthernet);
    if (ethernet_dict) {
      auto ethernet = mojom::ManagedEthernetProperties::New();
      ethernet->authentication =
          GetManagedString(ethernet_dict, ::onc::ethernet::kAuthentication);
      ethernet->eap =
          GetManagedEAPProperties(ethernet_dict, ::onc::ethernet::kEAP);
      managed_properties->type_properties =
          mojom::NetworkTypeManagedProperties::NewEthernet(std::move(ethernet));
    }
  }

  std::move(callback).Run(std::move(managed_properties));
}

void CrosNetworkConfig::SetProperties(const std::string& guid,
                                      mojom::ConfigPropertiesPtr properties,
                                      SetPropertiesCallback callback) {
  if (!network_configuration_handler_) {
    NET_LOG(ERROR) << "SetProperties called with no handler";
    std::move(callback).Run(false, kErrorNotReady);
    return;
  }
  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "SetProperties called with unconfigured network: "
                   << guid;
    std::move(callback).Run(false, kErrorNetworkUnavailable);
    return;
  }

  // If EthernetEAP properties exist, and properties.ethernet.eap is provided,
  // apply the configuration to the EthernetEAP service. Currently we only
  // support configuration of EAP properties or other properties (e.g IP
  // Config), not both.
  if (network->type() == shill::kTypeEthernet &&
      properties->type_config->is_ethernet() &&
      properties->type_config->get_ethernet()->eap) {
    const NetworkState* eap_state = network_state_handler_->GetEAPForEthernet(
        network->path(), /*connected_only=*/false);
    if (!eap_state) {
      NET_LOG(ERROR)
          << "SetProperties called with ethernet.eap but no EAP config: "
          << guid;
      std::move(callback).Run(false, kErrorNetworkUnavailable);
      return;
    }
    network = eap_state;
  }

  if (!features::IsApnRevampEnabled() &&
      network->type() == shill::kTypeCellular &&
      properties->type_config->is_cellular()) {
    UpdateCustomApnList(network, properties.get());
  }

  if (properties->type_config->is_cellular() &&
      properties->type_config->get_cellular()->text_message_allow_state) {
    const bool allow_text_messages =
        properties->type_config->get_cellular()
            ->text_message_allow_state->allow_text_messages;
    UserTextMessageSuppressionState state =
        allow_text_messages ? UserTextMessageSuppressionState::kAllow
                            : UserTextMessageSuppressionState::kSuppress;
    NetworkHandler::Get()
        ->network_metadata_store()
        ->SetUserTextMessageSuppressionState(guid, state);
  }

  std::optional<base::Value::Dict> onc =
      GetOncFromConfigProperties(properties.get(), guid);
  if (!onc) {
    NET_LOG(ERROR) << "Bad ONC Configuration for " << guid;
    std::move(callback).Run(false, kErrorInvalidONCConfiguration);
    return;
  }

  SetPropertiesInternal(guid, *network, std::move(*onc), std::move(callback));
}

void CrosNetworkConfig::SetPropertiesInternal(const std::string& guid,
                                              const NetworkState& network,
                                              base::Value::Dict onc,
                                              SetPropertiesCallback callback) {
  NET_LOG(DEBUG) << "Configuring properties for " << guid << ": " << onc;

  if (features::IsApnRevampAndAllowApnModificationPolicyEnabled()) {
    const base::Value::Dict* global_policy_dict =
        network_configuration_handler_->GetGlobalConfigFromPolicy(
            /*userhash=*/std::string());
    bool allow_apn_modification = GetBoolean(
        global_policy_dict, ::onc::global_network_config::kAllowAPNModification,
        /*value_if_key_missing_from_dict=*/true);
    const base::Value::List* existing_custom_apn_list =
        NetworkHandler::Get()->network_metadata_store()->GetCustomApnList(guid);
    if (allow_apn_modification && existing_custom_apn_list) {
      chromeos::onc::FillInCellularCustomAPNListFieldsInOncObject(
          chromeos::onc::kNetworkConfigurationSignature, onc,
          existing_custom_apn_list);
    }
  }

  int callback_id = callback_id_++;
  set_properties_callbacks_[callback_id] = std::move(callback);

  // If the profile path is empty the network is not saved, so we need to call
  // CreateConfiguration(). This can happen for EthernetEAP where a default
  // service is generated by Shill but may not be saved.
  if (network.profile_path().empty()) {
    NET_LOG(USER) << "Configuring properties for " << guid
                  << " (no profile entry set)";
    std::string user_id_hash = LoginState::Get()->primary_user_hash();

    network_configuration_handler_->CreateConfiguration(
        user_id_hash, onc,
        base::BindOnce(&CrosNetworkConfig::SetPropertiesConfigureSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::SetPropertiesFailure,
                       weak_factory_.GetWeakPtr(), guid, callback_id));
    return;
  }

  network_configuration_handler_->SetProperties(
      network.path(), onc,
      base::BindOnce(&CrosNetworkConfig::SetPropertiesSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::SetPropertiesFailure,
                     weak_factory_.GetWeakPtr(), guid, callback_id));
}

void CrosNetworkConfig::SetPropertiesSuccess(int callback_id) {
  auto iter = set_properties_callbacks_.find(callback_id);
  DCHECK(iter != set_properties_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true, /*message=*/"");
  set_properties_callbacks_.erase(iter);
}

void CrosNetworkConfig::SetPropertiesConfigureSuccess(
    int callback_id,
    const std::string& service_path,
    const std::string& guid) {
  auto iter = set_properties_callbacks_.find(callback_id);
  DCHECK(iter != set_properties_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true, /*message=*/"");
  set_properties_callbacks_.erase(iter);
}

void CrosNetworkConfig::SetPropertiesFailure(const std::string& guid,
                                             int callback_id,
                                             const std::string& error_name) {
  auto iter = set_properties_callbacks_.find(callback_id);
  DCHECK(iter != set_properties_callbacks_.end());
  NET_LOG(ERROR) << "Failed to set network properties: " << guid
                 << " Error: " << error_name;
  std::move(iter->second).Run(/*success=*/false, error_name);
  set_properties_callbacks_.erase(iter);
}

void CrosNetworkConfig::ConfigureNetwork(mojom::ConfigPropertiesPtr properties,
                                         bool shared,
                                         ConfigureNetworkCallback callback) {
  if (!network_configuration_handler_) {
    NET_LOG(ERROR) << "Configure called with no handler";
    std::move(callback).Run(/*guid=*/std::nullopt, kErrorNotReady);
    return;
  }

  if (!shared && UserManager::Get()->GetPrimaryUser() !=
                     UserManager::Get()->GetActiveUser()) {
    NET_LOG(ERROR)
        << "Attempt to set unshared configuration from non primary user";
    std::move(callback).Run(/*guid=*/std::nullopt, kErrorAccessToSharedConfig);
    return;
  }

  if (properties->type_config->is_vpn() &&
      network_configuration_handler_->IsProhibitedFromConfiguringVpn()) {
    std::move(callback).Run(/*guid=*/std::nullopt,
                            kErrorUserIsProhibitedFromConfiguringVpn);
    return;
  }

  std::optional<base::Value::Dict> onc =
      GetOncFromConfigProperties(properties.get(), /*guid=*/std::nullopt);
  if (!onc) {
    std::move(callback).Run(/*guid=*/std::nullopt,
                            kErrorInvalidONCConfiguration);
    return;
  }

  std::string user_id_hash =
      shared ? "" : LoginState::Get()->primary_user_hash();

  int callback_id = callback_id_++;
  configure_network_callbacks_[callback_id] = std::move(callback);

  network_configuration_handler_->CreateConfiguration(
      user_id_hash, onc.value(),
      base::BindOnce(&CrosNetworkConfig::ConfigureNetworkSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::ConfigureNetworkFailure,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void CrosNetworkConfig::ConfigureNetworkSuccess(int callback_id,
                                                const std::string& service_path,
                                                const std::string& guid) {
  auto iter = configure_network_callbacks_.find(callback_id);
  DCHECK(iter != configure_network_callbacks_.end());
  std::move(iter->second).Run(guid, /*message=*/"");
  configure_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::ConfigureNetworkFailure(int callback_id,
                                                const std::string& error_name) {
  auto iter = configure_network_callbacks_.find(callback_id);
  DCHECK(iter != configure_network_callbacks_.end());
  DCHECK(iter->second);
  NET_LOG(ERROR) << "Failed to configure network. Error: " << error_name;
  std::move(iter->second).Run(/*guid=*/std::nullopt, error_name);
  configure_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::ForgetNetwork(const std::string& guid,
                                      ForgetNetworkCallback callback) {
  if (!network_configuration_handler_) {
    NET_LOG(ERROR) << "ForgetNetwork called with no handler";
    std::move(callback).Run(/*success=*/false);
    return;
  }
  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "ForgetNetwork called with unconfigured network: "
                   << guid;
    std::move(callback).Run(/*success=*/false);
    return;
  }

  bool allow_forget_shared_config = true;
  ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_UNKNOWN;
  std::string user_id_hash = LoginState::Get()->primary_user_hash();
  if (network_configuration_handler_->FindPolicyByGUID(user_id_hash, guid,
                                                       &onc_source)) {
    if (onc_source == ::onc::ONC_SOURCE_USER_POLICY) {
      // Prevent a policy controlled configuration removal.
      std::move(callback).Run(/*success=*/false);
      return;
    }
    if (onc_source == ::onc::ONC_SOURCE_DEVICE_POLICY)
      allow_forget_shared_config = false;
  }

  int callback_id = callback_id_++;
  forget_network_callbacks_[callback_id] = std::move(callback);

  if (allow_forget_shared_config) {
    network_configuration_handler_->RemoveConfiguration(
        network->path(),
        base::BindOnce(&CrosNetworkConfig::ForgetNetworkSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::ForgetNetworkFailure,
                       weak_factory_.GetWeakPtr(), guid, callback_id));
  } else {
    network_configuration_handler_->RemoveConfigurationFromCurrentProfile(
        network->path(),
        base::BindOnce(&CrosNetworkConfig::ForgetNetworkSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::ForgetNetworkFailure,
                       weak_factory_.GetWeakPtr(), guid, callback_id));
  }
}

void CrosNetworkConfig::ForgetNetworkSuccess(int callback_id) {
  auto iter = forget_network_callbacks_.find(callback_id);
  DCHECK(iter != forget_network_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true);
  forget_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::ForgetNetworkFailure(const std::string& guid,
                                             int callback_id,
                                             const std::string& error_name) {
  auto iter = forget_network_callbacks_.find(callback_id);
  DCHECK(iter != forget_network_callbacks_.end());
  NET_LOG(ERROR) << "Failed to forget network: " << guid
                 << " Error: " << error_name;
  std::move(iter->second).Run(/*success=*/false);
  forget_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::SetNetworkTypeEnabledState(
    mojom::NetworkType type,
    bool enabled,
    SetNetworkTypeEnabledStateCallback callback) {
  if (!NetworkTypeCanBeDisabled(type)) {
    std::move(callback).Run(/*success=*/false);
    return;
  }
  NetworkTypePattern pattern = MojoTypeToPattern(type);
  if (!network_state_handler_->IsTechnologyAvailable(pattern)) {
    NET_LOG(ERROR) << "Technology unavailable: " << type;
    std::move(callback).Run(/*success=*/false);
    return;
  }
  if (network_state_handler_->IsTechnologyProhibited(pattern)) {
    NET_LOG(ERROR) << "Technology prohibited: " << type;
    std::move(callback).Run(/*success=*/false);
    return;
  }

  NET_LOG(USER) << __func__ << " " << type << ":" << enabled;

  // Set the technologies enabled state and return true. The call to Shill does
  // not have a 'success' callback (and errors are already logged).
  technology_state_controller_->SetTechnologiesEnabled(
      pattern, enabled, network_handler::ErrorCallback());
  std::move(callback).Run(/*success=*/true);
}

void CrosNetworkConfig::SetCellularSimState(
    mojom::CellularSimStatePtr sim_state,
    SetCellularSimStateCallback callback) {
  const DeviceState* device_state =
      network_state_handler_->GetDeviceStateByType(
          NetworkTypePattern::Cellular());
  if (!device_state || device_state->IsSimAbsent()) {
    std::move(callback).Run(/*success=*/false);
    return;
  }

  const std::string& lock_type = device_state->sim_lock_type();

  if (lock_type == shill::kSIMLockNetworkPin) {
    NET_LOG(ERROR) << "SetCellularSimState: carrier locked sim.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  // When unblocking a PUK locked SIM, a new PIN must be provided.
  if (lock_type == shill::kSIMLockPuk && !sim_state->new_pin) {
    NET_LOG(ERROR) << "SetCellularSimState: PUK locked and no pin provided.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  int callback_id = callback_id_++;
  set_cellular_sim_state_callbacks_[callback_id] = std::move(callback);

  if (lock_type == shill::kSIMLockPuk) {
    // Unblock a PUK locked SIM.
    network_device_handler_->UnblockPin(
        device_state->path(), sim_state->current_pin_or_puk,
        *sim_state->new_pin,
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateFailure,
                       weak_factory_.GetWeakPtr(), callback_id));
    return;
  }

  if (lock_type == shill::kSIMLockPin) {
    // Unlock locked SIM.
    network_device_handler_->EnterPin(
        device_state->path(), sim_state->current_pin_or_puk,
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateFailure,
                       weak_factory_.GetWeakPtr(), callback_id));
    return;
  }

  if (sim_state->new_pin) {
    // Change the SIM PIN.
    network_device_handler_->ChangePin(
        device_state->path(), sim_state->current_pin_or_puk,
        *sim_state->new_pin,
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateSuccess,
                       weak_factory_.GetWeakPtr(), callback_id),
        base::BindOnce(&CrosNetworkConfig::SetCellularSimStateFailure,
                       weak_factory_.GetWeakPtr(), callback_id));
    return;
  }

  // Enable or disable SIM locking.
  network_device_handler_->RequirePin(
      device_state->path(), sim_state->require_pin,
      sim_state->current_pin_or_puk,
      base::BindOnce(&CrosNetworkConfig::SetCellularSimStateSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::SetCellularSimStateFailure,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void CrosNetworkConfig::SetCellularSimStateSuccess(int callback_id) {
  auto iter = set_cellular_sim_state_callbacks_.find(callback_id);
  DCHECK(iter != set_cellular_sim_state_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true);
  set_cellular_sim_state_callbacks_.erase(iter);
}

void CrosNetworkConfig::SetCellularSimStateFailure(
    int callback_id,
    const std::string& error_name) {
  auto iter = set_cellular_sim_state_callbacks_.find(callback_id);
  DCHECK(iter != set_cellular_sim_state_callbacks_.end());
  std::move(iter->second).Run(/*success=*/false);
  set_cellular_sim_state_callbacks_.erase(iter);
}

void CrosNetworkConfig::SelectCellularMobileNetwork(
    const std::string& guid,
    const std::string& network_id,
    SelectCellularMobileNetworkCallback callback) {
  const DeviceState* device_state = nullptr;
  const NetworkState* network_state =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  if (network_state) {
    device_state =
        network_state_handler_->GetDeviceState(network_state->device_path());
  }
  if (!device_state) {
    std::move(callback).Run(/*success=*/false);
    return;
  }

  int callback_id = callback_id_++;
  select_cellular_mobile_network_callbacks_[callback_id] = std::move(callback);

  network_device_handler_->RegisterCellularNetwork(
      device_state->path(), network_id,
      base::BindOnce(&CrosNetworkConfig::SelectCellularMobileNetworkSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::SelectCellularMobileNetworkFailure,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void CrosNetworkConfig::SelectCellularMobileNetworkSuccess(int callback_id) {
  auto iter = select_cellular_mobile_network_callbacks_.find(callback_id);
  DCHECK(iter != select_cellular_mobile_network_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true);
  select_cellular_mobile_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::SelectCellularMobileNetworkFailure(
    int callback_id,
    const std::string& error_name) {
  auto iter = select_cellular_mobile_network_callbacks_.find(callback_id);
  DCHECK(iter != select_cellular_mobile_network_callbacks_.end());
  std::move(iter->second).Run(/*success=*/false);
  select_cellular_mobile_network_callbacks_.erase(iter);
}

void CrosNetworkConfig::UpdateCustomApnList(
    const NetworkState* network,
    const mojom::ConfigProperties* properties) {
  DCHECK(!features::IsApnRevampEnabled());

  const mojom::CellularConfigProperties& cellular_config =
      *properties->type_config->get_cellular();
  if (!cellular_config.apn) {
    return;
  }
  const DeviceState* device =
      network_state_handler_->GetDeviceState(network->device_path());
  if (!device) {
    // Unexpected, but see note in NetworkStateToMojo.
    NET_LOG(DEBUG) << "Cellular device is not available for APN list: "
                   << network->device_path();
    return;
  }
  // Do not update custom APN list if APN is in device APN list.
  if (device->HasAPN(cellular_config.apn->access_point_name)) {
    return;
  }

  // The pre-revamp UI only supports setting a single custom apn.
  base::Value::List custom_apn_list;
  custom_apn_list.Append(MojoApnToOnc(*cellular_config.apn));

  NET_LOG(DEBUG) << "Saving Custom APN entry for " << network->guid();
  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();
  network_metadata_store->SetCustomApnList(network->guid(),
                                           std::move(custom_apn_list));
}

std::vector<mojom::ApnPropertiesPtr> CrosNetworkConfig::GetCustomApnList(
    const std::string& guid) {
  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();
  std::vector<mojom::ApnPropertiesPtr> mojo_custom_apns;
  const base::Value::List* custom_apn_list =
      network_metadata_store->GetCustomApnList(guid);
  if (!custom_apn_list) {
    return mojo_custom_apns;
  }
  for (const auto& apn : *custom_apn_list) {
    DCHECK(apn.is_dict());

    bool is_apn_revamp_enabled = features::IsApnRevampEnabled();
    mojom::ApnPropertiesPtr mojo_apn =
        GetApnProperties(apn.GetDict(), is_apn_revamp_enabled);
    if (is_apn_revamp_enabled) {
      mojo_apn->state = OncApnStateTypeToMojo(base::OptionalToPtr(
          GetString(&apn.GetDict(), ::onc::cellular_apn::kState)));
    }

    mojo_custom_apns.push_back(std::move(mojo_apn));
  }
  return mojo_custom_apns;
}

void CrosNetworkConfig::RequestNetworkScan(mojom::NetworkType type) {
  network_state_handler_->RequestScan(MojoTypeToPattern(type));
}

void CrosNetworkConfig::GetGlobalPolicy(GetGlobalPolicyCallback callback) {
  auto result = mojom::GlobalPolicy::New();

  // Network configuration handler can be nullptr in tests.
  if (!network_configuration_handler_) {
    std::move(callback).Run(std::move(result));
    return;
  }

  // Global network configuration policy values come from the device policy.
  const base::Value::Dict* global_policy_dict =
      network_configuration_handler_->GetGlobalConfigFromPolicy(
          /*userhash=*/std::string());
  if (!global_policy_dict) {
    std::move(callback).Run(std::move(result));
    return;
  }

  // Sets mojom global policy results directly from the |global_policy_dict|.
  // If there is no key (in the case of non-managed devices), the default
  // mojom::GlobalPolicy() boolean value(s) specified explicitly in
  // cros_network_config.mojom is used instead.
  if (features::IsApnRevampAndAllowApnModificationPolicyEnabled()) {
    result->allow_apn_modification = GetBoolean(
        global_policy_dict, ::onc::global_network_config::kAllowAPNModification,
        /*value_if_key_missing_from_dict=*/result->allow_apn_modification);
  }
  result->allow_cellular_sim_lock = GetBoolean(
      global_policy_dict, ::onc::global_network_config::kAllowCellularSimLock,
      /*value_if_key_missing_from_dict=*/result->allow_cellular_sim_lock);
  result->allow_cellular_hotspot = GetBoolean(
      global_policy_dict, ::onc::global_network_config::kAllowCellularHotspot,
      /*value_if_key_missing_from_dict=*/result->allow_cellular_hotspot);
  result->allow_only_policy_cellular_networks =
      GetBoolean(global_policy_dict,
                 ::onc::global_network_config::kAllowOnlyPolicyCellularNetworks,
                 /*value_if_key_missing_from_dict=*/
                 result->allow_only_policy_cellular_networks);
  result->allow_only_policy_networks_to_autoconnect = GetBoolean(
      global_policy_dict,
      ::onc::global_network_config::kAllowOnlyPolicyNetworksToAutoconnect,
      /*value_if_key_missing_from_dict=*/
      result->allow_only_policy_networks_to_autoconnect);
  result->allow_only_policy_wifi_networks_to_connect =
      GetBoolean(global_policy_dict,
                 ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnect,
                 /*value_if_key_missing_from_dict=*/
                 result->allow_only_policy_wifi_networks_to_connect);
  result->allow_only_policy_wifi_networks_to_connect_if_available = GetBoolean(
      global_policy_dict,
      ::onc::global_network_config::kAllowOnlyPolicyWiFiToConnectIfAvailable,
      /*value_if_key_missing_from_dict=*/
      result->allow_only_policy_wifi_networks_to_connect_if_available);
  result->dns_queries_monitored = GetDnsQueriesMonitoredValue();
  result->report_xdr_events_enabled = GetReportXdrEventsEnabledValue();

  std::optional<std::vector<std::string>> blocked_hex_ssids = GetStringList(
      global_policy_dict, ::onc::global_network_config::kBlockedHexSSIDs);
  if (blocked_hex_ssids) {
    result->blocked_hex_ssids = std::move(*blocked_hex_ssids);
  }

  if (policy_util::AreEphemeralNetworkPoliciesEnabled()) {
    result->recommended_values_are_ephemeral =
        GetBoolean(global_policy_dict,
                   ::onc::global_network_config::kRecommendedValuesAreEphemeral,
                   /*value_if_key_missing_from_dict=*/
                   result->recommended_values_are_ephemeral);
    result->user_created_network_configurations_are_ephemeral =
        GetBoolean(global_policy_dict,
                   ::onc::global_network_config::
                       kUserCreatedNetworkConfigurationsAreEphemeral,
                   /*value_if_key_missing_from_dict=*/
                   result->user_created_network_configurations_are_ephemeral);
  }

    std::string allow_text_messages_onc =
        GetString(global_policy_dict,
                  ::onc::global_network_config::kAllowTextMessages)
            .value_or(::onc::cellular::kTextMessagesUnset);
    if (allow_text_messages_onc == ::onc::cellular::kTextMessagesAllow) {
      result->allow_text_messages = mojom::SuppressionType::kAllow;
    } else if (allow_text_messages_onc ==
               ::onc::cellular::kTextMessagesSuppress) {
      result->allow_text_messages = mojom::SuppressionType::kSuppress;
    } else {
      result->allow_text_messages = mojom::SuppressionType::kUnset;
    }

  std::move(callback).Run(std::move(result));
}

void CrosNetworkConfig::StartConnect(const std::string& guid,
                                     StartConnectCallback callback) {
  std::string service_path = GetServicePathFromGuid(guid);
  if (service_path.empty()) {
    std::move(callback).Run(mojom::StartConnectResult::kInvalidGuid,
                            NetworkConnectionHandler::kErrorNotFound);
    return;
  }

  int callback_id = callback_id_++;
  start_connect_callbacks_[callback_id] = std::move(callback);

  network_connection_handler_->ConnectToNetwork(
      service_path,
      base::BindOnce(&CrosNetworkConfig::StartConnectSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::StartConnectFailure,
                     weak_factory_.GetWeakPtr(), callback_id),
      true /* check_error_state */, ConnectCallbackMode::ON_STARTED);
}

void CrosNetworkConfig::StartConnectSuccess(int callback_id) {
  auto iter = start_connect_callbacks_.find(callback_id);
  DCHECK(iter != start_connect_callbacks_.end());
  std::move(iter->second)
      .Run(mojom::StartConnectResult::kSuccess, std::string());
  start_connect_callbacks_.erase(iter);
}

void CrosNetworkConfig::StartConnectFailure(int callback_id,
                                            const std::string& error_name) {
  auto iter = start_connect_callbacks_.find(callback_id);
  DCHECK(iter != start_connect_callbacks_.end());
  mojom::StartConnectResult result;
  if (error_name == NetworkConnectionHandler::kErrorNotFound) {
    result = mojom::StartConnectResult::kInvalidGuid;
  } else if (error_name == NetworkConnectionHandler::kErrorConnected ||
             error_name == NetworkConnectionHandler::kErrorConnecting) {
    result = mojom::StartConnectResult::kInvalidState;
  } else if (error_name == NetworkConnectionHandler::kErrorConnectCanceled) {
    result = mojom::StartConnectResult::kCanceled;
  } else if (error_name == NetworkConnectionHandler::kErrorPassphraseRequired ||
             error_name == NetworkConnectionHandler::kErrorBadPassphrase ||
             error_name ==
                 NetworkConnectionHandler::kErrorCertificateRequired ||
             error_name ==
                 NetworkConnectionHandler::kErrorConfigurationRequired ||
             error_name ==
                 NetworkConnectionHandler::kErrorAuthenticationRequired ||
             error_name == NetworkConnectionHandler::kErrorCertLoadTimeout ||
             error_name == NetworkConnectionHandler::kErrorConfigureFailed) {
    result = mojom::StartConnectResult::kNotConfigured;
  } else if (error_name == NetworkConnectionHandler::kErrorBlockedByPolicy) {
    result = mojom::StartConnectResult::kBlocked;
  } else {
    result = mojom::StartConnectResult::kUnknown;
  }
  std::move(iter->second).Run(result, error_name);
  start_connect_callbacks_.erase(iter);
}

void CrosNetworkConfig::StartDisconnect(const std::string& guid,
                                        StartDisconnectCallback callback) {
  std::string service_path = GetServicePathFromGuid(guid);
  if (service_path.empty()) {
    std::move(callback).Run(/*success=*/false);
    return;
  }

  int callback_id = callback_id_++;
  start_disconnect_callbacks_[callback_id] = std::move(callback);

  network_connection_handler_->DisconnectNetwork(
      service_path,
      base::BindOnce(&CrosNetworkConfig::StartDisconnectSuccess,
                     weak_factory_.GetWeakPtr(), callback_id),
      base::BindOnce(&CrosNetworkConfig::StartDisconnectFailure,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void CrosNetworkConfig::StartDisconnectSuccess(int callback_id) {
  auto iter = start_disconnect_callbacks_.find(callback_id);
  DCHECK(iter != start_disconnect_callbacks_.end());
  std::move(iter->second).Run(/*success=*/true);
  start_disconnect_callbacks_.erase(iter);
}

void CrosNetworkConfig::StartDisconnectFailure(int callback_id,
                                               const std::string& error_name) {
  auto iter = start_disconnect_callbacks_.find(callback_id);
  DCHECK(iter != start_disconnect_callbacks_.end());
  std::move(iter->second).Run(/*success=*/false);
  start_disconnect_callbacks_.erase(iter);
}

void CrosNetworkConfig::SetVpnProviders(
    std::vector<mojom::VpnProviderPtr> providers) {
  vpn_providers_ = std::move(providers);
  for (auto& observer : observers_)
    observer->OnVpnProvidersChanged();
}

void CrosNetworkConfig::GetVpnProviders(GetVpnProvidersCallback callback) {
  std::move(callback).Run(mojo::Clone(vpn_providers_));
}

void CrosNetworkConfig::GetNetworkCertificates(
    GetNetworkCertificatesCallback callback) {
  const std::vector<NetworkCertificateHandler::Certificate>&
      handler_server_cas =
          network_certificate_handler_->server_ca_certificates();
  std::vector<mojom::NetworkCertificatePtr> server_cas;
  for (const auto& cert : handler_server_cas)
    server_cas.push_back(GetMojoCert(cert, mojom::CertificateType::kServerCA));

  std::vector<mojom::NetworkCertificatePtr> user_certs;
  const std::vector<NetworkCertificateHandler::Certificate>&
      handler_user_certs = network_certificate_handler_->client_certificates();
  for (const auto& cert : handler_user_certs)
    user_certs.push_back(GetMojoCert(cert, mojom::CertificateType::kUserCert));

  std::move(callback).Run(std::move(server_cas), std::move(user_certs));
}

void CrosNetworkConfig::GetAlwaysOnVpn(GetAlwaysOnVpnCallback callback) {
  const NetworkProfile* profile =
      network_profile_handler_->GetDefaultUserProfile();
  if (!profile) {
    NET_LOG(ERROR) << "GetAlwaysOnVpn: no user profile found";
    // No profile available, ensure the callback gets fired with always-on VPN
    // disabled.
    OnGetAlwaysOnVpn(std::move(callback), shill::kAlwaysOnVpnModeOff,
                     std::string());
    return;
  }

  network_profile_handler_->GetAlwaysOnVpnConfiguration(
      profile->path,
      base::BindOnce(&CrosNetworkConfig::OnGetAlwaysOnVpn,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CrosNetworkConfig::OnGetAlwaysOnVpn(GetAlwaysOnVpnCallback callback,
                                         std::string mode,
                                         std::string service_path) {
  mojom::AlwaysOnVpnMode vpn_mode;
  if (mode == shill::kAlwaysOnVpnModeOff) {
    vpn_mode = mojom::AlwaysOnVpnMode::kOff;
  } else if (mode == shill::kAlwaysOnVpnModeBestEffort) {
    vpn_mode = mojom::AlwaysOnVpnMode::kBestEffort;
  } else if (mode == shill::kAlwaysOnVpnModeStrict) {
    vpn_mode = mojom::AlwaysOnVpnMode::kStrict;
  } else {
    NOTREACHED_IN_MIGRATION()
        << "OnGetAlwaysOnVpn: invalid always-on VPN mode: " << mode;
    vpn_mode = mojom::AlwaysOnVpnMode::kOff;
  }

  std::string guid;
  const NetworkState* network =
      network_state_handler_->GetNetworkState(service_path);
  // |network| is expected to be null when the service has not been set (yet)
  // or has been removed.
  if (network) {
    guid = network->guid();
  }

  mojom::AlwaysOnVpnPropertiesPtr properties =
      mojom::AlwaysOnVpnProperties::New(vpn_mode, guid);
  std::move(callback).Run(std::move(properties));
}

void CrosNetworkConfig::SetAlwaysOnVpn(
    mojom::AlwaysOnVpnPropertiesPtr properties) {
  const NetworkProfile* profile =
      network_profile_handler_->GetDefaultUserProfile();
  if (!profile) {
    NET_LOG(ERROR) << "SetAlwaysOnVpn: no user profile found";
    return;
  }

  std::string mode;
  switch (properties->mode) {
    case mojom::AlwaysOnVpnMode::kBestEffort:
      mode = shill::kAlwaysOnVpnModeBestEffort;
      break;
    case mojom::AlwaysOnVpnMode::kStrict:
      mode = shill::kAlwaysOnVpnModeStrict;
      break;
    case mojom::AlwaysOnVpnMode::kOff:
      mode = shill::kAlwaysOnVpnModeOff;
      break;
    default:
      NOTREACHED_IN_MIGRATION()
          << "SetAlwaysOnVpn: invalid mode: " << properties->mode;
      return;
  }
  network_profile_handler_->SetAlwaysOnVpnMode(profile->path, mode);

  if (properties->service_guid.empty()) {
    return;
  }
  std::string service_path = GetServicePathFromGuid(properties->service_guid);
  if (service_path.empty()) {
    return;
  }
  network_profile_handler_->SetAlwaysOnVpnService(profile->path, service_path);
}

void CrosNetworkConfig::GetSupportedVpnTypes(
    GetSupportedVpnTypesCallback callback) {
  ShillManagerClient::Get()->GetProperties(
      base::BindOnce(&CrosNetworkConfig::OnGetSupportedVpnTypes,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CrosNetworkConfig::OnGetSupportedVpnTypes(
    GetSupportedVpnTypesCallback callback,
    std::optional<base::Value::Dict> properties) {
  std::vector<std::string> result;
  if (!properties) {
    NET_LOG(ERROR) << "GetSupportedVpnTypes: GetProperties failed.";
    std::move(callback).Run(result);
    return;
  }
  const std::string* value =
      properties->FindString(shill::kSupportedVPNTypesProperty);
  if (value) {
    result = base::SplitString(*value, ",", base::TRIM_WHITESPACE,
                               base::SPLIT_WANT_NONEMPTY);
  }
  std::move(callback).Run(result);
}

void CrosNetworkConfig::RequestTrafficCounters(
    const std::string& guid,
    RequestTrafficCountersCallback callback) {
  if (!traffic_counters::TrafficCountersHandler::IsInitialized()) {
    NET_LOG(ERROR)
        << "RequestTrafficCounters failure: traffic counters handler not "
        << "initialized while requesting traffic counters for network guid "
        << guid;
    std::move(callback).Run({});
    return;
  }
  std::string service_path = GetServicePathFromGuid(guid);
  if (service_path.empty()) {
    NET_LOG(ERROR) << "RequestTrafficCounters: service path for guid " << guid
                   << " not found";
    std::move(callback).Run({});
    return;
  }
  traffic_counters::TrafficCountersHandler::Get()->RequestTrafficCounters(
      service_path,
      base::BindOnce(&CrosNetworkConfig::PopulateTrafficCounters,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void CrosNetworkConfig::PopulateTrafficCounters(
    RequestTrafficCountersCallback callback,
    std::optional<base::Value> traffic_counters) {
  if (!traffic_counters || !traffic_counters->is_list() ||
      !traffic_counters->GetList().size()) {
    std::move(callback).Run({});
    return;
  }
  std::vector<mojom::TrafficCounterPtr> counters;
  for (const base::Value& tc : traffic_counters->GetList()) {
    DCHECK(tc.is_dict());
    const base::Value::Dict& tc_dict = tc.GetDict();
    const std::string* source = tc_dict.FindString("source");
    DCHECK(source);

    // Since rx_bytes may be larger than the maximum value representable by
    // uint32_t, we must check whether it was implicitly converted to a double
    // during D-Bus deserialization.
    uint64_t rx_bytes = 0;
    if (const base::Value* const rb = tc_dict.Find("rx_bytes")) {
      if (rb->is_int()) {
        rx_bytes = rb->GetInt();
      } else if (rb->is_double()) {
        rx_bytes = std::floor(rb->GetDouble());
      } else {
        LOG(ERROR) << "Unexpected type " << rb->type() << " for rx_bytes";
      }
    } else {
      LOG(ERROR) << "Missing field: rx_bytes";
    }

    // Since tx_bytes may be larger than the maximum value representable by
    // uint32_t, we must check whether it was implicitly converted to a double
    // during D-Bus deserialization.
    uint64_t tx_bytes = 0;
    if (const base::Value* const tb = tc_dict.Find("tx_bytes")) {
      if (tb->is_int()) {
        tx_bytes = tb->GetInt();
      } else if (tb->is_double()) {
        tx_bytes = std::floor(tb->GetDouble());
      } else {
        LOG(ERROR) << "Unexpected type " << tb->type() << " for tx_bytes";
      }
    } else {
      LOG(ERROR) << "Missing field: tx_bytes";
    }

    counters.push_back(mojom::TrafficCounter::New(
        ConvertToTrafficCounterSourceEnum(*source), rx_bytes, tx_bytes));
  }
  std::move(callback).Run(std::move(counters));
}

void CrosNetworkConfig::ResetTrafficCounters(const std::string& guid) {
  if (!traffic_counters::TrafficCountersHandler::IsInitialized()) {
    NET_LOG(ERROR)
        << "ResetTrafficCounters failure: traffic counters handler not "
        << "initialized while resetting traffic counters for network guid "
        << guid;
    return;
  }
  std::string service_path = GetServicePathFromGuid(guid);
  if (service_path.empty()) {
    NET_LOG(ERROR) << "ResetTrafficCounters: service path for guid " << guid
                   << " not found";
    return;
  }
  traffic_counters::TrafficCountersHandler::Get()->ResetTrafficCounters(
      service_path);
}

void CrosNetworkConfig::SetTrafficCountersResetDay(
    const std::string& guid,
    mojom::UInt32ValuePtr day,
    SetTrafficCountersResetDayCallback callback) {
  if (!traffic_counters::TrafficCountersHandler::IsInitialized()) {
    NET_LOG(ERROR)
        << "SetTrafficCountersResetDay failure: traffic counters handler not "
        << "initialized while setting reset day for network guid " << guid;
    std::move(callback).Run(/*success=*/false);
    return;
  }
  std::string service_path = GetServicePathFromGuid(guid);
  if (service_path.empty()) {
    NET_LOG(ERROR) << "SetTrafficCountersResetDay failure: service path not "
                      "found for guid "
                   << guid;
    std::move(callback).Run(/*success=*/false);
    return;
  }
  traffic_counters::TrafficCountersHandler::Get()->SetTrafficCountersResetDay(
      guid, day->value, std::move(callback));
}

void CrosNetworkConfig::CreateCustomApn(const std::string& network_guid,
                                        mojom::ApnPropertiesPtr apn,
                                        CreateCustomApnCallback callback) {
  if (!features::IsApnRevampEnabled()) {
    receivers_.ReportBadMessage(
        "CreateCustomApn cannot be called if the APN Revamp feature flag is "
        "disabled.");
    std::move(callback).Run(/*success=*/false);
    return;
  }

  if (features::IsApnRevampAndAllowApnModificationPolicyEnabled() &&
      !network_configuration_handler_->AllowApnModification()) {
    NET_LOG(ERROR)
        << "Cannot create custom APN if AllowAPNModification is false.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(network_guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "CreateCustomApn: Called with unconfigured network: "
                   << network_guid << ".";
    CellularNetworkMetricsLogger::LogCreateCustomApnResult(
        /*success=*/false, std::move(apn));
    std::move(callback).Run(/*success=*/false);
    return;
  }

  std::optional<base::Value::List> new_apn_list =
      CopyExistingAndPrependNewApn(network_guid, apn,
                                   /*enable_exclusively=*/false);
  if (!new_apn_list) {
    NET_LOG(ERROR) << "CreateCustomApn: New APN list will not be valid.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  SetPropertiesInternal(
      network_guid, *network,
      CustomApnListToOnc(network_guid, &new_apn_list.value()),
      base::BindOnce(
          [](const std::string& guid, base::Value::List new_apn_list,
             mojom::ApnPropertiesPtr apn, CreateCustomApnCallback callback,
             bool success, const std::string& message) {
            if (success) {
              NetworkHandler::Get()->network_metadata_store()->SetCustomApnList(
                  guid, std::move(new_apn_list));
            } else {
              NET_LOG(ERROR)
                  << "CreateCustomApn: Failed to update the custom APN "
                     "list in Shill for network: "
                  << guid << ": [" << message << ']';
            }
            std::move(callback).Run(success);
            CellularNetworkMetricsLogger::LogCreateCustomApnResult(
                success, std::move(apn));
          },
          network_guid, new_apn_list->Clone(), std::move(apn),
          std::move(callback)));
}

void CrosNetworkConfig::CreateExclusivelyEnabledCustomApn(
    const std::string& network_guid,
    mojom::ApnPropertiesPtr apn,
    CreateExclusivelyEnabledCustomApnCallback callback) {
  if (!features::IsApnRevampEnabled()) {
    receivers_.ReportBadMessage(
        "CreateExclusivelyEnabledCustomApn cannot be called if the APN Revamp "
        "feature flag is disabled.");
    std::move(callback).Run(/*success=*/false);
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(network_guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "CreateExclusivelyEnabledCustomApn: Called with "
                   << "unconfigured network: " << network_guid << ".";
    CellularNetworkMetricsLogger::LogCreateExclusivelyEnabledCustomApnResult(
        /*success=*/false, std::move(apn));
    std::move(callback).Run(/*success=*/false);
    return;
  }

  std::optional<base::Value::List> new_apn_list = CopyExistingAndPrependNewApn(
      network_guid, apn, /*enable_exclusively=*/true);
  if (!new_apn_list) {
    NET_LOG(ERROR)
        << "CreateExclusivelyEnabledCustomApn: New APN list will not be valid.";
    std::move(callback).Run(/*success=*/false);
    return;
  }

  SetPropertiesInternal(
      network_guid, *network,
      CustomApnListToOnc(network_guid, &new_apn_list.value()),
      base::BindOnce(
          [](const std::string& guid, base::Value::List new_apn_list,
             mojom::ApnPropertiesPtr apn,
             CreateExclusivelyEnabledCustomApnCallback callback, bool success,
             const std::string& message) {
            if (success) {
              NetworkHandler::Get()->network_metadata_store()->SetCustomApnList(
                  guid, std::move(new_apn_list));
            } else {
              NET_LOG(ERROR) << "CreateExclusivelyEnabledCustomApn: Failed to "
                             << "update the custom APN list in Shill for "
                             << "network: " << guid << ": [" << message << "]";
            }
            std::move(callback).Run(success);
            CellularNetworkMetricsLogger::
                LogCreateExclusivelyEnabledCustomApnResult(success,
                                                           std::move(apn));
          },
          network_guid, new_apn_list->Clone(), std::move(apn),
          std::move(callback)));
}

void CrosNetworkConfig::RemoveCustomApn(const std::string& network_guid,
                                        const std::string& apn_id) {
  if (!features::IsApnRevampEnabled()) {
    receivers_.ReportBadMessage(
        "RemoveCustomApn: Cannot be called if the APN Revamp feature flag is "
        "disabled.");
    return;
  }

  if (features::IsApnRevampAndAllowApnModificationPolicyEnabled() &&
      !network_configuration_handler_->AllowApnModification()) {
    NET_LOG(ERROR)
        << "Cannot remove custom APN if AllowAPNModification is false.";
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(network_guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "RemoveCustomApn: Called with unconfigured network: "
                   << network_guid << ".";
    CellularNetworkMetricsLogger::LogRemoveCustomApnResult(
        /*success=*/false, /*apn_types=*/{});
    return;
  }

  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();
  DCHECK(network_metadata_store);

  const base::Value::List* current_apns =
      network_metadata_store->GetCustomApnList(network_guid);
  if (!current_apns || current_apns->empty()) {
    NET_LOG(ERROR) << "RemoveCustomApn: Called for network: " << network_guid
                   << " that does not have any user APNs.";
    CellularNetworkMetricsLogger::LogRemoveCustomApnResult(
        /*success=*/false, /*apn_types=*/{});
    return;
  }

  base::Value::List new_apns = current_apns->Clone();
  std::vector<mojom::ApnType> removed_apn_apn_types;
  if (!new_apns.EraseIf([&apn_id,
                         &removed_apn_apn_types](const base::Value& item) {
        const std::string* item_id =
            item.GetDict().FindString(::onc::cellular_apn::kId);
        if (item_id && apn_id == *item_id) {
          removed_apn_apn_types = OncApnTypesToMojo(GetRequiredStringList(
              &item.GetDict(), ::onc::cellular_apn::kApnTypes));
          return true;
        }
        return false;
      })) {
    NET_LOG(ERROR) << "RemoveCustomApn: Called for network: " << network_guid
                   << " that does have an user APNs with id: " << apn_id << '.';
    CellularNetworkMetricsLogger::LogRemoveCustomApnResult(
        /*success=*/false, std::move(removed_apn_apn_types));
    return;
  }
  NET_LOG(USER) << "RemoveCustomApn: Setting custom APNs for: " << network_guid
                << ": " << new_apns.size();
  if (!new_apns.empty() && !DoesDefaultApnExist(new_apns)) {
    NET_LOG(ERROR)
        << "RemoveCustomApn: Cannot remove the APN because there is one or more"
        << " remaining APNs but no remaining APN with a default type.";
    return;
  }

  SetPropertiesInternal(
      network_guid, *network, CustomApnListToOnc(network_guid, &new_apns),
      base::BindOnce(
          [](const std::string& guid, base::Value::List new_apns,
             std::vector<mojom::ApnType> apn_types, bool success,
             const std::string& message) {
            if (success) {
              NetworkHandler::Get()->network_metadata_store()->SetCustomApnList(
                  guid, std::move(new_apns));
            } else {
              NET_LOG(ERROR)
                  << "RemoveCustomApn: Failed to update the custom APN "
                     "list in Shill for network: "
                  << guid << ": [" << message << ']';
            }
            CellularNetworkMetricsLogger::LogRemoveCustomApnResult(
                success, std::move(apn_types));
          },
          network_guid, new_apns.Clone(), std::move(removed_apn_apn_types)));
}

void CrosNetworkConfig::ModifyCustomApn(const std::string& network_guid,
                                        mojom::ApnPropertiesPtr apn) {
  if (!features::IsApnRevampEnabled()) {
    receivers_.ReportBadMessage(
        "ModifyCustomApn: Cannot be called if the APN Revamp feature flag is "
        "disabled.");
    return;
  }

  if (features::IsApnRevampAndAllowApnModificationPolicyEnabled() &&
      !network_configuration_handler_->AllowApnModification()) {
    NET_LOG(ERROR)
        << "Cannot modify custom APN if AllowAPNModification is false.";
    return;
  }

  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(network_guid);
  if (!network || network->profile_path().empty()) {
    NET_LOG(ERROR) << "ModifyCustomApn: Called with unconfigured network: "
                   << network_guid << ".";
    CellularNetworkMetricsLogger::LogModifyCustomApnResult(
        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/std::nullopt,
        /*old_apn_state=*/std::nullopt);
    return;
  }

  if (!apn->id.has_value()) {
    NET_LOG(ERROR)
        << "ModifyCustomApn: Called with an APN without ID for network: "
        << network_guid << '.';
    CellularNetworkMetricsLogger::LogModifyCustomApnResult(
        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/std::nullopt,
        /*old_apn_state=*/std::nullopt);
    return;
  }

  NetworkMetadataStore* network_metadata_store =
      NetworkHandler::Get()->network_metadata_store();
  DCHECK(network_metadata_store);

  const base::Value::List* old_custom_apns =
      network_metadata_store->GetCustomApnList(network_guid);
  if (!old_custom_apns || old_custom_apns->empty()) {
    NET_LOG(ERROR) << "ModifyCustomApn: Called for network: " << network_guid
                   << " that does not have any user APNs.";
    CellularNetworkMetricsLogger::LogModifyCustomApnResult(
        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/std::nullopt,
        /*old_apn_state=*/std::nullopt);
    return;
  }

  base::Value::List new_custom_apns;
  std::vector<mojom::ApnType> modified_apn_old_apn_types;
  mojom::ApnState modified_apn_old_apn_state;

  bool was_value_replaced = false;
  for (const base::Value& old_apn_value : *old_custom_apns) {
    const base::Value::Dict& old_apn = old_apn_value.GetDict();
    const std::string* old_apn_id =
        old_apn.FindString(::onc::cellular_apn::kId);
    DCHECK(old_apn_id);

    if (*apn->id == *old_apn_id) {
      new_custom_apns.Append(MojoApnToOnc(*apn));
      modified_apn_old_apn_types = OncApnTypesToMojo(
          GetRequiredStringList(&old_apn, ::onc::cellular_apn::kApnTypes));
      modified_apn_old_apn_state = OncApnStateTypeToMojo(
          old_apn.FindString(::onc::cellular_apn::kState));
      was_value_replaced = true;
    } else {
      new_custom_apns.Append(old_apn.Clone());
    }
  }

  if (!was_value_replaced) {
    NET_LOG(ERROR) << "ModifyCustomApn: Called for network: " << network_guid
                   << " that does have an user APNs with id: " << *apn->id
                   << '.';
    CellularNetworkMetricsLogger::LogModifyCustomApnResult(
        /*success=*/false, /*old_apn_types=*/{}, /*apn_state=*/std::nullopt,
        /*old_apn_state=*/std::nullopt);
    return;
  }

  if (!DoesDefaultApnExist(new_custom_apns)) {
    NET_LOG(ERROR)
        << "ModifyCustomApn: Cannot change the type to attach because there "
        << "will be no APN with a default type remaining.";
    return;
  }

  NET_LOG(USER) << "ModifyCustomApn: Setting custom APNs for: " << network_guid
                << ": " << new_custom_apns.size();
  SetPropertiesInternal(
      network_guid, *network,
      CustomApnListToOnc(network_guid, &new_custom_apns),
      base::BindOnce(
          [](const std::string& guid, base::Value::List new_apns,
             std::vector<mojom::ApnType> old_apn_types,
             const mojom::ApnState apn_state,
             const mojom::ApnState old_apn_state, bool success,
             const std::string& message) {
            if (success) {
              NetworkHandler::Get()->network_metadata_store()->SetCustomApnList(
                  guid, std::move(new_apns));
            } else {
              NET_LOG(ERROR)
                  << "ModifyCustomApn: Failed to update the custom APN "
                     "list in Shill for network: "
                  << guid << ": [" << message << ']';
            }
            // TODO(b/162365553) Add test coverage for the case when there is a
            // failure from shill.
            CellularNetworkMetricsLogger::LogModifyCustomApnResult(
                success, old_apn_types, apn_state, old_apn_state);
          },
          network_guid, new_custom_apns.Clone(),
          std::move(modified_apn_old_apn_types), apn->state,
          modified_apn_old_apn_state));
}

// static
mojom::TrafficCounterSource CrosNetworkConfig::GetTrafficCounterEnumForTesting(
    const std::string& source) {
  return ConvertToTrafficCounterSourceEnum(source);
}

// NetworkStateHandlerObserver
void CrosNetworkConfig::NetworkListChanged() {
  for (auto& observer : observers_)
    observer->OnNetworkStateListChanged();
}

void CrosNetworkConfig::DeviceListChanged() {
  for (auto& observer : observers_)
    observer->OnDeviceStateListChanged();
}

void CrosNetworkConfig::ActiveNetworksChanged(
    const std::vector<const NetworkState*>& active_networks) {
  std::vector<mojom::NetworkStatePropertiesPtr> result;
  for (const NetworkState* network : active_networks) {
    mojom::NetworkStatePropertiesPtr mojo_network = NetworkStateToMojo(
        network_state_handler_, cellular_esim_profile_handler_, vpn_providers_,
        network);
    if (mojo_network)
      result.emplace_back(std::move(mojo_network));
  }
  for (auto& observer : observers_)
    observer->OnActiveNetworksChanged(mojo::Clone(result));
}

void CrosNetworkConfig::NetworkPropertiesUpdated(const NetworkState* network) {
  if (network->type() == shill::kTypeEthernetEap)
    return;
  mojom::NetworkStatePropertiesPtr mojo_network =
      NetworkStateToMojo(network_state_handler_, cellular_esim_profile_handler_,
                         vpn_providers_, network);
  if (!mojo_network)
    return;
  for (auto& observer : observers_)
    observer->OnNetworkStateChanged(mojo_network.Clone());
}

void CrosNetworkConfig::DevicePropertiesUpdated(const DeviceState* device) {
  DeviceListChanged();
}

void CrosNetworkConfig::ScanCompleted(const DeviceState* device) {
  DeviceListChanged();
}

void CrosNetworkConfig::ScanStarted(const DeviceState* device) {
  DeviceListChanged();
}

void CrosNetworkConfig::NetworkConnectionStateChanged(
    const NetworkState* network) {
  if (!network->Matches(NetworkTypePattern::Cellular())) {
    return;
  }
  // inhibit_reason device property is dependent on network connection state of
  // cellular networks. Notify device list change so that clients will update
  // with new inhibit reason.
  DeviceListChanged();
}

void CrosNetworkConfig::OnShuttingDown() {
  network_state_handler_observer_.Reset();
  network_state_handler_ = nullptr;
}

void CrosNetworkConfig::OnCertificatesChanged() {
  for (auto& observer : observers_)
    observer->OnNetworkCertificatesChanged();
}

void CrosNetworkConfig::OnInhibitStateChanged() {
  DeviceListChanged();
}

void CrosNetworkConfig::PoliciesApplied(const std::string& userhash) {
  for (auto& observer : observers_)
    observer->OnPoliciesApplied(userhash);
}

void CrosNetworkConfig::OnManagedNetworkConfigurationHandlerShuttingDown() {
  if (network_configuration_handler_ &&
      network_configuration_handler_->HasObserver(this)) {
    network_configuration_handler_->RemoveObserver(this);
  }
  network_configuration_handler_ = nullptr;
}

const std::string& CrosNetworkConfig::GetServicePathFromGuid(
    const std::string& guid) {
  const NetworkState* network =
      network_state_handler_->GetNetworkStateFromGuid(guid);
  return network ? network->path() : base::EmptyString();
}

}  // namespace ash::network_config