普通文本  |  1703行  |  54.91 KB

//
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include "shill/service.h"

#include <stdio.h>
#include <time.h>

#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <vector>

#include <base/strings/string_number_conversions.h>
#include <base/strings/stringprintf.h>
#if defined(__ANDROID__)
#include <dbus/service_constants.h>
#else
#include <chromeos/dbus/service_constants.h>
#endif  // __ANDROID__

#include "shill/connection.h"
#include "shill/control_interface.h"
#include "shill/dhcp_properties.h"
#include "shill/error.h"
#include "shill/http_proxy.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/metrics.h"
#include "shill/net/event_history.h"
#include "shill/net/sockets.h"
#include "shill/profile.h"
#include "shill/property_accessor.h"
#include "shill/refptr_types.h"
#include "shill/service_property_change_notifier.h"
#include "shill/store_interface.h"

#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
#include "shill/eap_credentials.h"
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X

using base::Bind;
using std::map;
using std::string;
using std::vector;

namespace shill {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kService;
static string ObjectID(const Service* s) { return s->GetRpcIdentifier(); }
}

const char Service::kAutoConnBusy[] = "busy";
const char Service::kAutoConnConnected[] = "connected";
const char Service::kAutoConnConnecting[] = "connecting";
const char Service::kAutoConnExplicitDisconnect[] = "explicitly disconnected";
const char Service::kAutoConnNotConnectable[] = "not connectable";
const char Service::kAutoConnOffline[] = "offline";
const char Service::kAutoConnTechnologyNotConnectable[] =
    "technology not connectable";
const char Service::kAutoConnThrottled[] = "throttled";

#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
const size_t Service::kEAPMaxCertificationElements = 10;
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X

const char Service::kCheckPortalAuto[] = "auto";
const char Service::kCheckPortalFalse[] = "false";
const char Service::kCheckPortalTrue[] = "true";

const char Service::kErrorDetailsNone[] = "";

const int Service::kPriorityNone = 0;

const char Service::kServiceSortAutoConnect[] = "AutoConnect";
const char Service::kServiceSortConnectable[] = "Connectable";
const char Service::kServiceSortDependency[] = "Dependency";
const char Service::kServiceSortHasEverConnected[] = "HasEverConnected";
const char Service::kServiceSortIsConnected[] = "IsConnected";
const char Service::kServiceSortIsConnecting[] = "IsConnecting";
const char Service::kServiceSortIsFailed[] = "IsFailed";
const char Service::kServiceSortIsOnline[] = "IsOnline";
const char Service::kServiceSortIsPortalled[] = "IsPortal";
const char Service::kServiceSortPriority[] = "Priority";
const char Service::kServiceSortPriorityWithinTechnology[] =
    "PriorityWithinTechnology";
const char Service::kServiceSortSecurity[] = "Security";
const char Service::kServiceSortProfileOrder[] = "ProfileOrder";
const char Service::kServiceSortEtc[] = "Etc";
const char Service::kServiceSortSerialNumber[] = "SerialNumber";
const char Service::kServiceSortTechnology[] = "Technology";

const char Service::kStorageAutoConnect[] = "AutoConnect";
const char Service::kStorageCheckPortal[] = "CheckPortal";
const char Service::kStorageDNSAutoFallback[] = "DNSAutoFallback";
const char Service::kStorageError[] = "Error";
const char Service::kStorageFavorite[] = "Favorite";
const char Service::kStorageGUID[] = "GUID";
const char Service::kStorageHasEverConnected[] = "HasEverConnected";
const char Service::kStorageName[] = "Name";
const char Service::kStoragePriority[] = "Priority";
const char Service::kStoragePriorityWithinTechnology[]
    = "PriorityWithinTechnology";
const char Service::kStorageProxyConfig[] = "ProxyConfig";
const char Service::kStorageSaveCredentials[] = "SaveCredentials";
const char Service::kStorageType[] = "Type";
const char Service::kStorageUIData[] = "UIData";
const char Service::kStorageConnectionId[] = "ConnectionId";
const char Service::kStorageLinkMonitorDisabled[] = "LinkMonitorDisabled";
const char Service::kStorageManagedCredentials[] = "ManagedCredentials";

const uint8_t Service::kStrengthMax = 100;
const uint8_t Service::kStrengthMin = 0;

const uint64_t Service::kMaxAutoConnectCooldownTimeMilliseconds =
    30 * 60 * 1000;
const uint64_t Service::kMinAutoConnectCooldownTimeMilliseconds = 1000;
const uint64_t Service::kAutoConnectCooldownBackoffFactor = 2;

const int Service::kDisconnectsMonitorSeconds = 5 * 60;
const int Service::kMisconnectsMonitorSeconds = 5 * 60;
const int Service::kMaxDisconnectEventHistory = 20;
const int Service::kMaxMisconnectEventHistory = 20;

// static
unsigned int Service::next_serial_number_ = 0;

