diff --git a/build/repositories.bzl b/build/repositories.bzl index 20a9d7a3f94..1c9a13822e0 100644 --- a/build/repositories.bzl +++ b/build/repositories.bzl @@ -40,9 +40,10 @@ def wfa_measurement_system_repositories(): wfa_repo_archive( name = "wfa_measurement_proto", + # DO_NOT_SUBMIT(world-federation-of-advertisers/cross-media-measurement-api#165): Use version. + commit = "0c5c23a3cfd0dfdadce854d842fc53584a486b7a", repo = "cross-media-measurement-api", - sha256 = "1d829e7d95e6dedea1a4ea746e5613915dd60ca095b7b35bdcf19fa067697f2a", - version = "0.39.2", + sha256 = "ff8ef13159101fb78c8bb13174eca9ee3b8355c2b0867a82d592e58f443483bb", ) wfa_repo_archive( diff --git a/src/main/k8s/local/metric_spec_config.textproto b/src/main/k8s/local/metric_spec_config.textproto index f5ea4b53c6e..8314de06cbe 100644 --- a/src/main/k8s/local/metric_spec_config.textproto +++ b/src/main/k8s/local/metric_spec_config.textproto @@ -15,7 +15,7 @@ frequency_histogram_params { epsilon: 0.115 delta: 1.0E-12 } - maximum_frequency_per_user: 10 + maximum_frequency: 10 } impression_count_params { privacy_params { diff --git a/src/main/k8s/testing/secretfiles/llv2_protocol_config_config.textproto b/src/main/k8s/testing/secretfiles/llv2_protocol_config_config.textproto index 19d6f63d859..09cdeb8c2c0 100644 --- a/src/main/k8s/testing/secretfiles/llv2_protocol_config_config.textproto +++ b/src/main/k8s/testing/secretfiles/llv2_protocol_config_config.textproto @@ -11,7 +11,6 @@ protocol_config { delta: 1.0 } elliptic_curve_id: 415 - maximum_frequency: 10 noise_mechanism: GEOMETRIC } duchy_protocol_config { diff --git a/src/main/kotlin/org/wfanet/measurement/api/v2alpha/tools/CreateMeasurementFlags.kt b/src/main/kotlin/org/wfanet/measurement/api/v2alpha/tools/CreateMeasurementFlags.kt index f34de69c74b..3fb6b7d5a48 100644 --- a/src/main/kotlin/org/wfanet/measurement/api/v2alpha/tools/CreateMeasurementFlags.kt +++ b/src/main/kotlin/org/wfanet/measurement/api/v2alpha/tools/CreateMeasurementFlags.kt @@ -198,12 +198,12 @@ class CreateMeasurementFlags { private set @set:Option( - names = ["--reach-max-frequency"], - description = ["Maximum frequency per user"], + names = ["--max-frequency"], + description = ["Maximum frequency revealed in the distribution"], required = false, defaultValue = "10", ) - var maximumFrequencyPerUser by Delegates.notNull() + var maximumFrequency by Delegates.notNull() private set } @@ -233,7 +233,7 @@ class CreateMeasurementFlags { private set @set:Option( - names = ["--impression-max-frequency"], + names = ["--max-frequency-per-user"], description = ["Maximum frequency per user"], required = true, ) @@ -358,9 +358,9 @@ class CreateMeasurementFlags { measurementParams.eventMeasurementParams.eventMeasurementTypeParams.reachAndFrequency .frequencyPrivacyDelta } - maximumFrequencyPerUser = + maximumFrequency = measurementParams.eventMeasurementParams.eventMeasurementTypeParams.reachAndFrequency - .maximumFrequencyPerUser + .maximumFrequency } } diff --git a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/herald/LiquidLegionsV2Starter.kt b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/herald/LiquidLegionsV2Starter.kt index a0f838ea756..54be1562dec 100644 --- a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/herald/LiquidLegionsV2Starter.kt +++ b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/herald/LiquidLegionsV2Starter.kt @@ -16,6 +16,7 @@ package org.wfanet.measurement.duchy.daemon.herald import java.util.logging.Logger import org.wfanet.measurement.api.Version +import org.wfanet.measurement.api.v2alpha.DifferentialPrivacyParams import org.wfanet.measurement.api.v2alpha.MeasurementSpec import org.wfanet.measurement.duchy.daemon.utils.key import org.wfanet.measurement.duchy.daemon.utils.sha1Hash @@ -35,6 +36,7 @@ import org.wfanet.measurement.internal.duchy.createComputationRequest import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsSketchAggregationV2.ComputationDetails.ComputationParticipant import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsSketchAggregationV2.ComputationDetails.Parameters import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsSketchAggregationV2.Stage +import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsSketchAggregationV2Kt.ComputationDetailsKt.ParametersKt import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsSketchAggregationV2Kt.ComputationDetailsKt.parameters import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsV2NoiseConfig.NoiseMechanism import org.wfanet.measurement.internal.duchy.protocol.LiquidLegionsV2NoiseConfigKt.reachNoiseConfig @@ -291,75 +293,76 @@ object LiquidLegionsV2Starter { /** Creates a liquid legions v2 `Parameters` from the system Api computation. */ private fun Computation.toLiquidLegionsV2Parameters(): Parameters { - require(mpcProtocolConfig.hasLiquidLegionsV2()) { + check(mpcProtocolConfig.hasLiquidLegionsV2()) { "Missing liquidLegionV2 in the duchy protocol config." } val llv2Config = mpcProtocolConfig.liquidLegionsV2 - return parameters { - maximumFrequency = llv2Config.maximumFrequency - sketchParameters = liquidLegionsSketchParameters { - decayRate = llv2Config.sketchParams.decayRate - size = llv2Config.sketchParams.maxSize + val apiVersion = Version.fromString(publicApiVersion) + check(apiVersion == Version.V2_ALPHA) { "Unsupported API version $apiVersion" } + val measurementSpec = MeasurementSpec.parseFrom(measurementSpec) + + when (val measurementType = measurementSpec.measurementTypeCase) { + MeasurementSpec.MeasurementTypeCase.REACH_AND_FREQUENCY, + MeasurementSpec.MeasurementTypeCase.REACH -> {} + MeasurementSpec.MeasurementTypeCase.IMPRESSION, + MeasurementSpec.MeasurementTypeCase.DURATION, + MeasurementSpec.MeasurementTypeCase.POPULATION, + MeasurementSpec.MeasurementTypeCase.MEASUREMENTTYPE_NOT_SET -> + error("Unsupported measurement type $measurementType") + } + + return parameters { populate(llv2Config, measurementSpec) } + } + + private fun ParametersKt.Dsl.populate( + protocolConfig: Computation.MpcProtocolConfig.LiquidLegionsV2, + measurementSpec: MeasurementSpec + ) { + sketchParameters = liquidLegionsSketchParameters { + decayRate = protocolConfig.sketchParams.decayRate + size = protocolConfig.sketchParams.maxSize + } + ellipticCurveId = protocolConfig.ellipticCurveId + if (measurementSpec.hasReachAndFrequency()) { + maximumFrequency = measurementSpec.reachAndFrequency.maximumFrequency + if (maximumFrequency == 0) { + @Suppress("DEPRECATION") // For legacy Computations. + maximumFrequency = protocolConfig.maximumFrequency } - ellipticCurveId = llv2Config.ellipticCurveId - noise = liquidLegionsV2NoiseConfig { - noiseMechanism = mpcProtocolConfig.liquidLegionsV2.noiseMechanism.toInternalNoiseMechanism() - reachNoiseConfig = reachNoiseConfig { - val mpcNoise = llv2Config.mpcNoise - blindHistogramNoise = mpcNoise.blindedHistogramNoise.toDuchyDifferentialPrivacyParams() - noiseForPublisherNoise = mpcNoise.publisherNoise.toDuchyDifferentialPrivacyParams() - - when (Version.fromString(publicApiVersion)) { - Version.V2_ALPHA -> { - val measurementSpec = MeasurementSpec.parseFrom(measurementSpec) - @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") // Proto enum fields are never null. - when (measurementSpec.measurementTypeCase) { - MeasurementSpec.MeasurementTypeCase.REACH -> { - val reach = measurementSpec.reach - require(reach.privacyParams.delta > 0) { - "LLv2 requires that privacy_params.delta be greater than 0" - } - require(reach.privacyParams.epsilon > MIN_REACH_EPSILON) { - "LLv2 requires that privacy_params.epsilon be greater than $MIN_REACH_EPSILON" - } - globalReachDpNoise = reach.privacyParams.toDuchyDifferentialPrivacyParams() - } - MeasurementSpec.MeasurementTypeCase.REACH_AND_FREQUENCY -> { - val reachAndFrequency = measurementSpec.reachAndFrequency - require(reachAndFrequency.reachPrivacyParams.delta > 0) { - "LLv2 requires that reach_privacy_params.delta be greater than 0" - } - require(reachAndFrequency.reachPrivacyParams.epsilon > MIN_REACH_EPSILON) { - "LLv2 requires that reach_privacy_params.epsilon be greater than $MIN_REACH_EPSILON" - } - require(reachAndFrequency.frequencyPrivacyParams.delta > 0) { - "LLv2 requires that frequency_privacy_params.delta be greater than 0" - } - require( - reachAndFrequency.frequencyPrivacyParams.epsilon > MIN_FREQUENCY_EPSILON - ) { - "LLv2 requires that frequency_privacy_params.epsilon be greater than " + - "$MIN_FREQUENCY_EPSILON" - } - globalReachDpNoise = - reachAndFrequency.reachPrivacyParams.toDuchyDifferentialPrivacyParams() - this@liquidLegionsV2NoiseConfig.frequencyNoiseConfig = - reachAndFrequency.frequencyPrivacyParams.toDuchyDifferentialPrivacyParams() - } - MeasurementSpec.MeasurementTypeCase.IMPRESSION, - MeasurementSpec.MeasurementTypeCase.DURATION, - MeasurementSpec.MeasurementTypeCase.POPULATION, - MeasurementSpec.MeasurementTypeCase.MEASUREMENTTYPE_NOT_SET -> { - throw IllegalArgumentException( - "Missing Reach and ReachAndFrequency in the measurementSpec." - ) - } - } - } - Version.VERSION_UNSPECIFIED -> error("Public api version is invalid or unspecified.") + require(maximumFrequency > 0) { "Maximum frequency must be greater than 0" } + } + + noise = liquidLegionsV2NoiseConfig { + noiseMechanism = protocolConfig.noiseMechanism.toInternalNoiseMechanism() + + reachNoiseConfig = reachNoiseConfig { + val mpcNoise = protocolConfig.mpcNoise + blindHistogramNoise = mpcNoise.blindedHistogramNoise.toDuchyDifferentialPrivacyParams() + noiseForPublisherNoise = mpcNoise.publisherNoise.toDuchyDifferentialPrivacyParams() + + val reachPrivacyParams: DifferentialPrivacyParams = + if (measurementSpec.hasReachAndFrequency()) { + measurementSpec.reachAndFrequency.reachPrivacyParams + } else { + measurementSpec.reach.privacyParams } + require(reachPrivacyParams.delta > 0) { "Reach privacy delta must be greater than 0" } + require(reachPrivacyParams.epsilon > MIN_REACH_EPSILON) { + "Reach privacy epsilon must be greater than $MIN_REACH_EPSILON" + } + globalReachDpNoise = reachPrivacyParams.toDuchyDifferentialPrivacyParams() + } + + if (measurementSpec.hasReachAndFrequency()) { + val frequencyPrivacyParams = measurementSpec.reachAndFrequency.frequencyPrivacyParams + require(frequencyPrivacyParams.delta > 0) { + "Frequency privacy delta must be be greater than 0" + } + require(frequencyPrivacyParams.epsilon > MIN_FREQUENCY_EPSILON) { + "Frequency privacy epsilon must be greater than $MIN_FREQUENCY_EPSILON" } + frequencyNoiseConfig = frequencyPrivacyParams.toDuchyDifferentialPrivacyParams() } } } diff --git a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2Mill.kt b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2Mill.kt index 488301d7e4c..97f1ac9088f 100644 --- a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2Mill.kt +++ b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2Mill.kt @@ -25,7 +25,6 @@ import java.security.cert.X509Certificate import java.time.Clock import java.time.Duration import java.util.logging.Logger -import kotlin.math.min import org.wfanet.anysketch.crypto.CombineElGamalPublicKeysRequest import org.wfanet.measurement.api.Version import org.wfanet.measurement.api.v2alpha.MeasurementSpec @@ -479,13 +478,16 @@ class LiquidLegionsV2Mill( private suspend fun completeSetupPhaseAtAggregator(token: ComputationToken): ComputationToken { val llv2Details = token.computationDetails.liquidLegionsV2 require(AGGREGATOR == llv2Details.role) { "invalid role for this function." } + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the set of computation participants is a subset of all Duchies. + val inputBlobCount = workerStubs.size val (bytes, nextToken) = existingOutputOr(token) { val request = dataClients .readAllRequisitionBlobs(token, duchyId) - .concat(readAndCombineAllInputBlobs(token, workerStubs.size)) - .toCompleteSetupPhaseRequest(token, llv2Details, token.requisitionsCount) + .concat(readAndCombineAllInputBlobs(token, inputBlobCount)) + .toCompleteSetupPhaseRequest(llv2Details, token.requisitionsCount) val cryptoResult: CompleteSetupPhaseResponse = cryptoWorker.completeSetupPhase(request) logStageDurationMetric( token, @@ -521,7 +523,7 @@ class LiquidLegionsV2Mill( val request = dataClients .readAllRequisitionBlobs(token, duchyId) - .toCompleteSetupPhaseRequest(token, llv2Details, token.requisitionsCount) + .toCompleteSetupPhaseRequest(llv2Details, token.requisitionsCount) val cryptoResult: CompleteSetupPhaseResponse = cryptoWorker.completeSetupPhase(request) logStageDurationMetric( token, @@ -555,6 +557,7 @@ class LiquidLegionsV2Mill( val llv2Details = token.computationDetails.liquidLegionsV2 val llv2Parameters = llv2Details.parameters require(AGGREGATOR == llv2Details.role) { "invalid role for this function." } + val maximumRequestedFrequency = llv2Parameters.maximumFrequency.coerceAtLeast(1) val (bytes, nextToken) = existingOutputOr(token) { val request = completeExecutionPhaseOneAtAggregatorRequest { @@ -565,11 +568,8 @@ class LiquidLegionsV2Mill( combinedRegisterVector = readAndCombineAllInputBlobs(token, 1) totalSketchesCount = token.requisitionsCount noiseMechanism = llv2Details.parameters.noise.noiseMechanism - if ( - llv2Parameters.noise.hasFrequencyNoiseConfig() && - (getMaximumRequestedFrequency(token) > 1) - ) { - noiseParameters = getFrequencyNoiseParams(token, llv2Parameters) + if (llv2Parameters.noise.hasFrequencyNoiseConfig() && maximumRequestedFrequency > 1) { + noiseParameters = getFrequencyNoiseParams(llv2Parameters) } } val cryptoResult: CompleteExecutionPhaseOneAtAggregatorResponse = @@ -656,7 +656,7 @@ class LiquidLegionsV2Mill( "invalid role for this function." } var reach = 0L - val maximumRequestedFrequency = getMaximumRequestedFrequency(token) + val maximumRequestedFrequency = llv2Parameters.maximumFrequency.coerceAtLeast(1) val measurementSpec = MeasurementSpec.parseFrom(token.computationDetails.kingdomComputation.measurementSpec) @@ -679,12 +679,16 @@ class LiquidLegionsV2Mill( vidSamplingIntervalWidth = measurementSpec.vidSamplingInterval.width if (llv2Parameters.noise.hasReachNoiseConfig()) { reachDpNoiseBaseline = globalReachDpNoiseBaseline { + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 globalReachDpNoise = llv2Parameters.noise.reachNoiseConfig.globalReachDpNoise } } if (llv2Parameters.noise.hasFrequencyNoiseConfig() && (maximumRequestedFrequency > 1)) { frequencyNoiseParameters = flagCountTupleNoiseGenerationParameters { + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 maximumFrequency = maximumRequestedFrequency dpParams = llv2Parameters.noise.frequencyNoiseConfig @@ -728,7 +732,7 @@ class LiquidLegionsV2Mill( } // If this is a reach-only computation, then our job is done. - if (measurementSpec.hasReach() || maximumRequestedFrequency == 1) { + if (maximumRequestedFrequency == 1) { sendResultToKingdom(token, ReachResult(reach)) return completeComputation(nextToken, CompletedReason.SUCCEEDED) } @@ -757,7 +761,7 @@ class LiquidLegionsV2Mill( val llv2Details = token.computationDetails.liquidLegionsV2 val llv2Parameters = llv2Details.parameters require(NON_AGGREGATOR == llv2Details.role) { "invalid role for this function." } - val maximumRequestedFrequency = getMaximumRequestedFrequency(token) + val maximumRequestedFrequency = llv2Parameters.maximumFrequency.coerceAtLeast(1) val (bytes, nextToken) = existingOutputOr(token) { val request = completeExecutionPhaseTwoRequest { @@ -769,7 +773,7 @@ class LiquidLegionsV2Mill( if (llv2Parameters.noise.hasFrequencyNoiseConfig()) { partialCompositeElGamalPublicKey = llv2Details.partiallyCombinedPublicKey if (maximumRequestedFrequency > 1) { - noiseParameters = getFrequencyNoiseParams(token, llv2Parameters) + noiseParameters = getFrequencyNoiseParams(llv2Parameters) } } noiseMechanism = llv2Details.parameters.noise.noiseMechanism @@ -797,19 +801,16 @@ class LiquidLegionsV2Mill( stub = nextDuchyStub(llv2Details.participantList) ) - val measurementSpec = - MeasurementSpec.parseFrom(token.computationDetails.kingdomComputation.measurementSpec) - - return if (measurementSpec.hasReach() || maximumRequestedFrequency == 1) { - // If this is a reach-only computation, then our job is done. - completeComputation(nextToken, CompletedReason.SUCCEEDED) - } else { - dataClients.transitionComputationToStage( - nextToken, - inputsToNextStage = nextToken.outputPathList(), - stage = Stage.WAIT_EXECUTION_PHASE_THREE_INPUTS.toProtocolStage() - ) + // If this is a reach-only computation, then our job is done. + if (maximumRequestedFrequency == 1) { + return completeComputation(nextToken, CompletedReason.SUCCEEDED) } + + return dataClients.transitionComputationToStage( + nextToken, + inputsToNextStage = nextToken.outputPathList(), + stage = Stage.WAIT_EXECUTION_PHASE_THREE_INPUTS.toProtocolStage() + ) } private suspend fun completeExecutionPhaseThreeAtAggregator( @@ -825,9 +826,11 @@ class LiquidLegionsV2Mill( localElGamalKeyPair = llv2Details.localElgamalKey curveId = llv2Parameters.ellipticCurveId.toLong() sameKeyAggregatorMatrix = readAndCombineAllInputBlobs(token, 1) - maximumFrequency = getMaximumRequestedFrequency(token) + maximumFrequency = llv2Parameters.maximumFrequency.coerceAtLeast(1) if (llv2Parameters.noise.hasFrequencyNoiseConfig()) { globalFrequencyDpNoisePerBucket = perBucketFrequencyDpNoiseBaseline { + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 dpParams = llv2Parameters.noise.frequencyNoiseConfig } @@ -946,18 +949,19 @@ class LiquidLegionsV2Mill( } private fun ByteString.toCompleteSetupPhaseRequest( - token: ComputationToken, llv2Details: LiquidLegionsSketchAggregationV2.ComputationDetails, totalRequisitionsCount: Int ): CompleteSetupPhaseRequest { val noiseConfig = llv2Details.parameters.noise return completeSetupPhaseRequest { combinedRegisterVector = this@toCompleteSetupPhaseRequest - maximumFrequency = getMaximumRequestedFrequency(token) + maximumFrequency = llv2Details.parameters.maximumFrequency.coerceAtLeast(1) if (noiseConfig.hasReachNoiseConfig()) { noiseParameters = registerNoiseGenerationParameters { compositeElGamalPublicKey = llv2Details.combinedPublicKey curveId = llv2Details.parameters.ellipticCurveId.toLong() + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle + // the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 totalSketchesCount = totalRequisitionsCount dpParams = reachNoiseDifferentialPrivacyParams { @@ -973,30 +977,15 @@ class LiquidLegionsV2Mill( } private fun getFrequencyNoiseParams( - token: ComputationToken, llv2Parameters: Parameters ): FlagCountTupleNoiseGenerationParameters { - return FlagCountTupleNoiseGenerationParameters.newBuilder() - .apply { - maximumFrequency = getMaximumRequestedFrequency(token) - contributorsCount = workerStubs.size + 1 - dpParams = llv2Parameters.noise.frequencyNoiseConfig - } - .build() - } - - private fun getMaximumRequestedFrequency(token: ComputationToken): Int { - var maximumRequestedFrequency = - token.computationDetails.liquidLegionsV2.parameters.maximumFrequency - val measurementSpec = - MeasurementSpec.parseFrom(token.computationDetails.kingdomComputation.measurementSpec) - val measurementSpecMaximumFrequency = - if (measurementSpec.hasReach()) 1 - else measurementSpec.reachAndFrequency.maximumFrequencyPerUser - if (measurementSpecMaximumFrequency > 0) { - maximumRequestedFrequency = min(maximumRequestedFrequency, measurementSpecMaximumFrequency) + return flagCountTupleNoiseGenerationParameters { + maximumFrequency = llv2Parameters.maximumFrequency + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle the + // case where the computation participants is a subset of all Duchies. + contributorsCount = workerStubs.size + 1 + dpParams = llv2Parameters.noise.frequencyNoiseConfig } - return maximumRequestedFrequency } companion object { diff --git a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/ReachOnlyLiquidLegionsV2Mill.kt b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/ReachOnlyLiquidLegionsV2Mill.kt index c47ec1c8433..56693981f60 100644 --- a/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/ReachOnlyLiquidLegionsV2Mill.kt +++ b/src/main/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/ReachOnlyLiquidLegionsV2Mill.kt @@ -85,12 +85,10 @@ import org.wfanet.measurement.system.v1alpha.ComputationControlGrpcKt.Computatio import org.wfanet.measurement.system.v1alpha.ComputationLogEntriesGrpcKt.ComputationLogEntriesCoroutineStub import org.wfanet.measurement.system.v1alpha.ComputationParticipantKey import org.wfanet.measurement.system.v1alpha.ComputationParticipantKt -import org.wfanet.measurement.system.v1alpha.ComputationParticipantKt.RequisitionParamsKt import org.wfanet.measurement.system.v1alpha.ComputationParticipantsGrpcKt.ComputationParticipantsCoroutineStub import org.wfanet.measurement.system.v1alpha.ComputationsGrpcKt import org.wfanet.measurement.system.v1alpha.ReachOnlyLiquidLegionsV2 import org.wfanet.measurement.system.v1alpha.confirmComputationParticipantRequest -import org.wfanet.measurement.system.v1alpha.reachOnlyLiquidLegionsV2 import org.wfanet.measurement.system.v1alpha.setParticipantRequisitionParamsRequest /** @@ -461,12 +459,15 @@ class ReachOnlyLiquidLegionsV2Mill( private suspend fun completeSetupPhaseAtAggregator(token: ComputationToken): ComputationToken { val rollv2Details = token.computationDetails.reachOnlyLiquidLegionsV2 require(AGGREGATOR == rollv2Details.role) { "invalid role for this function." } + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the set of computation participants is a subset of all Duchies. + val inputBlobCount = workerStubs.size val (bytes, nextToken) = existingOutputOr(token) { val request = dataClients .readAllRequisitionBlobs(token, duchyId) - .concat(readAndCombineAllInputBlobsSetupPhaseAtAggregator(token, workerStubs.size)) + .concat(readAndCombineAllInputBlobsSetupPhaseAtAggregator(token, inputBlobCount)) .toCompleteSetupPhaseAtAggregatorRequest(rollv2Details, token.requisitionsCount) val cryptoResult: CompleteReachOnlySetupPhaseResponse = cryptoWorker.completeReachOnlySetupPhaseAtAggregator(request) @@ -560,6 +561,8 @@ class ReachOnlyLiquidLegionsV2Mill( inputBlob.substring(inputBlob.size() - kBytesPerCipherText, inputBlob.size()) if (rollv2Parameters.noise.hasReachNoiseConfig()) { reachDpNoiseBaseline = globalReachDpNoiseBaseline { + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 globalReachDpNoise = rollv2Parameters.noise.reachNoiseConfig.globalReachDpNoise } @@ -573,6 +576,8 @@ class ReachOnlyLiquidLegionsV2Mill( noiseParameters = registerNoiseGenerationParameters { compositeElGamalPublicKey = rollv2Details.combinedPublicKey curveId = rollv2Details.parameters.ellipticCurveId.toLong() + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to + // handle the case where the computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 totalSketchesCount = token.requisitionsCount dpParams = reachNoiseDifferentialPrivacyParams { @@ -705,6 +710,8 @@ class ReachOnlyLiquidLegionsV2Mill( noiseParameters = registerNoiseGenerationParameters { compositeElGamalPublicKey = rollv2Details.combinedPublicKey curveId = rollv2Details.parameters.ellipticCurveId.toLong() + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle + // the case where the set of computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 totalSketchesCount = totalRequisitionsCount dpParams = reachNoiseDifferentialPrivacyParams { @@ -731,6 +738,8 @@ class ReachOnlyLiquidLegionsV2Mill( combinedRegisterVector = combinedInputBlobs.substring( 0, + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle + // the case where the set of computation participants is a subset of all Duchies. combinedInputBlobs.size() - workerStubs.size * kBytesPerCipherText ) curveId = rollv2Details.parameters.ellipticCurveId.toLong() @@ -738,6 +747,8 @@ class ReachOnlyLiquidLegionsV2Mill( noiseParameters = registerNoiseGenerationParameters { compositeElGamalPublicKey = rollv2Details.combinedPublicKey curveId = rollv2Details.parameters.ellipticCurveId.toLong() + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle + // the case where the set of computation participants is a subset of all Duchies. contributorsCount = workerStubs.size + 1 totalSketchesCount = totalRequisitionsCount dpParams = reachNoiseDifferentialPrivacyParams { @@ -751,6 +762,8 @@ class ReachOnlyLiquidLegionsV2Mill( compositeElGamalPublicKey = rollv2Details.combinedPublicKey serializedExcessiveNoiseCiphertext = combinedInputBlobs.substring( + // TODO(world-federation-of-advertisers/cross-media-measurement#1194): Fix this to handle + // the case where the set of computation participants is a subset of all Duchies. combinedInputBlobs.size() - workerStubs.size * kBytesPerCipherText, combinedInputBlobs.size() ) diff --git a/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessLifeOfAReportIntegrationTest.kt b/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessLifeOfAReportIntegrationTest.kt index 7edadb0d105..e26b191b4ac 100644 --- a/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessLifeOfAReportIntegrationTest.kt +++ b/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessLifeOfAReportIntegrationTest.kt @@ -1373,7 +1373,7 @@ abstract class InProcessLifeOfAReportIntegrationTest( MetricSpecKt.frequencyHistogramParams { reachPrivacyParams = DP_PARAMS frequencyPrivacyParams = DP_PARAMS - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = VID_SAMPLING_INTERVAL } @@ -1404,7 +1404,7 @@ abstract class InProcessLifeOfAReportIntegrationTest( val expectedResult = calculateExpectedReachAndFrequencyMeasurementResult( sampledVids, - metric.metricSpec.frequencyHistogram.maximumFrequencyPerUser + metric.metricSpec.frequencyHistogram.maximumFrequency ) val reach = diff --git a/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessReportingServer.kt b/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessReportingServer.kt index ac62d69593b..4beb7c24866 100644 --- a/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessReportingServer.kt +++ b/src/main/kotlin/org/wfanet/measurement/integration/common/reporting/v2/InProcessReportingServer.kt @@ -264,7 +264,7 @@ class InProcessReportingServer( epsilon = 0.0033 delta = 1e-12 } - maximumFrequencyPerUser = 10 + maximumFrequency = 10 } frequencyHistogramVidSamplingInterval = MetricSpecConfigKt.vidSamplingInterval { diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/common/Llv2ProtocolConfig.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/common/Llv2ProtocolConfig.kt index 69e32829549..f017dfb0c97 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/common/Llv2ProtocolConfig.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/deploy/common/Llv2ProtocolConfig.kt @@ -42,6 +42,8 @@ object Llv2ProtocolConfig { flags.config.reader().use { parseTextProto(it, Llv2ProtocolConfigConfig.getDefaultInstance()) } + configMessage.protocolConfig.validate() + protocolConfig = configMessage.protocolConfig duchyProtocolConfig = configMessage.duchyProtocolConfig requiredExternalDuchyIds = configMessage.requiredExternalDuchyIdsList.toSet() @@ -57,6 +59,8 @@ object Llv2ProtocolConfig { require(!Llv2ProtocolConfig::protocolConfig.isInitialized) require(!Llv2ProtocolConfig::duchyProtocolConfig.isInitialized) require(!Llv2ProtocolConfig::requiredExternalDuchyIds.isInitialized) + protocolConfig.validate() + Llv2ProtocolConfig.protocolConfig = protocolConfig Llv2ProtocolConfig.duchyProtocolConfig = duchyProtocolConfig Llv2ProtocolConfig.requiredExternalDuchyIds = requiredExternalDuchyIds @@ -64,6 +68,11 @@ object Llv2ProtocolConfig { } } +private fun ProtocolConfig.LiquidLegionsV2.validate() { + @Suppress("DEPRECATION") + require(maximumFrequency == 0) { "LiquidLegionsV2.maximum_frequency is deprecated" } +} + class Llv2ProtocolConfigFlags { @CommandLine.Option( names = ["--llv2-protocol-config-config"], diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsService.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsService.kt index 60aeb86d8d1..7f2456c8366 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsService.kt @@ -132,14 +132,11 @@ class MeasurementsService( "measurement_consumer_certificate does not belong to ${request.parent}" } - val measurementSpec = request.measurement.measurementSpec - grpcRequire(!measurementSpec.data.isEmpty && !measurementSpec.signature.isEmpty) { - "Measurement spec is unspecified" - } + grpcRequire(request.measurement.hasMeasurementSpec()) { "measurement_spec is unspecified" } val parsedMeasurementSpec = try { - MeasurementSpec.parseFrom(measurementSpec.data) + MeasurementSpec.parseFrom(request.measurement.measurementSpec.data) } catch (e: InvalidProtocolBufferException) { failGrpc(Status.INVALID_ARGUMENT) { "Failed to parse measurement spec" } } @@ -267,7 +264,7 @@ class MeasurementsService( } } -private fun DifferentialPrivacyParams.hasEpsilonAndDeltaSet(): Boolean { +private fun DifferentialPrivacyParams.hasValidEpsilonAndDelta(): Boolean { return this.epsilon > 0 && this.delta >= 0 } @@ -282,26 +279,29 @@ private fun MeasurementSpec.validate() { @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") when (measurementTypeCase) { MeasurementSpec.MeasurementTypeCase.REACH -> { - grpcRequire(reach.privacyParams.hasEpsilonAndDeltaSet()) { - "Reach privacy params are unspecified" + grpcRequire(reach.privacyParams.hasValidEpsilonAndDelta()) { + "Reach privacy params are invalid" } grpcRequire(vidSamplingInterval.width > 0) { "Vid sampling interval is unspecified" } } MeasurementSpec.MeasurementTypeCase.REACH_AND_FREQUENCY -> { - grpcRequire(reachAndFrequency.reachPrivacyParams.hasEpsilonAndDeltaSet()) { - "Reach privacy params are unspecified" + grpcRequire(reachAndFrequency.reachPrivacyParams.hasValidEpsilonAndDelta()) { + "Reach privacy params are invalid" } - grpcRequire(reachAndFrequency.frequencyPrivacyParams.hasEpsilonAndDeltaSet()) { - "Frequency privacy params are unspecified" + grpcRequire(reachAndFrequency.frequencyPrivacyParams.hasValidEpsilonAndDelta()) { + "Frequency privacy params are invalid" + } + grpcRequire(reachAndFrequency.maximumFrequency > 0) { + "maximum_frequency must be greater than 0" } grpcRequire(vidSamplingInterval.width > 0) { "Vid sampling interval is unspecified" } } MeasurementSpec.MeasurementTypeCase.IMPRESSION -> { - grpcRequire(impression.privacyParams.hasEpsilonAndDeltaSet()) { - "Impressions privacy params are unspecified" + grpcRequire(impression.privacyParams.hasValidEpsilonAndDelta()) { + "Impressions privacy params are invalid" } grpcRequire(impression.maximumFrequencyPerUser > 0) { @@ -309,8 +309,8 @@ private fun MeasurementSpec.validate() { } } MeasurementSpec.MeasurementTypeCase.DURATION -> { - grpcRequire(duration.privacyParams.hasEpsilonAndDeltaSet()) { - "Duration privacy params are unspecified" + grpcRequire(duration.privacyParams.hasValidEpsilonAndDelta()) { + "Duration privacy params are invalid" } grpcRequire(duration.maximumWatchDurationPerUser > 0) { diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt index 16f668fd284..1b91fe5f9ef 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/ProtoConversions.kt @@ -15,7 +15,6 @@ package org.wfanet.measurement.kingdom.service.api.v2alpha import com.google.protobuf.util.Timestamps -import com.google.type.date import com.google.type.interval import java.time.ZoneOffset import org.wfanet.measurement.api.Version @@ -232,6 +231,7 @@ fun InternalProtocolConfig.toProtocolConfig( source.liquidLegionsV2.dataProviderNoise.toDifferentialPrivacyParams() } ellipticCurveId = source.liquidLegionsV2.ellipticCurveId + @Suppress("DEPRECATION") // For legacy Measurements. maximumFrequency = source.liquidLegionsV2.maximumFrequency // Use `GEOMETRIC` for unspecified InternalNoiseMechanism for old Measurements. noiseMechanism = diff --git a/src/main/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ProtoConversions.kt b/src/main/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ProtoConversions.kt index f400f2a46d7..c5e0a51e001 100644 --- a/src/main/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ProtoConversions.kt +++ b/src/main/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ProtoConversions.kt @@ -237,6 +237,7 @@ fun buildMpcProtocolConfig( .toSystemDifferentialPrivacyParams() } ellipticCurveId = protocolConfig.liquidLegionsV2.ellipticCurveId + @Suppress("DEPRECATION") // For legacy Measurements. maximumFrequency = protocolConfig.liquidLegionsV2.maximumFrequency // Use `GEOMETRIC` for unspecified InternalNoiseMechanism for old Measurements. noiseMechanism = diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt index e75724e2047..68f976ebaa5 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulator.kt @@ -833,25 +833,22 @@ class EdpSimulator( private fun encryptLiquidLegionsV2Sketch( sketch: Sketch, + ellipticCurveId: Int, combinedPublicKey: AnySketchElGamalPublicKey, - protocol: ProtocolConfig.LiquidLegionsV2 + maximumValue: Int, ): ByteString { + require(maximumValue > 0) { "Maximum value must be positive" } logger.log(Level.INFO, "Encrypting Liquid Legions V2 Sketch...") - return sketchEncrypter.encrypt( - sketch, - protocol.ellipticCurveId, - combinedPublicKey, - protocol.maximumFrequency - ) + return sketchEncrypter.encrypt(sketch, ellipticCurveId, combinedPublicKey, maximumValue) } private fun encryptReachOnlyLiquidLegionsV2Sketch( sketch: Sketch, + ellipticCurveId: Int, combinedPublicKey: AnySketchElGamalPublicKey, - protocol: ProtocolConfig.ReachOnlyLiquidLegionsV2 ): ByteString { logger.log(Level.INFO, "Encrypting Reach-Only Liquid Legions V2 Sketch...") - return sketchEncrypter.encrypt(sketch, protocol.ellipticCurveId, combinedPublicKey) + return sketchEncrypter.encrypt(sketch, ellipticCurveId, combinedPublicKey) } /** @@ -901,7 +898,13 @@ class EdpSimulator( ) } - val encryptedSketch = encryptLiquidLegionsV2Sketch(sketch, combinedPublicKey, liquidLegionsV2) + val encryptedSketch = + encryptLiquidLegionsV2Sketch( + sketch, + liquidLegionsV2.ellipticCurveId, + combinedPublicKey, + measurementSpec.reachAndFrequency.maximumFrequency.coerceAtLeast(1) + ) fulfillRequisition(requisition.name, requisitionFingerprint, nonce, encryptedSketch) } @@ -916,31 +919,30 @@ class EdpSimulator( nonce: Long, eventGroupSpecs: Iterable ) { - val roLlv2Protocol: ProtocolConfig.Protocol = + val protocolConfig: ProtocolConfig.ReachOnlyLiquidLegionsV2 = requireNotNull( - requisition.protocolConfig.protocolsList.find { protocol -> - protocol.hasReachOnlyLiquidLegionsV2() + requisition.protocolConfig.protocolsList.find { protocol -> + protocol.hasReachOnlyLiquidLegionsV2() + } + ) { + "Protocol with ReachOnlyLiquidLegionsV2 is missing" } - ) { - "Protocol with ReachOnlyLiquidLegionsV2 is missing" - } - val reachOnlyLiquidLegionsV2: ProtocolConfig.ReachOnlyLiquidLegionsV2 = - roLlv2Protocol.reachOnlyLiquidLegionsV2 - val combinedPublicKey = - requisition.getCombinedPublicKey(reachOnlyLiquidLegionsV2.ellipticCurveId) + .reachOnlyLiquidLegionsV2 + val combinedPublicKey: AnySketchElGamalPublicKey = + requisition.getCombinedPublicKey(protocolConfig.ellipticCurveId) chargeLiquidLegionsV2PrivacyBudget( requisition.name, measurementSpec, eventGroupSpecs.map { it.spec }, - reachOnlyLiquidLegionsV2.noiseMechanism, + protocolConfig.noiseMechanism, requisition.duchiesCount ) val sketch = try { generateSketch( - reachOnlyLiquidLegionsV2.sketchParams.toSketchConfig(), + protocolConfig.sketchParams.toSketchConfig(), measurementSpec, eventGroupSpecs, ) @@ -957,7 +959,11 @@ class EdpSimulator( } val encryptedSketch = - encryptReachOnlyLiquidLegionsV2Sketch(sketch, combinedPublicKey, reachOnlyLiquidLegionsV2) + encryptReachOnlyLiquidLegionsV2Sketch( + sketch, + protocolConfig.ellipticCurveId, + combinedPublicKey + ) fulfillRequisition(requisition.name, requisitionFingerprint, nonce, encryptedSketch) } @@ -1152,7 +1158,10 @@ class EdpSimulator( return when (measurementSpec.measurementTypeCase) { MeasurementSpec.MeasurementTypeCase.REACH_AND_FREQUENCY -> { val (sampledReachValue, frequencyMap) = - MeasurementResults.computeReachAndFrequency(sampledVids) + MeasurementResults.computeReachAndFrequency( + sampledVids, + measurementSpec.reachAndFrequency.maximumFrequency + ) logger.info("Adding $directNoiseMechanism publisher noise to direct reach and frequency...") val sampledNoisedReachValue = diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/MeasurementResults.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/MeasurementResults.kt index d6943299b84..3cf2177f667 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/MeasurementResults.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/dataprovider/MeasurementResults.kt @@ -21,10 +21,7 @@ object MeasurementResults { data class ReachAndFrequency(val reach: Int, val relativeFrequencyDistribution: Map) /** Computes reach and frequency using the "deterministic count distinct" methodology. */ - fun computeReachAndFrequency( - sampledVids: Iterable, - maxFrequency: Int = Int.MAX_VALUE - ): ReachAndFrequency { + fun computeReachAndFrequency(sampledVids: Iterable, maxFrequency: Int): ReachAndFrequency { val eventsPerVid: Map = sampledVids.groupingBy { it }.eachCount() val reach: Int = eventsPerVid.keys.size diff --git a/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt b/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt index 2d02185f428..47137391edc 100644 --- a/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt +++ b/src/main/kotlin/org/wfanet/measurement/loadtest/measurementconsumer/MeasurementConsumerSimulator.kt @@ -130,21 +130,7 @@ class MeasurementConsumerSimulator( val measurement: Measurement, val measurementSpec: MeasurementSpec, val requisitions: List, - ) { - val maximumFrequency: Int - get() { - // TODO(world-federation-of-advertisers/cross-media-measurement-api#160): Use field from - // MeasurementSpec. - val protocols = measurement.protocolConfig.protocolsList - if (protocols.find { it.hasDirect() } != null) { - return Int.MAX_VALUE - } - - val protocol = - protocols.find { it.hasLiquidLegionsV2() } ?: error("Unable to determine max frequency") - return protocol.liquidLegionsV2.maximumFrequency - } - } + ) private val MeasurementInfo.sampledVids: Sequence get() { @@ -216,7 +202,7 @@ class MeasurementConsumerSimulator( delay(Duration.ofSeconds(5)) failure = getFailure(invalidMeasurement.name) } - assertThat(failure.message).contains("reach_privacy_params.delta") + assertThat(failure.message).contains("delta") logger.info("Receive failed Measurement from Kingdom: ${failure.message}. Test passes.") } @@ -527,11 +513,10 @@ class MeasurementConsumerSimulator( } private fun getExpectedReachAndFrequencyResult(measurementInfo: MeasurementInfo): Result { - val (reach, relativeFrequencyDistribution) = MeasurementResults.computeReachAndFrequency( measurementInfo.sampledVids.asIterable(), - measurementInfo.maximumFrequency + measurementInfo.measurementSpec.reachAndFrequency.maximumFrequency ) return result { this.reach = reach { value = reach.toLong() } @@ -563,6 +548,7 @@ class MeasurementConsumerSimulator( reachAndFrequency = reachAndFrequency { reachPrivacyParams = outputDpParams frequencyPrivacyParams = outputDpParams + maximumFrequency = 10 } vidSamplingInterval = vidSamplingInterval { start = 0.0f @@ -591,14 +577,15 @@ class MeasurementConsumerSimulator( serializedMeasurementPublicKey: ByteString, nonceHashes: List ): MeasurementSpec { + val invalidPrivacyParams = differentialPrivacyParams { + epsilon = 1.0 + delta = 0.0 + } return newReachAndFrequencyMeasurementSpec(serializedMeasurementPublicKey, nonceHashes).copy { - val invalidPrivacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 0.0 - } reachAndFrequency = reachAndFrequency { reachPrivacyParams = invalidPrivacyParams frequencyPrivacyParams = invalidPrivacyParams + maximumFrequency = 10 } } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/postgres/writers/SetMeasurementResult.kt b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/postgres/writers/SetMeasurementResult.kt index 47ff23f5e71..76ba467120b 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/postgres/writers/SetMeasurementResult.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/postgres/writers/SetMeasurementResult.kt @@ -154,7 +154,6 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : val scalarTableColumnsList = ArrayList() // Each metric contains results for several columns for (metric in report.metricsList) { - @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") // Proto enum fields are never null. when (val metricType = metric.details.metricTypeCase) { // REACH, IMPRESSION_COUNT, and WATCH_DURATION are aggregated in one table. Metric.Details.MetricTypeCase.REACH, @@ -163,7 +162,16 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : // One namedSetOperation is one column in the report for (namedSetOperation in metric.namedSetOperationsList) { scalarTableColumnsList += - namedSetOperation.toResultColumn(metricType, measurementResultsMap) + ReportKt.DetailsKt.ResultKt.column { + columnHeader = buildColumnHeader(metricType.name, namedSetOperation.displayName) + setOperations += + namedSetOperation.sortedMeasurementCalculations.map { + calculateScalarResult( + metricType, + it.getMeasurementResults(measurementResultsMap) + ) + } + } } } Metric.Details.MetricTypeCase.FREQUENCY_HISTOGRAM -> { @@ -220,33 +228,28 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : } } - /** Convert an [Metric.NamedSetOperation] to a [Report.Details.Result.Column] of a [Report] */ - private fun Metric.NamedSetOperation.toResultColumn( - metricType: Metric.Details.MetricTypeCase, - measurementResultsMap: Map, - maximumFrequency: Int = 0 - ): Report.Details.Result.Column { - val source = this - return ReportKt.DetailsKt.ResultKt.column { - columnHeader = buildColumnHeader(metricType.name, source.displayName) - for (measurementCalculation in - source.measurementCalculationsList.sortedWith { a, b -> - val start = Timestamps.compare(a.timeInterval.startTime, b.timeInterval.startTime) - if (start != 0) { - start - } else { - Timestamps.compare(a.timeInterval.endTime, b.timeInterval.endTime) - } - }) { - setOperations.addAll( - measurementCalculation.toSetOperationResults( - metricType, - measurementResultsMap, - maximumFrequency - ) - ) + private val Metric.NamedSetOperation.sortedMeasurementCalculations: List + get() { + return measurementCalculationsList.sortedWith { a, b -> + val start = Timestamps.compare(a.timeInterval.startTime, b.timeInterval.startTime) + if (start != 0) { + start + } else { + Timestamps.compare(a.timeInterval.endTime, b.timeInterval.endTime) + } } } + + private fun MeasurementCalculation.getMeasurementResults( + measurementResultsMap: Map + ): List { + return weightedMeasurementsList.map { + MeasurementResult( + measurementResultsMap[it.measurementReferenceId]?.result + ?: throw MeasurementNotFoundException(), + it.coefficient + ) + } } /** Build a column header given the metric and set operation name. */ @@ -254,39 +257,25 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : return "${metricTypeName}_$setOperationName" } - /** Calculate the equation in [MeasurementCalculation] to get the result. */ - private fun MeasurementCalculation.toSetOperationResults( + /** Calculate the equation to get the scalar result. */ + private fun calculateScalarResult( metricType: Metric.Details.MetricTypeCase, - measurementResultsMap: Map, - maximumFrequency: Int = 0 - ): List { - val source = this - val measurementResultsList = - source.weightedMeasurementsList.map { - MeasurementResult( - measurementResultsMap[it.measurementReferenceId]?.result - ?: throw MeasurementNotFoundException(), - it.coefficient - ) - } - @Suppress("WHEN_ENUM_CAN_BE_NULL_IN_JAVA") // Proto enum fields are never null. + measurementResultsList: List, + ): Double { return when (metricType) { - Metric.Details.MetricTypeCase.REACH -> listOf(calculateReachResults(measurementResultsList)) + Metric.Details.MetricTypeCase.REACH -> calculateReachResult(measurementResultsList) Metric.Details.MetricTypeCase.IMPRESSION_COUNT -> - listOf(calculateImpressionResults(measurementResultsList)) + calculateImpressionResult(measurementResultsList) Metric.Details.MetricTypeCase.WATCH_DURATION -> - listOf(calculateWatchDurationResults(measurementResultsList)) - Metric.Details.MetricTypeCase.FREQUENCY_HISTOGRAM -> { - calculateFrequencyHistogramResults(measurementResultsList, maximumFrequency) - } - Metric.Details.MetricTypeCase.METRICTYPE_NOT_SET -> { - error("Metric Type should be set.") - } + calculateWatchDurationResult(measurementResultsList) + Metric.Details.MetricTypeCase.FREQUENCY_HISTOGRAM -> + error("$metricType is not a scalar metric type") + Metric.Details.MetricTypeCase.METRICTYPE_NOT_SET -> error("Metric Type should be set.") } } /** Calculate the reach result by summing up weighted [Measurement]s. */ - private fun calculateReachResults(measurementResultsList: List): Double { + private fun calculateReachResult(measurementResultsList: List): Double { return measurementResultsList .sumOf { (result, coefficient) -> if (!result.hasReach()) { @@ -298,7 +287,7 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : } /** Calculate the impression result by summing up weighted [Measurement]s. */ - private fun calculateImpressionResults( + private fun calculateImpressionResult( measurementCoefficientPairsList: List ): Double { return measurementCoefficientPairsList @@ -312,7 +301,7 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : } /** Calculate the watch duration result by summing up weighted [Measurement]s. */ - private fun calculateWatchDurationResults( + private fun calculateWatchDurationResult( measurementCoefficientPairsList: List ): Double { val watchDuration = @@ -344,10 +333,9 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : } /** Calculate the frequency histogram result by summing up weighted [Measurement]s. */ - private fun calculateFrequencyHistogramResults( - measurementCoefficientPairsList: List, - maximumFrequency: Int - ): List { + private fun calculateFrequencyHistogram( + measurementCoefficientPairsList: List + ): Map { val aggregatedFrequencyHistogramMap = measurementCoefficientPairsList .map { (result, coefficient) -> @@ -369,12 +357,7 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : aggregatedFrequencyHistogramMap } - val resultFrequencyHistogramMap = mutableMapOf() - for (i in 1L..maximumFrequency) { - resultFrequencyHistogramMap[i] = aggregatedFrequencyHistogramMap.getOrDefault(i, 0.0) - } - - return resultFrequencyHistogramMap.entries.sortedBy { it.key }.map { it.value } + return aggregatedFrequencyHistogramMap } /** Convert a [Metric] to a [Report.Details.Result.HistogramTable] of a [Report] */ @@ -382,25 +365,38 @@ class SetMeasurementResult(private val request: SetMeasurementResultRequest) : rowHeaders: List, measurementResultsMap: Map ): Report.Details.Result.HistogramTable { + val setOperationFrequencyHistograms: List>> = + namedSetOperationsList.map { + it.sortedMeasurementCalculations.map { calculation -> + calculateFrequencyHistogram(calculation.getMeasurementResults(measurementResultsMap)) + } + } + val largestFrequency = + setOperationFrequencyHistograms.flatten().flatMap { it.keys }.maxOrNull() ?: 1L + val source = this - val maximumFrequency = source.details.frequencyHistogram.maximumFrequencyPerUser return ReportKt.DetailsKt.ResultKt.histogramTable { for (rowHeader in rowHeaders) { - for (frequency in 1..maximumFrequency) { + for (frequency in 1..largestFrequency) { rows += ReportKt.DetailsKt.ResultKt.HistogramTableKt.row { this.rowHeader = rowHeader - this.frequency = frequency + this.frequency = frequency.toInt() } } } - for (namedSetOperation in source.namedSetOperationsList) { + for ((namedSetOperation, frequencyHistograms) in + source.namedSetOperationsList.zip(setOperationFrequencyHistograms)) { columns += - namedSetOperation.toResultColumn( - source.details.metricTypeCase, - measurementResultsMap, - maximumFrequency - ) + ReportKt.DetailsKt.ResultKt.column { + columnHeader = + buildColumnHeader(source.details.metricTypeCase.name, namedSetOperation.displayName) + for (frequencyHistogram in frequencyHistograms) { + for (frequency in 1..largestFrequency) { + setOperations += frequencyHistogram.getOrDefault(frequency, 0.0) + } + } + } } } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/readers/MetricReader.kt b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/readers/MetricReader.kt index 0bc8a86c40c..43a027f809c 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/readers/MetricReader.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/readers/MetricReader.kt @@ -106,6 +106,7 @@ class MetricReader(private val readContext: ReadContext) { Metrics.DifferentialPrivacyDelta, Metrics.FrequencyDifferentialPrivacyEpsilon, Metrics.FrequencyDifferentialPrivacyDelta, + Metrics.MaximumFrequency, Metrics.MaximumFrequencyPerUser, Metrics.MaximumWatchDurationPerUser, Metrics.VidSamplingIntervalStart, @@ -367,6 +368,7 @@ class MetricReader(private val readContext: ReadContext) { val differentialPrivacyDelta: Double = row["DifferentialPrivacyDelta"] val frequencyDifferentialPrivacyEpsilon: Double? = row["FrequencyDifferentialPrivacyEpsilon"] val frequencyDifferentialPrivacyDelta: Double? = row["FrequencyDifferentialPrivacyDelta"] + val maximumFrequency: Int? = row["MaximumFrequency"] val maximumFrequencyPerUser: Int? = row["MaximumFrequencyPerUser"] val maximumWatchDurationPerUser: Int? = row["MaximumWatchDurationPerUser"] val vidSamplingStart: Float = row["VidSamplingIntervalStart"] @@ -415,7 +417,7 @@ class MetricReader(private val readContext: ReadContext) { if ( frequencyDifferentialPrivacyDelta == null || frequencyDifferentialPrivacyEpsilon == null || - maximumFrequencyPerUser == null + maximumFrequency == null ) { throw IllegalStateException() } @@ -432,7 +434,7 @@ class MetricReader(private val readContext: ReadContext) { epsilon = frequencyDifferentialPrivacyEpsilon delta = frequencyDifferentialPrivacyDelta } - this.maximumFrequencyPerUser = maximumFrequencyPerUser + this.maximumFrequency = maximumFrequency } } MetricSpec.TypeCase.IMPRESSION_COUNT -> { diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/writers/CreateMetrics.kt b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/writers/CreateMetrics.kt index 4622d6b99f2..bc30caa5050 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/writers/CreateMetrics.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/deploy/v2/postgres/writers/CreateMetrics.kt @@ -171,9 +171,10 @@ class CreateMetrics(private val requests: List) : VidSamplingIntervalWidth, CreateTime, MetricDetails, - MetricDetailsJson + MetricDetailsJson, + MaximumFrequency ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20) """ ) { requests.forEach { @@ -207,8 +208,9 @@ class CreateMetrics(private val requests: List) : bind("$10", frequencyHistogram.reachPrivacyParams.delta) bind("$11", frequencyHistogram.frequencyPrivacyParams.epsilon) bind("$12", frequencyHistogram.reachPrivacyParams.delta) - bind("$13", frequencyHistogram.maximumFrequencyPerUser) + bind("$13", null) bind("$14", null) + bind("$20", frequencyHistogram.maximumFrequency) } MetricSpec.TypeCase.REACH -> { val reach = it.metric.metricSpec.reach @@ -218,6 +220,7 @@ class CreateMetrics(private val requests: List) : bind("$12", null) bind("$13", null) bind("$14", null) + bind("$20", null) } MetricSpec.TypeCase.IMPRESSION_COUNT -> { val impressionCount = it.metric.metricSpec.impressionCount @@ -227,6 +230,7 @@ class CreateMetrics(private val requests: List) : bind("$12", null) bind("$13", impressionCount.maximumFrequencyPerUser) bind("$14", null) + bind("$20", null) } MetricSpec.TypeCase.WATCH_DURATION -> { val watchDuration = it.metric.metricSpec.watchDuration @@ -236,6 +240,7 @@ class CreateMetrics(private val requests: List) : bind("$12", null) bind("$13", null) bind("$14", watchDuration.maximumWatchDurationPerUser) + bind("$20", null) } MetricSpec.TypeCase.TYPE_NOT_SET -> {} } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsService.kt index 936b729c74a..48c5a4d321f 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsService.kt @@ -190,7 +190,6 @@ private val REACH_ONLY_VID_SAMPLING_START_LIST = (0 until NUMBER_REACH_ONLY_BUCKETS).map { it * REACH_ONLY_VID_SAMPLING_WIDTH } private const val REACH_ONLY_REACH_EPSILON = 0.0041 private const val REACH_ONLY_FREQUENCY_EPSILON = 0.0001 -private const val REACH_ONLY_MAXIMUM_FREQUENCY_PER_USER = 1 private const val REACH_FREQUENCY_VID_SAMPLING_WIDTH = 5.0f / NUMBER_VID_BUCKETS private const val NUMBER_REACH_FREQUENCY_BUCKETS = 19 @@ -235,7 +234,7 @@ private val REACH_ONLY_MEASUREMENT_SPEC = epsilon = REACH_ONLY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_ONLY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = 1 } private val timeIntervalComparator: (TimeInterval, TimeInterval) -> Int = { a, b -> @@ -1243,7 +1242,7 @@ class ReportsService( InternalMetricTypeCase.FREQUENCY_HISTOGRAM -> { reachAndFrequency = buildReachAndFrequencyMeasurementSpec( - internalMetricDetails.frequencyHistogram.maximumFrequencyPerUser + internalMetricDetails.frequencyHistogram.maximumFrequency ) vidSamplingInterval = buildReachAndFrequencyVidSamplingInterval(secureRandom) } @@ -1257,8 +1256,7 @@ class ReportsService( InternalMetricTypeCase.WATCH_DURATION -> { duration = buildDurationMeasurementSpec( - internalMetricDetails.watchDuration.maximumWatchDurationPerUser, - internalMetricDetails.watchDuration.maximumFrequencyPerUser + internalMetricDetails.watchDuration.maximumWatchDurationPerUser ) vidSamplingInterval = buildDurationVidSamplingInterval(secureRandom) } @@ -1667,7 +1665,7 @@ private fun buildDurationVidSamplingInterval(secureRandom: SecureRandom): VidSam /** Builds a [MeasurementSpec.ReachAndFrequency] for reach-frequency. */ private fun buildReachAndFrequencyMeasurementSpec( - maximumFrequencyPerUser: Int + maximumFrequency: Int ): MeasurementSpec.ReachAndFrequency { return MeasurementSpecKt.reachAndFrequency { reachPrivacyParams = differentialPrivacyParams { @@ -1678,7 +1676,7 @@ private fun buildReachAndFrequencyMeasurementSpec( epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - this.maximumFrequencyPerUser = maximumFrequencyPerUser + this.maximumFrequency = maximumFrequency } } @@ -1697,8 +1695,7 @@ private fun buildImpressionMeasurementSpec( /** Builds a [MeasurementSpec.ReachAndFrequency] for watch duration. */ private fun buildDurationMeasurementSpec( - maximumWatchDurationPerUser: Int, - maximumFrequencyPerUser: Int + maximumWatchDurationPerUser: Int ): MeasurementSpec.Duration { return MeasurementSpecKt.duration { privacyParams = differentialPrivacyParams { @@ -1706,7 +1703,6 @@ private fun buildDurationMeasurementSpec( delta = DIFFERENTIAL_PRIVACY_DELTA } this.maximumWatchDurationPerUser = maximumWatchDurationPerUser - this.maximumFrequencyPerUser = maximumFrequencyPerUser } } @@ -1725,7 +1721,6 @@ private fun SetOperation.Type.toInternal(): InternalSetOperation.Type { private fun WatchDurationParams.toInternal(): InternalWatchDurationParams { val source = this return InternalMetricKt.watchDurationParams { - maximumFrequencyPerUser = source.maximumFrequencyPerUser maximumWatchDurationPerUser = source.maximumWatchDurationPerUser } } @@ -1742,7 +1737,7 @@ private fun ImpressionCountParams.toInternal(): InternalImpressionCountParams { private fun FrequencyHistogramParams.toInternal(): InternalFrequencyHistogramParams { val source = this return InternalMetricKt.frequencyHistogramParams { - maximumFrequencyPerUser = source.maximumFrequencyPerUser + maximumFrequency = source.maximumFrequencyPerUser } } @@ -2083,10 +2078,7 @@ private fun InternalSetOperation.Type.toType(): SetOperation.Type { /** Converts an internal [InternalWatchDurationParams] to a public [WatchDurationParams]. */ private fun InternalWatchDurationParams.toWatchDuration(): WatchDurationParams { val source = this - return watchDurationParams { - maximumFrequencyPerUser = source.maximumFrequencyPerUser - maximumWatchDurationPerUser = source.maximumWatchDurationPerUser - } + return watchDurationParams { maximumWatchDurationPerUser = source.maximumWatchDurationPerUser } } /** Converts an internal [InternalImpressionCountParams] to a public [ImpressionCountParams]. */ @@ -2100,7 +2092,7 @@ private fun InternalImpressionCountParams.toImpressionCount(): ImpressionCountPa */ private fun InternalFrequencyHistogramParams.toFrequencyHistogram(): FrequencyHistogramParams { val source = this - return frequencyHistogramParams { maximumFrequencyPerUser = source.maximumFrequencyPerUser } + return frequencyHistogramParams { maximumFrequencyPerUser = source.maximumFrequency } } /** Converts an internal [InternalPeriodicTimeInterval] to a public [PeriodicTimeInterval]. */ diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaults.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaults.kt index 3fbddaf96a6..6af9daba260 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaults.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaults.kt @@ -141,12 +141,9 @@ private fun MetricSpec.FrequencyHistogramParams.withDefaults( metricSpecConfig.frequencyHistogramParams.frequencyPrivacyParams.epsilon, metricSpecConfig.frequencyHistogramParams.frequencyPrivacyParams.delta ) - maximumFrequencyPerUser = - if (hasMaximumFrequencyPerUser()) { - maximumFrequencyPerUser - } else { - metricSpecConfig.frequencyHistogramParams.maximumFrequencyPerUser - } + if (maximumFrequency == 0) { + maximumFrequency = metricSpecConfig.frequencyHistogramParams.maximumFrequency + } } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt index 7d260fe84e8..78a46194428 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsService.kt @@ -1510,7 +1510,7 @@ private fun buildMetricResult(metric: InternalMetric): MetricResult { frequencyHistogram = calculateFrequencyHistogramResults( metric.weightedMeasurementsList, - metric.metricSpec.frequencyHistogram.maximumFrequencyPerUser + metric.metricSpec.frequencyHistogram.maximumFrequency ) } InternalMetricSpec.TypeCase.IMPRESSION_COUNT -> { diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt index 16fe231dcb6..acee66d98cf 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ProtoConversions.kt @@ -195,9 +195,7 @@ fun MetricSpec.FrequencyHistogramParams.toInternal(): InternalMetricSpec.Frequen return InternalMetricSpecKt.frequencyHistogramParams { reachPrivacyParams = source.reachPrivacyParams.toInternal() frequencyPrivacyParams = source.frequencyPrivacyParams.toInternal() - if (source.hasMaximumFrequencyPerUser()) { - maximumFrequencyPerUser = source.maximumFrequencyPerUser - } + maximumFrequency = source.maximumFrequency } } @@ -255,7 +253,7 @@ fun InternalMetricSpec.toMetricSpec(): MetricSpec { InternalMetricSpec.TypeCase.FREQUENCY_HISTOGRAM -> frequencyHistogram = MetricSpecKt.frequencyHistogramParams { - maximumFrequencyPerUser = source.frequencyHistogram.maximumFrequencyPerUser + maximumFrequency = source.frequencyHistogram.maximumFrequency reachPrivacyParams = source.frequencyHistogram.reachPrivacyParams.toPrivacyParams() frequencyPrivacyParams = source.frequencyHistogram.frequencyPrivacyParams.toPrivacyParams() @@ -319,7 +317,7 @@ fun InternalMetricSpec.FrequencyHistogramParams.toReachAndFrequency(): return MeasurementSpecKt.reachAndFrequency { reachPrivacyParams = source.reachPrivacyParams.toCmmsPrivacyParams() frequencyPrivacyParams = source.frequencyPrivacyParams.toCmmsPrivacyParams() - maximumFrequencyPerUser = source.maximumFrequencyPerUser + maximumFrequency = source.maximumFrequency } } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/MeasurementsServiceTest.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/MeasurementsServiceTest.kt index 066f17fd152..da7edf796e8 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/MeasurementsServiceTest.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/MeasurementsServiceTest.kt @@ -206,9 +206,7 @@ abstract class MeasurementsServiceTest { @Test fun `setMeasurementResult succeeds in setting the result for report with RF metric`() { val metricDetails = - MetricKt.details { - frequencyHistogram = MetricKt.frequencyHistogramParams { maximumFrequencyPerUser = 2 } - } + MetricKt.details { frequencyHistogram = Metric.FrequencyHistogramParams.getDefaultInstance() } val createdReport = runBlocking { reportsService.createReport( createReportRequest { @@ -319,11 +317,9 @@ abstract class MeasurementsServiceTest { } @Test - fun `setMeasurementResult sets result for RF metric report with padding at the end`() { + fun `setMeasurementResult sets result for RF metric report including frequencies with 0 value`() { val metricDetails = - MetricKt.details { - frequencyHistogram = MetricKt.frequencyHistogramParams { maximumFrequencyPerUser = 3 } - } + MetricKt.details { frequencyHistogram = Metric.FrequencyHistogramParams.getDefaultInstance() } val createdReport = runBlocking { reportsService.createReport( createReportRequest { @@ -359,8 +355,8 @@ abstract class MeasurementsServiceTest { reach = MeasurementKt.ResultKt.reach { value = 100L } frequency = MeasurementKt.ResultKt.frequency { - relativeFrequencyDistribution[1] = 0.8 relativeFrequencyDistribution[2] = 0.2 + relativeFrequencyDistribution[3] = 0.8 } } } @@ -373,10 +369,7 @@ abstract class MeasurementsServiceTest { MeasurementKt.result { reach = MeasurementKt.ResultKt.reach { value = 200L } frequency = - MeasurementKt.ResultKt.frequency { - relativeFrequencyDistribution[1] = 0.3 - relativeFrequencyDistribution[2] = 0.7 - } + MeasurementKt.ResultKt.frequency { relativeFrequencyDistribution[2] = 0.7 } } } ) @@ -426,117 +419,6 @@ abstract class MeasurementsServiceTest { rowHeader = "1970-01-01T00:01:50.000000011Z-1970-01-01T00:02:00.000000012Z" frequency = 3 } - columns += - ReportKt.DetailsKt.ResultKt.column { - columnHeader = - buildColumnHeader( - metricDetails.metricTypeCase.name, - NAMED_SET_OPERATION.displayName - ) - setOperations += 260.0 - setOperations += 440.0 - setOperations += 0.0 - setOperations += 520.0 - setOperations += 880.0 - setOperations += 0.0 - } - } - } - ) - } - - @Test - fun `setMeasurementResult sets result for RF metric report with padding at the beginning`() { - val metricDetails = - MetricKt.details { - frequencyHistogram = MetricKt.frequencyHistogramParams { maximumFrequencyPerUser = 2 } - } - val createdReport = runBlocking { - reportsService.createReport( - createReportRequest { - measurements += - CreateReportRequestKt.measurementKey { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - measurementReferenceId = MEASUREMENT_REFERENCE_ID - } - measurements += - CreateReportRequestKt.measurementKey { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - measurementReferenceId = MEASUREMENT_REFERENCE_ID_2 - } - report = report { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - reportIdempotencyKey = "1235" - periodicTimeInterval = PERIODIC_TIME_INTERVAL - metrics += metric { - details = metricDetails - namedSetOperations += NAMED_SET_OPERATION - } - } - } - ) - } - runBlocking { - service.setMeasurementResult( - setMeasurementResultRequest { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - measurementReferenceId = MEASUREMENT_REFERENCE_ID - result = - MeasurementKt.result { - reach = MeasurementKt.ResultKt.reach { value = 100L } - frequency = - MeasurementKt.ResultKt.frequency { relativeFrequencyDistribution[2] = 0.2 } - } - } - ) - service.setMeasurementResult( - setMeasurementResultRequest { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - measurementReferenceId = MEASUREMENT_REFERENCE_ID_2 - result = - MeasurementKt.result { - reach = MeasurementKt.ResultKt.reach { value = 200L } - frequency = - MeasurementKt.ResultKt.frequency { relativeFrequencyDistribution[2] = 0.7 } - } - } - ) - } - val retrievedReport = runBlocking { - reportsService.getReport( - getReportRequest { - measurementConsumerReferenceId = MEASUREMENT_CONSUMER_REFERENCE_ID - externalReportId = createdReport.externalReportId - } - ) - } - assertThat(retrievedReport.state).isEqualTo(Report.State.SUCCEEDED) - assertThat(retrievedReport.details.result) - .usingDoubleTolerance(2.0) - .isEqualTo( - ReportKt.DetailsKt.result { - histogramTables += - ReportKt.DetailsKt.ResultKt.histogramTable { - rows += - ReportKt.DetailsKt.ResultKt.HistogramTableKt.row { - rowHeader = "1970-01-01T00:01:40.000000010Z-1970-01-01T00:01:50.000000011Z" - frequency = 1 - } - rows += - ReportKt.DetailsKt.ResultKt.HistogramTableKt.row { - rowHeader = "1970-01-01T00:01:40.000000010Z-1970-01-01T00:01:50.000000011Z" - frequency = 2 - } - rows += - ReportKt.DetailsKt.ResultKt.HistogramTableKt.row { - rowHeader = "1970-01-01T00:01:50.000000011Z-1970-01-01T00:02:00.000000012Z" - frequency = 1 - } - rows += - ReportKt.DetailsKt.ResultKt.HistogramTableKt.row { - rowHeader = "1970-01-01T00:01:50.000000011Z-1970-01-01T00:02:00.000000012Z" - frequency = 2 - } columns += ReportKt.DetailsKt.ResultKt.column { columnHeader = @@ -546,8 +428,10 @@ abstract class MeasurementsServiceTest { ) setOperations += 0.0 setOperations += 440.0 + setOperations += 80.0 setOperations += 0.0 setOperations += 880.0 + setOperations += 160.0 } } } @@ -558,11 +442,7 @@ abstract class MeasurementsServiceTest { fun `setMeasurementResult succeeds in setting the result for report with duration metric`() { val metricDetails = MetricKt.details { - watchDuration = - MetricKt.watchDurationParams { - maximumFrequencyPerUser = 2 - maximumWatchDurationPerUser = 100 - } + watchDuration = MetricKt.watchDurationParams { maximumWatchDurationPerUser = 100 } } val createdReport = runBlocking { reportsService.createReport( @@ -969,8 +849,7 @@ abstract class MeasurementsServiceTest { metrics += metric { details = MetricKt.details { - frequencyHistogram = - MetricKt.frequencyHistogramParams { maximumFrequencyPerUser = 2 } + frequencyHistogram = Metric.FrequencyHistogramParams.getDefaultInstance() } namedSetOperations += NAMED_SET_OPERATION } diff --git a/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/v2/MetricsServiceTest.kt b/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/v2/MetricsServiceTest.kt index c4a74e4b0d8..7a18249e9f6 100644 --- a/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/v2/MetricsServiceTest.kt +++ b/src/main/kotlin/org/wfanet/measurement/reporting/service/internal/testing/v2/MetricsServiceTest.kt @@ -204,7 +204,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -562,7 +562,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -673,7 +673,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -783,7 +783,7 @@ abstract class MetricsServiceTest { epsilon = 3.0 delta = 4.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -854,7 +854,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1168,7 +1168,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1239,7 +1239,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1316,7 +1316,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1404,7 +1404,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1501,7 +1501,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1591,7 +1591,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1670,7 +1670,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1748,7 +1748,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1828,7 +1828,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1909,7 +1909,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -1989,7 +1989,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -2068,7 +2068,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -2180,7 +2180,7 @@ abstract class MetricsServiceTest { epsilon = 1.0 delta = 2.0 } - maximumFrequencyPerUser = 5 + maximumFrequency = 5 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { diff --git a/src/main/proto/wfa/measurement/config/reporting/metric_spec_config.proto b/src/main/proto/wfa/measurement/config/reporting/metric_spec_config.proto index 9a82588cefb..c672a2e93c5 100644 --- a/src/main/proto/wfa/measurement/config/reporting/metric_spec_config.proto +++ b/src/main/proto/wfa/measurement/config/reporting/metric_spec_config.proto @@ -46,9 +46,9 @@ message MetricSpecConfig { DifferentialPrivacyParams frequency_privacy_params = 2; // Maximum frequency cut-off value in the frequency histogram. // - // Counts with frequency higher than `max_frequency` will be aggregated + // Counts with frequency higher than `maximum_frequency` will be aggregated // together. - int32 maximum_frequency_per_user = 3; + int32 maximum_frequency = 3; } // Parameters that are used to generate `Impression Count` metric. message ImpressionCountParams { diff --git a/src/main/proto/wfa/measurement/internal/kingdom/protocol_config.proto b/src/main/proto/wfa/measurement/internal/kingdom/protocol_config.proto index d8f447eeb4e..dbbb12fb07c 100644 --- a/src/main/proto/wfa/measurement/internal/kingdom/protocol_config.proto +++ b/src/main/proto/wfa/measurement/internal/kingdom/protocol_config.proto @@ -53,7 +53,10 @@ message ProtocolConfig { // The maximum frequency to reveal in the histogram. For reach-only liquid // legions protocol, this field should be ignored. - int32 maximum_frequency = 4; + // + // Deprecated: No longer set on new Measurements where it is specified in + // MeasurementSpec instead. + int32 maximum_frequency = 4 [deprecated = true]; // The mechanism to generate noise during computation. // @@ -86,4 +89,4 @@ message LiquidLegionsSketchParams { // The size of the distribution of the sampling indicator value. // Reach-Only Liquid Legions protocol ignores this field. int64 sampling_indicator_size = 3; -} \ No newline at end of file +} diff --git a/src/main/proto/wfa/measurement/internal/reporting/metric.proto b/src/main/proto/wfa/measurement/internal/reporting/metric.proto index 7d0794f6487..8782fc8ec21 100644 --- a/src/main/proto/wfa/measurement/internal/reporting/metric.proto +++ b/src/main/proto/wfa/measurement/internal/reporting/metric.proto @@ -25,13 +25,13 @@ option java_multiple_files = true; message Metric { message ReachParams {} message FrequencyHistogramParams { - int32 maximum_frequency_per_user = 1; + int32 maximum_frequency = 1; } message ImpressionCountParams { int32 maximum_frequency_per_user = 1; } message WatchDurationParams { - int32 maximum_frequency_per_user = 1; + reserved 1; // In seconds. int32 maximum_watch_duration_per_user = 2; } diff --git a/src/main/proto/wfa/measurement/internal/reporting/v2/metric.proto b/src/main/proto/wfa/measurement/internal/reporting/v2/metric.proto index e1a86ebd072..a0333c8b638 100644 --- a/src/main/proto/wfa/measurement/internal/reporting/v2/metric.proto +++ b/src/main/proto/wfa/measurement/internal/reporting/v2/metric.proto @@ -35,7 +35,7 @@ message MetricSpec { message FrequencyHistogramParams { DifferentialPrivacyParams reach_privacy_params = 1; DifferentialPrivacyParams frequency_privacy_params = 2; - int32 maximum_frequency_per_user = 3; + int32 maximum_frequency = 3; } message ImpressionCountParams { DifferentialPrivacyParams privacy_params = 1; diff --git a/src/main/proto/wfa/measurement/reporting/v1alpha/metric.proto b/src/main/proto/wfa/measurement/reporting/v1alpha/metric.proto index c69d6fb53cb..2a18e2b4650 100644 --- a/src/main/proto/wfa/measurement/reporting/v1alpha/metric.proto +++ b/src/main/proto/wfa/measurement/reporting/v1alpha/metric.proto @@ -29,8 +29,7 @@ message Metric { message ReachParams {} // Parameters that are used to generate `Frequency Histogram` metric. message FrequencyHistogramParams { - // Maximum frequency cut-off value in the frequency histogram. Counts with - // frequency higher than `max_frequency` will be aggregated together. + // Maximum frequency to reveal in the histogram. int32 maximum_frequency_per_user = 1; } // Parameters that are used to generate `Impression Count` metric. @@ -47,7 +46,9 @@ message Metric { // Parameters that are used to generate `Watch Duration` metric. message WatchDurationParams { // Maximum frequency per user that will be included in this measurement. - int32 maximum_frequency_per_user = 1; + // + // Deprecated: Not supported by the CMMS. + int32 maximum_frequency_per_user = 1 [deprecated = true]; // Maximum watch duration per user that will be included in this // measurement. Recommended maximum_watch_duration_per_user = cap on the // total watch duration of all the impressions of a user = 4000 sec for the diff --git a/src/main/proto/wfa/measurement/reporting/v2alpha/metric.proto b/src/main/proto/wfa/measurement/reporting/v2alpha/metric.proto index 5cb513fe38b..d379081a129 100644 --- a/src/main/proto/wfa/measurement/reporting/v2alpha/metric.proto +++ b/src/main/proto/wfa/measurement/reporting/v2alpha/metric.proto @@ -56,9 +56,9 @@ message MetricSpec { [(google.api.field_behavior) = REQUIRED]; // Maximum frequency cut-off value in the frequency histogram. // - // Counts with frequency higher than `max_frequency` will be aggregated + // Counts with frequency higher than `maximum_frequency` will be aggregated // together. If not set, the default value will be used and outputted here. - optional int32 maximum_frequency_per_user = 3; + int32 maximum_frequency = 3; } // Parameters that are used to generate `Impression Count` metric. message ImpressionCountParams { diff --git a/src/main/proto/wfa/measurement/system/v1alpha/computation.proto b/src/main/proto/wfa/measurement/system/v1alpha/computation.proto index 2ea33f97dbf..29d663a4213 100644 --- a/src/main/proto/wfa/measurement/system/v1alpha/computation.proto +++ b/src/main/proto/wfa/measurement/system/v1alpha/computation.proto @@ -154,7 +154,10 @@ message Computation { // The maximum frequency to reveal in the histogram. For reach-only liquid // legions protocol, this field should be ignored. - int32 maximum_frequency = 4 [(google.api.field_behavior) = REQUIRED]; + // + // Deprecated: Specified in MeasurementSpec instead except for legacy + // Computations. + int32 maximum_frequency = 4 [deprecated = true]; // The mechanism to generate noise during computation. NoiseMechanism noise_mechanism = 5 diff --git a/src/main/resources/reporting/postgres/create-v2-reporting-schema.sql b/src/main/resources/reporting/postgres/create-v2-reporting-schema.sql index c0eed674fe2..a492db13eb9 100644 --- a/src/main/resources/reporting/postgres/create-v2-reporting-schema.sql +++ b/src/main/resources/reporting/postgres/create-v2-reporting-schema.sql @@ -238,7 +238,9 @@ CREATE TABLE Metrics ( FrequencyDifferentialPrivacyEpsilon DOUBLE PRECISION, FrequencyDifferentialPrivacyDelta DOUBLE PRECISION, - -- Must not be NULL if MetricType is FREQUENCY_HISTOGRAM or IMPRESSION_COUNT + -- Must not be NULL if MetricType is FREQUENCY_HISTOGRAM + MaximumFrequency bigint, + -- Must not be NULL if MetricType is IMPRESSION_COUNT MaximumFrequencyPerUser bigint, -- Must not be NULL if MetricType is WATCH_DURATION MaximumWatchDurationPerUser bigint, diff --git a/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/BenchmarkTest.kt b/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/BenchmarkTest.kt index bbc9e539d14..2729169babe 100644 --- a/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/BenchmarkTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/BenchmarkTest.kt @@ -306,6 +306,7 @@ class BenchmarkTest { "--api-key=$API_KEY", "--measurement-consumer=measurementConsumers/777", "--reach-and-frequency", + "--max-frequency=5", "--reach-privacy-epsilon=0.015", "--reach-privacy-delta=0.0", "--frequency-privacy-epsilon=0.02", @@ -388,7 +389,7 @@ class BenchmarkTest { "--impression", "--impression-privacy-epsilon=0.015", "--impression-privacy-delta=0.0", - "--impression-max-frequency=1000", + "--max-frequency-per-user=1000", "--vid-sampling-start=0.1", "--vid-sampling-width=0.2", "--private-key-der-file=$SECRETS_DIR/mc_cs_private.der", diff --git a/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/MeasurementSystemTest.kt b/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/MeasurementSystemTest.kt index 30deae4c12e..3603fa21cba 100644 --- a/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/MeasurementSystemTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/api/v2alpha/tools/MeasurementSystemTest.kt @@ -792,7 +792,7 @@ class MeasurementSystemTest { "--reach-privacy-delta=0.0", "--frequency-privacy-epsilon=0.02", "--frequency-privacy-delta=0.0", - "--reach-max-frequency=1000", + "--max-frequency=5", "--vid-sampling-start=0.1", "--vid-sampling-width=0.2", "--measurement-consumer=$measurementConsumerName", @@ -887,7 +887,7 @@ class MeasurementSystemTest { epsilon = 0.02 delta = 0.0 } - maximumFrequencyPerUser = 1000 + maximumFrequency = 5 } vidSamplingInterval = MeasurementSpecKt.vidSamplingInterval { @@ -996,7 +996,7 @@ class MeasurementSystemTest { "--impression", "--impression-privacy-epsilon=0.015", "--impression-privacy-delta=0.0", - "--impression-max-frequency=1000", + "--max-frequency-per-user=1000", "--vid-sampling-start=0.1", "--vid-sampling-width=0.2", "--private-key-der-file=$SECRETS_DIR/mc_cs_private.der", diff --git a/src/test/kotlin/org/wfanet/measurement/duchy/daemon/herald/HeraldTest.kt b/src/test/kotlin/org/wfanet/measurement/duchy/daemon/herald/HeraldTest.kt index 9ff4d00a892..bce92309db9 100644 --- a/src/test/kotlin/org/wfanet/measurement/duchy/daemon/herald/HeraldTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/duchy/daemon/herald/HeraldTest.kt @@ -157,6 +157,7 @@ private val PUBLIC_API_MEASUREMENT_SPEC = measurementSpec { epsilon = 2.1 delta = 2.2 } + maximumFrequency = 10 } nonceHashes += REACH_ONLY_REQUISITION_1.nonceHash nonceHashes += REACH_ONLY_REQUISITION_2.nonceHash @@ -195,7 +196,6 @@ private val LLV2_MPC_PROTOCOL_CONFIG = mpcProtocolConfig { } } ellipticCurveId = 415 - maximumFrequency = 10 noiseMechanism = SystemNoiseMechanism.GEOMETRIC } } @@ -566,7 +566,6 @@ class HeraldTest { role = RoleInComputation.AGGREGATOR parameters = LiquidLegionsSketchAggregationV2Kt.ComputationDetailsKt.parameters { - maximumFrequency = 10 sketchParameters = liquidLegionsSketchParameters { decayRate = 12.0 size = 100_000L diff --git a/src/test/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2MillTest.kt b/src/test/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2MillTest.kt index e5e5590b648..3cd497760d4 100644 --- a/src/test/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2MillTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/duchy/daemon/mill/liquidlegionsv2/LiquidLegionsV2MillTest.kt @@ -51,8 +51,6 @@ import org.mockito.kotlin.whenever import org.wfanet.anysketch.crypto.CombineElGamalPublicKeysRequest import org.wfanet.anysketch.crypto.CombineElGamalPublicKeysResponse import org.wfanet.measurement.api.v2alpha.ElGamalPublicKey as V2AlphaElGamalPublicKey -import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.reach -import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.reachAndFrequency import org.wfanet.measurement.api.v2alpha.MeasurementSpecKt.vidSamplingInterval import org.wfanet.measurement.api.v2alpha.measurementSpec import org.wfanet.measurement.common.crypto.SigningKeyHandle @@ -180,7 +178,6 @@ private const val DUCHY_ONE_NAME = "DUCHY_ONE" private const val DUCHY_TWO_NAME = "DUCHY_TWO" private const val DUCHY_THREE_NAME = "DUCHY_THREE" private const val MAX_FREQUENCY = 15 -private const val MAX_REQUESTED_FREQUENCY = 15 private const val DECAY_RATE = 12.0 private const val SKETCH_SIZE = 100_000L private const val CURVE_ID = 415L // NID_X9_62_prime256v1 @@ -261,6 +258,12 @@ private val LLV2_PARAMETERS = parameters { noise = TEST_NOISE_CONFIG ellipticCurveId = CURVE_ID.toInt() } +private val LLV2_PARAMETERS_FREQUENCY_ONE = LLV2_PARAMETERS.copy { maximumFrequency = 1 } +private val LLV2_PARAMETERS_REACH = + LLV2_PARAMETERS.copy { + clearMaximumFrequency() + noise = noise.copy { clearFrequencyNoiseConfig() } + } // In the test, use the same set of cert and encryption key for all parties. private const val CONSENT_SIGNALING_CERT_NAME = "Just a name" @@ -331,7 +334,6 @@ private val MEASUREMENT_SPEC = measurementSpec { nonceHashes += TEST_REQUISITION_1.nonceHash nonceHashes += TEST_REQUISITION_2.nonceHash nonceHashes += TEST_REQUISITION_3.nonceHash - reachAndFrequency = reachAndFrequency { maximumFrequencyPerUser = MAX_REQUESTED_FREQUENCY } } private val SERIALIZED_MEASUREMENT_SPEC: ByteString = MEASUREMENT_SPEC.toByteString() @@ -339,27 +341,8 @@ private val MEASUREMENT_SPEC_WITH_VID_SAMPLING_WIDTH = measurementSpec { nonceHashes += TEST_REQUISITION_1.nonceHash nonceHashes += TEST_REQUISITION_2.nonceHash nonceHashes += TEST_REQUISITION_3.nonceHash - reachAndFrequency = reachAndFrequency {} vidSamplingInterval = vidSamplingInterval { width = 0.5f } } - -private val MEASUREMENT_SPEC_FREQUENCY_ONE = measurementSpec { - nonceHashes += TEST_REQUISITION_1.nonceHash - nonceHashes += TEST_REQUISITION_2.nonceHash - nonceHashes += TEST_REQUISITION_3.nonceHash - reachAndFrequency = reachAndFrequency { maximumFrequencyPerUser = 1 } -} -private val SERIALIZED_MEASUREMENT_SPEC_FREQUENCY_ONE: ByteString = - MEASUREMENT_SPEC_FREQUENCY_ONE.toByteString() - -private val REACH_MEASUREMENT_SPEC = measurementSpec { - nonceHashes += TEST_REQUISITION_1.nonceHash - nonceHashes += TEST_REQUISITION_2.nonceHash - nonceHashes += TEST_REQUISITION_3.nonceHash - reach = reach {} -} -private val SERIALIZED_REACH_MEASUREMENT_SPEC: ByteString = REACH_MEASUREMENT_SPEC.toByteString() - private val SERIALIZED_MEASUREMENT_SPEC_WITH_VID_SAMPLING_WIDTH = MEASUREMENT_SPEC_WITH_VID_SAMPLING_WIDTH.toByteString() @@ -392,97 +375,37 @@ private val AGGREGATOR_COMPUTATION_DETAILS = computationDetails { } } -private val AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE = computationDetails { - kingdomComputation = kingdomComputationDetails { - publicApiVersion = PUBLIC_API_VERSION - measurementPublicKey = ENCRYPTION_PUBLIC_KEY.toDuchyEncryptionPublicKey() - measurementSpec = SERIALIZED_MEASUREMENT_SPEC_FREQUENCY_ONE +private val AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE = + AGGREGATOR_COMPUTATION_DETAILS.copy { + liquidLegionsV2 = liquidLegionsV2.copy { parameters = LLV2_PARAMETERS_FREQUENCY_ONE } } - liquidLegionsV2 = - LiquidLegionsSketchAggregationV2Kt.computationDetails { - role = RoleInComputation.AGGREGATOR - parameters = LLV2_PARAMETERS - participant += - listOf(COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3, COMPUTATION_PARTICIPANT_1) - combinedPublicKey = COMBINED_PUBLIC_KEY - // partiallyCombinedPublicKey and combinedPublicKey are the same at the aggregator. - partiallyCombinedPublicKey = COMBINED_PUBLIC_KEY - localElgamalKey = DUCHY_ONE_KEY_PAIR - } -} -private val AGGREGATOR_REACH_COMPUTATION_DETAILS = computationDetails { - kingdomComputation = kingdomComputationDetails { - publicApiVersion = PUBLIC_API_VERSION - measurementPublicKey = ENCRYPTION_PUBLIC_KEY.toDuchyEncryptionPublicKey() - measurementSpec = SERIALIZED_REACH_MEASUREMENT_SPEC +private val AGGREGATOR_COMPUTATION_DETAILS_REACH = + AGGREGATOR_COMPUTATION_DETAILS.copy { + liquidLegionsV2 = liquidLegionsV2.copy { parameters = LLV2_PARAMETERS_REACH } } - liquidLegionsV2 = - LiquidLegionsSketchAggregationV2Kt.computationDetails { - role = RoleInComputation.AGGREGATOR - parameters = LLV2_PARAMETERS - participant += - listOf(COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3, COMPUTATION_PARTICIPANT_1) - combinedPublicKey = COMBINED_PUBLIC_KEY - // partiallyCombinedPublicKey and combinedPublicKey are the same at the aggregator. - partiallyCombinedPublicKey = COMBINED_PUBLIC_KEY - localElgamalKey = DUCHY_ONE_KEY_PAIR - } -} -private val NON_AGGREGATOR_COMPUTATION_DETAILS = computationDetails { - kingdomComputation = kingdomComputationDetails { - publicApiVersion = PUBLIC_API_VERSION - measurementPublicKey = ENCRYPTION_PUBLIC_KEY.toDuchyEncryptionPublicKey() - measurementSpec = SERIALIZED_MEASUREMENT_SPEC +private val NON_AGGREGATOR_COMPUTATION_DETAILS = + AGGREGATOR_COMPUTATION_DETAILS.copy { + liquidLegionsV2 = + liquidLegionsV2.copy { + role = RoleInComputation.NON_AGGREGATOR + participant.clear() + participant += + listOf(COMPUTATION_PARTICIPANT_1, COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3) + partiallyCombinedPublicKey = PARTIALLY_COMBINED_PUBLIC_KEY + } } - liquidLegionsV2 = - LiquidLegionsSketchAggregationV2Kt.computationDetails { - role = RoleInComputation.NON_AGGREGATOR - parameters = LLV2_PARAMETERS - participant += - listOf(COMPUTATION_PARTICIPANT_1, COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3) - combinedPublicKey = COMBINED_PUBLIC_KEY - partiallyCombinedPublicKey = PARTIALLY_COMBINED_PUBLIC_KEY - localElgamalKey = DUCHY_ONE_KEY_PAIR - } -} -private val NON_AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE = computationDetails { - kingdomComputation = kingdomComputationDetails { - publicApiVersion = PUBLIC_API_VERSION - measurementPublicKey = ENCRYPTION_PUBLIC_KEY.toDuchyEncryptionPublicKey() - measurementSpec = SERIALIZED_MEASUREMENT_SPEC_FREQUENCY_ONE +private val NON_AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE = + NON_AGGREGATOR_COMPUTATION_DETAILS.copy { + liquidLegionsV2 = liquidLegionsV2.copy { parameters = LLV2_PARAMETERS_FREQUENCY_ONE } } - liquidLegionsV2 = - LiquidLegionsSketchAggregationV2Kt.computationDetails { - role = RoleInComputation.NON_AGGREGATOR - parameters = LLV2_PARAMETERS - participant += - listOf(COMPUTATION_PARTICIPANT_1, COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3) - combinedPublicKey = COMBINED_PUBLIC_KEY - partiallyCombinedPublicKey = PARTIALLY_COMBINED_PUBLIC_KEY - localElgamalKey = DUCHY_ONE_KEY_PAIR - } -} -private val NON_AGGREGATOR_REACH_COMPUTATION_DETAILS = computationDetails { - kingdomComputation = kingdomComputationDetails { - publicApiVersion = PUBLIC_API_VERSION - measurementPublicKey = ENCRYPTION_PUBLIC_KEY.toDuchyEncryptionPublicKey() - measurementSpec = SERIALIZED_REACH_MEASUREMENT_SPEC +private val NON_AGGREGATOR_COMPUTATION_DETAILS_REACH = + NON_AGGREGATOR_COMPUTATION_DETAILS.copy { + liquidLegionsV2 = liquidLegionsV2.copy { parameters = LLV2_PARAMETERS_REACH } } - liquidLegionsV2 = - LiquidLegionsSketchAggregationV2Kt.computationDetails { - role = RoleInComputation.NON_AGGREGATOR - parameters = LLV2_PARAMETERS - participant += - listOf(COMPUTATION_PARTICIPANT_1, COMPUTATION_PARTICIPANT_2, COMPUTATION_PARTICIPANT_3) - combinedPublicKey = COMBINED_PUBLIC_KEY - partiallyCombinedPublicKey = PARTIALLY_COMBINED_PUBLIC_KEY - localElgamalKey = DUCHY_ONE_KEY_PAIR - } -} @RunWith(JUnit4::class) class LiquidLegionsV2MillTest { @@ -1267,7 +1190,7 @@ class LiquidLegionsV2MillTest { globalReachDpNoise = TEST_NOISE_CONFIG.reachNoiseConfig.globalReachDpNoise } } - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY parallelism = PARALLELISM } ) @@ -1372,7 +1295,7 @@ class LiquidLegionsV2MillTest { globalReachDpNoise = TEST_NOISE_CONFIG.reachNoiseConfig.globalReachDpNoise } } - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY parallelism = PARALLELISM } ) @@ -1614,7 +1537,7 @@ class LiquidLegionsV2MillTest { curveId = CURVE_ID parallelism = PARALLELISM noiseParameters = flagCountTupleNoiseGenerationParameters { - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY contributorsCount = WORKER_COUNT dpParams = TEST_NOISE_CONFIG.frequencyNoiseConfig } @@ -1693,7 +1616,7 @@ class LiquidLegionsV2MillTest { fakeComputationDb.addComputation( partialToken.localComputationId, partialToken.computationStage, - computationDetails = AGGREGATOR_REACH_COMPUTATION_DETAILS, + computationDetails = AGGREGATOR_COMPUTATION_DETAILS_REACH, blobs = listOf( inputBlobContext.toMetadata(ComputationBlobDependency.INPUT), @@ -1816,7 +1739,7 @@ class LiquidLegionsV2MillTest { curveId = CURVE_ID parallelism = PARALLELISM noiseParameters = flagCountTupleNoiseGenerationParameters { - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY contributorsCount = WORKER_COUNT dpParams = TEST_NOISE_CONFIG.frequencyNoiseConfig } @@ -1893,7 +1816,7 @@ class LiquidLegionsV2MillTest { fakeComputationDb.addComputation( partialToken.localComputationId, partialToken.computationStage, - computationDetails = NON_AGGREGATOR_REACH_COMPUTATION_DETAILS, + computationDetails = NON_AGGREGATOR_COMPUTATION_DETAILS_REACH, blobs = listOf( inputBlobContext.toMetadata(ComputationBlobDependency.INPUT), @@ -1921,7 +1844,6 @@ class LiquidLegionsV2MillTest { flagCountTuples = ByteString.copyFromUtf8("data") localElGamalKeyPair = DUCHY_ONE_KEY_PAIR compositeElGamalPublicKey = COMBINED_PUBLIC_KEY - partialCompositeElGamalPublicKey = PARTIALLY_COMBINED_PUBLIC_KEY curveId = CURVE_ID parallelism = PARALLELISM } @@ -2029,7 +1951,7 @@ class LiquidLegionsV2MillTest { localElGamalKeyPair = DUCHY_ONE_KEY_PAIR compositeElGamalPublicKey = COMBINED_PUBLIC_KEY curveId = CURVE_ID - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY vidSamplingIntervalWidth = 0.5f liquidLegionsParametersBuilder.apply { decayRate = DECAY_RATE @@ -2041,7 +1963,7 @@ class LiquidLegionsV2MillTest { } frequencyNoiseParametersBuilder.apply { contributorsCount = WORKER_COUNT - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY dpParams = TEST_NOISE_CONFIG.frequencyNoiseConfig } } @@ -2059,11 +1981,11 @@ class LiquidLegionsV2MillTest { ) .build() val computationDetailsWithReach = - AGGREGATOR_COMPUTATION_DETAILS.toBuilder() + AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE.toBuilder() .apply { liquidLegionsV2Builder.apply { reachEstimateBuilder.reach = 123 } kingdomComputation = - kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC_FREQUENCY_ONE } + kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC } } .build() val inputBlobContext = @@ -2072,9 +1994,9 @@ class LiquidLegionsV2MillTest { ComputationBlobContext(GLOBAL_ID, EXECUTION_PHASE_TWO.toProtocolStage(), 1L) computationStore.writeString(inputBlobContext, "data") val computationDetailsWithVidSamplingWidth = - AGGREGATOR_COMPUTATION_DETAILS.copy { + AGGREGATOR_COMPUTATION_DETAILS_FREQUENCY_ONE.copy { kingdomComputation = - kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC_FREQUENCY_ONE } + kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC } } fakeComputationDb.addComputation( partialToken.localComputationId, @@ -2170,11 +2092,11 @@ class LiquidLegionsV2MillTest { ) .build() val computationDetailsWithReach = - AGGREGATOR_COMPUTATION_DETAILS.copy { + AGGREGATOR_COMPUTATION_DETAILS_REACH.copy { liquidLegionsV2 = liquidLegionsV2.copy { reachEstimate = reachEstimate.copy { reach = 123 } } kingdomComputation = - kingdomComputation.copy { measurementSpec = SERIALIZED_REACH_MEASUREMENT_SPEC } + kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC } } val inputBlobContext = ComputationBlobContext(GLOBAL_ID, EXECUTION_PHASE_TWO.toProtocolStage(), 0L) @@ -2182,9 +2104,9 @@ class LiquidLegionsV2MillTest { ComputationBlobContext(GLOBAL_ID, EXECUTION_PHASE_TWO.toProtocolStage(), 1L) computationStore.writeString(inputBlobContext, "data") val computationDetailsWithVidSamplingWidth = - AGGREGATOR_COMPUTATION_DETAILS.copy { + AGGREGATOR_COMPUTATION_DETAILS_REACH.copy { kingdomComputation = - kingdomComputation.copy { measurementSpec = SERIALIZED_REACH_MEASUREMENT_SPEC } + kingdomComputation.copy { measurementSpec = SERIALIZED_MEASUREMENT_SPEC } } fakeComputationDb.addComputation( partialToken.localComputationId, @@ -2450,7 +2372,7 @@ class LiquidLegionsV2MillTest { sameKeyAggregatorMatrix = ByteString.copyFromUtf8("data") localElGamalKeyPair = DUCHY_ONE_KEY_PAIR curveId = CURVE_ID - maximumFrequency = MAX_REQUESTED_FREQUENCY + maximumFrequency = MAX_FREQUENCY globalFrequencyDpNoisePerBucketBuilder.apply { contributorsCount = WORKER_COUNT dpParams = TEST_NOISE_CONFIG.frequencyNoiseConfig diff --git a/src/test/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsServiceTest.kt index 6cbeefdeba0..46da57bfb16 100644 --- a/src/test/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/kingdom/service/api/v2alpha/MeasurementsServiceTest.kt @@ -432,21 +432,7 @@ class MeasurementsServiceTest { clearFailure() results.clear() - measurementSpec = - measurementSpec.copy { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - impression = impression { - privacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 0.0 - } - maximumFrequencyPerUser = 1 - } - } - .toByteString() - } + measurementSpec = measurementSpec.copy { data = IMPRESSION_MEASUREMENT_SPEC.toByteString() } protocolConfig = protocolConfig.copy { @@ -509,21 +495,7 @@ class MeasurementsServiceTest { clearFailure() results.clear() - measurementSpec = - measurementSpec.copy { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - duration = duration { - privacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 0.0 - } - maximumWatchDurationPerUser = 1 - } - } - .toByteString() - } + measurementSpec = measurementSpec.copy { data = DURATION_MEASUREMENT_SPEC.toByteString() } protocolConfig = protocolConfig.copy { @@ -784,560 +756,509 @@ class MeasurementsServiceTest { @Test fun `createMeasurement throws INVALID_ARGUMENT when measurement spec is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = MEASUREMENT.copy { clearMeasurementSpec() } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { measurement = MEASUREMENT.copy { clearMeasurementSpec() } } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("measurement_spec") } @Test fun `createMeasurement throws INVALID_ARGUMENT when measurement public key is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = MEASUREMENT_SPEC.copy { clearMeasurementPublicKey() }.toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = MEASUREMENT_SPEC.copy { clearMeasurementPublicKey() }.toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("key") } @Test fun `createMeasurement throws INVALID_ARGUMENT when RF reach privacy params are missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = + MEASUREMENT_SPEC.copy { + reachAndFrequency = reachAndFrequency.copy { clearReachPrivacyParams() } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - reachAndFrequency = reachAndFrequency { - frequencyPrivacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - vidSamplingInterval = vidSamplingInterval { width = 1.0F } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("reach") } @Test fun `createMeasurement throws INVALID_ARGUMENT when RF frequency privacy params are missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = + MEASUREMENT_SPEC.copy { + reachAndFrequency = reachAndFrequency.copy { clearFrequencyPrivacyParams() } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - reachAndFrequency = reachAndFrequency { - reachPrivacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - vidSamplingInterval = vidSamplingInterval { width = 1.0F } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("frequency") } @Test fun `createMeasurement throws INVALID_ARGUMENT when RF vid sampling interval is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = MEASUREMENT_SPEC.copy { clearVidSamplingInterval() }.toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - clearVidSamplingInterval() - reachAndFrequency = reachAndFrequency { - reachPrivacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - frequencyPrivacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("sampling") } @Test fun `createMeasurement throws INVALID_ARGUMENT when RF epsilon privacy param is 0`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = + MEASUREMENT_SPEC.copy { + reachAndFrequency = + reachAndFrequency.copy { + reachPrivacyParams = reachPrivacyParams.copy { clearEpsilon() } + } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - clearVidSamplingInterval() - reachAndFrequency = reachAndFrequency { - reachPrivacyParams = differentialPrivacyParams { - epsilon = 0.0 - delta = 0.0 - } - frequencyPrivacyParams = differentialPrivacyParams { - epsilon = 0.0 - delta = 0.0 - } - } - vidSamplingInterval = vidSamplingInterval { width = 1.0F } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) + runBlocking { service.createMeasurement(request) } + } + } + + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("privacy") + } + + @Test + fun `createMeasurement throws INVALID_ARGUMENT when RF spec is missing max frequency`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = signedData { + data = + MEASUREMENT_SPEC.copy { + reachAndFrequency = reachAndFrequency.copy { clearMaximumFrequency() } + } + .toByteString() } } + } + + val exception = + assertFailsWith { + withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { + runBlocking { service.createMeasurement(request) } + } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().contains("maximum_frequency") } @Test fun `createMeasurement throws INVALID_ARGUMENT when Reach-only privacy params are missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + REACH_ONLY_MEASUREMENT.copy { + measurementSpec = signedData { + data = + REACH_ONLY_MEASUREMENT_SPEC.copy { reach = reach.copy { clearPrivacyParams() } } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - reach = reach {} - vidSamplingInterval = vidSamplingInterval { width = 1.0F } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("privacy") } @Test fun `createMeasurement throws INVALID_ARGUMENT when Reach-only vid sampling interval is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + REACH_ONLY_MEASUREMENT.copy { + measurementSpec = signedData { + data = REACH_ONLY_MEASUREMENT_SPEC.copy { clearVidSamplingInterval() }.toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - clearVidSamplingInterval() - reach = reach { - privacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("sampling") } @Test fun `createMeasurement throws INVALID_ARGUMENT when Reach-only epsilon privacy param is 0`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + REACH_ONLY_MEASUREMENT.copy { + measurementSpec = signedData { + data = + REACH_ONLY_MEASUREMENT_SPEC.copy { + reach = reach.copy { privacyParams = privacyParams.copy { clearEpsilon() } } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - clearVidSamplingInterval() - reach = reach { - privacyParams = differentialPrivacyParams { - epsilon = 0.0 - delta = 0.0 - } - } - vidSamplingInterval = vidSamplingInterval { width = 1.0F } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("privacy") } @Test fun `createMeasurement throws INVALID_ARGUMENT when impression privacy params are missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = + measurementSpec.copy { + data = + IMPRESSION_MEASUREMENT_SPEC.copy { + impression = impression.copy { clearPrivacyParams() } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - impression = impression { maximumFrequencyPerUser = 1 } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("privacy") } @Test fun `createMeasurement throws INVALID_ARGUMENT when impression max freq per user is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = + measurementSpec.copy { + data = + IMPRESSION_MEASUREMENT_SPEC.copy { + impression = impression.copy { clearMaximumFrequencyPerUser() } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - impression = impression { - privacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("frequency") } @Test fun `createMeasurement throws INVALID_ARGUMENT when duration privacy params are missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = + measurementSpec.copy { + data = + DURATION_MEASUREMENT_SPEC.copy { duration = duration.copy { clearPrivacyParams() } } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - duration = duration { maximumWatchDurationPerUser = 1 } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("privacy") } @Test fun `createMeasurement throws INVALID_ARGUMENT when duration watch time per user is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = + measurementSpec.copy { + data = + DURATION_MEASUREMENT_SPEC.copy { + duration = duration.copy { clearMaximumWatchDurationPerUser() } + } + .toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = - MEASUREMENT_SPEC.copy { - clearReachAndFrequency() - duration = duration { - privacyParams = differentialPrivacyParams { - epsilon = 1.0 - delta = 1.0 - } - } - } - .toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("maximum") } @Test fun `createMeasurement throws INVALID_ARGUMENT when measurement type is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + measurementSpec = + measurementSpec.copy { + data = MEASUREMENT_SPEC.copy { clearMeasurementType() }.toByteString() + } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - measurementSpec = signedData { - data = MEASUREMENT_SPEC.copy { clearMeasurementType() }.toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("type") } @Test fun `createMeasurement throws INVALID_ARGUMENT when Data Providers is missing`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = MEASUREMENT.copy { dataProviders.clear() } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { measurement = MEASUREMENT.copy { dataProviders.clear() } } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("provider") } @Test fun `createMeasurement throws INVALID_ARGUMENT when Data Providers Entry is missing key`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = MEASUREMENT.copy { dataProviders[0] = dataProviders[0].copy { clearKey() } } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - dataProviders.clear() - dataProviders += dataProviderEntry { key = "" } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("provider") } @Test fun `createMeasurement throws error when Data Providers Entry value is missing cert name`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + dataProviders[0] = + dataProviders[0].copy { value = value.copy { clearDataProviderCertificate() } } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - dataProviders.clear() - dataProviders += dataProviderEntry { key = DATA_PROVIDERS_NAME } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("provider") } @Test fun `createMeasurement throws error when Data Providers Entry value is missing public key`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + dataProviders[0] = + dataProviders[0].copy { value = value.copy { clearDataProviderPublicKey() } } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - dataProviders.clear() - dataProviders += dataProviderEntry { - key = DATA_PROVIDERS_NAME - value = value { dataProviderCertificate = DATA_PROVIDERS_CERTIFICATE_NAME } - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("key") } @Test fun `createMeasurement throws error when Data Providers Entry value is missing spec`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + dataProviders[0] = + dataProviders[0].copy { value = value.copy { clearEncryptedRequisitionSpec() } } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - dataProviders.clear() - dataProviders += dataProviderEntry { - key = DATA_PROVIDERS_NAME - value = value { - dataProviderCertificate = DATA_PROVIDERS_CERTIFICATE_NAME - dataProviderPublicKey = signedData { - data = UPDATE_TIME.toByteString() - signature = UPDATE_TIME.toByteString() - } - } - } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("requisition") } @Test fun `createMeasurement throws error when Data Providers Entry value is missing nonce hash`() { + val request = createMeasurementRequest { + parent = MEASUREMENT_CONSUMER_NAME + measurement = + MEASUREMENT.copy { + dataProviders[0] = dataProviders[0].copy { value = value.copy { clearNonceHash() } } + } + } + val exception = assertFailsWith { withMeasurementConsumerPrincipal(MEASUREMENT_CONSUMER_NAME) { - runBlocking { - service.createMeasurement( - createMeasurementRequest { - measurement = - MEASUREMENT.copy { - dataProviders[0] = - dataProviders[0].copy { value = value.copy { clearNonceHash() } } - } - } - ) - } + runBlocking { service.createMeasurement(request) } } } + assertThat(exception.status.code).isEqualTo(Status.Code.INVALID_ARGUMENT) + assertThat(exception).hasMessageThat().ignoringCase().contains("nonce") } @Test @@ -1866,11 +1787,36 @@ class MeasurementsServiceTest { epsilon = 1.0 delta = 1.0 } + maximumFrequency = 10 } vidSamplingInterval = vidSamplingInterval { width = 1.0f } nonceHashes += EXTERNAL_DATA_PROVIDER_IDS.map { it.value.toByteString() } } + private val IMPRESSION_MEASUREMENT_SPEC = + MEASUREMENT_SPEC.copy { + clearReachAndFrequency() + impression = impression { + privacyParams = differentialPrivacyParams { + epsilon = 1.0 + delta = 0.0 + } + maximumFrequencyPerUser = 1 + } + } + + private val DURATION_MEASUREMENT_SPEC = + MEASUREMENT_SPEC.copy { + clearReachAndFrequency() + duration = duration { + privacyParams = differentialPrivacyParams { + epsilon = 1.0 + delta = 0.0 + } + maximumWatchDurationPerUser = 1 + } + } + private val MEASUREMENT = measurement { name = MEASUREMENT_NAME measurementConsumerCertificate = MEASUREMENT_CONSUMER_CERTIFICATE_NAME diff --git a/src/test/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ComputationsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ComputationsServiceTest.kt index ad4271dd421..2afffda8c14 100644 --- a/src/test/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ComputationsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/kingdom/service/system/v1alpha/ComputationsServiceTest.kt @@ -226,7 +226,6 @@ private val INTERNAL_MEASUREMENT = internalMeasurement { samplingIndicatorSize = 1000 } ellipticCurveId = 123 - maximumFrequency = 12 noiseMechanism = InternalNoiseMechanism.GEOMETRIC } } @@ -319,7 +318,6 @@ class ComputationsServiceTest { decayRate = 10.0 maxSize = 100 } - maximumFrequency = 12 mpcNoiseBuilder.apply { blindedHistogramNoiseBuilder.apply { epsilon = 1.1 @@ -331,7 +329,6 @@ class ComputationsServiceTest { } } ellipticCurveId = 123 - maximumFrequency = 12 noiseMechanism = NoiseMechanism.GEOMETRIC } addRequisitions( diff --git a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt index 14821445245..aad66a9d3e2 100644 --- a/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/loadtest/dataprovider/EdpSimulatorTest.kt @@ -671,7 +671,6 @@ class EdpSimulatorTest { samplingIndicatorSize = 10_000_000 } ellipticCurveId = 415 - maximumFrequency = 12 } } } @@ -1133,7 +1132,6 @@ class EdpSimulatorTest { samplingIndicatorSize = 10_000_000 } ellipticCurveId = 415 - maximumFrequency = 12 } } } @@ -1483,6 +1481,7 @@ class EdpSimulatorTest { reachAndFrequency = reachAndFrequency { reachPrivacyParams = OUTPUT_DP_PARAMS frequencyPrivacyParams = OUTPUT_DP_PARAMS + maximumFrequency = 10 } vidSamplingInterval = vidSamplingInterval { start = 0.0f @@ -1525,7 +1524,6 @@ class EdpSimulatorTest { noiseMechanism = NOISE_MECHANISM sketchParams = LIQUID_LEGIONS_SKETCH_PARAMS ellipticCurveId = 415 - maximumFrequency = 12 } } } diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsServiceTest.kt index e4af21a5425..8eaf3d9028f 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v1alpha/ReportsServiceTest.kt @@ -209,7 +209,6 @@ private val REACH_ONLY_VID_SAMPLING_START_LIST = (0 until NUMBER_REACH_ONLY_BUCKETS).map { it * REACH_ONLY_VID_SAMPLING_WIDTH } private const val REACH_ONLY_REACH_EPSILON = 0.0041 private const val REACH_ONLY_FREQUENCY_EPSILON = 0.0001 -private const val REACH_ONLY_MAXIMUM_FREQUENCY_PER_USER = 1 private const val REACH_FREQUENCY_VID_SAMPLING_WIDTH = 5.0f / NUMBER_VID_BUCKETS private const val NUMBER_REACH_FREQUENCY_BUCKETS = 19 @@ -221,6 +220,7 @@ private val REACH_FREQUENCY_VID_SAMPLING_START_LIST = } private const val REACH_FREQUENCY_REACH_EPSILON = 0.0033 private const val REACH_FREQUENCY_FREQUENCY_EPSILON = 0.115 +private const val MAXIMUM_FREQUENCY = 10 private const val IMPRESSION_VID_SAMPLING_WIDTH = 62.0f / NUMBER_VID_BUCKETS private const val NUMBER_IMPRESSION_BUCKETS = 1 @@ -621,7 +621,7 @@ private val REACH_ONLY_MEASUREMENT_SPEC = measurementSpec { epsilon = REACH_ONLY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_ONLY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = 1 } vidSamplingInterval = vidSamplingInterval { start = REACH_ONLY_VID_SAMPLING_START_LIST[SECURE_RANDOM_OUTPUT_INT] @@ -707,7 +707,7 @@ private val REACH_FREQUENCY_MEASUREMENT_SPEC = measurementSpec { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = MAXIMUM_FREQUENCY } vidSamplingInterval = vidSamplingInterval { start = REACH_FREQUENCY_VID_SAMPLING_START_LIST[SECURE_RANDOM_OUTPUT_INT] @@ -852,7 +852,6 @@ private val WATCH_DURATION_MEASUREMENT_SPEC = measurementSpec { delta = DIFFERENTIAL_PRIVACY_DELTA } maximumWatchDurationPerUser = MAXIMUM_WATCH_DURATION_PER_USER - maximumFrequencyPerUser = MAXIMUM_FREQUENCY_PER_USER } vidSamplingInterval = vidSamplingInterval { start = WATCH_DURATION_VID_SAMPLING_START_LIST[SECURE_RANDOM_OUTPUT_INT] @@ -1024,9 +1023,7 @@ private val INTERNAL_FREQUENCY_HISTOGRAM_METRIC = internalMetric { details = InternalMetricKt.details { frequencyHistogram = - InternalMetricKt.frequencyHistogramParams { - maximumFrequencyPerUser = MAXIMUM_FREQUENCY_PER_USER - } + InternalMetricKt.frequencyHistogramParams { maximumFrequency = MAXIMUM_FREQUENCY_PER_USER } cumulative = false } namedSetOperations.add(INTERNAL_NAMED_FREQUENCY_HISTOGRAM_SET_OPERATION) @@ -1053,7 +1050,6 @@ private val INTERNAL_IMPRESSION_METRIC = internalMetric { // Watch duration metric private val WATCH_DURATION_METRIC = metric { watchDuration = watchDurationParams { - maximumFrequencyPerUser = MAXIMUM_FREQUENCY_PER_USER maximumWatchDurationPerUser = MAXIMUM_WATCH_DURATION_PER_USER } cumulative = false @@ -1064,7 +1060,6 @@ private val INTERNAL_WATCH_DURATION_METRIC = internalMetric { InternalMetricKt.details { watchDuration = InternalMetricKt.watchDurationParams { - maximumFrequencyPerUser = MAXIMUM_FREQUENCY_PER_USER maximumWatchDurationPerUser = MAXIMUM_WATCH_DURATION_PER_USER } cumulative = false diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaultsTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaultsTest.kt index 6fd41fd8845..34eb140e4d7 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaultsTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricSpecDefaultsTest.kt @@ -41,7 +41,7 @@ private const val REACH_FREQUENCY_VID_SAMPLING_WIDTH = 5.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_VID_SAMPLING_START = 48.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_REACH_EPSILON = 0.0033 private const val REACH_FREQUENCY_FREQUENCY_EPSILON = 0.115 -private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER = 10 +private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY = 10 private const val IMPRESSION_VID_SAMPLING_WIDTH = 62.0f / NUMBER_VID_BUCKETS private const val IMPRESSION_VID_SAMPLING_START = 143.0f / NUMBER_VID_BUCKETS @@ -115,8 +115,7 @@ class MetricSpecDefaultsTest { epsilon = METRIC_SPEC_CONFIG.frequencyHistogramParams.frequencyPrivacyParams.epsilon delta = METRIC_SPEC_CONFIG.frequencyHistogramParams.frequencyPrivacyParams.delta } - maximumFrequencyPerUser = - METRIC_SPEC_CONFIG.frequencyHistogramParams.maximumFrequencyPerUser + maximumFrequency = METRIC_SPEC_CONFIG.frequencyHistogramParams.maximumFrequency } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -141,8 +140,7 @@ class MetricSpecDefaultsTest { epsilon = METRIC_SPEC_CONFIG.frequencyHistogramParams.frequencyPrivacyParams.epsilon * 2 delta = METRIC_SPEC_CONFIG.frequencyHistogramParams.frequencyPrivacyParams.delta * 2 } - maximumFrequencyPerUser = - METRIC_SPEC_CONFIG.frequencyHistogramParams.maximumFrequencyPerUser * 2 + maximumFrequency = METRIC_SPEC_CONFIG.frequencyHistogramParams.maximumFrequency * 2 } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -420,7 +418,7 @@ class MetricSpecDefaultsTest { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = REACH_FREQUENCY_MAXIMUM_FREQUENCY } frequencyHistogramVidSamplingInterval = MetricSpecConfigKt.vidSamplingInterval { diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt index 8c00f626285..e536e9c8d07 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/MetricsServiceTest.kt @@ -210,7 +210,7 @@ private const val REACH_FREQUENCY_VID_SAMPLING_WIDTH = 5.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_VID_SAMPLING_START = 48.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_REACH_EPSILON = 0.0033 private const val REACH_FREQUENCY_FREQUENCY_EPSILON = 0.115 -private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER = 10 +private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY = 10 private const val IMPRESSION_VID_SAMPLING_WIDTH = 62.0f / NUMBER_VID_BUCKETS private const val IMPRESSION_VID_SAMPLING_START = 143.0f / NUMBER_VID_BUCKETS @@ -254,7 +254,7 @@ private val METRIC_SPEC_CONFIG = metricSpecConfig { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = REACH_FREQUENCY_MAXIMUM_FREQUENCY } frequencyHistogramVidSamplingInterval = MetricSpecConfigKt.vidSamplingInterval { diff --git a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsServiceTest.kt b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsServiceTest.kt index c7f8e40d392..961c5794f14 100644 --- a/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsServiceTest.kt +++ b/src/test/kotlin/org/wfanet/measurement/reporting/service/api/v2alpha/ReportsServiceTest.kt @@ -119,7 +119,7 @@ private const val REACH_FREQUENCY_VID_SAMPLING_WIDTH = 5.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_VID_SAMPLING_START = 48.0f / NUMBER_VID_BUCKETS private const val REACH_FREQUENCY_REACH_EPSILON = 0.0033 private const val REACH_FREQUENCY_FREQUENCY_EPSILON = 0.115 -private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER = 10 +private const val REACH_FREQUENCY_MAXIMUM_FREQUENCY = 10 private const val IMPRESSION_VID_SAMPLING_WIDTH = 62.0f / NUMBER_VID_BUCKETS private const val IMPRESSION_VID_SAMPLING_START = 143.0f / NUMBER_VID_BUCKETS @@ -3164,7 +3164,7 @@ class ReportsServiceTest { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = REACH_FREQUENCY_MAXIMUM_FREQUENCY } frequencyHistogramVidSamplingInterval = MetricSpecConfigKt.vidSamplingInterval { @@ -3246,7 +3246,7 @@ class ReportsServiceTest { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = REACH_FREQUENCY_MAXIMUM_FREQUENCY } vidSamplingInterval = MetricSpecKt.vidSamplingInterval { @@ -3267,7 +3267,7 @@ class ReportsServiceTest { epsilon = REACH_FREQUENCY_FREQUENCY_EPSILON delta = DIFFERENTIAL_PRIVACY_DELTA } - maximumFrequencyPerUser = REACH_FREQUENCY_MAXIMUM_FREQUENCY_PER_USER + maximumFrequency = REACH_FREQUENCY_MAXIMUM_FREQUENCY } vidSamplingInterval = InternalMetricSpecKt.vidSamplingInterval {