onc_translator_shill_to_onc.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 TranslateVPN();
65  void TranslateWiFiWithState();
66  void TranslateCellularWithState();
67  void TranslateNetworkWithState();
68
69  // Creates an ONC object from |dictionary| according to the signature
70  // associated to |onc_field_name| and adds it to |onc_object_| at
71  // |onc_field_name|.
72  void TranslateAndAddNestedObject(const std::string& onc_field_name,
73                                   const base::DictionaryValue& dictionary);
74
75  // Creates an ONC object from |shill_dictionary_| according to the signature
76  // associated to |onc_field_name| and adds it to |onc_object_| at
77  // |onc_field_name|.
78  void TranslateAndAddNestedObject(const std::string& onc_field_name);
79
80  // Translates a list of nested objects and adds the list to |onc_object_| at
81  // |onc_field_name|. If there are errors while parsing individual objects or
82  // if the resulting list contains no entries, the result will not be added to
83  // |onc_object_|.
84  void TranslateAndAddListOfObjects(const std::string& onc_field_name,
85                                    const base::ListValue& list);
86
87  // Applies function CopyProperty to each field of |value_signature| and its
88  // base signatures.
89  void CopyPropertiesAccordingToSignature(
90      const OncValueSignature* value_signature);
91
92  // Applies function CopyProperty to each field of |onc_signature_| and its
93  // base signatures.
94  void CopyPropertiesAccordingToSignature();
95
96  // If |shill_property_name| is defined in |field_signature|, copies this
97  // entry from |shill_dictionary_| to |onc_object_| if it exists.
98  void CopyProperty(const OncFieldSignature* field_signature);
99
100  // If existent, translates the entry at |shill_property_name| in
101  // |shill_dictionary_| using |table|. It is an error if no matching table
102  // entry is found. Writes the result as entry at |onc_field_name| in
103  // |onc_object_|.
104  void TranslateWithTableAndSet(const std::string& shill_property_name,
105                                const StringTranslationEntry table[],
106                                const std::string& onc_field_name);
107
108  const base::DictionaryValue* shill_dictionary_;
109  const OncValueSignature* onc_signature_;
110  const FieldTranslationEntry* field_translation_table_;
111  scoped_ptr<base::DictionaryValue> onc_object_;
112
113  DISALLOW_COPY_AND_ASSIGN(ShillToONCTranslator);
114};
115
116scoped_ptr<base::DictionaryValue>
117ShillToONCTranslator::CreateTranslatedONCObject() {
118  onc_object_.reset(new base::DictionaryValue);
119  if (onc_signature_ == &kNetworkWithStateSignature) {
120    TranslateNetworkWithState();
121  } else if (onc_signature_ == &kEthernetSignature) {
122    TranslateEthernet();
123  } else if (onc_signature_ == &kVPNSignature) {
124    TranslateVPN();
125  } else if (onc_signature_ == &kOpenVPNSignature) {
126    TranslateOpenVPN();
127  } else if (onc_signature_ == &kWiFiWithStateSignature) {
128    TranslateWiFiWithState();
129  } else if (onc_signature_ == &kCellularWithStateSignature) {
130    TranslateCellularWithState();
131  } else {
132    CopyPropertiesAccordingToSignature();
133  }
134  return onc_object_.Pass();
135}
136
137void ShillToONCTranslator::TranslateEthernet() {
138  std::string shill_network_type;
139  shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
140                                                   &shill_network_type);
141  const char* onc_auth = ::onc::ethernet::kNone;
142  if (shill_network_type == shill::kTypeEthernetEap)
143    onc_auth = ::onc::ethernet::k8021X;
144  onc_object_->SetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
145                                             onc_auth);
146}
147
148void ShillToONCTranslator::TranslateOpenVPN() {
149  if (shill_dictionary_->HasKey(shill::kOpenVPNVerifyX509NameProperty))
150    TranslateAndAddNestedObject(::onc::openvpn::kVerifyX509);
151
152  // Shill supports only one RemoteCertKU but ONC requires a list. If existing,
153  // wraps the value into a list.
154  std::string certKU;
155  if (shill_dictionary_->GetStringWithoutPathExpansion(
156          shill::kOpenVPNRemoteCertKUProperty, &certKU)) {
157    scoped_ptr<base::ListValue> certKUs(new base::ListValue);
158    certKUs->AppendString(certKU);
159    onc_object_->SetWithoutPathExpansion(::onc::openvpn::kRemoteCertKU,
160                                         certKUs.release());
161  }
162
163  for (const OncFieldSignature* field_signature = onc_signature_->fields;
164       field_signature->onc_field_name != NULL; ++field_signature) {
165    const std::string& onc_field_name = field_signature->onc_field_name;
166    if (onc_field_name == ::onc::vpn::kSaveCredentials ||
167        onc_field_name == ::onc::openvpn::kRemoteCertKU ||
168        onc_field_name == ::onc::openvpn::kServerCAPEMs) {
169      CopyProperty(field_signature);
170      continue;
171    }
172
173    std::string shill_property_name;
174    const base::Value* shill_value = NULL;
175    if (!field_translation_table_ ||
176        !GetShillPropertyName(field_signature->onc_field_name,
177                              field_translation_table_,
178                              &shill_property_name) ||
179        !shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
180                                                    &shill_value)) {
181      continue;
182    }
183
184    scoped_ptr<base::Value> translated;
185    std::string shill_str;
186    if (shill_value->GetAsString(&shill_str)) {
187      // Shill wants all Provider/VPN fields to be strings. Translates these
188      // strings back to the correct ONC type.
189      translated = ConvertStringToValue(
190          shill_str,
191          field_signature->value_signature->onc_type);
192
193      if (translated.get() == NULL) {
194        LOG(ERROR) << "Shill property '" << shill_property_name
195                   << "' with value " << *shill_value
196                   << " couldn't be converted to base::Value::Type "
197                   << field_signature->value_signature->onc_type;
198      } else {
199        onc_object_->SetWithoutPathExpansion(onc_field_name,
200                                             translated.release());
201      }
202    } else {
203      LOG(ERROR) << "Shill property '" << shill_property_name
204                 << "' has value " << *shill_value
205                 << ", but expected a string";
206    }
207  }
208}
209
210void ShillToONCTranslator::TranslateVPN() {
211  TranslateWithTableAndSet(
212      shill::kProviderTypeProperty, kVPNTypeTable, ::onc::vpn::kType);
213  CopyPropertiesAccordingToSignature();
214
215  std::string vpn_type;
216  if (onc_object_->GetStringWithoutPathExpansion(::onc::vpn::kType,
217                                                 &vpn_type)) {
218    if (vpn_type == ::onc::vpn::kTypeL2TP_IPsec) {
219      TranslateAndAddNestedObject(::onc::vpn::kIPsec);
220      TranslateAndAddNestedObject(::onc::vpn::kL2TP);
221    } else {
222      TranslateAndAddNestedObject(vpn_type);
223    }
224  }
225}
226
227void ShillToONCTranslator::TranslateWiFiWithState() {
228  TranslateWithTableAndSet(
229      shill::kSecurityProperty, kWiFiSecurityTable, ::onc::wifi::kSecurity);
230  std::string ssid = shill_property_util::GetSSIDFromProperties(
231      *shill_dictionary_, NULL /* ignore unknown encoding */);
232  if (!ssid.empty())
233    onc_object_->SetStringWithoutPathExpansion(::onc::wifi::kSSID, ssid);
234  CopyPropertiesAccordingToSignature();
235}
236
237void ShillToONCTranslator::TranslateCellularWithState() {
238  CopyPropertiesAccordingToSignature();
239  const base::DictionaryValue* dictionary = NULL;
240  if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
241        shill::kServingOperatorProperty, &dictionary)) {
242    TranslateAndAddNestedObject(::onc::cellular::kServingOperator, *dictionary);
243  }
244  if (shill_dictionary_->GetDictionaryWithoutPathExpansion(
245        shill::kCellularApnProperty, &dictionary)) {
246    TranslateAndAddNestedObject(::onc::cellular::kAPN, *dictionary);
247  }
248  const base::ListValue* list = NULL;
249  if (shill_dictionary_->GetListWithoutPathExpansion(
250          shill::kCellularApnListProperty, &list)) {
251    TranslateAndAddListOfObjects(::onc::cellular::kAPNList, *list);
252  }
253}
254
255void ShillToONCTranslator::TranslateNetworkWithState() {
256  CopyPropertiesAccordingToSignature();
257
258  std::string shill_network_type;
259  shill_dictionary_->GetStringWithoutPathExpansion(shill::kTypeProperty,
260                                                   &shill_network_type);
261  std::string onc_network_type = ::onc::network_type::kEthernet;
262  if (shill_network_type != shill::kTypeEthernet &&
263      shill_network_type != shill::kTypeEthernetEap) {
264    TranslateStringToONC(
265        kNetworkTypeTable, shill_network_type, &onc_network_type);
266  }
267  if (!onc_network_type.empty()) {
268    onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kType,
269                                               onc_network_type);
270    TranslateAndAddNestedObject(onc_network_type);
271  }
272
273  // Since Name is a read only field in Shill unless it's a VPN, it is copied
274  // here, but not when going the other direction (if it's not a VPN).
275  std::string name;
276  shill_dictionary_->GetStringWithoutPathExpansion(shill::kNameProperty,
277                                                   &name);
278  onc_object_->SetStringWithoutPathExpansion(::onc::network_config::kName,
279                                             name);
280
281  std::string state;
282  if (shill_dictionary_->GetStringWithoutPathExpansion(shill::kStateProperty,
283                                                       &state)) {
284    std::string onc_state = ::onc::connection_state::kNotConnected;
285    if (NetworkState::StateIsConnected(state)) {
286      onc_state = ::onc::connection_state::kConnected;
287    } else if (NetworkState::StateIsConnecting(state)) {
288      onc_state = ::onc::connection_state::kConnecting;
289    }
290    onc_object_->SetStringWithoutPathExpansion(
291        ::onc::network_config::kConnectionState, onc_state);
292  }
293}
294
295void ShillToONCTranslator::TranslateAndAddNestedObject(
296    const std::string& onc_field_name) {
297  TranslateAndAddNestedObject(onc_field_name, *shill_dictionary_);
298}
299
300void ShillToONCTranslator::TranslateAndAddNestedObject(
301    const std::string& onc_field_name,
302    const base::DictionaryValue& dictionary) {
303  const OncFieldSignature* field_signature =
304      GetFieldSignature(*onc_signature_, onc_field_name);
305  ShillToONCTranslator nested_translator(dictionary,
306                                         *field_signature->value_signature);
307  scoped_ptr<base::DictionaryValue> nested_object =
308      nested_translator.CreateTranslatedONCObject();
309  if (nested_object->empty())
310    return;
311  onc_object_->SetWithoutPathExpansion(onc_field_name, nested_object.release());
312}
313
314void ShillToONCTranslator::TranslateAndAddListOfObjects(
315    const std::string& onc_field_name,
316    const base::ListValue& list) {
317  const OncFieldSignature* field_signature =
318      GetFieldSignature(*onc_signature_, onc_field_name);
319  if (field_signature->value_signature->onc_type != Value::TYPE_LIST) {
320    LOG(ERROR) << "ONC Field name: '" << onc_field_name << "' has type '"
321               << field_signature->value_signature->onc_type
322               << "', expected: base::Value::TYPE_LIST.";
323    return;
324  }
325  DCHECK(field_signature->value_signature->onc_array_entry_signature);
326  scoped_ptr<base::ListValue> result(new base::ListValue());
327  for (base::ListValue::const_iterator it = list.begin();
328       it != list.end(); ++it) {
329    const base::DictionaryValue* shill_value = NULL;
330    if (!(*it)->GetAsDictionary(&shill_value))
331      continue;
332    ShillToONCTranslator nested_translator(
333        *shill_value,
334        *field_signature->value_signature->onc_array_entry_signature);
335    scoped_ptr<base::DictionaryValue> nested_object =
336        nested_translator.CreateTranslatedONCObject();
337    if (nested_object->empty())
338      // The nested object couldn't be parsed, so simply omit it.
339      continue;
340    result->Append(nested_object.release());
341  }
342  if (result->empty())
343    // There are no entries in the list, so there is no need to expose this
344    // field.
345    return;
346  onc_object_->SetWithoutPathExpansion(onc_field_name, result.release());
347}
348
349void ShillToONCTranslator::CopyPropertiesAccordingToSignature() {
350  CopyPropertiesAccordingToSignature(onc_signature_);
351}
352
353void ShillToONCTranslator::CopyPropertiesAccordingToSignature(
354    const OncValueSignature* value_signature) {
355  if (value_signature->base_signature)
356    CopyPropertiesAccordingToSignature(value_signature->base_signature);
357  for (const OncFieldSignature* field_signature = value_signature->fields;
358       field_signature->onc_field_name != NULL; ++field_signature) {
359    CopyProperty(field_signature);
360  }
361}
362
363void ShillToONCTranslator::CopyProperty(
364    const OncFieldSignature* field_signature) {
365  std::string shill_property_name;
366  const base::Value* shill_value = NULL;
367  if (!field_translation_table_ ||
368      !GetShillPropertyName(field_signature->onc_field_name,
369                            field_translation_table_,
370                            &shill_property_name) ||
371      !shill_dictionary_->GetWithoutPathExpansion(shill_property_name,
372                                                  &shill_value)) {
373    return;
374  }
375
376  if (shill_value->GetType() != field_signature->value_signature->onc_type) {
377    LOG(ERROR) << "Shill property '" << shill_property_name
378               << "' with value " << *shill_value
379               << " has base::Value::Type " << shill_value->GetType()
380               << " but ONC field '" << field_signature->onc_field_name
381               << "' requires type "
382               << field_signature->value_signature->onc_type << ".";
383    return;
384  }
385
386 onc_object_->SetWithoutPathExpansion(field_signature->onc_field_name,
387                                      shill_value->DeepCopy());
388}
389
390void ShillToONCTranslator::TranslateWithTableAndSet(
391    const std::string& shill_property_name,
392    const StringTranslationEntry table[],
393    const std::string& onc_field_name) {
394  std::string shill_value;
395  if (!shill_dictionary_->GetStringWithoutPathExpansion(shill_property_name,
396                                                        &shill_value)) {
397    return;
398  }
399  std::string onc_value;
400  if (TranslateStringToONC(table, shill_value, &onc_value)) {
401    onc_object_->SetStringWithoutPathExpansion(onc_field_name, onc_value);
402    return;
403  }
404  LOG(ERROR) << "Shill property '" << shill_property_name << "' with value "
405             << shill_value << " couldn't be translated to ONC";
406}
407
408}  // namespace
409
410scoped_ptr<base::DictionaryValue> TranslateShillServiceToONCPart(
411    const base::DictionaryValue& shill_dictionary,
412    const OncValueSignature* onc_signature) {
413  CHECK(onc_signature != NULL);
414
415  ShillToONCTranslator translator(shill_dictionary, *onc_signature);
416  return translator.CreateTranslatedONCObject();
417}
418
419}  // namespace onc
420}  // namespace chromeos
421