chromium/ash/components/arc/net/arc_net_utils.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/components/arc/net/arc_net_utils.h"

#include <netinet/in.h>

#include "ash/components/arc/mojom/arc_wifi.mojom.h"
#include "ash/components/arc/mojom/net.mojom-shared.h"
#include "ash/components/arc/mojom/net.mojom.h"
#include "base/containers/map_util.h"
#include "base/strings/stringprintf.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/onc/network_onc_utils.h"
#include "net/base/ip_address.h"
#include "third_party/cros_system_api/dbus/shill/dbus-constants.h"

namespace {

std::string PackedIPAddressToString(sa_family_t family,
                                    const std::string& data) {
  if (family != AF_INET && family != AF_INET6) {
    NET_LOG(ERROR) << "Invalid IP family " << family;
    return "";
  }
  if (family == AF_INET && data.length() != sizeof(in_addr)) {
    NET_LOG(ERROR) << "Invalid packed IPv4 data size " << data.length()
                   << ", expected " << sizeof(in_addr);
    return "";
  }
  if (family == AF_INET6 && data.length() != sizeof(in6_addr)) {
    NET_LOG(ERROR) << "Invalid packed IPv6 data size " << data.length()
                   << ", expected " << sizeof(in6_addr);
    return "";
  }

  char buf[INET6_ADDRSTRLEN] = {0};
  return !inet_ntop(family, data.data(), buf, sizeof(buf)) ? "" : buf;
}

ash::NetworkStateHandler* GetStateHandler() {
  return ash::NetworkHandler::Get()->network_state_handler();
}

// Update the IP configuration fields in the given |mojo| NetworkConfiguration
// object with |network_state|.
void UpdateIpConfiguration(const ash::NetworkState& network_state,
                           arc::mojom::NetworkConfiguration* mojo) {
  const ash::NetworkConfig* config = network_state.network_config();

  // Service may not be connected.
  if (!config) {
    return;
  }

  // Only set the IP address and gateway if both are defined.
  if (config->ipv4_address && config->ipv4_gateway) {
    mojo->host_ipv4_address = config->ipv4_address->addr.ToString();
    mojo->host_ipv4_prefix_length = config->ipv4_address->prefix_len;
    mojo->host_ipv4_gateway = config->ipv4_gateway->ToString();
  }
  if (config->ipv6_addresses.size() > 0 && config->ipv6_gateway) {
    for (const auto& cidr : config->ipv6_addresses) {
      mojo->host_ipv6_global_addresses->push_back(cidr.addr.ToString());
    }
    // Assume that all addresses have the same prefix length.
    mojo->host_ipv6_prefix_length = config->ipv6_addresses[0].prefix_len;
    mojo->host_ipv6_gateway = config->ipv6_gateway->ToString();
  }

  for (const auto& dns : config->dns_servers) {
    mojo->host_dns_addresses->push_back(dns.ToString());
  }
  for (const auto& domain : config->search_domains) {
    mojo->host_search_domains->push_back(domain);
  }

  for (const auto& cidr : config->included_routes) {
    mojo->include_routes->push_back(base::StringPrintf(
        "%s/%d", cidr.addr.ToString().c_str(), cidr.prefix_len));
  }
  for (const auto& cidr : config->excluded_routes) {
    mojo->exclude_routes->push_back(base::StringPrintf(
        "%s/%d", cidr.addr.ToString().c_str(), cidr.prefix_len));
  }

  mojo->host_mtu = config->mtu > 0 ? config->mtu : 0;
}

const ash::NetworkState* GetShillBackedNetwork(
    const ash::NetworkState* network) {
  if (!network) {
    return nullptr;
  }

  // Non-Tether networks are already backed by Shill.
  const std::string type = network->type();
  if (type.empty() || !ash::NetworkTypePattern::Tether().MatchesType(type)) {
    return network;
  }

  // Tether networks which are not connected are also not backed by Shill.
  if (!network->IsConnectedState()) {
    return nullptr;
  }

  // Connected Tether networks delegate to an underlying Wi-Fi network.
  DCHECK(!network->tether_guid().empty());
  return GetStateHandler()->GetNetworkStateFromGuid(network->tether_guid());
}

std::string IPv4AddressToString(uint32_t addr) {
  char buf[INET_ADDRSTRLEN] = {0};
  struct in_addr ia;
  ia.s_addr = addr;
  return !inet_ntop(AF_INET, &ia, buf, sizeof(buf)) ? std::string() : buf;
}

std::string KeyManagementMethodToString(arc::mojom::KeyManagement management) {
  switch (management) {
    case arc::mojom::KeyManagement::kIeee8021X:
      return "WEP-8021X";
    case arc::mojom::KeyManagement::kFtEap:
      return "FT-EAP";
    case arc::mojom::KeyManagement::kFtPsk:
      return "FT-PSK";
    case arc::mojom::KeyManagement::kFtSae:
      return "FT-SAE";
    case arc::mojom::KeyManagement::kWpaEap:
      return "WPA-EAP";
    case arc::mojom::KeyManagement::kWpaEapSha256:
      return "WPA-EAP-SHA256";
    case arc::mojom::KeyManagement::kWpaPsk:
      return "WPA-PSK";
    case arc::mojom::KeyManagement::kSae:
      return "SAE";
    case arc::mojom::KeyManagement::kNone:
      return "None";
  }
  return "unknown";
}

patchpanel::SocketConnectionEvent::IpProtocol TranslateIpProtocol(
    arc::mojom::IpProtocol proto) {
  switch (proto) {
    case arc::mojom::IpProtocol::kTcp:
      return patchpanel::SocketConnectionEvent::IpProtocol::
          SocketConnectionEvent_IpProtocol_TCP;
    case arc::mojom::IpProtocol::kUdp:
      return patchpanel::SocketConnectionEvent::IpProtocol::
          SocketConnectionEvent_IpProtocol_UDP;
    case arc::mojom::IpProtocol::kUnknown:
      NET_LOG(ERROR) << "Unknown IP protocol";
      return patchpanel::SocketConnectionEvent::IpProtocol::
          SocketConnectionEvent_IpProtocol_UNKNOWN_PROTO;
  }
}

patchpanel::SocketConnectionEvent::SocketEvent TranslateSocketEvent(
    arc::mojom::SocketEvent event) {
  switch (event) {
    case arc::mojom::SocketEvent::kOpen:
      return patchpanel::SocketConnectionEvent::SocketEvent::
          SocketConnectionEvent_SocketEvent_OPEN;
    case arc::mojom::SocketEvent::kClose:
      return patchpanel::SocketConnectionEvent::SocketEvent::
          SocketConnectionEvent_SocketEvent_CLOSE;
    case arc::mojom::SocketEvent::kUnknown:
      NET_LOG(ERROR) << "Unknown socket event";
      return patchpanel::SocketConnectionEvent::SocketEvent::
          SocketConnectionEvent_SocketEvent_UNKNOWN_EVENT;
  }
}

patchpanel::SocketConnectionEvent::QosCategory TranslateQosCategory(
    arc::mojom::QosCategory category) {
  switch (category) {
    case arc::mojom::QosCategory::kRealtimeInteractive:
      return patchpanel::SocketConnectionEvent::QosCategory::
          SocketConnectionEvent_QosCategory_REALTIME_INTERACTIVE;
    case arc::mojom::QosCategory::kMultimediaConferencing:
      return patchpanel::SocketConnectionEvent::QosCategory::
          SocketConnectionEvent_QosCategory_MULTIMEDIA_CONFERENCING;
    case arc::mojom::QosCategory::kUnknown:
      return patchpanel::SocketConnectionEvent::QosCategory::
          SocketConnectionEvent_QosCategory_UNKNOWN_CATEGORY;
  }
}

}  // namespace

