root/chromeos/network/shill_property_handler.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. GetListValue
  2. handler_
  3. OnPropertyChanged
  4. shill_manager_
  5. Init
  6. UpdateManagerProperties
  7. IsTechnologyAvailable
  8. IsTechnologyEnabled
  9. IsTechnologyEnabling
  10. IsTechnologyUninitialized
  11. SetTechnologyEnabled
  12. SetCheckPortalList
  13. RequestScan
  14. ConnectToBestServices
  15. RequestProperties
  16. OnPropertyChanged
  17. ManagerPropertiesCallback
  18. CheckPendingStateListUpdates
  19. ManagerPropertyChanged
  20. UpdateProperties
  21. UpdateObserved
  22. UpdateAvailableTechnologies
  23. UpdateEnabledTechnologies
  24. UpdateUninitializedTechnologies
  25. EnableTechnologyFailed
  26. GetPropertiesCallback
  27. PropertyChangedCallback
  28. NetworkServicePropertyChangedCallback
  29. GetIPConfigCallback
  30. UpdateIPConfigProperty
  31. NetworkDevicePropertyChangedCallback

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/network/shill_property_handler.h"

#include "base/bind.h"
#include "base/format_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/shill_device_client.h"
#include "chromeos/dbus/shill_ipconfig_client.h"
#include "chromeos/dbus/shill_manager_client.h"
#include "chromeos/dbus/shill_profile_client.h"
#include "chromeos/dbus/shill_service_client.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace {

// Limit the number of services or devices we observe. Since they are listed in
// priority order, it should be reasonable to ignore services past this.
const size_t kMaxObserved = 100;

const base::ListValue* GetListValue(const std::string& key,
                                    const base::Value& value) {
  const base::ListValue* vlist = NULL;
  if (!value.GetAsList(&vlist)) {
    LOG(ERROR) << "Error parsing key as list: " << key;
    return NULL;
  }
  return vlist;
}

}  // namespace

namespace chromeos {
namespace internal {

// Class to manage Shill service property changed observers. Observers are
// added on construction and removed on destruction. Runs the handler when
// OnPropertyChanged is called.
class ShillPropertyObserver : public ShillPropertyChangedObserver {
 public:
  typedef base::Callback<void(ManagedState::ManagedType type,
                              const std::string& service,
                              const std::string& name,
                              const base::Value& value)> Handler;

  ShillPropertyObserver(ManagedState::ManagedType type,
                        const std::string& path,
                        const Handler& handler)
      : type_(type),
        path_(path),
        handler_(handler) {
    if (type_ == ManagedState::MANAGED_TYPE_NETWORK) {
      DBusThreadManager::Get()->GetShillServiceClient()->
          AddPropertyChangedObserver(dbus::ObjectPath(path_), this);
    } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) {
      DBusThreadManager::Get()->GetShillDeviceClient()->
          AddPropertyChangedObserver(dbus::ObjectPath(path_), this);
    } else {
      NOTREACHED();
    }
  }

  virtual ~ShillPropertyObserver() {
    if (type_ == ManagedState::MANAGED_TYPE_NETWORK) {
      DBusThreadManager::Get()->GetShillServiceClient()->
          RemovePropertyChangedObserver(dbus::ObjectPath(path_), this);
    } else if (type_ == ManagedState::MANAGED_TYPE_DEVICE) {
      DBusThreadManager::Get()->GetShillDeviceClient()->
          RemovePropertyChangedObserver(dbus::ObjectPath(path_), this);
    } else {
      NOTREACHED();
    }
  }

  // ShillPropertyChangedObserver overrides.
  virtual void OnPropertyChanged(const std::string& key,
                                 const base::Value& value) OVERRIDE {
    handler_.Run(type_, path_, key, value);
  }

 private:
  ManagedState::ManagedType type_;
  std::string path_;
  Handler handler_;

