[Cobalt 1.1 privacy] Handle unset event_code_buffer_max in
PrivacyEncodingParamsCalculator

If a metric does not have event_code_buffer_max set, the
PrivacyEncodingParamsCalculator now computes the number of
valid event vectors for the metric and uses that as the
effective event_code_buffer_max when computing sparsity.

Bug: 72699
Change-Id: I08260ac98eb2ae0af103bacdc767b6875f0adcd5
Reviewed-on: https://fuchsia-review.googlesource.com/c/cobalt/+/504739
Commit-Queue: Laura Peskin <pesk@google.com>
Reviewed-by: Jared Weinstein <jaredweinstein@google.com>
diff --git a/src/bin/config_parser/src/privacy/privacy_encoding_params.go b/src/bin/config_parser/src/privacy/privacy_encoding_params.go
index 5beb245..7b8f5f1 100644
--- a/src/bin/config_parser/src/privacy/privacy_encoding_params.go
+++ b/src/bin/config_parser/src/privacy/privacy_encoding_params.go
@@ -339,7 +339,7 @@
 			config.ReportDefinition_SELECT_MOST_COMMON:
 			numEventVectors, err = 1, nil
 		case config.ReportDefinition_AT_LEAST_ONCE:
-			numEventVectors, err = metric.EventCodeBufferMax, nil
+			numEventVectors, err = getEventCodeBufferMax(metric), nil
 		default:
 			err = fmt.Errorf("unexpected LocalAggregationProcedure: %v", report.LocalAggregationProcedure)
 		}
@@ -352,7 +352,7 @@
 		config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
 		config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
 		config.ReportDefinition_STRING_COUNTS:
-		numEventVectors, err = metric.EventCodeBufferMax, nil
+		numEventVectors, err = getEventCodeBufferMax(metric), nil
 
 	default:
 		err = fmt.Errorf("unsupported ReportType: %v", report.ReportType)
@@ -360,6 +360,33 @@
 	return numEventVectors, err
 }
 
+// Returns the maximum number of event vectors for which a device stores local aggregates.
+// This is either the event_code_buffer_max value specified in the MetricDefinition, or
+// (if that field is unset) the total number of valid event vectors.
+func getEventCodeBufferMax(metric *config.MetricDefinition) (bufferMax uint64) {
+	if metric.EventCodeBufferMax != 0 {
+		return metric.EventCodeBufferMax
+	}
+	return numEventVectors(metric)
+}
+
+// Returns the total number of valid event vectors for a MetricDefinition.
+func numEventVectors(metric *config.MetricDefinition) (numEventVectors uint64) {
+	numEventVectors = 1
+	for _, dim := range metric.MetricDimensions {
+		numEventVectors *= uint64(numEventCodes(dim))
+	}
+	return numEventVectors
+}
+
+// A helper function returning the number of valid event codes for a MetricDimension.
+func numEventCodes(dim *config.MetricDefinition_MetricDimension) (numEventCodes uint32) {
+	if dim.MaxEventCode != 0 {
+		return dim.MaxEventCode + 1
+	}
+	return uint32(len(dim.EventCodes))
+}
+
 // Returns the max number of buckets which may be populated in a contribution for |report| for each event vector defined in |metric|.
 // Returns 1 for reports for which a contribution consists of a single integer per event vector.
 func getNumBucketsPerEventVector(metric *config.MetricDefinition, report *config.ReportDefinition) (numBuckets uint64, err error) {
diff --git a/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go b/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
index da98652..d084d70 100644
--- a/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
+++ b/src/bin/config_parser/src/privacy/privacy_encoding_params_test.go
@@ -181,21 +181,74 @@
 	}
 }
 