Service::Service(ControlInterface* control_interface,
                 EventDispatcher* dispatcher,
                 Metrics* metrics,
                 Manager* manager,
                 Technology::Identifier technology)
    : weak_ptr_factory_(this),
      state_(kStateIdle),
      previous_state_(kStateIdle),
      failure_(kFailureUnknown),
      auto_connect_(false),
      retain_auto_connect_(false),
      check_portal_(kCheckPortalAuto),
      connectable_(false),
      error_(ConnectFailureToString(failure_)),
      error_details_(kErrorDetailsNone),
      previous_error_serial_number_(0),
      explicitly_disconnected_(false),
      is_in_user_connect_(false),
      priority_(kPriorityNone),
      priority_within_technology_(kPriorityNone),
      crypto_algorithm_(kCryptoNone),
      key_rotation_(false),
      endpoint_auth_(false),
      strength_(0),
      save_credentials_(true),
      dhcp_properties_(new DhcpProperties()),
      technology_(technology),
      failed_time_(0),
      has_ever_connected_(false),
      disconnects_(kMaxDisconnectEventHistory),
      misconnects_(kMaxMisconnectEventHistory),
      auto_connect_cooldown_milliseconds_(0),
      store_(PropertyStore::PropertyChangeCallback(
          base::Bind(&Service::OnPropertyChanged,
                     weak_ptr_factory_.GetWeakPtr()))),
      dispatcher_(dispatcher),
      control_interface_(control_interface),
      serial_number_(next_serial_number_++),
      unique_name_(base::UintToString(serial_number_)),
      friendly_name_(unique_name_),
      adaptor_(control_interface->CreateServiceAdaptor(this)),
      property_change_notifier_(
          new ServicePropertyChangeNotifier(adaptor_.get())),
      metrics_(metrics),
      manager_(manager),
      sockets_(new Sockets()),
      time_(Time::GetInstance()),
      connection_id_(0),
      is_dns_auto_fallback_allowed_(false),
      link_monitor_disabled_(false),
      managed_credentials_(false),
      unreliable_(false) {
  HelpRegisterDerivedBool(kAutoConnectProperty,
                          &Service::GetAutoConnect,
                          &Service::SetAutoConnectFull,
                          &Service::ClearAutoConnect);

  // kActivationTypeProperty: Registered in CellularService
  // kActivationStateProperty: Registered in CellularService
  // kCellularApnProperty: Registered in CellularService
  // kCellularLastGoodApnProperty: Registered in CellularService
  // kNetworkTechnologyProperty: Registered in CellularService
  // kOutOfCreditsProperty: Registered in CellularService
  // kPaymentPortalProperty: Registered in CellularService
  // kRoamingStateProperty: Registered in CellularService
  // kServingOperatorProperty: Registered in CellularService
  // kUsageURLProperty: Registered in CellularService
  // kCellularPPPUsernameProperty: Registered in CellularService
  // kCellularPPPPasswordProperty: Registered in CellularService

  // kNetworkIdProperty: Registered in WiMaxService

  HelpRegisterDerivedString(kCheckPortalProperty,
                            &Service::GetCheckPortal,
                            &Service::SetCheckPortal);
  store_.RegisterConstBool(kConnectableProperty, &connectable_);
  HelpRegisterConstDerivedRpcIdentifier(kDeviceProperty,
                                        &Service::GetDeviceRpcId);
#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
  store_.RegisterConstStrings(kEapRemoteCertificationProperty,
                              &remote_certification_);
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X
  HelpRegisterDerivedString(kGuidProperty,
                            &Service::GetGuid,
                            &Service::SetGuid);

  // TODO(ers): in flimflam clearing Error has the side-effect of
  // setting the service state to IDLE. Is this important? I could
  // see an autotest depending on it.
  store_.RegisterConstString(kErrorProperty, &error_);
  store_.RegisterConstString(kErrorDetailsProperty, &error_details_);
  HelpRegisterConstDerivedUint16(kHTTPProxyPortProperty,
                                 &Service::GetHTTPProxyPort);
  HelpRegisterConstDerivedRpcIdentifier(kIPConfigProperty,
                                        &Service::GetIPConfigRpcIdentifier);
  HelpRegisterDerivedBool(kIsActiveProperty, &Service::IsActive, nullptr,
                          nullptr);
  // kModeProperty: Registered in WiFiService

  HelpRegisterDerivedString(kNameProperty,
                            &Service::GetNameProperty,
                            &Service::SetNameProperty);
  // kPassphraseProperty: Registered in WiFiService
  // kPassphraseRequiredProperty: Registered in WiFiService, WiMaxService
  store_.RegisterConstString(kPreviousErrorProperty,
                             &previous_error_);
  store_.RegisterConstInt32(kPreviousErrorSerialNumberProperty,
                            &previous_error_serial_number_);
  HelpRegisterDerivedInt32(kPriorityProperty,
                           &Service::GetPriority,
                           &Service::SetPriority);
  HelpRegisterDerivedInt32(kPriorityWithinTechnologyProperty,
                           &Service::GetPriorityWithinTechnology,
                           &Service::SetPriorityWithinTechnology);
  HelpRegisterDerivedString(kProfileProperty,
                            &Service::GetProfileRpcId,
                            &Service::SetProfileRpcId);
  HelpRegisterDerivedString(kProxyConfigProperty,
                            &Service::GetProxyConfig,
                            &Service::SetProxyConfig);
  store_.RegisterBool(kSaveCredentialsProperty, &save_credentials_);
  HelpRegisterConstDerivedString(kTetheringProperty,
                                 &Service::GetTethering);
  HelpRegisterDerivedString(kTypeProperty,
                            &Service::CalculateTechnology,
                            nullptr);
  // kSecurityProperty: Registered in WiFiService
  HelpRegisterDerivedString(kStateProperty,
                            &Service::CalculateState,
                            nullptr);
  store_.RegisterConstUint8(kSignalStrengthProperty, &strength_);
  store_.RegisterString(kUIDataProperty, &ui_data_);
  HelpRegisterConstDerivedStrings(kDiagnosticsDisconnectsProperty,
                                  &Service::GetDisconnectsProperty);
  HelpRegisterConstDerivedStrings(kDiagnosticsMisconnectsProperty,
                                  &Service::GetMisconnectsProperty);
  store_.RegisterConstInt32(kConnectionIdProperty, &connection_id_);
  store_.RegisterBool(kDnsAutoFallbackProperty, &is_dns_auto_fallback_allowed_);
  store_.RegisterBool(kLinkMonitorDisableProperty, &link_monitor_disabled_);
  store_.RegisterBool(kManagedCredentialsProperty, &managed_credentials_);

  HelpRegisterObservedDerivedBool(kVisibleProperty,
                                  &Service::GetVisibleProperty,
                                  nullptr,
                                  nullptr);

  store_.RegisterConstString(kPortalDetectionFailedPhaseProperty,
                             &portal_detection_failure_phase_);
  store_.RegisterConstString(kPortalDetectionFailedStatusProperty,
                             &portal_detection_failure_status_);

  metrics_->RegisterService(*this);

  static_ip_parameters_.PlumbPropertyStore(&store_);

  IgnoreParameterForConfigure(kTypeProperty);
  IgnoreParameterForConfigure(kProfileProperty);

  dhcp_properties_->InitPropertyStore(&store_);

  LOG(INFO) << Technology::NameFromIdentifier(technology) << " service "
            << unique_name_ << " constructed.";
}

Service::~Service() {
  metrics_->DeregisterService(*this);
  LOG(INFO) << "Service " << unique_name_ << " destroyed.";
}

void Service::AutoConnect() {
  const char* reason = nullptr;
  if (IsAutoConnectable(&reason)) {
    Error error;
    LOG(INFO) << "Auto-connecting to service " << unique_name_;
    ThrottleFutureAutoConnects();
    Connect(&error, __func__);
  } else {
    if (reason == kAutoConnConnected || reason == kAutoConnBusy) {
      SLOG(this, 1)
          << "Suppressed autoconnect to service " << unique_name_ << " "
          << "(" << reason << ")";
    } else {
      LOG(INFO) << "Suppressed autoconnect to service " << unique_name_ << " "
                << "(" << reason << ")";
    }
  }
}

void Service::Connect(Error* /*error*/, const char* reason) {
  LOG(INFO) << "Connect to service " << unique_name() <<": " << reason;
  ClearExplicitlyDisconnected();
  // Clear any failure state from a previous connect attempt.
  if (state() == kStateFailure)
    SetState(kStateIdle);
}

void Service::Disconnect(Error* /*error*/, const char* reason) {
  string log_message = base::StringPrintf(
      "Disconnecting from service %s: %s", unique_name_.c_str(), reason);
  if (IsActive(nullptr)) {
    LOG(INFO) << log_message;
  } else {
    SLOG(this, 1) << log_message;
  }
}

void Service::DisconnectWithFailure(ConnectFailure failure,
                                    Error* error,
                                    const char* reason) {
  Disconnect(error, reason);
  SetFailure(failure);
}

