blob: 1eb975ad0cb77c5c346211d3392ddd40f0f52ff1 [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/service_impl.h"
#include <glog/logging.h>
#include "src/aggregator.h"
#include "src/data_plan.h"
#include "src/data_plan_provider.h"
#include "src/device.h"
#include "src/policy.h"
#include "src/service_manager.h"
namespace cashew {
// Flimflam Service D-Bus identifiers
static const char *kFlimflamServiceName = "org.chromium.flimflam";
// Flimflam Service property names
static const char *kFlimflamServiceDeviceProperty = "Device";
static const char *kFlimflamServiceStateProperty = "State";
static const char *kFlimflamServiceTypeProperty = "Type";
static const char *kFlimflamServiceUsageUrlProperty = "Cellular.UsageUrl";
static const char *kFlimflamServiceStickyHostRouteProperty = "StickyHostRoute";
// Flimflam Service on-the-wire State values
static const char *kFlimflamServiceStateIdle = "idle";
static const char *kFlimflamServiceStateCarrier = "carrier";
static const char *kFlimflamServiceStateAssociation = "association";
static const char *kFlimflamServiceStateConfiguration = "configuration";
static const char *kFlimflamServiceStateReady = "ready";
static const char *kFlimflamServiceStatePortal = "portal";
static const char *kFlimflamServiceStateOnline = "online";
static const char *kFlimflamServiceStateDisconnect = "disconnect";
static const char *kFlimflamServiceStateFailure = "failure";
static const char *kFlimflamServiceStateActivationFailure =
"activation-failure";
// Flimflam Service on-the-wire Type values
static const char *kFlimflamServiceTypeEthernet = "ethernet";
static const char *kFlimflamServiceTypeWifi = "wifi";
static const char *kFlimflamServiceTypeWimax = "wimax";
static const char *kFlimflamServiceTypeBluetooth = "bluetooth";
static const char *kFlimflamServiceTypeCellular = "cellular";
// Chromium OS Usage API property names
static const char *kCrosUsageVersionProperty = "version";
static const char *kCrosUsageStatusProperty = "status";
static const char *kCrosUsageRestrictedProperty = "restricted";
static const char *kCrosUsagePlansProperty = "plans";
// Chromium OS Usage API version values
static const int kCrosUsageVersionMinSupported = 1;
static const int kCrosUsageVersionMaxSupported = 1;
// Chromium OS Usage API status values
// NOTE: add new values to IsValidCrosUsageStatus below
// NOTE: add new values to Metrics Manager's UsageRequestStatus enum
// NOTE: add new values to MetricsEnumFromStatusString below
// TODO(vlaviano): improve this
static const char *kCrosUsageStatusOk = "OK";
static const char *kCrosUsageStatusError = "ERROR";
static const char *kCrosUsageStatusMalformedRequest = "MALFORMED REQUEST";
static const char *kCrosUsageStatusInternalError = "INTERNAL ERROR";
static const char *kCrosUsageStatusServiceUnavailable = "SERVICE UNAVAILABLE";
static const char *kCrosUsageStatusRequestRefused = "REQUEST REFUSED";
static const char *kCrosUsageStatusUnknownDevice = "UNKNOWN DEVICE";
// GetServiceProperties retry interval
static const guint kSecondsPerMinute = 60;
static const guint kGetServicePropertiesIntervalSeconds = 1 * kSecondsPerMinute;
ServiceImpl::ServiceImpl(ServiceManager * const parent,
DBus::Connection& connection, // NOLINT
MetricsManager * const metrics_manager,
Aggregator * const aggregator,
const DBus::Path& path)
: DBus::ObjectProxy(connection, path, kFlimflamServiceName),
parent_(CHECK_NOTNULL(parent)), connection_(connection),
metrics_manager_(CHECK_NOTNULL(metrics_manager)),
aggregator_(CHECK_NOTNULL(aggregator)), prev_rx_bytes_(0),
prev_tx_bytes_(0), path_(path), state_(kStateUnknown),
type_(kTypeUnknown), device_(NULL), provider_(NULL),
request_in_progress_(false), usage_request_complete_(false),
update_timeout_source_(NULL), policy_(NULL),
is_default_service_(false), get_properties_source_id_(0),
retrying_get_properties_(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(StaticGetServicePropertiesCallback, this);
if (get_properties_source_id_ == 0) {
LOG(ERROR) << path_ << ": ctor: g_idle_add failed";
}
property_changed_handler_.delegate(this);
}
ServiceImpl::~ServiceImpl() {
DeleteCarrierState();
DeleteDataPlans(&data_plans_);
if (device_ != NULL) {
LOG(INFO) << path_ << ": deleting device " << device_->GetPath();
delete device_;
device_ = NULL;
}
if (get_properties_source_id_ != 0 &&
!g_source_remove(get_properties_source_id_)) {
LOG(WARNING) << path_ << ": dtor: g_source_remove failed";
}
}
const DBus::Path& ServiceImpl::GetPath() const {
return path_;
}
Service::State ServiceImpl::GetState() const {
return state_;
}
Service::Type ServiceImpl::GetType() const {
return type_;
}
// static
Service::Type ServiceImpl::TypeFromString(const std::string& type) {
if (type == kFlimflamServiceTypeEthernet) {
return kTypeEthernet;
}
if (type == kFlimflamServiceTypeWifi) {
return kTypeWifi;
}
if (type == kFlimflamServiceTypeWimax) {
return kTypeWimax;
}
if (type == kFlimflamServiceTypeBluetooth) {
return kTypeBluetooth;
}
if (type == kFlimflamServiceTypeCellular) {
return kTypeCellular;
}
return kTypeUnknown;
}
Device* ServiceImpl::GetDevice() const {
return device_;
}
DBusDataPlanList ServiceImpl::GetDBusDataPlans() const {
DBusDataPlanList dbus_data_plans;
DataPlanList::const_iterator it;
for (it = data_plans_.begin(); it != data_plans_.end(); ++it) {
DataPlan *plan = *it;
DCHECK(plan != NULL);
// filter out inactive plans
if (plan->IsActive()) {
dbus_data_plans.push_back(plan->ToDBusFormat());
} else {
LOG(INFO) << path_ << ": GetBusDataPlans: skipping inactive plan: \""
<< plan->GetName() << "\"";
}
}
return dbus_data_plans;
}
bool ServiceImpl::IsDefaultService() const {
if (is_default_service_) {
// cross-check with parent's idea of default technology
if (parent_->GetDefaultTechnology() != kTypeCellular) {
LOG(WARNING) << path_ << ": IsDefaultService: "
<< "service manager doesn't think default technology is cellular";
}
return true;
}
return false;
}
// Flimflam Service D-Bus Proxy methods
void ServiceImpl::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 ServiceImpl::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 == kFlimflamServiceDeviceProperty) {
OnDeviceUpdate(new_value.reader().get_path());
} else if (property_name == kFlimflamServiceStateProperty) {
OnStateUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamServiceTypeProperty) {
OnTypeUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamServiceUsageUrlProperty) {
OnUsageUrlUpdate(new_value.reader().get_string());
} else if (property_name == kFlimflamServiceStickyHostRouteProperty) {
OnStickyHostRouteUpdate(new_value.reader().get_string());
} else {
// we don't care about this property
}
}
// Device methods
void ServiceImpl::OnCarrierUpdate(const std::string& carrier) {
LOG(INFO) << path_ << ": OnCarrierUpdate: carrier = " << carrier;
DCHECK(device_ != NULL);
DCHECK(carrier == device_->GetCarrier());
// get rid of state associated with old carrier
if (provider_ != NULL) {
DCHECK(carrier != provider_->GetCarrier());
}
DeleteCarrierState();
// we need to make a new request
usage_request_complete_ = false;
// if we don't have new carrier info, we can't do anything now
if (carrier.empty()) {
return;
}
// create state for new carrier
LOG(INFO) << path_ << ": OnCarrierUpdate: creating data plan provider";
provider_ = new(std::nothrow) DataPlanProvider(carrier);
if (provider_ == NULL) {
LOG(ERROR) << path_
<< ": OnCarrierUpdate: could not create data plan provider";
return;
}
provider_->SetDelegate(this);
LOG(INFO) << path_ << ": OnCarrierUpdate: getting policy for " << carrier;
policy_ = Policy::GetPolicy(carrier);
if (policy_ == NULL) {
LOG(ERROR) << path_ << ": OnCarrierUpdate: could not get policy for "
<< carrier;
DeleteCarrierState();
return;
}
// if we already have usage url, set new provider in motion
if (!usage_url_.empty()) {
provider_->SetUsageUrl(usage_url_);
ReconsiderSendingUsageRequests();
} else {
// we'll do this later in OnUsageUrlUpdate
}
}
void ServiceImpl::OnByteCounterUpdate(uint64 rx_bytes, uint64 tx_bytes) {
LOG(INFO) << path_ << ": OnByteCounterUpdate: rx_bytes = " << rx_bytes
<< ", tx_bytes = " << tx_bytes;
// try to detect overflow
if (rx_bytes < prev_rx_bytes_) {
LOG(WARNING) << path_ << ": OnByteCounterUpdate: overflow detected:"
" |rx_bytes|";
rx_bytes = kuint64max;
}
if (tx_bytes < prev_tx_bytes_) {
LOG(WARNING) << path_ << ": OnByteCounterUpdate: overflow detected:"
" |tx_bytes|";
tx_bytes = kuint64max;
}
// compute deltas
uint64 delta_rx_bytes = rx_bytes - prev_rx_bytes_;
uint64 delta_tx_bytes = tx_bytes - prev_tx_bytes_;
// pass along byte counter info to |aggregator_|
aggregator_->OnByteCounterUpdate(this, delta_rx_bytes, delta_tx_bytes);
// update prev counts
prev_rx_bytes_ = rx_bytes;
prev_tx_bytes_ = tx_bytes;
// dispatch this notification to our data plans list and consider sending
// out a signal if any plans were updated
if (usage_request_complete_ &&
DataPlan::OnByteCounterUpdate(&data_plans_, this, parent_, device_,
delta_rx_bytes, delta_tx_bytes)) {
MaybeEmitDataPlansUpdate();
}
}
void ServiceImpl::OnByteCounterStopped() {
// reset previous byte counter info trackers
prev_rx_bytes_ = 0;
prev_tx_bytes_ = 0;
}
// DataPlan methods
bool ServiceImpl::OnDataPlanExpired(DataPlan *data_plan) {
CHECK(data_plan != NULL);
CHECK(device_ != NULL);
// update byte counter stats
if (!device_->ReadStats()) {
LOG(WARNING) << "OnDataPlanExpired: failed to read byte counter stats";
return false;
}
// request byte counter stats
uint64 rx_bytes;
uint64 tx_bytes;
if (!device_->GetByteCounterStats(&rx_bytes, &tx_bytes)) {
LOG(WARNING) << "OnDataPlanExpired: failed to retrieve byte counter stats";
return false;
}
// compute deltas
uint64 delta_rx_bytes = rx_bytes - prev_rx_bytes_;
uint64 delta_tx_bytes = tx_bytes - prev_tx_bytes_;
ByteCount used_bytes_to_assign = delta_rx_bytes + delta_tx_bytes;
// try to detect overflow
if (static_cast<uint64>(used_bytes_to_assign) < delta_rx_bytes ||
static_cast<uint64>(used_bytes_to_assign) < delta_tx_bytes ||
used_bytes_to_assign < 0) {
LOG(WARNING) << "OnDataPlanExpired: overflow detected:"
" |used_bytes_to_assign|";
return false;
}
// pass along usage info to |aggregator_|
aggregator_->OnByteCounterUpdate(this, delta_rx_bytes, delta_tx_bytes);
// update prev counts
prev_rx_bytes_ = rx_bytes;
prev_tx_bytes_ = tx_bytes;
// assign as much as we can to the expiring |data_plan|
ByteCount assigned_bytes = DataPlan::AssignBytesToPlan(data_plan,
used_bytes_to_assign);
CHECK_LE(assigned_bytes, used_bytes_to_assign);
// if we still have bytes left, assign them to other active data plans
used_bytes_to_assign -= assigned_bytes;
if (used_bytes_to_assign > 0) {
DataPlan::OnByteCounterUpdate(&data_plans_, this, parent_, device_,
used_bytes_to_assign, 0);
}
return true;
}
// DataPlanProviderDelegate methods
void ServiceImpl::OnRequestComplete(const DataPlanProvider *provider,
bool successful,
const Value *parsed_usage_update) {
DCHECK(provider != NULL);
DCHECK(!successful || parsed_usage_update != NULL);
if (provider != provider_) {
LOG(WARNING) << path_ << ": OnRequestComplete: wrong provider";
return;
}
DCHECK(request_in_progress_);
DCHECK(policy_ != NULL);
LOG(INFO) << path_ << ": OnRequestComplete: result = " << successful;
request_in_progress_ = false;
if (!successful) {
LOG(WARNING) << path_ << ": OnRequestComplete: request failed";
metrics_manager_->OnUsageRequestStatusSample(
MetricsManager::kUsageRequestStatusFailed);
return;
}
// interpret and validate parsed usage update
if (!parsed_usage_update->IsType(Value::TYPE_DICTIONARY)) {
LOG(WARNING) << path_
<< ": OnRequestComplete: root value is not a dictionary";
return;
}
const DictionaryValue *root =
static_cast<const DictionaryValue*>(parsed_usage_update);
int version;
if (!root->GetInteger(kCrosUsageVersionProperty, &version)) {
LOG(WARNING) << path_ << ": OnRequestComplete: no version property";
return;
}
LOG(INFO) << path_ << ": OnRequestComplete: version = " << version;
if (version < kCrosUsageVersionMinSupported ||
version > kCrosUsageVersionMaxSupported) {
LOG(WARNING) << path_ << ": OnRequestComplete: version " << version
<< " not in supported range of [" << kCrosUsageVersionMinSupported
<< ", " << kCrosUsageVersionMaxSupported << "]";
return;
}
std::string status;
if (!root->GetString(kCrosUsageStatusProperty, &status)) {
LOG(WARNING) << path_ << ": OnRequestComplete: no status property";
return;
}
LOG(INFO) << path_ << ": OnRequestComplete: status = " << status;
metrics_manager_->OnUsageRequestStatusSample(
MetricsEnumFromStatusString(status));
if (!IsValidCrosUsageStatus(status)) {
LOG(WARNING) << path_ << ": OnRequestComplete: invalid status: " << status;
return;
}
if (status != kCrosUsageStatusOk) {
LOG(WARNING) << path_ << ": OnRequestComplete: status: " << status;
OnCrosUsageErrorResult(status);
return;
}
bool restricted = false;
if (root->GetBoolean(kCrosUsageRestrictedProperty, &restricted)) {
LOG(INFO) << path_ << ": OnRequestComplete: restricted = " << restricted;
} else {
LOG(INFO) << path_ << ": OnRequestComplete: no restricted property";
// restricted property is optional
}
ListValue *plans = NULL; // list to which this points is owned by dictionary
if (!root->GetList(kCrosUsagePlansProperty, &plans)) {
LOG(WARNING) << path_ << ": OnRequestComplete: no plans property";
return;
}
DCHECK(plans != NULL);
size_t plans_size = plans->GetSize();
LOG(INFO) << path_ << ": OnRequestComplete: plans list has size "
<< plans_size;
// walk plans list and convert each into a DataPlan object
DataPlanList new_plans;
for (size_t i = 0; i < plans_size; ++i) {
DictionaryValue *plan_dict = NULL;
if (!plans->GetDictionary(i, &plan_dict)) {
LOG(WARNING) << path_
<< ": OnRequestComplete: could not get plan[" << i << "]";
continue;
}
DCHECK(plan_dict != NULL);
DataPlan *plan = DataPlan::FromDictionaryValue(plan_dict, policy_);
if (plan == NULL) {
LOG(WARNING) << path_
<< ": OnRequestComplete: could not convert plan[" << i << "]";
continue;
}
// set the service for |plan|
plan->SetService(this);
LOG(INFO) << path_ << ": OnRequestComplete: converted plan[" << i << "]";
new_plans.push_back(plan);
}
// store new info, causing it to be used for subsequent updates to clients
DataPlanList old_plans = data_plans_;
data_plans_ = new_plans;
LOG(INFO) << path_ << ": OnRequestComplete: updated data plans";
MaybeEmitDataPlansUpdate();
LOG(INFO) << path_ << ": OnRequestComplete: deleting old data plans";
DeleteDataPlans(&old_plans);
// check if we have any active data plans associated with this service
DataPlan *active_plan = DataPlan::GetActivePlan(data_plans_);
if (active_plan == NULL) {
LOG(INFO) << path_ << ": OnRequestComplete: no active plans";
return;
}
// we have a successfully completed usage API request
usage_request_complete_ = true;
StopSendingUsageRequests();
}
// Service Manager methods
void ServiceImpl::OnDefaultServiceUpdate(bool is_default_service) {
DCHECK(is_default_service_ == !is_default_service);
LOG(INFO) << path_ << ": OnDefaultServiceUpdate: is_default_service = "
<< is_default_service;
is_default_service_ = is_default_service;
if (is_default_service_) {
// we just became the default service
// cross-check with parent's idea of default technology
if (parent_->GetDefaultTechnology() != kTypeCellular) {
LOG(WARNING) << path_ << ": OnDefaultServiceUpdate: "
<< "service manager doesn't think default technology is cellular";
}
}
ReconsiderSendingUsageRequests();
}
// Private methods
// static
Service::State ServiceImpl::StateFromString(const std::string& state) {
if (state == kFlimflamServiceStateIdle) {
return kStateIdle;
}
if (state == kFlimflamServiceStateCarrier) {
return kStateCarrier;
}
if (state == kFlimflamServiceStateAssociation) {
return kStateAssociation;
}
if (state == kFlimflamServiceStateConfiguration) {
return kStateConfiguration;
}
if (state == kFlimflamServiceStateReady) {
return kStateReady;
}
if (state == kFlimflamServiceStatePortal) {
return kStatePortal;
}
if (state == kFlimflamServiceStateOnline) {
return kStateOnline;
}
if (state == kFlimflamServiceStateDisconnect) {
return kStateDisconnect;
}
if (state == kFlimflamServiceStateFailure) {
return kStateFailure;
}
if (state == kFlimflamServiceStateActivationFailure) {
return kStateActivationFailure;
}
return kStateUnknown;
}
void ServiceImpl::OnDeviceUpdate(const DBus::Path& device_path) {
LOG(INFO) << path_ << ": OnDeviceUpdate: device_path = " << device_path;
// if there's an existing device with a non-matching path: destroy it and
// fall through to make a new one below.
if (device_ != NULL && device_->GetPath() != device_path) {
LOG(WARNING) << path_ << ": OnDeviceUpdate: device path changed from "
<< device_->GetPath() << " to " << device_path;
delete device_;
device_ = NULL;
}
// if there's an existing device with matching path: all is well
if (device_ != NULL) {
return;
}
// if there's no existing device: make one
device_ = Device::NewDevice(this, connection_, device_path);
if (device_ == NULL) {
LOG(ERROR) << path_ << ": OnDeviceUpdate: couldn't create device for "
<< device_path;
return;
}
LOG(INFO) << path_ << ": OnDeviceUpdate: created device for "
<< device_path;
// start the byte counter for the new device
if (device_ != NULL && IsConnected() && !device_->StartByteCounter()) {
LOG(WARNING) << path_ << ": OnDeviceUpdate: could not start byte counter";
}
// we need to make a new request
usage_request_complete_ = false;
ReconsiderSendingUsageRequests();
}
void ServiceImpl::OnStateUpdate(const std::string& state) {
LOG(INFO) << path_ << ": OnStateUpdate: state = " << state;
Service::State old_state = state_;
state_ = StateFromString(state);
if (!IsConnectedState(old_state) && IsConnected()) {
OnConnected();
} else if (IsConnectedState(old_state) && !IsConnected()) {
OnDisconnected();
}
}
void ServiceImpl::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 ServiceImpl::OnUsageUrlUpdate(const std::string& usage_url) {
LOG(INFO) << path_ << ": OnUsageUrlUpdate: url = " << usage_url;
if (usage_url == usage_url_) {
return;
}
// we need to make a new request
usage_request_complete_ = false;
usage_url_ = usage_url;
if (provider_ != NULL) {
DCHECK(usage_url_ != provider_->GetUsageUrl());
StopSendingUsageRequests();
provider_->SetUsageUrl(usage_url_);
ReconsiderSendingUsageRequests();
}
}
void ServiceImpl::OnStickyHostRouteUpdate(const std::string& sticky_route) {
LOG(INFO) << path_ << ": OnStickyHostRouteUpdate: sticky route = "
<< sticky_route;
if (sticky_route == sticky_host_route_) {
return;
}
sticky_host_route_ = sticky_route;
ReconsiderSendingUsageRequests();
}
// static
gboolean ServiceImpl::StaticGetServicePropertiesCallback(gpointer data) {
ServiceImpl *service = reinterpret_cast<ServiceImpl*>(data);
CHECK_NOTNULL(service);
DCHECK_NE(service->get_properties_source_id_, 0);
if (!service->GetServiceProperties()) {
// call failed, so try again later
LOG(WARNING) << service->GetPath()
<< ": StaticGetServicePropertiesCallback: "
<< "GetServiceProperties failed, will retry in "
<< kGetServicePropertiesIntervalSeconds << " secs";
// if we've arrived here from our first g_idle_add call, set up a timer
if (!service->retrying_get_properties_) {
guint source_id =
g_timeout_add_seconds(kGetServicePropertiesIntervalSeconds,
StaticGetServicePropertiesCallback, service);
service->get_properties_source_id_ = source_id;
if (source_id == 0) {
LOG(ERROR) << service->GetPath()
<< ": StaticGetServicePropertiesCallback: "
<< "g_timeout_add_seconds failed";
return FALSE; // get rid of old glib source and give up
}
service->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;
}
service->get_properties_source_id_ = 0;
service->retrying_get_properties_ = false;
return FALSE; // we succeeded, so don't repeat
}
bool ServiceImpl::GetServiceProperties() {
LOG(INFO) << path_ << ": GetServiceProperties";
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_
<< ": GetServiceProperties: GetProperties() -> Exception: "
<< error.name() << ": " << error.message();
return false;
} catch (...) { // NOLINT
LOG(WARNING) << path_
<< ": GetServiceProperties: GetProperties() -> Exception";
return false;
}
LOG(INFO) << path_ << ": GetServiceProperties: Received "
<< properties.size() << " properties";
// grab the properties in which we're interested
PropertyMap::const_iterator it;
it = properties.find(kFlimflamServiceDeviceProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnDeviceUpdate(value.reader().get_path());
} else {
LOG(WARNING) << path_ << ": GetServiceProperties: no Device property";
}
it = properties.find(kFlimflamServiceStateProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnStateUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_ << ": GetServiceProperties: no State property";
}
it = properties.find(kFlimflamServiceTypeProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnTypeUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_ << ": GetServiceProperties: no Type property";
}
it = properties.find(kFlimflamServiceStickyHostRouteProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnStickyHostRouteUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_
<< ": GetServiceProperties: no StickyHostRoute property";
}
// don't expect to find Cellular.* properties if we're not a cellular service
if (type_ != kTypeCellular) {
return true;
}
it = properties.find(kFlimflamServiceUsageUrlProperty);
if (it != properties.end()) {
const DBus::Variant& value = static_cast<DBus::Variant>(it->second);
OnUsageUrlUpdate(value.reader().get_string());
} else {
LOG(WARNING) << path_
<< ": GetServiceProperties: no Cellular.UsageUrl property";
}
return true;
}
void ServiceImpl::DeleteDataPlans(DataPlanList *data_plans) {
DCHECK(data_plans != NULL);
while (!data_plans->empty()) {
DataPlan *plan = *data_plans->begin();
DCHECK(plan != NULL);
LOG(INFO) << path_ << ": DeleteDataPlans: deleting plan: "
<< plan->GetName();
delete plan;
data_plans->erase(data_plans->begin());
}
}
void ServiceImpl::AddHardcodedDataPlan() {
const std::string name = "Chromium OS Test Plan";
DataPlan::Type type = DataPlan::kTypeMeteredPaid;
base::Time update_time = base::Time::Now();
base::TimeDelta twelve_hours = base::TimeDelta::FromHours(12);
base::Time start_time = base::Time::Now() - twelve_hours;
base::Time end_time = base::Time::Now() + twelve_hours;
ByteCount data_bytes_max = 100*1024*1024; // 100 MB
ByteCount data_bytes_used = 30*1024*1024; // 30 MB
DataPlan *plan = new(std::nothrow) DataPlan(name, type, update_time,
start_time, end_time,
data_bytes_max, data_bytes_used);
if (plan == NULL) {
LOG(ERROR) << path_ << ": AddHardcodedDataPlan: could not create plan";
return;
}
data_plans_.push_back(plan);
}
void ServiceImpl::RequestUsageUpdate() {
DCHECK(ShouldSendUsageRequests());
if (request_in_progress_) {
LOG(WARNING) << path_
<< ": RequestUsageUpdate: request already in progress";
return;
}
request_in_progress_ = true;
if (!provider_->RequestUsageUpdate()) {
LOG(WARNING) << path_ << ": RequestUsageUpdate: request failed";
request_in_progress_ = false;
} else {
LOG(INFO) << path_ << ": RequestUsageUpdate: request succeeded";
}
}
void ServiceImpl::CancelPendingRequests() {
if (provider_ != NULL && request_in_progress_) {
provider_->CancelPendingRequests();
request_in_progress_ = false;
}
}
gboolean ServiceImpl::UpdateTimeoutCallback() {
LOG(INFO) << path_ << ": UpdateTimeoutCallback";
RequestUsageUpdate();
return TRUE;
}
// static
gboolean ServiceImpl::StaticUpdateTimeoutCallback(gpointer data) {
ServiceImpl *service = reinterpret_cast<ServiceImpl*>(data);
CHECK_NOTNULL(service);
return service->UpdateTimeoutCallback();
}
bool ServiceImpl::CreateUpdateTimer() {
DCHECK(provider_ != NULL);
DCHECK(policy_ != NULL);
guint idle_seconds = policy_->GetUpdateTimerIdleSecs(data_plans_);
LOG(INFO) << path_ << ": CreateUpdateTimer: idle_seconds = " << idle_seconds;
if (update_timeout_source_ != NULL) {
LOG(WARNING) << path_ << ": CreateUpdateTimer: timer already running";
return false;
}
update_timeout_source_ = g_timeout_source_new_seconds(idle_seconds);
if (update_timeout_source_ == NULL) {
LOG(WARNING) << path_
<< ": CreateUpdateTimer: could not create timeout source";
return false;
}
g_source_set_callback(update_timeout_source_, StaticUpdateTimeoutCallback,
this, NULL);
g_source_attach(update_timeout_source_, NULL);
return true;
}
void ServiceImpl::DestroyUpdateTimer() {
LOG(INFO) << path_ << ": DestroyUpdateTimer";
if (update_timeout_source_ != NULL) {
g_source_destroy(update_timeout_source_);
update_timeout_source_ = NULL;
}
}
void ServiceImpl::DeleteCarrierState() {
LOG(INFO) << path_ << ": DeleteCarrierState";
if (policy_ != NULL) {
LOG(INFO) << path_ << ": DeleteCarrierState: deleting policy";
delete policy_;
policy_ = NULL;
}
StopSendingUsageRequests();
if (provider_ != NULL) {
LOG(INFO) << path_ << ": DeleteCarrierState: deleting data plan provider";
delete provider_;
provider_ = NULL;
}
}
bool ServiceImpl::IsValidCrosUsageStatus(const std::string& status) {
if (status == kCrosUsageStatusOk ||
status == kCrosUsageStatusError ||
status == kCrosUsageStatusMalformedRequest ||
status == kCrosUsageStatusInternalError ||
status == kCrosUsageStatusServiceUnavailable ||
status == kCrosUsageStatusRequestRefused ||
status == kCrosUsageStatusUnknownDevice) {
return true;
}
return false;
}
MetricsManager::UsageRequestStatus ServiceImpl::MetricsEnumFromStatusString(
const std::string& status) {
DCHECK(IsValidCrosUsageStatus(status));
if (status == kCrosUsageStatusOk) {
return MetricsManager::kUsageRequestStatusOk;
}
if (status == kCrosUsageStatusError) {
return MetricsManager::kUsageRequestStatusError;
}
if (status == kCrosUsageStatusMalformedRequest) {
return MetricsManager::kUsageRequestStatusMalformedRequest;
}
if (status == kCrosUsageStatusInternalError) {
return MetricsManager::kUsageRequestStatusInternalError;
}
if (status == kCrosUsageStatusServiceUnavailable) {
return MetricsManager::kUsageRequestStatusServiceUnavailable;
}
if (status == kCrosUsageStatusRequestRefused) {
return MetricsManager::kUsageRequestStatusRequestRefused;
}
if (status == kCrosUsageStatusUnknownDevice) {
return MetricsManager::kUsageRequestStatusUnknownDevice;
}
LOG(WARNING) << "MetricsEnumFromStatusString: Unknown status: " << status;
return MetricsManager::kUsageRequestStatusError;
}
void ServiceImpl::OnCrosUsageErrorResult(const std::string& status) {
LOG(WARNING) << path_ << ": OnCrosUsageErrorResult: " << status;
// TODO(vlaviano): hook for future use
}
// static
bool ServiceImpl::IsConnectedState(State state) {
return (state == kStateReady || state == kStateOnline);
}
bool ServiceImpl::IsConnected() const {
return IsConnectedState(state_);
}
void ServiceImpl::OnConnected() {
LOG(INFO) << path_ << ": OnConnected";
DCHECK(IsConnected());
DCHECK(!usage_request_complete_);
// start byte counter
if (device_ != NULL && !device_->ByteCounterRunning() &&
!device_->StartByteCounter()) {
LOG(WARNING) << path_ << ": OnConnected: could not start byte counter";
}
ReconsiderSendingUsageRequests();
}
void ServiceImpl::OnDisconnected() {
LOG(INFO) << path_ << ": OnDisconnected";
DCHECK(!IsConnected());
// stop byte counter
if (device_ != NULL) {
device_->StopByteCounter();
}
// we'll issue a new usage API request when we reconnect
usage_request_complete_ = false;
ReconsiderSendingUsageRequests();
}
bool ServiceImpl::IsSendingUsageRequests() const {
return request_in_progress_ || update_timeout_source_ != NULL;
}
bool ServiceImpl::ShouldSendUsageRequests() const {
if (usage_request_complete_) {
LOG(INFO) << path_ <<
": ShouldSendUsageRequests: no: request already completed";
return false;
}
if (provider_ == NULL) {
LOG(INFO) << path_ << ": ShouldSendUsageRequests: no: no provider";
DCHECK(policy_ == NULL);
return false;
}
DCHECK(device_ != NULL);
DCHECK(!device_->GetCarrier().empty());
DCHECK(policy_ != NULL);
if (usage_url_.empty()) {
LOG(INFO) << path_ << ": ShouldSendUsageRequests: no: no usage url";
return false;
}
if (!IsConnected()) {
LOG(INFO) << path_ << ": ShouldSendUsageRequests: no: not connected";
return false;
}
if (sticky_host_route_.empty() && !IsDefaultService()) {
LOG(INFO) << path_ << ": ShouldSendUsageRequests: "
<< "no: no sticky host route and not default service";
return false;
}
LOG(INFO) << path_ << ": ShouldSendUsageRequests: yes";
return true;
}
void ServiceImpl::StopSendingUsageRequests() {
LOG(INFO) << path_ << ": StopSendingUsageRequests";
DestroyUpdateTimer();
CancelPendingRequests();
}
void ServiceImpl::StartSendingUsageRequests() {
LOG(INFO) << path_ << ": StartSendingUsageRequests";
DCHECK(!IsSendingUsageRequests());
RequestUsageUpdate();
CreateUpdateTimer();
}
void ServiceImpl::ReconsiderSendingUsageRequests() {
LOG(INFO) << path_ << ": ReconsiderSendingUsageRequests";
if (!IsSendingUsageRequests() && ShouldSendUsageRequests()) {
StartSendingUsageRequests();
} else if (IsSendingUsageRequests() && !ShouldSendUsageRequests()) {
StopSendingUsageRequests();
}
}
void ServiceImpl::MaybeEmitDataPlansUpdate() {
DCHECK(policy_ != NULL);
if (policy_->ShouldEmitDataPlansUpdate(data_plans_)) {
LOG(INFO) << path_ << ": MaybeEmitDataPlansUpdate: sending update";
parent_->EmitDataPlansUpdate(*this);
} else {
LOG(INFO) << path_ << ": MaybeEmitDataPlansUpdate: not sending update";
}
}
} // namespace cashew