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