void Service::UserInitiatedDisconnect(Error* error) {
  Disconnect(error, "D-Bus RPC");
  explicitly_disconnected_ = true;
}

void Service::UserInitiatedConnect(Error* error) {
  Connect(error, "D-Bus RPC");
  is_in_user_connect_ = true;
}

void Service::ActivateCellularModem(const string& /*carrier*/,
                                    Error* error,
                                    const ResultCallback& /*callback*/) {
  Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported,
                        "Service doesn't support cellular modem activation.");
}

void Service::CompleteCellularActivation(Error* error) {
  Error::PopulateAndLog(
      FROM_HERE, error, Error::kNotSupported,
      "Service doesn't support cellular activation completion.");
}

bool Service::IsActive(Error* /*error*/) {
  return state() != kStateUnknown &&
    state() != kStateIdle &&
    state() != kStateFailure;
}

// static
bool Service::IsConnectedState(ConnectState state) {
  return (state == kStateConnected ||
          state == kStatePortal ||
          state == kStateOnline);
}

// static
bool Service::IsConnectingState(ConnectState state) {
  return (state == kStateAssociating ||
          state == kStateConfiguring);
}

bool Service::IsConnected() const {
  return IsConnectedState(state());
}

bool Service::IsConnecting() const {
  return IsConnectingState(state());
}

void Service::SetState(ConnectState state) {
  if (state == state_) {
    return;
  }

  LOG(INFO) << "Service " << unique_name_ << ": state "
            << ConnectStateToString(state_) << " -> "
            << ConnectStateToString(state);

  // Metric reporting for result of user-initiated connection attempt.
  if (is_in_user_connect_ && ((state == kStateConnected) ||
      (state == kStateFailure) || (state == kStateIdle))) {
    ReportUserInitiatedConnectionResult(state);
    is_in_user_connect_ = false;
  }

  if (state == kStateFailure) {
    NoteDisconnectEvent();
  }

  previous_state_ = state_;
  state_ = state;
  if (state != kStateFailure) {
    failure_ = kFailureUnknown;
    SetErrorDetails(kErrorDetailsNone);
  }
  if (state == kStateConnected) {
    failed_time_ = 0;
    has_ever_connected_ = true;
    SaveToProfile();
    // When we succeed in connecting, forget that connects failed in the past.
    // Give services one chance at a fast autoconnect retry by resetting the
    // cooldown to 0 to indicate that the last connect was successful.
    auto_connect_cooldown_milliseconds_  = 0;
    reenable_auto_connect_task_.Cancel();
  }
  UpdateErrorProperty();
  manager_->UpdateService(this);
  metrics_->NotifyServiceStateChanged(*this, state);
  adaptor_->EmitStringChanged(kStateProperty, GetStateString());
}

void Service::SetPortalDetectionFailure(const string& phase,
                                        const string& status) {
  portal_detection_failure_phase_ = phase;
  portal_detection_failure_status_ = status;
  adaptor_->EmitStringChanged(kPortalDetectionFailedPhaseProperty, phase);
  adaptor_->EmitStringChanged(kPortalDetectionFailedStatusProperty, status);
}

void Service::ReEnableAutoConnectTask() {
  // Kill the thing blocking AutoConnect().
  reenable_auto_connect_task_.Cancel();
  // Post to the manager, giving it an opportunity to AutoConnect again.
  manager_->UpdateService(this);
}

void Service::ThrottleFutureAutoConnects() {
  if (auto_connect_cooldown_milliseconds_ > 0) {
    LOG(INFO) << "Throttling future autoconnects to service " << unique_name_
              << ". Next autoconnect in "
              << auto_connect_cooldown_milliseconds_ << " milliseconds.";
    reenable_auto_connect_task_.Reset(Bind(&Service::ReEnableAutoConnectTask,
                                           weak_ptr_factory_.GetWeakPtr()));
    dispatcher_->PostDelayedTask(reenable_auto_connect_task_.callback(),
                                 auto_connect_cooldown_milliseconds_);
  }
  auto_connect_cooldown_milliseconds_ =
      std::min(kMaxAutoConnectCooldownTimeMilliseconds,
               std::max(kMinAutoConnectCooldownTimeMilliseconds,
                        auto_connect_cooldown_milliseconds_ *
                        kAutoConnectCooldownBackoffFactor));
}

void Service::SaveFailure() {
  previous_error_ = ConnectFailureToString(failure_);
  ++previous_error_serial_number_;
}

void Service::SetFailure(ConnectFailure failure) {
  failure_ = failure;
  SaveFailure();
  failed_time_ = time(nullptr);
  UpdateErrorProperty();
  SetState(kStateFailure);
}

void Service::SetFailureSilent(ConnectFailure failure) {
  NoteDisconnectEvent();
  // Note that order matters here, since SetState modifies |failure_| and
  // |failed_time_|.
  SetState(kStateIdle);
  failure_ = failure;
  SaveFailure();
  UpdateErrorProperty();
  failed_time_ = time(nullptr);
}

string Service::GetRpcIdentifier() const {
  return adaptor_->GetRpcIdentifier();
}

string Service::GetLoadableStorageIdentifier(
    const StoreInterface& storage) const {
  return IsLoadableFrom(storage) ? GetStorageIdentifier() : "";
}

bool Service::IsLoadableFrom(const StoreInterface& storage) const {
  return storage.ContainsGroup(GetStorageIdentifier());
}

bool Service::Load(StoreInterface* storage) {
  const string id = GetStorageIdentifier();
  if (!storage->ContainsGroup(id)) {
    LOG(WARNING) << "Service is not available in the persistent store: " << id;
    return false;
  }

  auto_connect_ = IsAutoConnectByDefault();
  retain_auto_connect_ =
      storage->GetBool(id, kStorageAutoConnect, &auto_connect_);
  // The legacy "Favorite" flag will override retain_auto_connect_ if present.
  storage->GetBool(id, kStorageFavorite, &retain_auto_connect_);

  LoadString(storage, id, kStorageCheckPortal, kCheckPortalAuto,
             &check_portal_);
  LoadString(storage, id, kStorageGUID, "", &guid_);
  if (!storage->GetInt(id, kStoragePriority, &priority_)) {
    priority_ = kPriorityNone;
  }
  if (!storage->GetInt(id, kStoragePriorityWithinTechnology,
                       &priority_within_technology_)) {
    priority_within_technology_ = kPriorityNone;
  }
  LoadString(storage, id, kStorageProxyConfig, "", &proxy_config_);
  storage->GetBool(id, kStorageSaveCredentials, &save_credentials_);
  LoadString(storage, id, kStorageUIData, "", &ui_data_);

  storage->GetInt(id, kStorageConnectionId, &connection_id_);
  storage->GetBool(id, kStorageDNSAutoFallback, &is_dns_auto_fallback_allowed_);
  storage->GetBool(id, kStorageLinkMonitorDisabled, &link_monitor_disabled_);
  if (!storage->GetBool(id, kStorageManagedCredentials,
                        &managed_credentials_)) {
    managed_credentials_ = false;
  }

  static_ip_parameters_.Load(storage, id);

#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
  // Call OnEapCredentialsChanged with kReasonCredentialsLoaded to avoid
  // resetting the has_ever_connected value.
  if (mutable_eap()) {
    mutable_eap()->Load(storage, id);
    OnEapCredentialsChanged(kReasonCredentialsLoaded);
  }
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X

  ClearExplicitlyDisconnected();

  // Read has_ever_connected_ value from stored profile
  // now that the credentials have been loaded.
  storage->GetBool(id, kStorageHasEverConnected, &has_ever_connected_);

  dhcp_properties_->Load(storage, id);
  return true;
}

