blob: 4742d3f96e69911d129d3b4d45febaae89d36cf2 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package config_validator
import (
"config"
"errors"
"flag"
"fmt"
"math"
"privacy"
)
// This file contains logic to validate list of ReportDefinition protos in MetricDefinition protos.
var (
noHourlyReports = flag.Bool("no_hourly_reports", false, "Don't allow reports that send data hourly in the registry.")
allowReportingInterval = flag.Bool("allow_reporting_interval", false, "Allow the `reporting_interval` to be set in reports.")
validatePoissonMean = flag.Bool("validate_poisson_mean", false, "Validate that the specified poisson_mean is sufficient to provide the specified privacy level.")
)
// errIllegalHourlyReport is returned in validations when no_hourly_reports is set and an hourly report is encountered.
var errIllegalHourlyReport = errors.New("hourly reports are not allowed")
// Maps MetricTypes to valid ReportTypes.
var allowedReportTypes = map[config.MetricDefinition_MetricType]map[config.ReportDefinition_ReportType]bool{
config.MetricDefinition_OCCURRENCE: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS: true,
config.ReportDefinition_UNIQUE_DEVICE_COUNTS: true,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS: true,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS: true,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS: true,
},
config.MetricDefinition_INTEGER: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS: true,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS: true,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS: true,
config.ReportDefinition_FLEETWIDE_MEANS: true,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS: true,
},
config.MetricDefinition_INTEGER_HISTOGRAM: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_FLEETWIDE_HISTOGRAMS: true,
},
config.MetricDefinition_STRING: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_STRING_COUNTS: true,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS: true,
},
}
// For reports that must specify a policy, this is the suggestion we will give.
var SuggestedSystemProfileSelectionPolicy = map[config.ReportDefinition_ReportType]config.SystemProfileSelectionPolicy{
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS: config.SystemProfileSelectionPolicy_SELECT_LAST,
config.ReportDefinition_UNIQUE_DEVICE_COUNTS: config.SystemProfileSelectionPolicy_SELECT_LAST,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS: config.SystemProfileSelectionPolicy_SELECT_LAST,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS: config.SystemProfileSelectionPolicy_SELECT_LAST,
}
// These report types must not set 'system_profile_selection' in the registry.
// The 'system_profile_selection' field must always be set to REPORT_ALL.
// See: //src/bin/config_parser/src/config_parser/expand_defaults.go
var ReportAllOnlyReports = map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS: true,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS: true,
config.ReportDefinition_FLEETWIDE_MEANS: true,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS: true,
config.ReportDefinition_STRING_COUNTS: true,
}
func validateReportDefinitions(m *config.MetricDefinition) error {
reportErrors := newValidationErrors("report")
reportIds := map[uint32]int{}
reportNames := map[string]bool{}
for i, r := range m.Reports {
if _, ok := reportIds[r.Id]; ok {
reportErrors.addError(fmt.Sprintf("Report %d", r.Id), fmt.Errorf("there are two reports with id=%v", r.Id))
continue
}
reportIds[r.Id] = i
if _, ok := reportNames[r.ReportName]; ok {
reportErrors.addError(r.ReportName, fmt.Errorf("there are two reports with name=%v", r.ReportName))
continue
}
reportNames[r.ReportName] = true
if err := validateReportDefinitionForMetric(m, r); err != nil {
reportErrors.addError(r.ReportName, err)
continue
}
if suggestion, ok := SuggestedSystemProfileSelectionPolicy[r.ReportType]; ok {
if r.SystemProfileSelection == config.SystemProfileSelectionPolicy_SELECT_DEFAULT {
reportErrors.addError(r.ReportName, fmt.Errorf("you must manually specify the system_profile_selection for this report (%v may be a good choice)", suggestion))
}
}
if _, ok := ReportAllOnlyReports[r.ReportType]; ok {
// N.B.: config_parser/expand_defaults.go is run before validation, so SELECT_DEFAULT will get expanded to REPORT_ALL for appropriate metrics.
if r.SystemProfileSelection != config.SystemProfileSelectionPolicy_REPORT_ALL {
reportErrors.addError(r.ReportName, fmt.Errorf("reports of type %v should not manually specify system_profile_selection. The default value is REPORT_ALL", r.ReportType))
}
}
}
return reportErrors.err()
}
// Validate a single instance of a ReportDefinition with its associated metric.
func validateReportDefinitionForMetric(m *config.MetricDefinition, r *config.ReportDefinition) error {
for _, deletedReportId := range m.DeletedReportIds {
if r.Id == deletedReportId {
return fmt.Errorf("IDs from previously deleted reports are being reused, choose a new report ID for %v", r.Id)
}
}
// If the customer is using Fuchsia, then there mustn't be an app_version in
// the SystemProfileField. Currently the customers using Fuchsia are
// fuchsia: 1, cobalt_internal: 2147483647 (MaxInt32) and turquoise: 100000.
fuchsiaCustomerIds := []uint32{1, math.MaxInt32, 100000}
for _, id := range fuchsiaCustomerIds {
if m.CustomerId == id {
if err := validateReportHasNoAppVersionField(r); err != nil {
return err
}
}
}
if err := validateReportType(m.MetricType, r.ReportType); err != nil {
return err
}
if err := validateIntBucketsForReport(m, r); err != nil {
return err
}
if err := validateReportDefinition(m, r); err != nil {
return err
}
return nil
}
// Validate that the system profile field does not have and app_version.
func validateReportHasNoAppVersionField(r *config.ReportDefinition) error {
for _, spf := range r.GetSystemProfileField() {
if spf == config.SystemProfileField_APP_VERSION {
return fmt.Errorf("app_version must not be part of the" +
"system_profile_field")
}
}
return nil
}
// Validate a single instance of a ReportDefinition.
func validateReportDefinition(m *config.MetricDefinition, r *config.ReportDefinition) error {
if !validNameRegexp.MatchString(r.ReportName) {
return fmt.Errorf("Invalid report name. Report names must match the regular expression '%v'", validNameRegexp)
}
if r.Id == 0 {
return fmt.Errorf("Report ID of zero is not allowed. Please specify a positive report ID.")
}
if err := validateReportDefinitionForType(m, r); err != nil {
return err
}
if *noHourlyReports {
if err := validateNoHourlyReports(m, r); err != nil {
return err
}
}
return nil
}
// Validates that the MetricType and ReportType provided are compatible.
func validateReportType(mt config.MetricDefinition_MetricType, rt config.ReportDefinition_ReportType) error {
if rt == config.ReportDefinition_REPORT_TYPE_UNSET {
return fmt.Errorf("report_type is not set")
}
rts, ok := allowedReportTypes[mt]
if !ok {
return fmt.Errorf("unknown metric type %s", mt)
}
if _, ok = rts[rt]; !ok {
return fmt.Errorf("reports of type %s cannot be used with metrics of type %s", rt, mt)
}
return nil
}
// Validates ReportDefinitions of some types.
func validateReportDefinitionForType(m *config.MetricDefinition, r *config.ReportDefinition) error {
return validateReports(m, r)
}
// Validate reports.
func validateReports(m *config.MetricDefinition, r *config.ReportDefinition) error {
reportErrors := newValidationErrors("field")
if r.PrivacyLevel == config.ReportDefinition_PRIVACY_LEVEL_UNKNOWN {
reportErrors.addError("privacy_level", fmt.Errorf("The privacy_level field is required for reports of type %s", r.ReportType))
}
if r.PrivacyMechanism == config.ReportDefinition_PRIVACY_MECHANISM_UNSPECIFIED {
reportErrors.addError("privacy_mechanism", fmt.Errorf("The privacy_mechanism field is required for reports of type %s", r.ReportType))
}
if err := validatePoissonFields(r); err != nil {
reportErrors.addError("poisson_mean, num_index_points, or string_sketch_params", err)
}
if err := validateMinValueMaxValue(r); err != nil {
reportErrors.addError("min_value or max_value", err)
}
if err := validateMaxCount(r); err != nil {
reportErrors.addError("max_count", err)
}
if err := validateLocalAggregationPeriod(r); err != nil {
reportErrors.addError("local_aggregation_period", err)
}
if err := validateLocalAggregationProcedure(m, r); err != nil {
reportErrors.addError("local_aggregation_procedure", err)
}
if err := validateExpeditedSending(m, r); err != nil {
reportErrors.addError("expedited_sending", err)
}
if err := validateMaxReleaseStage(m, r); err != nil {
reportErrors.addError("max_release_stage", err)
}
if err := validateReportingInterval(r, *allowReportingInterval); err != nil {
reportErrors.addError("reporting_interval", err)
}
if err := validateExemptFromConsent(r); err != nil {
reportErrors.addError("exempt_from_consent", err)
}
if err := validateMaxPrivateIndex(m, r); err != nil {
reportErrors.addError("metric_dimensions, int_buckets, or num_index_points", err)
}
switch r.ReportType {
case config.ReportDefinition_STRING_COUNTS:
if err := validateStringCountsReportDef(r); err != nil {
reportErrors.addError("other", err)
}
case config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
if err := validateUniqueDeviceStringCountsReportDef(r); err != nil {
reportErrors.addError("other", err)
}
}
if err := validatePrivacyMechanismAndConfig(m, r); err != nil {
reportErrors.addError("other", err)
}
if err := validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r); err != nil {
reportErrors.addError("other", err)
}
return reportErrors.err()
}
func validateLocalAggregationPeriod(r *config.ReportDefinition) error {
switch r.ReportType {
case config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
if r.LocalAggregationPeriod == config.WindowSize_UNSET {
return fmt.Errorf("day based metrics must specify a local_aggregation_period")
}
case config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_STRING_COUNTS:
if r.LocalAggregationPeriod != config.WindowSize_UNSET {
return fmt.Errorf("hour based metrics must not specify a local_aggregation_period")
}
}
return nil
}
func validateLocalAggregationProcedure(m *config.MetricDefinition, r *config.ReportDefinition) error {
switch r.ReportType {
case config.ReportDefinition_UNIQUE_DEVICE_COUNTS:
switch r.LocalAggregationProcedure {
case config.ReportDefinition_AT_LEAST_ONCE,
config.ReportDefinition_SELECT_FIRST,
config.ReportDefinition_SELECT_MOST_COMMON:
default:
return fmt.Errorf("reports of type UNIQUE_DEVICE_COUNTS must specify a local_aggregation_procedure in the list (AT_LEAST_ONCE, SELECT_FIRST, SELECT_MOST_COMMON)")
}
case config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS:
if m.MetricType == config.MetricDefinition_INTEGER {
if r.LocalAggregationProcedure == config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET {
return fmt.Errorf("INTEGER metrics with reports of type %v must specify a local_aggregation_procedure", r.ReportType)
}
switch r.LocalAggregationProcedure {
case config.ReportDefinition_SUM_PROCEDURE,
config.ReportDefinition_MIN_PROCEDURE,
config.ReportDefinition_MAX_PROCEDURE,
config.ReportDefinition_MEAN,
config.ReportDefinition_MEDIAN,
config.ReportDefinition_PERCENTILE_N:
default:
return fmt.Errorf("INTEGER metrics with reports of type %v do not support the local_aggregation_procedure %v. Must be one of (SUM_PROCEDURE, MIN_PROCEDURE, MAX_PROCEDURE, MEAN, MEDIAN, PERCENTILE_N)", r.ReportType, r.LocalAggregationProcedure)
}
if r.LocalAggregationProcedure == config.ReportDefinition_PERCENTILE_N {
if r.LocalAggregationProcedurePercentileN == 0 || r.LocalAggregationProcedurePercentileN >= 100 {
return fmt.Errorf("metrics with PERCENTILE_N local_aggregation_procedure must set local_aggregation_procedure_percentile_n in (0, 100)")
}
}
} else if m.MetricType == config.MetricDefinition_OCCURRENCE {
if r.LocalAggregationProcedure != config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET {
return fmt.Errorf("OCCURRENCE metrics with reports of type %v do not support local_aggregation_procedure", r.ReportType)
}
}
default:
if r.LocalAggregationProcedure != config.ReportDefinition_LOCAL_AGGREGATION_PROCEDURE_UNSET {
return fmt.Errorf("reports of type %v do not support local_aggregation_procedure", r.ReportType)
}
}
if r.LocalAggregationProcedure == config.ReportDefinition_SELECT_FIRST && r.EventVectorBufferMax > 1 {
return fmt.Errorf("reports with local_aggregation_procedure: SELECT_FIRST should not set event_vector_buffer_max to a value other than 1 (the default)")
}
return nil
}
func validateExpeditedSending(m *config.MetricDefinition, r *config.ReportDefinition) error {
if !r.ExpeditedSending {
return nil
}
switch r.ReportType {
case config.ReportDefinition_UNIQUE_DEVICE_COUNTS:
switch r.LocalAggregationProcedure {
case config.ReportDefinition_AT_LEAST_ONCE,
config.ReportDefinition_SELECT_FIRST:
default:
return fmt.Errorf("reports of type UNIQUE_DEVICE_COUNTS with expedited_sending enabled must specify a local_aggregation_procedure in the list (AT_LEAST_ONCE, SELECT_FIRST)")
}
case config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
default:
return fmt.Errorf("reports of type %v do not support expedited_sending", r.ReportType)
}
if r.PrivacyLevel != config.ReportDefinition_NO_ADDED_PRIVACY {
return fmt.Errorf("The expedited_sending can not be enabled unless the privacy_level field is set to NO_ADDED_PRIVACY")
}
return nil
}
func validateMaxReleaseStage(m *config.MetricDefinition, r *config.ReportDefinition) error {
if r.MaxReleaseStage == config.ReleaseStage_RELEASE_STAGE_NOT_SET {
return nil
}
if r.MaxReleaseStage > m.MetaData.MaxReleaseStage {
return fmt.Errorf("report is collected in report stage %v which is later than its metric which has a report stage of %v", r.MaxReleaseStage, m.MetaData.MaxReleaseStage)
}
return nil
}
// Validates there are no hourly reports.
func validateNoHourlyReports(m *config.MetricDefinition, r *config.ReportDefinition) error {
// Currently the hourly reports are identified by the report type only.
switch r.ReportType {
case config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
return nil
case config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_STRING_COUNTS:
if r.ReportingInterval == config.ReportDefinition_DAYS_1 {
return nil
}
fallthrough
case config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_FLEETWIDE_MEANS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS:
return errIllegalHourlyReport
}
return fmt.Errorf("report of type %v is not expected", r.ReportType)
}
// Validates the `reporting_interval` field.
func validateReportingInterval(r *config.ReportDefinition, allowed bool) error {
// Currently only FLEETWIDE_OCCURRENCE_COUNTS reports potentially support
// setting the `reporting_interval` flag.
switch r.ReportingInterval {
case config.ReportDefinition_REPORTING_INTERVAL_UNSET:
return nil
default:
if allowed && (r.ReportType == config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS || r.ReportType == config.ReportDefinition_STRING_COUNTS) {
return nil
}
return fmt.Errorf("setting `reporting_interval` is not allowed for type %v", r.ReportType)
}
}
// Validates the `exempt_from_consent` field.
func validateExemptFromConsent(r *config.ReportDefinition) error {
if !r.ExemptFromConsent {
return nil
}
if r.PrivacyLevel == config.ReportDefinition_NO_ADDED_PRIVACY {
return fmt.Errorf("setting `exempt_from_consent` is not allowed unless differential privacy is enabled")
}
return nil
}
// Validates that the max private index value fits in a signed int32.
func validateMaxPrivateIndex(m *config.MetricDefinition, r *config.ReportDefinition) error {
if r.PrivacyLevel == config.ReportDefinition_NO_ADDED_PRIVACY {
return nil
}
numPrivateIndices, err := privacy.GetNumPrivateIndices(m, r)
if err != nil {
return err
}
if numPrivateIndices >= math.MaxInt32 {
return fmt.Errorf("privacy enabled reports can not handle more than a signed int32 of possible values, try reducing the number of dimensions/events, histogram buckets, or num_index_points, current number of indices is: %v", numPrivateIndices)
}
return nil
}
/////////////////////////////////////////////////////////////////
// Validation for specific report types:
/////////////////////////////////////////////////////////////////
func validateStringCountsReportDef(r *config.ReportDefinition) error {
if r.IntBuckets != nil {
return fmt.Errorf("int_buckets can not be specified for report of type %s.", r.ReportType)
}
if r.StringBufferMax == 0 {
return fmt.Errorf("no string_buffer_max specified for report of type %s.", r.ReportType)
}
return nil
}
func validateUniqueDeviceStringCountsReportDef(r *config.ReportDefinition) error {
if r.IntBuckets != nil {
return fmt.Errorf("int_buckets can not be specified for report of type %s.", r.ReportType)
}
if r.StringBufferMax == 0 {
return fmt.Errorf("no string_buffer_max specified for report of type %s.", r.ReportType)
}
return nil
}
/////////////////////////////////////////////////////////////////
// Validation for specific fields of report definitions
//
// These functions are oblivious to the report's type and should
// only be called with reports for which that field is required.
/////////////////////////////////////////////////////////////////
func validatePoissonFields(r *config.ReportDefinition) error {
if r.PrivacyLevel != config.ReportDefinition_NO_ADDED_PRIVACY {
if r.PoissonMean == 0.0 {
return fmt.Errorf("poisson_mean must be set when privacy_level is not NO_ADDED_PRIVACY")
}
if r.PoissonMean < 0.0 {
return fmt.Errorf("poisson_mean should not be negative")
}
if r.NumIndexPoints == 0 {
return fmt.Errorf("num_index_points must be set when privacy_level is not NO_ADDED_PRIVACY")
}
if r.ReportType == config.ReportDefinition_STRING_COUNTS || r.ReportType == config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS {
if r.GetStringSketchParams() == nil {
return fmt.Errorf("string_sketch_params must be set for string reports when privacy_level is not NO_ADDED_PRIVACY")
}
}
} else {
if r.PoissonMean != 0.0 {
return fmt.Errorf("poisson_mean should not be set when privacy_level is not NO_ADDED_PRIVACY")
}
}
return nil
}
func validateMinValueMaxValue(r *config.ReportDefinition) error {
switch r.PrivacyLevel {
case
config.ReportDefinition_PRIVACY_LEVEL_UNKNOWN:
return nil
case
config.ReportDefinition_NO_ADDED_PRIVACY:
if r.MaxValue != 0 || r.MinValue != 0 {
return fmt.Errorf("min_value and max_value should not be set for reports with no added privacy")
}
case
config.ReportDefinition_LOW_PRIVACY,
config.ReportDefinition_MEDIUM_PRIVACY,
config.ReportDefinition_HIGH_PRIVACY:
switch r.ReportType {
case
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_FLEETWIDE_MEANS:
if r.MaxValue == 0 && r.MinValue == 0 {
return fmt.Errorf("min_value and max_value must be set for reports of type %s", r.ReportType)
}
if r.MaxValue < r.MinValue {
return fmt.Errorf("min_value must be less than or equal to max_value: %v > %v", r.MinValue, r.MaxValue)
}
case
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_STRING_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
if r.MinValue != 0 || r.MaxValue != 0 {
return fmt.Errorf("min_value and max_value should not be set for reports of type %s", r.ReportType)
}
default:
return fmt.Errorf("unrecognized report type %s", r.ReportType)
}
}
return nil
}
func validateMaxCount(r *config.ReportDefinition) error {
switch r.PrivacyLevel {
case
config.ReportDefinition_PRIVACY_LEVEL_UNKNOWN:
return nil
case
config.ReportDefinition_NO_ADDED_PRIVACY:
if r.MaxCount != 0 {
return fmt.Errorf("max_count should not be set for reports with no added privacy")
}
case
config.ReportDefinition_LOW_PRIVACY,
config.ReportDefinition_MEDIUM_PRIVACY,
config.ReportDefinition_HIGH_PRIVACY:
switch r.ReportType {
case
config.ReportDefinition_FLEETWIDE_HISTOGRAMS,
config.ReportDefinition_STRING_COUNTS,
config.ReportDefinition_FLEETWIDE_MEANS:
if r.MaxCount == 0 {
return fmt.Errorf("max_count must be set for reports of type %s", r.ReportType)
}
case
config.ReportDefinition_UNIQUE_DEVICE_COUNTS,
config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS,
config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS,
config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS,
config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS,
config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS,
config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS:
if r.MaxCount != 0 {
return fmt.Errorf("max_count should not be set for reports of type %s", r.ReportType)
}
default:
return fmt.Errorf("unrecognized report type %s", r.ReportType)
}
}
return nil
}
// Validates that new privacy field values matches the legacy private field values. This validation assumes legacy and new privacy fields are already validated.
func validateNewPrivacyFieldAndLegacyPrivacyFieldMatch(r *config.ReportDefinition) error {
switch r.PrivacyLevel {
case config.ReportDefinition_NO_ADDED_PRIVACY:
if r.PrivacyMechanism != config.ReportDefinition_DE_IDENTIFICATION {
return fmt.Errorf("privacy_mechanism should set to DE_IDENTIFICATION when privacy_level is NO_ADDED_PRIVACY")
}
case config.ReportDefinition_LOW_PRIVACY,
config.ReportDefinition_MEDIUM_PRIVACY,
config.ReportDefinition_HIGH_PRIVACY:
if r.PrivacyMechanism != config.ReportDefinition_SHUFFLED_DIFFERENTIAL_PRIVACY {
return fmt.Errorf("privacy_mechanism should set to SHUFFLED_DIFFERENTIAL_PRIVACY when privacy_level is not NO_ADDED_PRIVACY")
}
switch op := r.PrivacyConfig.(type) {
case *config.ReportDefinition_ShuffledDp:
if op.ShuffledDp.PoissonMean != r.PoissonMean {
return fmt.Errorf("the value of poission_mean in shuffled_dp should match the value of poission_mean")
}
if op.ShuffledDp.ReportingThreshold != r.ReportingThreshold {
return fmt.Errorf("the value of reporting_threshold in shuffled_dp should match the value of reporting_threshold")
}
default:
return fmt.Errorf("Unhandled privacy config")
}
}
return nil
}
func validatePrivacyMechanismAndConfig(m *config.MetricDefinition, r *config.ReportDefinition) error {
switch r.PrivacyMechanism {
case config.ReportDefinition_DE_IDENTIFICATION:
if r.PrivacyConfig != nil {
return fmt.Errorf("you shouldn't specify privacy config when select DE_IDENTIFICATION")
}
case config.ReportDefinition_SHUFFLED_DIFFERENTIAL_PRIVACY:
if r.PrivacyConfig != nil {
switch op := r.PrivacyConfig.(type) {
case *config.ReportDefinition_ShuffledDp:
if err := validateShuffledDpConfig(op.ShuffledDp); err != nil {
return err
}
if *validatePoissonMean {
return validateReportPoissonMean(m, r)
}
return nil
default:
return fmt.Errorf("you specified the wrong privacy config, you should specify shuffled_dp privacy config when select SHUFFLED_DIFFERENTIAL_PRIVACY")
}
}
return fmt.Errorf("you should specify shuffled_dp privacy config when select SHUFFLED_DIFFERENTIAL_PRIVACY")
}
return nil
}
func validateReportPoissonMean(m *config.MetricDefinition, r *config.ReportDefinition) error {
p, err := privacy.GetPrivacyParamsForReport(m, r)
if err != nil {
return err
}
if err := p.Validate(); err != nil {
return err
}
return nil
}
func validateShuffledDpConfig(c *config.ReportDefinition_ShuffledDifferentialPrivacyConfig) error {
if c.Epsilon <= 0 {
return fmt.Errorf("epsilon is %f, must be > 0", c.Epsilon)
}
if c.Delta <= 0 || c.Delta >= 1 {
return fmt.Errorf("delta %e must be > 0 and < 1", c.Delta)
}
if c.ReportingThreshold <= 0 {
return fmt.Errorf("reporting threshold must be > 0")
}
if c.PoissonMean <= 0.0 {
return fmt.Errorf("poisson_mean must be set and should not be negative")
}
return nil
}