[Cobalt 1.1 privacy] Decode private indices for FleetwideMeans reports

Adds a tagged union SumOrCount to hold numeric
contributions to FleetwideMeans reports, and
adds a method to decode a private index as a
pair (event_vector, sum_or_count).

Change-Id: Ia6bfa6bb15956fa52c8373958137b4c708eb9cc3
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/442593
Commit-Queue: Laura Peskin <pesk@google.com>
Reviewed-by: Alexandre Zani <azani@google.com>
diff --git a/src/lib/privacy/private_index_decoding.cc b/src/lib/privacy/private_index_decoding.cc
index a3a262a..d52ab5d 100644
--- a/src/lib/privacy/private_index_decoding.cc
+++ b/src/lib/privacy/private_index_decoding.cc
@@ -6,6 +6,7 @@
 
 namespace cobalt {
 namespace {
+
 util::Status ValidateIndexAsInteger(uint64_t index, uint64_t num_index_points) {
   if (index >= num_index_points) {
     return util::Status(util::INVALID_ARGUMENT,
@@ -13,6 +14,18 @@
   }
   return util::Status::OK;
 }
+
+util::Status ValidateIndexAsCount(uint64_t index, uint64_t num_index_points) {
+  if (index < num_index_points) {
+    return util::Status(util::INVALID_ARGUMENT, "index is less than num_index_points.");
+  }
+  if (index >= 2 * num_index_points) {
+    return util::Status(util::INVALID_ARGUMENT,
+                        "index is greater than or equal to 2 * num_index_points.");
+  }
+  return util::Status::OK;
+}
+
 }  // namespace
 
 util::Status DecodePrivateIndexAsEventVector(
@@ -50,4 +63,38 @@
   return util::Status::OK;
 }
 
+cobalt::util::Status DecodePrivateIndexAsSumOrCount(
+    uint64_t index,
+    const google::protobuf::RepeatedPtrField<MetricDefinition::MetricDimension>& metric_dimensions,
+    int64_t min_value, int64_t max_value, uint64_t max_count, uint64_t num_index_points,
+    std::vector<uint32_t>* event_vector, SumOrCount* sum_or_count) {
+  uint64_t event_vector_index;
+  uint64_t value_index;
+  ValueAndEventVectorIndicesFromIndex(index, logger::GetNumEventVectors(metric_dimensions) - 1,
+                                      &value_index, &event_vector_index);
+  if (util::Status decode_event_vector_index =
+          DecodePrivateIndexAsEventVector(event_vector_index, metric_dimensions, event_vector);
+      !decode_event_vector_index.ok()) {
+    return decode_event_vector_index;
+  }
+
+  if (IsCountIndex(value_index, num_index_points)) {
+    if (util::Status validate_value_index = ValidateIndexAsCount(value_index, num_index_points);
+        !validate_value_index.ok()) {
+      return validate_value_index;
+    }
+    (*sum_or_count).type = SumOrCount::COUNT;
+    (*sum_or_count).count = CountFromIndex(value_index, max_count, num_index_points);
+    return util::Status::OK;
+  }
+
+  if (util::Status validate_value_index = ValidateIndexAsInteger(value_index, num_index_points);
+      !validate_value_index.ok()) {
+    return validate_value_index;
+  }
+  (*sum_or_count).type = SumOrCount::SUM;
+  (*sum_or_count).sum = IntegerFromIndex(value_index, min_value, max_value, num_index_points);
+  return util::Status::OK;
+}
+
 }  // namespace cobalt
diff --git a/src/lib/privacy/private_index_decoding.h b/src/lib/privacy/private_index_decoding.h
index dd72d29..7cf3386 100644
--- a/src/lib/privacy/private_index_decoding.h
+++ b/src/lib/privacy/private_index_decoding.h
@@ -12,6 +12,16 @@
 
 namespace cobalt {
 
+// A value which represents either a sum or a count. Used when decoding observations for
+// FleetwideMeans reports.
+struct SumOrCount {
+  enum SumOrCountType { SUM, COUNT };
+  SumOrCountType type;
+
+  int64_t sum = 0;
+  uint64_t count = 0;
+};
+
 // Populates |event_vector| with the event vector which corresponds to |index| according to the
 // contents of |metric_dimensions| and returns an OK status, or returns an error status if |index|
 // does not represent a valid event vector according to |metric_dimensions|.
@@ -30,6 +40,21 @@
     int64_t min_value, int64_t max_value, uint64_t num_index_points,
     std::vector<uint32_t>* event_vector, int64_t* integer_value);
 
+// Populates |event_vector| and |sum_or_count| with the event vector and SumAndCount which
+// correspond to |index| according to |metric_dimensions|, |min_value|, |max_value|, |max_count| and
+// |num_index_points|, or returns an error status if |index| does not represent a valid
+// (event_vector, sum)  or (event_vector, count) pair.
+//
+// After a successful call, |sum_or_count| will be a SumOrCount struct whose |type| field is set to
+// either SUM or COUNT. If the |type| is SUM, then the |sum| field is populated with a signed int
+// representing a sum; if the |type| is COUNT, then the |count| field is populated with an unsigned
+// int representing a count.
+util::Status DecodePrivateIndexAsSumOrCount(
+    uint64_t index,
+    const google::protobuf::RepeatedPtrField<MetricDefinition::MetricDimension>& metric_dimensions,
+    int64_t min_value, int64_t max_value, uint64_t max_count, uint64_t num_index_points,
+    std::vector<uint32_t>* event_vector, SumOrCount* sum_or_count);
+
 }  // namespace cobalt
 
 #endif  // COBALT_SRC_LIB_PRIVACY_PRIVATE_INDEX_DECODING_H_