  DISALLOW_COPY_AND_ASSIGN(ShillPropertyObserver);
};

//------------------------------------------------------------------------------
// ShillPropertyHandler

ShillPropertyHandler::ShillPropertyHandler(Listener* listener)
    : listener_(listener),
      shill_manager_(DBusThreadManager::Get()->GetShillManagerClient()) {
}

ShillPropertyHandler::~ShillPropertyHandler() {
  // Delete network service observers.
  STLDeleteContainerPairSecondPointers(
      observed_networks_.begin(), observed_networks_.end());
  STLDeleteContainerPairSecondPointers(
      observed_devices_.begin(), observed_devices_.end());
  CHECK(shill_manager_ == DBusThreadManager::Get()->GetShillManagerClient());
  shill_manager_->RemovePropertyChangedObserver(this);
}

void ShillPropertyHandler::Init() {
  UpdateManagerProperties();
  shill_manager_->AddPropertyChangedObserver(this);
}

void ShillPropertyHandler::UpdateManagerProperties() {
  NET_LOG_EVENT("UpdateManagerProperties", "");
  shill_manager_->GetProperties(
      base::Bind(&ShillPropertyHandler::ManagerPropertiesCallback,
                 AsWeakPtr()));
}

bool ShillPropertyHandler::IsTechnologyAvailable(
    const std::string& technology) const {
  return available_technologies_.count(technology) != 0;
}

bool ShillPropertyHandler::IsTechnologyEnabled(
    const std::string& technology) const {
  return enabled_technologies_.count(technology) != 0;
}

bool ShillPropertyHandler::IsTechnologyEnabling(
    const std::string& technology) const {
  return enabling_technologies_.count(technology) != 0;
}

bool ShillPropertyHandler::IsTechnologyUninitialized(
    const std::string& technology) const {
  return uninitialized_technologies_.count(technology) != 0;
}

void ShillPropertyHandler::SetTechnologyEnabled(
    const std::string& technology,
    bool enabled,
    const network_handler::ErrorCallback& error_callback) {
  if (enabled) {
    enabling_technologies_.insert(technology);
    shill_manager_->EnableTechnology(
        technology,
        base::Bind(&base::DoNothing),
        base::Bind(&ShillPropertyHandler::EnableTechnologyFailed,
                   AsWeakPtr(), technology, error_callback));
  } else {
    // Immediately clear locally from enabled and enabling lists.
    enabled_technologies_.erase(technology);
    enabling_technologies_.erase(technology);
    shill_manager_->DisableTechnology(
        technology,
        base::Bind(&base::DoNothing),
        base::Bind(&network_handler::ShillErrorCallbackFunction,
                   "SetTechnologyEnabled Failed",
                   technology, error_callback));
  }
}

void ShillPropertyHandler::SetCheckPortalList(
    const std::string& check_portal_list) {
  base::StringValue value(check_portal_list);
  shill_manager_->SetProperty(
      shill::kCheckPortalListProperty,
      value,
      base::Bind(&base::DoNothing),
      base::Bind(&network_handler::ShillErrorCallbackFunction,
                 "SetCheckPortalList Failed",
                 "", network_handler::ErrorCallback()));
}

void ShillPropertyHandler::RequestScan() const {
  shill_manager_->RequestScan(
      "",
      base::Bind(&base::DoNothing),
      base::Bind(&network_handler::ShillErrorCallbackFunction,
                 "RequestScan Failed",
                 "", network_handler::ErrorCallback()));
}

void ShillPropertyHandler::ConnectToBestServices() const {
  NET_LOG_EVENT("ConnectToBestServices", "");
  shill_manager_->ConnectToBestServices(
      base::Bind(&base::DoNothing),
      base::Bind(&network_handler::ShillErrorCallbackFunction,
                 "ConnectToBestServices Failed",
                 "", network_handler::ErrorCallback()));
}

void ShillPropertyHandler::RequestProperties(ManagedState::ManagedType type,
                                             const std::string& path) {
  if (pending_updates_[type].find(path) != pending_updates_[type].end())
    return;  // Update already requested.

  NET_LOG_DEBUG("Request Properties", path);
  pending_updates_[type].insert(path);
  if (type == ManagedState::MANAGED_TYPE_NETWORK ||
      type == ManagedState::MANAGED_TYPE_FAVORITE) {
    DBusThreadManager::Get()->GetShillServiceClient()->GetProperties(
        dbus::ObjectPath(path),
        base::Bind(&ShillPropertyHandler::GetPropertiesCallback,
                   AsWeakPtr(), type, path));
  } else if (type == ManagedState::MANAGED_TYPE_DEVICE) {
    DBusThreadManager::Get()->GetShillDeviceClient()->GetProperties(
        dbus::ObjectPath(path),
        base::Bind(&ShillPropertyHandler::GetPropertiesCallback,
                   AsWeakPtr(), type, path));
  } else {
    NOTREACHED();
  }
}

void ShillPropertyHandler::OnPropertyChanged(const std::string& key,
                                             const base::Value& value) {
  ManagerPropertyChanged(key, value);
  CheckPendingStateListUpdates(key);
}

//------------------------------------------------------------------------------
// Private methods

void ShillPropertyHandler::ManagerPropertiesCallback(
    DBusMethodCallStatus call_status,
    const base::DictionaryValue& properties) {
  if (call_status != DBUS_METHOD_CALL_SUCCESS) {
    NET_LOG_ERROR("ManagerPropertiesCallback",
                  base::StringPrintf("Failed: %d", call_status));
    return;
  }
  NET_LOG_EVENT("ManagerPropertiesCallback", "Success");
  const base::Value* update_service_value = NULL;
  const base::Value* update_service_complete_value = NULL;
  for (base::DictionaryValue::Iterator iter(properties);
       !iter.IsAtEnd(); iter.Advance()) {
    // Defer updating Services until all other properties have been updated.
    if (iter.key() == shill::kServicesProperty)
      update_service_value = &iter.value();
    else if (iter.key() == shill::kServiceCompleteListProperty)
      update_service_complete_value = &iter.value();
    else
      ManagerPropertyChanged(iter.key(), iter.value());
  }
  // Update Services which can safely assume other properties have been set.
  if (update_service_value)
    ManagerPropertyChanged(shill::kServicesProperty, *update_service_value);
  // Update ServiceCompleteList which skips entries that have already been
  // requested for Services.
  if (update_service_complete_value) {
    ManagerPropertyChanged(shill::kServiceCompleteListProperty,
                           *update_service_complete_value);
  }

  CheckPendingStateListUpdates("");
}

void ShillPropertyHandler::CheckPendingStateListUpdates(
    const std::string& key) {
  // Once there are no pending updates, signal the state list changed callbacks.
  if ((key.empty() || key == shill::kServicesProperty) &&
      pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0) {
    listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_NETWORK);
  }
  // Both Network update requests and Favorite update requests will affect
  // the list of favorites, so wait for both to complete.
  if ((key.empty() || key == shill::kServiceCompleteListProperty) &&
      pending_updates_[ManagedState::MANAGED_TYPE_NETWORK].size() == 0 &&
      pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) {
    listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE);
  }
  if ((key.empty() || key == shill::kDevicesProperty) &&
      pending_updates_[ManagedState::MANAGED_TYPE_DEVICE].size() == 0) {
    listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_DEVICE);
  }
}

