blob: 19e7eff0b853eafc35ca31e6530a9863b60a5dad [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/network/onc/onc_translator.h"
#include <string>
#include "base/basictypes.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/values.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_util.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_translation_tables.h"
#include "chromeos/network/shill_property_util.h"
#include "components/onc/onc_constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
namespace onc {
namespace {
// Converts |str| to a base::Value of the given |type|. If the conversion fails,
// returns NULL.
scoped_ptr<base::Value> ConvertStringToValue(const std::string& str,
base::Value::Type type) {
base::Value* value;
if (type == base::Value::TYPE_STRING) {
value = new base::StringValue(str);
} else {
value = base::JSONReader::Read(str);
}
if (value == NULL || value->GetType() != type) {
delete value;
value = NULL;
}
return make_scoped_ptr(value);
}
// This class implements the translation of properties from the given
// |shill_dictionary| to a new ONC object of signature |onc_signature|. Using
// recursive calls to CreateTranslatedONCObject of new instances, nested objects
// are translated.
class ShillToONCTranslator {
public:
ShillToONCTranslator(const base::DictionaryValue& shill_dictionary,
const OncValueSignature& onc_signature)
: shill_dictionary_(&shill_dictionary),
onc_signature_(&onc_signature) {
field_translation_table_ = GetFieldTranslationTable(onc_signature);
}
ShillToONCTranslator(const base::DictionaryValue& shill_dictionary,
const OncValueSignature& onc_signature,
const FieldTranslationEntry* field_translation_table)
: shill_dictionary_(&shill_dictionary),
onc_signature_(&onc_signature),
field_translation_table_(field_translation_table) {
}
// Translates the associated Shill dictionary and creates an ONC object of the
// given signature.
scoped_ptr<base::DictionaryValue> CreateTranslatedONCObject();
private:
void TranslateEthernet();
void TranslateOpenVPN();
void TranslateIPsec();
void TranslateVPN();
void TranslateWiFiWithState();
void TranslateCellularWithState();
void TranslateCellularDevice();
void TranslateNetworkWithState();
void TranslateIPConfig();
// Creates an ONC object from |dictionary| according to the signature
// associated to |onc_field_name| and adds it to |onc_object_| at
// |onc_field_name|.
void TranslateAndAddNestedObject(const std::string& onc_field_name,
const base::DictionaryValue& dictionary);
// Creates an ONC object from |shill_dictionary_| according to the signature
// associated to |onc_field_name| and adds it to |onc_object_| at
// |onc_field_name|.
void TranslateAndAddNestedObject(const std::string& onc_field_name);
// Translates a list of nested objects and adds the list to |onc_object_| at
// |onc_field_name|. If there are errors while parsing individual objects or
// if the resulting list contains no entries, the result will not be added to
// |onc_object_|.
void TranslateAndAddListOfObjects(const std::string& onc_field_name,
const base::ListValue& list);
// Applies function CopyProperty to each field of |value_signature| and its
// base signatures.
void CopyPropertiesAccordingToSignature(
const OncValueSignature* value_signature);
// Applies function CopyProperty to each field of |onc_signature_| and its
// base signatures.
void CopyPropertiesAccordingToSignature();
// If |shill_property_name| is defined in |field_signature|, copies this
// entry from |shill_dictionary_| to |onc_object_| if it exists.
void CopyProperty(const OncFieldSignature* field_signature);
// If existent, translates the entry at |shill_property_name| in
// |shill_dictionary_| using |table|. It is an error if no matching table
// entry is found. Writes the result as entry at |onc_field_name| in
// |onc_object_|.
void TranslateWithTableAndSet(const std::string& shill_property_name,
const StringTranslationEntry table[],
const std::string& onc_field_name);
const base::DictionaryValue* shill_dictionary_;
const OncValueSignature* onc_signature_;
const FieldTranslationEntry* field_translation_table_;
scoped_ptr<base::DictionaryValue> onc_object_;
DISALLOW_COPY_AND_ASSIGN(ShillToONCTranslator);
};
scoped_ptr<base::DictionaryValue>
ShillToONCTranslator::CreateTranslatedONCObject() {
onc_object_.reset(new base::DictionaryValue);
if (onc_signature_ == &kNetworkWithStateSignature) {
TranslateNetworkWithState();
} else if (onc_signature_ == &kEthernetSignature) {
TranslateEthernet();
} else if (onc_signature_ == &kVPNSignature) {
TranslateVPN();
} else if (onc_signature_ == &kOpenVPNSignature) {
TranslateOpenVPN();
} else if (onc_signature_ == &kIPsecSignature) {
TranslateIPsec();
} else if (onc_signature_ == &kWiFiWithStateSignature) {
TranslateWiFiWithState();
} else if (onc_signature_ == &kCellularWithStateSignature) {
if (field_translation_table_ == kCellularDeviceTable)
TranslateCellularDevice();
else
TranslateCellularWithState();
} else if (onc_signature_ == &kIPConfigSignature) {
TranslateIPConfig();
} else {
CopyPropertiesAccordingToSignature();
}
return onc_object_.Pass();
}
void ShillToONCTranslator::TranslateEthernet() {
std::string shill_network_type;
shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
&shill_network_type);
const char* onc_auth = ::onc::ethernet::kAuthenticationNone;
if (shill_network_type == shill::kTypeEthernetEap)
onc_auth = ::onc::ethernet::k8021X;
onc_object_->SetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
onc_auth);
}
void ShillToONCTranslator::TranslateOpenVPN() {
if (shill_dictionary_->HasKey(shill::kOpenVPNVerifyX509NameProperty))
TranslateAndAddNestedObject(::onc::openvpn::kVerifyX509);
// Shill supports only one RemoteCertKU but ONC requires a list. If existing,
// wraps the value into a list.
std::string certKU;
if (shill_dictionary_->GetStringWithoutPathExpansion(
shill::kOpenVPNRemoteCertKUProperty, &certKU)) {
scoped_ptr<base::ListValue> certKUs(new base::ListValue);
certKUs->AppendString(certKU);
onc_object_->SetWithoutPathExpansion(::onc::openvpn::kRemoteCertKU,
certKUs.release());
}
for (const OncFieldSignature* field_signature = onc_signature_->fields;
field_signature->onc_field_name != NULL; ++field_signature) {
const std::string& onc_field_name = field_signature->onc_field_name;
if (onc_field_name == ::onc::vpn::kSaveCredentials ||
onc_field_name == ::onc::openvpn::kRemoteCertKU ||
onc_field_name == ::onc::openvpn::kServerCAPEMs) {
CopyProperty(field_signature);
continue;
}
std::string shill_property_name;
const base::Value* shill_value = NULL;
if (!field_translation_table_ ||
!GetShillPropertyName(field_signature->onc_field_name,
field_translation_table_,
&shill_property_name) ||
!shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
&shill_value)) {
continue;
}
scoped_ptr<base::Value> translated;
std::string shill_str;
if (shill_value->GetAsString(&shill_str)) {
// Shill wants all Provider/VPN fields to be strings. Translates these
// strings back to the correct ONC type.
translated = ConvertStringToValue(
shill_str,
field_signature->value_signature->onc_type);
if (translated.get() == NULL) {
LOG(ERROR) << "Shill property '" << shill_property_name
<< "' with value " << *shill_value
<< " couldn't be converted to base::Value::Type "
<< field_signature->value_signature->onc_type;
} else {
onc_object_->SetWithoutPathExpansion(onc_field_name,
translated.release());
}
} else {
LOG(ERROR) << "Shill property '" << shill_property_name
<< "' has value " << *shill_value
<< ", but expected a string";
}
}
}
void ShillToONCTranslator::TranslateIPsec() {
CopyPropertiesAccordingToSignature();
if (shill_dictionary_->HasKey(shill::kL2tpIpsecXauthUserProperty))
TranslateAndAddNestedObject(::onc::ipsec::kXAUTH);
}
void ShillToONCTranslator::TranslateVPN() {
TranslateWithTableAndSet(
shill::kProviderTypeProperty, kVPNTypeTable, ::onc::vpn::kType);
CopyPropertiesAccordingToSignature();
std::string vpn_type;
if (onc_object_->GetStringWithoutPathExpansion(::onc::vpn::kType,
&vpn_type)) {
if (vpn_type == ::onc::vpn::kTypeL2TP_IPsec) {
TranslateAndAddNestedObject(::onc::vpn::kIPsec);
TranslateAndAddNestedObject(::onc::vpn::kL2TP);
} else {
TranslateAndAddNestedObject(vpn_type);
}
}
}
void ShillToONCTranslator::TranslateWiFiWithState() {
TranslateWithTableAndSet(
shill::kSecurityProperty, kWiFiSecurityTable, ::onc::wifi::kSecurity);
std::string ssid = shill_property_util::GetSSIDFromProperties(
*shill_dictionary_, NULL /* ignore unknown encoding */);
if (!ssid.empty())
onc_object_->SetStringWithoutPathExpansion(::onc::wifi::kSSID, ssid);
CopyPropertiesAccordingToSignature();
}
void ShillToONCTranslator::TranslateCellularWithState() {
CopyPropertiesAccordingToSignature();
const base::DictionaryValue* dictionary = NULL;
if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
shill::kServingOperatorProperty, &dictionary)) {
TranslateAndAddNestedObject(::onc::cellular::kServingOperator, *dictionary);
}
if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
shill::kCellularApnProperty, &dictionary)) {
TranslateAndAddNestedObject(::onc::cellular::kAPN, *dictionary);
}
// Merge the Device dictionary with this one (Cellular) using the
// CellularDevice signature.
const base::DictionaryValue* device_dictionary = NULL;
if (!shill_dictionary_->GetDictionaryWithoutPathExpansion(
shill::kDeviceProperty, &device_dictionary)) {
return;
}
ShillToONCTranslator nested_translator(*device_dictionary,
kCellularWithStateSignature,
kCellularDeviceTable);
scoped_ptr<base::DictionaryValue> nested_object =
nested_translator.CreateTranslatedONCObject();
onc_object_->MergeDictionary(nested_object.get());
}
void ShillToONCTranslator::TranslateCellularDevice() {
CopyPropertiesAccordingToSignature();
const base::DictionaryValue* shill_sim_lock_status = NULL;
if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
shill::kSIMLockStatusProperty, &shill_sim_lock_status)) {
TranslateAndAddNestedObject(::onc::cellular::kSIMLockStatus,
*shill_sim_lock_status);
}
const base::ListValue* shill_apns = NULL;
if (shill_dictionary_->GetListWithoutPathExpansion(
shill::kCellularApnListProperty, &shill_apns)) {
TranslateAndAddListOfObjects(::onc::cellular::kAPNList, *shill_apns);
}
const base::ListValue* shill_found_networks = NULL;
if (shill_dictionary_->GetListWithoutPathExpansion(
shill::kFoundNetworksProperty, &shill_found_networks)) {
TranslateAndAddListOfObjects(::onc::cellular::kFoundNetworks,
*shill_found_networks);
}
}
void ShillToONCTranslator::TranslateNetworkWithState() {
CopyPropertiesAccordingToSignature();
std::string shill_network_type;
shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
&shill_network_type);
std::string onc_network_type = ::onc::network_type::kEthernet;
if (shill_network_type != shill::kTypeEthernet &&
shill_network_type != shill::kTypeEthernetEap) {
TranslateStringToONC(
kNetworkTypeTable, shill_network_type, &onc_network_type);
}
// Translate nested Cellular, WiFi, etc. properties.
if (!onc_network_type.empty()) {
onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kType,
onc_network_type);
TranslateAndAddNestedObject(onc_network_type);
}
// Since Name is a read only field in Shill unless it's a VPN, it is copied
// here, but not when going the other direction (if it's not a VPN).
std::string name;
shill_dictionary_->GetStringWithoutPathExpansion(shill::kNameProperty,
&name);
onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kName,
name);
// Limit ONC state to "NotConnected", "Connected", or "Connecting".
std::string state;
if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kStateProperty,
&state)) {
std::string onc_state = ::onc::connection_state::kNotConnected;
if (NetworkState::StateIsConnected(state)) {
onc_state = ::onc::connection_state::kConnected;
} else if (NetworkState::StateIsConnecting(state)) {
onc_state = ::onc::connection_state::kConnecting;
}
onc_object_->SetStringWithoutPathExpansion(
::onc::network_config::kConnectionState, onc_state);
}
// Use a human-readable aa:bb format for any hardware MAC address. Note:
// this property is provided by the caller but is not part of the Shill
// Service properties (it is copied from the Device properties).
std::string address;
if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kAddressProperty,
&address)) {
onc_object_->SetStringWithoutPathExpansion(
::onc::network_config::kMacAddress,
network_util::FormattedMacAddress(address));
}
// Shill's Service has an IPConfig property (note the singular), not an
// IPConfigs property. However, we require the caller of the translation to
// patch the Shill dictionary before passing it to the translator.
const base::ListValue* shill_ipconfigs = NULL;
if (shill_dictionary_->GetListWithoutPathExpansion(shill::kIPConfigsProperty,
&shill_ipconfigs)) {
TranslateAndAddListOfObjects(::onc::network_config::kIPConfigs,
*shill_ipconfigs);
}
}
void ShillToONCTranslator::TranslateIPConfig() {
CopyPropertiesAccordingToSignature();
std::string shill_ip_method;
shill_dictionary_->GetStringWithoutPathExpansion(shill::kMethodProperty,
&shill_ip_method);
std::string type;
if (shill_ip_method == shill::kTypeIPv4 ||
shill_ip_method == shill::kTypeDHCP) {
type = ::onc::ipconfig::kIPv4;
} else if (shill_ip_method == shill::kTypeIPv6 ||
shill_ip_method == shill::kTypeDHCP6) {
type = ::onc::ipconfig::kIPv6;
} else {
return; // Ignore unhandled IPConfig types, e.g. bootp, zeroconf, ppp
}
onc_object_->SetStringWithoutPathExpansion(::onc::ipconfig::kType, type);
}
void ShillToONCTranslator::TranslateAndAddNestedObject(
const std::string& onc_field_name) {
TranslateAndAddNestedObject(onc_field_name, *shill_dictionary_);
}
void ShillToONCTranslator::TranslateAndAddNestedObject(
const std::string& onc_field_name,
const base::DictionaryValue& dictionary) {
const OncFieldSignature* field_signature =
GetFieldSignature(*onc_signature_, onc_field_name);
if (!field_signature) {
NOTREACHED() << "Unable to find signature for field: " << onc_field_name;
return;
}
ShillToONCTranslator nested_translator(dictionary,
*field_signature->value_signature);
scoped_ptr<base::DictionaryValue> nested_object =
nested_translator.CreateTranslatedONCObject();
if (nested_object->empty())
return;
onc_object_->SetWithoutPathExpansion(onc_field_name, nested_object.release());
}
void ShillToONCTranslator::TranslateAndAddListOfObjects(
const std::string& onc_field_name,
const base::ListValue& list) {
const OncFieldSignature* field_signature =
GetFieldSignature(*onc_signature_, onc_field_name);
if (field_signature->value_signature->onc_type != base::Value::TYPE_LIST) {
LOG(ERROR) << "ONC Field name: '" << onc_field_name << "' has type '"
<< field_signature->value_signature->onc_type
<< "', expected: base::Value::TYPE_LIST.";
return;
}
DCHECK(field_signature->value_signature->onc_array_entry_signature);
scoped_ptr<base::ListValue> result(new base::ListValue());
for (base::ListValue::const_iterator it = list.begin();
it != list.end(); ++it) {
const base::DictionaryValue* shill_value = NULL;
if (!(*it)->GetAsDictionary(&shill_value))
continue;
ShillToONCTranslator nested_translator(
*shill_value,
*field_signature->value_signature->onc_array_entry_signature);
scoped_ptr<base::DictionaryValue> nested_object =
nested_translator.CreateTranslatedONCObject();
// If the nested object couldn't be parsed, simply omit it.
if (nested_object->empty())
continue;
result->Append(nested_object.release());
}
// If there are no entries in the list, there is no need to expose this field.
if (result->empty())
return;
onc_object_->SetWithoutPathExpansion(onc_field_name, result.release());
}
void ShillToONCTranslator::CopyPropertiesAccordingToSignature() {
CopyPropertiesAccordingToSignature(onc_signature_);
}
void ShillToONCTranslator::CopyPropertiesAccordingToSignature(
const OncValueSignature* value_signature) {
if (value_signature->base_signature)
CopyPropertiesAccordingToSignature(value_signature->base_signature);
for (const OncFieldSignature* field_signature = value_signature->fields;
field_signature->onc_field_name != NULL; ++field_signature) {
CopyProperty(field_signature);
}
}
void ShillToONCTranslator::CopyProperty(
const OncFieldSignature* field_signature) {
std::string shill_property_name;
const base::Value* shill_value = NULL;
if (!field_translation_table_ ||
!GetShillPropertyName(field_signature->onc_field_name,
field_translation_table_,
&shill_property_name) ||
!shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
&shill_value)) {
return;
}
if (shill_value->GetType() != field_signature->value_signature->onc_type) {
LOG(ERROR) << "Shill property '" << shill_property_name
<< "' with value " << *shill_value
<< " has base::Value::Type " << shill_value->GetType()
<< " but ONC field '" << field_signature->onc_field_name
<< "' requires type "
<< field_signature->value_signature->onc_type << ".";
return;
}
onc_object_->SetWithoutPathExpansion(field_signature->onc_field_name,
shill_value->DeepCopy());
}
void ShillToONCTranslator::TranslateWithTableAndSet(
const std::string& shill_property_name,
const StringTranslationEntry table[],
const std::string& onc_field_name) {
std::string shill_value;
if (!shill_dictionary_->GetStringWithoutPathExpansion(shill_property_name,
&shill_value)) {
return;
}
std::string onc_value;
if (TranslateStringToONC(table, shill_value, &onc_value)) {
onc_object_->SetStringWithoutPathExpansion(onc_field_name, onc_value);
return;
}
LOG(ERROR) << "Shill property '" << shill_property_name << "' with value "
<< shill_value << " couldn't be translated to ONC";
}
} // namespace
scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart(
const base::DictionaryValue& shill_dictionary,
const OncValueSignature* onc_signature) {
CHECK(onc_signature != NULL);
ShillToONCTranslator translator(shill_dictionary, *onc_signature);
return translator.CreateTranslatedONCObject();
}
} // namespace onc
} // namespace chromeos