blob: 47a67cfccc9e649e51b7e8057b4b77a82eefb87d [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"
"fmt"
)
// This file contains logic to validate list of ReportDefinition protos in MetricDefinition protos.
// Maps MetricTypes to valid ReportTypes.
var allowedReportTypes = map[config.MetricDefinition_MetricType]map[config.ReportDefinition_ReportType]bool{
config.MetricDefinition_EVENT_OCCURRED: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT: true,
config.ReportDefinition_UNIQUE_N_DAY_ACTIVES: true,
},
config.MetricDefinition_EVENT_COUNT: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_EVENT_COMPONENT_OCCURRENCE_COUNT: true,
config.ReportDefinition_INT_RANGE_HISTOGRAM: true,
config.ReportDefinition_NUMERIC_AGGREGATION: true,
config.ReportDefinition_PER_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_PER_DEVICE_HISTOGRAM: true,
},
config.MetricDefinition_ELAPSED_TIME: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_INT_RANGE_HISTOGRAM: true,
config.ReportDefinition_NUMERIC_AGGREGATION: true,
config.ReportDefinition_NUMERIC_PERF_RAW_DUMP: true,
config.ReportDefinition_PER_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_PER_DEVICE_HISTOGRAM: true,
},
config.MetricDefinition_FRAME_RATE: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_INT_RANGE_HISTOGRAM: true,
config.ReportDefinition_NUMERIC_AGGREGATION: true,
config.ReportDefinition_NUMERIC_PERF_RAW_DUMP: true,
config.ReportDefinition_PER_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_PER_DEVICE_HISTOGRAM: true,
},
config.MetricDefinition_MEMORY_USAGE: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_INT_RANGE_HISTOGRAM: true,
config.ReportDefinition_NUMERIC_AGGREGATION: true,
config.ReportDefinition_NUMERIC_PERF_RAW_DUMP: true,
config.ReportDefinition_PER_DEVICE_NUMERIC_STATS: true,
config.ReportDefinition_PER_DEVICE_HISTOGRAM: true,
},
config.MetricDefinition_INT_HISTOGRAM: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_INT_RANGE_HISTOGRAM: true,
},
config.MetricDefinition_CUSTOM: map[config.ReportDefinition_ReportType]bool{
config.ReportDefinition_CUSTOM_RAW_DUMP: true,
},
}
func validateReportDefinitions(m config.MetricDefinition) error {
reportIds := map[uint32]int{}
reportNames := map[string]bool{}
for i, r := range m.Reports {
if _, ok := reportIds[r.Id]; ok {
return fmt.Errorf("There are two reports with id=%v.", r.Id)
}
reportIds[r.Id] = i
if _, ok := reportNames[r.ReportName]; ok {
return fmt.Errorf("There are two reports with name=%v.", r.ReportName)
}
reportNames[r.ReportName] = true
if err := validateReportDefinitionForMetric(m, *r); err != nil {
return fmt.Errorf("Error validating report '%s': %v", r.ReportName, err)
}
}
return nil
}
// Validate a single instance of a ReportDefinition with its associated metric.
func validateReportDefinitionForMetric(m config.MetricDefinition, r config.ReportDefinition) error {
if err := validateReportType(m.MetricType, r.ReportType); err != nil {
return err
}
if err := validateReportDefinition(r); err != nil {
return err
}
return nil
}
// Validate a single instance of a ReportDefinition.
func validateReportDefinition(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(r); err != nil {
return fmt.Errorf("Report %s: %v", r.ReportName, 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
}
/////////////////////////////////////////////////////////////////
// Validation for specific report types:
/////////////////////////////////////////////////////////////////
// Validates ReportDefinitions of some types.
func validateReportDefinitionForType(r config.ReportDefinition) error {
switch r.ReportType {
case config.ReportDefinition_SIMPLE_OCCURRENCE_COUNT:
return validateSimpleOccurrenceCountReportDef(r)
case config.ReportDefinition_UNIQUE_N_DAY_ACTIVES:
return validateUniqueActivesReportDef(r)
case config.ReportDefinition_PER_DEVICE_NUMERIC_STATS:
return validatePerDeviceNumericStatsReportDef(r)
case config.ReportDefinition_PER_DEVICE_HISTOGRAM:
return validatePerDeviceHistogramReportDef(r)
case config.ReportDefinition_NUMERIC_AGGREGATION:
return validateNumericAggregationReportDef(r)
}
return nil
}
func validateSimpleOccurrenceCountReportDef(r config.ReportDefinition) error {
if err := validateLocalPrivacyNoiseLevel(r); err != nil {
return err
}
return nil
}
func validateUniqueActivesReportDef(r config.ReportDefinition) error {
if err := validateWindowSize(r); err != nil {
return err
}
if err := validateLocalPrivacyNoiseLevel(r); err != nil {
return err
}
return nil
}
func validatePerDeviceNumericStatsReportDef(r config.ReportDefinition) error {
if err := validateWindowSize(r); err != nil {
return err
}
return nil
}
func validatePerDeviceHistogramReportDef(r config.ReportDefinition) error {
if r.IntBuckets == nil {
return fmt.Errorf("No int_buckets specified for report of type %s.", r.ReportType)
}
if err := validateWindowSize(r); err != nil {
return err
}
return nil
}
func validateNumericAggregationReportDef(r config.ReportDefinition) error {
if err := validatePercentiles(r); err != nil {
return err
}
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.
/////////////////////////////////////////////////////////////////
// Check that the report definition has a nonempty window_size field, and that none of the window sizes
// are UNSET.
func validateWindowSize(r config.ReportDefinition) error {
if len(r.WindowSize) == 0 {
return fmt.Errorf("No window_size specified for report of type %s.", r.ReportType)
}
for _, ws := range r.WindowSize {
if ws == config.WindowSize_UNSET {
return fmt.Errorf("Unset window size found for report of type %s.", r.ReportType)
}
}
return nil
}
// Check that the local_privacy_noise_level field is set.
func validateLocalPrivacyNoiseLevel(r config.ReportDefinition) error {
if r.LocalPrivacyNoiseLevel == config.ReportDefinition_NOISE_LEVEL_UNSET {
return fmt.Errorf("No local_privacy_noise_level specified for report of type %s.", r.ReportType)
}
return nil
}
// Check that the percentiles are between 0 and 100.
func validatePercentiles(r config.ReportDefinition) error {
for _, percentage := range r.Percentiles {
if percentage < 0 || percentage > 100 {
return fmt.Errorf(
"Percentiles must be in the range 0-100. Received value %d for report of type %s.",
percentage, r.ReportType)
}
}
return nil
}