void ShillPropertyHandler::ManagerPropertyChanged(const std::string& key,
                                                  const base::Value& value) {
  if (key == shill::kDefaultServiceProperty) {
    std::string service_path;
    value.GetAsString(&service_path);
    listener_->DefaultNetworkServiceChanged(service_path);
  } else if (key == shill::kServicesProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist) {
      listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_NETWORK, *vlist);
      UpdateProperties(ManagedState::MANAGED_TYPE_NETWORK, *vlist);
      // UpdateObserved used to use kServiceWatchListProperty for TYPE_NETWORK,
      // however that prevents us from receiving Strength updates from inactive
      // networks. The overhead for observing all services is not unreasonable
      // (and we limit the max number of observed services to kMaxObserved).
      UpdateObserved(ManagedState::MANAGED_TYPE_NETWORK, *vlist);
    }
  } else if (key == shill::kServiceCompleteListProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist) {
      listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_FAVORITE, *vlist);
      UpdateProperties(ManagedState::MANAGED_TYPE_FAVORITE, *vlist);
    }
  } else if (key == shill::kDevicesProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist) {
      listener_->UpdateManagedList(ManagedState::MANAGED_TYPE_DEVICE, *vlist);
      UpdateProperties(ManagedState::MANAGED_TYPE_DEVICE, *vlist);
      UpdateObserved(ManagedState::MANAGED_TYPE_DEVICE, *vlist);
    }
  } else if (key == shill::kAvailableTechnologiesProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist)
      UpdateAvailableTechnologies(*vlist);
  } else if (key == shill::kEnabledTechnologiesProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist)
      UpdateEnabledTechnologies(*vlist);
  } else if (key == shill::kUninitializedTechnologiesProperty) {
    const base::ListValue* vlist = GetListValue(key, value);
    if (vlist)
      UpdateUninitializedTechnologies(*vlist);
  } else if (key == shill::kProfilesProperty) {
    listener_->ProfileListChanged();
  } else if (key == shill::kCheckPortalListProperty) {
    std::string check_portal_list;
    if (value.GetAsString(&check_portal_list))
      listener_->CheckPortalListChanged(check_portal_list);
  } else {
    VLOG(2) << "Ignored Manager Property: " << key;
  }
}

