diff --git a/index.bs b/index.bs index 47d6e6e45..9a88b28ad 100644 --- a/index.bs +++ b/index.bs @@ -836,7 +836,7 @@ An attribution source is a [=struct=] with the following items: :: A non-negative integer. : remaining aggregatable attribution bucket budget :: A [=map=] whose [=map/key|keys=] are [=strings=] and whose [=map/value|values=] are - non-negative integers. + positive integers. : aggregatable dedup keys :: A [=set=] of [=aggregatable dedup key/dedup key|aggregatable dedup key values=] associated with this [=attribution source=]. : debug reporting enabled @@ -3683,7 +3683,7 @@ To parse aggregatable buckets given a [=map=] |map|: :: |filterPair|[0] : [=aggregatable bucket/negated filters=] :: |filterPair|[1] - 1. [=set/Append=] |aggregatableBucket| to |aggregatableBuckets|. + 1. [=list/Append=] |aggregatableBucket| to |aggregatableBuckets|. 1. Return |aggregatableBuckets|. To parse attribution scopes for trigger from a [=map=] |map|: @@ -3972,7 +3972,7 @@ To find matching aggregatable bucket given an [=attribution trigger=] To get remaining aggregatable budget for matched bucket given an [=attribution trigger=] |trigger| and an [=attribution source=] |sourceToAttribute|: 1. Let |matchedBucket| be the result of running [=find matching aggregatable bucket=] with |trigger| and |sourceToAttribute|. 1. [=Assert=]: |matchedBucket| is not null. -1. If |sourceToAttribute|'s [=attribution source/remaining aggregatable attribution bucket budget=][|matchedBucket|] does not [=map/exists|exist=] +1. If |sourceToAttribute|'s [=attribution source/remaining aggregatable attribution bucket budget=][|matchedBucket|] does not [=map/exists|exist=]: 1. Return [=allowed aggregatable budget per source=]. 1. Return |sourceToAttribute|'s [=attribution source/remaining aggregatable attribution bucket budget=][|matchedBucket|]. diff --git a/ts/src/header-validator/index.html b/ts/src/header-validator/index.html index 849479011..1cde11055 100644 --- a/ts/src/header-validator/index.html +++ b/ts/src/header-validator/index.html @@ -85,6 +85,7 @@

Attribution Reporting Header Validation

(details)

(details) +

(details)

