Skip to content

Commit 5078b99

Browse files
committed
Add support for public extensions in Reports.
1 parent f839d01 commit 5078b99

File tree

9 files changed

+546
-19
lines changed

9 files changed

+546
-19
lines changed

crates/daphne-server/tests/e2e/e2e.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ async fn leader_upload(version: DapVersion) {
301301
report_metadata: ReportMetadata {
302302
id: ReportId([1; 16]),
303303
time: t.now,
304+
public_extensions: match version {
305+
DapVersion::Draft09 => None,
306+
DapVersion::Latest => Some(Vec::new()),
307+
},
304308
},
305309
public_share: b"public share".to_vec(),
306310
encrypted_input_shares: [
@@ -533,6 +537,150 @@ async fn leader_upload_taskprov_wrong_version(version: DapVersion) {
533537

534538
async_test_versions!(leader_upload_taskprov_wrong_version);
535539

540+
#[tokio::test]
541+
async fn leader_upload_taskprov_public() {
542+
let version = DapVersion::Latest;
543+
let t = TestRunner::default_with_version(version).await;
544+
let client = t.http_client();
545+
let hpke_config_list = t.get_hpke_configs(version, client).await.unwrap();
546+
547+
let (task_config, task_id, taskprov_advertisement) = DapTaskParameters {
548+
version,
549+
min_batch_size: 10,
550+
query: DapBatchMode::TimeInterval,
551+
leader_url: t.task_config.leader_url.clone(),
552+
helper_url: t.task_config.helper_url.clone(),
553+
..Default::default()
554+
}
555+
.to_config_with_taskprov(
556+
b"cool task".to_vec(),
557+
t.now,
558+
daphne::roles::aggregator::TaskprovConfig {
559+
hpke_collector_config: &t.taskprov_collector_hpke_receiver.config,
560+
vdaf_verify_key_init: &t.taskprov_vdaf_verify_key_init,
561+
},
562+
)
563+
.unwrap();
564+
565+
let mut report = task_config
566+
.vdaf
567+
.produce_report(
568+
&hpke_config_list,
569+
t.now + 1,
570+
&task_id,
571+
DapMeasurement::U32Vec(vec![1; 10]),
572+
version,
573+
)
574+
.unwrap();
575+
report.report_metadata.public_extensions = Some(vec![Extension::Taskprov]);
576+
t.leader_request_expect_ok(
577+
client,
578+
&format!("tasks/{}/reports", task_id.to_base64url()),
579+
&http::Method::POST,
580+
DapMediaType::Report,
581+
Some(
582+
&taskprov_advertisement
583+
.serialize_to_header_value(version)
584+
.unwrap(),
585+
),
586+
report.get_encoded_with_param(&version).unwrap(),
587+
)
588+
.await
589+
.unwrap();
590+
}
591+
592+
#[tokio::test]
593+
async fn leader_upload_taksprov_public_errors() {
594+
let version = DapVersion::Latest;
595+
let t = TestRunner::default_with_version(version).await;
596+
let client = t.http_client();
597+
let hpke_config_list = t.get_hpke_configs(version, client).await.unwrap();
598+
599+
let (task_config, task_id, taskprov_advertisement) = DapTaskParameters {
600+
version,
601+
min_batch_size: 10,
602+
query: DapBatchMode::TimeInterval,
603+
leader_url: t.task_config.leader_url.clone(),
604+
helper_url: t.task_config.helper_url.clone(),
605+
..Default::default()
606+
}
607+
.to_config_with_taskprov(
608+
b"cool task".to_vec(),
609+
t.now,
610+
daphne::roles::aggregator::TaskprovConfig {
611+
hpke_collector_config: &t.taskprov_collector_hpke_receiver.config,
612+
vdaf_verify_key_init: &t.taskprov_vdaf_verify_key_init,
613+
},
614+
)
615+
.unwrap();
616+
617+
// Repeated public extension
618+
let mut report = task_config
619+
.vdaf
620+
.produce_report(
621+
&hpke_config_list,
622+
t.now + 1,
623+
&task_id,
624+
DapMeasurement::U32Vec(vec![1; 10]),
625+
version,
626+
)
627+
.unwrap();
628+
report.report_metadata.public_extensions = Some(vec![Extension::Taskprov, Extension::Taskprov]);
629+
t.leader_request_expect_abort(
630+
client,
631+
None,
632+
&format!("tasks/{}/reports", task_id.to_base64url()),
633+
&http::Method::POST,
634+
DapMediaType::Report,
635+
Some(
636+
&taskprov_advertisement
637+
.serialize_to_header_value(version)
638+
.unwrap(),
639+
),
640+
report.get_encoded_with_param(&version).unwrap(),
641+
400,
642+
"invalidMessage",
643+
)
644+
.await
645+
.unwrap();
646+
647+
// Unsupported public extension
648+
let mut report = task_config
649+
.vdaf
650+
.produce_report(
651+
&hpke_config_list,
652+
t.now + 1,
653+
&task_id,
654+
DapMeasurement::U32Vec(vec![1; 10]),
655+
version,
656+
)
657+
.unwrap();
658+
report.report_metadata.public_extensions = Some(vec![
659+
Extension::Taskprov,
660+
Extension::NotImplemented {
661+
typ: 3,
662+
payload: b"ignore".to_vec(),
663+
},
664+
]);
665+
t.leader_request_expect_abort(
666+
client,
667+
None,
668+
&format!("tasks/{}/reports", task_id.to_base64url()),
669+
&http::Method::POST,
670+
DapMediaType::Report,
671+
Some(
672+
&taskprov_advertisement
673+
.serialize_to_header_value(version)
674+
.unwrap(),
675+
),
676+
report.get_encoded_with_param(&version).unwrap(),
677+
400,
678+
"unsupportedExtension",
679+
)
680+
.await
681+
.unwrap();
682+
}
683+
536684
async fn internal_leader_process(version: DapVersion) {
537685
let t = TestRunner::default_with_version(version).await;
538686
let path = t.upload_path();
@@ -1348,6 +1496,116 @@ async fn leader_selected() {
13481496
.unwrap();
13491497
}
13501498

1499+
#[tokio::test]
1500+
async fn leader_collect_taskprov_repeated_abort() {
1501+
let version = DapVersion::Latest;
1502+
const DAP_TASKPROV_COLLECTOR_TOKEN: &str = "I-am-the-collector";
1503+
let t = TestRunner::default_with_version(version).await;
1504+
let batch_interval = t.batch_interval();
1505+
1506+
let client = t.http_client();
1507+
let hpke_config_list = t.get_hpke_configs(version, client).await.unwrap();
1508+
1509+
let (task_config, task_id, taskprov_advertisement) = DapTaskParameters {
1510+
version,
1511+
min_batch_size: 10,
1512+
query: DapBatchMode::TimeInterval,
1513+
leader_url: t.task_config.leader_url.clone(),
1514+
helper_url: t.task_config.helper_url.clone(),
1515+
..Default::default()
1516+
}
1517+
.to_config_with_taskprov(
1518+
b"cool task".to_vec(),
1519+
t.now,
1520+
daphne::roles::aggregator::TaskprovConfig {
1521+
hpke_collector_config: &t.taskprov_collector_hpke_receiver.config,
1522+
vdaf_verify_key_init: &t.taskprov_vdaf_verify_key_init,
1523+
},
1524+
)
1525+
.unwrap();
1526+
1527+
let path = TestRunner::upload_path_for_task(&task_id);
1528+
let method = &Method::POST;
1529+
// The reports are uploaded in the background.
1530+
let mut rng = thread_rng();
1531+
for _ in 0..t.task_config.min_batch_size {
1532+
let extensions = vec![Extension::Taskprov];
1533+
let now = rng.gen_range(TestRunner::report_interval(&batch_interval));
1534+
t.leader_request_expect_ok(
1535+
client,
1536+
&path,
1537+
method,
1538+
DapMediaType::Report,
1539+
Some(
1540+
&taskprov_advertisement
1541+
.serialize_to_header_value(version)
1542+
.unwrap(),
1543+
),
1544+
{
1545+
let mut report = task_config
1546+
.vdaf
1547+
.produce_report_with_extensions(
1548+
&hpke_config_list,
1549+
now,
1550+
&task_id,
1551+
DapMeasurement::U32Vec(vec![1; 10]),
1552+
extensions,
1553+
version,
1554+
)
1555+
.unwrap();
1556+
report.report_metadata.public_extensions = Some(vec![Extension::Taskprov]);
1557+
report.get_encoded_with_param(&version).unwrap()
1558+
},
1559+
)
1560+
.await
1561+
.unwrap();
1562+
}
1563+
1564+
let agg_param = DapAggregationParam::Empty;
1565+
1566+
// Get the collect URI.
1567+
let collect_req = CollectionReq {
1568+
query: Query::TimeInterval { batch_interval },
1569+
agg_param: agg_param.get_encoded().unwrap(),
1570+
};
1571+
let collect_uri = t
1572+
.leader_post_collect_using_token(
1573+
client,
1574+
DAP_TASKPROV_COLLECTOR_TOKEN,
1575+
Some(&taskprov_advertisement),
1576+
Some(&task_id),
1577+
collect_req.get_encoded_with_param(&t.version).unwrap(),
1578+
)
1579+
.await
1580+
.unwrap();
1581+
println!("collect_uri: {collect_uri}");
1582+
1583+
// Poll the collect URI before the CollectResp is ready.
1584+
let resp = t
1585+
.poll_collection_url_using_token(client, &collect_uri, DAP_TASKPROV_COLLECTOR_TOKEN)
1586+
.await
1587+
.unwrap();
1588+
#[expect(clippy::format_in_format_args)]
1589+
{
1590+
assert_eq!(
1591+
resp.status(),
1592+
202,
1593+
"response: {} {}",
1594+
format!("{resp:?}"),
1595+
resp.text().await.unwrap()
1596+
);
1597+
}
1598+
1599+
// The reports are aggregated in the background.
1600+
let agg_telem = t.internal_process(client).await.unwrap();
1601+
assert_eq!(
1602+
agg_telem.reports_processed, task_config.min_batch_size,
1603+
"reports processed"
1604+
);
1605+
assert_eq!(agg_telem.reports_aggregated, 0, "reports aggregated");
1606+
assert_eq!(agg_telem.reports_collected, 0, "reports collected");
1607+
}
1608+
13511609
async fn leader_collect_taskprov_ok(version: DapVersion) {
13521610
const DAP_TASKPROV_COLLECTOR_TOKEN: &str = "I-am-the-collector";
13531611
let t = TestRunner::default_with_version(version).await;

crates/daphne/src/error/aborts.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ pub enum DapAbort {
9595
/// Unrecognized DAP task. Sent in response to a request indicating an unrecognized task ID.
9696
#[error("unrecognizedTask")]
9797
UnrecognizedTask { task_id: TaskId },
98+
99+
/// Unsupported Extension. Sent in response to a report upload with an unsupported extension.
100+
#[error("unsupportedExtension")]
101+
UnsupportedExtension { detail: String, task_id: TaskId },
98102
}
99103

100104
impl DapAbort {
@@ -116,7 +120,8 @@ impl DapAbort {
116120
| Self::InvalidBatchSize { detail, task_id }
117121
| Self::BatchModeMismatch { detail, task_id }
118122
| Self::UnauthorizedRequest { detail, task_id }
119-
| Self::InvalidMessage { detail, task_id } => (
123+
| Self::InvalidMessage { detail, task_id }
124+
| Self::UnsupportedExtension { detail, task_id } => (
120125
Some(task_id),
121126
Some(detail),
122127
None,
@@ -259,6 +264,20 @@ impl DapAbort {
259264
})
260265
}
261266

267+
pub fn unsupported_extension(
268+
task_id: &TaskId,
269+
unknown_extensions: &[u16],
270+
) -> Result<Self, DapError> {
271+
let detail = serde_json::to_string(&unknown_extensions);
272+
match detail {
273+
Ok(s) => Ok(Self::UnsupportedExtension {
274+
detail: s,
275+
task_id: *task_id,
276+
}),
277+
Err(x) => Err(fatal_error!(err = %x,)),
278+
}
279+
}
280+
262281
fn title_and_type(&self) -> (&'static str, Option<String>) {
263282
let (title, dap_abort_type) = match self {
264283
Self::BatchInvalid { .. } => ("Batch boundary check failed", Some(self.to_string())),
@@ -300,6 +319,9 @@ impl DapAbort {
300319
Some(self.to_string()),
301320
),
302321
Self::BadRequest(..) => ("Bad request", None),
322+
Self::UnsupportedExtension { .. } => {
323+
("Unsupported extensions in report", Some(self.to_string()))
324+
}
303325
};
304326

305327
(

crates/daphne/src/hpke.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,10 @@ mod test {
612612
report_metadata: &ReportMetadata {
613613
id: ReportId(rand::random()),
614614
time: rand::random(),
615+
public_extensions: match version {
616+
DapVersion::Draft09 => None,
617+
DapVersion::Latest => Some(Vec::new()),
618+
},
615619
},
616620
};
617621
let plaintext = b"plaintext";
@@ -703,6 +707,10 @@ mod test {
703707
let report_metadata = &ReportMetadata {
704708
id: ReportId(rand::random()),
705709
time: rand::random(),
710+
public_extensions: match version {
711+
DapVersion::Draft09 => None,
712+
DapVersion::Latest => Some(Vec::new()),
713+
},
706714
};
707715
let public_share = &vec![rand::random(); (0..100).choose(&mut rand::thread_rng()).unwrap()];
708716

0 commit comments

Comments
 (0)