void ShillPropertyHandler::UpdateProperties(ManagedState::ManagedType type,
                                            const base::ListValue& entries) {
  std::set<std::string>& requested_updates = requested_updates_[type];
  std::set<std::string>& requested_service_updates =
      requested_updates_[ManagedState::MANAGED_TYPE_NETWORK];  // For favorites
  std::set<std::string> new_requested_updates;
  NET_LOG_DEBUG(
      base::StringPrintf("UpdateProperties: %" PRIuS, entries.GetSize()),
      ManagedState::TypeToString(type));
  for (base::ListValue::const_iterator iter = entries.begin();
       iter != entries.end(); ++iter) {
    std::string path;
    (*iter)->GetAsString(&path);
    if (path.empty())
      continue;
    if (type == ManagedState::MANAGED_TYPE_FAVORITE &&
        requested_service_updates.count(path) > 0)
      continue;  // Update already requested

    // We add a special case for devices here to work around an issue in shill
    // that prevents it from sending property changed signals for cellular
    // devices (see crbug.com/321854).
    if (type == ManagedState::MANAGED_TYPE_DEVICE ||
        requested_updates.find(path) == requested_updates.end())
      RequestProperties(type, path);
    new_requested_updates.insert(path);
  }
  requested_updates.swap(new_requested_updates);
}

void ShillPropertyHandler::UpdateObserved(ManagedState::ManagedType type,
                                          const base::ListValue& entries) {
  DCHECK(type == ManagedState::MANAGED_TYPE_NETWORK ||
         type == ManagedState::MANAGED_TYPE_DEVICE);
  ShillPropertyObserverMap& observer_map =
      (type == ManagedState::MANAGED_TYPE_NETWORK)
      ? observed_networks_ : observed_devices_;
  ShillPropertyObserverMap new_observed;
  for (base::ListValue::const_iterator iter1 = entries.begin();
       iter1 != entries.end(); ++iter1) {
    std::string path;
    (*iter1)->GetAsString(&path);
    if (path.empty())
      continue;
    ShillPropertyObserverMap::iterator iter2 = observer_map.find(path);
    if (iter2 != observer_map.end()) {
      new_observed[path] = iter2->second;
    } else {
      // Create an observer for future updates.
      new_observed[path] = new ShillPropertyObserver(
          type, path, base::Bind(
              &ShillPropertyHandler::PropertyChangedCallback, AsWeakPtr()));
    }
    observer_map.erase(path);
    // Limit the number of observed services.
    if (new_observed.size() >= kMaxObserved)
      break;
  }
  // Delete network service observers still in observer_map.
  for (ShillPropertyObserverMap::iterator iter =  observer_map.begin();
       iter != observer_map.end(); ++iter) {
    delete iter->second;
  }
  observer_map.swap(new_observed);
}