Validation Result diff --git a/ts/src/header-validator/index.ts b/ts/src/header-validator/index.ts index aad868ce6..f971e91e1 100644 --- a/ts/src/header-validator/index.ts +++ b/ts/src/header-validator/index.ts @@ -23,6 +23,9 @@ const sourceTypeFieldset = const effective = document.querySelector('#effective')! const flexCheckbox = form.elements.namedItem('flex') as HTMLInputElement +const aggregatableBucketCheckbox = form.elements.namedItem( + 'aggregatableBucket' +) as HTMLInputElement function sourceType(): SourceType { return parseSourceType(sourceTypeRadios.value) @@ -31,6 +34,7 @@ function sourceType(): SourceType { function validate(): void { sourceTypeFieldset.disabled = true flexCheckbox.disabled = true + aggregatableBucketCheckbox.disabled = true let v: validator.Validator @@ -38,10 +42,12 @@ function validate(): void { case 'source': sourceTypeFieldset.disabled = false flexCheckbox.disabled = false + aggregatableBucketCheckbox.disabled = false v = source.validator({ vsv: vsv.Chromium, sourceType: sourceType(), fullFlex: flexCheckbox.checked, + aggregatableBucket: aggregatableBucketCheckbox.checked, noteInfoGain: true, }) break @@ -50,6 +56,7 @@ function validate(): void { v = trigger.validator({ vsv: vsv.Chromium, fullFlex: flexCheckbox.checked, + aggregatableBucket: aggregatableBucketCheckbox.checked, }) break case 'os-source': @@ -100,6 +107,7 @@ document.querySelector('#linkify')!.addEventListener('click', () => { } url.searchParams.set('flex', flexCheckbox.checked.toString()) + url.searchParams.set('aggregatableBucket', flexCheckbox.checked.toString()) void navigator.clipboard.writeText(url.toString()) }) @@ -137,5 +145,6 @@ if (st !== null && st in SourceType) { sourceTypeRadios.value = st flexCheckbox.checked = params.get('flex') === 'true' +aggregatableBucketCheckbox.checked = params.get('aggregatableBucket') === 'true' validate() diff --git a/ts/src/header-validator/main.ts b/ts/src/header-validator/main.ts index 7ce094432..a0b65f711 100644 --- a/ts/src/header-validator/main.ts +++ b/ts/src/header-validator/main.ts @@ -12,6 +12,7 @@ interface Arguments { file?: string fullFlex: boolean + aggregatableBucket: boolean sourceType?: SourceType silent: boolean @@ -48,6 +49,11 @@ const options = parse( description: 'If true, parse experimental Full Flexible Event fields.', }, + aggregatableBucket: { + type: Boolean, + description: 'If true, parse experimental Aggregatable Bucket fields.', + }, + silent: { type: Boolean, description: 'If true, suppress output.', @@ -95,10 +101,12 @@ const out = validate( ? trigger.validator({ vsv: vsv.Chromium, fullFlex: options.fullFlex, + aggregatableBucket: options.aggregatableBucket, }) : source.validator({ vsv: vsv.Chromium, fullFlex: options.fullFlex, + aggregatableBucket: options.aggregatableBucket, sourceType: options.sourceType, }) ) diff --git a/ts/src/header-validator/source.test.ts b/ts/src/header-validator/source.test.ts index 01aecbaf1..5f8938dd5 100644 --- a/ts/src/header-validator/source.test.ts +++ b/ts/src/header-validator/source.test.ts @@ -51,11 +51,12 @@ const testCases: TestCase[] = [ "max_event_states": 4 }, "aggregatable_bucket_max_budget": { - "firebase": 32768, - "third_party": 32768 + "1": 32768, + "2": 32768 } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expected: Maybe.some({ aggregatableReportWindow: 3601, aggregationKeys: new Map([['a', 15n]]), @@ -100,8 +101,8 @@ const testCases: TestCase[] = [ maxEventStates: 4, }, aggregatableBucketBudget: new Map([ - ['firebase', 32768], - ['third_party', 32768], + ['1', 32768], + ['2', 32768], ]), }), }, @@ -3082,6 +3083,7 @@ const testCases: TestCase[] = [ "aggregatable_bucket_max_budget": ["1"] }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'must be an object', @@ -3098,6 +3100,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'bucket exceeds max length per aggregatable bucket (51 > 50)', @@ -3117,6 +3120,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, }, { name: 'aggregatable-buckets-too-many', @@ -3147,6 +3151,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'exceeds the maximum number of keys (20)', @@ -3165,6 +3170,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'must be in the range [1, 65536]', @@ -3185,6 +3191,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'must be in the range [1, 65536]', @@ -3201,6 +3208,7 @@ const testCases: TestCase[] = [ } }`, sourceType: SourceType.navigation, + parseAggregatableBucket: true, expectedErrors: [ { msg: 'must be a number', @@ -3218,6 +3226,7 @@ testCases.forEach((tc) => sourceType: tc.sourceType ?? SourceType.navigation, fullFlex: tc.parseFullFlex, noteInfoGain: tc.noteInfoGain, + aggregatableBucket: tc.parseAggregatableBucket, }) ) ) diff --git a/ts/src/header-validator/to-json.ts b/ts/src/header-validator/to-json.ts index 3ccbbcd47..343963901 100644 --- a/ts/src/header-validator/to-json.ts +++ b/ts/src/header-validator/to-json.ts @@ -206,6 +206,7 @@ type Source = CommonDebug & export interface Options { fullFlex?: boolean | undefined + aggregatableBucket?: boolean | undefined } export function serializeSource( @@ -247,9 +248,9 @@ export function serializeSource( ...ifNotNull('attribution_scopes', s.attributionScopes, (v) => serializeAttributionScopes(v) ), - aggregatable_bucket_max_budget: Object.fromEntries( - s.aggregatableBucketBudget - ), + aggregatable_bucket_max_budget: opts.aggregatableBucket + ? Object.fromEntries(s.aggregatableBucketBudget) + : {}, } return stringify(source) @@ -411,10 +412,9 @@ export function serializeTrigger( serializeAggregatableDedupKey ), - aggregatable_buckets: Array.from( - t.aggregatableBuckets, - serializeAggregatableBucket - ), + aggregatable_buckets: opts.aggregatableBucket + ? Array.from(t.aggregatableBuckets, serializeAggregatableBucket) + : [], aggregatable_source_registration_time: t.aggregatableSourceRegistrationTime, diff --git a/ts/src/header-validator/trigger.test.ts b/ts/src/header-validator/trigger.test.ts index bc8904bf7..e66f25c6f 100644 --- a/ts/src/header-validator/trigger.test.ts +++ b/ts/src/header-validator/trigger.test.ts @@ -54,6 +54,7 @@ const testCases: jsontest.TestCase[] = [ }, "attribution_scopes": ["1"] }`, + parseAggregatableBucket: true, expected: Maybe.some({ aggregatableDedupKeys: [ { @@ -1743,6 +1744,7 @@ const testCases: jsontest.TestCase[] = [ { name: 'aggregatable-buckets-wrong-type', input: `{"aggregatable_buckets": 1}`, + parseAggregatableBucket: true, expectedErrors: [ { path: ['aggregatable_buckets'], @@ -1753,6 +1755,7 @@ const testCases: jsontest.TestCase[] = [ { name: 'aggregatable-buckets-value-wrong-type', input: `{"aggregatable_buckets": [1]}`, + parseAggregatableBucket: true, expectedErrors: [ { path: ['aggregatable_buckets', 0], @@ -1765,6 +1768,7 @@ const testCases: jsontest.TestCase[] = [ input: `{"aggregatable_buckets": [{ "bucket": 1 }]}`, + parseAggregatableBucket: true, expectedErrors: [ { path: ['aggregatable_buckets', 0, 'bucket'], @@ -1778,6 +1782,7 @@ const testCases: jsontest.TestCase[] = [ "filters": [], "not_filters": [] }]}`, + parseAggregatableBucket: true, expectedErrors: [ { path: ['aggregatable_buckets', 0, 'bucket'], @@ -1792,6 +1797,7 @@ const testCases: jsontest.TestCase[] = [ "filters": [], "not_filters": [] }]}`, + parseAggregatableBucket: true, }, ] @@ -1801,6 +1807,7 @@ testCases.forEach((tc) => trigger.validator({ vsv: { ...vsv.Chromium, ...tc.vsv }, fullFlex: tc.parseFullFlex, + aggregatableBucket: tc.parseAggregatableBucket, }) ) ) diff --git a/ts/src/header-validator/validate-json.test.ts b/ts/src/header-validator/validate-json.test.ts index f3cecf87d..d56d2a6c6 100644 --- a/ts/src/header-validator/validate-json.test.ts +++ b/ts/src/header-validator/validate-json.test.ts @@ -4,4 +4,5 @@ import * as vsv from '../vendor-specific-values' export type TestCase = testutil.TestCase & { vsv?: Readonly> parseFullFlex?: boolean + parseAggregatableBucket?: boolean } diff --git a/ts/src/header-validator/validate-json.ts b/ts/src/header-validator/validate-json.ts index 6d26d934c..ec697b6fa 100644 --- a/ts/src/header-validator/validate-json.ts +++ b/ts/src/header-validator/validate-json.ts @@ -38,6 +38,7 @@ export const UINT32_MAX: number = 2 ** 32 - 1 export interface RegistrationOptions { vsv: VendorSpecificValues fullFlex?: boolean | undefined + aggregatableBucket?: boolean | undefined } export class RegistrationContext< diff --git a/ts/src/header-validator/validate-source.ts b/ts/src/header-validator/validate-source.ts index 2d7de7b2b..3470fba25 100644 --- a/ts/src/header-validator/validate-source.ts +++ b/ts/src/header-validator/validate-source.ts @@ -752,10 +752,12 @@ function source(j: Json, ctx: Context): Maybe { 'attribution_scopes', withDefault(attributionScopes, null) ), - aggregatableBucketBudget: field( - 'aggregatable_bucket_max_budget', - withDefault(aggregatableBucketBudget, new Map()) - ), + aggregatableBucketBudget: ctx.opts.aggregatableBucket + ? field( + 'aggregatable_bucket_max_budget', + withDefault(aggregatableBucketBudget, new Map()) + ) + : () => Maybe.some(new Map()), ...commonDebugFields, ...priorityField, diff --git a/ts/src/header-validator/validate-trigger.ts b/ts/src/header-validator/validate-trigger.ts index cd5487d63..1b3d6cb9b 100644 --- a/ts/src/header-validator/validate-trigger.ts +++ b/ts/src/header-validator/validate-trigger.ts @@ -402,10 +402,9 @@ function trigger(j: Json, ctx: Context): Maybe { 'aggregatable_deduplication_keys', withDefault(aggregatableDedupKeys, []) ), - aggregatableBuckets: field( - 'aggregatable_buckets', - withDefault(aggregatableBuckets, []) - ), + aggregatableBuckets: ctx.opts.aggregatableBucket + ? field('aggregatable_buckets', withDefault(aggregatableBuckets, [])) + : () => Maybe.some([]), aggregatableSourceRegistrationTime: () => aggregatableSourceRegTimeVal, eventTriggerData: field( 'event_trigger_data',