cashew: reset local counter on plan transition

The local counter fails to estimate data usage correctly if it is active
during a transition from one plan to another. This CL is the first of
two that will resolve this issue. It addresses plan transitions due to
data quota exhaustion. The subsequent CL will address plan transitions
due to the expiration time having passed.

Changes:
- Add the ability to reset the local byte counter.
- Update OnByteCounterUpdate event handler to iterate over all active
  plans and assign measured data traffic appropriately to each plan in
  turn. If a plan is consumed, any overage is applied to the next active
  plan and the local byte counter is reset accordingly. This continues
  until we're either out of data to assign or out of active plans.
- Move the bulk of the ServiceImpl::OnByteCounterUpdate logic to the
  DataPlan class to isolate it for unit testing.

BUG=chromium-os:9860
TEST=unit tests, manual testing on device

Change-Id: Ibfe2ee15eb6b79cb4f6575f289629be8388e12f7

Review URL: http://codereview.chromium.org/6250171
diff --git a/src/byte_counter.h b/src/byte_counter.h
index f934607..d225d06 100644
--- a/src/byte_counter.h
+++ b/src/byte_counter.h
@@ -23,9 +23,11 @@
 
     // received bytes
     virtual uint64 GetRxBytes() const = 0;
+    virtual void SetRxBytes(uint64 rx_bytes) = 0;
 
     // transmitted bytes
     virtual uint64 GetTxBytes() const = 0;
+    virtual void SetTxBytes(uint64 tx_bytes) = 0;
 
     // set delegate interface that will receive counter updates
     // it's ok to clear this by setting it to NULL
@@ -37,7 +39,7 @@
     static ByteCounter* NewByteCounter(const std::string& interface);
 };
 
-// interface for delegates
+// interface to be implemented by delegates
 class ByteCounterDelegate {
   public:
     virtual ~ByteCounterDelegate() {}
diff --git a/src/data_plan.cc b/src/data_plan.cc
index 29a23d1..0be2daa 100644
--- a/src/data_plan.cc
+++ b/src/data_plan.cc
@@ -8,7 +8,9 @@
 
 #include <glog/logging.h>
 
+#include "src/device.h"
 #include "src/policy.h"
+#include "src/service_manager.h"
 
 namespace cashew {
 
@@ -44,8 +46,8 @@
 
 DataPlan::DataPlan(const std::string& name, DataPlan::Type type,
                    base::Time update_time, base::Time start_time,
-                   base::Time end_time, Bytes data_bytes_max,
-                   Bytes data_bytes_used)
+                   base::Time end_time, ByteCount data_bytes_max,
+                   ByteCount data_bytes_used)
     : 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),
@@ -77,23 +79,25 @@
   return end_time_;
 }
 
