Split private and non-private observation generation
Private and non-private observation generation is generalized such the
aggregation procedures for each only need to provide a way to encode
aggregated values as observations.
Bug: 303091672
Test: atest AdServicesCobaltUnitTests
Change-Id: I6d85e7d4ccd98dd0523b59c165f8583c6b6a598d
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/impl/CobaltPeriodicJobImpl.java b/adservices/libraries/cobalt/java/com/android/cobalt/impl/CobaltPeriodicJobImpl.java
index e74111b..30a8e14 100644
--- a/adservices/libraries/cobalt/java/com/android/cobalt/impl/CobaltPeriodicJobImpl.java
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/impl/CobaltPeriodicJobImpl.java
@@ -31,7 +31,7 @@
import com.android.cobalt.data.ObservationStoreEntity;
import com.android.cobalt.data.ReportKey;
import com.android.cobalt.domain.Project;
-import com.android.cobalt.observations.CountObservationGenerator;
+import com.android.cobalt.observations.ObservationGeneratorFactory;
import com.android.cobalt.observations.PrivacyGenerator;
import com.android.cobalt.system.CobaltClock;
import com.android.cobalt.system.SystemClock;
@@ -46,7 +46,6 @@
import com.google.cobalt.ObservationMetadata;
import com.google.cobalt.ReleaseStage;
import com.google.cobalt.ReportDefinition;
-import com.google.cobalt.ReportDefinition.ReportType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.FluentFuture;
@@ -87,12 +86,10 @@
private final Duration mUploadDoneDelay;
private final SystemClock mSystemClock;
private final boolean mEnabled;
- private final SystemData mSystemData;
- private final PrivacyGenerator mPrivacyGenerator;
- private final SecureRandom mSecureRandom;
private final Uploader mUploader;
private final Encrypter mEncrypter;
private final ByteString mApiKey;
+ private final ObservationGeneratorFactory mObservationGeneratorFactory;
public CobaltPeriodicJobImpl(
@NonNull Project project,
@@ -118,12 +115,15 @@
mUploadDoneDelay = Objects.requireNonNull(uploadDoneDelay);
mSystemClock = Objects.requireNonNull(systemClock);
mEnabled = enabled;
- mSystemData = Objects.requireNonNull(systemData);
- mPrivacyGenerator = Objects.requireNonNull(privacyGenerator);
- mSecureRandom = Objects.requireNonNull(secureRandom);
mUploader = Objects.requireNonNull(uploader);
mEncrypter = Objects.requireNonNull(encrypter);
mApiKey = Objects.requireNonNull(apiKey);
+ mObservationGeneratorFactory =
+ new ObservationGeneratorFactory(
+ mProject,
+ Objects.requireNonNull(systemData),
+ Objects.requireNonNull(privacyGenerator),
+ Objects.requireNonNull(secureRandom));
}
/**
@@ -180,23 +180,11 @@
metric.getId(),
report.getId());
relevantReports.add(reportKey);
- if (report.getReportType() != ReportType.FLEETWIDE_OCCURRENCE_COUNTS) {
- // Skip observation generation after recording the report is relevant in case
- // a disabled report may be enabled again.
- continue;
- }
logInfo(
"Generating observations for day %s for report %s",
dayIndexToGenerate, reportKey);
ObservationGenerator generator =
- new CountObservationGenerator(
- mSystemData,
- mPrivacyGenerator,
- mSecureRandom,
- mProject.getCustomerId(),
- mProject.getProjectId(),
- metric,
- report);
+ mObservationGeneratorFactory.getObservationGenerator(metric, report);
results.add(
mDataService.generateCountObservations(
reportKey, dayIndexToGenerate, dayIndexLoggerEnabled, generator));
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/CountObservationGenerator.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/CountObservationGenerator.java
deleted file mode 100644
index ce393e5..0000000
--- a/adservices/libraries/cobalt/java/com/android/cobalt/observations/CountObservationGenerator.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cobalt.observations;
-
-import static com.android.cobalt.collect.ImmutableHelpers.toImmutableList;
-
-import android.annotation.NonNull;
-
-import com.android.cobalt.data.EventRecordAndSystemProfile;
-import com.android.cobalt.data.ObservationGenerator;
-import com.android.cobalt.system.SystemData;
-
-import com.google.cobalt.IntegerObservation;
-import com.google.cobalt.MetricDefinition;
-import com.google.cobalt.Observation;
-import com.google.cobalt.ObservationMetadata;
-import com.google.cobalt.ObservationToEncrypt;
-import com.google.cobalt.PrivateIndexObservation;
-import com.google.cobalt.ReportDefinition;
-import com.google.cobalt.ReportDefinition.PrivacyLevel;
-import com.google.cobalt.ReportParticipationObservation;
-import com.google.cobalt.SystemProfile;
-import com.google.cobalt.UnencryptedObservationBatch;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.protobuf.ByteString;
-
-import java.security.SecureRandom;
-import java.util.Objects;
-
-/** Observation generator for FLEETWIDE_OCCURRENCE_COUNTS. */
-public final class CountObservationGenerator implements ObservationGenerator {
- private final SystemData mSystemData;
- private final PrivacyGenerator mPrivacyGenerator;
- private final SecureRandom mSecureRandom;
- private final int mCustomerId;
- private final int mProjectId;
- private final MetricDefinition mMetric;
- private final ReportDefinition mReport;
-
- public CountObservationGenerator(
- @NonNull SystemData systemData,
- @NonNull PrivacyGenerator privacyGenerator,
- @NonNull SecureRandom secureRandom,
- int customerId,
- int projectId,
- @NonNull MetricDefinition metric,
- @NonNull ReportDefinition report) {
-
- this.mSystemData = Objects.requireNonNull(systemData);
- this.mPrivacyGenerator = Objects.requireNonNull(privacyGenerator);
- this.mSecureRandom = Objects.requireNonNull(secureRandom);
- this.mCustomerId = customerId;
- this.mProjectId = projectId;
- this.mMetric = Objects.requireNonNull(metric);
- this.mReport = Objects.requireNonNull(report);
- }
-
- /**
- * Generate the observations that occurred for a single day and report.
- *
- * @param dayIndex the day to generate observations for
- * @param allEventData per-system profile event data being aggregated
- * @return observations to be stored in the database for later sending
- */
- @Override
- public ImmutableList<UnencryptedObservationBatch> generateObservations(
- int dayIndex,
- ImmutableListMultimap<SystemProfile, EventRecordAndSystemProfile> allEventData) {
- if (allEventData.isEmpty() && mReport.getPrivacyLevel() != PrivacyLevel.NO_ADDED_PRIVACY) {
- // Reports with privacy enabled need to send fabricated observations and a report
- // participation observation even if no real observations are present.
- return ImmutableList.of(
- generateObservations(
- dayIndex,
- // Use the current system profile since none is provided.
- mSystemData.filteredSystemProfile(mReport),
- ImmutableList.of()));
- }
-
- return allEventData.keySet().stream()
- .map(
- systemProfile ->
- generateObservations(
- dayIndex, systemProfile, allEventData.get(systemProfile)))
- .collect(toImmutableList());
- }
-
- /**
- * Generate an observation batch from events for a given day and system profile.
- *
- * @param dayIndex the day observations are being generated for
- * @param systemProfile the system profile of the observations
- * @param eventData the events
- * @return an UnencryptedObservation batch holding the generated observations
- */
- private UnencryptedObservationBatch generateObservations(
- int dayIndex,
- SystemProfile systemProfile,
- ImmutableList<EventRecordAndSystemProfile> eventData) {
- if (mReport.getEventVectorBufferMax() != 0
- && eventData.size() > mReport.getEventVectorBufferMax()) {
- // Each EventRecordAndSystemProfile contains a unique event vector for the system
- // profile and day so the number of events can be compared to the event vector buffer
- // max of the report.
- eventData = eventData.subList(0, (int) mReport.getEventVectorBufferMax());
- }
-
- ImmutableList<ObservationToEncrypt> observations =
- mReport.getPrivacyLevel() != PrivacyLevel.NO_ADDED_PRIVACY
- ? buildPrivateObservations(eventData)
- : buildNonPrivateObservations(eventData);
-
- return UnencryptedObservationBatch.newBuilder()
- .setMetadata(
- ObservationMetadata.newBuilder()
- .setCustomerId(mCustomerId)
- .setProjectId(mProjectId)
- .setMetricId(mMetric.getId())
- .setReportId(mReport.getId())
- .setDayIndex(dayIndex)
- .setSystemProfile(systemProfile))
- .addAllUnencryptedObservations(observations)
- .build();
- }
-
- /** Securely generate 8 random bytes. */
- private ByteString generateSecureRandomByteString() {
- byte[] randomId = new byte[8];
- mSecureRandom.nextBytes(randomId);
- return ByteString.copyFrom(randomId);
- }
-
- /**
- * Build a list of non-private observations from event data.
- *
- * @param eventData the event data to encode
- * @return ObservationToEncrypts containing non-private observations
- */
- private ImmutableList<ObservationToEncrypt> buildNonPrivateObservations(
- ImmutableList<EventRecordAndSystemProfile> eventData) {
- IntegerObservation.Builder integerBuilder = IntegerObservation.newBuilder();
- eventData.stream().map(this::buildIntegerValue).forEach(integerBuilder::addValues);
- Observation observation =
- Observation.newBuilder()
- .setInteger(integerBuilder)
- .setRandomId(generateSecureRandomByteString())
- .build();
- return ImmutableList.of(
- // Reports without privacy only make a single contribution so the id is set.
- buildObservationToEncrypt(observation, /* setContributionId= */ true));
- }
-
- /**
- * Build an intger observation value from an event vector and aggregate value in an event.
- *
- * @param countEvent the event
- * @return an IntegerObservation.Value
- */
- private IntegerObservation.Value buildIntegerValue(EventRecordAndSystemProfile event) {
- return IntegerObservation.Value.newBuilder()
- .addAllEventCodes(event.eventVector().eventCodes())
- .setValue(event.aggregateValue().getIntegerValue())
- .build();
- }
-
- /**
- * Build a list of private observations to encrypt from a set of event indices.
- *
- * @param eventData the events which occurred
- * @return a list of observations to encrypt, including fabricated observations
- */
- private ImmutableList<ObservationToEncrypt> buildPrivateObservations(
- ImmutableList<EventRecordAndSystemProfile> eventData) {
- ImmutableList<Integer> eventIndices =
- eventData.stream()
- .map(countEvent -> asIndex(countEvent))
- .collect(toImmutableList());
- ImmutableList<Integer> allIndices =
- mPrivacyGenerator.addNoise(eventIndices, maxIndexForReport(), mReport);
- ImmutableList<Observation> observations =
- ImmutableList.<Observation>builder()
- .addAll(allIndices.stream().map(i -> buildPrivateObservation(i)).iterator())
- .add(buildParticipationObservation())
- .build();
- ImmutableList.Builder<ObservationToEncrypt> toEncrypt = ImmutableList.builder();
- boolean setContributionId = true;
- for (int i = 0; i < observations.size(); ++i) {
- // Reports with privacy enabled split a single contribution across multiple
- // observations, both private and participation. However, only 1 needs the
- // contribution id set.
- toEncrypt.add(buildObservationToEncrypt(observations.get(i), setContributionId));
- setContributionId = false;
- }
-
- return toEncrypt.build();
- }
-
- /**
- * Turn an index into an observation.
- *
- * @param index the private index
- * @return an Observation that contains a private observation
- */
- private Observation buildPrivateObservation(int index) {
- return Observation.newBuilder()
- .setPrivateIndex(PrivateIndexObservation.newBuilder().setIndex(index).build())
- .setRandomId(generateSecureRandomByteString())
- .build();
- }
-
- /**
- * Create a report participation observation.
- *
- * @return an Observation that contains a report participation observation
- */
- private Observation buildParticipationObservation() {
- return Observation.newBuilder()
- .setReportParticipation(ReportParticipationObservation.getDefaultInstance())
- .setRandomId(generateSecureRandomByteString())
- .build();
- }
-
- /**
- * Create an observation to encrypt and optionally set the contribution id
- *
- * @param observation the observation
- * @param setContributionId whether the contribution id should be set
- * @return an ObservationToEncrypt
- */
- private ObservationToEncrypt buildObservationToEncrypt(
- Observation observation, boolean setContributionId) {
- ObservationToEncrypt.Builder builder = ObservationToEncrypt.newBuilder();
- builder.setObservation(observation);
- if (setContributionId) {
- builder.setContributionId(generateSecureRandomByteString());
- }
-
- return builder.build();
- }
-
- /**
- * Convert an event into a private index.
- *
- * @param event the event to convert
- * @return the index of the event
- */
- private int asIndex(EventRecordAndSystemProfile event) {
- int maxEventVectorIndex = maxEventVectorIndexForMetric();
- int eventVectorIndex =
- PrivateIndexCalculations.eventVectorToIndex(event.eventVector(), mMetric);
-
- long clippedValue =
- PrivateIndexCalculations.clipValue(
- event.aggregateValue().getIntegerValue(), mReport);
- int clippedValueIndex =
- PrivateIndexCalculations.longToIndex(
- clippedValue,
- mReport.getMinValue(),
- mReport.getMaxValue(),
- mReport.getNumIndexPoints(),
- mSecureRandom);
- return PrivateIndexCalculations.valueAndEventVectorIndicesToIndex(
- clippedValueIndex, eventVectorIndex, maxEventVectorIndex);
- }
-
- private int maxIndexForReport() {
- return PrivateIndexCalculations.getNumEventVectors(mMetric.getMetricDimensionsList())
- * mReport.getNumIndexPoints()
- - 1;
- }
-
- private int maxEventVectorIndexForMetric() {
- return PrivateIndexCalculations.getNumEventVectors(mMetric.getMetricDimensionsList()) - 1;
- }
-}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/IntegerEncoder.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/IntegerEncoder.java
new file mode 100644
index 0000000..8e7e23a
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/IntegerEncoder.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+
+import com.android.cobalt.data.EventRecordAndSystemProfile;
+
+import com.google.cobalt.IntegerObservation;
+import com.google.cobalt.Observation;
+import com.google.common.collect.ImmutableList;
+
+import java.security.SecureRandom;
+
+/**
+ * Encodes events for the same day and report as a single {@link Observation} that wraps an {@link
+ * IntegerObservation}.
+ *
+ * <p>Note, this encoder expects input {@link AggregateValue} objects to have an inner integer value
+ * set and will use 0 if not.
+ */
+final class IntegerEncoder implements NonPrivateObservationGenerator.Encoder {
+ private final SecureRandom mSecureRandom;
+
+ IntegerEncoder(@NonNull SecureRandom secureRandom) {
+ this.mSecureRandom = requireNonNull(secureRandom);
+ }
+
+ /**
+ * Encodes integer events for the same day and report as a single {@link IntegerObservation}.
+ *
+ * @param events the events to encode
+ * @return an observation which wraps an {@link IntegerObservation} holding the input events
+ */
+ @Override
+ public Observation encode(ImmutableList<EventRecordAndSystemProfile> events) {
+ IntegerObservation.Builder integerObservation = IntegerObservation.newBuilder();
+ for (EventRecordAndSystemProfile event : events) {
+ IntegerObservation.Value value =
+ IntegerObservation.Value.newBuilder()
+ .addAllEventCodes(event.eventVector().eventCodes())
+ .setValue(event.aggregateValue().getIntegerValue())
+ .build();
+ integerObservation.addValues(value);
+ }
+ return Observation.newBuilder()
+ .setInteger(integerObservation)
+ .setRandomId(RandomId.generate(mSecureRandom))
+ .build();
+ }
+}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/NonPrivateObservationGenerator.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/NonPrivateObservationGenerator.java
new file mode 100644
index 0000000..9564f4c
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/NonPrivateObservationGenerator.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+
+import com.android.cobalt.data.EventRecordAndSystemProfile;
+import com.android.cobalt.data.ObservationGenerator;
+
+import com.google.cobalt.Observation;
+import com.google.cobalt.ObservationMetadata;
+import com.google.cobalt.ObservationToEncrypt;
+import com.google.cobalt.ReportDefinition;
+import com.google.cobalt.SystemProfile;
+import com.google.cobalt.UnencryptedObservationBatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+
+import java.security.SecureRandom;
+import java.util.Collection;
+import java.util.Map;
+
+/** Generates non-private observations from event data. */
+final class NonPrivateObservationGenerator implements ObservationGenerator {
+ /** Interface to encode aggregated values as observations for non-private reports. */
+ interface Encoder {
+ /**
+ * Encodes multiple events for the same report, day, and system profile as a single
+ * non-private observation.
+ *
+ * @param events the events that need to be encoded
+ * @return a non-private observation that contains all input event data
+ */
+ Observation encode(ImmutableList<EventRecordAndSystemProfile> events);
+ }
+
+ private final SecureRandom mSecureRandom;
+ private final Encoder mEncoder;
+ private final int mCustomerId;
+ private final int mProjectId;
+ private final int mMetricId;
+ private final ReportDefinition mReport;
+
+ NonPrivateObservationGenerator(
+ @NonNull SecureRandom secureRandom,
+ @NonNull Encoder encoder,
+ int customerId,
+ int projectId,
+ int metricId,
+ ReportDefinition report) {
+ this.mSecureRandom = requireNonNull(secureRandom);
+ this.mEncoder = requireNonNull(encoder);
+ this.mCustomerId = customerId;
+ this.mProjectId = projectId;
+ this.mMetricId = metricId;
+ this.mReport = report;
+ }
+
+ /**
+ * Generate the non-private observations that occurred for a report and day.
+ *
+ * @param dayIndex the day index to generate observations for
+ * @param allEventData the data for events that occurred that are relevant to the day and Report
+ * @return the observations to store in the DB for later sending, contained in
+ * UnencryptedObservationBatches with their metadata
+ */
+ @Override
+ public ImmutableList<UnencryptedObservationBatch> generateObservations(
+ int dayIndex,
+ ImmutableListMultimap<SystemProfile, EventRecordAndSystemProfile> allEventData) {
+ ImmutableList.Builder<UnencryptedObservationBatch> batches = ImmutableList.builder();
+ for (Map.Entry<SystemProfile, Collection<EventRecordAndSystemProfile>> eventData :
+ allEventData.asMap().entrySet()) {
+ SystemProfile systemProfile = eventData.getKey();
+ ImmutableList<EventRecordAndSystemProfile> events =
+ ImmutableList.copyOf(eventData.getValue());
+ if (mReport.getEventVectorBufferMax() != 0
+ && events.size() > mReport.getEventVectorBufferMax()) {
+ // Each EventRecordAndSystemProfile contains a unique event vector for the
+ // system profile and day so the number of events can be compared to the event
+ // vector buffer max of the report.
+ events = events.subList(0, (int) mReport.getEventVectorBufferMax());
+ }
+
+ ObservationToEncrypt observation =
+ ObservationToEncrypt.newBuilder()
+ .setObservation(mEncoder.encode(events))
+ .setContributionId(RandomId.generate(mSecureRandom))
+ .build();
+ UnencryptedObservationBatch.Builder batch =
+ UnencryptedObservationBatch.newBuilder()
+ .setMetadata(
+ ObservationMetadata.newBuilder()
+ .setCustomerId(mCustomerId)
+ .setProjectId(mProjectId)
+ .setMetricId(mMetricId)
+ .setReportId(mReport.getId())
+ .setDayIndex(dayIndex)
+ .setSystemProfile(systemProfile))
+ .addUnencryptedObservations(observation);
+ batches.add(batch.build());
+ }
+ return batches.build();
+ }
+}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/ObservationGeneratorFactory.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/ObservationGeneratorFactory.java
new file mode 100644
index 0000000..01a8088
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/ObservationGeneratorFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+
+import com.android.cobalt.data.ObservationGenerator;
+import com.android.cobalt.domain.Project;
+import com.android.cobalt.system.SystemData;
+
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.ReportDefinition;
+import com.google.cobalt.ReportDefinition.ReportType;
+
+import java.security.SecureRandom;
+
+/** Creates {@link ObservationGenerator} instances for reports. */
+public final class ObservationGeneratorFactory {
+ private final Project mProject;
+ private final SystemData mSystemData;
+ private final PrivacyGenerator mPrivacyGenerator;
+ private final SecureRandom mSecureRandom;
+
+ public ObservationGeneratorFactory(
+ @NonNull Project project,
+ @NonNull SystemData systemData,
+ @NonNull PrivacyGenerator privacyGenerator,
+ @NonNull SecureRandom secureRandom) {
+ mProject = requireNonNull(project);
+ mSystemData = requireNonNull(systemData);
+ mPrivacyGenerator = requireNonNull(privacyGenerator);
+ mSecureRandom = requireNonNull(secureRandom);
+ }
+
+ /**
+ * Creates an {@link ObservationGenerator} instance for a report.
+ *
+ * <p>Note, only FLEETWIDE_OCCURRENCE_COUNTS are supported.
+ *
+ * @param metric the metric observations are being generated for
+ * @param report the metric observations are being generated for
+ * @return the {@link ObservationGenerator} required for the provided report
+ */
+ public ObservationGenerator getObservationGenerator(
+ MetricDefinition metric, ReportDefinition report) {
+ if (report.getReportType() != ReportType.FLEETWIDE_OCCURRENCE_COUNTS) {
+ throw new AssertionError(
+ "Unrecognized or unsupported report type: " + report.getReportTypeValue());
+ }
+
+ switch (report.getPrivacyLevel()) {
+ case NO_ADDED_PRIVACY:
+ return new NonPrivateObservationGenerator(
+ mSecureRandom,
+ new IntegerEncoder(mSecureRandom),
+ mProject.getCustomerId(),
+ mProject.getProjectId(),
+ metric.getId(),
+ report);
+ case LOW_PRIVACY:
+ case MEDIUM_PRIVACY:
+ case HIGH_PRIVACY:
+ return new PrivateObservationGenerator(
+ mSystemData,
+ mPrivacyGenerator,
+ mSecureRandom,
+ new PrivateIntegerEncoder(mSecureRandom, metric, report),
+ mProject.getCustomerId(),
+ mProject.getProjectId(),
+ metric,
+ report);
+ default:
+ throw new AssertionError(
+ "Unknown or unset privacy level: " + report.getPrivacyLevelValue());
+ }
+ }
+}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivacyGenerator.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivacyGenerator.java
index 5ce9e31..0399b78 100644
--- a/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivacyGenerator.java
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivacyGenerator.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkArgument;
+import com.google.cobalt.PrivateIndexObservation;
import com.google.cobalt.ReportDefinition;
import com.google.common.collect.ImmutableList;
import com.google.common.math.BigDecimalMath;
@@ -35,17 +36,14 @@
}
/**
- * Adds noise to a list of private indices.
+ * Generates noise for a report.
*
- * <p>Each private index is an observation which actually occurred.
- *
- * @param indices the private indices
* @param maxIndex the maximum private index value for the report
* @param reportDefinition the privacy-enabled report containing the privacy parameters
* @return private indices that include noise according to the report's privacy parameters
*/
- ImmutableList<Integer> addNoise(
- ImmutableList<Integer> indices, int maxIndex, ReportDefinition reportDefinition) {
+ ImmutableList<PrivateIndexObservation> generateNoise(
+ int maxIndex, ReportDefinition reportDefinition) {
checkArgument(maxIndex >= 0, "maxIndex value cannot be negative");
double lambda = reportDefinition.getPoissonMean();
checkArgument(lambda > 0, "poisson_mean must be positive, got %s", lambda);
@@ -66,13 +64,15 @@
int addedOnes = samplePoissonDistribution(lambdaTimesNumIndex);
- ImmutableList.Builder<Integer> withNoise = ImmutableList.<Integer>builder();
- withNoise.addAll(indices);
+ ImmutableList.Builder<PrivateIndexObservation> noise = ImmutableList.builder();
for (int i = 0; i < addedOnes; ++i) {
- withNoise.add(sampleUniformDistribution(maxIndex));
+ noise.add(
+ PrivateIndexObservation.newBuilder()
+ .setIndex(sampleUniformDistribution(maxIndex))
+ .build());
}
- return withNoise.build();
+ return noise.build();
}
/**
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateIntegerEncoder.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateIntegerEncoder.java
new file mode 100644
index 0000000..b175071
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateIntegerEncoder.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+
+import com.android.cobalt.data.EventVector;
+
+import com.google.cobalt.AggregateValue;
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.PrivateIndexObservation;
+import com.google.cobalt.ReportDefinition;
+
+import java.security.SecureRandom;
+
+/**
+ * Encodes an integer event as an {@link PrivateIndexObservation}.
+ *
+ * <p>Note, this encoder expects input {@link AggregateValue} objects to have an inner integer value
+ * set and will use 0 if not.
+ */
+final class PrivateIntegerEncoder implements PrivateObservationGenerator.Encoder {
+ private final MetricDefinition mMetric;
+ private final ReportDefinition mReport;
+ private final SecureRandom mSecureRandom;
+
+ PrivateIntegerEncoder(
+ @NonNull SecureRandom secureRandom,
+ @NonNull MetricDefinition metric,
+ @NonNull ReportDefinition report) {
+ this.mSecureRandom = requireNonNull(secureRandom);
+ this.mMetric = requireNonNull(metric);
+ this.mReport = requireNonNull(report);
+ }
+
+ /**
+ * Encodes one event and aggregated value as a single private observation.
+ *
+ * @param eventVector the event vector to encode
+ * @param aggregateValue the aggregated value to encode
+ * @return the privacy encoded observation
+ */
+ @Override
+ public PrivateIndexObservation encode(EventVector eventVector, AggregateValue aggregateValue) {
+ int maxEventVectorIndex =
+ PrivateIndexCalculations.getNumEventVectors(mMetric.getMetricDimensionsList()) - 1;
+ int eventVectorIndex = PrivateIndexCalculations.eventVectorToIndex(eventVector, mMetric);
+
+ long clippedValue =
+ PrivateIndexCalculations.clipValue(aggregateValue.getIntegerValue(), mReport);
+ int clippedValueIndex =
+ PrivateIndexCalculations.longToIndex(
+ clippedValue,
+ mReport.getMinValue(),
+ mReport.getMaxValue(),
+ mReport.getNumIndexPoints(),
+ mSecureRandom);
+ int privateIndex =
+ PrivateIndexCalculations.valueAndEventVectorIndicesToIndex(
+ clippedValueIndex, eventVectorIndex, maxEventVectorIndex);
+ return PrivateIndexObservation.newBuilder().setIndex(privateIndex).build();
+ }
+}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateObservationGenerator.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateObservationGenerator.java
new file mode 100644
index 0000000..caa377d
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/PrivateObservationGenerator.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+
+import com.android.cobalt.data.EventRecordAndSystemProfile;
+import com.android.cobalt.data.EventVector;
+import com.android.cobalt.data.ObservationGenerator;
+import com.android.cobalt.system.SystemData;
+
+import com.google.cobalt.AggregateValue;
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.Observation;
+import com.google.cobalt.ObservationMetadata;
+import com.google.cobalt.ObservationToEncrypt;
+import com.google.cobalt.PrivateIndexObservation;
+import com.google.cobalt.ReportDefinition;
+import com.google.cobalt.ReportParticipationObservation;
+import com.google.cobalt.SystemProfile;
+import com.google.cobalt.UnencryptedObservationBatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+
+import java.security.SecureRandom;
+
+/** Generates private observations from event data and report privacy parameters. */
+final class PrivateObservationGenerator implements ObservationGenerator {
+ /**
+ * Interface to encode an aggregated value as a private index observation for private reports.
+ */
+ interface Encoder {
+ /**
+ * Encodes one event and aggregated value as a single private observation.
+ *
+ * <p>Note, retuning a single private observation implies that report types that have
+ * multiple values in their {@link AggregateValue}, like histograms, aren't supported.
+ *
+ * @param eventVector the event vector to encode
+ * @param aggregateValue the aggregated value to encode
+ * @return the privacy encoded observation
+ */
+ PrivateIndexObservation encode(EventVector eventVector, AggregateValue aggregateValue);
+ }
+
+ private final SystemData mSystemData;
+ private final PrivacyGenerator mPrivacyGenerator;
+ private final SecureRandom mSecureRandom;
+ private final Encoder mEncoder;
+ private final int mCustomerId;
+ private final int mProjectId;
+ private final MetricDefinition mMetric;
+ private final ReportDefinition mReport;
+
+ PrivateObservationGenerator(
+ @NonNull SystemData systemData,
+ @NonNull PrivacyGenerator privacyGenerator,
+ @NonNull SecureRandom secureRandom,
+ @NonNull Encoder encoder,
+ int customerId,
+ int projectId,
+ @NonNull MetricDefinition metric,
+ @NonNull ReportDefinition report) {
+ this.mSystemData = requireNonNull(systemData);
+ this.mPrivacyGenerator = requireNonNull(privacyGenerator);
+ this.mSecureRandom = requireNonNull(secureRandom);
+ this.mEncoder = requireNonNull(encoder);
+ this.mCustomerId = customerId;
+ this.mProjectId = projectId;
+ this.mMetric = requireNonNull(metric);
+ this.mReport = requireNonNull(report);
+ }
+
+ /**
+ * Generate the private observations that for a report and day.
+ *
+ * @param dayIndex the day index to generate observations for
+ * @param allEventData the data for events that occurred that are relevant to the day and Report
+ * @return the observations to store in the DB for later sending, contained in
+ * UnencryptedObservationBatches with their metadata
+ */
+ @Override
+ public ImmutableList<UnencryptedObservationBatch> generateObservations(
+ int dayIndex,
+ ImmutableListMultimap<SystemProfile, EventRecordAndSystemProfile> allEventData) {
+ if (allEventData.isEmpty()) {
+ return ImmutableList.of(
+ generateObservations(
+ dayIndex,
+ // Use the current system profile since none is provided.
+ mSystemData.filteredSystemProfile(mReport),
+ ImmutableList.of()));
+ }
+
+ ImmutableList.Builder<UnencryptedObservationBatch> batches = ImmutableList.builder();
+ for (SystemProfile systemProfile : allEventData.keySet()) {
+ batches.add(
+ generateObservations(dayIndex, systemProfile, allEventData.get(systemProfile)));
+ }
+
+ return batches.build();
+ }
+
+ /**
+ * Generate an observation batch from events for a report, day, and system profile.
+ *
+ * @param dayIndex the day observations are being generated for
+ * @param systemProfile the system profile of the observations
+ * @param events the events
+ * @return an UnencryptedObservation batch holding the generated observations
+ */
+ private UnencryptedObservationBatch generateObservations(
+ int dayIndex,
+ SystemProfile systemProfile,
+ ImmutableList<EventRecordAndSystemProfile> events) {
+ if (mReport.getEventVectorBufferMax() != 0
+ && events.size() > mReport.getEventVectorBufferMax()) {
+ // Each EventRecordAndSystemProfile contains a unique event vector for the system
+ // profile and day so the number of events can be compared to the event vector
+ // buffer max of the report.
+ events = events.subList(0, (int) mReport.getEventVectorBufferMax());
+ }
+
+ ImmutableList.Builder<Observation> observations = ImmutableList.builder();
+ for (EventRecordAndSystemProfile event : events) {
+ observations.add(
+ Observation.newBuilder()
+ .setPrivateIndex(
+ mEncoder.encode(event.eventVector(), event.aggregateValue()))
+ .setRandomId(RandomId.generate(mSecureRandom))
+ .build());
+ }
+ for (PrivateIndexObservation privateIndex :
+ mPrivacyGenerator.generateNoise(maxIndexForReport(), mReport)) {
+ observations.add(
+ Observation.newBuilder()
+ .setPrivateIndex(privateIndex)
+ .setRandomId(RandomId.generate(mSecureRandom))
+ .build());
+ }
+ observations.add(
+ Observation.newBuilder()
+ .setReportParticipation(ReportParticipationObservation.getDefaultInstance())
+ .setRandomId(RandomId.generate(mSecureRandom))
+ .build());
+
+ ImmutableList.Builder<ObservationToEncrypt> toEncrypt = ImmutableList.builder();
+ boolean setContributionId = true;
+ for (Observation observation : observations.build()) {
+ ObservationToEncrypt.Builder builder = ObservationToEncrypt.newBuilder();
+ builder.setObservation(observation);
+ if (setContributionId) {
+ builder.setContributionId(RandomId.generate(mSecureRandom));
+ }
+
+ // Reports with privacy enabled split a single contribution across multiple
+ // observations, both private and participation. However, only 1 needs the contribution
+ // id set.
+ toEncrypt.add(builder.build());
+ setContributionId = false;
+ }
+
+ return UnencryptedObservationBatch.newBuilder()
+ .setMetadata(
+ ObservationMetadata.newBuilder()
+ .setCustomerId(mCustomerId)
+ .setProjectId(mProjectId)
+ .setMetricId(mMetric.getId())
+ .setReportId(mReport.getId())
+ .setDayIndex(dayIndex)
+ .setSystemProfile(systemProfile))
+ .addAllUnencryptedObservations(toEncrypt.build())
+ .build();
+ }
+
+ private int maxIndexForReport() {
+ return PrivateIndexCalculations.getNumEventVectors(mMetric.getMetricDimensionsList())
+ * mReport.getNumIndexPoints()
+ - 1;
+ }
+}
diff --git a/adservices/libraries/cobalt/java/com/android/cobalt/observations/RandomId.java b/adservices/libraries/cobalt/java/com/android/cobalt/observations/RandomId.java
new file mode 100644
index 0000000..e7566db
--- /dev/null
+++ b/adservices/libraries/cobalt/java/com/android/cobalt/observations/RandomId.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import com.google.protobuf.ByteString;
+
+import java.security.SecureRandom;
+
+/** Methods for generating random ids. */
+final class RandomId {
+
+ /** Generates a random id suitable use as a contribution id or an observation random id. */
+ static ByteString generate(SecureRandom secureRandom) {
+ byte[] randomId = new byte[8];
+ secureRandom.nextBytes(randomId);
+ return ByteString.copyFrom(randomId);
+ }
+
+ private RandomId() {}
+}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/IntegerEncoderTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/IntegerEncoderTest.java
new file mode 100644
index 0000000..dc7ba19
--- /dev/null
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/IntegerEncoderTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cobalt.data.EventRecordAndSystemProfile;
+import com.android.cobalt.data.EventVector;
+import com.android.cobalt.observations.testing.FakeSecureRandom;
+
+import com.google.cobalt.AggregateValue;
+import com.google.cobalt.IntegerObservation;
+import com.google.cobalt.Observation;
+import com.google.cobalt.SystemProfile;
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.security.SecureRandom;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class IntegerEncoderTest {
+ private static final SecureRandom SECURE_RANDOM = new FakeSecureRandom();
+ private static final SystemProfile SYSTEM_PROFILE = SystemProfile.getDefaultInstance();
+
+ @Test
+ public void encodesEventsIntoOneObservation() throws Exception {
+ EventRecordAndSystemProfile event1 =
+ EventRecordAndSystemProfile.create(
+ SYSTEM_PROFILE,
+ EventVector.create(ImmutableList.of(1, 5)),
+ AggregateValue.newBuilder().setIntegerValue(100).build());
+ EventRecordAndSystemProfile event2 =
+ EventRecordAndSystemProfile.create(
+ SYSTEM_PROFILE,
+ EventVector.create(ImmutableList.of(2, 6)),
+ AggregateValue.newBuilder().setIntegerValue(200).build());
+
+ IntegerObservation observation =
+ IntegerObservation.newBuilder()
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .addAllEventCodes(List.of(1, 5))
+ .setValue(100))
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .addAllEventCodes(List.of(2, 6))
+ .setValue(200))
+ .build();
+
+ IntegerEncoder encoder = new IntegerEncoder(SECURE_RANDOM);
+ assertThat(encoder.encode(ImmutableList.of(event1, event2)))
+ .isEqualTo(
+ Observation.newBuilder()
+ .setInteger(observation)
+ .setRandomId(
+ ByteString.copyFrom(new byte[] {0, 0, 0, 0, 0, 0, 0, 0}))
+ .build());
+ }
+}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/NonPrivateObservationGeneratorTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/NonPrivateObservationGeneratorTest.java
new file mode 100644
index 0000000..20f0014
--- /dev/null
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/NonPrivateObservationGeneratorTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cobalt.data.EventRecordAndSystemProfile;
+import com.android.cobalt.data.EventVector;
+import com.android.cobalt.observations.testing.FakeSecureRandom;
+
+import com.google.cobalt.AggregateValue;
+import com.google.cobalt.IntegerObservation;
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.MetricDefinition.MetricType;
+import com.google.cobalt.MetricDefinition.TimeZonePolicy;
+import com.google.cobalt.Observation;
+import com.google.cobalt.ObservationMetadata;
+import com.google.cobalt.ReportDefinition;
+import com.google.cobalt.ReportDefinition.PrivacyLevel;
+import com.google.cobalt.ReportDefinition.ReportType;
+import com.google.cobalt.SystemProfile;
+import com.google.cobalt.UnencryptedObservationBatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.protobuf.ByteString;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.SecureRandom;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public final class NonPrivateObservationGeneratorTest {
+ private static final int DAY_INDEX = 19201; // 2022-07-28
+ private static final int CUSTOMER = 1;
+ private static final int PROJECT = 2;
+ private static final int METRIC_ID = 3;
+ private static final int REPORT_ID = 4;
+ private static final SystemProfile SYSTEM_PROFILE_1 =
+ SystemProfile.newBuilder().setAppVersion("1.2.3").build();
+ private static final SystemProfile SYSTEM_PROFILE_2 =
+ SystemProfile.newBuilder().setAppVersion("2.4.8").build();
+ private static final ObservationMetadata METADATA_1 =
+ ObservationMetadata.newBuilder()
+ .setCustomerId(CUSTOMER)
+ .setProjectId(PROJECT)
+ .setMetricId(METRIC_ID)
+ .setReportId(REPORT_ID)
+ .setDayIndex(DAY_INDEX)
+ .setSystemProfile(SYSTEM_PROFILE_1)
+ .build();
+ private static final ObservationMetadata METADATA_2 =
+ ObservationMetadata.newBuilder()
+ .setCustomerId(CUSTOMER)
+ .setProjectId(PROJECT)
+ .setMetricId(METRIC_ID)
+ .setReportId(REPORT_ID)
+ .setDayIndex(DAY_INDEX)
+ .setSystemProfile(SYSTEM_PROFILE_2)
+ .build();
+ private static final int EVENT_COUNT_1 = 3;
+ private static final int EVENT_COUNT_2 = 17;
+ private static final EventVector EVENT_VECTOR_1 = EventVector.create(ImmutableList.of(1, 5));
+ private static final EventVector EVENT_VECTOR_2 = EventVector.create(ImmutableList.of(2, 6));
+ private static final EventRecordAndSystemProfile EVENT_1 =
+ createEvent(EVENT_VECTOR_1, EVENT_COUNT_1);
+ private static final EventRecordAndSystemProfile EVENT_2 =
+ createEvent(EVENT_VECTOR_2, EVENT_COUNT_2);
+
+ // Deterministic randomly generated bytes due to the FakeSecureRandom.
+ private static final ByteString RANDOM_BYTES_1 =
+ ByteString.copyFrom(new byte[] {0, 0, 0, 0, 0, 0, 0, 0});
+ private static final ByteString RANDOM_BYTES_2 =
+ ByteString.copyFrom(new byte[] {1, 1, 1, 1, 1, 1, 1, 1});
+ private static final ByteString RANDOM_BYTES_3 =
+ ByteString.copyFrom(new byte[] {2, 2, 2, 2, 2, 2, 2, 2});
+ private static final ByteString RANDOM_BYTES_4 =
+ ByteString.copyFrom(new byte[] {3, 3, 3, 3, 3, 3, 3, 3});
+ private static final Observation OBSERVATION_1 =
+ Observation.newBuilder()
+ .setInteger(
+ IntegerObservation.newBuilder()
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .setValue(EVENT_COUNT_1)
+ .addAllEventCodes(EVENT_VECTOR_1.eventCodes())))
+ .setRandomId(RANDOM_BYTES_1)
+ .build();
+ private static final Observation OBSERVATION_1_AND_2 =
+ Observation.newBuilder()
+ .setInteger(
+ IntegerObservation.newBuilder()
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .setValue(EVENT_COUNT_1)
+ .addAllEventCodes(EVENT_VECTOR_1.eventCodes()))
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .setValue(EVENT_COUNT_2)
+ .addAllEventCodes(EVENT_VECTOR_2.eventCodes())))
+ .setRandomId(RANDOM_BYTES_1)
+ .build();
+ private static final Observation OBSERVATION_2 =
+ Observation.newBuilder()
+ .setInteger(
+ IntegerObservation.newBuilder()
+ .addValues(
+ IntegerObservation.Value.newBuilder()
+ .setValue(EVENT_COUNT_2)
+ .addAllEventCodes(EVENT_VECTOR_2.eventCodes())))
+ .setRandomId(RANDOM_BYTES_3)
+ .build();
+ private static final Observation NO_EVENT_CODES_OBSERVATION =
+ Observation.newBuilder()
+ .setInteger(
+ IntegerObservation.newBuilder()
+ .addValues(IntegerObservation.Value.newBuilder().setValue(7)))
+ .setRandomId(RANDOM_BYTES_1)
+ .build();
+
+ private static final MetricDefinition METRIC =
+ MetricDefinition.newBuilder()
+ .setId(METRIC_ID)
+ .setMetricType(MetricType.OCCURRENCE)
+ .setTimeZonePolicy(TimeZonePolicy.OTHER_TIME_ZONE)
+ .setOtherTimeZone("America/Los_Angeles")
+ .build();
+ private static final ReportDefinition REPORT =
+ ReportDefinition.newBuilder()
+ .setId(REPORT_ID)
+ .setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
+ .setPrivacyLevel(PrivacyLevel.NO_ADDED_PRIVACY)
+ .build();
+
+ private final SecureRandom mSecureRandom;
+ private NonPrivateObservationGenerator mGenerator;
+
+ public NonPrivateObservationGeneratorTest() {
+ mSecureRandom = new FakeSecureRandom();
+ mGenerator = null;
+ }
+
+ private NonPrivateObservationGenerator createObservationGenerator(
+ int customerId, int projectId, MetricDefinition metric, ReportDefinition report) {
+ return new NonPrivateObservationGenerator(
+ mSecureRandom,
+ new IntegerEncoder(mSecureRandom),
+ customerId,
+ projectId,
+ metric.getId(),
+ report);
+ }
+
+ private static EventRecordAndSystemProfile createEvent(
+ List<Integer> eventCodes, int aggregateValue) {
+ // System profile fields are ignored during observation generation and can be anything.
+ return EventRecordAndSystemProfile.create(
+ /* systemProfile= */ SystemProfile.getDefaultInstance(),
+ EventVector.create(eventCodes),
+ AggregateValue.newBuilder().setIntegerValue(aggregateValue).build());
+ }
+
+ private static EventRecordAndSystemProfile createEvent(
+ EventVector eventVector, int aggregateValue) {
+ // System profile fields are ignored during observation generation and can be anything.
+ return EventRecordAndSystemProfile.create(
+ /* systemProfile= */ SystemProfile.getDefaultInstance(),
+ eventVector,
+ AggregateValue.newBuilder().setIntegerValue(aggregateValue).build());
+ }
+
+ @Test
+ public void generateObservations_noEvents_nothingGenerated() throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(DAY_INDEX, ImmutableListMultimap.of());
+ assertThat(result).isEmpty();
+ }
+
+ @Test
+ public void generateObservations_oneEvent_generated() throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(
+ DAY_INDEX, ImmutableListMultimap.of(SYSTEM_PROFILE_1, EVENT_1));
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
+ assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_2);
+ assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(OBSERVATION_1);
+ }
+
+ @Test
+ public void generateObservations_oneEventWithNoEventCodes_generated() throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(
+ DAY_INDEX,
+ ImmutableListMultimap.of(
+ SYSTEM_PROFILE_1, createEvent(ImmutableList.of(), 7)));
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
+ assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_2);
+ assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(NO_EVENT_CODES_OBSERVATION);
+ }
+
+ @Test
+ public void generateObservations_twoEvents_oneObservationGenerated() throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(
+ DAY_INDEX,
+ ImmutableListMultimap.of(
+ SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_1, EVENT_2));
+
+ // Verify both event vectors are aggregated into one observation.
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
+ assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_2);
+ assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(OBSERVATION_1_AND_2);
+ }
+
+ @Test
+ public void generateObservations_twoEventsInTwoSystemProfiles_separateObservations()
+ throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(
+ DAY_INDEX,
+ ImmutableListMultimap.of(
+ SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_2, EVENT_2));
+
+ // Verify that separate system profiles are aggregated into separate batches.
+ assertThat(result).hasSize(2);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
+ assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_2);
+ assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(OBSERVATION_1);
+ assertThat(result.get(1).getMetadata()).isEqualTo(METADATA_2);
+ assertThat(result.get(1).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(1).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_4);
+ assertThat(result.get(1).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(OBSERVATION_2);
+ }
+
+ @Test
+ public void generateObservations_eventVectorBufferMax_oneEventSent() throws Exception {
+ mGenerator =
+ createObservationGenerator(
+ CUSTOMER,
+ PROJECT,
+ METRIC,
+ REPORT.toBuilder().setEventVectorBufferMax(1).build());
+ List<UnencryptedObservationBatch> result =
+ mGenerator.generateObservations(
+ DAY_INDEX,
+ ImmutableListMultimap.of(
+ SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_1, EVENT_2));
+
+ // Verify only the first event vector is aggregated into an observation.
+ assertThat(result).hasSize(1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
+ assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
+ assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
+ .isEqualTo(RANDOM_BYTES_2);
+ assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
+ .isEqualTo(OBSERVATION_1);
+ }
+}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/ObservationGeneratorFactoryTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/ObservationGeneratorFactoryTest.java
new file mode 100644
index 0000000..be0d6c5
--- /dev/null
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/ObservationGeneratorFactoryTest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.cobalt.data.ObservationGenerator;
+import com.android.cobalt.domain.Project;
+import com.android.cobalt.observations.testing.FakeSecureRandom;
+import com.android.cobalt.system.SystemData;
+
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.ReportDefinition;
+import com.google.cobalt.ReportDefinition.PrivacyLevel;
+import com.google.cobalt.ReportDefinition.ReportType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.security.SecureRandom;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class ObservationGeneratorFactoryTest {
+ private final ObservationGeneratorFactory mFactory;
+
+ public ObservationGeneratorFactoryTest() {
+ Project project =
+ Project.create(/* customerId= */ 0, /* projectId= */ 1, /* metrics= */ List.of());
+ SecureRandom secureRandom = new FakeSecureRandom();
+ this.mFactory =
+ new ObservationGeneratorFactory(
+ project,
+ new SystemData(),
+ new PrivacyGenerator(secureRandom),
+ secureRandom);
+ }
+
+ @Test
+ public void getObservationGenerator_nonPrivateFleetwideOccurrenceCounts() throws Exception {
+ ObservationGenerator generator =
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
+ .setPrivacyLevel(PrivacyLevel.NO_ADDED_PRIVACY)
+ .build());
+
+ assertThat(generator).isInstanceOf(NonPrivateObservationGenerator.class);
+ }
+
+ @Test
+ public void getObservationGenerator_privateFleetwideOccurrenceCounts() throws Exception {
+ ObservationGenerator generator =
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
+ .setPrivacyLevel(PrivacyLevel.HIGH_PRIVACY)
+ .build());
+
+ assertThat(generator).isInstanceOf(PrivateObservationGenerator.class);
+ }
+
+ @Test
+ public void getObservationGenerator_fleetwideOccurrenceCounts_noPrivacyLevelSet()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_reportTypeNotSet_throwsAssertionError() throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder().build()));
+ }
+
+ @Test
+ public void getObservationGenerator_reportTypeUnset_throwsAssertionError() throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.REPORT_TYPE_UNSET)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_uniqueDeviceCounts_throwsAssertionError() throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.UNIQUE_DEVICE_COUNTS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_uniqueDeviceHistograms_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.UNIQUE_DEVICE_HISTOGRAMS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_hourlyValueHistograms_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.HOURLY_VALUE_HISTOGRAMS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_fleetwideHistograms_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.FLEETWIDE_HISTOGRAMS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_fleetwideMeans_throwsAssertionError() throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.FLEETWIDE_MEANS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_uniqueDeviceNumericStats_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.UNIQUE_DEVICE_NUMERIC_STATS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_hourlyValueNumericStats_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.HOURLY_VALUE_NUMERIC_STATS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_stringsCounts_throwsAssertionError() throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.STRING_COUNTS)
+ .build()));
+ }
+
+ @Test
+ public void getObservationGenerator_uniqueDeviceStringCounts_throwsAssertionError()
+ throws Exception {
+ assertThrows(
+ AssertionError.class,
+ () ->
+ mFactory.getObservationGenerator(
+ MetricDefinition.getDefaultInstance(),
+ ReportDefinition.newBuilder()
+ .setReportType(ReportType.UNIQUE_DEVICE_STRING_COUNTS)
+ .build()));
+ }
+}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorStatisticalTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorStatisticalTest.java
index 4df99ab..f85d35a 100644
--- a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorStatisticalTest.java
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorStatisticalTest.java
@@ -21,6 +21,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.google.cobalt.PrivateIndexObservation;
import com.google.cobalt.ReportDefinition;
import com.google.cobalt.ReportDefinition.PrivacyLevel;
import com.google.common.collect.ImmutableList;
@@ -46,7 +47,6 @@
.setId(5)
.setPrivacyLevel(PrivacyLevel.LOW_PRIVACY)
.build();
- private static final ImmutableList<Integer> sEmptyIndices = ImmutableList.of();
private final SecureRandom mSecureRandom;
private final PrivacyGenerator mPrivacyGenerator;
@@ -99,7 +99,7 @@
ReportDefinition report = sReportTemplate.toBuilder().setPoissonMean(poissonMean).build();
int totalIndicesAdded = 0;
for (int i = 0; i < numTrials; i++) {
- totalIndicesAdded += mPrivacyGenerator.addNoise(sEmptyIndices, maxIndex, report).size();
+ totalIndicesAdded += mPrivacyGenerator.generateNoise(maxIndex, report).size();
}
double stddev = Math.sqrt((double) (numTrials * (1 + maxIndex)) * poissonMean);
double expectedIndicesAdded = (double) (numTrials * (1 + maxIndex)) * poissonMean;
@@ -231,11 +231,11 @@
int numIndicesAdded = 0;
int[] indexCounts = new int[n];
while (numIndicesAdded < numTrials) {
- ImmutableList<Integer> noisedIndices =
- mPrivacyGenerator.addNoise(sEmptyIndices, maxIndex, report);
- for (int i : noisedIndices) {
+ ImmutableList<PrivateIndexObservation> noisedIndices =
+ mPrivacyGenerator.generateNoise(maxIndex, report);
+ for (PrivateIndexObservation o : noisedIndices) {
numIndicesAdded++;
- indexCounts[i]++;
+ indexCounts[(int) o.getIndex()]++;
if (numIndicesAdded >= numTrials) {
break;
}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorTest.java
index e4e4d22..cf455d3 100644
--- a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorTest.java
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivacyGeneratorTest.java
@@ -24,6 +24,7 @@
import com.android.cobalt.observations.testing.FakeSecureRandom;
+import com.google.cobalt.PrivateIndexObservation;
import com.google.cobalt.ReportDefinition;
import com.google.cobalt.ReportDefinition.PrivacyLevel;
import com.google.common.collect.ImmutableList;
@@ -46,78 +47,70 @@
mPrivacyGenerator = new PrivacyGenerator(new FakeSecureRandom());
}
+ private static PrivateIndexObservation makeObservation(int i) {
+ return PrivateIndexObservation.newBuilder().setIndex(i).build();
+ }
+
@Test
- public void testAddNoise_noEventsNoNoise_empty() throws Exception {
- ImmutableList<Integer> result = mPrivacyGenerator.addNoise(ImmutableList.of(), 0, sReport);
+ public void testAddNoise_noNoise_empty() throws Exception {
+ ImmutableList<PrivateIndexObservation> result = mPrivacyGenerator.generateNoise(0, sReport);
// The report's lambda is too small to trigger a fabricated observation.
assertThat(result).isEmpty();
}
@Test
- public void testAddNoise_noEventsButFabricatedObservation_oneIndex() throws Exception {
+ public void testAddNoise_fabricatedObservation_oneIndex() throws Exception {
// Use a larger Poisson mean that is guaranteed to cause a fabricated observation to be
// created, due to the FakeSecureRandom implementation.
- ImmutableList<Integer> result =
- mPrivacyGenerator.addNoise(
- ImmutableList.of(), 0, sReport.toBuilder().setPoissonMean(0.1).build());
+ ImmutableList<PrivateIndexObservation> result =
+ mPrivacyGenerator.generateNoise(0, sReport.toBuilder().setPoissonMean(0.1).build());
// A fabricated observation.
- assertThat(result).containsExactly(0);
+ assertThat(result).containsExactly(makeObservation(0));
}
@Test
- public void testAddNoise_noEventsButTwoFabricatedObservations_oneIndex() throws Exception {
+ public void testAddNoise_twoFabricatedObservations_oneIndex() throws Exception {
// Use an even larger Poisson mean that is guaranteed to cause two fabricated observations
// to be created, due to the FakeSecureRandom implementation.
- ImmutableList<Integer> result =
- mPrivacyGenerator.addNoise(
- ImmutableList.of(), 0, sReport.toBuilder().setPoissonMean(0.52).build());
+ ImmutableList<PrivateIndexObservation> result =
+ mPrivacyGenerator.generateNoise(
+ 0, sReport.toBuilder().setPoissonMean(0.52).build());
// Two fabricated observations.
- assertThat(result).containsExactly(0, 0);
+ assertThat(result).containsExactly(makeObservation(0), makeObservation(0));
}
@Test
- public void testAddNoise_oneEventNoNoise_oneIndex() throws Exception {
- ImmutableList<Integer> result = mPrivacyGenerator.addNoise(ImmutableList.of(0), 0, sReport);
- // Real index returned, as the report's lambda is too small to trigger a fabricated
- // observation.
- assertThat(result).containsExactly(0);
- }
-
- @Test
- public void testAddNoise_oneEventAndFabricatedObservation_twoIndices() throws Exception {
+ public void testAddNoise_oneFabricatedObservation_twoIndices() throws Exception {
// Use a larger Poisson mean that is guaranteed to cause a fabricated observation to be
// created, due to the FakeSecureRandom implementation.
- ImmutableList<Integer> result =
- mPrivacyGenerator.addNoise(
- ImmutableList.of(0), 0, sReport.toBuilder().setPoissonMean(0.1).build());
- // Real index returned, and a fabricated index are expected.
- assertThat(result).containsExactly(0, 0);
+ ImmutableList<PrivateIndexObservation> result =
+ mPrivacyGenerator.generateNoise(0, sReport.toBuilder().setPoissonMean(0.1).build());
+ // A fabricated index is expected.
+ assertThat(result).containsExactly(makeObservation(0));
}
@Test
- public void testAddNoise_oneEventAndTwoFabricatedObservations_threeIndices() throws Exception {
+ public void testAddNoise_twoFabricatedObservations_threeIndices() throws Exception {
// Use an even larger Poisson mean that is guaranteed to cause two fabricated observations
// to be created, due to the FakeSecureRandom implementation.
- ImmutableList<Integer> result =
- mPrivacyGenerator.addNoise(
- ImmutableList.of(0), 0, sReport.toBuilder().setPoissonMean(0.52).build());
- // Real index returned, and two fabricated indices are expected.
- assertThat(result).containsExactly(0, 0, 0);
+ ImmutableList<PrivateIndexObservation> result =
+ mPrivacyGenerator.generateNoise(
+ 0, sReport.toBuilder().setPoissonMean(0.52).build());
+ // Two fabricated indices are expected.
+ assertThat(result).containsExactly(makeObservation(0), makeObservation(0));
}
@Test
- public void testAddNoise_oneEventForMetricWithDimensions_threeObservations() throws Exception {
+ public void testAddNoise_metricWithDimensions_threeObservations() throws Exception {
// Use a larger Poisson mean that is guaranteed to cause a single fabricated observation to
// be created, due to the FakeSecureRandom implementation. This is smaller than other tests,
// because the poisson mean is multiplied by the number of indices, which is larger here due
// the metric dimensions.
- ImmutableList<Integer> result =
- mPrivacyGenerator.addNoise(
- ImmutableList.of(2, 3),
- 5,
- sReport.toBuilder().setPoissonMean(0.02).build());
- // Real indices returned, and a fabricated index are expected.
- assertThat(result).containsExactly(2, 3, 5);
+ ImmutableList<PrivateIndexObservation> result =
+ mPrivacyGenerator.generateNoise(
+ 5, sReport.toBuilder().setPoissonMean(0.02).build());
+ // A fabricated index is expected.
+ assertThat(result).containsExactly(makeObservation(5));
}
@Test
@@ -125,7 +118,7 @@
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
- () -> mPrivacyGenerator.addNoise(ImmutableList.of(), -1, sReport));
+ () -> mPrivacyGenerator.generateNoise(-1, sReport));
assertThat(thrown).hasMessageThat().contains("maxIndex value cannot be negative");
}
@@ -135,10 +128,8 @@
assertThrows(
IllegalArgumentException.class,
() ->
- mPrivacyGenerator.addNoise(
- ImmutableList.of(),
- 0,
- sReport.toBuilder().setPoissonMean(-0.1).build()));
+ mPrivacyGenerator.generateNoise(
+ 0, sReport.toBuilder().setPoissonMean(-0.1).build()));
assertThat(thrown).hasMessageThat().contains("poisson_mean must be positive");
}
}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateIntegerEncoderTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateIntegerEncoderTest.java
new file mode 100644
index 0000000..158b0db
--- /dev/null
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateIntegerEncoderTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cobalt.observations;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.cobalt.data.EventVector;
+import com.android.cobalt.observations.testing.FakeSecureRandom;
+
+import com.google.cobalt.AggregateValue;
+import com.google.cobalt.MetricDefinition;
+import com.google.cobalt.MetricDefinition.MetricDimension;
+import com.google.cobalt.PrivateIndexObservation;
+import com.google.cobalt.ReportDefinition;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.security.SecureRandom;
+
+@RunWith(JUnit4.class)
+public final class PrivateIntegerEncoderTest {
+ private static final SecureRandom SECURE_RANDOM = new FakeSecureRandom();
+
+ @Test
+ public void encodesAsPrivateIndex() throws Exception {
+ // Use a metric with 6 possible event vectors: [(0,5), (1,5), (2,5), (0,6), (1,6), (2,6)].
+ MetricDefinition metric =
+ MetricDefinition.newBuilder()
+ .addMetricDimensions(MetricDimension.newBuilder().setMaxEventCode(2))
+ .addMetricDimensions(
+ MetricDimension.newBuilder()
+ .putEventCodes(5, "5")
+ .putEventCodes(6, "6"))
+ .build();
+
+ // Use a report with 21 possible values and 11 index points so private index encoding of a
+ // value `v` is always `6 * floor(v/2) + eventIndex` where `eventIndex` is the index of the
+ // event vector in the above list.
+ ReportDefinition report =
+ ReportDefinition.newBuilder()
+ .setNumIndexPoints(11)
+ .setMinValue(0)
+ .setMaxValue(20)
+ .build();
+ PrivateIntegerEncoder encoder = new PrivateIntegerEncoder(SECURE_RANDOM, metric, report);
+ assertThat(
+ encoder.encode(
+ EventVector.create(1, 5),
+ AggregateValue.newBuilder().setIntegerValue(3).build()))
+ .isEqualTo(PrivateIndexObservation.newBuilder().setIndex(7).build());
+ assertThat(
+ encoder.encode(
+ EventVector.create(2, 6),
+ AggregateValue.newBuilder().setIntegerValue(17).build()))
+ .isEqualTo(PrivateIndexObservation.newBuilder().setIndex(53).build());
+ }
+
+ @Test
+ public void valueBelowMinimum_encodedAsMinimumValue() throws Exception {
+ // Use a metric with 6 possible event vectors: [(0,5), (1,5), (2,5), (0,6), (1,6), (2,6)].
+ MetricDefinition metric =
+ MetricDefinition.newBuilder()
+ .addMetricDimensions(MetricDimension.newBuilder().setMaxEventCode(2))
+ .addMetricDimensions(
+ MetricDimension.newBuilder()
+ .putEventCodes(5, "5")
+ .putEventCodes(6, "6"))
+ .build();
+
+ // Use a report with 21 possible values and 11 index points so private index encoding of a
+ // value `v` is always `6 * floor(v/2) + eventIndex` where `eventIndex` is the index of the
+ // event vector in the above list.
+ ReportDefinition report =
+ ReportDefinition.newBuilder()
+ .setNumIndexPoints(11)
+ .setMinValue(0)
+ .setMaxValue(20)
+ .build();
+ PrivateIntegerEncoder encoder = new PrivateIntegerEncoder(SECURE_RANDOM, metric, report);
+ assertThat(
+ encoder.encode(
+ EventVector.create(1, 5),
+ AggregateValue.newBuilder().setIntegerValue(-1).build()))
+ .isEqualTo(
+ encoder.encode(
+ EventVector.create(1, 5),
+ AggregateValue.newBuilder().setIntegerValue(0).build()));
+ }
+
+ @Test
+ public void valueAboveMaximum_encodedAsMaximumValue() throws Exception {
+ // Use a metric with 6 possible event vectors: [(0,5), (1,5), (2,5), (0,6), (1,6), (2,6)].
+ MetricDefinition metric =
+ MetricDefinition.newBuilder()
+ .addMetricDimensions(MetricDimension.newBuilder().setMaxEventCode(2))
+ .addMetricDimensions(
+ MetricDimension.newBuilder()
+ .putEventCodes(5, "5")
+ .putEventCodes(6, "6"))
+ .build();
+
+ // Use a report with 21 possible values and 11 index points so private index encoding of a
+ // value `v` is always `6 * floor(v/2) + eventIndex` where `eventIndex` is the index of the
+ // event vector in the above list.
+ ReportDefinition report =
+ ReportDefinition.newBuilder()
+ .setNumIndexPoints(11)
+ .setMinValue(0)
+ .setMaxValue(20)
+ .build();
+ PrivateIntegerEncoder encoder = new PrivateIntegerEncoder(SECURE_RANDOM, metric, report);
+ assertThat(
+ encoder.encode(
+ EventVector.create(1, 5),
+ AggregateValue.newBuilder().setIntegerValue(21).build()))
+ .isEqualTo(
+ encoder.encode(
+ EventVector.create(1, 5),
+ AggregateValue.newBuilder().setIntegerValue(20).build()));
+ }
+}
diff --git a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/CountObservationGeneratorTest.java b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateObservationGeneratorTest.java
similarity index 64%
rename from adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/CountObservationGeneratorTest.java
rename to adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateObservationGeneratorTest.java
index 78df537..b28fa99 100644
--- a/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/CountObservationGeneratorTest.java
+++ b/adservices/libraries/cobalt/tests/src/com/android/cobalt/observations/PrivateObservationGeneratorTest.java
@@ -26,7 +26,6 @@
import com.android.cobalt.system.SystemData;
import com.google.cobalt.AggregateValue;
-import com.google.cobalt.IntegerObservation;
import com.google.cobalt.MetricDefinition;
import com.google.cobalt.MetricDefinition.MetricDimension;
import com.google.cobalt.MetricDefinition.MetricType;
@@ -52,13 +51,12 @@
import java.util.List;
@RunWith(AndroidJUnit4.class)
-public final class CountObservationGeneratorTest {
+public final class PrivateObservationGeneratorTest {
private static final int DAY_INDEX = 19201; // 2022-07-28
private static final int CUSTOMER = 1;
private static final int PROJECT = 2;
private static final int METRIC_ID = 3;
private static final int REPORT_ID = 4;
- private static final int PRIVATE_REPORT_ID = 5;
private static final SystemProfile SYSTEM_PROFILE_1 =
SystemProfile.newBuilder().setAppVersion("1.2.3").build();
private static final SystemProfile SYSTEM_PROFILE_2 =
@@ -81,32 +79,12 @@
.setDayIndex(DAY_INDEX)
.setSystemProfile(SYSTEM_PROFILE_2)
.build();
- private static final ObservationMetadata PRIVATE_METADATA_1 =
- ObservationMetadata.newBuilder()
- .setCustomerId(CUSTOMER)
- .setProjectId(PROJECT)
- .setMetricId(METRIC_ID)
- .setReportId(PRIVATE_REPORT_ID)
- .setDayIndex(DAY_INDEX)
- .setSystemProfile(SYSTEM_PROFILE_1)
- .build();
- private static final ObservationMetadata PRIVATE_METADATA_2 =
- ObservationMetadata.newBuilder()
- .setCustomerId(CUSTOMER)
- .setProjectId(PROJECT)
- .setMetricId(METRIC_ID)
- .setReportId(PRIVATE_REPORT_ID)
- .setDayIndex(DAY_INDEX)
- .setSystemProfile(SYSTEM_PROFILE_2)
- .build();
private static final int EVENT_COUNT_1 = 3;
private static final int EVENT_COUNT_2 = 17;
private static final EventVector EVENT_VECTOR_1 = EventVector.create(ImmutableList.of(1, 5));
private static final EventVector EVENT_VECTOR_2 = EventVector.create(ImmutableList.of(2, 6));
private static final EventRecordAndSystemProfile EVENT_1 =
createEvent(EVENT_VECTOR_1, EVENT_COUNT_1);
- private static final EventRecordAndSystemProfile EVENT_2 =
- createEvent(EVENT_VECTOR_2, EVENT_COUNT_2);
// Deterministic randomly generated bytes due to the FakeSecureRandom.
private static final ByteString RANDOM_BYTES_1 =
@@ -117,47 +95,6 @@
ByteString.copyFrom(new byte[] {2, 2, 2, 2, 2, 2, 2, 2});
private static final ByteString RANDOM_BYTES_4 =
ByteString.copyFrom(new byte[] {3, 3, 3, 3, 3, 3, 3, 3});
- private static final Observation OBSERVATION_1 =
- Observation.newBuilder()
- .setInteger(
- IntegerObservation.newBuilder()
- .addValues(
- IntegerObservation.Value.newBuilder()
- .setValue(EVENT_COUNT_1)
- .addAllEventCodes(EVENT_VECTOR_1.eventCodes())))
- .setRandomId(RANDOM_BYTES_1)
- .build();
- private static final Observation OBSERVATION_1_AND_2 =
- Observation.newBuilder()
- .setInteger(
- IntegerObservation.newBuilder()
- .addValues(
- IntegerObservation.Value.newBuilder()
- .setValue(EVENT_COUNT_1)
- .addAllEventCodes(EVENT_VECTOR_1.eventCodes()))
- .addValues(
- IntegerObservation.Value.newBuilder()
- .setValue(EVENT_COUNT_2)
- .addAllEventCodes(EVENT_VECTOR_2.eventCodes())))
- .setRandomId(RANDOM_BYTES_1)
- .build();
- private static final Observation OBSERVATION_2 =
- Observation.newBuilder()
- .setInteger(
- IntegerObservation.newBuilder()
- .addValues(
- IntegerObservation.Value.newBuilder()
- .setValue(EVENT_COUNT_2)
- .addAllEventCodes(EVENT_VECTOR_2.eventCodes())))
- .setRandomId(RANDOM_BYTES_3)
- .build();
- private static final Observation NO_EVENT_CODES_OBSERVATION =
- Observation.newBuilder()
- .setInteger(
- IntegerObservation.newBuilder()
- .addValues(IntegerObservation.Value.newBuilder().setValue(7)))
- .setRandomId(RANDOM_BYTES_1)
- .build();
private static final MetricDefinition METRIC =
MetricDefinition.newBuilder()
@@ -182,12 +119,6 @@
ReportDefinition.newBuilder()
.setId(REPORT_ID)
.setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
- .setPrivacyLevel(PrivacyLevel.NO_ADDED_PRIVACY)
- .build();
- private static final ReportDefinition PRIVATE_REPORT =
- ReportDefinition.newBuilder()
- .setId(PRIVATE_REPORT_ID)
- .setReportType(ReportType.FLEETWIDE_OCCURRENCE_COUNTS)
.addSystemProfileField(SystemProfileField.APP_VERSION)
.setPrivacyLevel(PrivacyLevel.LOW_PRIVACY)
// Use a poisson mean that will not produce a fabricated observation.
@@ -205,20 +136,21 @@
private final SecureRandom mSecureRandom;
private final PrivacyGenerator mPrivacyGenerator;
- private CountObservationGenerator mGenerator;
+ private PrivateObservationGenerator mGenerator;
- public CountObservationGeneratorTest() {
+ public PrivateObservationGeneratorTest() {
mSecureRandom = new FakeSecureRandom();
mPrivacyGenerator = new PrivacyGenerator(mSecureRandom);
mGenerator = null;
}
- private CountObservationGenerator createObservationGenerator(
+ private PrivateObservationGenerator createObservationGenerator(
int customerId, int projectId, MetricDefinition metric, ReportDefinition report) {
- return new CountObservationGenerator(
+ return new PrivateObservationGenerator(
new SystemData(SYSTEM_PROFILE_1.getAppVersion()),
mPrivacyGenerator,
mSecureRandom,
+ new PrivateIntegerEncoder(mSecureRandom, metric, report),
customerId,
projectId,
metric,
@@ -244,125 +176,16 @@
}
@Test
- public void generateObservations_noEvents_nothingGenerated() throws Exception {
+ public void generateObservations_noEvents_reportParticipationOnly() throws Exception {
mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
List<UnencryptedObservationBatch> result =
mGenerator.generateObservations(DAY_INDEX, ImmutableListMultimap.of());
- assertThat(result).isEmpty();
- }
-
- @Test
- public void generateObservations_oneEvent_generated() throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(
- DAY_INDEX, ImmutableListMultimap.of(SYSTEM_PROFILE_1, EVENT_1));
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
- assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_2);
- assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
- .isEqualTo(OBSERVATION_1);
- }
-
- @Test
- public void generateObservations_oneEventWithNoEventCodes_generated() throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(
- DAY_INDEX,
- ImmutableListMultimap.of(
- SYSTEM_PROFILE_1, createEvent(ImmutableList.of(), 7)));
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
- assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_2);
- assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
- .isEqualTo(NO_EVENT_CODES_OBSERVATION);
- }
-
- @Test
- public void generateObservations_twoEvents_oneObservationGenerated() throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(
- DAY_INDEX,
- ImmutableListMultimap.of(
- SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_1, EVENT_2));
-
- // Verify both event vectors are aggregated into one observation.
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
- assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_2);
- assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
- .isEqualTo(OBSERVATION_1_AND_2);
- }
-
- @Test
- public void generateObservations_twoEventsInTwoSystemProfiles_separateObservations()
- throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(
- DAY_INDEX,
- ImmutableListMultimap.of(
- SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_2, EVENT_2));
-
- // Verify that separate system profiles are aggregated into separate batches.
- assertThat(result).hasSize(2);
- assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
- assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_2);
- assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
- .isEqualTo(OBSERVATION_1);
- assertThat(result.get(1).getMetadata()).isEqualTo(METADATA_2);
- assertThat(result.get(1).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(1).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_4);
- assertThat(result.get(1).getUnencryptedObservations(0).getObservation())
- .isEqualTo(OBSERVATION_2);
- }
-
- @Test
- public void generateObservations_eventVectorBufferMax_oneEventSent() throws Exception {
- mGenerator =
- createObservationGenerator(
- CUSTOMER,
- PROJECT,
- METRIC,
- REPORT.toBuilder().setEventVectorBufferMax(1).build());
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(
- DAY_INDEX,
- ImmutableListMultimap.of(
- SYSTEM_PROFILE_1, EVENT_1, SYSTEM_PROFILE_1, EVENT_2));
-
- // Verify only the first event vector is aggregated into an observation.
- assertThat(result).hasSize(1);
- assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
- assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
- assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
- .isEqualTo(RANDOM_BYTES_2);
- assertThat(result.get(0).getUnencryptedObservations(0).getObservation())
- .isEqualTo(OBSERVATION_1);
- }
-
- @Test
- public void generatePrivateObservations_noEvents_reportParticipationOnly() throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, PRIVATE_REPORT);
- List<UnencryptedObservationBatch> result =
- mGenerator.generateObservations(DAY_INDEX, ImmutableListMultimap.of());
// Only a report participation observation is expected, as the report's lambda is too small
// to trigger a fabricated observation.
assertThat(result).hasSize(1);
// Used the current system's SystemProfile, as no logged events have system profiles.
- assertThat(result.get(0).getMetadata()).isEqualTo(PRIVATE_METADATA_1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(1);
assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
.isEqualTo(RANDOM_BYTES_2);
@@ -376,7 +199,7 @@
}
@Test
- public void generatePrivateObservations_noEventsButFabricatedObservation_twoObservations()
+ public void generateObservations_noEventsButFabricatedObservation_twoObservations()
throws Exception {
mGenerator =
createObservationGenerator(
@@ -385,10 +208,7 @@
// lambda = poissonMean*((((maxEventVectorIndex+1)*numIndexPoints)-1)+1)
// = poissonMean*((((0+1)*11)-1)+1) = poissonMean*11 >= 0.1
// poissonMean >= 0.0091
- CUSTOMER,
- PROJECT,
- METRIC,
- PRIVATE_REPORT.toBuilder().setPoissonMean(0.01).build());
+ CUSTOMER, PROJECT, METRIC, REPORT.toBuilder().setPoissonMean(0.01).build());
List<UnencryptedObservationBatch> result =
mGenerator.generateObservations(DAY_INDEX, ImmutableListMultimap.of());
@@ -396,7 +216,7 @@
// batch.
assertThat(result).hasSize(1);
// Used the current system's SystemProfile, as no logged events have system profiles.
- assertThat(result.get(0).getMetadata()).isEqualTo(PRIVATE_METADATA_1);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_1);
assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(2);
assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
.isEqualTo(RANDOM_BYTES_3);
@@ -421,9 +241,8 @@
}
@Test
- public void generatePrivateObservations_oneEvent_generatedPlusReportParticipation()
- throws Exception {
- mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, PRIVATE_REPORT);
+ public void generateObservations_oneEvent_generatedPlusReportParticipation() throws Exception {
+ mGenerator = createObservationGenerator(CUSTOMER, PROJECT, METRIC, REPORT);
List<UnencryptedObservationBatch> result =
mGenerator.generateObservations(
DAY_INDEX,
@@ -434,7 +253,7 @@
// small to trigger a fabricated observation.
assertThat(result).hasSize(1);
// All observations use the logged system profile.
- assertThat(result.get(0).getMetadata()).isEqualTo(PRIVATE_METADATA_2);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_2);
assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(2);
assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
.isEqualTo(RANDOM_BYTES_3);
@@ -456,7 +275,7 @@
}
@Test
- public void generatePrivateObservations_oneEventAndFabricatedObservation_threeObservations()
+ public void generateObservations_oneEventAndFabricatedObservation_threeObservations()
throws Exception {
mGenerator =
createObservationGenerator(
@@ -466,10 +285,7 @@
// lambda = poissonMean*((((maxEventVectorIndex+1)*numIndexPoints)-1)+1)
// = poissonMean*((((0+1)*11)-1)+1) = poissonMean*11 >= 0.1
// poissonMean >= 0.0091
- CUSTOMER,
- PROJECT,
- METRIC,
- PRIVATE_REPORT.toBuilder().setPoissonMean(0.01).build());
+ CUSTOMER, PROJECT, METRIC, REPORT.toBuilder().setPoissonMean(0.01).build());
List<UnencryptedObservationBatch> result =
mGenerator.generateObservations(
DAY_INDEX,
@@ -480,7 +296,7 @@
// batch.
assertThat(result).hasSize(1);
// All observations use the logged system profile.
- assertThat(result.get(0).getMetadata()).isEqualTo(PRIVATE_METADATA_2);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_2);
assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(3);
assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
.isEqualTo(RANDOM_BYTES_4);
@@ -510,7 +326,7 @@
}
@Test
- public void generatePrivateObservations_oneEventForMetricWithDimensions_threeObservations()
+ public void generateObservations_oneEventForMetricWithDimensions_threeObservations()
throws Exception {
mGenerator =
createObservationGenerator(
@@ -524,7 +340,7 @@
// lambda = poissonMean*((((maxEventVectorIndex+1)*numIndexPoints)-1)+1)
// = poissonMean*((((5+1)*11)-1)+1) = poissonMean*66 >= 0.1
// poissonMean >= 0.00152
- PRIVATE_REPORT.toBuilder().setPoissonMean(0.002).build());
+ REPORT.toBuilder().setPoissonMean(0.002).build());
List<UnencryptedObservationBatch> result =
mGenerator.generateObservations(
DAY_INDEX, ImmutableListMultimap.of(SYSTEM_PROFILE_2, EVENT_1));
@@ -533,7 +349,7 @@
// batch.
assertThat(result).hasSize(1);
// All observations use the logged system profile.
- assertThat(result.get(0).getMetadata()).isEqualTo(PRIVATE_METADATA_2);
+ assertThat(result.get(0).getMetadata()).isEqualTo(METADATA_2);
assertThat(result.get(0).getUnencryptedObservationsList()).hasSize(3);
assertThat(result.get(0).getUnencryptedObservations(0).getContributionId())
.isEqualTo(RANDOM_BYTES_4);