• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.cobalt.observations;
18 
19 import static java.util.Objects.requireNonNull;
20 
21 import com.android.cobalt.data.EventRecordAndSystemProfile;
22 import com.android.cobalt.data.EventVector;
23 import com.android.cobalt.data.ObservationGenerator;
24 import com.android.cobalt.logging.CobaltOperationLogger;
25 import com.android.cobalt.system.SystemData;
26 
27 import com.google.cobalt.AggregateValue;
28 import com.google.cobalt.MetricDefinition;
29 import com.google.cobalt.Observation;
30 import com.google.cobalt.ObservationMetadata;
31 import com.google.cobalt.ObservationToEncrypt;
32 import com.google.cobalt.PrivateIndexObservation;
33 import com.google.cobalt.ReportDefinition;
34 import com.google.cobalt.ReportParticipationObservation;
35 import com.google.cobalt.SystemProfile;
36 import com.google.cobalt.UnencryptedObservationBatch;
37 import com.google.common.collect.ImmutableList;
38 import com.google.common.collect.ImmutableListMultimap;
39 
40 import java.security.SecureRandom;
41 
42 /** Generates private observations from event data and report privacy parameters. */
43 final class PrivateObservationGenerator implements ObservationGenerator {
44     /**
45      * Interface to encode an aggregated value as a private index observation for private reports.
46      */
47     interface Encoder {
48         /**
49          * Encodes one event and aggregated value as a single private observation.
50          *
51          * <p>Note, retuning a single private observation implies that report types that have
52          * multiple values in their {@link AggregateValue}, like histograms, aren't supported.
53          *
54          * @param eventVector the event vector to encode
55          * @param aggregateValue the aggregated value to encode
56          * @return the privacy encoded observation
57          */
encode(EventVector eventVector, AggregateValue aggregateValue)58         PrivateIndexObservation encode(EventVector eventVector, AggregateValue aggregateValue);
59     }
60 
61     private final SystemData mSystemData;
62     private final PrivacyGenerator mPrivacyGenerator;
63     private final SecureRandom mSecureRandom;
64     private final Encoder mEncoder;
65     private final CobaltOperationLogger mOperationLogger;
66     private final int mCustomerId;
67     private final int mProjectId;
68     private final MetricDefinition mMetric;
69     private final ReportDefinition mReport;
70 
PrivateObservationGenerator( SystemData systemData, PrivacyGenerator privacyGenerator, SecureRandom secureRandom, Encoder encoder, CobaltOperationLogger operationLogger, int customerId, int projectId, MetricDefinition metric, ReportDefinition report)71     PrivateObservationGenerator(
72             SystemData systemData,
73             PrivacyGenerator privacyGenerator,
74             SecureRandom secureRandom,
75             Encoder encoder,
76             CobaltOperationLogger operationLogger,
77             int customerId,
78             int projectId,
79             MetricDefinition metric,
80             ReportDefinition report) {
81         this.mSystemData = requireNonNull(systemData);
82         this.mPrivacyGenerator = requireNonNull(privacyGenerator);
83         this.mSecureRandom = requireNonNull(secureRandom);
84         this.mEncoder = requireNonNull(encoder);
85         this.mOperationLogger = requireNonNull(operationLogger);
86         this.mCustomerId = customerId;
87         this.mProjectId = projectId;
88         this.mMetric = requireNonNull(metric);
89         this.mReport = requireNonNull(report);
90     }
91 
92     /**
93      * Generate the private observations that for a report and day.
94      *
95      * @param dayIndex the day index to generate observations for
96      * @param allEventData the data for events that occurred that are relevant to the day and Report
97      * @return the observations to store in the DB for later sending, contained in
98      *     UnencryptedObservationBatches with their metadata
99      */
100     @Override
generateObservations( int dayIndex, ImmutableListMultimap<SystemProfile, EventRecordAndSystemProfile> allEventData)101     public ImmutableList<UnencryptedObservationBatch> generateObservations(
102             int dayIndex,
103             ImmutableListMultimap<SystemProfile, EventRecordAndSystemProfile> allEventData) {
104         if (allEventData.isEmpty()) {
105             return ImmutableList.of(
106                     generateObservations(
107                             dayIndex,
108                             // Use the current system profile since none is provided.
109                             mSystemData.filteredSystemProfile(mReport),
110                             ImmutableList.of()));
111         }
112 
113         ImmutableList.Builder<UnencryptedObservationBatch> batches = ImmutableList.builder();
114         for (SystemProfile systemProfile : allEventData.keySet()) {
115             batches.add(
116                     generateObservations(dayIndex, systemProfile, allEventData.get(systemProfile)));
117         }
118 
119         return batches.build();
120     }
121 
122     /**
123      * Generate an observation batch from events for a report, day, and system profile.
124      *
125      * @param dayIndex the day observations are being generated for
126      * @param systemProfile the system profile of the observations
127      * @param events the events
128      * @return an UnencryptedObservation batch holding the generated observations
129      */
generateObservations( int dayIndex, SystemProfile systemProfile, ImmutableList<EventRecordAndSystemProfile> events)130     private UnencryptedObservationBatch generateObservations(
131             int dayIndex,
132             SystemProfile systemProfile,
133             ImmutableList<EventRecordAndSystemProfile> events) {
134         if (mReport.getEventVectorBufferMax() != 0
135                 && events.size() > mReport.getEventVectorBufferMax()) {
136             // Each EventRecordAndSystemProfile contains a unique event vector for the system
137             // profile and day so the number of events can be compared to the event vector
138             // buffer max of the report.
139             mOperationLogger.logEventVectorBufferMaxExceeded(mMetric.getId(), mReport.getId());
140             events = events.subList(0, (int) mReport.getEventVectorBufferMax());
141         }
142 
143         ImmutableList.Builder<Observation> observations = ImmutableList.builder();
144         for (EventRecordAndSystemProfile event : events) {
145             observations.add(
146                     Observation.newBuilder()
147                             .setPrivateIndex(
148                                     mEncoder.encode(event.eventVector(), event.aggregateValue()))
149                             .setRandomId(RandomId.generate(mSecureRandom))
150                             .build());
151         }
152         for (PrivateIndexObservation privateIndex :
153                 mPrivacyGenerator.generateNoise(maxIndexForReport(), mReport)) {
154             observations.add(
155                     Observation.newBuilder()
156                             .setPrivateIndex(privateIndex)
157                             .setRandomId(RandomId.generate(mSecureRandom))
158                             .build());
159         }
160         observations.add(
161                 Observation.newBuilder()
162                         .setReportParticipation(ReportParticipationObservation.getDefaultInstance())
163                         .setRandomId(RandomId.generate(mSecureRandom))
164                         .build());
165 
166         ImmutableList.Builder<ObservationToEncrypt> toEncrypt = ImmutableList.builder();
167         boolean setContributionId = true;
168         for (Observation observation : observations.build()) {
169             ObservationToEncrypt.Builder builder = ObservationToEncrypt.newBuilder();
170             builder.setObservation(observation);
171             if (setContributionId) {
172                 builder.setContributionId(RandomId.generate(mSecureRandom));
173             }
174 
175             // Reports with privacy enabled split a single contribution across multiple
176             // observations, both private and participation. However, only 1 needs the contribution
177             // id set.
178             toEncrypt.add(builder.build());
179             setContributionId = false;
180         }
181 
182         return UnencryptedObservationBatch.newBuilder()
183                 .setMetadata(
184                         ObservationMetadata.newBuilder()
185                                 .setCustomerId(mCustomerId)
186                                 .setProjectId(mProjectId)
187                                 .setMetricId(mMetric.getId())
188                                 .setReportId(mReport.getId())
189                                 .setDayIndex(dayIndex)
190                                 .setSystemProfile(systemProfile))
191                 .addAllUnencryptedObservations(toEncrypt.build())
192                 .build();
193     }
194 
maxIndexForReport()195     private int maxIndexForReport() {
196         return PrivateIndexCalculations.getNumEventVectors(mMetric.getMetricDimensionsList())
197                         * mReport.getNumIndexPoints()
198                 - 1;
199     }
200 }
201