// // 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 #include #include #include #include #include #include #include #include #if defined(__ANDROID__) #include #else #include #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()) { 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(), &set_error); if (error->IsSuccess() && set_error.IsFailure()) { error->CopyFrom(set_error); } } else if (it.second.IsTypeCompatible()) { 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(), &set_error); if (error->IsSuccess() && set_error.IsFailure()) { error->CopyFrom(set_error); } } else if (it.second.IsTypeCompatible()) { 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(), &set_error); if (error->IsSuccess() && set_error.IsFailure()) { error->CopyFrom(set_error); } } else if (it.second.IsTypeCompatible()) { 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(), &set_error); if (error->IsSuccess() && set_error.IsFailure()) { error->CopyFrom(set_error); } } else if (it.second.IsTypeCompatible()) { 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(), &set_error); if (error->IsSuccess() && set_error.IsFailure()) { error->CopyFrom(set_error); } } else if (it.second.IsTypeCompatible()) { 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(), &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()) { 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()) { return false; } } else if (it.second.IsTypeCompatible()) { 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()) { return false; } } else if (it.second.IsTypeCompatible()) { 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()) { return false; } } else if (it.second.IsTypeCompatible()) { 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()) { return false; } } else if (it.second.IsTypeCompatible()) { 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()) { return false; } } else if (it.second.IsTypeCompatible()) { 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()) { 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& 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::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(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(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(this, get, set))); } void Service::HelpRegisterConstDerivedRpcIdentifier( const string& name, RpcIdentifier(Service::*get)(Error*) const) { store_.RegisterDerivedRpcIdentifier( name, RpcIdentifierAccessor(new CustomReadOnlyAccessor( this, get))); } void Service::HelpRegisterConstDerivedUint16( const string& name, uint16_t(Service::*get)(Error*) const) { store_.RegisterDerivedUint16( name, Uint16Accessor(new CustomReadOnlyAccessor(this, get))); } void Service::HelpRegisterConstDerivedStrings( const string& name, Strings(Service::*get)(Error* error) const) { store_.RegisterDerivedStrings( name, StringsAccessor(new CustomReadOnlyAccessor(this, get))); } void Service::HelpRegisterConstDerivedString( const string& name, string(Service::*get)(Error* error) const) { store_.RegisterDerivedString( name, StringAccessor(new CustomReadOnlyAccessor(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(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 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(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