bool Service::Unload() {
  auto_connect_ = IsAutoConnectByDefault();
  retain_auto_connect_ = false;
  check_portal_ = kCheckPortalAuto;
  ClearExplicitlyDisconnected();
  guid_ = "";
  has_ever_connected_ = false;
  priority_ = kPriorityNone;
  priority_within_technology_ = kPriorityNone;
  proxy_config_ = "";
  save_credentials_ = true;
  ui_data_ = "";
  connection_id_ = 0;
  is_dns_auto_fallback_allowed_ = false;
  link_monitor_disabled_ = false;
  managed_credentials_ = false;
#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
  if (mutable_eap()) {
    mutable_eap()->Reset();
  }
  ClearEAPCertification();
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X
  Error error;  // Ignored.
  Disconnect(&error, __func__);
  return false;
}

void Service::Remove(Error* /*error*/) {
  manager()->RemoveService(this);
  // |this| may no longer be valid now.
}

bool Service::Save(StoreInterface* storage) {
  const string id = GetStorageIdentifier();

  storage->SetString(id, kStorageType, GetTechnologyString());

  if (retain_auto_connect_) {
    storage->SetBool(id, kStorageAutoConnect, auto_connect_);
  } else {
    storage->DeleteKey(id, kStorageAutoConnect);
  }

  // Remove this legacy flag.
  storage->DeleteKey(id, kStorageFavorite);

  if (check_portal_ == kCheckPortalAuto) {
    storage->DeleteKey(id, kStorageCheckPortal);
  } else {
    storage->SetString(id, kStorageCheckPortal, check_portal_);
  }

  SaveString(storage, id, kStorageGUID, guid_, false, true);
  storage->SetBool(id, kStorageHasEverConnected, has_ever_connected_);
  storage->SetString(id, kStorageName, friendly_name_);
  if (priority_ != kPriorityNone) {
    storage->SetInt(id, kStoragePriority, priority_);
  } else {
    storage->DeleteKey(id, kStoragePriority);
  }
  if (priority_within_technology_ != kPriorityNone) {
    storage->SetInt(id, kStoragePriorityWithinTechnology,
                    priority_within_technology_);
  } else {
    storage->DeleteKey(id, kStoragePriorityWithinTechnology);
  }
  SaveString(storage, id, kStorageProxyConfig, proxy_config_, false, true);
  storage->SetBool(id, kStorageSaveCredentials, save_credentials_);
  SaveString(storage, id, kStorageUIData, ui_data_, false, true);

  storage->SetInt(id, kStorageConnectionId, connection_id_);
  storage->SetBool(id, kStorageDNSAutoFallback, is_dns_auto_fallback_allowed_);
  storage->SetBool(id, kStorageLinkMonitorDisabled, link_monitor_disabled_);
  storage->SetBool(id, kStorageManagedCredentials, managed_credentials_);

  static_ip_parameters_.Save(storage, id);
#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
  if (eap()) {
    eap()->Save(storage, id, save_credentials_);
  }
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X
  dhcp_properties_->Save(storage, id);
  return true;
}

