blob: d8801fab75490578dc45e08e9ed11771751f27ab [file] [log] [blame]
// Copyright (c) 2012 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/data_plan.h"
#include <time.h>
#include <glog/logging.h>
#include "src/device.h"
#include "src/policy.h"
#include "src/service_manager.h"
namespace cashew {
// libcros CellularDataPlan property names
// (what we send to libcros)
static const char *kCellularDataPlanName = "CellularPlanName";
static const char *kCellularDataPlanType = "CellularPlanType";
static const char *kCellularDataPlanUpdateTime = "CellularPlanUpdateTime";
static const char *kCellularDataPlanStartTime = "CellularPlanStart";
static const char *kCellularDataPlanEndTime = "CellularPlanEnd";
static const char *kCellularDataPlanDataBytesMax = "CellularPlanDataBytes";
static const char *kCellularDataPlanDataBytesUsed = "CellularDataBytesUsed";
// libcros CellularDataPlan type values
static const char *kCellularDataPlanTypeUnlimited = "UNLIMITED";
static const char *kCellularDataPlanTypeMeteredFree = "METERED_BASE";
static const char *kCellularDataPlanTypeMeteredPaid = "METERED_PAID";
// Chromium OS Usage API Data Plan property names
// (what we receive from carrier usage API)
static const char *kCrosUsageDataPlanLastUpdateProperty = "lastUpdate";
static const char *kCrosUsageDataPlanNameProperty = "planName";
static const char *kCrosUsageDataPlanTypeProperty = "planType";
static const char *kCrosUsageDataPlanMaxBytesProperty = "maxBytes";
static const char *kCrosUsageDataPlanUsedBytesProperty = "usedBytes";
static const char *kCrosUsageDataPlanEndTimeProperty = "expirationTime";
static const char *kCrosUsageDataPlanStartTimeProperty = "startTime";
// Chromium OS Usage API Data Plan type values
static const char *kCrosUsageDataPlanTypeUnlimited = "UNLIMITED";
static const char *kCrosUsageDataPlanTypeMeteredFree = "METERED_FREE";
static const char *kCrosUsageDataPlanTypeMeteredPaid = "METERED_PAID";
DataPlan::DataPlan(const std::string& name, DataPlan::Type type,
base::Time update_time, base::Time start_time,
base::Time end_time, ByteCount data_bytes_max,
ByteCount data_bytes_used)
: service_(NULL), name_(name), type_(type), update_time_(update_time),
start_time_(start_time), end_time_(end_time),
data_bytes_max_(data_bytes_max), data_bytes_used_(data_bytes_used),
local_bytes_used_(0), total_bytes_used_(data_bytes_used),
expiration_timeout_source_(NULL) {
CHECK_GE(data_bytes_max_, 0);
CHECK_GE(data_bytes_used_, 0);
// create and start the expiration timer
if (!CreateExpirationTimer()) {
LOG(WARNING) << "ctor: failed to create and start expiration timer";
}
}
DataPlan::~DataPlan() {
// destroy the expiration timer
DestroyExpirationTimer();
}
const std::string& DataPlan::GetName() const {
return name_;
}
DataPlan::Type DataPlan::GetType() const {
return type_;
}
base::Time DataPlan::GetUpdateTime() const {
return update_time_;
}
base::Time DataPlan::GetStartTime() const {
return start_time_;
}
base::Time DataPlan::GetEndTime() const {
return end_time_;
}
ByteCount DataPlan::GetDataBytesMax() const {
return data_bytes_max_;
}
ByteCount DataPlan::GetDataBytesUsed() const {
return data_bytes_used_;
}
ByteCount DataPlan::GetLocalBytesUsed() const {
return local_bytes_used_;
}
ByteCount DataPlan::GetTotalBytesUsed() const {
return total_bytes_used_;
}
void DataPlan::SetService(Service *service) {
CHECK(service != NULL);
service_ = service;
}
void DataPlan::SetLocalBytesUsed(ByteCount local_bytes_used) {
CHECK_GE(local_bytes_used, 0);
local_bytes_used_ = local_bytes_used;
total_bytes_used_ = data_bytes_used_ + local_bytes_used_;
if (total_bytes_used_ < 0) {
LOG(WARNING) << "SetLocalBytesUsed: overflow detected: |total_bytes_used|";
total_bytes_used_ = kint64max;
}
// if we run out of bytes, stop expiration timer
if (!HasBytesRemaining()) {
DestroyExpirationTimer();
}
}
bool DataPlan::IsActive() const {
// is the plan current and not exahusted?
return IsCurrent() && HasBytesRemaining();
}
DBusDataPlan DataPlan::ToDBusFormat() const {
DBusDataPlan plan;
// Indexing into map w/ nonexistent key causes empty DBus::Variant to be
// created and inserted. We then fill in the desired value via its writer
// DBus::MessageIter.
plan[kCellularDataPlanName].writer().append_string(name_.c_str());
plan[kCellularDataPlanType].writer().append_string(
TypeToLibcrosString(type_));
plan[kCellularDataPlanUpdateTime].writer().append_int64(
update_time_.ToInternalValue());
plan[kCellularDataPlanStartTime].writer().append_int64(
start_time_.ToInternalValue());
plan[kCellularDataPlanEndTime].writer().append_int64(
end_time_.ToInternalValue());
// omit max bytes field for unlimited plans
if (type_ != kTypeUnlimited) {
plan[kCellularDataPlanDataBytesMax].writer().append_int64(data_bytes_max_);
}
// send our best estimate of data bytes used
// this is the baseline, if any, that we received from the carrier usage API
// plus any local traffic that we've measured since then
plan[kCellularDataPlanDataBytesUsed].writer().append_int64(total_bytes_used_);
return plan;
}
// static
DataPlan* DataPlan::FromDictionaryValue(const DictionaryValue *value,
const Policy *policy) {
CHECK_NOTNULL(value);
CHECK_NOTNULL(policy);
std::string last_update_iso8601;
if (!value->GetString(kCrosUsageDataPlanLastUpdateProperty,
&last_update_iso8601)) {
LOG(WARNING) << "FromDictionaryValue: no last update property";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: last update = " << last_update_iso8601;
base::Time last_update;
bool local = policy->ZonelessTimeStringsAreLocal();
if (!TimeFromIso8601(last_update_iso8601, &last_update, local)) {
LOG(WARNING) << "FromDictionaryValue: could not convert last update time";
return NULL;
}
std::string plan_name;
if (!value->GetString(kCrosUsageDataPlanNameProperty, &plan_name)) {
LOG(WARNING) << "FromDictionaryValue: no plan name property";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: plan name = " << plan_name;
std::string plan_type_string;
if (!value->GetString(kCrosUsageDataPlanTypeProperty, &plan_type_string)) {
LOG(WARNING) << "FromDictionaryValue: no plan type property";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: plan type string = " << plan_type_string;
Type plan_type;
if (!CrosUsageStringToType(plan_type_string, &plan_type)) {
LOG(WARNING) << "FromDictionaryValue: invalid plan type string: "
<< plan_type_string;
return NULL;
}
LOG(INFO) << "FromDictionaryValue: converted plan type string to type "
<< plan_type;
int64 max_bytes = 0;
// we only expect max bytes for metered plans
if (plan_type != kTypeUnlimited) {
if (!GetInt64FromDictionary(*value, kCrosUsageDataPlanMaxBytesProperty,
&max_bytes)) {
LOG(WARNING) << "FromDictionaryValue: no max bytes property";
return NULL;
}
if (max_bytes < 0) {
LOG(WARNING) << "FromDictionaryValue: max bytes is negative";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: max bytes = " << max_bytes;
}
int64 used_bytes = 0;
if (!GetInt64FromDictionary(*value, kCrosUsageDataPlanUsedBytesProperty,
&used_bytes)) {
// used bytes is required for metered plans, optional for unlimited plans
if (plan_type != kTypeUnlimited) {
LOG(WARNING) << "FromDictionaryValue: no used bytes property";
return NULL;
} else {
LOG(INFO)
<< "FromDictionaryValue: no used bytes property (using default of 0)";
}
}
if (used_bytes < 0) {
LOG(WARNING) << "FromDictionaryValue: used bytes is negative";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: used bytes = " << used_bytes;
std::string start_time_iso8601;
if (!value->GetString(kCrosUsageDataPlanStartTimeProperty,
&start_time_iso8601)) {
LOG(WARNING) << "FromDictionaryValue: no start time property";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: start time = " << start_time_iso8601;
base::Time start_time;
if (!TimeFromIso8601(start_time_iso8601, &start_time, local)) {
LOG(WARNING) << "FromDictionaryValue: could not convert start time";
return NULL;
}
std::string end_time_iso8601;
if (!value->GetString(kCrosUsageDataPlanEndTimeProperty,
&end_time_iso8601)) {
LOG(WARNING) << "FromDictionaryValue: no end time property";
return NULL;
}
LOG(INFO) << "FromDictionaryValue: end time = " << end_time_iso8601;
base::Time end_time;
if (!TimeFromIso8601(end_time_iso8601, &end_time, local)) {
LOG(WARNING) << "FromDictionaryValue: could not convert end time";
return NULL;
}
if (start_time > end_time) {
LOG(WARNING) << "FromDictionaryValue: start time > end time";
return NULL;
}
return new(std::nothrow) DataPlan(plan_name, plan_type, last_update,
start_time, end_time, max_bytes,
used_bytes);
}
// static
bool DataPlan::TimeFromIso8601(const std::string& time_8601,
base::Time *time_out,
bool zoneless_strings_are_local) {
CHECK_NOTNULL(time_out);
static const char *kIso8601UtcFormat = "%Y-%m-%dT%H:%M:%SZ";
static const char *kIso8601ZonelessFormat = "%Y-%m-%dT%H:%M:%S";
struct tm tm;
time_t tt;
bool tm_is_utc;
// convert from ISO 8601 string to struct tm
// first try the expected format (ISO 8601 with UTC 'Z' suffix)
// if that fails, try format with no timezone suffix
// we'll interpret this as either UTC or local time based on input flag
// TODO(vlaviano): support +/-nn:nn timezone specifier
memset(&tm, 0, sizeof(tm));
if (strptime(time_8601.c_str(), kIso8601UtcFormat, &tm) != NULL) {
tm_is_utc = true;
} else if (strptime(time_8601.c_str(), kIso8601ZonelessFormat, &tm) != NULL) {
if (zoneless_strings_are_local) {
tm_is_utc = false;
} else {
tm_is_utc = true;
}
} else {
LOG(WARNING) << "TimeFromIso8601: strptime failed for time string: "
<< time_8601;
return false;
}
tm.tm_isdst = -1;
// convert from struct tm to time_t
if (tm_is_utc) {
// NOTE: timegm interprets its input as being in UTC.
if ((tt = timegm(&tm)) == -1) {
LOG(WARNING) << "TimeFromIso8601: timegm failed";
return false;
}
} else {
// NOTE: mktime interprets its input as being in local time.
if ((tt = mktime(&tm)) == -1) {
LOG(WARNING) << "TimeFromIso8601: mktime failed";
return false;
}
}
// convert from time_t to base::Time
*time_out = base::Time::FromTimeT(tt);
return true;
}
// static
DataPlan* DataPlan::GetActivePlan(const DataPlanList& data_plans) {
DataPlanList::const_iterator it;
for (it = data_plans.begin(); it != data_plans.end(); ++it) {
DataPlan *plan = *it;
DCHECK(plan != NULL);
if (plan->IsActive()) {
return plan;
}
}
return NULL;
}
// static
ByteCount DataPlan::AssignBytesToPlan(DataPlan *data_plan,
ByteCount bytes_to_assign) {
CHECK(data_plan != NULL);
CHECK_GE(bytes_to_assign, 0);
// we cannot assign any bytes to |data_plan| if it is inactive
if (!data_plan->IsActive()) {
LOG(INFO) << "AssignBytesToPlan: cannot assign bytes to inactive plan: "
<< data_plan->GetName();
return 0;
}
ByteCount assigned_bytes = 0;
ByteCount data_remaining = 0; // relevant for metered plans
if (data_plan->GetType() != DataPlan::kTypeUnlimited) {
data_remaining = data_plan->GetDataBytesMax() -
data_plan->GetTotalBytesUsed();
CHECK_GE(data_remaining, 0);
}
if (data_plan->GetType() == DataPlan::kTypeUnlimited ||
bytes_to_assign < data_remaining) {
// can assign all the bytes to |data_plan|
assigned_bytes = bytes_to_assign;
} else {
assigned_bytes = data_remaining;
}
CHECK_GE(assigned_bytes, 0);
data_plan->SetLocalBytesUsed(data_plan->GetLocalBytesUsed() + assigned_bytes);
LOG(INFO) << "AssignBytesToPlan: " << data_plan->GetName()
<< ": updated plan state: data bytes used = "
<< data_plan->GetDataBytesUsed() << ", local bytes used = "
<< data_plan->GetLocalBytesUsed() << ", total bytes used = "
<< data_plan->GetTotalBytesUsed();
return assigned_bytes;
}
// static
bool DataPlan::OnByteCounterUpdate(DataPlanList *data_plans, Service *service,
ServiceManager *service_manager,
Device *device, uint64 delta_rx_bytes,
uint64 delta_tx_bytes) {
DCHECK(data_plans != NULL);
DCHECK(service != NULL);
DCHECK(service_manager != NULL);
DCHECK(device != NULL);
DCHECK(device->ByteCounterRunning());
LOG(INFO) << "OnByteCounterUpdate: delta_rx_bytes = " << delta_rx_bytes
<< ", delta_tx_bytes = " << delta_tx_bytes;
DataPlan *active_plan = DataPlan::GetActivePlan(*data_plans);
if (active_plan == NULL) {
LOG(WARNING) << "OnByteCounterUpdate: no active plan";
return false;
}
ByteCount used_bytes_to_assign = delta_rx_bytes + delta_tx_bytes;
// try to detect two error conditions:
// (1) overflow of the unsigned addition above prior to the assignment
// (2) no overflow, but result has high order bit set and so is interpreted as
// a negative number when assigned to |local_bytes_used|
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) << "OnByteCounterUpdate: overflow detected:"
" |used_bytes_to_assign|";
return false;
}
LOG(INFO) << "OnByteCounterUpdate: used_bytes_to_assign = "
<< used_bytes_to_assign;
// iterate through active plans, assigning as much data as possible to each
// plan and expiring it if its data quota is fully consumed
bool any_plan_updated = false;
while (used_bytes_to_assign > 0 && active_plan != NULL) {
ByteCount bytes_assigned_to_active_plan = AssignBytesToPlan(active_plan,
used_bytes_to_assign);
CHECK_LE(bytes_assigned_to_active_plan, used_bytes_to_assign);
if (bytes_assigned_to_active_plan > 0) {
used_bytes_to_assign -= bytes_assigned_to_active_plan;
any_plan_updated = true;
}
if (!active_plan->HasBytesRemaining()) {
// notify service manager that plan has become exhausted
service_manager->OnDataPlanInactive(*service, *active_plan);
active_plan = DataPlan::GetActivePlan(*data_plans);
}
}
// if we have no more active plans, stop our local counter
if (active_plan == NULL) {
device->StopByteCounter();
// warn if we've measured usage beyond what should have been available
if (used_bytes_to_assign > 0) {
LOG(WARNING) << "OnByteCounterUpdate: used_bytes_to_assign = "
<< used_bytes_to_assign << ", but no active plan";
}
}
return any_plan_updated;
}
// private methods
bool DataPlan::IsCurrent() const {
base::Time now = base::Time::Now();
return now >= start_time_ && now < end_time_;
}
bool DataPlan::HasBytesRemaining() const {
return type_ == kTypeUnlimited || total_bytes_used_ < data_bytes_max_;
}
void DataPlan::OnDataPlanExpired() {
CHECK(service_ != NULL);
if (!service_->OnDataPlanExpired(this)) {
LOG(WARNING) << "OnDataPlanExpired: failed to handle expired data plan";
}
}
// static
gboolean DataPlan::StaticOnDataPlanExpired(gpointer data) {
DataPlan *data_plan = reinterpret_cast<DataPlan*>(data);
CHECK(data_plan != NULL);
data_plan->OnDataPlanExpired();
return FALSE; // don't call back
}
bool DataPlan::CreateExpirationTimer() {
if (expiration_timeout_source_ != NULL) {
LOG(WARNING) << "CreateExpirationTimer: timer already running";
return false;
}
base::TimeDelta timeout = end_time_ - base::Time::Now();
expiration_timeout_source_ = g_timeout_source_new_seconds(
timeout.InSeconds());
if (expiration_timeout_source_ == NULL) {
LOG(WARNING) << "CreateExpirationTimer: failed to create timeout source";
return false;
}
g_source_set_callback(expiration_timeout_source_, StaticOnDataPlanExpired,
this, NULL);
if (g_source_attach(expiration_timeout_source_, NULL) == 0) {
LOG(WARNING) << "CreateExpirationTimer: failed to attach timeout source";
return false;
}
return true;
}
void DataPlan::DestroyExpirationTimer() {
if (expiration_timeout_source_ != NULL) {
g_source_destroy(expiration_timeout_source_);
expiration_timeout_source_ = NULL;
}
}
const char* DataPlan::TypeToLibcrosString(DataPlan::Type type) const {
switch (type) {
case kTypeUnlimited:
return kCellularDataPlanTypeUnlimited;
case kTypeMeteredFree:
return kCellularDataPlanTypeMeteredFree;
case kTypeMeteredPaid:
return kCellularDataPlanTypeMeteredPaid;
default:
CHECK(false);
}
}
// static
bool DataPlan::CrosUsageStringToType(const std::string& type_string,
DataPlan::Type *type_out) {
CHECK_NOTNULL(type_out);
if (type_string == kCrosUsageDataPlanTypeUnlimited) {
*type_out = kTypeUnlimited;
} else if (type_string == kCrosUsageDataPlanTypeMeteredFree) {
*type_out = kTypeMeteredFree;
} else if (type_string == kCrosUsageDataPlanTypeMeteredPaid) {
*type_out = kTypeMeteredPaid;
} else {
return false;
}
return true;
}
// static
bool DataPlan::GetInt64FromDictionary(const DictionaryValue& dict,
const std::string& key,
int64* out_value) {
CHECK_NOTNULL(out_value);
int generic_int_out = 0;
if (dict.GetInteger(key, &generic_int_out)) {
*out_value = generic_int_out;
return true;
}
double generic_double_out = 0;
if (dict.GetDouble(key, &generic_double_out)) {
*out_value = generic_double_out;
return true;
}
return false;
}
} // namespace cashew