network_state.cc revision c2db58bd994c04d98e4ee2cd7565b71548655fe3
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/network_state.h"
6
7#include "base/i18n/icu_encoding_detection.h"
8#include "base/i18n/icu_string_conversions.h"
9#include "base/json/json_writer.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/string_util.h"
12#include "base/strings/stringprintf.h"
13#include "base/strings/utf_string_conversion_utils.h"
14#include "chromeos/network/network_event_log.h"
15#include "chromeos/network/network_profile_handler.h"
16#include "chromeos/network/network_util.h"
17#include "chromeos/network/onc/onc_utils.h"
18#include "third_party/cros_system_api/dbus/service_constants.h"
19
20namespace {
21
22const char kErrorUnknown[] = "Unknown";
23
24bool ConvertListValueToStringVector(const base::ListValue& string_list,
25                                    std::vector<std::string>* result) {
26  for (size_t i = 0; i < string_list.GetSize(); ++i) {
27    std::string str;
28    if (!string_list.GetString(i, &str))
29      return false;
30    result->push_back(str);
31  }
32  return true;
33}
34
35// Replace non UTF8 characters in |str| with a replacement character.
36std::string ValidateUTF8(const std::string& str) {
37  std::string result;
38  for (int32 index = 0; index < static_cast<int32>(str.size()); ++index) {
39    uint32 code_point_out;
40    bool is_unicode_char = base::ReadUnicodeCharacter(str.c_str(), str.size(),
41                                                      &index, &code_point_out);
42    const uint32 kFirstNonControlChar = 0x20;
43    if (is_unicode_char && (code_point_out >= kFirstNonControlChar)) {
44      base::WriteUnicodeCharacter(code_point_out, &result);
45    } else {
46      const uint32 kReplacementChar = 0xFFFD;
47      // Puts kReplacementChar if character is a control character [0,0x20)
48      // or is not readable UTF8.
49      base::WriteUnicodeCharacter(kReplacementChar, &result);
50    }
51  }
52  return result;
53}
54
55bool IsCaCertNssSet(const base::DictionaryValue& properties) {
56  std::string ca_cert_nss;
57  if (properties.GetStringWithoutPathExpansion(flimflam::kEapCaCertNssProperty,
58                                               &ca_cert_nss) &&
59      !ca_cert_nss.empty()) {
60    return true;
61  }
62
63  const base::DictionaryValue* provider = NULL;
64  properties.GetDictionaryWithoutPathExpansion(flimflam::kProviderProperty,
65                                               &provider);
66  if (!provider)
67    return false;
68  if (provider->GetStringWithoutPathExpansion(
69          flimflam::kL2tpIpsecCaCertNssProperty, &ca_cert_nss) &&
70      !ca_cert_nss.empty()) {
71    return true;
72  }
73  if (provider->GetStringWithoutPathExpansion(
74          flimflam::kOpenVPNCaCertNSSProperty, &ca_cert_nss) &&
75      !ca_cert_nss.empty()) {
76    return true;
77  }
78
79  return false;
80}
81
82}  // namespace
83
84namespace chromeos {
85
86NetworkState::NetworkState(const std::string& path)
87    : ManagedState(MANAGED_TYPE_NETWORK, path),
88      auto_connect_(false),
89      favorite_(false),
90      priority_(0),
91      prefix_length_(0),
92      signal_strength_(0),
93      connectable_(false),
94      activate_over_non_cellular_networks_(false),
95      cellular_out_of_credits_(false),
96      has_ca_cert_nss_(false) {
97}
98
99NetworkState::~NetworkState() {
100}
101
102bool NetworkState::PropertyChanged(const std::string& key,
103                                   const base::Value& value) {
104  // Keep care that these properties are the same as in |GetProperties|.
105  if (ManagedStatePropertyChanged(key, value))
106    return true;
107  if (key == flimflam::kSignalStrengthProperty) {
108    return GetIntegerValue(key, value, &signal_strength_);
109  } else if (key == flimflam::kStateProperty) {
110    return GetStringValue(key, value, &connection_state_);
111  } else if (key == flimflam::kConnectableProperty) {
112    return GetBooleanValue(key, value, &connectable_);
113  } else if (key == flimflam::kErrorProperty) {
114    if (!GetStringValue(key, value, &error_))
115      return false;
116    // Shill uses "Unknown" to indicate an unset error state.
117    if (error_ == kErrorUnknown)
118      error_.clear();
119    return true;
120  } else if (key == shill::kErrorDetailsProperty) {
121    return GetStringValue(key, value, &error_details_);
122  } else if (key == IPConfigProperty(flimflam::kAddressProperty)) {
123    return GetStringValue(key, value, &ip_address_);
124  } else if (key == IPConfigProperty(flimflam::kGatewayProperty)) {
125    return GetStringValue(key, value, &gateway_);
126  } else if (key == IPConfigProperty(flimflam::kNameServersProperty)) {
127    const base::ListValue* dns_servers;
128    if (!value.GetAsList(&dns_servers))
129      return false;
130    dns_servers_.clear();
131    ConvertListValueToStringVector(*dns_servers, &dns_servers_);
132    return true;
133  } else if (key == IPConfigProperty(flimflam::kPrefixlenProperty)) {
134    return GetIntegerValue(key, value, &prefix_length_);
135  } else if (key == flimflam::kActivationStateProperty) {
136    return GetStringValue(key, value, &activation_state_);
137  } else if (key == flimflam::kRoamingStateProperty) {
138    return GetStringValue(key, value, &roaming_);
139  } else if (key == flimflam::kSecurityProperty) {
140    return GetStringValue(key, value, &security_);
141  } else if (key == flimflam::kAutoConnectProperty) {
142    return GetBooleanValue(key, value, &auto_connect_);
143  } else if (key == flimflam::kFavoriteProperty) {
144    return GetBooleanValue(key, value, &favorite_);
145  } else if (key == flimflam::kPriorityProperty) {
146    return GetIntegerValue(key, value, &priority_);
147  } else if (key == flimflam::kProxyConfigProperty) {
148    std::string proxy_config_str;
149    if (!value.GetAsString(&proxy_config_str)) {
150      NET_LOG_ERROR("Failed to parse " + key, path());
151      return false;
152    }
153
154    proxy_config_.Clear();
155    if (proxy_config_str.empty())
156      return true;
157
158    scoped_ptr<base::DictionaryValue> proxy_config_dict(
159        onc::ReadDictionaryFromJson(proxy_config_str));
160    if (proxy_config_dict) {
161      // Warning: The DictionaryValue returned from
162      // ReadDictionaryFromJson/JSONParser is an optimized derived class that
163      // doesn't allow releasing ownership of nested values. A Swap in the wrong
164      // order leads to memory access errors.
165      proxy_config_.MergeDictionary(proxy_config_dict.get());
166    } else {
167      NET_LOG_ERROR("Failed to parse " + key, path());
168    }
169    return true;
170  } else if (key == flimflam::kUIDataProperty) {
171    if (!GetUIDataFromValue(value, &ui_data_)) {
172      NET_LOG_ERROR("Failed to parse " + key, path());
173      return false;
174    }
175    return true;
176  } else if (key == flimflam::kNetworkTechnologyProperty) {
177    return GetStringValue(key, value, &network_technology_);
178  } else if (key == flimflam::kDeviceProperty) {
179    return GetStringValue(key, value, &device_path_);
180  } else if (key == flimflam::kGuidProperty) {
181    return GetStringValue(key, value, &guid_);
182  } else if (key == flimflam::kProfileProperty) {
183    return GetStringValue(key, value, &profile_path_);
184  } else if (key == shill::kActivateOverNonCellularNetworkProperty) {
185    return GetBooleanValue(key, value, &activate_over_non_cellular_networks_);
186  } else if (key == shill::kOutOfCreditsProperty) {
187    return GetBooleanValue(key, value, &cellular_out_of_credits_);
188  } else if (key == flimflam::kUsageURLProperty) {
189    return GetStringValue(key, value, &usage_url_);
190  } else if (key == flimflam::kPaymentPortalProperty) {
191    const DictionaryValue* dict;
192    if (!value.GetAsDictionary(&dict))
193      return false;
194    if (!dict->GetStringWithoutPathExpansion(
195            flimflam::kPaymentPortalURL, &payment_url_) ||
196        !dict->GetStringWithoutPathExpansion(
197            flimflam::kPaymentPortalMethod, &post_method_) ||
198        !dict->GetStringWithoutPathExpansion(
199            flimflam::kPaymentPortalPostData, &post_data_)) {
200      return false;
201    }
202    return true;
203  }
204  return false;
205}
206
207bool NetworkState::InitialPropertiesReceived(
208    const base::DictionaryValue& properties) {
209  NET_LOG_DEBUG("InitialPropertiesReceived", path());
210  bool changed = UpdateName(properties);
211  bool had_ca_cert_nss = has_ca_cert_nss_;
212  has_ca_cert_nss_ = IsCaCertNssSet(properties);
213  changed |= had_ca_cert_nss != has_ca_cert_nss_;
214  return changed;
215}
216
217void NetworkState::GetProperties(base::DictionaryValue* dictionary) const {
218  // Keep care that these properties are the same as in |PropertyChanged|.
219  dictionary->SetStringWithoutPathExpansion(flimflam::kNameProperty, name());
220  dictionary->SetStringWithoutPathExpansion(flimflam::kTypeProperty, type());
221  dictionary->SetIntegerWithoutPathExpansion(flimflam::kSignalStrengthProperty,
222                                             signal_strength_);
223  dictionary->SetStringWithoutPathExpansion(flimflam::kStateProperty,
224                                            connection_state_);
225  dictionary->SetBooleanWithoutPathExpansion(flimflam::kConnectableProperty,
226                                             connectable_);
227
228  dictionary->SetStringWithoutPathExpansion(flimflam::kErrorProperty,
229                                            error_);
230  dictionary->SetStringWithoutPathExpansion(shill::kErrorDetailsProperty,
231                                            error_details_);
232
233  // IPConfig properties
234  base::DictionaryValue* ipconfig_properties = new base::DictionaryValue;
235  ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kAddressProperty,
236                                                     ip_address_);
237  ipconfig_properties->SetStringWithoutPathExpansion(flimflam::kGatewayProperty,
238                                                     gateway_);
239  base::ListValue* name_servers = new base::ListValue;
240  name_servers->AppendStrings(dns_servers_);
241  ipconfig_properties->SetWithoutPathExpansion(flimflam::kNameServersProperty,
242                                               name_servers);
243  ipconfig_properties->SetIntegerWithoutPathExpansion(
244      flimflam::kPrefixlenProperty, prefix_length_);
245
246  dictionary->SetWithoutPathExpansion(shill::kIPConfigProperty,
247                                      ipconfig_properties);
248
249  dictionary->SetStringWithoutPathExpansion(flimflam::kActivationStateProperty,
250                                            activation_state_);
251  dictionary->SetStringWithoutPathExpansion(flimflam::kRoamingStateProperty,
252                                            roaming_);
253  dictionary->SetStringWithoutPathExpansion(flimflam::kSecurityProperty,
254                                            security_);
255  dictionary->SetBooleanWithoutPathExpansion(flimflam::kAutoConnectProperty,
256                                             auto_connect_);
257  dictionary->SetBooleanWithoutPathExpansion(flimflam::kFavoriteProperty,
258                                             favorite_);
259  dictionary->SetIntegerWithoutPathExpansion(flimflam::kPriorityProperty,
260                                             priority_);
261  // Proxy config and ONC source are intentionally omitted: These properties are
262  // placed in NetworkState to transition ProxyConfigServiceImpl from
263  // NetworkLibrary to the new network stack. The networking extension API
264  // shouldn't depend on this member. Once ManagedNetworkConfigurationHandler
265  // is used instead of NetworkLibrary, we can remove them again.
266  dictionary->SetStringWithoutPathExpansion(
267      flimflam::kNetworkTechnologyProperty,
268      network_technology_);
269  dictionary->SetStringWithoutPathExpansion(flimflam::kDeviceProperty,
270                                            device_path_);
271  dictionary->SetStringWithoutPathExpansion(flimflam::kGuidProperty, guid_);
272  dictionary->SetStringWithoutPathExpansion(flimflam::kProfileProperty,
273                                            profile_path_);
274  dictionary->SetBooleanWithoutPathExpansion(
275      shill::kActivateOverNonCellularNetworkProperty,
276      activate_over_non_cellular_networks_);
277  dictionary->SetBooleanWithoutPathExpansion(shill::kOutOfCreditsProperty,
278                                             cellular_out_of_credits_);
279  base::DictionaryValue* payment_portal_properties = new DictionaryValue;
280  payment_portal_properties->SetStringWithoutPathExpansion(
281      flimflam::kPaymentPortalURL,
282      payment_url_);
283  payment_portal_properties->SetStringWithoutPathExpansion(
284      flimflam::kPaymentPortalMethod,
285      post_method_);
286  payment_portal_properties->SetStringWithoutPathExpansion(
287      flimflam::kPaymentPortalPostData,
288      post_data_);
289  dictionary->SetWithoutPathExpansion(flimflam::kPaymentPortalProperty,
290                                      payment_portal_properties);
291}
292
293bool NetworkState::IsConnectedState() const {
294  return StateIsConnected(connection_state_);
295}
296
297bool NetworkState::IsConnectingState() const {
298  return StateIsConnecting(connection_state_);
299}
300
301bool NetworkState::IsManaged() const {
302  return ui_data_.onc_source() == onc::ONC_SOURCE_DEVICE_POLICY ||
303         ui_data_.onc_source() == onc::ONC_SOURCE_USER_POLICY;
304}
305
306bool NetworkState::IsPrivate() const {
307  return !profile_path_.empty() &&
308      profile_path_ != NetworkProfileHandler::kSharedProfilePath;
309}
310
311std::string NetworkState::GetDnsServersAsString() const {
312  std::string result;
313  for (size_t i = 0; i < dns_servers_.size(); ++i) {
314    if (i != 0)
315      result += ",";
316    result += dns_servers_[i];
317  }
318  return result;
319}
320
321std::string NetworkState::GetNetmask() const {
322  return network_util::PrefixLengthToNetmask(prefix_length_);
323}
324
325bool NetworkState::UpdateName(const base::DictionaryValue& properties) {
326  std::string updated_name = GetNameFromProperties(path(), properties);
327  if (updated_name != name()) {
328    set_name(updated_name);
329    return true;
330  }
331  return false;
332}
333
334// static
335std::string NetworkState::GetNameFromProperties(
336    const std::string& service_path,
337    const base::DictionaryValue& properties) {
338  std::string name;
339  properties.GetStringWithoutPathExpansion(flimflam::kNameProperty, &name);
340
341  std::string hex_ssid;
342  properties.GetStringWithoutPathExpansion(flimflam::kWifiHexSsid, &hex_ssid);
343
344  if (hex_ssid.empty()) {
345    // Validate name for UTF8.
346    std::string valid_ssid = ValidateUTF8(name);
347    if (valid_ssid != name) {
348      NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
349          "%s: UTF8: %s", service_path.c_str(), valid_ssid.c_str()));
350    }
351    return valid_ssid;
352  }
353
354  std::string ssid;
355  std::vector<uint8> raw_ssid_bytes;
356  if (base::HexStringToBytes(hex_ssid, &raw_ssid_bytes)) {
357    ssid = std::string(raw_ssid_bytes.begin(), raw_ssid_bytes.end());
358    NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
359        "%s: %s, SSID: %s", service_path.c_str(),
360        hex_ssid.c_str(), ssid.c_str()));
361  } else {
362    NET_LOG_ERROR("GetNameFromProperties",
363                  base::StringPrintf("%s: Error processing: %s",
364                                     service_path.c_str(), hex_ssid.c_str()));
365    return name;
366  }
367
368  if (IsStringUTF8(ssid)) {
369    if (ssid != name) {
370      NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
371          "%s: UTF8: %s", service_path.c_str(), ssid.c_str()));
372    }
373    return ssid;
374  }
375
376  // Detect encoding and convert to UTF-8.
377  std::string country_code;
378  properties.GetStringWithoutPathExpansion(
379      flimflam::kCountryProperty, &country_code);
380  std::string encoding;
381  if (!base::DetectEncoding(ssid, &encoding)) {
382    // TODO(stevenjb): This is currently experimental. If we find a case where
383    // base::DetectEncoding() fails, we need to figure out whether we can use
384    // country_code with ConvertToUtf8(). crbug.com/233267.
385    encoding = country_code;
386  }
387  if (!encoding.empty()) {
388    std::string utf8_ssid;
389    if (base::ConvertToUtf8AndNormalize(ssid, encoding, &utf8_ssid)) {
390      if (utf8_ssid != name) {
391        NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
392            "%s: Encoding=%s: %s", service_path.c_str(),
393            encoding.c_str(), utf8_ssid.c_str()));
394      }
395      return utf8_ssid;
396    }
397  }
398
399  // Unrecognized encoding. Only use raw bytes if name_ is empty.
400  NET_LOG_DEBUG("GetNameFromProperties", base::StringPrintf(
401      "%s: Unrecognized Encoding=%s: %s", service_path.c_str(),
402      encoding.c_str(), ssid.c_str()));
403  if (name.empty() && !ssid.empty())
404    return ssid;
405  return name;
406}
407
408// static
409bool NetworkState::StateIsConnected(const std::string& connection_state) {
410  return (connection_state == flimflam::kStateReady ||
411          connection_state == flimflam::kStateOnline ||
412          connection_state == flimflam::kStatePortal);
413}
414
415// static
416bool NetworkState::StateIsConnecting(const std::string& connection_state) {
417  return (connection_state == flimflam::kStateAssociation ||
418          connection_state == flimflam::kStateConfiguration ||
419          connection_state == flimflam::kStateCarrier);
420}
421
422// static
423std::string NetworkState::IPConfigProperty(const char* key) {
424  return base::StringPrintf("%s.%s", shill::kIPConfigProperty, key);
425}
426
427// static
428bool NetworkState::GetUIDataFromValue(const base::Value& ui_data_value,
429                                      NetworkUIData* out) {
430  std::string ui_data_str;
431  if (!ui_data_value.GetAsString(&ui_data_str))
432    return false;
433  if (ui_data_str.empty()) {
434    *out = NetworkUIData();
435    return true;
436  }
437  scoped_ptr<base::DictionaryValue> ui_data_dict(
438      chromeos::onc::ReadDictionaryFromJson(ui_data_str));
439  if (!ui_data_dict)
440    return false;
441  *out = NetworkUIData(*ui_data_dict);
442  return true;
443}
444
445}  // namespace chromeos
446