diff --git a/src/lib/privacy/private_index_decoding_test.cc b/src/lib/privacy/private_index_decoding_test.cc
index be4be5d..e843c2b 100644
--- a/src/lib/privacy/private_index_decoding_test.cc
+++ b/src/lib/privacy/private_index_decoding_test.cc
@@ -10,6 +10,7 @@
 
 const int64_t kMinValue = -10;
 const int64_t kMaxValue = 50;
+const uint64_t kMaxCount = 100;
 const uint64_t kNumIndexPoints = 5;
 
 }  // namespace
@@ -41,10 +42,14 @@
 
   uint64_t MaxEventVectorIndex() { return logger::GetNumEventVectors(metric_dimensions_) - 1; }
 
-  uint64_t MaxIntegerValueIndex() {
+  uint64_t MaxIndexForIntegerValue() {
     return logger::GetNumEventVectors(metric_dimensions_) * kNumIndexPoints - 1;
   }
 
+  uint64_t MinIndexForCount() { return MaxIndexForIntegerValue() + 1; }
+
+  uint64_t MaxIndexForCount() { return 2 * MinIndexForCount() - 1; }
+
   google::protobuf::RepeatedPtrField<MetricDefinition::MetricDimension> metric_dimensions_;
 };
 
@@ -80,7 +85,7 @@
   EXPECT_EQ(integer_value, kMinValue);
 
   // Check that the maximum private index is decoded correctly.
-  status = DecodePrivateIndexAsInteger(MaxIntegerValueIndex(), metric_dimensions_, kMinValue,
+  status = DecodePrivateIndexAsInteger(MaxIndexForIntegerValue(), metric_dimensions_, kMinValue,
                                        kMaxValue, kNumIndexPoints, &event_vector, &integer_value);
   ASSERT_TRUE(status.ok());
   EXPECT_EQ(event_vector, std::vector<uint32_t>({2, 300, 10}));
@@ -91,9 +96,63 @@
   std::vector<uint32_t> event_vector;
   int64_t integer_value;
   util::Status status =
-      DecodePrivateIndexAsInteger(MaxIntegerValueIndex() + 1, metric_dimensions_, kMinValue,
+      DecodePrivateIndexAsInteger(MaxIndexForIntegerValue() + 1, metric_dimensions_, kMinValue,
                                   kMaxValue, kNumIndexPoints, &event_vector, &integer_value);
   EXPECT_FALSE(status.ok());
 }
 
+TEST_F(PrivateIndexDecodingTest, DecodeAsSumOrCountValidSum) {
+  std::vector<uint32_t> event_vector;
+  SumOrCount sum_or_count;
+  // Check that the minimum private index that corresponds to sum is decoded correctly.
+  util::Status status =
+      DecodePrivateIndexAsSumOrCount(0, metric_dimensions_, kMinValue, kMaxValue, kMaxCount,
+                                     kNumIndexPoints, &event_vector, &sum_or_count);
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ(event_vector, std::vector<uint32_t>({0, 0, 0}));
+  EXPECT_EQ(sum_or_count.type, SumOrCount::SUM);
+  EXPECT_EQ(sum_or_count.sum, kMinValue);
+
+  // Check that the maximum private index that corresponds to a sum is decoded correctly.
+  status = DecodePrivateIndexAsSumOrCount(MaxIndexForIntegerValue(), metric_dimensions_, kMinValue,
+                                          kMaxValue, kMaxCount, kNumIndexPoints, &event_vector,
+                                          &sum_or_count);
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ(event_vector, std::vector<uint32_t>({2, 300, 10}));
+  EXPECT_EQ(sum_or_count.type, SumOrCount::SUM);
+  EXPECT_EQ(sum_or_count.sum, kMaxValue);
+}
+
+TEST_F(PrivateIndexDecodingTest, DecodeAsSumOrCountValidCount) {
+  std::vector<uint32_t> event_vector;
+  SumOrCount sum_or_count;
+  // Check that the minimum private index that corresponds to a count is decoded correctly.
+  util::Status status =
+      DecodePrivateIndexAsSumOrCount(MinIndexForCount(), metric_dimensions_, kMinValue, kMaxValue,
+                                     kMaxCount, kNumIndexPoints, &event_vector, &sum_or_count);
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ(event_vector, std::vector<uint32_t>({0, 0, 0}));
+  EXPECT_EQ(sum_or_count.type, SumOrCount::COUNT);
+  EXPECT_EQ(sum_or_count.count, 0u);
+
+  // Check that the maximum private index that corresponds to a count is decoded correctly.
+  status =
+      DecodePrivateIndexAsSumOrCount(MaxIndexForCount(), metric_dimensions_, kMinValue, kMaxValue,
+                                     kMaxCount, kNumIndexPoints, &event_vector, &sum_or_count);
+  ASSERT_TRUE(status.ok());
+  EXPECT_EQ(event_vector, std::vector<uint32_t>({2, 300, 10}));
+  EXPECT_EQ(sum_or_count.type, SumOrCount::COUNT);
+  EXPECT_EQ(sum_or_count.count, kMaxCount);
+}
+
+TEST_F(PrivateIndexDecodingTest, DecodeAsSumOrCountInvalid) {
+  std::vector<uint32_t> event_vector;
+  SumOrCount sum_or_count;
+
+  util::Status status = DecodePrivateIndexAsSumOrCount(
+      MaxIndexForCount() + 1, metric_dimensions_, kMinValue, kMaxValue, kMaxCount, kNumIndexPoints,
+      &event_vector, &sum_or_count);
+  EXPECT_FALSE(status.ok());
+}
+
 }  // namespace cobalt