+func TestGetEventCodeBufferMax(t *testing.T) {
+	var eventCodeBufferMax uint64 = 5
+	var maxEventCode uint32 = 2
+
+	dimensionMaxEventCode := config.MetricDefinition_MetricDimension{
+		Dimension:    "dim0",
+		MaxEventCode: maxEventCode,
+	}
+	dimensionNoMaxEventCode := config.MetricDefinition_MetricDimension{
+		Dimension:  "dim1",
+		EventCodes: map[uint32]string{0: "event0", 1: "event1", 5: "event5"},
+	}
+	metricDimensions := []*config.MetricDefinition_MetricDimension{
+		&dimensionMaxEventCode,
+		&dimensionNoMaxEventCode,
+	}
+
+	metricMaxSet := config.MetricDefinition{
+		MetricName:         "MetricWithEventCodeBufferMaxSet",
+		EventCodeBufferMax: eventCodeBufferMax,
+		MetricDimensions:   metricDimensions,
+	}
+	metricMaxUnset := config.MetricDefinition{
+		MetricName:       "MetricWithEventCodeBufferMaxUnset",
+		MetricDimensions: metricDimensions,
+	}
+	var tests = []struct {
+		metric   *config.MetricDefinition
+		expected uint64
+	}{
+		{&metricMaxSet, uint64(eventCodeBufferMax)},
+		{&metricMaxUnset, uint64((maxEventCode + 1) * 3)},
+	}
+	for _, test := range tests {
+		result := getEventCodeBufferMax(test.metric)
+		if result != test.expected {
+			t.Errorf("eventCodeBufferMax() for metric %s: expected %d, got %d",
+				test.metric.MetricName, test.expected, result)
+		}
+	}
+
+}
+
 func TestGetSparsityForReport(t *testing.T) {
 	var eventCodeBufferMax uint64 = 5
 	var stringBufferMax uint32 = 3
 	var numLinearBuckets uint32 = 7
+	var maxEventCode uint32 = 2
 
 	linearBuckets := config.LinearIntegerBuckets{NumBuckets: numLinearBuckets}
 	buckets := config.IntegerBuckets{
 		Buckets: &config.IntegerBuckets_Linear{&linearBuckets},
 	}
 
+	dimension := config.MetricDefinition_MetricDimension{
+		Dimension:    "dim0",
+		MaxEventCode: maxEventCode,
+	}
+
 	// Metrics
 	occurrenceMetric := config.MetricDefinition{
 		MetricType:         config.MetricDefinition_OCCURRENCE,
 		EventCodeBufferMax: eventCodeBufferMax,
 	}
+	occurrenceMetricEventCodeBufferMaxUnset := config.MetricDefinition{
+		MetricType:       config.MetricDefinition_OCCURRENCE,
+		MetricDimensions: []*config.MetricDefinition_MetricDimension{&dimension},
+	}
 	integerMetric := config.MetricDefinition{
 		MetricType:         config.MetricDefinition_INTEGER,
 		EventCodeBufferMax: eventCodeBufferMax,
@@ -279,9 +332,12 @@
 	}{
 		// Valid input:
 		{args{&occurrenceMetric, &fleetwideOccurrenceCountsReport}, true, eventCodeBufferMax},
+		{args{&occurrenceMetricEventCodeBufferMaxUnset, &fleetwideOccurrenceCountsReport}, true,
+			uint64(maxEventCode) + 1},
 		{args{&occurrenceMetric, &atLeastOnceUniqueDeviceCountsReport}, true, eventCodeBufferMax},
 		{args{&occurrenceMetric, &selectFirstUniqueDeviceCountsReport}, true, 1},
 		{args{&occurrenceMetric, &selectMostCommonUniqueDeviceCountsReport}, true, 1},
+		{args{&occurrenceMetricEventCodeBufferMaxUnset, &selectMostCommonUniqueDeviceCountsReport}, true, 1},
 		{args{&occurrenceMetric, &uniqueDeviceHistogramsReport}, true, eventCodeBufferMax},
 		{args{&occurrenceMetric, &hourlyValueHistogramsReport}, true, eventCodeBufferMax},
 		{args{&occurrenceMetric, &uniqueDeviceNumericStatsReport}, true, eventCodeBufferMax},