blob: 17aa1eb6af77885345e404894a7d530b4548d502 [file] [log] [blame]
// 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
}