| // 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 |
| } |