void ShillPropertyHandler::UpdateAvailableTechnologies(
    const base::ListValue& technologies) {
  available_technologies_.clear();
  NET_LOG_EVENT("AvailableTechnologiesChanged",
                base::StringPrintf("Size: %" PRIuS, technologies.GetSize()));
  for (base::ListValue::const_iterator iter = technologies.begin();
       iter != technologies.end(); ++iter) {
    std::string technology;
    (*iter)->GetAsString(&technology);
    DCHECK(!technology.empty());
    available_technologies_.insert(technology);
  }
  listener_->TechnologyListChanged();
}

void ShillPropertyHandler::UpdateEnabledTechnologies(
    const base::ListValue& technologies) {
  enabled_technologies_.clear();
  NET_LOG_EVENT("EnabledTechnologiesChanged",
                base::StringPrintf("Size: %" PRIuS, technologies.GetSize()));
  for (base::ListValue::const_iterator iter = technologies.begin();
       iter != technologies.end(); ++iter) {
    std::string technology;
    (*iter)->GetAsString(&technology);
    DCHECK(!technology.empty());
    enabled_technologies_.insert(technology);
    enabling_technologies_.erase(technology);
  }
  listener_->TechnologyListChanged();
}

void ShillPropertyHandler::UpdateUninitializedTechnologies(
    const base::ListValue& technologies) {
  uninitialized_technologies_.clear();
  NET_LOG_EVENT("UninitializedTechnologiesChanged",
                base::StringPrintf("Size: %" PRIuS, technologies.GetSize()));
  for (base::ListValue::const_iterator iter = technologies.begin();
       iter != technologies.end(); ++iter) {
    std::string technology;
    (*iter)->GetAsString(&technology);
    DCHECK(!technology.empty());
    uninitialized_technologies_.insert(technology);
  }
  listener_->TechnologyListChanged();
}

void ShillPropertyHandler::EnableTechnologyFailed(
    const std::string& technology,
    const network_handler::ErrorCallback& error_callback,
    const std::string& dbus_error_name,
    const std::string& dbus_error_message) {
  enabling_technologies_.erase(technology);
  network_handler::ShillErrorCallbackFunction(
      "EnableTechnology Failed",
      technology, error_callback,
      dbus_error_name, dbus_error_message);
}

void ShillPropertyHandler::GetPropertiesCallback(
    ManagedState::ManagedType type,
    const std::string& path,
    DBusMethodCallStatus call_status,
    const base::DictionaryValue& properties) {
  NET_LOG_DEBUG("GetPropertiesCallback: " + ManagedState::TypeToString(type),
                path);
  pending_updates_[type].erase(path);
  if (call_status != DBUS_METHOD_CALL_SUCCESS) {
    // The shill service no longer exists.  This can happen when a network
    // has been removed.
    NET_LOG_DEBUG("Failed to get properties",
                  base::StringPrintf("%s: %d", path.c_str(), call_status));
    return;
  }
  // Update Favorite properties for networks in the Services list.
  if (type == ManagedState::MANAGED_TYPE_NETWORK) {
    // Only networks with a ProfilePath set are Favorites.
    std::string profile_path;
    properties.GetStringWithoutPathExpansion(
        shill::kProfileProperty, &profile_path);
    if (!profile_path.empty()) {
      listener_->UpdateManagedStateProperties(
          ManagedState::MANAGED_TYPE_FAVORITE, path, properties);
    }
  }
  listener_->UpdateManagedStateProperties(type, path, properties);
  // Request IPConfig parameters for networks.
  if (type == ManagedState::MANAGED_TYPE_NETWORK &&
      properties.HasKey(shill::kIPConfigProperty)) {
    std::string ip_config_path;
    if (properties.GetString(shill::kIPConfigProperty, &ip_config_path)) {
      DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties(
          dbus::ObjectPath(ip_config_path),
          base::Bind(&ShillPropertyHandler::GetIPConfigCallback,
                     AsWeakPtr(), path));
    }
  }

  // Notify the listener only when all updates for that type have completed.
  if (pending_updates_[type].size() == 0) {
    listener_->ManagedStateListChanged(type);
    // Notify that Favorites have changed when notifying for Networks if there
    // are no additional Favorite updates pending.
    if (type == ManagedState::MANAGED_TYPE_NETWORK &&
        pending_updates_[ManagedState::MANAGED_TYPE_FAVORITE].size() == 0) {
      listener_->ManagedStateListChanged(ManagedState::MANAGED_TYPE_FAVORITE);
    }
  }
}