namespace arc::net_utils {

void FillConfigurationsFromState(const ash::NetworkState* network_state,
                                 const base::Value::Dict* shill_dict,
                                 arc::mojom::NetworkConfiguration* mojo) {
  // Initialize optional array fields to avoid null guards both here and in ARC.
  mojo->host_ipv6_global_addresses = std::vector<std::string>();
  mojo->host_search_domains = std::vector<std::string>();
  mojo->host_dns_addresses = std::vector<std::string>();
  mojo->include_routes = std::vector<std::string>();
  mojo->exclude_routes = std::vector<std::string>();
  mojo->connection_state =
      TranslateConnectionState(network_state->connection_state());
  mojo->guid = network_state->guid();
  mojo->is_default_network =
      network_state == GetStateHandler()->DefaultNetwork();
  mojo->service_name = network_state->path();
  if (mojo->guid.empty()) {
    NET_LOG(ERROR) << "Missing GUID property for network "
                   << network_state->path();
  }
  mojo->type = TranslateNetworkType(network_state->type());
  mojo->is_metered =
      shill_dict &&
      shill_dict->FindBool(shill::kMeteredProperty).value_or(false);

  // Add shill's Device properties to the given mojo NetworkConfiguration
  // objects. This adds the network interface.
  if (const auto* device =
          GetStateHandler()->GetDeviceState(network_state->device_path())) {
    mojo->network_interface = device->interface();
  }

  UpdateIpConfiguration(*network_state, mojo);

  if (mojo->type == arc::mojom::NetworkType::WIFI) {
    mojo->wifi = arc::mojom::WiFi::New();
    mojo->wifi->bssid = network_state->bssid();
    mojo->wifi->hex_ssid = network_state->GetHexSsid();
    mojo->wifi->security =
        TranslateWiFiSecurity(network_state->security_class());
    mojo->wifi->frequency = network_state->frequency();
    mojo->wifi->signal_strength = network_state->signal_strength();
    mojo->wifi->rssi = network_state->rssi();
    if (shill_dict) {
      mojo->wifi->hidden_ssid =
          shill_dict->FindBoolByDottedPath(shill::kWifiHiddenSsid)
              .value_or(false);
      const auto* fqdn =
          shill_dict->FindStringByDottedPath(shill::kPasspointFQDNProperty);
      if (fqdn && !fqdn->empty()) {
        mojo->wifi->fqdn = *fqdn;
      }
    }
  }
}

arc::mojom::WifiScanResultPtr NetworkStateToWifiScanResult(
    const ash::NetworkState& network_state) {
  auto mojo = arc::mojom::WifiScanResult::New();
  mojo->bssid = network_state.bssid();
  mojo->hex_ssid = network_state.GetHexSsid();
  mojo->security = TranslateWiFiSecurity(network_state.security_class());
  mojo->frequency = network_state.frequency();
  mojo->rssi = network_state.rssi();
  return mojo;
}

void FillConfigurationsFromDevice(const patchpanel::NetworkDevice& device,
                                  arc::mojom::NetworkConfiguration* mojo) {
  mojo->network_interface = device.phys_ifname();
  mojo->arc_network_interface = device.guest_ifname();
  mojo->arc_ipv4_address = IPv4AddressToString(device.ipv4_addr());
  mojo->arc_ipv4_gateway = IPv4AddressToString(device.host_ipv4_addr());
  mojo->arc_ipv4_prefix_length = device.ipv4_subnet().prefix_len();
  // Fill in DNS proxy addresses.
  mojo->dns_proxy_addresses = std::vector<std::string>();
  if (!device.dns_proxy_ipv4_addr().empty()) {
    auto dns_proxy_ipv4_addr =
        PackedIPAddressToString(AF_INET, device.dns_proxy_ipv4_addr());
    mojo->dns_proxy_addresses->emplace_back(dns_proxy_ipv4_addr);
  }
  if (!device.dns_proxy_ipv6_addr().empty()) {
    auto dns_proxy_ipv6_addr =
        PackedIPAddressToString(AF_INET6, device.dns_proxy_ipv6_addr());
    mojo->dns_proxy_addresses->emplace_back(dns_proxy_ipv6_addr);
  }
  // Assign the technology of the physical device the virtual device is tied to.
  switch (device.technology_type()) {
    case patchpanel::NetworkDevice::CELLULAR:
      mojo->type = mojom::NetworkType::CELLULAR;
      break;
    case patchpanel::NetworkDevice::ETHERNET:
      mojo->type = mojom::NetworkType::ETHERNET;
      break;
    case patchpanel::NetworkDevice::WIFI:
      mojo->type = mojom::NetworkType::WIFI;
      break;
    default:
      break;
  }
}

std::string TranslateEapMethod(arc::mojom::EapMethod method) {
  switch (method) {
    case arc::mojom::EapMethod::kLeap:
      return shill::kEapMethodLEAP;
    case arc::mojom::EapMethod::kPeap:
      return shill::kEapMethodPEAP;
    case arc::mojom::EapMethod::kTls:
      return shill::kEapMethodTLS;
    case arc::mojom::EapMethod::kTtls:
      return shill::kEapMethodTTLS;
    case arc::mojom::EapMethod::kNone:
      return "";
  }
  NET_LOG(ERROR) << "Unknown EAP method";
  return "";
}

std::string TranslateEapPhase2Method(arc::mojom::EapPhase2Method method) {
  switch (method) {
    case arc::mojom::EapPhase2Method::kPap:
      return shill::kEapPhase2AuthTTLSPAP;
    case arc::mojom::EapPhase2Method::kMschap:
      return shill::kEapPhase2AuthTTLSMSCHAP;
    case arc::mojom::EapPhase2Method::kMschapv2:
      return shill::kEapPhase2AuthTTLSMSCHAPV2;
    case arc::mojom::EapPhase2Method::kNone:
      return "";
  }
  NET_LOG(ERROR) << "Unknown EAP phase 2 method";
  return "";
}

std::string TranslateEapMethodToOnc(arc::mojom::EapMethod method) {
  switch (method) {
    case arc::mojom::EapMethod::kLeap:
      return onc::eap::kLEAP;
    case arc::mojom::EapMethod::kPeap:
      return onc::eap::kPEAP;
    case arc::mojom::EapMethod::kTls:
      return onc::eap::kEAP_TLS;
    case arc::mojom::EapMethod::kTtls:
      return onc::eap::kEAP_TTLS;
    case arc::mojom::EapMethod::kNone:
      return std::string();
  }
  NET_LOG(ERROR) << "Unknown EAP method: " << method;
  return std::string();
}

std::string TranslateEapPhase2MethodToOnc(arc::mojom::EapPhase2Method method) {
  switch (method) {
    case arc::mojom::EapPhase2Method::kPap:
      return onc::eap::kPAP;
    case arc::mojom::EapPhase2Method::kMschap:
      return onc::eap::kMSCHAP;
    case arc::mojom::EapPhase2Method::kMschapv2:
      return onc::eap::kMSCHAPv2;
    case arc::mojom::EapPhase2Method::kNone:
      return std::string();
  }
  NET_LOG(ERROR) << "Unknown EAP phase 2 method: " << method;
  return std::string();
}

std::string TranslateKeyManagement(mojom::KeyManagement management) {
  switch (management) {
    case arc::mojom::KeyManagement::kIeee8021X:
      return shill::kKeyManagementIEEE8021X;
    case arc::mojom::KeyManagement::kFtEap:
    case arc::mojom::KeyManagement::kFtPsk:
    case arc::mojom::KeyManagement::kFtSae:
    case arc::mojom::KeyManagement::kWpaEap:
    case arc::mojom::KeyManagement::kWpaEapSha256:
    case arc::mojom::KeyManagement::kWpaPsk:
    case arc::mojom::KeyManagement::kSae:
      // Currently these key managements are not handled.
      NET_LOG(ERROR) << "Key management is not supported";
      return "";
    case arc::mojom::KeyManagement::kNone:
      return "";
  }
  NET_LOG(ERROR) << "Unknown key management";
  return "";
}

std::string TranslateKeyManagementToOnc(mojom::KeyManagement management) {
  if (management == arc::mojom::KeyManagement::kIeee8021X) {
    return onc::wifi::kWEP_8021X;
  } else if (management == arc::mojom::KeyManagement::kNone) {
    return std::string();
  }
  // Currently other key managements are not handled.
  NET_LOG(ERROR) << "Key management is not supported or invalid: "
                 << KeyManagementMethodToString(management);
  return std::string();
}

arc::mojom::SecurityType TranslateWiFiSecurity(
    const std::string& security_class) {
  if (security_class == shill::kSecurityClassNone) {
    return arc::mojom::SecurityType::NONE;
  }

  if (security_class == shill::kSecurityClassWep) {
    return arc::mojom::SecurityType::WEP_PSK;
  }

  if (security_class == shill::kSecurityClassPsk) {
    return arc::mojom::SecurityType::WPA_PSK;
  }

  if (security_class == shill::kSecurityClass8021x) {
    return arc::mojom::SecurityType::WPA_EAP;
  }

  NET_LOG(ERROR) << "Unknown WiFi security class " << security_class;
  return arc::mojom::SecurityType::NONE;
}

arc::mojom::ConnectionStateType TranslateConnectionState(
    const std::string& state) {
  if (state == shill::kStateReady) {
    return arc::mojom::ConnectionStateType::CONNECTED;
  }

  if (state == shill::kStateAssociation ||
      state == shill::kStateConfiguration) {
    return arc::mojom::ConnectionStateType::CONNECTING;
  }

  if ((state == shill::kStateIdle) || (state == shill::kStateFailure) ||
      (state == shill::kStateDisconnecting) || (state == "")) {
    return arc::mojom::ConnectionStateType::NOT_CONNECTED;
  }
  if (ash::NetworkState::StateIsPortalled(state)) {
    return arc::mojom::ConnectionStateType::PORTAL;
  }

  if (state == shill::kStateOnline) {
    return arc::mojom::ConnectionStateType::ONLINE;
  }

  // The remaining cases defined in shill dbus-constants are legacy values from
  // Flimflam and are not expected to be encountered.
  NOTREACHED() << "Unknown connection state: " << state;
}

arc::mojom::NetworkType TranslateNetworkType(const std::string& type) {
  if (type == shill::kTypeWifi) {
    return arc::mojom::NetworkType::WIFI;
  }

  if (type == shill::kTypeVPN) {
    return arc::mojom::NetworkType::VPN;
  }

  if (type == shill::kTypeEthernet) {
    return arc::mojom::NetworkType::ETHERNET;
  }

  if (type == shill::kTypeEthernetEap) {
    return arc::mojom::NetworkType::ETHERNET;
  }

  if (type == shill::kTypeCellular) {
    return arc::mojom::NetworkType::CELLULAR;
  }

  NOTREACHED() << "Unknown network type: " << type;
}

std::vector<arc::mojom::NetworkConfigurationPtr> TranslateNetworkDevices(
    const std::vector<patchpanel::NetworkDevice>& devices,
    const std::string& arc_vpn_path,
    const ash::NetworkStateHandler::NetworkStateList& active_network_states,
    const std::map<std::string, base::Value::Dict>& shill_network_properties) {
  // Map of interface name to a unique active network state on it, if such state
  // exists. Otherwise, report device without associated network states.
  std::map<std::string, arc::mojom::NetworkConfigurationPtr> ifname_config_map;
  std::vector<arc::mojom::NetworkConfigurationPtr> network_configs;

  for (const patchpanel::NetworkDevice& device : devices) {
    // Filter non-ARC devices.
    if (device.guest_type() != patchpanel::NetworkDevice::ARC &&
        device.guest_type() != patchpanel::NetworkDevice::ARCVM) {
      continue;
    }
    auto config_it = ifname_config_map
                         .try_emplace(device.phys_ifname(),
                                      arc::mojom::NetworkConfiguration::New())
                         .first;
    FillConfigurationsFromDevice(device, config_it->second.get());
    // Connection state default to NOT_CONNECTED unless there is active network
    // state on interface device.phys_ifname().
    config_it->second->connection_state =
        arc::mojom::ConnectionStateType::NOT_CONNECTED;
  }

  for (const ash::NetworkState* const state : active_network_states) {
    // Never tell Android about its own VPN.
    if (state->path() == arc_vpn_path) {
      continue;
    }
    // For networks established with instant tethering, the underlying WiFi
    // networks are not part of active networks. Replace any such tethered
    // network with its underlying backing network, because ARC cannot match its
    // datapath with the tethered network configuration. For other cases, the
    // underlying_state is identical to state.
    const ash::NetworkState* const underlying_state =
        GetShillBackedNetwork(state);
    if (!underlying_state) {
      continue;
    }

    std::string if_name;
    if (const auto* device = GetStateHandler()->GetDeviceState(
            underlying_state->device_path())) {
      if_name = device->interface();
    }
    const base::Value::Dict* shill_dict =
        base::FindOrNull(shill_network_properties, underlying_state->path());

    if (if_name.empty()) {
      // Add active network without network_interface (e.g.: host VPN).
      auto network = arc::mojom::NetworkConfiguration::New();
      FillConfigurationsFromState(underlying_state, shill_dict, network.get());
      network_configs.push_back(std::move(network));
    } else {
      const auto itr = ifname_config_map.find(if_name);
      if (itr != ifname_config_map.end()) {
        FillConfigurationsFromState(underlying_state, shill_dict,
                                    itr->second.get());
      } else {
        NET_LOG(ERROR) << "Interface " << if_name << " does not have a device.";
      }
    }
  }

  for (auto& network_config : ifname_config_map) {
    if (network_config.second->arc_network_interface.has_value()) {
      network_configs.push_back(std::move(network_config.second));
    }
  }
  return network_configs;
}

std::vector<arc::mojom::NetworkConfigurationPtr> TranslateNetworkStates(
    const std::string& arc_vpn_path,
    const ash::NetworkStateHandler::NetworkStateList& network_states,
    const std::map<std::string, base::Value::Dict>& shill_network_properties) {
  std::vector<arc::mojom::NetworkConfigurationPtr> networks;
  for (const ash::NetworkState* const state : network_states) {
    // Never tell Android about its own VPN.
    if (state->path() == arc_vpn_path) {
      continue;
    }
    // For networks established with instant tethering, the underlying WiFi
    // networks are not part of active networks. Replace any such tethered
    // network with its underlying backing network, because ARC cannot match its
    // datapath with the tethered network configuration. For other cases, the
    // underlying_state is identical to state.
    const ash::NetworkState* const underlying_state =
        GetShillBackedNetwork(state);
    if (!underlying_state) {
      continue;
    }

    const auto it = shill_network_properties.find(underlying_state->path());
    const base::Value::Dict* shill_dict =
        (it != shill_network_properties.end()) ? &it->second : nullptr;
    auto network = arc::mojom::NetworkConfiguration::New();
    FillConfigurationsFromState(underlying_state, shill_dict, network.get());
    networks.push_back(std::move(network));
  }
  return networks;
}

std::vector<arc::mojom::WifiScanResultPtr> TranslateScanResults(
    const ash::NetworkStateHandler::NetworkStateList& network_states) {
  std::vector<arc::mojom::WifiScanResultPtr> results;
  for (const ash::NetworkState* const state : network_states) {
    if (state->GetNetworkTechnologyType() !=
        ash::NetworkState::NetworkTechnologyType::kWiFi) {
      continue;
    }

    results.push_back(NetworkStateToWifiScanResult(*state));
  }
  return results;
}

base::Value::List TranslateSubjectNameMatchListToValue(
    const std::vector<std::string>& string_list) {
  base::Value::List result;
  for (const auto& item : string_list) {
    // Type and value is separated by ":" in vector, separate them by splitting
    // at ":".
    int idx = item.find(":");
    std::string type = item.substr(0, idx);
    std::string value = item.substr(idx + 1, item.size());
    base::Value::Dict entry;
    entry.Set(::onc::eap_subject_alternative_name_match::kType, type);
    entry.Set(::onc::eap_subject_alternative_name_match::kValue, value);
    result.Append(std::move(entry));
  }
  return result;
}

std::unique_ptr<patchpanel::SocketConnectionEvent>
TranslateSocketConnectionEvent(const mojom::SocketConnectionEventPtr& mojom) {
  std::unique_ptr<patchpanel::SocketConnectionEvent> msg =
      std::make_unique<patchpanel::SocketConnectionEvent>();

  msg->set_saddr(
      std::string{reinterpret_cast<const char*>(mojom->src_addr.bytes().data()),
                  mojom->src_addr.size()});
  msg->set_daddr(reinterpret_cast<const char*>(mojom->dst_addr.bytes().data()),
                 mojom->dst_addr.size());

  msg->set_sport(mojom->src_port);
  msg->set_dport(mojom->dst_port);

  if (const auto protocol = TranslateIpProtocol(mojom->proto);
      protocol != patchpanel::SocketConnectionEvent::IpProtocol::
                      SocketConnectionEvent_IpProtocol_UNKNOWN_PROTO) {
    msg->set_proto(protocol);
  } else {
    NET_LOG(ERROR) << "IP protocol is unknown, translate socket connection "
                      "event failed. IP protocol is: "
                   << mojom->proto;
    return nullptr;
  }

  if (const auto event = TranslateSocketEvent(mojom->event);
      event != patchpanel::SocketConnectionEvent::SocketEvent::
                   SocketConnectionEvent_SocketEvent_UNKNOWN_EVENT) {
    msg->set_event(event);
  } else {
    NET_LOG(ERROR) << "Socket event is unknown, translate socket connection "
                      "event failed. Socket event is: "
                   << mojom->event;
    return nullptr;
  }

  if (const auto category = TranslateQosCategory(mojom->qos_category)) {
    msg->set_category(category);
  }

  return msg;
}

bool AreConfigurationsEquivalent(
    std::vector<arc::mojom::NetworkConfigurationPtr>& latest_networks,
    std::vector<arc::mojom::NetworkConfigurationPtr>& cached_networks) {
  if (cached_networks.size() != latest_networks.size()) {
    return false;
  }

  const auto arc_iface_compare =
      [](const arc::mojom::NetworkConfigurationPtr& a,
         const arc::mojom::NetworkConfigurationPtr& b) -> bool {
    return a->arc_network_interface > b->arc_network_interface;
  };
  std::sort(cached_networks.begin(), cached_networks.end(), arc_iface_compare);
  std::sort(latest_networks.begin(), latest_networks.end(), arc_iface_compare);

  for (size_t i = 0; i < latest_networks.size(); ++i) {
    const arc::mojom::NetworkConfigurationPtr& latest = latest_networks.at(i);
    const arc::mojom::NetworkConfigurationPtr& cached = cached_networks.at(i);

    if (latest->arc_network_interface != cached->arc_network_interface ||
        latest->guid != cached->guid ||
        latest->connection_state != cached->connection_state ||
        latest->is_default_network != cached->is_default_network ||
        latest->type != cached->type ||
        latest->is_metered != cached->is_metered ||
        latest->network_interface != cached->network_interface ||
        latest->host_mtu != cached->host_mtu ||
        latest->host_dns_addresses != cached->host_dns_addresses ||
        latest->dns_proxy_addresses != cached->dns_proxy_addresses ||
        latest->host_search_domains != cached->host_search_domains ||
        latest->host_ipv4_address != cached->host_ipv4_address ||
        latest->host_ipv6_global_addresses !=
            cached->host_ipv6_global_addresses) {
      return false;
    }
  }

  return true;
}
}  // namespace arc::net_utils