| // Copyright 2020 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. |
| // |
| // Populates privacy parameters for reports. The populated privacy parameters |
| // is temporarily empty due to deprecated Rappor encoding scheme. |
| // |
| // Reports that use the Poisson encoding scheme must manually specify privacy |
| // encoding parameters. |
| // |
| // TODO(b/278932979): update this comment once Poisson encoding parameters are |
| // populated by the registry parser. |
| |
| package privacy |
| |
| import ( |
| "config" |
| "fmt" |
| ) |
| |
| // Holds the privacy parameters for a single private report. |
| type PrivacyParams struct { |
| sparsity uint64 |
| epsilon float64 |
| delta float64 |
| lambda float64 |
| threshold uint32 |
| } |
| |
| func GetPrivacyParamsForReport(m *config.MetricDefinition, r *config.ReportDefinition) (params *PrivacyParams, err error) { |
| sparsity, err := getSparsityForReport(m, r) |
| if err != nil { |
| return |
| } |
| |
| privacyConfig, ok := r.PrivacyConfig.(*config.ReportDefinition_ShuffledDp) |
| if !ok { |
| err = fmt.Errorf("Expected PrivacyConfig to be ShuffledDp, but instead got: %T", r.PrivacyConfig) |
| } |
| c := privacyConfig.ShuffledDp |
| |
| params = &PrivacyParams{ |
| sparsity: sparsity, |
| epsilon: c.Epsilon, |
| delta: c.Delta, |
| lambda: c.PoissonMean * float64(c.ReportingThreshold), |
| threshold: c.ReportingThreshold, |
| } |
| return |
| } |
| |
| // Validates that the specified lambda is sufficient for the desired privacy level. |
| func (p PrivacyParams) Validate() error { |
| calc := NewPoissonParameterCalculator() |
| if calc.IsPrivate(p.lambda, p.sparsity, p.epsilon, p.delta) { |
| return nil |
| } |
| |
| pld, err := newPoissonPld(p.lambda, p.sparsity, calc.discretization, calc.truncation) |
| if err != nil { |
| return fmt.Errorf("newPoissonPld: %v", err) |
| } |
| |
| delta := pld.getDeltaForPrivacyLossDistribution(p.epsilon) |
| return fmt.Errorf("poisson_mean is too small resulting in a delta of %v", delta) |
| } |
| |
| // Returns the number of valid integer values for |report|. For FleetwideOccurrenceCounts, |
| // UniqueDeviceNumericStats, and HourlyValueNumericStats reports, this is the number of integers |
| // in the range [|report.MinValue|, |report.MaxValue|] including both endpoints. For |
| // FleetwideHistograms and StringCounts reports it is the number of integers in the range |
| // [0, |report.MaxCount|]. |
| // |
| // For UniqueDeviceCounts, UniqueDeviceHistograms, and HourlyValueHistograms reports, all observations |
| // contain the same (implicit or explicit) numeric value of 1, so the returned range size is 1. |
| // |
| // A FleetwideMeans report has two separate configured ranges: one for sum values and another for |
| // count values. The returned range size is the maximum of the two range sizes. |
| func GetIntegerRangeSizeForReport(report *config.ReportDefinition) (rangeSize uint64, err error) { |
| switch report.ReportType { |
| case config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS: |
| { |
| size := report.MaxValue - report.MinValue + 1 |
| if size > 0 { |
| rangeSize = uint64(size) |
| } else { |
| return rangeSize, fmt.Errorf("min value %d is larger than max value %d", report.MinValue, report.MaxValue) |
| } |
| } |
| case config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_STRING_COUNTS: |
| rangeSize, err = report.MaxCount+1, nil |
| case config.ReportDefinition_FLEETWIDE_MEANS: |
| { |
| sumSize := report.MaxValue - report.MinValue + 1 |
| if sumSize > 0 { |
| rangeSize = uint64(sumSize) |
| } else { |
| return rangeSize, fmt.Errorf("min value %d is larger than max value %d", report.MinValue, report.MaxValue) |
| } |
| if rangeSize < report.MaxCount { |
| rangeSize = report.MaxCount + 1 |
| } |
| } |
| case config.ReportDefinition_UNIQUE_DEVICE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS: |
| rangeSize = 1 |
| default: |
| return rangeSize, fmt.Errorf("unsupported ReportType: %v", report.ReportType) |
| } |
| return rangeSize, nil |
| |
| } |
| |
| // Returns the sparsity for a given |metric| and |report|. This is the max number of elements in the index vector representation |
| // of a contribution for |report|. |
| func getSparsityForReport(metric *config.MetricDefinition, report *config.ReportDefinition) (sparsity uint64, err error) { |
| numEventVectors, err := getNumEventVectorsPerContribution(metric, report) |
| if err != nil { |
| return sparsity, err |
| } |
| |
| numBuckets, err := getPerEventVectorSparsity(metric, report) |
| if err != nil { |
| return sparsity, err |
| } |
| |
| return numEventVectors * numBuckets, nil |
| } |
| |
| // Returns the max number of event vectors which may be included in a contribution for |report|. |
| func getNumEventVectorsPerContribution(metric *config.MetricDefinition, report *config.ReportDefinition) (numEventVectors uint64, err error) { |
| switch report.ReportType { |
| case config.ReportDefinition_UNIQUE_DEVICE_COUNTS: |
| switch report.LocalAggregationProcedure { |
| case |
| config.ReportDefinition_SELECT_FIRST, |
| config.ReportDefinition_SELECT_MOST_COMMON: |
| numEventVectors, err = 1, nil |
| case config.ReportDefinition_AT_LEAST_ONCE: |
| numEventVectors, err = GetEventVectorBufferMax(metric, report), nil |
| default: |
| err = fmt.Errorf("unexpected LocalAggregationProcedure: %v", report.LocalAggregationProcedure) |
| } |
| case |
| config.ReportDefinition_FLEETWIDE_OCCURRENCE_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_HISTOGRAMS, |
| config.ReportDefinition_FLEETWIDE_MEANS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS, |
| config.ReportDefinition_STRING_COUNTS, |
| config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS: |
| numEventVectors, err = GetEventVectorBufferMax(metric, report), nil |
| |
| default: |
| err = fmt.Errorf("unsupported ReportType: %v", report.ReportType) |
| } |
| return numEventVectors, err |
| } |
| |
| // 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 getPerEventVectorSparsity(metric *config.MetricDefinition, report *config.ReportDefinition) (numBuckets uint64, err error) { |
| switch metric.MetricType { |
| case config.MetricDefinition_OCCURRENCE: |
| numBuckets, err = 1, nil |
| case config.MetricDefinition_INTEGER: |
| switch report.ReportType { |
| case config.ReportDefinition_FLEETWIDE_HISTOGRAMS: |
| numBuckets, err = GetNumHistogramBuckets(report.IntBuckets) |
| // An Observation for FLEETWIDE_MEANS is equivalent to a histogram with 2 buckets per event code: |
| // one representing the sum, the other representing the count. |
| case config.ReportDefinition_FLEETWIDE_MEANS: |
| numBuckets, err = 2, nil |
| case |
| config.ReportDefinition_UNIQUE_DEVICE_HISTOGRAMS, |
| config.ReportDefinition_HOURLY_VALUE_HISTOGRAMS, |
| config.ReportDefinition_UNIQUE_DEVICE_NUMERIC_STATS, |
| config.ReportDefinition_HOURLY_VALUE_NUMERIC_STATS: |
| numBuckets, err = 1, nil |
| } |
| case config.MetricDefinition_INTEGER_HISTOGRAM: |
| numBuckets, err = GetNumHistogramBuckets(metric.IntBuckets) |
| case config.MetricDefinition_STRING: |
| numBuckets, err = getPerEventVectorSparsityForString(report) |
| default: |
| err = fmt.Errorf("unsupported metric type %v", metric.MetricType) |
| } |
| return numBuckets, err |
| } |
| |
| func getPerEventVectorSparsityForString(report *config.ReportDefinition) (sparsity uint64, err error) { |
| if report.ReportType != config.ReportDefinition_STRING_COUNTS && report.ReportType != config.ReportDefinition_UNIQUE_DEVICE_STRING_COUNTS { |
| return 0, fmt.Errorf("expected report of type StringCounts or UniqueDeviceStringCounts, found %v", |
| report.ReportType) |
| } |
| |
| if report.StringSketchParams == nil { |
| return 0, fmt.Errorf("string_sketch_params must be set.") |
| } |
| |
| numHashes := report.StringSketchParams.NumHashes |
| numCellsPerHash := report.StringSketchParams.NumCellsPerHash |
| if numHashes <= 0 || numCellsPerHash <= 0 { |
| return 0, fmt.Errorf("num_hashes and num_cells_per_hash must be set to positive values for string reports with privacy.") |
| } |
| |
| if report.StringBufferMax != 0 && int32(report.StringBufferMax) < numCellsPerHash { |
| sparsity, err = uint64(report.StringBufferMax)*uint64(numHashes), nil |
| } else { |
| sparsity, err = uint64(numCellsPerHash*numHashes), nil |
| } |
| |
| return |
| } |