void ShillPropertyHandler::PropertyChangedCallback(
    ManagedState::ManagedType type,
    const std::string& path,
    const std::string& key,
    const base::Value& value) {
  if (type == ManagedState::MANAGED_TYPE_NETWORK)
    NetworkServicePropertyChangedCallback(path, key, value);
  else if (type == ManagedState::MANAGED_TYPE_DEVICE)
    NetworkDevicePropertyChangedCallback(path, key, value);
  else
    NOTREACHED();
}

void ShillPropertyHandler::NetworkServicePropertyChangedCallback(
    const std::string& path,
    const std::string& key,
    const base::Value& value) {
  if (key == shill::kIPConfigProperty) {
    // Request the IPConfig for the network and update network properties
    // when the request completes.
    std::string ip_config_path;
    value.GetAsString(&ip_config_path);
    DCHECK(!ip_config_path.empty());
    DBusThreadManager::Get()->GetShillIPConfigClient()->GetProperties(
        dbus::ObjectPath(ip_config_path),
        base::Bind(&ShillPropertyHandler::GetIPConfigCallback,
                   AsWeakPtr(), path));
  } else {
    listener_->UpdateNetworkServiceProperty(path, key, value);
  }
}

void ShillPropertyHandler::GetIPConfigCallback(
    const std::string& service_path,
    DBusMethodCallStatus call_status,
    const base::DictionaryValue& properties)  {
  if (call_status != DBUS_METHOD_CALL_SUCCESS) {
    NET_LOG_ERROR("Failed to get IP Config properties",
                  base::StringPrintf("%s: %d",
                                     service_path.c_str(), call_status));
    return;
  }
  UpdateIPConfigProperty(service_path, properties, shill::kAddressProperty);
  UpdateIPConfigProperty(service_path, properties, shill::kNameServersProperty);
  UpdateIPConfigProperty(service_path, properties, shill::kPrefixlenProperty);
  UpdateIPConfigProperty(service_path, properties, shill::kGatewayProperty);
  UpdateIPConfigProperty(service_path, properties,
                         shill::kWebProxyAutoDiscoveryUrlProperty);
}

void ShillPropertyHandler::UpdateIPConfigProperty(
    const std::string& service_path,
    const base::DictionaryValue& properties,
    const char* property) {
  const base::Value* value;
  if (!properties.GetWithoutPathExpansion(property, &value)) {
    LOG(ERROR) << "Failed to get IPConfig property: " << property
               << ", for: " << service_path;
    return;
  }
  listener_->UpdateNetworkServiceProperty(
      service_path, NetworkState::IPConfigProperty(property), *value);
}

void ShillPropertyHandler::NetworkDevicePropertyChangedCallback(
    const std::string& path,
    const std::string& key,
    const base::Value& value) {
  listener_->UpdateDeviceProperty(path, key, value);
}

}  // namespace internal
}  // namespace chromeos

/* [<][>][^][v][top][bottom][index][help] */