blob: e90cfd95f4958870100e593b2c6048292e24854f [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS 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 "src/device_impl.h"
#include <glog/logging.h>
#include "src/byte_counter.h"
#include "src/service.h"
using std::vector;
namespace cashew {
// Flimflam Device D-Bus identifiers
static const char *kFlimflamDeviceName = "org.chromium.flimflam";
// Flimflam Device property names
static const char *kFlimflamDeviceCarrierProperty = "Cellular.Carrier";
static const char *kFlimflamDeviceInterfaceProperty = "Interface";
static const char *kFlimflamDeviceTypeProperty = "Type";
static const char *kFlimflamDeviceIPConfigsProperty = "IPConfigs";
// Flimflam Device on-the-wire Type values
static const char *kFlimflamDeviceTypeEthernet = "ethernet";
static const char *kFlimflamDeviceTypeWifi = "wifi";
static const char *kFlimflamDeviceTypeWimax = "wimax";
static const char *kFlimflamDeviceTypeBluetooth = "bluetooth";
static const char *kFlimflamDeviceTypeGPS = "gps";
static const char *kFlimflamDeviceTypeCellular = "cellular";
// TODO(vlaviano): what does vendor look like?
static const char *kFlimflamDeviceTypeVendor = "";
// GetDeviceProperties retry interval
static const guint kSecondsPerMinute = 60;
static const guint kGetDevicePropertiesIntervalSeconds = 1 * kSecondsPerMinute;
DeviceImpl::DeviceImpl(Service * const parent,
DBus::Connection& connection, // NOLINT
const DBus::Path& path)
: DBus::ObjectProxy(connection, path, kFlimflamDeviceName),
parent_(CHECK_NOTNULL(parent)), path_(path), type_(kTypeUnknown),
get_properties_source_id_(0), retrying_get_properties_(false),
byte_counter_(NULL), byte_counter_running_(false) {
// schedule a GetProperties() call to our Flimflam service path to init state
// we'll keep trying periodically until we succeed
// we'll subsequently update this state by monitoring PropertyChanged signals
get_properties_source_id_ =
g_idle_add(StaticGetDevicePropertiesCallback, this);
if (get_properties_source_id_ == 0) {
LOG(ERROR) << path_ << ": ctor: g_idle_add failed";
}
property_changed_handler_.delegate(this);
}
DeviceImpl::~DeviceImpl() {
StopByteCounter();
if (get_properties_source_id_ != 0 &&
!g_source_remove(get_properties_source_id_)) {
LOG(WARNING) << path_ << ": dtor: g_source_remove failed";
}
}
const DBus::Path& DeviceImpl::GetPath() const {
return path_;
}
Device::Type DeviceImpl::GetType() const {
return type_;
}
const std::string& DeviceImpl::GetCarrier() const {
return carrier_;
}
const std::string& DeviceImpl::GetInterface() const {
return interface_;
}
void DeviceImpl::GetNameServers(vector<std::string>* nameservers) const {
if (ipconfig_ == NULL) {
LOG(WARNING) << path_ << ": ipconfig_ == NULL in GetNameServers";
return;
}
*nameservers = ipconfig_->GetNameServers();
}
// Flimflam Device D-Bus Proxy methods
void DeviceImpl::PropertyChanged(const std::string& property_name,
const DBus::Variant& new_value) {
LOG(INFO) << path_ << ": PropertyChanged: property_name = " << property_name;
// Queue a tuple representing this signal for later processing from the glib
// main loop. We do this to avoid libdbus-c++ deadlocks that can occur when
// sending a dbus message from within a dbus callback like this one.
PropertyChangedSignal signal(property_name, new_value);
property_changed_handler_.EnqueueSignal(signal);
}
// PropertyChangedDelegate methods
void DeviceImpl::OnPropertyChanged(const PropertyChangedHandler *handler,
const std::string& property_name,
const DBus::Variant& new_value) {
DCHECK(handler == &property_changed_handler_);
LOG(INFO) << path_ << ": OnPropertyChanged: property_name = "
<< property_name;
if (property_name == kFlimflamDeviceCarrierProperty) {
OnCarrierUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamDeviceInterfaceProperty) {
OnInterfaceUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamDeviceTypeProperty) {
OnTypeUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamDeviceIPConfigsProperty) {
vector< ::DBus::Path> ipconfigs(new_value.operator vector< ::DBus::Path>());
OnIPConfigsUpdate(ipconfigs);
} else {
// we don't care about this property
}
}
// Service methods
bool DeviceImpl::StartByteCounter() {
if (byte_counter_running_) {
LOG(WARNING) << path_ << ": StartByteCounter: counter already running";
return false;
}
byte_counter_running_ = true;
if (interface_.empty()) {
// if we don't yet know our interface name, we report success but hold off
// creating a byte counter object; we'll do so later in OnInterfaceUpdate
LOG(INFO) << path_
<< ": StartByteCounter: no interface name, delaying counter creation";
return true;
}
if (!CreateByteCounter()) {
LOG(WARNING) << path_
<< ": StartByteCounter: couldn't create byte counter for " << interface_;
byte_counter_running_ = false;
return false;
}
LOG(INFO) << path_ << ": StartByteCounter: created byte counter for "
<< interface_;
return true;
}
void DeviceImpl::StopByteCounter() {
if (byte_counter_running_) {
// NOTE: |byte_counter_| is an instance of ProcfsByteCounter.
// The implementation of ProcfsByteCounter is subtle in a way that
// the destructor of ProcfsByteCounter may again trigger the invocation
// of this method. To prevent |byte_counter_| from being deleted again
// during its destruction, we must set |byte_counter_running_| to false
// before calling DeleteByteCounter to delete |byte_counter_|.
byte_counter_running_ = false;
DeleteByteCounter();
// notify |parent_|
parent_->OnByteCounterStopped();
}
}
bool DeviceImpl::ReadStats() {
return byte_counter_ ? byte_counter_->ReadStats() : false;
}
bool DeviceImpl::GetByteCounterStats(uint64 *rx_bytes, uint64 *tx_bytes) const {
CHECK(rx_bytes != NULL);
CHECK(tx_bytes != NULL);
if (!byte_counter_running_) {
LOG(WARNING) << "GetByteCounterStats: byte counter is not running";
return false;
}
*rx_bytes = byte_counter_->GetRxBytes();
*tx_bytes = byte_counter_->GetTxBytes();
return true;
}
bool DeviceImpl::ByteCounterRunning() const {
return byte_counter_running_;
}
// ByteCounterDelegate methods
void DeviceImpl::OnByteCounterUpdate(const ByteCounter *counter,
uint64 rx_bytes, uint64 tx_bytes) {
DCHECK(byte_counter_running_);
DCHECK(byte_counter_ != NULL);
DCHECK(counter == byte_counter_);
LOG(INFO) << path_ << ": OnByteCounterUpdate: rx_bytes = " << rx_bytes
<< ", tx_bytes = " << tx_bytes;
parent_->OnByteCounterUpdate(rx_bytes, tx_bytes);
}
// Private methods
Device::Type DeviceImpl::TypeFromString(const std::string& type) const {
if (type == kFlimflamDeviceTypeEthernet) {
return kTypeEthernet;
}
if (type == kFlimflamDeviceTypeWifi) {
return kTypeWifi;
}
if (type == kFlimflamDeviceTypeWimax) {
return kTypeWimax;
}
if (type == kFlimflamDeviceTypeBluetooth) {
return kTypeBluetooth;
}
if (type == kFlimflamDeviceTypeGPS) {
return kTypeGPS;
}
if (type == kFlimflamDeviceTypeCellular) {
return kTypeCellular;
}
if (type == kFlimflamDeviceTypeVendor) {
return kTypeVendor;
}
return kTypeUnknown;
}
void DeviceImpl::OnCarrierUpdate(const std::string& carrier) {
LOG(INFO) << path_ << ": OnCarrierUpdate: carrier = " << carrier;
// only propagate this to parent Service if there's been a change
// we expect to see only one such change (from "Unknown")
if (carrier_ == carrier) {
return;
}
if (!carrier_.empty()) {
LOG(WARNING) << path_ << ": OnCarrierUpdate: carrier change from "
<< carrier_ << " to " << carrier << "!";
}
carrier_ = carrier;
parent_->OnCarrierUpdate(carrier);
}
void DeviceImpl::OnInterfaceUpdate(const std::string& interface) {
LOG(INFO) << path_ << ": OnInterfaceUpdate: interface = " << interface;
if (interface_ == interface) {
return;
}
if (!interface_.empty()) {
LOG(WARNING) << path_ << ": OnInterfaceUpdate: interface change from "
<< interface_ << " to " << interface << "!";
}
const std::string old_interface = interface_;
interface_ = interface;
if (!byte_counter_running_) {
return;
}
// get rid of byte counter for old interface
StopByteCounter();
// if we don't have new interface info, we can't make a new byte counter yet
if (interface_.empty()) {
return;
}
// create byte counter for new interface
// TODO(vlaviano): creating new counter on interface change may confuse our
// parent service.
if (!StartByteCounter()) {
LOG(ERROR) << path_
<< ": OnInterfaceUpdate: could not create byte counter for "
<< interface_;
return;
}
}
void DeviceImpl::OnTypeUpdate(const std::string& type) {
LOG(INFO) << path_ << ": OnTypeUpdate: type = " << type;
type_ = TypeFromString(type);
if (type_ != kTypeCellular) {
LOG(WARNING) << path_ << ": OnTypeUpdate: type is not cellular!";
}
}
void DeviceImpl::OnIPConfigsUpdate(const vector< ::DBus::Path>& ipconfigs) {
LOG(WARNING) << path_ << ": OnIPConfigsUpdate: " << ipconfigs.size()
<< " ipconfigs";
// TODO(jglasgow): handle multiple IP configs
for (vector< ::DBus::Path>::const_iterator it(ipconfigs.begin());
it != ipconfigs.end();
it++) {
if (ipconfig_ != NULL && *it == ipconfig_->GetPath()) {
LOG(INFO) << path_ << ": OnIPConfigsUpdate: Using existing ipconfig: "
<< *it;
break;
}
LOG(INFO) << path_ << ": OnIPConfigsUpdate: Using ipconfig at " << *it;
ipconfig_.reset(Ipconfig::NewIpconfig(conn(), *it));
break;
}
}
// static
gboolean DeviceImpl::StaticGetDevicePropertiesCallback(gpointer data) {
DeviceImpl *device = reinterpret_cast<DeviceImpl*>(data);
CHECK_NOTNULL(device);
DCHECK_NE(device->get_properties_source_id_, 0);
if (!device->GetDeviceProperties()) {
// call failed, so try again later
LOG(WARNING) << device->GetPath()
<< ": StaticGetDevicePropertiesCallback: "
<< "GetDeviceProperties failed, will retry in "
<< kGetDevicePropertiesIntervalSeconds << " secs";
// if we've arrived here from our first g_idle_add call, set up a timer
if (!device->retrying_get_properties_) {
guint source_id =
g_timeout_add_seconds(kGetDevicePropertiesIntervalSeconds,
StaticGetDevicePropertiesCallback, device);
device->get_properties_source_id_ = source_id;
if (source_id == 0) {
LOG(ERROR) << device->GetPath()
<< ": StaticGetDevicePropertiesCallback: "
<< "g_timeout_add_seconds failed";
return FALSE; // get rid of old glib source and give up
}
device->retrying_get_properties_ = true;
// get rid of our old glib source, but our new timer will call us
return FALSE;
}
// otherwise, our timer is already set up
// return TRUE to indicate that we want to be called again later
return TRUE;
}
device->get_properties_source_id_ = 0;
device->retrying_get_properties_ = false;
return FALSE; // we succeeded, so don't repeat
}
bool DeviceImpl::GetDeviceProperties() {
LOG(INFO) << path_ << ": GetDeviceProperties";
PropertyMap properties;
// dbus-c++ throws exceptions
// invoke the "Existing Non-conformant Code" clause of the style guide and
// isolate the rest of the system from this
try {
// TODO(vlaviano): make this call asynchronous
properties = GetProperties();
} catch (DBus::Error& error) { // NOLINT
LOG(WARNING) << path_
<< ": GetDeviceProperties: GetProperties() -> Exception: "
<< error.name() << ": " << error.message();
return false;
} catch (...) { // NOLINT
LOG(WARNING) << path_
<< ": GetDeviceProperties: GetProperties() -> Exception";
return false;
}
LOG(INFO) << "Received " << properties.size() << " properties";
// grab the properties in which we're interested
PropertyMap::const_iterator it;
it = properties.find(kFlimflamDeviceInterfaceProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnInterfaceUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_ << ": GetDeviceProperties: no Interface property";
}
it = properties.find(kFlimflamDeviceTypeProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnTypeUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_ << ": GetDeviceProperties: no Type property";
}
it = properties.find(kFlimflamDeviceIPConfigsProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
vector< ::DBus::Path> ipconfigs(value.operator vector< ::DBus::Path>());
OnIPConfigsUpdate(ipconfigs);
} else {
LOG(WARNING) << path_ << ": GetDeviceProperties: no IPConfigs property";
}
// don't expect to find Cellular.* properties if we're not a cellular device
if (type_ != kTypeCellular) {
return true;
}
it = properties.find(kFlimflamDeviceCarrierProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnCarrierUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_
<< ": GetDeviceProperties: no Cellular.Carrier property";
}
return true;
}
bool DeviceImpl::CreateByteCounter() {
if (byte_counter_ != NULL) {
LOG(WARNING) << path_ << ": CreateByteCounter: counter already exists";
return false;
}
if (interface_.empty()) {
LOG(WARNING) << path_ << ": CreateByteCounter: interface name unknown";
return false;
}
byte_counter_ = ByteCounter::NewByteCounter(interface_);
if (byte_counter_ == NULL) {
LOG(ERROR) << path_
<< ": CreateByteCounter: could not create byte counter for "
<< interface_;
return false;
}
byte_counter_->SetDelegate(this);
return true;
}
void DeviceImpl::DeleteByteCounter() {
LOG(INFO) << path_ << ": DeleteByteCounter";
if (byte_counter_ != NULL) {
delete byte_counter_;
byte_counter_ = NULL;
}
}
} // namespace cashew