-Bytes DataPlan::GetDataBytesMax() const {
+ByteCount DataPlan::GetDataBytesMax() const {
   return data_bytes_max_;
 }
 
-Bytes DataPlan::GetDataBytesUsed() const {
+ByteCount DataPlan::GetDataBytesUsed() const {
   return data_bytes_used_;
 }
 
-Bytes DataPlan::GetLocalBytesUsed() const {
+ByteCount DataPlan::GetLocalBytesUsed() const {
   return local_bytes_used_;
 }
 
-Bytes DataPlan::GetTotalBytesUsed() const {
+ByteCount DataPlan::GetTotalBytesUsed() const {
   return total_bytes_used_;
 }
 
-void DataPlan::SetLocalBytesUsed(Bytes local_bytes_used) {
+// TODO(vlaviano): ensure that this method stops the plan's expiration timer
+// (when it is implemented in a future CL) if the plan is fully consumed
+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_;
@@ -316,6 +320,105 @@
   return NULL;
 }
 
+// static
+bool DataPlan::OnByteCounterUpdate(DataPlanList *data_plans, Service *service,
+                                   ServiceManager *service_manager,
+                                   Device *device, uint64 rx_bytes,
+                                   uint64 tx_bytes) {
+  DCHECK(data_plans != NULL);
+  DCHECK(service != NULL);
+  DCHECK(service_manager != NULL);
+  DCHECK(device != NULL);
+  DCHECK(device->ByteCounterRunning());
+  DLOG(INFO) << "OnByteCounterUpdate: rx_bytes = " << rx_bytes
+      << ", tx_bytes = " << tx_bytes;
+  DataPlan *active_plan = DataPlan::GetActivePlan(*data_plans);
+  if (active_plan == NULL) {
+    DLOG(WARNING) << "OnByteCounterUpdate: no active plan";
+    device->StopByteCounter();
+    return false;
+  }
+  ByteCount local_bytes_used = rx_bytes + tx_bytes;
+  DLOG(INFO) << "OnByteCounterUpdate: local_bytes_used = " << local_bytes_used;
+  // 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 (local_bytes_used < 0 ||
+      static_cast<uint64>(local_bytes_used) < rx_bytes ||
+      static_cast<uint64>(local_bytes_used) < tx_bytes) {
+    LOG(WARNING) << "OnByteCounterUpdate: overflow detected";
+    return false;
+  }
+  // compute delta since last sample
+  // this delta represents the data usage that we need to assign to active plans
+  // this computation relies on the local counter being based on the currently
+  //   active plan
+  // TODO(vlaviano): Currently, this is not always true. It's possible for the
+  //   local byte counter to be associated with a plan that was active at the
+  //   beginning of the sample interval but has now expired. A future CL will
+  //   add expiration timers to plan objects to handle this case.
+  ByteCount previous_local_bytes_used = active_plan->GetLocalBytesUsed();
+  DLOG(INFO) << "OnByteCounterUpdate: previous_local_bytes_used = "
+      << previous_local_bytes_used;
+  DCHECK_LE(previous_local_bytes_used, local_bytes_used);
+  ByteCount used_bytes_to_assign = local_bytes_used - previous_local_bytes_used;
+  DLOG(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
+  ByteCount assigned_bytes = 0;
+  bool any_plan_updated = false;
+  while (used_bytes_to_assign > 0 && active_plan != NULL) {
+    ByteCount active_plan_data_remaining = 0;  // relevant for metered plans
+    if (active_plan->GetType() != DataPlan::kTypeUnlimited) {
+      active_plan_data_remaining = active_plan->GetDataBytesMax() -
+          active_plan->GetTotalBytesUsed();
+      DCHECK_GE(active_plan_data_remaining, 0);
+    }
+    if (active_plan->GetType() == DataPlan::kTypeUnlimited ||
+        used_bytes_to_assign < active_plan_data_remaining) {
+      // can assign all data from this sample and plan not fully consumed
+      assigned_bytes = used_bytes_to_assign;
+      used_bytes_to_assign = 0;
+    } else {
+      // plan fully consumed, and there might be an overage remaining to be
+      // assigned to the next active plan
+      assigned_bytes = active_plan_data_remaining;
+      used_bytes_to_assign -= assigned_bytes;
+    }
+    DCHECK_GE(assigned_bytes, 0);
+    active_plan->SetLocalBytesUsed(previous_local_bytes_used + assigned_bytes);
+    any_plan_updated = true;
+    DLOG(INFO) << "OnByteCounterUpdate: " << active_plan->GetName()
+        << ": updated plan state: data bytes used = "
+        << active_plan->GetDataBytesUsed() << ", local bytes used = "
+        << active_plan->GetLocalBytesUsed() << ", total bytes used = "
+        << active_plan->GetTotalBytesUsed();
+
+    if (!active_plan->IsActive()) {
+      // notify service manager that plan has become inactive
+      service_manager->OnDataPlanInactive(*service, *active_plan);
+
+      // we've consumed a plan, so reset byte counter to only reflect overage
+      // TODO(vlaviano): byte counter api shouldn't expose rx/tx split.
+      device->ResetByteCounter(used_bytes_to_assign, 0);
+      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) {
+      DLOG(WARNING) << "OnByteCounterUpdate: used_bytes_to_assign = "
+          << used_bytes_to_assign << ", but no active plan";
+    }
+  }
+  return any_plan_updated;
+}
+
 // private methods
 
 const char* DataPlan::TypeToLibcrosString(DataPlan::Type type) const {
diff --git a/src/data_plan.h b/src/data_plan.h
index 4a1fe50..701118e 100644
--- a/src/data_plan.h
+++ b/src/data_plan.h
@@ -20,11 +20,14 @@
 namespace cashew {
 
 class DataPlan;
+class Device;
 class Policy;
+class Service;
+class ServiceManager;
 
 // byte count
 // TODO(vlaviano): this should be a uint64, but libcros/Chrome expects this
-typedef int64 Bytes;
+typedef int64 ByteCount;
 
 // List of DataPlan objects
 // TODO(vlaviano): consider promoting this from a typedef to a class
@@ -49,7 +52,8 @@
     // |data_bytes_max| must be set to 0 for unlimited plans
     DataPlan(const std::string& name, DataPlan::Type type,
              base::Time update_time, base::Time start_time,
-             base::Time end_time, Bytes data_bytes_max, Bytes data_bytes_used);
+             base::Time end_time, ByteCount data_bytes_max,
+             ByteCount data_bytes_used);
     virtual ~DataPlan();
 
     // get plan name
@@ -68,20 +72,20 @@
     virtual base::Time GetEndTime() const;
 
     // get plan max bytes
-    virtual Bytes GetDataBytesMax() const;
+    virtual ByteCount GetDataBytesMax() const;
 
     // get plan used bytes
-    virtual Bytes GetDataBytesUsed() const;
+    virtual ByteCount GetDataBytesUsed() const;
 
     // get local used bytes
-    virtual Bytes GetLocalBytesUsed() const;
+    virtual ByteCount GetLocalBytesUsed() const;
 
     // set local used bytes
     // must be >= 0
-    virtual void SetLocalBytesUsed(Bytes local_bytes_used);
+    virtual void SetLocalBytesUsed(ByteCount local_bytes_used);
 
     // get total used bytes
-    virtual Bytes GetTotalBytesUsed() const;
+    virtual ByteCount GetTotalBytesUsed() const;
 
     // is this an active plan?
     // we define active to mean that the plan is current and not exhausted
@@ -105,6 +109,17 @@
     // returns NULL if list is empty or contains no active plans
     static DataPlan* GetActivePlan(const DataPlanList& data_plans);
 
+    // event handler for |service's| local byte counter on |device|
+    // updates active plans in |data_plans| based on the |rx_bytes| and
+    //   |tx_bytes| values gathered during the most recent sample interval
+    // notifies |service_manager| if an active plan is completely consumed
+    // returns true if any plans were updated and false otherwise
+    // |data_plans|, |service|, |service_manager| and |device| must not be NULL
+    static bool OnByteCounterUpdate(DataPlanList *data_plans, Service *service,
+                                    ServiceManager *service_manager,
+                                    Device *device, uint64 rx_bytes,
+                                    uint64 tx_bytes);
+
   private:
     // human-readable plan name
     const std::string name_;
@@ -124,20 +139,20 @@
 
     // max bytes available during plan period
     // irrelevant for plans of type kTypeUnlimited
-    const Bytes data_bytes_max_;
+    const ByteCount data_bytes_max_;
 
     // bytes consumed so far during plan period
     // this measurement comes from the carrier API
-    Bytes data_bytes_used_;
+    ByteCount data_bytes_used_;
 
     // local measurement of bytes used since we received |data_bytes_used_|
     // baseline from the carrier API
-    Bytes local_bytes_used_;
+    ByteCount local_bytes_used_;
 
     // our best estimate of total bytes used for this plan
     // carrier baseline (|data_bytes_used_|) +
     // locally measured traffic (|local_bytes_used_|)
-    Bytes total_bytes_used_;
+    ByteCount total_bytes_used_;
 
     // convert plan type enum value to string formatted for libcros
     const char* TypeToLibcrosString(Type type) const;
diff --git a/src/data_plan_unittest.cc b/src/data_plan_unittest.cc
index c5b58f8..0afee0c 100644
--- a/src/data_plan_unittest.cc
+++ b/src/data_plan_unittest.cc
@@ -6,8 +6,11 @@
 #include <gtest/gtest.h>
 
 #include "src/data_plan.h"
+#include "src/device_mock.h"
 #include "src/json_reader.h"
 #include "src/policy.h"
+#include "src/service_manager_mock.h"
+#include "src/service_mock.h"
 
 namespace cashew {
 
@@ -15,14 +18,20 @@
 class DataPlanTest: public ::testing::Test {
   protected:
     DataPlanTest() : policy_(NULL), current_unlimited_plan_(NULL),
-        current_available_metered_plan_(NULL) {}
+        current_available_metered_plan_(NULL), plan_one_(NULL), plan_two_(NULL),
+        plan_three_(NULL) {}
 
     virtual ~DataPlanTest() {
       EXPECT_TRUE(policy_ == NULL);
       EXPECT_TRUE(current_unlimited_plan_ == NULL);
       EXPECT_TRUE(current_available_metered_plan_ == NULL);
+      EXPECT_TRUE(plan_one_ == NULL);
+      EXPECT_TRUE(plan_two_ == NULL);
+      EXPECT_TRUE(plan_three_ == NULL);
     }
 
+    static const int kBytesPerMegabyte = 1024 * 1024;
+
     virtual void SetUp() {
       EXPECT_TRUE(policy_ == NULL);
       policy_ = Policy::GetPolicy(kTestCarrierName);
@@ -34,8 +43,8 @@
       base::Time now = base::Time::Now();
       base::Time start_time = now - twelve_hours;
       base::Time end_time = now + twelve_hours;
-      Bytes max_bytes = 0;
-      Bytes used_bytes = 5 * 1024 * 1024;
+      ByteCount max_bytes = 0;
+      ByteCount used_bytes = 5 * kBytesPerMegabyte;
       current_unlimited_plan_ = new(std::nothrow) DataPlan(
           "Current Unlimited Plan", type, now, start_time, end_time, max_bytes,
           used_bytes);
@@ -43,14 +52,37 @@
 
       EXPECT_TRUE(current_available_metered_plan_ == NULL);
       type = DataPlan::kTypeMeteredFree;
-      max_bytes = 100 * 1024 * 1024;
+      max_bytes = 100 * kBytesPerMegabyte;
       current_available_metered_plan_ = new(std::nothrow) DataPlan(
           "Current Available Metered Plan", type, now, start_time, end_time,
           max_bytes, used_bytes);
       ASSERT_TRUE(current_available_metered_plan_ != NULL);
+
+      EXPECT_TRUE(plan_one_ == NULL);
+      used_bytes = 0;
+      plan_one_ = new(std::nothrow) DataPlan("Plan One", type, now, start_time,
+                                             end_time, max_bytes, used_bytes);
+      ASSERT_TRUE(plan_one_ != NULL);
+
+      EXPECT_TRUE(plan_two_ == NULL);
+      plan_two_ = new(std::nothrow) DataPlan("Plan Two", type, now, start_time,
+                                             end_time, max_bytes, used_bytes);
+      ASSERT_TRUE(plan_two_ != NULL);
+
+      EXPECT_TRUE(plan_three_ == NULL);
+      plan_three_ = new(std::nothrow) DataPlan("Plan Three", type, now,
+                                               start_time, end_time, max_bytes,
+                                               used_bytes);
+      ASSERT_TRUE(plan_three_ != NULL);
     }
 
     virtual void TearDown() {
+      delete plan_three_;
+      plan_three_ = NULL;
+      delete plan_two_;
+      plan_two_ = NULL;
+      delete plan_one_;
+      plan_one_ = NULL;
       delete current_available_metered_plan_;
       current_available_metered_plan_ = NULL;
       delete current_unlimited_plan_;
@@ -78,6 +110,16 @@
 
     // current available metered plan shared by multiple tests
     DataPlan *current_available_metered_plan_;
+
+    // three metered plans shared by multiple tests
+    DataPlan *plan_one_;
+    DataPlan *plan_two_;
+    DataPlan *plan_three_;
+
+    // mocks
+    ServiceMock service_mock_;
+    ServiceManagerMock service_manager_mock_;
+    DeviceMock device_mock_;
 };
 
 typedef DataPlanTest DataPlanDeathTest;
@@ -285,30 +327,32 @@
   // this test uses |current_unlimited_plan_| created in fixture SetUp
   // we expect plan to be initialized with local bytes == 0 and total bytes ==
   // |used_bytes|
-  EXPECT_EQ(5 * 1024 * 1024, current_unlimited_plan_->GetDataBytesUsed());
+  EXPECT_EQ(5 * kBytesPerMegabyte, current_unlimited_plan_->GetDataBytesUsed());
   EXPECT_EQ(0, current_unlimited_plan_->GetLocalBytesUsed());
-  EXPECT_EQ(5 * 1024 * 1024, current_unlimited_plan_->GetTotalBytesUsed());
+  EXPECT_EQ(5 * kBytesPerMegabyte,
+            current_unlimited_plan_->GetTotalBytesUsed());
 }
 
 TEST_F(DataPlanTest, TotalBytesAfterSetLocalBytes) {
   // this test uses |current_unlimited_plan_| created in fixture SetUp
-  Bytes local_bytes = 10 * 1024 * 1024;
+  ByteCount local_bytes = 10 * kBytesPerMegabyte;
   current_unlimited_plan_->SetLocalBytesUsed(local_bytes);
   // we expect that, after we set local bytes, total bytes == data bytes +
   // local bytes
-  EXPECT_EQ(5 * 1024 * 1024, current_unlimited_plan_->GetDataBytesUsed());
+  EXPECT_EQ(5 * kBytesPerMegabyte, current_unlimited_plan_->GetDataBytesUsed());
   EXPECT_EQ(local_bytes, current_unlimited_plan_->GetLocalBytesUsed());
-  EXPECT_EQ(15 * 1024 * 1024, current_unlimited_plan_->GetTotalBytesUsed());
+  EXPECT_EQ(15 * kBytesPerMegabyte,
+            current_unlimited_plan_->GetTotalBytesUsed());
 }
 
 TEST_F(DataPlanTest, TotalBytesAfterOverflow) {
   // this test uses |current_unlimited_plan_| created in fixture SetUp
-  Bytes local_bytes = kint64max;
+  ByteCount local_bytes = kint64max;
   current_unlimited_plan_->SetLocalBytesUsed(local_bytes);
   // we expect that, after we set local bytes to a value that will cause the
   // total bytes computation to overflow, the overflow will be detected and
   // total bytes will be set to kint64max.
-  EXPECT_EQ(5 * 1024 * 1024, current_unlimited_plan_->GetDataBytesUsed());
+  EXPECT_EQ(5 * kBytesPerMegabyte, current_unlimited_plan_->GetDataBytesUsed());
   EXPECT_EQ(local_bytes, current_unlimited_plan_->GetLocalBytesUsed());
   EXPECT_EQ(kint64max, current_unlimited_plan_->GetTotalBytesUsed());
 }
@@ -320,8 +364,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now - twelve_hours;
   base::Time end_time = now + twelve_hours;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 5 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 5 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -337,8 +381,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now + twelve_hours;
   base::Time end_time = now + twelve_hours + twelve_hours;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 5 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 5 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -354,8 +398,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now - twelve_hours - twelve_hours;
   base::Time end_time = now - twelve_hours;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 5 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 5 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -371,8 +415,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now;
   base::Time end_time = now + twelve_hours;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 5 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 5 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -388,8 +432,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now - twelve_hours;
   base::Time end_time = now;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 5 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 5 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -412,8 +456,8 @@
   base::Time now = base::Time::Now();
   base::Time start_time = now - twelve_hours;
   base::Time end_time = now + twelve_hours;
-  Bytes max_bytes = 100 * 1024 * 1024;
-  Bytes used_bytes = 100 * 1024 * 1024;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 100 * kBytesPerMegabyte;
   DataPlan *plan = new(std::nothrow) DataPlan(name, type, now, start_time,
                                               end_time, max_bytes, used_bytes);
   ASSERT_TRUE(plan != NULL);
@@ -425,7 +469,7 @@
 
 TEST_F(DataPlanTest, IsActiveConsumedLocalMeteredPlan) {
   // this test uses |current_available_metered_plan_| created in fixture SetUp
-  current_available_metered_plan_->SetLocalBytesUsed(95 * 1024 * 1024);
+  current_available_metered_plan_->SetLocalBytesUsed(95 * kBytesPerMegabyte);
   // we expect that a current metered plan that has used bytes < max bytes but
   // is consumed by virtue of local bytes + used bytes being >= max bytes will
   // not be considered active
@@ -439,4 +483,721 @@
   EXPECT_TRUE(current_unlimited_plan_->IsActive());
 }
 
+// tests for local byte counter maintenance across plan transitions
+
+// empty list, we should stop the counter
+TEST_F(DataPlanTest, NoActivePlans) {
+  // create empty list
+  DataPlanList data_plans;
+  ASSERT_TRUE(data_plans.empty());
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  // failure will most likely manifest as violated mock expectation
+  EXPECT_FALSE(result);
+}
+
+// single plan, usage doesn't consume
+TEST_F(DataPlanTest, OnePlanNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_one_->IsActive());
+}
+
+// single plan, usage exactly consumes
+TEST_F(DataPlanTest, OnePlanConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(0, 0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 50 * kBytesPerMegabyte;
+  uint64 tx_bytes = 50 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+}
+
+// single plan, usage consumes and leaves overage but no active plan
+TEST_F(DataPlanTest, OnePlanConsumedWithOverage) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(10 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 55 * kBytesPerMegabyte;
+  uint64 tx_bytes = 55 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+}
+
+// two plans, first is inactive because quota consumed and second is active
+// usage is applied to but does not consume second plan
+TEST_F(DataPlanTest, TwoPlansFirstInactiveConsumed) {
+  // create consumed plan and put it in list
+  std::string name = "Consumed Plan";
+  DataPlan::Type type = DataPlan::kTypeMeteredFree;
+  base::TimeDelta twelve_hours = base::TimeDelta::FromHours(12);
+  base::Time now = base::Time::Now();
+  base::Time start_time = now - twelve_hours;
+  base::Time end_time = now + twelve_hours;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 100 * kBytesPerMegabyte;
+  DataPlan *consumed_plan = new(std::nothrow) DataPlan(name, type, now,
+                                                       start_time, end_time,
+                                                       max_bytes, used_bytes);
+  ASSERT_TRUE(consumed_plan != NULL);
+  DataPlanList data_plans;
+  data_plans.push_back(consumed_plan);
+
+  // use active plan from test fixture
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(consumed_plan->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(consumed_plan->GetDataBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(consumed_plan->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(consumed_plan->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(consumed_plan->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_two_->IsActive());
+
+  // clean up
+  delete consumed_plan;
+}
+
+// two plans, first is inactive because it has expired and second is active
+// usage is applied to but does not consume second plan
+TEST_F(DataPlanTest, TwoPlansFirstInactiveExpired) {
+  // create expired plan and put it in list
+  std::string name = "Expired Plan";
+  DataPlan::Type type = DataPlan::kTypeMeteredFree;
+  base::TimeDelta twelve_hours = base::TimeDelta::FromHours(12);
+  base::Time now = base::Time::Now();
+  base::Time start_time = now - 2 * twelve_hours;
+  base::Time end_time = now - twelve_hours;
+  ByteCount max_bytes = 100 * kBytesPerMegabyte;
+  ByteCount used_bytes = 0;
+  DataPlan *expired_plan = new(std::nothrow) DataPlan(name, type, now,
+                                                      start_time, end_time,
+                                                      max_bytes, used_bytes);
+  ASSERT_TRUE(expired_plan != NULL);
+  DataPlanList data_plans;
+  data_plans.push_back(expired_plan);
+
+  // use active plan from test fixture
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(expired_plan->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(expired_plan->GetDataBytesUsed(), 0);
+  EXPECT_EQ(expired_plan->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(expired_plan->GetTotalBytesUsed(), 0);
+  EXPECT_FALSE(expired_plan->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_two_->IsActive());
+
+  // clean up
+  delete expired_plan;
+}
+
+// two plans, first is unlimited and should absorb all usage
+// second plan should remain untouched
+TEST_F(DataPlanTest, TwoPlansFirstUnlimited) {
+  // use plans from test fixture
+  DataPlanList data_plans;
+  data_plans.push_back(current_unlimited_plan_);
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 100 * kBytesPerMegabyte;
+  uint64 tx_bytes = 100 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(current_unlimited_plan_->GetDataBytesMax(), 0);
+  EXPECT_EQ(current_unlimited_plan_->GetDataBytesUsed(), 5 * kBytesPerMegabyte);
+  EXPECT_EQ(current_unlimited_plan_->GetLocalBytesUsed(),
+            200 * kBytesPerMegabyte);
+  EXPECT_EQ(current_unlimited_plan_->GetTotalBytesUsed(),
+            205 * kBytesPerMegabyte);
+  EXPECT_TRUE(current_unlimited_plan_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 0);
+  EXPECT_TRUE(plan_two_->IsActive());
+}
+
+// two plans, the first is metered and the second is unlimited
+// usage consumes the first plan, and the overage is absorbed by the second
+TEST_F(DataPlanTest, TwoPlansSecondUnlimited) {
+  // use plans from test fixture
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(current_unlimited_plan_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(50 * kBytesPerMegabyte,
+                                               0)).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 75 * kBytesPerMegabyte;
+  uint64 tx_bytes = 75 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(current_unlimited_plan_->GetDataBytesMax(), 0);
+  EXPECT_EQ(current_unlimited_plan_->GetDataBytesUsed(), 5 * kBytesPerMegabyte);
+  EXPECT_EQ(current_unlimited_plan_->GetLocalBytesUsed(),
+            50 * kBytesPerMegabyte);
+  EXPECT_EQ(current_unlimited_plan_->GetTotalBytesUsed(),
+            55 * kBytesPerMegabyte);
+  EXPECT_TRUE(current_unlimited_plan_->IsActive());
+}
+
+// two plans, usage doesn't consume first
+TEST_F(DataPlanTest, TwoPlansFirstNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 0);
+  EXPECT_TRUE(plan_two_->IsActive());
+}
+
+// two plans, usage consumes first, doesn't consume second
+TEST_F(DataPlanTest, TwoPlansSecondNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(50 * kBytesPerMegabyte,
+                                               0)).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 75 * kBytesPerMegabyte;
+  uint64 tx_bytes = 75 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_two_->IsActive());
+}
+
+// two plans, usage consumes first, consumes second exactly
+TEST_F(DataPlanTest, TwoPlansSecondConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(100 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_two_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(0, 0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 100 * kBytesPerMegabyte;
+  uint64 tx_bytes = 100 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_two_->IsActive());
+}
+
+// two plans, usage consumes first, consumes second, with overage
+TEST_F(DataPlanTest, TwoPlansSecondConsumedWithOverage) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(110 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_two_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(10 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 105 * kBytesPerMegabyte;
+  uint64 tx_bytes = 105 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_two_->IsActive());
+}
+
+// three plans, usage doesn't consume first
+TEST_F(DataPlanTest, ThreePlansFirstNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+  data_plans.push_back(plan_three_);
+
+  // set up mock expectations and method return values
+  EXPECT_CALL(device_mock_, ByteCounterRunning())
+      .WillRepeatedly(testing::Return(true));
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 25 * kBytesPerMegabyte;
+  uint64 tx_bytes = 25 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 0);
+  EXPECT_TRUE(plan_two_->IsActive());
+  EXPECT_EQ(plan_three_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetTotalBytesUsed(), 0);
+  EXPECT_TRUE(plan_three_->IsActive());
+}
+
+// three plans, usage consumes first, doesn't consume second
+TEST_F(DataPlanTest, ThreePlansSecondNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+  data_plans.push_back(plan_three_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(50 * kBytesPerMegabyte,
+                                               0)).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 75 * kBytesPerMegabyte;
+  uint64 tx_bytes = 75 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_two_->IsActive());
+  EXPECT_EQ(plan_three_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetLocalBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetTotalBytesUsed(), 0);
+  EXPECT_TRUE(plan_three_->IsActive());
+}
+
+// three plans, usage consumes first, consumes second, doesn't consume third
+TEST_F(DataPlanTest, ThreePlansThirdNotConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+  data_plans.push_back(plan_three_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(150 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_two_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(50 * kBytesPerMegabyte,
+                                               0)).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 125 * kBytesPerMegabyte;
+  uint64 tx_bytes = 125 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_two_->IsActive());
+  EXPECT_EQ(plan_three_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetLocalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetTotalBytesUsed(), 50 * kBytesPerMegabyte);
+  EXPECT_TRUE(plan_three_->IsActive());
+}
+
+// three plans, usage consumes first, consumes second, exactly consumes third
+TEST_F(DataPlanTest, ThreePlansThirdConsumed) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+  data_plans.push_back(plan_three_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(200 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_two_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(100 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_three_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(0, 0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 150 * kBytesPerMegabyte;
+  uint64 tx_bytes = 150 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_two_->IsActive());
+  EXPECT_EQ(plan_three_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_three_->IsActive());
+}
+
+// three plans, usage consumes first, consumes second, consumes third w/ overage
+TEST_F(DataPlanTest, ThreePlansThirdConsumedWithOverage) {
+  DataPlanList data_plans;
+  data_plans.push_back(plan_one_);
+  data_plans.push_back(plan_two_);
+  data_plans.push_back(plan_three_);
+
+  // set up mock expectations and method return values
+  {
+    testing::InSequence expect_calls_in_order;
+    EXPECT_CALL(device_mock_, ByteCounterRunning())
+        .WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_one_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(210 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_two_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(110 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(service_manager_mock_,
+                OnDataPlanInactive(testing::Ref(service_mock_),
+                                   testing::Ref(*plan_three_))).Times(1);
+    EXPECT_CALL(device_mock_, ResetByteCounter(10 * kBytesPerMegabyte,
+                                               0)).Times(1);
+    EXPECT_CALL(device_mock_, StopByteCounter()).Times(1);
+  }
+
+  // simulate a byte counter update
+  uint64 rx_bytes = 155 * kBytesPerMegabyte;
+  uint64 tx_bytes = 155 * kBytesPerMegabyte;
+  bool result = DataPlan::OnByteCounterUpdate(&data_plans, &service_mock_,
+                                              &service_manager_mock_,
+                                              &device_mock_, rx_bytes,
+                                              tx_bytes);
+
+  // check if test passed
+  EXPECT_TRUE(result);
+  EXPECT_EQ(plan_one_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_one_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_one_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_one_->IsActive());
+  EXPECT_EQ(plan_two_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_two_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_two_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_two_->IsActive());
+  EXPECT_EQ(plan_three_->GetDataBytesMax(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetDataBytesUsed(), 0);
+  EXPECT_EQ(plan_three_->GetLocalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_EQ(plan_three_->GetTotalBytesUsed(), 100 * kBytesPerMegabyte);
+  EXPECT_FALSE(plan_three_->IsActive());
+}
+
 }  // namespace cashew
diff --git a/src/device.h b/src/device.h
index 7955234..bd24903 100644
--- a/src/device.h
+++ b/src/device.h
@@ -53,17 +53,23 @@
 
     // start a new byte counter from 0
     // there must not be an existing byte counter
-    // |parent_| will receive periodic updates via Service::OnByteCounterUpdate
+    // parent service will receive periodic updates via OnByteCounterUpdate
     // returns true on success and false on failure
     virtual bool StartByteCounter() = 0;
 
     // destroy any existing byte counter
-    // |parent_| will no longer receive updates after this call
+    // parent service will no longer receive updates after this call
     virtual void StopByteCounter() = 0;
 
     // is the byte counter running?
     virtual bool ByteCounterRunning() const = 0;
 
+    // reset byte counter to an arbitrary baseline value
+    // counter will continue to run and increment relative to new baseline
+    // there must be an existing byte counter
+    // parent service must already have received at least one update
+    virtual void ResetByteCounter(uint64 rx_bytes, uint64 tx_bytes) = 0;
+
     // factory
     static Device* NewDevice(Service * const parent,
                              DBus::Connection& connection,  // NOLINT
diff --git a/src/device_impl.cc b/src/device_impl.cc
index 2477308..1f16ef6 100644
--- a/src/device_impl.cc
+++ b/src/device_impl.cc
@@ -144,6 +144,15 @@
   return byte_counter_running_;
 }
 
+void DeviceImpl::ResetByteCounter(uint64 rx_bytes, uint64 tx_bytes) {
+  DLOG(INFO) << path_ << ": ResetByteCounter: rx_bytes = " << rx_bytes
+      << ", tx_bytes = " << tx_bytes;
+  DCHECK(byte_counter_running_);
+  DCHECK(byte_counter_ != NULL);
+  byte_counter_->SetRxBytes(rx_bytes);
+  byte_counter_->SetTxBytes(tx_bytes);
+}
+
 // ByteCounterDelegate methods
 
 void DeviceImpl::OnByteCounterUpdate(const ByteCounter *counter,
diff --git a/src/device_impl.h b/src/device_impl.h
index 8da64fb..e34d2cb 100644
--- a/src/device_impl.h
+++ b/src/device_impl.h
@@ -54,10 +54,11 @@
     virtual bool StartByteCounter();
     virtual void StopByteCounter();
     virtual bool ByteCounterRunning() const;
+    virtual void ResetByteCounter(uint64 rx_bytes, uint64 tx_bytes);
 
     // ByteCounterDelegate methods
 
-    // called when bye counter is updated
+    // called when byte counter is updated
     virtual void OnByteCounterUpdate(const ByteCounter *counter,
                                      uint64 rx_bytes, uint64 tx_bytes);
 
diff --git a/src/device_mock.h b/src/device_mock.h
index d55fbf3..8cfddb7 100644
--- a/src/device_mock.h
+++ b/src/device_mock.h
@@ -22,6 +22,7 @@
     MOCK_METHOD0(StartByteCounter, bool());
     MOCK_METHOD0(StopByteCounter, void());
     MOCK_CONST_METHOD0(ByteCounterRunning, bool());
+    MOCK_METHOD2(ResetByteCounter, void(uint64 rx_bytes, uint64 tx_bytes));
 };
 
 }  // namespace cashew
diff --git a/src/procfs_byte_counter.cc b/src/procfs_byte_counter.cc
index e22586c..6186a13 100644
--- a/src/procfs_byte_counter.cc
+++ b/src/procfs_byte_counter.cc
@@ -65,10 +65,20 @@
   return counter_rx_bytes_;
 }
 
+void ProcfsByteCounter::SetRxBytes(uint64 rx_bytes) {
+  DLOG(INFO) << interface_ << ": SetRxBytes: rx_bytes = " << rx_bytes;
+  counter_rx_bytes_ = rx_bytes;
+}
+
 uint64 ProcfsByteCounter::GetTxBytes() const {
   return counter_tx_bytes_;
 }
 
+void ProcfsByteCounter::SetTxBytes(uint64 tx_bytes) {
+  DLOG(INFO) << interface_ << ": SetTxBytes: tx_bytes = " << tx_bytes;
+  counter_tx_bytes_ = tx_bytes;
+}
+
 void ProcfsByteCounter::SetDelegate(ByteCounterDelegate *delegate) {
   DLOG(INFO) << interface_ << ": SetDelegate";
   delegate_ = delegate;
diff --git a/src/procfs_byte_counter.h b/src/procfs_byte_counter.h
index 78fc8e7..40a250a 100644
--- a/src/procfs_byte_counter.h
+++ b/src/procfs_byte_counter.h
@@ -26,7 +26,9 @@
     virtual ~ProcfsByteCounter();
     virtual const std::string& GetInterface() const;
     virtual uint64 GetRxBytes() const;
+    virtual void SetRxBytes(uint64 rx_bytes);
     virtual uint64 GetTxBytes() const;
+    virtual void SetTxBytes(uint64 tx_bytes);
     virtual void SetDelegate(ByteCounterDelegate *delegate);
 
   private:
diff --git a/src/service_impl.cc b/src/service_impl.cc
index da04095..ee69de6 100644
--- a/src/service_impl.cc
+++ b/src/service_impl.cc
@@ -259,35 +259,14 @@
 }
 
 void ServiceImpl::OnByteCounterUpdate(uint64 rx_bytes, uint64 tx_bytes) {
-  DCHECK(device_ != NULL);
-  DCHECK(device_->ByteCounterRunning());
   DLOG(INFO) << path_ << ": OnByteCounterUpdate: rx_bytes = " << rx_bytes
       << ", tx_bytes = " << tx_bytes;
-  DataPlan *active_plan = DataPlan::GetActivePlan(data_plans_);
-  if (active_plan == NULL) {
-    DLOG(WARNING) << path_ << ": OnByteCounterUpdate: no active plan";
-    return;
+  // dispatch this notification to our data plans list and consider sending
+  // out a signal if any plans were updated
+  if (DataPlan::OnByteCounterUpdate(&data_plans_, this, parent_, device_,
+                                    rx_bytes, tx_bytes)) {
+    MaybeEmitDataPlansUpdate();
   }
-  Bytes local_bytes_used = rx_bytes + tx_bytes;
-  DLOG(INFO) << path_ << ": OnByteCounterUpdate: local_bytes_used = "
-      << local_bytes_used;
-  // 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 (local_bytes_used < 0 ||
-      static_cast<uint64>(local_bytes_used) < rx_bytes ||
-      static_cast<uint64>(local_bytes_used) < tx_bytes) {
-    LOG(WARNING) << path_ << ": OnByteCounterUpdate: overflow detected";
-    return;
-  }
-  active_plan->SetLocalBytesUsed(local_bytes_used);
-  DLOG(INFO) << path_
-      << ": OnByteCounterUpdate: updated plan state: data bytes used = "
-      << active_plan->GetDataBytesUsed() << ", local bytes used = "
-      << active_plan->GetLocalBytesUsed() << ", total bytes used = "
-      << active_plan->GetTotalBytesUsed();
-  MaybeEmitDataPlansUpdate();
 }
 
 // DataPlanProviderDelegate methods
@@ -696,8 +675,8 @@
   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;
-  Bytes data_bytes_max = 100*1024*1024;  // 100 MB
-  Bytes data_bytes_used = 30*1024*1024;  // 30 MB
+  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,
diff --git a/src/service_manager.h b/src/service_manager.h
index 97fdee6..264ff5f 100644
--- a/src/service_manager.h
+++ b/src/service_manager.h
@@ -70,6 +70,10 @@
     // a child Service wants us to emit an update about its data plans
     virtual void EmitDataPlansUpdate(const Service& service) = 0;
 
+    // |plan|, associated with |service|, has become inactive
+    virtual void OnDataPlanInactive(const Service& service,
+                                    const DataPlan& plan) = 0;
+
     // factory
     static ServiceManager* NewServiceManager(
                                         DBus::Connection& connection,  // NOLINT
diff --git a/src/service_manager_impl.cc b/src/service_manager_impl.cc
index 1069a06..232387d 100644
--- a/src/service_manager_impl.cc
+++ b/src/service_manager_impl.cc
@@ -165,6 +165,15 @@
   cashew_server_->EmitDataPlansUpdate(service);
 }
 
+void ServiceManagerImpl::OnDataPlanInactive(const Service& service,
+                                            const DataPlan& plan) {
+  DLOG(INFO) << "OnDataPlanInactive: service = " << service.GetPath()
+      << ", plan = " << plan.GetName();
+  DCHECK(!plan.IsActive());
+  // TODO(vlaviano): hook for future use
+  // we could emit a PlanExpired D-Bus signal for Chrome to consume
+}
+
 // Private methods
 
 void ServiceManagerImpl::DeleteServices(ServiceMap *service_map) {
diff --git a/src/service_manager_impl.h b/src/service_manager_impl.h
index 2ad53be..806ed28 100644
--- a/src/service_manager_impl.h
+++ b/src/service_manager_impl.h
@@ -60,9 +60,9 @@
                                    const DBus::Variant& new_value);
 
     // Service methods
-
-    // a child Service wants us to emit an update about its data plans
     virtual void EmitDataPlansUpdate(const Service& service);
+    virtual void OnDataPlanInactive(const Service& service,
+                                    const DataPlan& plan);
 
   private:
     // D-Bus connection owned by our creator
diff --git a/src/service_manager_mock.h b/src/service_manager_mock.h
index 32f31d5..09f1c3d 100644
--- a/src/service_manager_mock.h
+++ b/src/service_manager_mock.h
@@ -22,6 +22,8 @@
     MOCK_CONST_METHOD0(GetConnectivityState,
                        ServiceManager::ConnectivityState());
     MOCK_METHOD1(EmitDataPlansUpdate, void(const Service& service));
+    MOCK_METHOD2(OnDataPlanInactive,
+                 void(const Service& service, const DataPlan& plan));
 };
 
 }  // namespace cashew