// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The implementation of TranslateONCObjectToShill is structured in two parts:
// - The recursion through the existing ONC hierarchy
// see TranslateONCHierarchy
// - The local translation of an object depending on the associated signature
// see LocalTranslator::TranslateFields
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "ash/constants/ash_features.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/ash/components/network/client_cert_util.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/onc/network_onc_utils.h"
#include "chromeos/ash/components/network/onc/onc_translation_tables.h"
#include "chromeos/ash/components/network/onc/onc_translator.h"
#include "chromeos/ash/components/network/shill_property_util.h"
#include "chromeos/components/onc/onc_signature.h"
#include "components/onc/onc_constants.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::onc {
namespace {
// Converts values to JSON strings. This will turn booleans into "true" or
// "false" which is what Shill expects for VPN values (including L2TP).
base::Value ConvertVpnValueToString(const base::Value& value) {
if (value.is_string())
return value.Clone();
std::string str;
CHECK(base::JSONWriter::Write(value, &str));
return base::Value(str);
}
// Returns the string value of |key| from |dict| if found, or the empty string
// otherwise.
std::string FindStringKeyOrEmpty(const base::Value::Dict& dict,
std::string_view key) {
const std::string* value = dict.FindString(key);
return value ? *value : std::string();
}
// Sets any client cert properties when ClientCertType is PKCS11Id.
void SetClientCertProperties(client_cert::ConfigType config_type,
const base::Value::Dict& onc_object,
base::Value::Dict* shill_dictionary) {
const std::string cert_type =
FindStringKeyOrEmpty(onc_object, ::onc::client_cert::kClientCertType);
if (cert_type != ::onc::client_cert::kPKCS11Id)
return;
const std::string pkcs11_id =
FindStringKeyOrEmpty(onc_object, ::onc::client_cert::kClientCertPKCS11Id);
if (pkcs11_id.empty()) {
// If the cert type is PKCS11 but the pkcs11 id is empty, set empty
// properties to indicate 'no certificate'.
client_cert::SetEmptyShillProperties(config_type, *shill_dictionary);
return;
}
int slot_id;
std::string cert_id =
client_cert::GetPkcs11AndSlotIdFromEapCertId(pkcs11_id, &slot_id);
client_cert::SetShillProperties(config_type, slot_id, cert_id,
*shill_dictionary);
}
// This class is responsible to translate the local fields of the given
// |onc_object| according to |onc_signature| into |shill_dictionary|. This
// translation should consider (if possible) only fields of this ONC object and
// not nested objects because recursion is handled by the calling function
// TranslateONCHierarchy.
class LocalTranslator {
public:
LocalTranslator(const chromeos::onc::OncValueSignature& onc_signature,
const base::Value::Dict& onc_object,
base::Value::Dict* shill_dictionary)
: onc_signature_(&onc_signature),
onc_object_(&onc_object),
shill_dictionary_(shill_dictionary) {
field_translation_table_ = GetFieldTranslationTable(onc_signature);
}
LocalTranslator(const LocalTranslator&) = delete;
LocalTranslator& operator=(const LocalTranslator&) = delete;
void TranslateFields();
private:
void TranslateEthernet();
void TranslateOpenVPN();
void TranslateIPsec();
void TranslateL2TP();
void TranslateVPN();
void TranslateWiFi();
void TranslateEAP();
void TranslateStaticIPConfig();
void TranslateNetworkConfiguration();
void TranslateCellular();
void TranslateApn();
// Copies all entries from |onc_object_| to |shill_dictionary_| for which a
// translation (shill_property_name) is defined by the translation table for
// |onc_signature_|.
void CopyFieldsAccordingToSignature();
// If existent, copies the value of field |onc_field_name| from |onc_object_|
// to the property |shill_property_name| in |shill_dictionary_|.
void CopyFieldFromONCToShill(const std::string& onc_field_name,
const std::string& shill_property_name);
// Adds |value| to |shill_dictionary| at the field shill_property_name given
// by the associated signature. Takes ownership of |value|. Does nothing if
// |value| is none or the property name cannot be read from the signature.
void AddValueAccordingToSignature(const std::string& onc_field_name,
const base::Value& value);
// Translates the value |onc_value| using |table|. It is an error if no
// matching table entry is found. Writes the result as entry at
// |shill_property_name| in |shill_dictionary_|.
void TranslateWithTableAndSet(const std::string& onc_value,
const StringTranslationEntry table[],
const std::string& shill_property_name);
raw_ptr<const chromeos::onc::OncValueSignature> onc_signature_;
raw_ptr<const FieldTranslationEntry> field_translation_table_;
raw_ptr<const base::Value::Dict> onc_object_;
raw_ptr<base::Value::Dict> shill_dictionary_;
};
void LocalTranslator::TranslateFields() {
if (onc_signature_ == &chromeos::onc::kNetworkConfigurationSignature) {
TranslateNetworkConfiguration();
} else if (onc_signature_ == &chromeos::onc::kCellularSignature) {
TranslateCellular();
} else if (onc_signature_ == &chromeos::onc::kCellularApnSignature) {
TranslateApn();
} else if (onc_signature_ == &chromeos::onc::kEthernetSignature) {
TranslateEthernet();
} else if (onc_signature_ == &chromeos::onc::kVPNSignature) {
TranslateVPN();
} else if (onc_signature_ == &chromeos::onc::kOpenVPNSignature) {
TranslateOpenVPN();
} else if (onc_signature_ == &chromeos::onc::kIPsecSignature) {
TranslateIPsec();
} else if (onc_signature_ == &chromeos::onc::kL2TPSignature) {
TranslateL2TP();
} else if (onc_signature_ == &chromeos::onc::kWiFiSignature) {
TranslateWiFi();
} else if (onc_signature_ == &chromeos::onc::kEAPSignature) {
TranslateEAP();
} else if (onc_signature_ == &chromeos::onc::kStaticIPConfigSignature) {
TranslateStaticIPConfig();
} else {
CopyFieldsAccordingToSignature();
}
}
void LocalTranslator::TranslateEthernet() {
const std::string* authentication =
onc_object_->FindString(::onc::ethernet::kAuthentication);
const char* shill_type = shill::kTypeEthernet;
if (authentication && *authentication == ::onc::ethernet::k8021X)
shill_type = shill::kTypeEthernetEap;
shill_dictionary_->Set(shill::kTypeProperty, shill_type);
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateOpenVPN() {
// SaveCredentials needs special handling when translating from Shill -> ONC
// so handle it explicitly here.
CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
shill::kSaveCredentialsProperty);
std::string user_auth_type = FindStringKeyOrEmpty(
*onc_object_, ::onc::openvpn::kUserAuthenticationType);
// The default behavior (if user_auth_type is empty) is to use both password
// and OTP in a static challenge and only the password otherwise. As long as
// Shill doe not know about the exact user authentication type, this is
// identical to kPasswordAndOTP.
if (user_auth_type.empty())
user_auth_type = ::onc::openvpn_user_auth_type::kPasswordAndOTP;
NET_LOG(DEBUG) << "USER AUTH TYPE: " << user_auth_type;
if (user_auth_type == ::onc::openvpn_user_auth_type::kPassword ||
user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP) {
CopyFieldFromONCToShill(::onc::openvpn::kPassword,
shill::kOpenVPNPasswordProperty);
}
if (user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP)
CopyFieldFromONCToShill(::onc::openvpn::kOTP, shill::kOpenVPNOTPProperty);
if (user_auth_type == ::onc::openvpn_user_auth_type::kOTP)
CopyFieldFromONCToShill(::onc::openvpn::kOTP, shill::kOpenVPNTokenProperty);
// Shill supports only one RemoteCertKU but ONC specifies a list, so copy only
// the first entry if the lists exists. Otherwise copy an empty string to
// reset any previous configuration.
const base::Value::List* cert_kus =
onc_object_->FindList(::onc::openvpn::kRemoteCertKU);
std::string cert_ku;
if (cert_kus) {
if (!cert_kus->empty() && (*cert_kus)[0].is_string()) {
cert_ku = (*cert_kus)[0].GetString();
}
}
shill_dictionary_->Set(shill::kOpenVPNRemoteCertKUProperty, cert_ku);
SetClientCertProperties(client_cert::ConfigType::kOpenVpn, *onc_object_,
shill_dictionary_);
const std::string* compression_algorithm =
onc_object_->FindString(::onc::openvpn::kCompressionAlgorithm);
if (compression_algorithm) {
TranslateWithTableAndSet(*compression_algorithm,
kOpenVpnCompressionAlgorithmTable,
shill::kOpenVPNCompressProperty);
}
// Modified CopyFieldsAccordingToSignature to handle RemoteCertKU and
// ServerCAPEMs and handle all other fields as strings.
for (const auto it : *onc_object_) {
std::string key = it.first;
base::Value translated;
if (key == ::onc::openvpn::kRemoteCertKU ||
key == ::onc::openvpn::kServerCAPEMs ||
key == ::onc::openvpn::kExtraHosts) {
translated = it.second.Clone();
} else {
// Shill wants all Provider/VPN fields to be strings.
translated = ConvertVpnValueToString(it.second);
}
if (!translated.is_none())
AddValueAccordingToSignature(key, translated);
}
}
void LocalTranslator::TranslateIPsec() {
const auto ike_version = onc_object_->FindInt(::onc::ipsec::kIKEVersion);
if (ike_version.has_value() && ike_version.value() == 2) {
SetClientCertProperties(client_cert::ConfigType::kIkev2, *onc_object_,
shill_dictionary_);
// The translation table set in this object is for L2TP/IPsec, so we do the
// copy manually.
CopyFieldFromONCToShill(::onc::ipsec::kAuthenticationType,
shill::kIKEv2AuthenticationTypeProperty);
CopyFieldFromONCToShill(::onc::ipsec::kPSK, shill::kIKEv2PskProperty);
CopyFieldFromONCToShill(::onc::ipsec::kServerCAPEMs,
shill::kIKEv2CaCertPemProperty);
CopyFieldFromONCToShill(::onc::ipsec::kLocalIdentity,
shill::kIKEv2LocalIdentityProperty);
CopyFieldFromONCToShill(::onc::ipsec::kRemoteIdentity,
shill::kIKEv2RemoteIdentityProperty);
} else {
// For L2TP/IPsec.
SetClientCertProperties(client_cert::ConfigType::kL2tpIpsec, *onc_object_,
shill_dictionary_);
CopyFieldsAccordingToSignature();
}
// SaveCredentials needs special handling when translating from Shill -> ONC
// so handle it explicitly here.
CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
shill::kSaveCredentialsProperty);
}
void LocalTranslator::TranslateL2TP() {
// SaveCredentials needs special handling when translating from Shill -> ONC
// so handle it explicitly here.
CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
shill::kSaveCredentialsProperty);
const base::Value* lcp_echo_disabled =
onc_object_->Find(::onc::l2tp::kLcpEchoDisabled);
if (lcp_echo_disabled) {
base::Value lcp_echo_disabled_value =
ConvertVpnValueToString(*lcp_echo_disabled);
shill_dictionary_->Set(shill::kL2TPIPsecLcpEchoDisabledProperty,
std::move(lcp_echo_disabled_value));
}
// Set shill::kL2TPIPsecUseLoginPasswordProperty according to whether or not
// the password substitution variable is set.
const std::string* password = onc_object_->FindString(::onc::l2tp::kPassword);
if (password &&
*password == ::onc::substitutes::kPasswordPlaceholderVerbatim) {
// TODO(b/220249018): shill::kL2tpIpsecUseLoginPasswordProperty is a string
// property containing "false" or "true". Migrate it to a bool to match
// shill::kEapUseLoginPasswordProperty.
shill_dictionary_->Set(shill::kL2TPIPsecUseLoginPasswordProperty, "true");
}
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateVPN() {
const std::string* onc_type = onc_object_->FindString(::onc::vpn::kType);
if (onc_type) {
TranslateWithTableAndSet(*onc_type, kVPNTypeTable,
shill::kProviderTypeProperty);
}
if (onc_type && *onc_type == ::onc::vpn::kThirdPartyVpn) {
// For third-party VPNs, |shill::kProviderHostProperty| is used to store the
// provider's extension ID.
const base::Value::Dict* onc_third_party_vpn =
onc_object_->FindDict(::onc::vpn::kThirdPartyVpn);
if (onc_third_party_vpn) {
const std::string* onc_extension_id =
onc_third_party_vpn->FindString(::onc::third_party_vpn::kExtensionID);
if (onc_extension_id) {
shill_dictionary_->Set(shill::kProviderHostProperty, *onc_extension_id);
}
}
} else {
CopyFieldFromONCToShill(::onc::vpn::kHost, shill::kProviderHostProperty);
}
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateWiFi() {
const std::string* security = onc_object_->FindString(::onc::wifi::kSecurity);
if (security) {
TranslateWithTableAndSet(*security, kWiFiSecurityTable,
shill::kSecurityClassProperty);
if (*security == ::onc::wifi::kWEP_8021X) {
shill_dictionary_->Set(shill::kEapKeyMgmtProperty,
shill::kKeyManagementIEEE8021X);
}
}
// We currently only support managed and no adhoc networks.
shill_dictionary_->Set(shill::kModeProperty, shill::kModeManaged);
std::optional<bool> allow_gateway_arp_polling =
onc_object_->FindBool(::onc::wifi::kAllowGatewayARPPolling);
if (allow_gateway_arp_polling) {
shill_dictionary_->Set(shill::kLinkMonitorDisableProperty,
!*allow_gateway_arp_polling);
}
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateEAP() {
const std::string outer =
FindStringKeyOrEmpty(*onc_object_, ::onc::eap::kOuter);
if (!outer.empty())
TranslateWithTableAndSet(outer, kEAPOuterTable, shill::kEapMethodProperty);
// Translate the inner protocol only for outer tunneling protocols.
if (outer == ::onc::eap::kPEAP || outer == ::onc::eap::kEAP_TTLS) {
std::string inner = FindStringKeyOrEmpty(*onc_object_, ::onc::eap::kInner);
// In ONC the Inner protocol defaults to "Automatic".
if (inner.empty())
inner = ::onc::eap::kAutomatic;
// ONC's Inner == "Automatic" translates to omitting the Phase2 property in
// Shill.
if (inner != ::onc::eap::kAutomatic) {
const StringTranslationEntry* table =
GetEapInnerTranslationTableForOncOuter(outer);
if (table) {
TranslateWithTableAndSet(inner, table, shill::kEapPhase2AuthProperty);
}
}
}
SetClientCertProperties(client_cert::ConfigType::kEap, *onc_object_,
shill_dictionary_);
// Set shill::kEapUseLoginPasswordProperty according to whether or not the
// password substitution variable is set.
const std::string* password_field =
onc_object_->FindString(::onc::eap::kPassword);
if (password_field &&
*password_field == ::onc::substitutes::kPasswordPlaceholderVerbatim) {
shill_dictionary_->Set(shill::kEapUseLoginPasswordProperty, true);
}
// Set shill::kEapSubjectAlternativeNameMatchProperty to the serialized form
// of the subject alternative name match list of dictionaries.
const base::Value::List* subject_alternative_name_match =
onc_object_->FindList(::onc::eap::kSubjectAlternativeNameMatch);
if (subject_alternative_name_match) {
base::Value::List serialized_dicts;
std::string serialized_dict;
JSONStringValueSerializer serializer(&serialized_dict);
for (const base::Value& v : *subject_alternative_name_match) {
if (serializer.Serialize(v)) {
serialized_dicts.Append(serialized_dict);
}
}
shill_dictionary_->Set(shill::kEapSubjectAlternativeNameMatchProperty,
std::move(serialized_dicts));
}
CopyFieldsAccordingToSignature();
// Set value or an empty list for ServerCAPEMs if it is not provided by onc.
// It will override the previous known list during properties merge.
if (onc_object_->contains(::onc::eap::kServerCAPEMs)) {
CopyFieldFromONCToShill(::onc::eap::kServerCAPEMs,
shill::kEapCaCertPemProperty);
} else {
bool is_supported_ca_pem_protocols =
(outer == ::onc::eap::kEAP_TLS || outer == ::onc::eap::kEAP_TTLS ||
outer == ::onc::eap::kPEAP);
if (is_supported_ca_pem_protocols) {
shill_dictionary_->Set(shill::kEapCaCertPemProperty, base::Value::List());
}
}
}
void LocalTranslator::TranslateStaticIPConfig() {
CopyFieldsAccordingToSignature();
// Shill expects 4 valid nameserver values. Ensure all values are valid and
// replace any invalid values with 0.0.0.0 (which has no effect). See
// https://crbug.com/922219 for details.
base::Value::List* name_servers =
shill_dictionary_->FindList(shill::kNameServersProperty);
if (name_servers) {
static const char kDefaultIpAddr[] = "0.0.0.0";
net::IPAddress ip_addr;
for (base::Value& value_ref : *name_servers) {
// AssignFromIPLiteral returns true if a string is valid ipv4 or ipv6.
if (!ip_addr.AssignFromIPLiteral(value_ref.GetString()))
value_ref = base::Value(kDefaultIpAddr);
}
while (name_servers->size() < 4) {
name_servers->Append(base::Value(kDefaultIpAddr));
}
}
}
void LocalTranslator::TranslateNetworkConfiguration() {
const std::string type =
FindStringKeyOrEmpty(*onc_object_, ::onc::network_config::kType);
if (type == ::onc::network_type::kWimaxDeprecated) {
NET_LOG(ERROR) << "WiMAX ONC configuration is no longer supported.";
return;
}
// Note; The Ethernet type might be overridden to EthernetEap in
// TranslateEthernet if Ethernet specific properties are provided.
TranslateWithTableAndSet(type, kNetworkTypeTable, shill::kTypeProperty);
// Shill doesn't allow setting the name for non-VPN networks.
if (type == ::onc::network_type::kVPN)
CopyFieldFromONCToShill(::onc::network_config::kName, shill::kNameProperty);
const std::string ip_address_config_type = FindStringKeyOrEmpty(
*onc_object_, ::onc::network_config::kIPAddressConfigType);
const std::string name_servers_config_type = FindStringKeyOrEmpty(
*onc_object_, ::onc::network_config::kNameServersConfigType);
if ((ip_address_config_type == ::onc::network_config::kIPConfigTypeDHCP) &&
(name_servers_config_type == ::onc::network_config::kIPConfigTypeDHCP)) {
// If neither type is set to Static, provide an empty dictionary to ensure
// that any unset properties are cleared.
// Note: A type defaults to DHCP if not specified.
// TODO(b/245885527): Come up with a better way to handle ONC defaults.
shill_dictionary_->Set(shill::kStaticIPConfigProperty, base::Value::Dict());
}
const base::Value::Dict* proxy_settings =
onc_object_->FindDict(::onc::network_config::kProxySettings);
if (proxy_settings) {
base::Value::Dict proxy_config =
ConvertOncProxySettingsToProxyConfig(*proxy_settings)
.value_or(base::Value::Dict());
std::string proxy_config_str;
base::JSONWriter::Write(proxy_config, &proxy_config_str);
shill_dictionary_->Set(shill::kProxyConfigProperty, proxy_config_str);
}
const std::string* checkCaptivePortal =
onc_object_->FindString(::onc::network_config::kCheckCaptivePortal);
if (checkCaptivePortal) {
TranslateWithTableAndSet(*checkCaptivePortal,
kCheckCaptivePortalTranslationTable,
shill::kCheckPortalProperty);
}
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateCellular() {
// User APNs for a Cellular network can be enabled/disabled by the user.
// Shill should only get enabled user APNs to create the data connection.
if (const base::Value::List* user_apn_list =
onc_object_->FindList(::onc::cellular::kCustomAPNList)) {
base::Value::List enabled_apns;
for (const base::Value& apn : *user_apn_list) {
const std::string& state =
FindStringKeyOrEmpty(apn.GetDict(), ::onc::cellular_apn::kState);
if (state != ::onc::cellular_apn::kStateEnabled)
continue;
base::Value::Dict shill_apn;
LocalTranslator translator(chromeos::onc::kCellularApnSignature,
apn.GetDict(), &shill_apn);
translator.TranslateFields();
enabled_apns.Append(std::move(shill_apn));
}
shill_dictionary_->Set(shill::kCellularCustomApnListProperty,
base::Value(std::move(enabled_apns)));
}
CopyFieldsAccordingToSignature();
}
void LocalTranslator::TranslateApn() {
const std::string* authentication =
onc_object_->FindString(::onc::cellular_apn::kAuthentication);
if (authentication) {
TranslateWithTableAndSet(*authentication,
kApnAuthenticationTranslationTable,
shill::kApnAuthenticationProperty);
}
if (!ash::features::IsApnRevampEnabled()) {
CopyFieldsAccordingToSignature();
return;
}
const std::string* ip_type =
onc_object_->FindString(::onc::cellular_apn::kIpType);
if (ip_type) {
TranslateWithTableAndSet(*ip_type, kApnIpTypeTranslationTable,
shill::kApnIpTypeProperty);
}
const base::Value::List* apn_types =
onc_object_->FindList(::onc::cellular_apn::kApnTypes);
DCHECK(apn_types) << "APN must have APN types";
if (ash::features::IsApnRevampAndPoliciesEnabled()) {
const std::string* apn_source =
onc_object_->FindString(::onc::cellular_apn::kSource);
if (apn_source) {
// APNs being translated from ONC to Shill should only ever be provided by
// an admin or by the user via the UI.
const bool is_unexpected_source =
*apn_source != ::onc::cellular_apn::kSourceAdmin &&
*apn_source != ::onc::cellular_apn::kSourceUi;
if (is_unexpected_source) {
NET_LOG(ERROR) << R"(Unexpected ONC to Shill APN source type of ")"
<< *apn_source
<< R"(". Setting Shill APN source type to ")"
<< shill::kApnSourceUi << R"(".)";
shill_dictionary_->Set(shill::kApnSourceProperty, shill::kApnSourceUi);
} else {
TranslateWithTableAndSet(*apn_source, kApnSourceTranslationTable,
shill::kApnSourceProperty);
}
} else {
// Shill expects that APNs provided Chrome will only ever be provided by
// the UI or by an admin. We default to the source being the UI but check
// if it was provided by an admin. For more information see
// b/329714110#comment5 and b/333100319.
shill_dictionary_->Set(shill::kApnSourceProperty, shill::kApnSourceUi);
}
} else {
shill_dictionary_->Set(shill::kApnSourceProperty, shill::kApnSourceUi);
}
// Convert array of APN types to comma-delimited, de-duped string, i.e.
// ["Default", "Attach", "Default"] -> "DEFAULT,IA".
bool contains_default = false;
bool contains_attach = false;
bool contains_tether = false;
for (const auto& apn_type : *apn_types) {
std::string apn_type_string = apn_type.GetString();
if (apn_type_string == ::onc::cellular_apn::kApnTypeDefault) {
contains_default = true;
} else if (apn_type_string == ::onc::cellular_apn::kApnTypeAttach) {
contains_attach = true;
} else if (apn_type_string == ::onc::cellular_apn::kApnTypeTether) {
contains_tether = true;
} else {
NOTREACHED_IN_MIGRATION() << "Invalid APN type: " << apn_type;
}
}
std::vector<std::string> apn_type_strings;
if (contains_default) {
apn_type_strings.push_back(shill::kApnTypeDefault);
}
if (contains_attach) {
apn_type_strings.push_back(shill::kApnTypeIA);
}
if (contains_tether) {
apn_type_strings.push_back(shill::kApnTypeDun);
}
const std::string apn_types_string = base::JoinString(apn_type_strings, ",");
if (apn_types_string.empty()) {
NET_LOG(ERROR) << "APN must have at least one APN type";
}
shill_dictionary_->Set(shill::kApnTypesProperty, apn_types_string);
CopyFieldsAccordingToSignature();
}
void LocalTranslator::CopyFieldsAccordingToSignature() {
for (const auto it : *onc_object_) {
AddValueAccordingToSignature(it.first, it.second);
}
}
void LocalTranslator::CopyFieldFromONCToShill(
const std::string& onc_field_name,
const std::string& shill_property_name) {
const base::Value* value = onc_object_->Find(onc_field_name);
if (!value)
return;
const chromeos::onc::OncFieldSignature* field_signature =
chromeos::onc::GetFieldSignature(*onc_signature_, onc_field_name);
if (field_signature) {
base::Value::Type expected_type =
field_signature->value_signature->onc_type;
if (value->type() != expected_type) {
NET_LOG(ERROR) << "Found field " << onc_field_name << " of type "
<< value->type() << " but expected type " << expected_type;
return;
}
} else {
NET_LOG(ERROR)
<< "Attempt to translate a field that is not part of the ONC format.";
return;
}
shill_dictionary_->Set(shill_property_name, value->Clone());
}
void LocalTranslator::AddValueAccordingToSignature(const std::string& onc_name,
const base::Value& value) {
if (value.is_none() || !field_translation_table_)
return;
std::string shill_property_name;
if (!GetShillPropertyName(onc_name, field_translation_table_,
&shill_property_name)) {
return;
}
shill_dictionary_->Set(shill_property_name, value.Clone());
}
void LocalTranslator::TranslateWithTableAndSet(
const std::string& onc_value,
const StringTranslationEntry table[],
const std::string& shill_property_name) {
std::string shill_value;
if (TranslateStringToShill(table, onc_value, &shill_value)) {
shill_dictionary_->Set(shill_property_name, shill_value);
return;
}
// As we previously validate ONC, this case should never occur. If it still
// occurs, we should check here. Otherwise the failure will only show up much
// later in Shill.
NET_LOG(ERROR) << "Value '" << onc_value
<< "' cannot be translated to Shill property: "
<< shill_property_name;
}
// Iterates recursively over |onc_object| and its |signature|. At each object
// applies the local translation using LocalTranslator::TranslateFields. The
// results are written to |shill_dictionary|.
void TranslateONCHierarchy(const chromeos::onc::OncValueSignature& signature,
const base::Value::Dict& onc_object,
base::Value::Dict& shill_dictionary) {
const std::vector<std::string> path =
GetPathToNestedShillDictionary(signature);
base::Value::Dict* target_shill_dictionary = &shill_dictionary;
for (const std::string& path_piece : path) {
target_shill_dictionary = target_shill_dictionary->EnsureDict(path_piece);
}
// Translates fields of |onc_object| and writes them to
// |target_shill_dictionary_| nested in |shill_dictionary|.
LocalTranslator translator(signature, onc_object, target_shill_dictionary);
translator.TranslateFields();
// Recurse into nested objects.
for (const auto it : onc_object) {
if (!it.second.is_dict())
continue;
const chromeos::onc::OncFieldSignature* field_signature =
chromeos::onc::GetFieldSignature(signature, it.first);
if (!field_signature) {
NET_LOG(ERROR) << "Unexpected or deprecated ONC key: " << it.first;
continue;
}
TranslateONCHierarchy(*field_signature->value_signature,
it.second.GetDict(), shill_dictionary);
}
}
} // namespace
base::Value::Dict TranslateONCObjectToShill(
const chromeos::onc::OncValueSignature* onc_signature,
const base::Value::Dict& onc_object) {
CHECK(onc_signature != nullptr);
base::Value::Dict shill_dictionary;
TranslateONCHierarchy(*onc_signature, onc_object, shill_dictionary);
return shill_dictionary;
}
} // namespace ash::onc