1//
2// Copyright (C) 2012 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "shill/wifi/wifi_service.h"
18
19#include <algorithm>
20#include <limits>
21#include <string>
22#include <utility>
23
24#include <base/strings/stringprintf.h>
25#include <base/strings/string_number_conversions.h>
26#include <base/strings/string_split.h>
27#include <base/strings/string_util.h>
28#if defined(__ANDROID__)
29#include <dbus/service_constants.h>
30#else
31#include <chromeos/dbus/service_constants.h>
32#endif  // __ANDROID__
33
34#include "shill/adaptor_interfaces.h"
35#include "shill/certificate_file.h"
36#include "shill/control_interface.h"
37#include "shill/device.h"
38#include "shill/eap_credentials.h"
39#include "shill/error.h"
40#include "shill/event_dispatcher.h"
41#include "shill/logging.h"
42#include "shill/manager.h"
43#include "shill/metrics.h"
44#include "shill/net/ieee80211.h"
45#include "shill/property_accessor.h"
46#include "shill/store_interface.h"
47#include "shill/supplicant/wpa_supplicant.h"
48#include "shill/wifi/wifi.h"
49#include "shill/wifi/wifi_endpoint.h"
50#include "shill/wifi/wifi_provider.h"
51
52using std::map;
53using std::set;
54using std::string;
55using std::vector;
56
57namespace shill {
58
59namespace Logging {
60static auto kModuleLogScope = ScopeLogger::kService;
61static string ObjectID(const WiFiService* w) { return w->GetRpcIdentifier(); }
62}
63
64const char WiFiService::kAutoConnNoEndpoint[] = "no endpoints";
65const char WiFiService::kAnyDeviceAddress[] = "any";
66const int WiFiService::kSuspectedCredentialFailureThreshold = 3;
67
68const char WiFiService::kStorageHiddenSSID[] = "WiFi.HiddenSSID";
69const char WiFiService::kStorageMode[] = "WiFi.Mode";
70const char WiFiService::kStoragePassphrase[] = "Passphrase";
71const char WiFiService::kStorageSecurity[] = "WiFi.Security";
72const char WiFiService::kStorageSecurityClass[] = "WiFi.SecurityClass";
73const char WiFiService::kStorageSSID[] = "SSID";
74const char WiFiService::kStoragePreferredDevice[] = "WiFi.PreferredDevice";
75const char WiFiService::kStorageRoamThreshold[] = "WiFi.RoamThreshold";
76const char WiFiService::kStorageRoamThresholdSet[] = "WiFi.RoamThresholdSet";
77
78bool WiFiService::logged_signal_warning = false;
79
80WiFiService::WiFiService(ControlInterface* control_interface,
81                         EventDispatcher* dispatcher,
82                         Metrics* metrics,
83                         Manager* manager,
84                         WiFiProvider* provider,
85                         const vector<uint8_t>& ssid,
86                         const string& mode,
87                         const string& security,
88                         bool hidden_ssid)
89    : Service(control_interface, dispatcher, metrics, manager,
90              Technology::kWifi),
91      need_passphrase_(false),
92      security_(security),
93      mode_(mode),
94      hidden_ssid_(hidden_ssid),
95      frequency_(0),
96      physical_mode_(Metrics::kWiFiNetworkPhyModeUndef),
97      raw_signal_strength_(0),
98      cipher_8021x_(kCryptoNone),
99      suspected_credential_failures_(0),
100      ssid_(ssid),
101      ieee80211w_required_(false),
102      expecting_disconnect_(false),
103      certificate_file_(new CertificateFile()),
104      roam_threshold_db_(0),
105      roam_threshold_db_set_(false),
106      provider_(provider) {
107  PropertyStore* store = this->mutable_store();
108  store->RegisterConstString(kModeProperty, &mode_);
109  HelpRegisterWriteOnlyDerivedString(kPassphraseProperty,
110                                     &WiFiService::SetPassphrase,
111                                     &WiFiService::ClearPassphrase,
112                                     nullptr);
113  store->RegisterBool(kPassphraseRequiredProperty, &need_passphrase_);
114  HelpRegisterConstDerivedString(kSecurityProperty,
115                                 &WiFiService::GetSecurity);
116  HelpRegisterConstDerivedString(kSecurityClassProperty,
117                                 &WiFiService::GetSecurityClass);
118
119  store->RegisterConstString(kWifiAuthMode, &auth_mode_);
120  store->RegisterBool(kWifiHiddenSsid, &hidden_ssid_);
121  store->RegisterConstUint16(kWifiFrequency, &frequency_);
122  store->RegisterConstUint16s(kWifiFrequencyListProperty, &frequency_list_);
123  store->RegisterConstUint16(kWifiPhyMode, &physical_mode_);
124  store->RegisterConstString(kWifiBSsid, &bssid_);
125  store->RegisterConstString(kCountryProperty, &country_code_);
126  store->RegisterConstStringmap(kWifiVendorInformationProperty,
127                                &vendor_information_);
128  store->RegisterConstBool(kWifiProtectedManagementFrameRequiredProperty,
129                           &ieee80211w_required_);
130
131  hex_ssid_ = base::HexEncode(ssid_.data(), ssid_.size());
132  store->RegisterConstString(kWifiHexSsid, &hex_ssid_);
133  HelpRegisterDerivedString(kWifiPreferredDeviceProperty,
134                            &WiFiService::GetPreferredDevice,
135                            &WiFiService::SetPreferredDevice);
136  HelpRegisterDerivedUint16(kWifiRoamThresholdProperty,
137                            &WiFiService::GetRoamThreshold,
138                            &WiFiService::SetRoamThreshold,
139                            &WiFiService::ClearRoamThreshold);
140
141  string ssid_string(
142      reinterpret_cast<const char*>(ssid_.data()), ssid_.size());
143  WiFi::SanitizeSSID(&ssid_string);
144  set_friendly_name(ssid_string);
145
146  SetEapCredentials(new EapCredentials());
147
148  // TODO(quiche): determine if it is okay to set EAP.KeyManagement for
149  // a service that is not 802.1x.
150  if (Is8021x()) {
151    // Passphrases are not mandatory for 802.1X.
152    need_passphrase_ = false;
153  } else if (security_ == kSecurityPsk) {
154    SetEAPKeyManagement("WPA-PSK");
155  } else if (security_ == kSecurityRsn) {
156    SetEAPKeyManagement("WPA-PSK");
157  } else if (security_ == kSecurityWpa) {
158    SetEAPKeyManagement("WPA-PSK");
159  } else if (security_ == kSecurityWep) {
160    SetEAPKeyManagement("NONE");
161  } else if (security_ == kSecurityNone) {
162    SetEAPKeyManagement("NONE");
163  } else {
164    LOG(ERROR) << "Unsupported security method " << security_;
165  }
166
167  // Until we know better (at Profile load time), use the generic name.
168  storage_identifier_ = GetDefaultStorageIdentifier();
169  UpdateConnectable();
170  UpdateSecurity();
171
172  // Now that |this| is a fully constructed WiFiService, synchronize observers
173  // with our current state, and emit the appropriate change notifications.
174  // (Initial observer state may have been set in our base class.)
175  NotifyPropertyChanges();
176
177  IgnoreParameterForConfigure(kModeProperty);
178  IgnoreParameterForConfigure(kSSIDProperty);
179  IgnoreParameterForConfigure(kSecurityProperty);
180  IgnoreParameterForConfigure(kSecurityClassProperty);
181  IgnoreParameterForConfigure(kWifiHexSsid);
182
183  InitializeCustomMetrics();
184
185  // Log the |unique_name| to |friendly_name| mapping for debugging purposes.
186  // The latter will be tagged for scrubbing.
187  LOG(INFO) << "Constructed WiFi service " << unique_name()
188            << " name: " << WiFi::LogSSID(friendly_name());
189}
190
191WiFiService::~WiFiService() {}
192
193bool WiFiService::IsAutoConnectable(const char** reason) const {
194  if (!Service::IsAutoConnectable(reason)) {
195    return false;
196  }
197
198  // Only auto-connect to Services which have visible Endpoints.
199  // (Needed because hidden Services may remain registered with
200  // Manager even without visible Endpoints.)
201  if (!HasEndpoints()) {
202    *reason = kAutoConnNoEndpoint;
203    return false;
204  }
205
206  CHECK(wifi_) << "We have endpoints but no WiFi device is selected?";
207
208  // Do not preempt an existing connection (whether pending, or
209  // connected, and whether to this service, or another).
210  if (!wifi_->IsIdle()) {
211    *reason = kAutoConnBusy;
212    return false;
213  }
214
215  return true;
216}
217
218void WiFiService::SetEAPKeyManagement(const string& key_management) {
219  Service::SetEAPKeyManagement(key_management);
220  UpdateSecurity();
221}
222
223void WiFiService::AddEndpoint(const WiFiEndpointConstRefPtr& endpoint) {
224  DCHECK(endpoint->ssid() == ssid());
225  endpoints_.insert(endpoint);
226  UpdateFromEndpoints();
227}
228
229void WiFiService::RemoveEndpoint(const WiFiEndpointConstRefPtr& endpoint) {
230  set<WiFiEndpointConstRefPtr>::iterator i = endpoints_.find(endpoint);
231  DCHECK(i != endpoints_.end());
232  if (i == endpoints_.end()) {
233    LOG(WARNING) << "In " << __func__ << "(): "
234                 << "ignoring non-existent endpoint "
235                 << endpoint->bssid_string();
236    return;
237  }
238  endpoints_.erase(i);
239  if (current_endpoint_ == endpoint) {
240    current_endpoint_ = nullptr;
241  }
242  UpdateFromEndpoints();
243}
244
245void WiFiService::NotifyCurrentEndpoint(
246    const WiFiEndpointConstRefPtr& endpoint) {
247  DCHECK(!endpoint || (endpoints_.find(endpoint) != endpoints_.end()));
248  current_endpoint_ = endpoint;
249  UpdateFromEndpoints();
250}
251
252void WiFiService::NotifyEndpointUpdated(
253    const WiFiEndpointConstRefPtr& endpoint) {
254  DCHECK(endpoints_.find(endpoint) != endpoints_.end());
255  UpdateFromEndpoints();
256}
257
258string WiFiService::GetStorageIdentifier() const {
259  return storage_identifier_;
260}
261
262bool WiFiService::SetPassphrase(const string& passphrase, Error* error) {
263  if (security_ == kSecurityWep) {
264    ValidateWEPPassphrase(passphrase, error);
265  } else if (security_ == kSecurityPsk ||
266             security_ == kSecurityWpa ||
267             security_ == kSecurityRsn) {
268    ValidateWPAPassphrase(passphrase, error);
269  } else {
270    error->Populate(Error::kNotSupported);
271  }
272
273  if (!error->IsSuccess()) {
274    LOG(ERROR) << "Passphrase could not be set: " << error->message();
275    return false;
276  }
277
278  return SetPassphraseInternal(passphrase, Service::kReasonPropertyUpdate);
279}
280
281bool WiFiService::SetPassphraseInternal(
282    const string& passphrase,
283    Service::UpdateCredentialsReason reason) {
284  if (passphrase_ == passphrase) {
285    // After a user logs in, Chrome may reconfigure a Service with the
286    // same credentials as before login. When that occurs, we don't
287    // want to bump the user off the network. Hence, we MUST return
288    // early. (See crbug.com/231456#c17)
289    return false;
290  }
291  passphrase_ = passphrase;
292  OnCredentialChange(reason);
293  return true;
294}
295
296// ClearPassphrase is separate from SetPassphrase, because the default
297// value for |passphrase_| would not pass validation.
298void WiFiService::ClearPassphrase(Error* /*error*/) {
299  passphrase_.clear();
300  ClearCachedCredentials();
301  UpdateConnectable();
302}
303
304string WiFiService::GetPreferredDevice(Error* /*error*/) {
305  return preferred_device_;
306}
307
308bool WiFiService::SetPreferredDevice(const string& device_name,
309                                     Error* /*error*/) {
310  // Reset device if it is not the preferred device.
311  if (!device_name.empty() && wifi_ && wifi_->link_name() != device_name) {
312    ResetWiFi();
313  }
314  preferred_device_ = device_name;
315  return true;
316}
317
318string WiFiService::GetTethering(Error* /*error*/) const {
319  if (IsConnected() && wifi_ && wifi_->IsConnectedViaTether()) {
320    return kTetheringConfirmedState;
321  }
322
323  // Only perform BSSID tests if there is exactly one matching endpoint,
324  // so we ignore campuses that may use locally administered BSSIDs.
325  if (endpoints_.size() == 1 &&
326      (*endpoints_.begin())->has_tethering_signature()) {
327    return kTetheringSuspectedState;
328  }
329
330  return kTetheringNotDetectedState;
331}
332
333string WiFiService::GetLoadableStorageIdentifier(
334    const StoreInterface& storage) const {
335  set<string> groups = storage.GetGroupsWithProperties(GetStorageProperties());
336  if (groups.empty()) {
337    LOG(WARNING) << "Configuration for service "
338                 << unique_name()
339                 << " is not available in the persistent store";
340    return "";
341  }
342  if (groups.size() > 1) {
343    LOG(WARNING) << "More than one configuration for service "
344                 << unique_name()
345                 << " is available; choosing the first.";
346  }
347  return *groups.begin();
348}
349
350bool WiFiService::IsLoadableFrom(const StoreInterface& storage) const {
351  return !storage.GetGroupsWithProperties(GetStorageProperties()).empty();
352}
353
354bool WiFiService::IsVisible() const {
355  // WiFi Services should be displayed only if they are in range (have
356  // endpoints that have shown up in a scan) or if the service is actively
357  // being connected.
358  return HasEndpoints() || IsConnected() || IsConnecting();
359}
360
361bool WiFiService::Load(StoreInterface* storage) {
362  string id = GetLoadableStorageIdentifier(*storage);
363  if (id.empty()) {
364    return false;
365  }
366
367  // Set our storage identifier to match the storage name in the Profile.
368  storage_identifier_ = id;
369
370  // Load properties common to all Services.
371  if (!Service::Load(storage)) {
372    return false;
373  }
374
375  // Load properties specific to WiFi services.
376  storage->GetBool(id, kStorageHiddenSSID, &hidden_ssid_);
377
378  // NB: mode, security and ssid parameters are never read in from
379  // Load() as they are provided from the scan.
380
381  string passphrase;
382  if (storage->GetCryptedString(id, kStoragePassphrase, &passphrase)) {
383    if (SetPassphraseInternal(passphrase, Service::kReasonCredentialsLoaded)) {
384      SLOG(this, 3) << "Loaded passphrase in WiFiService::Load.";
385    }
386  }
387
388  string preferred_device;
389  storage->GetString(id, kStoragePreferredDevice, &preferred_device);
390  SetPreferredDevice(preferred_device, nullptr);
391
392  uint64_t stored_roam_threshold_temp;
393  storage->GetUint64(id, kStorageRoamThreshold, &stored_roam_threshold_temp);
394  // Storing a uint64_t in a uint16_t is safe here since we know that we only
395  // set this storage value to a uint16_t value in WiFiService::Save.
396  roam_threshold_db_ = static_cast<uint16_t>(stored_roam_threshold_temp);
397  storage->GetBool(id, kStorageRoamThresholdSet, &roam_threshold_db_set_);
398
399  expecting_disconnect_ = false;
400  return true;
401}
402
403bool WiFiService::Save(StoreInterface* storage) {
404  // Save properties common to all Services.
405  if (!Service::Save(storage)) {
406    return false;
407  }
408
409  // Save properties specific to WiFi services.
410  const string id = GetStorageIdentifier();
411  storage->SetBool(id, kStorageHiddenSSID, hidden_ssid_);
412  storage->SetString(id, kStorageMode, mode_);
413  storage->SetCryptedString(id, kStoragePassphrase, passphrase_);
414  storage->SetString(id, kStorageSecurity, security_);
415  storage->SetString(id, kStorageSecurityClass,
416                     ComputeSecurityClass(security_));
417  storage->SetString(id, kStorageSSID, hex_ssid_);
418  storage->SetUint64(id, kStorageRoamThreshold,
419                     static_cast<uint64_t>(roam_threshold_db_));
420  storage->SetBool(id, kStorageRoamThresholdSet, roam_threshold_db_set_);
421  Service::SaveString(storage, id, kStoragePreferredDevice, preferred_device_,
422                      false, false);
423
424  return true;
425}
426
427bool WiFiService::Unload() {
428  // Expect the service to be disconnected if is currently connected or
429  // in the process of connecting.
430  if (IsConnected() || IsConnecting()) {
431    expecting_disconnect_ = true;
432  } else {
433    expecting_disconnect_ = false;
434  }
435  Service::Unload();
436  if (wifi_) {
437    wifi_->DestroyServiceLease(*this);
438  }
439  hidden_ssid_ = false;
440  ResetSuspectedCredentialFailures();
441  Error unused_error;
442  ClearPassphrase(&unused_error);
443  preferred_device_.clear();
444  roam_threshold_db_ = 0;
445  roam_threshold_db_set_ = false;
446  return provider_->OnServiceUnloaded(this);
447}
448
449void WiFiService::SetState(ConnectState state) {
450  Service::SetState(state);
451  NotifyPropertyChanges();
452}
453
454bool WiFiService::IsSecurityMatch(const string& security) const {
455  return ComputeSecurityClass(security) == ComputeSecurityClass(security_);
456}
457
458bool WiFiService::AddSuspectedCredentialFailure() {
459  if (!has_ever_connected()) {
460    return true;
461  }
462  ++suspected_credential_failures_;
463  return suspected_credential_failures_ >= kSuspectedCredentialFailureThreshold;
464}
465
466void WiFiService::ResetSuspectedCredentialFailures() {
467  suspected_credential_failures_ = 0;
468}
469
470void WiFiService::InitializeCustomMetrics() const {
471  SLOG(Metrics, this, 2) << __func__ << " for " << unique_name();
472  string histogram = metrics()->GetFullMetricName(
473      Metrics::kMetricTimeToJoinMillisecondsSuffix,
474      technology());
475  metrics()->AddServiceStateTransitionTimer(*this,
476                                            histogram,
477                                            Service::kStateAssociating,
478                                            Service::kStateConfiguring);
479}
480
481void WiFiService::SendPostReadyStateMetrics(
482    int64_t time_resume_to_ready_milliseconds) const {
483  metrics()->SendEnumToUMA(
484      metrics()->GetFullMetricName(Metrics::kMetricNetworkChannelSuffix,
485                                   technology()),
486      Metrics::WiFiFrequencyToChannel(frequency_),
487      Metrics::kMetricNetworkChannelMax);
488
489  DCHECK(physical_mode_ < Metrics::kWiFiNetworkPhyModeMax);
490  metrics()->SendEnumToUMA(
491      metrics()->GetFullMetricName(Metrics::kMetricNetworkPhyModeSuffix,
492                                   technology()),
493      static_cast<Metrics::WiFiNetworkPhyMode>(physical_mode_),
494      Metrics::kWiFiNetworkPhyModeMax);
495
496  string security_mode = security_;
497  if (current_endpoint_) {
498    security_mode = current_endpoint_->security_mode();
499  }
500  Metrics::WiFiSecurity security_uma =
501      Metrics::WiFiSecurityStringToEnum(security_mode);
502  DCHECK(security_uma != Metrics::kWiFiSecurityUnknown);
503  metrics()->SendEnumToUMA(
504      metrics()->GetFullMetricName(Metrics::kMetricNetworkSecuritySuffix,
505                                   technology()),
506      security_uma,
507      Metrics::kMetricNetworkSecurityMax);
508
509  if (Is8021x()) {
510    eap()->OutputConnectionMetrics(metrics(), technology());
511  }
512
513  // We invert the sign of the signal strength value, since UMA histograms
514  // cannot represent negative numbers (it stores them but cannot display
515  // them), and dBm values of interest start at 0 and go negative from there.
516  metrics()->SendToUMA(
517      metrics()->GetFullMetricName(Metrics::kMetricNetworkSignalStrengthSuffix,
518                                   technology()),
519      -raw_signal_strength_,
520      Metrics::kMetricNetworkSignalStrengthMin,
521      Metrics::kMetricNetworkSignalStrengthMax,
522      Metrics::kMetricNetworkSignalStrengthNumBuckets);
523
524  if (time_resume_to_ready_milliseconds > 0) {
525    metrics()->SendToUMA(
526        metrics()->GetFullMetricName(
527            Metrics::kMetricTimeResumeToReadyMillisecondsSuffix, technology()),
528        time_resume_to_ready_milliseconds,
529        Metrics::kTimerHistogramMillisecondsMin,
530        Metrics::kTimerHistogramMillisecondsMax,
531        Metrics::kTimerHistogramNumBuckets);
532  }
533
534  Metrics::WiFiApMode ap_mode_uma = Metrics::WiFiApModeStringToEnum(mode_);
535  metrics()->SendEnumToUMA(
536      metrics()->GetFullMetricName(Metrics::kMetricNetworkApModeSuffix,
537                                   technology()),
538      ap_mode_uma,
539      Metrics::kWiFiApModeMax);
540}
541
542// private methods
543void WiFiService::HelpRegisterConstDerivedString(
544    const string& name,
545    string(WiFiService::*get)(Error*)) {
546  mutable_store()->RegisterDerivedString(
547      name,
548      StringAccessor(
549          new CustomAccessor<WiFiService, string>(this, get, nullptr)));
550}
551
552void WiFiService::HelpRegisterDerivedString(
553    const string& name,
554    string(WiFiService::*get)(Error* error),
555    bool(WiFiService::*set)(const string&, Error*)) {
556  mutable_store()->RegisterDerivedString(
557      name,
558      StringAccessor(new CustomAccessor<WiFiService, string>(this, get, set)));
559}
560
561void WiFiService::HelpRegisterWriteOnlyDerivedString(
562    const string& name,
563    bool(WiFiService::*set)(const string&, Error*),
564    void(WiFiService::*clear)(Error* error),
565    const string* default_value) {
566  mutable_store()->RegisterDerivedString(
567      name,
568      StringAccessor(
569          new CustomWriteOnlyAccessor<WiFiService, string>(
570              this, set, clear, default_value)));
571}
572
573void WiFiService::HelpRegisterDerivedUint16(
574    const string& name,
575    uint16_t(WiFiService::*get)(Error* error),
576    bool(WiFiService::*set)(const uint16_t& value, Error* error),
577    void(WiFiService::*clear)(Error* error)) {
578  mutable_store()->RegisterDerivedUint16(
579      name, Uint16Accessor(new CustomAccessor<WiFiService, uint16_t>(
580                this, get, set, clear)));
581}
582
583void WiFiService::Connect(Error* error, const char* reason) {
584  if (!connectable()) {
585    LOG(ERROR) << "Can't connect. Service " << unique_name()
586               << " is not connectable.";
587    Error::PopulateAndLog(FROM_HERE,
588                          error,
589                          Error::kOperationFailed,
590                          Error::GetDefaultMessage(Error::kOperationFailed));
591    return;
592  }
593  if (IsConnecting() || IsConnected()) {
594    LOG(WARNING) << "Can't connect.  Service " << unique_name()
595                 << " is already connecting or connected.";
596    Error::PopulateAndLog(FROM_HERE,
597                          error,
598                          Error::kAlreadyConnected,
599                          Error::GetDefaultMessage(Error::kAlreadyConnected));
600    return;
601  }
602
603  WiFiRefPtr wifi = wifi_;
604  if (!wifi) {
605    // If this is a hidden service before it has been found in a scan, we
606    // may need to late-bind to any available WiFi Device.  We don't actually
607    // set |wifi_| in this case since we do not yet see any endpoints.  This
608    // will mean this service is not disconnectable until an endpoint is
609    // found.
610    wifi = ChooseDevice();
611    if (!wifi) {
612      LOG(ERROR) << "Can't connect. Service " << unique_name()
613                 << " cannot find a WiFi device.";
614      Error::PopulateAndLog(FROM_HERE,
615                            error,
616                            Error::kOperationFailed,
617                            Error::GetDefaultMessage(Error::kOperationFailed));
618      return;
619    }
620  }
621
622  if (wifi->IsCurrentService(this)) {
623    LOG(WARNING) << "Can't connect.  Service " << unique_name()
624                 << " is the current service (but, in " << GetStateString()
625                 << " state, not connected).";
626    Error::PopulateAndLog(FROM_HERE,
627                          error,
628                          Error::kInProgress,
629                          Error::GetDefaultMessage(Error::kInProgress));
630    return;
631  }
632
633  // Report number of BSSes available for this service.
634  metrics()->NotifyWifiAvailableBSSes(endpoints_.size());
635
636  if (Is8021x()) {
637    // If EAP key management is not set, set to a default.
638    if (GetEAPKeyManagement().empty())
639      SetEAPKeyManagement("WPA-EAP");
640    ClearEAPCertification();
641  }
642
643  expecting_disconnect_ = false;
644  Service::Connect(error, reason);
645  wifi->ConnectTo(this);
646}
647
648KeyValueStore WiFiService::GetSupplicantConfigurationParameters() const {
649  KeyValueStore params;
650
651  params.SetUint(WPASupplicant::kNetworkPropertyMode,
652                 WiFiEndpoint::ModeStringToUint(mode_));
653
654  if (mode_ == kModeAdhoc && frequency_ != 0) {
655    // Frequency is required in order to successfully connect to an IBSS
656    // with wpa_supplicant.  If we have one from our endpoint, insert it
657    // here.
658    params.SetInt(WPASupplicant::kNetworkPropertyFrequency, frequency_);
659  }
660
661  if (Is8021x()) {
662    eap()->PopulateSupplicantProperties(certificate_file_.get(), &params);
663  } else if (security_ == kSecurityPsk ||
664             security_ == kSecurityRsn ||
665             security_ == kSecurityWpa) {
666    const string psk_proto =
667        base::StringPrintf("%s %s",
668                           WPASupplicant::kSecurityModeWPA,
669                           WPASupplicant::kSecurityModeRSN);
670    params.SetString(WPASupplicant::kPropertySecurityProtocol, psk_proto);
671    params.SetString(WPASupplicant::kPropertyPreSharedKey, passphrase_);
672  } else if (security_ == kSecurityWep) {
673    params.SetString(WPASupplicant::kPropertyAuthAlg,
674                     WPASupplicant::kSecurityAuthAlg);
675    Error unused_error;
676    int key_index;
677    std::vector<uint8_t> password_bytes;
678    ParseWEPPassphrase(passphrase_, &key_index, &password_bytes, &unused_error);
679    params.SetUint8s(WPASupplicant::kPropertyWEPKey +
680                         base::IntToString(key_index),
681                     password_bytes);
682    params.SetUint(WPASupplicant::kPropertyWEPTxKeyIndex, key_index);
683  } else if (security_ == kSecurityNone) {
684    // Nothing special to do here.
685  } else {
686    NOTIMPLEMENTED() << "Unsupported security method " << security_;
687  }
688
689  params.SetString(WPASupplicant::kNetworkPropertyEapKeyManagement,
690                   key_management());
691
692  if (ieee80211w_required_) {
693    // TODO(pstew): We should also enable IEEE 802.11w if the user
694    // explicitly enables support for this through a service / device
695    // property.  crbug.com/219950
696    params.SetUint(WPASupplicant::kNetworkPropertyIeee80211w,
697                   WPASupplicant::kNetworkIeee80211wEnabled);
698  }
699
700  params.SetUint8s(WPASupplicant::kNetworkPropertySSID, ssid_);
701
702  return params;
703}
704
705
706void WiFiService::Disconnect(Error* error, const char* reason) {
707  Service::Disconnect(error, reason);
708  if (!wifi_) {
709    // If we are connecting to a hidden service, but have not yet found
710    // any endpoints, we could end up with a disconnect request without
711    // a wifi_ reference.  This is not a fatal error.
712    LOG_IF(ERROR, IsConnecting())
713         << "WiFi endpoints do not (yet) exist.  Cannot disconnect service "
714         << unique_name();
715    LOG_IF(FATAL, IsConnected())
716         << "WiFi device does not exist.  Cannot disconnect service "
717         << unique_name();
718    error->Populate(Error::kOperationFailed);
719    return;
720  }
721  wifi_->DisconnectFromIfActive(this);
722}
723
724string WiFiService::GetDeviceRpcId(Error* error) const {
725  if (!wifi_) {
726    error->Populate(Error::kNotFound, "Not associated with a device");
727    return control_interface()->NullRPCIdentifier();
728  }
729  return wifi_->GetRpcIdentifier();
730}
731
732void WiFiService::UpdateConnectable() {
733  bool is_connectable = false;
734  if (security_ == kSecurityNone) {
735    DCHECK(passphrase_.empty());
736    need_passphrase_ = false;
737    is_connectable = true;
738  } else if (Is8021x()) {
739    is_connectable = Is8021xConnectable();
740  } else if (security_ == kSecurityWep ||
741             security_ == kSecurityWpa ||
742             security_ == kSecurityPsk ||
743             security_ == kSecurityRsn) {
744    need_passphrase_ = passphrase_.empty();
745    is_connectable = !need_passphrase_;
746  }
747  SetConnectable(is_connectable);
748}
749
750void WiFiService::UpdateFromEndpoints() {
751  const WiFiEndpoint* representative_endpoint = nullptr;
752
753  if (current_endpoint_) {
754    representative_endpoint = current_endpoint_.get();
755  } else {
756    int16_t best_signal = std::numeric_limits<int16_t>::min();
757    bool preferred_device_found = false;
758    // This will set the representative_endpoint to the best endpoint associated
759    // with the preferred_device_ if it exist, otherwise the best overall
760    // endpoint.
761    for (const auto& endpoint : endpoints_) {
762      if (preferred_device_found) {
763        // Skip endpoints associated with non-preferred device.
764        if (endpoint->device()->link_name() != preferred_device_) {
765          continue;
766        }
767      } else if (endpoint->device() &&
768          endpoint->device()->link_name() == preferred_device_) {
769        // Found first endpoint associated with preferred device.
770        preferred_device_found = true;
771        best_signal = std::numeric_limits<int16_t>::min();
772      }
773
774      if (endpoint->signal_strength() >= best_signal) {
775        best_signal = endpoint->signal_strength();
776        representative_endpoint = endpoint.get();
777      }
778    }
779  }
780
781  WiFiRefPtr wifi;
782  if (representative_endpoint) {
783    wifi = representative_endpoint->device();
784    if (bssid_ != representative_endpoint->bssid_string() ||
785        raw_signal_strength_ != representative_endpoint->signal_strength() ||
786        frequency_ != representative_endpoint->frequency()) {
787        LOG(INFO)
788            << "Representative endpoint updated for service " << unique_name()
789            << ". "
790            << WiFi::LogSSID(representative_endpoint->ssid_string()) << ", "
791            << "bssid: " << representative_endpoint->bssid_string() << ", "
792            << "signal: " << representative_endpoint->signal_strength() << ", "
793            << "security: " << representative_endpoint->security_mode() << ", "
794            << "frequency: " << representative_endpoint->frequency();
795    }
796  } else if (IsConnected() || IsConnecting()) {
797    LOG(WARNING) << "Service " << unique_name()
798                 << " will disconnect due to no remaining endpoints.";
799  }
800
801  SetWiFi(wifi);
802
803  for (const auto& endpoint : endpoints_) {
804    if (endpoint->ieee80211w_required()) {
805      // Never reset ieee80211w_required_ to false, so we track whether we have
806      // ever seen an AP that requires 802.11w.
807      ieee80211w_required_ = true;
808    }
809  }
810
811  set<uint16_t> frequency_set;
812  for (const auto& endpoint : endpoints_) {
813    frequency_set.insert(endpoint->frequency());
814  }
815  frequency_list_.assign(frequency_set.begin(), frequency_set.end());
816
817  if (Is8021x())
818    cipher_8021x_ = ComputeCipher8021x(endpoints_);
819
820  uint16_t frequency = 0;
821  int16_t signal = std::numeric_limits<int16_t>::min();
822  string bssid;
823  string country_code;
824  Stringmap vendor_information;
825  uint16_t physical_mode = Metrics::kWiFiNetworkPhyModeUndef;
826  // Represent "unknown raw signal strength" as 0.
827  raw_signal_strength_ = 0;
828  if (representative_endpoint) {
829    frequency = representative_endpoint->frequency();
830    signal = representative_endpoint->signal_strength();
831    raw_signal_strength_ = signal;
832    bssid = representative_endpoint->bssid_string();
833    country_code = representative_endpoint->country_code();
834    vendor_information = representative_endpoint->GetVendorInformation();
835    physical_mode = representative_endpoint->physical_mode();
836  }
837
838  if (frequency_ != frequency) {
839    frequency_ = frequency;
840    adaptor()->EmitUint16Changed(kWifiFrequency, frequency_);
841  }
842  if (bssid_ != bssid) {
843    bssid_ = bssid;
844    adaptor()->EmitStringChanged(kWifiBSsid, bssid_);
845  }
846  if (country_code_ != country_code) {
847    country_code_ = country_code;
848    adaptor()->EmitStringChanged(kCountryProperty, country_code_);
849  }
850  if (vendor_information_ != vendor_information) {
851    vendor_information_ = vendor_information;
852    adaptor()->EmitStringmapChanged(kWifiVendorInformationProperty,
853                                    vendor_information_);
854  }
855  if (physical_mode_ != physical_mode) {
856    physical_mode_ = physical_mode;
857    adaptor()->EmitUint16Changed(kWifiPhyMode, physical_mode_);
858  }
859  adaptor()->EmitUint16sChanged(kWifiFrequencyListProperty, frequency_list_);
860  SetStrength(SignalToStrength(signal));
861  UpdateSecurity();
862  NotifyPropertyChanges();
863}
864
865void WiFiService::UpdateSecurity() {
866  CryptoAlgorithm algorithm = kCryptoNone;
867  bool key_rotation = false;
868  bool endpoint_auth = false;
869
870  if (security_ == kSecurityNone) {
871    // initial values apply
872  } else if (security_ == kSecurityWep) {
873    algorithm = kCryptoRc4;
874    key_rotation = Is8021x();
875    endpoint_auth = Is8021x();
876  } else if (security_ == kSecurityPsk ||
877             security_ == kSecurityWpa) {
878    algorithm = kCryptoRc4;
879    key_rotation = true;
880    endpoint_auth = false;
881  } else if (security_ == kSecurityRsn) {
882    algorithm = kCryptoAes;
883    key_rotation = true;
884    endpoint_auth = false;
885  } else if (security_ == kSecurity8021x) {
886    algorithm = cipher_8021x_;
887    key_rotation = true;
888    endpoint_auth = true;
889  }
890  SetSecurity(algorithm, key_rotation, endpoint_auth);
891}
892
893// static
894Service::CryptoAlgorithm WiFiService::ComputeCipher8021x(
895    const set<WiFiEndpointConstRefPtr>& endpoints) {
896
897  if (endpoints.empty())
898    return kCryptoNone;  // Will update after scan results.
899
900  // Find weakest cipher (across endpoints) of the strongest ciphers
901  // (per endpoint).
902  Service::CryptoAlgorithm cipher = Service::kCryptoAes;
903  for (const auto& endpoint : endpoints) {
904    Service::CryptoAlgorithm endpoint_cipher;
905    if (endpoint->has_rsn_property()) {
906      endpoint_cipher = Service::kCryptoAes;
907    } else if (endpoint->has_wpa_property()) {
908      endpoint_cipher = Service::kCryptoRc4;
909    } else {
910      // We could be in the Dynamic WEP case here. But that's okay,
911      // because |cipher_8021x_| is not defined in that case.
912      endpoint_cipher = Service::kCryptoNone;
913    }
914    cipher = std::min(cipher, endpoint_cipher);
915  }
916  return cipher;
917}
918
919// static
920void WiFiService::ValidateWEPPassphrase(const std::string& passphrase,
921                                        Error* error) {
922  ParseWEPPassphrase(passphrase, nullptr, nullptr, error);
923}
924
925// static
926void WiFiService::ValidateWPAPassphrase(const std::string& passphrase,
927                                        Error* error) {
928  unsigned int length = passphrase.length();
929  vector<uint8_t> passphrase_bytes;
930
931  if (base::HexStringToBytes(passphrase, &passphrase_bytes)) {
932    if (length != IEEE_80211::kWPAHexLen &&
933        (length < IEEE_80211::kWPAAsciiMinLen ||
934         length > IEEE_80211::kWPAAsciiMaxLen)) {
935      error->Populate(Error::kInvalidPassphrase);
936    }
937  } else {
938    if (length < IEEE_80211::kWPAAsciiMinLen ||
939        length > IEEE_80211::kWPAAsciiMaxLen) {
940      error->Populate(Error::kInvalidPassphrase);
941    }
942  }
943}
944
945// static
946void WiFiService::ParseWEPPassphrase(const string& passphrase,
947                                     int* key_index,
948                                     std::vector<uint8_t>* password_bytes,
949                                     Error* error) {
950  unsigned int length = passphrase.length();
951  int key_index_local;
952  std::string password_text;
953  bool is_hex = false;
954
955  switch (length) {
956    case IEEE_80211::kWEP40AsciiLen:
957    case IEEE_80211::kWEP104AsciiLen:
958      key_index_local = 0;
959      password_text = passphrase;
960      break;
961    case IEEE_80211::kWEP40AsciiLen + 2:
962    case IEEE_80211::kWEP104AsciiLen + 2:
963      if (CheckWEPKeyIndex(passphrase, error)) {
964        base::StringToInt(passphrase.substr(0, 1), &key_index_local);
965        password_text = passphrase.substr(2);
966      }
967      break;
968    case IEEE_80211::kWEP40HexLen:
969    case IEEE_80211::kWEP104HexLen:
970      if (CheckWEPIsHex(passphrase, error)) {
971        key_index_local = 0;
972        password_text = passphrase;
973        is_hex = true;
974      }
975      break;
976    case IEEE_80211::kWEP40HexLen + 2:
977    case IEEE_80211::kWEP104HexLen + 2:
978      if (CheckWEPKeyIndex(passphrase, error) &&
979         CheckWEPIsHex(passphrase.substr(2), error)) {
980        base::StringToInt(passphrase.substr(0, 1), &key_index_local);
981        password_text = passphrase.substr(2);
982        is_hex = true;
983      } else if (CheckWEPPrefix(passphrase, error) &&
984                 CheckWEPIsHex(passphrase.substr(2), error)) {
985        key_index_local = 0;
986        password_text = passphrase.substr(2);
987        is_hex = true;
988      }
989      break;
990    case IEEE_80211::kWEP40HexLen + 4:
991    case IEEE_80211::kWEP104HexLen + 4:
992      if (CheckWEPKeyIndex(passphrase, error) &&
993          CheckWEPPrefix(passphrase.substr(2), error) &&
994          CheckWEPIsHex(passphrase.substr(4), error)) {
995        base::StringToInt(passphrase.substr(0, 1), &key_index_local);
996        password_text = passphrase.substr(4);
997        is_hex = true;
998      }
999      break;
1000    default:
1001      error->Populate(Error::kInvalidPassphrase);
1002      break;
1003  }
1004
1005  if (error->IsSuccess()) {
1006    if (key_index)
1007      *key_index = key_index_local;
1008    if (password_bytes) {
1009      if (is_hex)
1010        base::HexStringToBytes(password_text, password_bytes);
1011      else
1012        password_bytes->insert(password_bytes->end(),
1013                               password_text.begin(),
1014                               password_text.end());
1015    }
1016  }
1017}
1018
1019// static
1020bool WiFiService::CheckWEPIsHex(const string& passphrase, Error* error) {
1021  vector<uint8_t> passphrase_bytes;
1022  if (base::HexStringToBytes(passphrase, &passphrase_bytes)) {
1023    return true;
1024  } else {
1025    error->Populate(Error::kInvalidPassphrase);
1026    return false;
1027  }
1028}
1029
1030// static
1031bool WiFiService::CheckWEPKeyIndex(const string& passphrase, Error* error) {
1032  const auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII;
1033  if (base::StartsWith(passphrase, "0:", kCaseInsensitive) ||
1034      base::StartsWith(passphrase, "1:", kCaseInsensitive) ||
1035      base::StartsWith(passphrase, "2:", kCaseInsensitive) ||
1036      base::StartsWith(passphrase, "3:", kCaseInsensitive)) {
1037    return true;
1038  } else {
1039    error->Populate(Error::kInvalidPassphrase);
1040    return false;
1041  }
1042}
1043
1044// static
1045bool WiFiService::CheckWEPPrefix(const string& passphrase, Error* error) {
1046  if (base::StartsWith(passphrase, "0x",
1047                       base::CompareCase::INSENSITIVE_ASCII)) {
1048    return true;
1049  } else {
1050    error->Populate(Error::kInvalidPassphrase);
1051    return false;
1052  }
1053}
1054
1055// static
1056string WiFiService::ComputeSecurityClass(const string& security) {
1057  if (security == kSecurityRsn ||
1058      security == kSecurityWpa) {
1059    return kSecurityPsk;
1060  } else {
1061    return security;
1062  }
1063}
1064
1065
1066int16_t WiFiService::SignalLevel() const {
1067  return current_endpoint_ ? current_endpoint_->signal_strength() :
1068      std::numeric_limits<int16_t>::min();
1069}
1070
1071// static
1072bool WiFiService::ParseStorageIdentifier(const string& storage_name,
1073                                         string* address,
1074                                         string* mode,
1075                                         string* security) {
1076  vector<string> wifi_parts = base::SplitString(
1077      storage_name, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
1078  if ((wifi_parts.size() != 5 && wifi_parts.size() != 6) ||
1079      wifi_parts[0] != kTypeWifi) {
1080    return false;
1081  }
1082  *address = wifi_parts[1];
1083  *mode = wifi_parts[3];
1084  if (wifi_parts.size() == 5) {
1085    *security = wifi_parts[4];
1086  } else {
1087    // Account for security type "802_1x" which got split up above.
1088    *security = wifi_parts[4] + "_" + wifi_parts[5];
1089  }
1090  return true;
1091}
1092
1093// static
1094bool WiFiService::FixupServiceEntries(StoreInterface* storage) {
1095  bool fixed_entry = false;
1096  set<string> groups = storage->GetGroups();
1097  for (const auto& id : groups) {
1098    string device_address, network_mode, security;
1099    if (!ParseStorageIdentifier(id, &device_address,
1100                                &network_mode, &security)) {
1101      continue;
1102    }
1103    if (!storage->GetString(id, kStorageType, nullptr)) {
1104      storage->SetString(id, kStorageType, kTypeWifi);
1105      fixed_entry = true;
1106    }
1107    if (!storage->GetString(id, kStorageMode, nullptr)) {
1108      storage->SetString(id, kStorageMode, network_mode);
1109      fixed_entry = true;
1110    }
1111    if (!storage->GetString(id, kStorageSecurity, nullptr)) {
1112      storage->SetString(id, kStorageSecurity, security);
1113      fixed_entry = true;
1114    }
1115    if (!storage->GetString(id, kStorageSecurityClass, nullptr)) {
1116      storage->SetString(id, kStorageSecurityClass,
1117                         ComputeSecurityClass(security));
1118      fixed_entry = true;
1119    }
1120  }
1121  return fixed_entry;
1122}
1123
1124// static
1125bool WiFiService::IsValidMode(const string& mode) {
1126  return mode == kModeManaged || mode == kModeAdhoc;
1127}
1128
1129// static
1130bool WiFiService::IsValidSecurityMethod(const string& method) {
1131  return method == kSecurityNone ||
1132      method == kSecurityWep ||
1133      method == kSecurityPsk ||
1134      method == kSecurityWpa ||
1135      method == kSecurityRsn ||
1136      method == kSecurity8021x;
1137}
1138
1139// static
1140bool WiFiService::IsValidSecurityClass(const string& security_class) {
1141  return IsValidSecurityMethod(security_class) &&
1142      ComputeSecurityClass(security_class) == security_class;
1143}
1144
1145// static
1146uint8_t WiFiService::SignalToStrength(int16_t signal_dbm) {
1147  int16_t strength;
1148  if (signal_dbm > 0) {
1149    if (!logged_signal_warning) {
1150      LOG(WARNING) << "Signal strength is suspiciously high. "
1151                   << "Assuming value " << signal_dbm << " is not in dBm.";
1152      logged_signal_warning = true;
1153    }
1154    strength = signal_dbm;
1155  } else {
1156    strength = 120 + signal_dbm;  // Call -20dBm "perfect".
1157  }
1158
1159  if (strength > kStrengthMax) {
1160    strength = kStrengthMax;
1161  } else if (strength < kStrengthMin) {
1162    strength = kStrengthMin;
1163  }
1164  return strength;
1165}
1166
1167KeyValueStore WiFiService::GetStorageProperties() const {
1168  KeyValueStore args;
1169  args.SetString(kStorageType, kTypeWifi);
1170  args.SetString(kStorageSSID, hex_ssid_);
1171  args.SetString(kStorageMode, mode_);
1172  args.SetString(kStorageSecurityClass, ComputeSecurityClass(security_));
1173  return args;
1174}
1175
1176string WiFiService::GetDefaultStorageIdentifier() const {
1177  string security = ComputeSecurityClass(security_);
1178  return base::ToLowerASCII(base::StringPrintf("%s_%s_%s_%s_%s",
1179                                               kTypeWifi,
1180                                               kAnyDeviceAddress,
1181                                               hex_ssid_.c_str(),
1182                                               mode_.c_str(),
1183                                               security.c_str()));
1184}
1185
1186string WiFiService::GetSecurity(Error* /*error*/) {
1187  if (current_endpoint_) {
1188    return current_endpoint_->security_mode();
1189  }
1190  return security_;
1191}
1192
1193string WiFiService::GetSecurityClass(Error* error) {
1194  return ComputeSecurityClass(GetSecurity(error));
1195}
1196
1197void WiFiService::ClearCachedCredentials() {
1198  if (wifi_) {
1199    wifi_->ClearCachedCredentials(this);
1200  }
1201}
1202
1203void WiFiService::OnEapCredentialsChanged(
1204    Service::UpdateCredentialsReason reason) {
1205  if (Is8021x()) {
1206    OnCredentialChange(reason);
1207  }
1208}
1209
1210void WiFiService::OnCredentialChange(Service::UpdateCredentialsReason reason) {
1211  ClearCachedCredentials();
1212  // Credential changes due to a property update are new and have not
1213  // necessarily been used for a successful connection.
1214  if (reason == kReasonPropertyUpdate)
1215    SetHasEverConnected(false);
1216  UpdateConnectable();
1217  ResetSuspectedCredentialFailures();
1218}
1219
1220void WiFiService::OnProfileConfigured() {
1221  if (profile() || !hidden_ssid()) {
1222    return;
1223  }
1224  // This situation occurs when a hidden WiFi service created via GetService
1225  // has been persisted to a profile in Manager::ConfigureService().  Now
1226  // that configuration is saved, we must join the service with its profile,
1227  // which will make this SSID eligible for directed probes during scans.
1228  manager()->RegisterService(this);
1229}
1230
1231bool WiFiService::Is8021x() const {
1232  if (security_ == kSecurity8021x)
1233    return true;
1234
1235  // Dynamic WEP + 802.1x.
1236  if (security_ == kSecurityWep &&
1237      GetEAPKeyManagement() == WPASupplicant::kKeyManagementIeee8021X)
1238    return true;
1239  return false;
1240}
1241
1242WiFiRefPtr WiFiService::ChooseDevice() {
1243  DeviceRefPtr device = nullptr;
1244  if (!preferred_device_.empty()) {
1245    device = manager()->GetEnabledDeviceByLinkName(preferred_device_);
1246    if (device->technology() != Technology::kWifi) {
1247      device = nullptr;
1248    }
1249  }
1250  if (!device) {
1251    device = manager()->GetEnabledDeviceWithTechnology(Technology::kWifi);
1252  }
1253  // If we have a valid device here, it's better be a WiFi device.
1254  CHECK(!device || device->technology() == Technology::kWifi)
1255      << "Unexpected device technology: " << device->technology();
1256  return static_cast<WiFi*>(device.get());
1257}
1258
1259void WiFiService::ResetWiFi() {
1260  SetWiFi(nullptr);
1261}
1262
1263void WiFiService::SetWiFi(const WiFiRefPtr& new_wifi) {
1264  if (wifi_ == new_wifi) {
1265    return;
1266  }
1267  ClearCachedCredentials();
1268  if (wifi_) {
1269    wifi_->DisassociateFromService(this);
1270  }
1271  if (new_wifi) {
1272    adaptor()->EmitRpcIdentifierChanged(kDeviceProperty,
1273                                        new_wifi->GetRpcIdentifier());
1274  } else {
1275    adaptor()->EmitRpcIdentifierChanged(
1276        kDeviceProperty, control_interface()->NullRPCIdentifier());
1277  }
1278  wifi_ = new_wifi;
1279}
1280
1281uint16_t WiFiService::GetRoamThreshold(Error* /*error*/) {
1282  return roam_threshold_db_;
1283}
1284
1285bool WiFiService::SetRoamThreshold(const uint16_t& threshold,
1286                                   Error* /*error*/) {
1287  roam_threshold_db_ = threshold;
1288  roam_threshold_db_set_ = true;
1289  return true;
1290}
1291
1292void WiFiService::ClearRoamThreshold(Error* /*error*/) {
1293  roam_threshold_db_ = 0;
1294  roam_threshold_db_set_ = false;
1295}
1296
1297}  // namespace shill
1298