onc_translator_shill_to_onc.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chromeos/network/onc/onc_translator.h"
6
7#include <string>
8
9#include "base/basictypes.h"
10#include "base/json/json_reader.h"
11#include "base/json/json_writer.h"
12#include "base/logging.h"
13#include "base/values.h"
14#include "chromeos/network/network_state.h"
15#include "chromeos/network/onc/onc_signature.h"
16#include "chromeos/network/onc/onc_translation_tables.h"
17#include "chromeos/network/shill_property_util.h"
18#include "components/onc/onc_constants.h"
19#include "third_party/cros_system_api/dbus/service_constants.h"
20
21namespace chromeos {
22namespace onc {
23
24namespace {
25
26// Converts |str| to a base::Value of the given |type|. If the conversion fails,
27// returns NULL.
28scoped_ptr<base::Value> ConvertStringToValue(const std::string& str,
29                                             base::Value::Type type) {
30  base::Value* value;
31  if (type == base::Value::TYPE_STRING) {
32    value = base::Value::CreateStringValue(str);
33  } else {
34    value = base::JSONReader::Read(str);
35  }
36
37  if (value == NULL || value->GetType() != type) {
38    delete value;
39    value = NULL;
40  }
41  return make_scoped_ptr(value);
42}
43
44// This class implements the translation of properties from the given
45// |shill_dictionary| to a new ONC object of signature |onc_signature|. Using
46// recursive calls to CreateTranslatedONCObject of new instances, nested objects
47// are translated.
48class ShillToONCTranslator {
49 public:
50  ShillToONCTranslator(const base::DictionaryValue& shill_dictionary,
51                       const OncValueSignature& onc_signature)
52      : shill_dictionary_(&shill_dictionary),
53        onc_signature_(&onc_signature) {
54    field_translation_table_ = GetFieldTranslationTable(onc_signature);
55  }
56
57  // Translates the associated Shill dictionary and creates an ONC object of the
58  // given signature.
59  scoped_ptr<base::DictionaryValue> CreateTranslatedONCObject();
60
61 private:
62  void TranslateEthernet();
63  void TranslateOpenVPN();
64  void TranslateIPsec();
65  void TranslateVPN();
66  void TranslateWiFiWithState();
67  void TranslateCellularWithState();
68  void TranslateNetworkWithState();
69  void TranslateIPConfig();
70
71  // Creates an ONC object from |dictionary| according to the signature
72  // associated to |onc_field_name| and adds it to |onc_object_| at
73  // |onc_field_name|.
74  void TranslateAndAddNestedObject(const std::string& onc_field_name,
75                                   const base::DictionaryValue& dictionary);
76
77  // Creates an ONC object from |shill_dictionary_| according to the signature
78  // associated to |onc_field_name| and adds it to |onc_object_| at
79  // |onc_field_name|.
80  void TranslateAndAddNestedObject(const std::string& onc_field_name);
81
82  // Translates a list of nested objects and adds the list to |onc_object_| at
83  // |onc_field_name|. If there are errors while parsing individual objects or
84  // if the resulting list contains no entries, the result will not be added to
85  // |onc_object_|.
86  void TranslateAndAddListOfObjects(const std::string& onc_field_name,
87                                    const base::ListValue& list);
88
89  // Applies function CopyProperty to each field of |value_signature| and its
90  // base signatures.
91  void CopyPropertiesAccordingToSignature(
92      const OncValueSignature* value_signature);
93
94  // Applies function CopyProperty to each field of |onc_signature_| and its
95  // base signatures.
96  void CopyPropertiesAccordingToSignature();
97
98  // If |shill_property_name| is defined in |field_signature|, copies this
99  // entry from |shill_dictionary_| to |onc_object_| if it exists.
100  void CopyProperty(const OncFieldSignature* field_signature);
101
102  // If existent, translates the entry at |shill_property_name| in
103  // |shill_dictionary_| using |table|. It is an error if no matching table
104  // entry is found. Writes the result as entry at |onc_field_name| in
105  // |onc_object_|.
106  void TranslateWithTableAndSet(const std::string& shill_property_name,
107                                const StringTranslationEntry table[],
108                                const std::string& onc_field_name);
109
110  const base::DictionaryValue* shill_dictionary_;
111  const OncValueSignature* onc_signature_;
112  const FieldTranslationEntry* field_translation_table_;
113  scoped_ptr<base::DictionaryValue> onc_object_;
114
115  DISALLOW_COPY_AND_ASSIGN(ShillToONCTranslator);
116};
117
118scoped_ptr<base::DictionaryValue>
119ShillToONCTranslator::CreateTranslatedONCObject() {
120  onc_object_.reset(new base::DictionaryValue);
121  if (onc_signature_ == &kNetworkWithStateSignature) {
122    TranslateNetworkWithState();
123  } else if (onc_signature_ == &kEthernetSignature) {
124    TranslateEthernet();
125  } else if (onc_signature_ == &kVPNSignature) {
126    TranslateVPN();
127  } else if (onc_signature_ == &kOpenVPNSignature) {
128    TranslateOpenVPN();
129  } else if (onc_signature_ == &kIPsecSignature) {
130    TranslateIPsec();
131  } else if (onc_signature_ == &kWiFiWithStateSignature) {
132    TranslateWiFiWithState();
133  } else if (onc_signature_ == &kCellularWithStateSignature) {
134    TranslateCellularWithState();
135  } else if (onc_signature_ == &kIPConfigSignature) {
136    TranslateIPConfig();
137  } else {
138    CopyPropertiesAccordingToSignature();
139  }
140  return onc_object_.Pass();
141}
142
143void ShillToONCTranslator::TranslateEthernet() {
144  std::string shill_network_type;
145  shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
146                                                   &shill_network_type);
147  const char* onc_auth = ::onc::ethernet::kNone;
148  if (shill_network_type == shill::kTypeEthernetEap)
149    onc_auth = ::onc::ethernet::k8021X;
150  onc_object_->SetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
151                                             onc_auth);
152}
153
154void ShillToONCTranslator::TranslateOpenVPN() {
155  if (shill_dictionary_->HasKey(shill::kOpenVPNVerifyX509NameProperty))
156    TranslateAndAddNestedObject(::onc::openvpn::kVerifyX509);
157
158  // Shill supports only one RemoteCertKU but ONC requires a list. If existing,
159  // wraps the value into a list.
160  std::string certKU;
161  if (shill_dictionary_->GetStringWithoutPathExpansion(
162          shill::kOpenVPNRemoteCertKUProperty, &certKU)) {
163    scoped_ptr<base::ListValue> certKUs(new base::ListValue);
164    certKUs->AppendString(certKU);
165    onc_object_->SetWithoutPathExpansion(::onc::openvpn::kRemoteCertKU,
166                                         certKUs.release());
167  }
168
169  for (const OncFieldSignature* field_signature = onc_signature_->fields;
170       field_signature->onc_field_name != NULL; ++field_signature) {
171    const std::string& onc_field_name = field_signature->onc_field_name;
172    if (onc_field_name == ::onc::vpn::kSaveCredentials ||
173        onc_field_name == ::onc::openvpn::kRemoteCertKU ||
174        onc_field_name == ::onc::openvpn::kServerCAPEMs) {
175      CopyProperty(field_signature);
176      continue;
177    }
178
179    std::string shill_property_name;
180    const base::Value* shill_value = NULL;
181    if (!field_translation_table_ ||
182        !GetShillPropertyName(field_signature->onc_field_name,
183                              field_translation_table_,
184                              &shill_property_name) ||
185        !shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
186                                                    &shill_value)) {
187      continue;
188    }
189
190    scoped_ptr<base::Value> translated;
191    std::string shill_str;
192    if (shill_value->GetAsString(&shill_str)) {
193      // Shill wants all Provider/VPN fields to be strings. Translates these
194      // strings back to the correct ONC type.
195      translated = ConvertStringToValue(
196          shill_str,
197          field_signature->value_signature->onc_type);
198
199      if (translated.get() == NULL) {
200        LOG(ERROR) << "Shill property '" << shill_property_name
201                   << "' with value " << *shill_value
202                   << " couldn't be converted to base::Value::Type "
203                   << field_signature->value_signature->onc_type;
204      } else {
205        onc_object_->SetWithoutPathExpansion(onc_field_name,
206                                             translated.release());
207      }
208    } else {
209      LOG(ERROR) << "Shill property '" << shill_property_name
210                 << "' has value " << *shill_value
211                 << ", but expected a string";
212    }
213  }
214}
215
216void ShillToONCTranslator::TranslateIPsec() {
217  CopyPropertiesAccordingToSignature();
218  if (shill_dictionary_->HasKey(shill::kL2tpIpsecXauthUserProperty))
219    TranslateAndAddNestedObject(::onc::ipsec::kXAUTH);
220}
221
222void ShillToONCTranslator::TranslateVPN() {
223  TranslateWithTableAndSet(
224      shill::kProviderTypeProperty, kVPNTypeTable, ::onc::vpn::kType);
225  CopyPropertiesAccordingToSignature();
226
227  std::string vpn_type;
228  if (onc_object_->GetStringWithoutPathExpansion(::onc::vpn::kType,
229                                                 &vpn_type)) {
230    if (vpn_type == ::onc::vpn::kTypeL2TP_IPsec) {
231      TranslateAndAddNestedObject(::onc::vpn::kIPsec);
232      TranslateAndAddNestedObject(::onc::vpn::kL2TP);
233    } else {
234      TranslateAndAddNestedObject(vpn_type);
235    }
236  }
237}
238
239void ShillToONCTranslator::TranslateWiFiWithState() {
240  TranslateWithTableAndSet(
241      shill::kSecurityProperty, kWiFiSecurityTable, ::onc::wifi::kSecurity);
242  std::string ssid = shill_property_util::GetSSIDFromProperties(
243      *shill_dictionary_, NULL /* ignore unknown encoding */);
244  if (!ssid.empty())
245    onc_object_->SetStringWithoutPathExpansion(::onc::wifi::kSSID, ssid);
246  CopyPropertiesAccordingToSignature();
247}
248
249void ShillToONCTranslator::TranslateCellularWithState() {
250  CopyPropertiesAccordingToSignature();
251  const base::DictionaryValue* dictionary = NULL;
252  if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
253        shill::kServingOperatorProperty, &dictionary)) {
254    TranslateAndAddNestedObject(::onc::cellular::kServingOperator, *dictionary);
255  }
256  if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
257        shill::kCellularApnProperty, &dictionary)) {
258    TranslateAndAddNestedObject(::onc::cellular::kAPN, *dictionary);
259  }
260  const base::ListValue* shill_apns = NULL;
261  if (shill_dictionary_->GetListWithoutPathExpansion(
262          shill::kCellularApnListProperty, &shill_apns)) {
263    TranslateAndAddListOfObjects(::onc::cellular::kAPNList, *shill_apns);
264  }
265}
266
267void ShillToONCTranslator::TranslateNetworkWithState() {
268  CopyPropertiesAccordingToSignature();
269
270  std::string shill_network_type;
271  shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
272                                                   &shill_network_type);
273  std::string onc_network_type = ::onc::network_type::kEthernet;
274  if (shill_network_type != shill::kTypeEthernet &&
275      shill_network_type != shill::kTypeEthernetEap) {
276    TranslateStringToONC(
277        kNetworkTypeTable, shill_network_type, &onc_network_type);
278  }
279  if (!onc_network_type.empty()) {
280    onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kType,
281                                               onc_network_type);
282    TranslateAndAddNestedObject(onc_network_type);
283  }
284
285  // Since Name is a read only field in Shill unless it's a VPN, it is copied
286  // here, but not when going the other direction (if it's not a VPN).
287  std::string name;
288  shill_dictionary_->GetStringWithoutPathExpansion(shill::kNameProperty,
289                                                   &name);
290  onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kName,
291                                             name);
292
293  std::string state;
294  if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kStateProperty,
295                                                       &state)) {
296    std::string onc_state = ::onc::connection_state::kNotConnected;
297    if (NetworkState::StateIsConnected(state)) {
298      onc_state = ::onc::connection_state::kConnected;
299    } else if (NetworkState::StateIsConnecting(state)) {
300      onc_state = ::onc::connection_state::kConnecting;
301    }
302    onc_object_->SetStringWithoutPathExpansion(
303        ::onc::network_config::kConnectionState, onc_state);
304  }
305
306  // Shill's Service has an IPConfig property (note the singular, and not a
307  // IPConfigs property). However, we require the caller of the translation to
308  // patch the Shill dictionary before passing it to the translator.
309  const base::ListValue* shill_ipconfigs = NULL;
310  if (shill_dictionary_->GetListWithoutPathExpansion(shill::kIPConfigsProperty,
311                                                     &shill_ipconfigs)) {
312    TranslateAndAddListOfObjects(::onc::network_config::kIPConfigs,
313                                 *shill_ipconfigs);
314  }
315}
316
317void ShillToONCTranslator::TranslateIPConfig() {
318  CopyPropertiesAccordingToSignature();
319  std::string shill_ip_method;
320  shill_dictionary_->GetStringWithoutPathExpansion(shill::kMethodProperty,
321                                                   &shill_ip_method);
322  if (shill_ip_method != shill::kTypeIPv4 &&
323      shill_ip_method != shill::kTypeIPv6) {
324    LOG(ERROR) << "Unhandled IPConfig Method value " << shill_ip_method;
325    return;
326  }
327
328  std::string type = ::onc::ipconfig::kIPv4;
329  if (shill_ip_method == shill::kTypeIPv6)
330    type = ::onc::ipconfig::kIPv6;
331  onc_object_->SetStringWithoutPathExpansion(::onc::ipconfig::kType, type);
332}
333
334void ShillToONCTranslator::TranslateAndAddNestedObject(
335    const std::string& onc_field_name) {
336  TranslateAndAddNestedObject(onc_field_name, *shill_dictionary_);
337}
338
339void ShillToONCTranslator::TranslateAndAddNestedObject(
340    const std::string& onc_field_name,
341    const base::DictionaryValue& dictionary) {
342  const OncFieldSignature* field_signature =
343      GetFieldSignature(*onc_signature_, onc_field_name);
344  DCHECK(field_signature) << "Unable to find signature for field "
345                          << onc_field_name << ".";
346  ShillToONCTranslator nested_translator(dictionary,
347                                         *field_signature->value_signature);
348  scoped_ptr<base::DictionaryValue> nested_object =
349      nested_translator.CreateTranslatedONCObject();
350  if (nested_object->empty())
351    return;
352  onc_object_->SetWithoutPathExpansion(onc_field_name, nested_object.release());
353}
354
355void ShillToONCTranslator::TranslateAndAddListOfObjects(
356    const std::string& onc_field_name,
357    const base::ListValue& list) {
358  const OncFieldSignature* field_signature =
359      GetFieldSignature(*onc_signature_, onc_field_name);
360  if (field_signature->value_signature->onc_type != base::Value::TYPE_LIST) {
361    LOG(ERROR) << "ONC Field name: '" << onc_field_name << "' has type '"
362               << field_signature->value_signature->onc_type
363               << "', expected: base::Value::TYPE_LIST.";
364    return;
365  }
366  DCHECK(field_signature->value_signature->onc_array_entry_signature);
367  scoped_ptr<base::ListValue> result(new base::ListValue());
368  for (base::ListValue::const_iterator it = list.begin();
369       it != list.end(); ++it) {
370    const base::DictionaryValue* shill_value = NULL;
371    if (!(*it)->GetAsDictionary(&shill_value))
372      continue;
373    ShillToONCTranslator nested_translator(
374        *shill_value,
375        *field_signature->value_signature->onc_array_entry_signature);
376    scoped_ptr<base::DictionaryValue> nested_object =
377        nested_translator.CreateTranslatedONCObject();
378    // If the nested object couldn't be parsed, simply omit it.
379    if (nested_object->empty())
380      continue;
381    result->Append(nested_object.release());
382  }
383  // If there are no entries in the list, there is no need to expose this field.
384  if (result->empty())
385    return;
386  onc_object_->SetWithoutPathExpansion(onc_field_name, result.release());
387}
388
389void ShillToONCTranslator::CopyPropertiesAccordingToSignature() {
390  CopyPropertiesAccordingToSignature(onc_signature_);
391}
392
393void ShillToONCTranslator::CopyPropertiesAccordingToSignature(
394    const OncValueSignature* value_signature) {
395  if (value_signature->base_signature)
396    CopyPropertiesAccordingToSignature(value_signature->base_signature);
397  for (const OncFieldSignature* field_signature = value_signature->fields;
398       field_signature->onc_field_name != NULL; ++field_signature) {
399    CopyProperty(field_signature);
400  }
401}
402
403void ShillToONCTranslator::CopyProperty(
404    const OncFieldSignature* field_signature) {
405  std::string shill_property_name;
406  const base::Value* shill_value = NULL;
407  if (!field_translation_table_ ||
408      !GetShillPropertyName(field_signature->onc_field_name,
409                            field_translation_table_,
410                            &shill_property_name) ||
411      !shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
412                                                  &shill_value)) {
413    return;
414  }
415
416  if (shill_value->GetType() != field_signature->value_signature->onc_type) {
417    LOG(ERROR) << "Shill property '" << shill_property_name
418               << "' with value " << *shill_value
419               << " has base::Value::Type " << shill_value->GetType()
420               << " but ONC field '" << field_signature->onc_field_name
421               << "' requires type "
422               << field_signature->value_signature->onc_type << ".";
423    return;
424  }
425
426 onc_object_->SetWithoutPathExpansion(field_signature->onc_field_name,
427                                      shill_value->DeepCopy());
428}
429
430void ShillToONCTranslator::TranslateWithTableAndSet(
431    const std::string& shill_property_name,
432    const StringTranslationEntry table[],
433    const std::string& onc_field_name) {
434  std::string shill_value;
435  if (!shill_dictionary_->GetStringWithoutPathExpansion(shill_property_name,
436                                                        &shill_value)) {
437    return;
438  }
439  std::string onc_value;
440  if (TranslateStringToONC(table, shill_value, &onc_value)) {
441    onc_object_->SetStringWithoutPathExpansion(onc_field_name, onc_value);
442    return;
443  }
444  LOG(ERROR) << "Shill property '" << shill_property_name << "' with value "
445             << shill_value << " couldn't be translated to ONC";
446}
447
448}  // namespace
449
450scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart(
451    const base::DictionaryValue& shill_dictionary,
452    const OncValueSignature* onc_signature) {
453  CHECK(onc_signature != NULL);
454
455  ShillToONCTranslator translator(shill_dictionary, *onc_signature);
456  return translator.CreateTranslatedONCObject();
457}
458
459}  // namespace onc
460}  // namespace chromeos
461