void Service::Configure(const KeyValueStore& args, Error* error) {
  for (const auto it : args.properties()) {
    if (it.second.IsTypeCompatible<bool>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring bool property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring bool property: " << it.first;
      Error set_error;
      store_.SetBoolProperty(it.first, it.second.Get<bool>(), &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    } else if (it.second.IsTypeCompatible<int32_t>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring int32_t property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring int32_t property: " << it.first;
      Error set_error;
      store_.SetInt32Property(it.first, it.second.Get<int32_t>(), &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    } else if (it.second.IsTypeCompatible<KeyValueStore>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring key value store property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring key value store property: " << it.first;
      Error set_error;
      store_.SetKeyValueStoreProperty(it.first,
                                      it.second.Get<KeyValueStore>(),
                                      &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    } else if (it.second.IsTypeCompatible<string>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring string property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring string property: " << it.first;
      Error set_error;
      store_.SetStringProperty(it.first, it.second.Get<string>(), &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    } else if (it.second.IsTypeCompatible<Strings>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring strings property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring strings property: " << it.first;
      Error set_error;
      store_.SetStringsProperty(it.first, it.second.Get<Strings>(), &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    } else if (it.second.IsTypeCompatible<Stringmap>()) {
      if (ContainsKey(parameters_ignored_for_configure_, it.first)) {
        SLOG(this, 5) << "Ignoring stringmap property: " << it.first;
        continue;
      }
      SLOG(this, 5) << "Configuring stringmap property: " << it.first;
      Error set_error;
      store_.SetStringmapProperty(it.first,
                                  it.second.Get<Stringmap>(),
                                  &set_error);
      if (error->IsSuccess() && set_error.IsFailure()) {
        error->CopyFrom(set_error);
      }
    }
  }
}

bool Service::DoPropertiesMatch(const KeyValueStore& args) const {
  for (const auto& it : args.properties()) {
    if (it.second.IsTypeCompatible<bool>()) {
      SLOG(this, 5) << "Checking bool property: " << it.first;
      Error get_error;
      bool value;
      if (!store_.GetBoolProperty(it.first, &value, &get_error) ||
          value != it.second.Get<bool>()) {
        return false;
      }
    } else if (it.second.IsTypeCompatible<int32_t>()) {
      SLOG(this, 5) << "Checking int32 property: " << it.first;
      Error get_error;
      int32_t value;
      if (!store_.GetInt32Property(it.first, &value, &get_error) ||
          value != it.second.Get<int32_t>()) {
        return false;
      }
    } else if (it.second.IsTypeCompatible<string>()) {
      SLOG(this, 5) << "Checking string property: " << it.first;
      Error get_error;
      string value;
      if (!store_.GetStringProperty(it.first, &value, &get_error) ||
          value != it.second.Get<string>()) {
        return false;
      }
    } else if (it.second.IsTypeCompatible<Strings>()) {
      SLOG(this, 5) << "Checking strings property: " << it.first;
      Error get_error;
      Strings value;
      if (!store_.GetStringsProperty(it.first, &value, &get_error) ||
          value != it.second.Get<Strings>()) {
        return false;
      }
    } else if (it.second.IsTypeCompatible<Stringmap>()) {
      SLOG(this, 5) << "Checking stringmap property: " << it.first;
      Error get_error;
      Stringmap value;
      if (!store_.GetStringmapProperty(it.first, &value, &get_error) ||
          value != it.second.Get<Stringmap>()) {
        return false;
      }
    } else if (it.second.IsTypeCompatible<KeyValueStore>()) {
      SLOG(this, 5) << "Checking key value store property: " << it.first;
      Error get_error;
      KeyValueStore value;
      if (!store_.GetKeyValueStoreProperty(it.first, &value, &get_error) ||
          value != it.second.Get<KeyValueStore>()) {
        return false;
      }
    }
  }
  return true;
}

bool Service::IsRemembered() const {
  return profile_ && !manager_->IsServiceEphemeral(this);
}

bool Service::IsDependentOn(const ServiceRefPtr& b) const {
  if (!connection_ || !b || !b->connection()) {
    return false;
  }
  return connection_->GetLowerConnection() == b->connection();
}

void Service::EnableAndRetainAutoConnect() {
  if (retain_auto_connect_) {
    // We do not want to clobber the value of auto_connect_ (it may
    // be user-set). So return early.
    return;
  }

  SetAutoConnect(true);
  RetainAutoConnect();
}

void Service::SetConnection(const ConnectionRefPtr& connection) {
  if (connection.get()) {
    // TODO(pstew): Make this function testable by using a factory here.
    // http://crbug.com/216664
    http_proxy_.reset(new HTTPProxy(connection));
    http_proxy_->Start(dispatcher_, sockets_.get());
    Error unused_error;
    connection->set_tethering(GetTethering(&unused_error));
  } else {
    http_proxy_.reset();
    static_ip_parameters_.ClearSavedParameters();
  }
  connection_ = connection;
  NotifyIPConfigChanges();
}

void Service::NotifyIPConfigChanges() {
  Error error;
  string ipconfig = GetIPConfigRpcIdentifier(&error);
  if (error.IsSuccess()) {
    adaptor_->EmitRpcIdentifierChanged(kIPConfigProperty, ipconfig);
  }
}

#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
bool Service::Is8021xConnectable() const {
  return eap() && eap()->IsConnectable();
}

bool Service::AddEAPCertification(const string& name, size_t depth) {
  if (depth >= kEAPMaxCertificationElements) {
    LOG(WARNING) << "Ignoring certification " << name
                 << " because depth " << depth
                 << " exceeds our maximum of "
                 << kEAPMaxCertificationElements;
    return false;
  }

  if (depth >= remote_certification_.size()) {
    remote_certification_.resize(depth + 1);
  } else if (name == remote_certification_[depth]) {
    return true;
  }

  remote_certification_[depth] = name;
  LOG(INFO) << "Received certification for "
            << name
            << " at depth "
            << depth;
  return true;
}

void Service::ClearEAPCertification() {
  remote_certification_.clear();
}

void Service::SetEapCredentials(EapCredentials* eap) {
  // This operation must be done at most once for the lifetime of the service.
  CHECK(eap && !eap_);

  eap_.reset(eap);
  eap_->InitPropertyStore(mutable_store());
}
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X

bool Service::HasStaticIPAddress() const {
  return static_ip_parameters().ContainsAddress();
}

bool Service::HasStaticNameServers() const {
  return static_ip_parameters().ContainsNameServers();
}

void Service::SetAutoConnect(bool connect) {
  if (auto_connect() == connect) {
    return;
  }
  auto_connect_ = connect;
  adaptor_->EmitBoolChanged(kAutoConnectProperty, auto_connect());
}

// static
// Note: keep in sync with ERROR_* constants in
// android/system/connectivity/shill/IService.aidl.
const char* Service::ConnectFailureToString(const ConnectFailure& state) {
  switch (state) {
    case kFailureUnknown:
      return "Unknown";
    case kFailureAAA:
      return kErrorAaaFailed;
    case kFailureActivation:
      return kErrorActivationFailed;
    case kFailureBadPassphrase:
      return kErrorBadPassphrase;
    case kFailureBadWEPKey:
      return kErrorBadWEPKey;
    case kFailureConnect:
      return kErrorConnectFailed;
    case kFailureDNSLookup:
      return kErrorDNSLookupFailed;
    case kFailureDHCP:
      return kErrorDhcpFailed;
    case kFailureEAPAuthentication:
      return kErrorEapAuthenticationFailed;
    case kFailureEAPLocalTLS:
      return kErrorEapLocalTlsFailed;
    case kFailureEAPRemoteTLS:
      return kErrorEapRemoteTlsFailed;
    case kFailureHTTPGet:
      return kErrorHTTPGetFailed;
    case kFailureInternal:
      return kErrorInternal;
    case kFailureIPSecCertAuth:
      return kErrorIpsecCertAuthFailed;
    case kFailureIPSecPSKAuth:
      return kErrorIpsecPskAuthFailed;
    case kFailureNeedEVDO:
      return kErrorNeedEvdo;
    case kFailureNeedHomeNetwork:
      return kErrorNeedHomeNetwork;
    case kFailureOTASP:
      return kErrorOtaspFailed;
    case kFailureOutOfRange:
      return kErrorOutOfRange;
    case kFailurePinMissing:
      return kErrorPinMissing;
    case kFailurePPPAuth:
      return kErrorPppAuthFailed;
    case kFailureMax:
      NOTREACHED();
  }
  return "Invalid";
}

// static
const char* Service::ConnectStateToString(const ConnectState& state) {
  switch (state) {
    case kStateUnknown:
      return "Unknown";
    case kStateIdle:
      return "Idle";
    case kStateAssociating:
      return "Associating";
    case kStateConfiguring:
      return "Configuring";
    case kStateConnected:
      return "Connected";
    case kStatePortal:
      return "Portal";
    case kStateFailure:
      return "Failure";
    case kStateOnline:
      return "Online";
  }
  return "Invalid";
}

string Service::GetTechnologyString() const {
  return Technology::NameFromIdentifier(technology());
}

string Service::CalculateTechnology(Error* /*error*/) {
  return GetTechnologyString();
}

void Service::NoteDisconnectEvent() {
  SLOG(this, 2) << __func__;
  // Ignore the event if it's user-initiated explicit disconnect.
  if (explicitly_disconnected_) {
    SLOG(this, 2) << "Explicit disconnect ignored.";
    return;
  }
  // Ignore the event if manager is not running (e.g., service disconnects on
  // shutdown).
  if (!manager_->running()) {
    SLOG(this, 2) << "Disconnect while manager stopped ignored.";
    return;
  }
  // Ignore the event if the system is suspending.
  PowerManager* power_manager = manager_->power_manager();
  if (!power_manager || power_manager->suspending()) {
    SLOG(this, 2) << "Disconnect in transitional power state ignored.";
    return;
  }
  int period = 0;
  EventHistory* events = nullptr;
  // Sometimes services transition to Idle before going into a failed state so
  // take into account the last non-idle state.
  ConnectState state = state_ == kStateIdle ? previous_state_ : state_;
  if (IsConnectedState(state)) {
    LOG(INFO) << "Noting an unexpected connection drop.";
    period = kDisconnectsMonitorSeconds;
    events = &disconnects_;
  } else if (IsConnectingState(state)) {
    LOG(INFO) << "Noting an unexpected failure to connect.";
    period = kMisconnectsMonitorSeconds;
    events = &misconnects_;
  } else {
    SLOG(this, 2)
        << "Not connected or connecting, state transition ignored.";
    return;
  }
  events->RecordEventAndExpireEventsBefore(period,
                                           EventHistory::kClockTypeMonotonic);
}

void Service::ReportUserInitiatedConnectionResult(ConnectState state) {
  // Report stats for wifi only for now.
  if (technology_ != Technology::kWifi)
    return;

  int result;
  switch (state) {
    case kStateConnected:
      result = Metrics::kUserInitiatedConnectionResultSuccess;
      break;
    case kStateFailure:
      result = Metrics::kUserInitiatedConnectionResultFailure;
      metrics_->NotifyUserInitiatedConnectionFailureReason(
          Metrics::kMetricWifiUserInitiatedConnectionFailureReason, failure_);
      break;
    case kStateIdle:
      // This assumes the device specific class (wifi, cellular) will advance
      // the service's state from idle to other state after connection attempt
      // is initiated for the given service.
      result = Metrics::kUserInitiatedConnectionResultAborted;
      break;
    default:
      return;
  }

  metrics_->NotifyUserInitiatedConnectionResult(
      Metrics::kMetricWifiUserInitiatedConnectionResult, result);
}

bool Service::HasRecentConnectionIssues() {
  disconnects_.ExpireEventsBefore(kDisconnectsMonitorSeconds,
                                  EventHistory::kClockTypeMonotonic);
  misconnects_.ExpireEventsBefore(kMisconnectsMonitorSeconds,
                                  EventHistory::kClockTypeMonotonic);
  return !disconnects_.Empty() || !misconnects_.Empty();
}

// static
bool Service::DecideBetween(int a, int b, bool* decision) {
  if (a == b)
    return false;
  *decision = (a > b);
  return true;
}

uint16_t Service::SecurityLevel() {
  return (crypto_algorithm_ << 2) | (key_rotation_ << 1) | endpoint_auth_;
}

// static
bool Service::Compare(Manager* manager,
                      ServiceRefPtr a,
                      ServiceRefPtr b,
                      bool compare_connectivity_state,
                      const vector<Technology::Identifier>& tech_order,
                      const char** reason) {
  bool ret;

  if (compare_connectivity_state && a->state() != b->state()) {
    if (DecideBetween(a->IsOnline(), b->IsOnline(), &ret)) {
      *reason = kServiceSortIsOnline;
      return ret;
    }

    if (DecideBetween(a->IsConnected(), b->IsConnected(), &ret)) {
      *reason = kServiceSortIsConnected;
      return ret;
    }

    if (DecideBetween(!a->IsPortalled(), !b->IsPortalled(), &ret)) {
      *reason = kServiceSortIsPortalled;
      return ret;
    }

    if (DecideBetween(a->IsConnecting(), b->IsConnecting(), &ret)) {
      *reason = kServiceSortIsConnecting;
      return ret;
    }

    if (DecideBetween(!a->IsFailed(), !b->IsFailed(), &ret)) {
      *reason = kServiceSortIsFailed;
      return ret;
    }
  }

  if (DecideBetween(a->connectable(), b->connectable(), &ret)) {
    *reason = kServiceSortConnectable;
    return ret;
  }

  if (DecideBetween(a->IsDependentOn(b), b->IsDependentOn(a), &ret)) {
    *reason = kServiceSortDependency;
    return ret;
  }

  // Ignore the auto-connect property if both services are connected
  // already. This allows connected non-autoconnectable VPN services to be
  // sorted higher than other connected services based on technology order.
  if (!a->IsConnected() &&
      DecideBetween(a->auto_connect(), b->auto_connect(), &ret)) {
    *reason = kServiceSortAutoConnect;
    return ret;
  }

  if (DecideBetween(a->has_ever_connected() || a->managed_credentials_,
                    b->has_ever_connected() || b->managed_credentials_,
                    &ret)) {
    *reason = kServiceSortHasEverConnected;
    return ret;
  }

  if (DecideBetween(a->priority(), b->priority(), &ret)) {
    *reason = kServiceSortPriority;
    return ret;
  }

  // TODO(pstew): Below this point we are making value judgements on
  // services that are not related to anything intrinsic or
  // user-specified.  These heuristics should be richer (contain
  // historical information, for example) and be subject to user
  // customization.
  for (vector<Technology::Identifier>::const_iterator it = tech_order.begin();
       it != tech_order.end();
       ++it) {
    if (DecideBetween(a->technology() == *it, b->technology() == *it, &ret)) {
      *reason = kServiceSortTechnology;
      return ret;
    }
  }

  if (DecideBetween(a->priority_within_technology(),
                    b->priority_within_technology(), &ret)) {
    *reason = kServiceSortPriorityWithinTechnology;
    return ret;
  }

  if (DecideBetween(a->SecurityLevel(), b->SecurityLevel(), &ret)) {
    *reason = kServiceSortSecurity;
    return ret;
  }

  // If the profiles for the two services are different,
  // we want to pick the highest priority one.  The
  // ephemeral profile is explicitly tested for since it is not
  // listed in the manager profiles_ list.
  if (a->profile() != b->profile()) {
    *reason = kServiceSortProfileOrder;
    if (manager->IsServiceEphemeral(b)) {
      return true;
    } else if (manager->IsServiceEphemeral(a)) {
      return false;
    } else if (manager->IsProfileBefore(b->profile(), a->profile())) {
      return true;
    } else {
      return false;
    }
  }

  if (DecideBetween(a->strength(), b->strength(), &ret)) {
    *reason = kServiceSortEtc;
    return ret;
  }

  *reason = kServiceSortSerialNumber;
  return a->serial_number_ < b->serial_number_;
}

const ProfileRefPtr& Service::profile() const { return profile_; }

void Service::set_profile(const ProfileRefPtr& p) { profile_ = p; }

void Service::SetProfile(const ProfileRefPtr& p) {
  SLOG(this, 2) << "SetProfile from "
                << (profile_ ? profile_->GetFriendlyName() : "(none)")
                << " to " << (p ? p->GetFriendlyName() : "(none)")
                << ".";
  if (profile_ == p) {
    return;
  }
  profile_ = p;
  Error error;
  string profile_rpc_id = GetProfileRpcId(&error);
  if (!error.IsSuccess()) {
    return;
  }
  adaptor_->EmitStringChanged(kProfileProperty, profile_rpc_id);
}

void Service::OnPropertyChanged(const string& property) {
  SLOG(this, 1) << __func__ << " " << property;
#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
  if (Is8021x() && EapCredentials::IsEapAuthenticationProperty(property)) {
    OnEapCredentialsChanged(kReasonPropertyUpdate);
  }
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X
  SaveToProfile();
  if ((property == kCheckPortalProperty ||
       property == kProxyConfigProperty) &&
      (state_ == kStateConnected ||
       state_ == kStatePortal ||
       state_ == kStateOnline)) {
    manager_->RecheckPortalOnService(this);
  }
}

void Service::OnAfterResume() {
  // Forget old autoconnect failures across suspend/resume.
  auto_connect_cooldown_milliseconds_  = 0;
  reenable_auto_connect_task_.Cancel();
  // Forget if the user disconnected us, we might be able to connect now.
  ClearExplicitlyDisconnected();
}

void Service::OnDarkResume() {
  // Nothing to do in the general case.
}

string Service::GetIPConfigRpcIdentifier(Error* error) const {
  if (!connection_) {
    error->Populate(Error::kNotFound);
    return control_interface_->NullRPCIdentifier();
  }

  string id = connection_->ipconfig_rpc_identifier();

  if (id.empty()) {
    // Do not return an empty IPConfig.
    error->Populate(Error::kNotFound);
    return control_interface_->NullRPCIdentifier();
  }

  return id;
}

void Service::SetConnectable(bool connectable) {
  if (connectable_ == connectable)
    return;
  connectable_ = connectable;
  adaptor_->EmitBoolChanged(kConnectableProperty, connectable_);
}

void Service::SetConnectableFull(bool connectable) {
  if (connectable_ == connectable) {
    return;
  }
  SetConnectable(connectable);
  if (manager_->HasService(this)) {
    manager_->UpdateService(this);
  }
}

string Service::GetStateString() const {
  // TODO(benchan): We may want to rename shill::kState* to avoid name clashing
  // with Service::kState*.
  switch (state()) {
    case kStateIdle:
      return shill::kStateIdle;
    case kStateAssociating:
      return shill::kStateAssociation;
    case kStateConfiguring:
      return shill::kStateConfiguration;
    case kStateConnected:
      return shill::kStateReady;
    case kStateFailure:
      return shill::kStateFailure;
    case kStatePortal:
      return shill::kStatePortal;
    case kStateOnline:
      return shill::kStateOnline;
    case kStateUnknown:
    default:
      return "";
  }
}

string Service::CalculateState(Error* /*error*/) {
  return GetStateString();
}

bool Service::IsAutoConnectable(const char** reason) const {
  if (manager_->IsTechnologyAutoConnectDisabled(technology_)) {
    *reason = kAutoConnTechnologyNotConnectable;
    return false;
  }

  if (!connectable()) {
    *reason = kAutoConnNotConnectable;
    return false;
  }

  if (IsConnected()) {
    *reason = kAutoConnConnected;
    return false;
  }

  if (IsConnecting()) {
    *reason = kAutoConnConnecting;
    return false;
  }

  if (explicitly_disconnected_) {
    *reason = kAutoConnExplicitDisconnect;
    return false;
  }

  if (!reenable_auto_connect_task_.IsCancelled()) {
    *reason = kAutoConnThrottled;
    return false;
  }

  if (!Technology::IsPrimaryConnectivityTechnology(technology_) &&
      !manager_->IsConnected()) {
    *reason = kAutoConnOffline;
    return false;
  }

  return true;
}

bool Service::IsPortalDetectionDisabled() const {
  return check_portal_ == kCheckPortalFalse;
}

bool Service::IsPortalDetectionAuto() const {
  return check_portal_ == kCheckPortalAuto;
}

void Service::HelpRegisterDerivedBool(
    const string& name,
    bool(Service::*get)(Error* error),
    bool(Service::*set)(const bool&, Error*),
    void(Service::*clear)(Error*)) {
  store_.RegisterDerivedBool(
      name,
      BoolAccessor(new CustomAccessor<Service, bool>(this, get, set, clear)));
}

void Service::HelpRegisterDerivedInt32(
    const string& name,
    int32_t(Service::*get)(Error* error),
    bool(Service::*set)(const int32_t&, Error*)) {
  store_.RegisterDerivedInt32(
      name,
      Int32Accessor(new CustomAccessor<Service, int32_t>(this, get, set)));
}

void Service::HelpRegisterDerivedString(
    const string& name,
    string(Service::*get)(Error* error),
    bool(Service::*set)(const string&, Error*)) {
  store_.RegisterDerivedString(
      name,
      StringAccessor(new CustomAccessor<Service, string>(this, get, set)));
}

void Service::HelpRegisterConstDerivedRpcIdentifier(
    const string& name,
    RpcIdentifier(Service::*get)(Error*) const) {
  store_.RegisterDerivedRpcIdentifier(
      name,
      RpcIdentifierAccessor(new CustomReadOnlyAccessor<Service, RpcIdentifier>(
          this, get)));
}

void Service::HelpRegisterConstDerivedUint16(
    const string& name,
    uint16_t(Service::*get)(Error*) const) {
  store_.RegisterDerivedUint16(
      name,
      Uint16Accessor(new CustomReadOnlyAccessor<Service, uint16_t>(this, get)));
}

void Service::HelpRegisterConstDerivedStrings(
    const string& name, Strings(Service::*get)(Error* error) const) {
  store_.RegisterDerivedStrings(
      name,
      StringsAccessor(new CustomReadOnlyAccessor<Service, Strings>(this, get)));
}

void Service::HelpRegisterConstDerivedString(
    const string& name, string(Service::*get)(Error* error) const) {
  store_.RegisterDerivedString(
      name,
      StringAccessor(new CustomReadOnlyAccessor<Service, string>(this, get)));
}

void Service::HelpRegisterObservedDerivedBool(
    const string& name,
    bool(Service::*get)(Error* error),
    bool(Service::*set)(const bool&, Error*),
    void(Service::*clear)(Error*)) {
  BoolAccessor accessor(
      new CustomAccessor<Service, bool>(this, get, set, clear));
  store_.RegisterDerivedBool(name, accessor);
  property_change_notifier_->AddBoolPropertyObserver(name, accessor);
}

// static
void Service::LoadString(StoreInterface* storage,
                         const string& id,
                         const string& key,
                         const string& default_value,
                         string* value) {
  if (!storage->GetString(id, key, value)) {
    *value = default_value;
  }
}

// static
void Service::SaveString(StoreInterface* storage,
                         const string& id,
                         const string& key,
                         const string& value,
                         bool crypted,
                         bool save) {
  if (value.empty() || !save) {
    storage->DeleteKey(id, key);
    return;
  }
  if (crypted) {
    storage->SetCryptedString(id, key, value);
    return;
  }
  storage->SetString(id, key, value);
}

map<string, string> Service::GetLoadableProfileEntries() {
  return manager_->GetLoadableProfileEntriesForService(this);
}

void Service::IgnoreParameterForConfigure(const string& parameter) {
  parameters_ignored_for_configure_.insert(parameter);
}

#if !defined(DISABLE_WIFI) || !defined(DISABLE_WIRED_8021X)
const string& Service::GetEAPKeyManagement() const {
  CHECK(eap());
  return eap()->key_management();
}

void Service::SetEAPKeyManagement(const string& key_management) {
  CHECK(mutable_eap());
  mutable_eap()->SetKeyManagement(key_management, nullptr);
}
#endif  // DISABLE_WIFI || DISABLE_WIRED_8021X

bool Service::GetAutoConnect(Error* /*error*/) {
  return auto_connect();
}

bool Service::SetAutoConnectFull(const bool& connect, Error* /*error*/) {
  LOG(INFO) << "Service " << unique_name() << ": AutoConnect="
            << auto_connect() << "->" << connect;
  if (!retain_auto_connect_) {
    RetainAutoConnect();
    // Irrespective of an actual change in the |kAutoConnectPropety|, we must
    // flush the current value of the property to the profile.
    if (IsRemembered()) {
      SaveToProfile();
    }
  }

  if (auto_connect() == connect) {
    return false;
  }

  SetAutoConnect(connect);
  manager_->UpdateService(this);
  return true;
}

void Service::ClearAutoConnect(Error* /*error*/) {
  if (auto_connect()) {
    SetAutoConnect(false);
    manager_->UpdateService(this);
  }

  retain_auto_connect_ = false;
}

string Service::GetCheckPortal(Error* error) {
  return check_portal_;
}

bool Service::SetCheckPortal(const string& check_portal, Error* error) {
  if (check_portal != kCheckPortalFalse &&
      check_portal != kCheckPortalTrue &&
      check_portal != kCheckPortalAuto) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          base::StringPrintf(
                              "Invalid Service CheckPortal property value: %s",
                              check_portal.c_str()));
    return false;
  }
  if (check_portal == check_portal_) {
    return false;
  }
  check_portal_ = check_portal;
  return true;
}

string Service::GetGuid(Error* error) {
  return guid_;
}

bool Service::SetGuid(const string& guid, Error* /*error*/) {
  if (guid_ == guid) {
    return false;
  }
  guid_ = guid;
  adaptor_->EmitStringChanged(kGuidProperty, guid_);
  return true;
}

void Service::RetainAutoConnect() {
  retain_auto_connect_ = true;
}

void Service::SetSecurity(CryptoAlgorithm crypto_algorithm, bool key_rotation,
                          bool endpoint_auth) {
  crypto_algorithm_ = crypto_algorithm;
  key_rotation_ = key_rotation;
  endpoint_auth_ = endpoint_auth;
}

string Service::GetNameProperty(Error* /*error*/) {
  return friendly_name_;
}

bool Service::SetNameProperty(const string& name, Error* error) {
  if (name != friendly_name_) {
    Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
                          base::StringPrintf(
                              "Service %s Name property cannot be modified.",
                              unique_name_.c_str()));
    return false;
  }
  return false;
}

void Service::SetHasEverConnected(bool has_ever_connected) {
  if (has_ever_connected_ == has_ever_connected)
    return;
  has_ever_connected_ = has_ever_connected;
}

int32_t Service::GetPriority(Error* error) {
  return priority_;
}

bool Service::SetPriority(const int32_t& priority, Error* error) {
  if (priority_ == priority) {
    return false;
  }
  priority_ = priority;
  adaptor_->EmitIntChanged(kPriorityProperty, priority_);
  return true;
}

int32_t Service::GetPriorityWithinTechnology(Error* error) {
  return priority_within_technology_;
}

bool Service::SetPriorityWithinTechnology(const int32_t& priority,
                                          Error* error) {
  if (priority_within_technology_ == priority) {
    return false;
  }
  priority_within_technology_ = priority;
  adaptor_->EmitIntChanged(kPriorityWithinTechnologyProperty,
                           priority_within_technology_);
  return true;
}

string Service::GetProfileRpcId(Error* error) {
  if (!profile_) {
    // This happens in some unit tests where profile_ is not set.
    error->Populate(Error::kNotFound);
    return "";
  }
  return profile_->GetRpcIdentifier();
}

bool Service::SetProfileRpcId(const string& profile, Error* error) {
  if (profile_ && profile_->GetRpcIdentifier() == profile) {
    return false;
  }
  ProfileConstRefPtr old_profile = profile_;
  // No need to Emit afterwards, since SetProfileForService will call
  // into SetProfile (if the profile actually changes).
  manager_->SetProfileForService(this, profile, error);
  // Can't just use error.IsSuccess(), because that also requires saving
  // the profile to succeed. (See Profile::AdoptService)
  return (profile_ != old_profile);
}

uint16_t Service::GetHTTPProxyPort(Error* /*error*/) const {
  if (http_proxy_.get()) {
    return static_cast<uint16_t>(http_proxy_->proxy_port());
  }
  return 0;
}

string Service::GetProxyConfig(Error* error) {
  return proxy_config_;
}

bool Service::SetProxyConfig(const string& proxy_config, Error* error) {
  if (proxy_config_ == proxy_config)
    return false;
  proxy_config_ = proxy_config;
  adaptor_->EmitStringChanged(kProxyConfigProperty, proxy_config_);
  return true;
}

string Service::GetTethering(Error* error) const {
  // The "Tethering" property isn't supported by the Service base class, and
  // therefore should not be listed in the properties returned by
  // the GetProperties() RPC method.
  error->Populate(Error::kNotSupported);
  return "";
}


void Service::NotifyPropertyChanges() {
  property_change_notifier_->UpdatePropertyObservers();
}

Strings Service::GetDisconnectsProperty(Error* /*error*/) const {
  return disconnects_.ExtractWallClockToStrings();
}

Strings Service::GetMisconnectsProperty(Error* /*error*/) const {
  return misconnects_.ExtractWallClockToStrings();
}

bool Service::GetVisibleProperty(Error* /*error*/) {
  return IsVisible();
}

void Service::SaveToProfile() {
  if (profile_.get() && profile_->GetConstStorage()) {
    profile_->UpdateService(this);
  }
}

void Service::SetFriendlyName(const string& friendly_name) {
  if (friendly_name == friendly_name_)
    return;
  friendly_name_ = friendly_name;
  adaptor()->EmitStringChanged(kNameProperty, friendly_name_);
}

void Service::SetStrength(uint8_t strength) {
  if (strength == strength_) {
    return;
  }
  strength_ = strength;
  adaptor_->EmitUint8Changed(kSignalStrengthProperty, strength);
}

void Service::SetErrorDetails(const string& details) {
  if (error_details_ == details) {
    return;
  }
  error_details_ = details;
  adaptor_->EmitStringChanged(kErrorDetailsProperty, error_details_);
}

void Service::UpdateErrorProperty() {
  const string error(ConnectFailureToString(failure_));
  if (error == error_) {
    return;
  }
  error_ = error;
  adaptor_->EmitStringChanged(kErrorProperty, error);
}

void Service::ClearExplicitlyDisconnected() {
  if (explicitly_disconnected_) {
    explicitly_disconnected_ = false;
    manager_->UpdateService(this);
  }
}

}  // namespace shill