Skip to content

Commit 483032d

Browse files
jhoylacjpatton
authored andcommitted
Add support for public extensions in Reports.
1 parent b01c4e4 commit 483032d

File tree

16 files changed

+880
-56
lines changed

16 files changed

+880
-56
lines changed

crates/dapf/src/acceptance/load_testing.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ pub async fn execute_single_combination_from_env(
448448
&measurment,
449449
VERSION,
450450
system_now.0,
451+
Some(vec![]),
451452
vec![messages::Extension::Taskprov],
452453
t.replay_reports,
453454
)

crates/dapf/src/acceptance/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,10 @@ impl Test {
655655
measurement.as_ref(),
656656
version,
657657
now.0,
658+
match version {
659+
DapVersion::Draft09 => None,
660+
DapVersion::Latest => Some(vec![]),
661+
},
658662
vec![messages::Extension::Taskprov],
659663
self.replay_reports,
660664
)

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

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ async fn leader_upload(version: DapVersion) {
304304
report_metadata: ReportMetadata {
305305
id: ReportId([1; 16]),
306306
time: t.now,
307+
public_extensions: match version {
308+
DapVersion::Draft09 => None,
309+
DapVersion::Latest => Some(Vec::new()),
310+
},
307311
},
308312
public_share: b"public share".to_vec(),
309313
encrypted_input_shares: [
@@ -424,6 +428,10 @@ async fn leader_upload_taskprov() {
424428
t.now,
425429
&task_id,
426430
DapMeasurement::U32Vec(vec![1; 10]),
431+
match version {
432+
DapVersion::Draft09 => None,
433+
DapVersion::Latest => Some(vec![]),
434+
},
427435
vec![Extension::Taskprov],
428436
version,
429437
)
@@ -451,6 +459,10 @@ async fn leader_upload_taskprov() {
451459
t.now,
452460
&task_id,
453461
DapMeasurement::U32Vec(vec![1; 10]),
462+
match version {
463+
DapVersion::Draft09 => None,
464+
DapVersion::Latest => Some(vec![]),
465+
},
454466
vec![Extension::Taskprov],
455467
version,
456468
)
@@ -516,6 +528,10 @@ async fn leader_upload_taskprov_wrong_version(version: DapVersion) {
516528
t.now,
517529
&task_id,
518530
DapMeasurement::U32Vec(vec![1; 10]),
531+
match version {
532+
DapVersion::Draft09 => None,
533+
DapVersion::Latest => Some(vec![]),
534+
},
519535
vec![Extension::Taskprov],
520536
version,
521537
)
@@ -541,6 +557,100 @@ async fn leader_upload_taskprov_wrong_version(version: DapVersion) {
541557

542558
async_test_versions!(leader_upload_taskprov_wrong_version);
543559

560+
#[tokio::test]
561+
async fn leader_upload_taksprov_public_errors() {
562+
let version = DapVersion::Latest;
563+
let t = TestRunner::default_with_version(version).await;
564+
let client = t.http_client();
565+
let hpke_config_list = t.get_hpke_configs(version, client).await.unwrap();
566+
567+
let (task_config, task_id, taskprov_advertisement) = DapTaskParameters {
568+
version,
569+
min_batch_size: 10,
570+
query: DapBatchMode::TimeInterval,
571+
leader_url: t.task_config.leader_url.clone(),
572+
helper_url: t.task_config.helper_url.clone(),
573+
..Default::default()
574+
}
575+
.to_config_with_taskprov(
576+
b"cool task".to_vec(),
577+
t.now,
578+
daphne::roles::aggregator::TaskprovConfig {
579+
hpke_collector_config: &t.taskprov_collector_hpke_receiver.config,
580+
vdaf_verify_key_init: &t.taskprov_vdaf_verify_key_init,
581+
},
582+
)
583+
.unwrap();
584+
585+
// Repeated public extension
586+
let report = task_config
587+
.vdaf
588+
.produce_report_with_extensions(
589+
&hpke_config_list,
590+
t.now + 1,
591+
&task_id,
592+
DapMeasurement::U32Vec(vec![1; 10]),
593+
Some(vec![Extension::Taskprov, Extension::Taskprov]),
594+
vec![],
595+
version,
596+
)
597+
.unwrap();
598+
t.leader_request_expect_abort(
599+
client,
600+
None,
601+
&format!("tasks/{}/reports", task_id.to_base64url()),
602+
&http::Method::POST,
603+
DapMediaType::Report,
604+
Some(
605+
&taskprov_advertisement
606+
.serialize_to_header_value(version)
607+
.unwrap(),
608+
),
609+
report.get_encoded_with_param(&version).unwrap(),
610+
400,
611+
"invalidMessage",
612+
)
613+
.await
614+
.unwrap();
615+
616+
// Unsupported public extension
617+
let report = task_config
618+
.vdaf
619+
.produce_report_with_extensions(
620+
&hpke_config_list,
621+
t.now + 1,
622+
&task_id,
623+
DapMeasurement::U32Vec(vec![1; 10]),
624+
Some(vec![
625+
Extension::Taskprov,
626+
Extension::NotImplemented {
627+
typ: 3,
628+
payload: b"ignore".to_vec(),
629+
},
630+
]),
631+
vec![],
632+
version,
633+
)
634+
.unwrap();
635+
t.leader_request_expect_abort(
636+
client,
637+
None,
638+
&format!("tasks/{}/reports", task_id.to_base64url()),
639+
&http::Method::POST,
640+
DapMediaType::Report,
641+
Some(
642+
&taskprov_advertisement
643+
.serialize_to_header_value(version)
644+
.unwrap(),
645+
),
646+
report.get_encoded_with_param(&version).unwrap(),
647+
400,
648+
"unsupportedExtension",
649+
)
650+
.await
651+
.unwrap();
652+
}
653+
544654
async fn internal_leader_process(version: DapVersion) {
545655
let t = TestRunner::default_with_version(version).await;
546656
let path = t.upload_path();
@@ -1408,6 +1518,10 @@ async fn leader_collect_taskprov_ok(version: DapVersion) {
14081518
now,
14091519
&task_id,
14101520
DapMeasurement::U32Vec(vec![1; 10]),
1521+
match version {
1522+
DapVersion::Draft09 => None,
1523+
DapVersion::Latest => Some(vec![]),
1524+
},
14111525
extensions,
14121526
version,
14131527
)

crates/daphne-service-utils/src/compute_offload/compute_offload.capnp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,24 @@ struct PartialDapTaskConfig @0xdcc9bf18fc62d406 {
5353
vdafVerifyKey @4 :VdafVerifyKey;
5454
}
5555

56+
struct PublicExtensionsList @0x8b3c98c0ddd0043e {
57+
58+
union {
59+
# Each extension is encoded according to the DAP spec in
60+
# tag-length-value form.
61+
list @0 :List(Data);
62+
63+
# draft09 compatibility: Previously DAP had no extensions in the
64+
# report.
65+
none @1 :Void;
66+
}
67+
}
68+
5669
struct ReportMetadata @0xefba178ad4584bc4 {
5770

58-
id @0 :Base.ReportId;
59-
time @1 :Base.Time;
71+
id @0 :Base.ReportId;
72+
time @1 :Base.Time;
73+
publicExtensions @2 :PublicExtensionsList;
6074
}
6175

6276
struct PrepareInit @0x8192568cb3d03f59 {

crates/daphne-service-utils/src/compute_offload/mod.rs

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@ use crate::{
1010
hpke_receiver_config::{self, hpke_config},
1111
initialize_reports,
1212
initialized_reports::{self, initialized_report},
13-
partial_dap_task_config, prepare_init, report_metadata, time_range,
13+
partial_dap_task_config, prepare_init, public_extensions_list, report_metadata, time_range,
1414
},
1515
};
1616
use daphne::{
1717
constants::DapAggregatorRole,
1818
hpke::{HpkeConfig, HpkeReceiverConfig},
19-
messages::{self, HpkeCiphertext, PrepareInit, ReportMetadata, ReportShare, TaskId},
19+
messages::{self, Extension, HpkeCiphertext, PrepareInit, ReportMetadata, ReportShare, TaskId},
2020
vdaf::{VdafConfig, VdafPrepShare, VdafPrepState},
2121
InitializedReport, PartialDapTaskConfigForReportInit, WithPeerPrepShare,
2222
};
23-
use prio::codec::{Encode, ParameterizedDecode, ParameterizedEncode};
23+
use prio::codec::{Decode, Encode, ParameterizedDecode, ParameterizedEncode};
2424
use std::{borrow::Cow, ops::Range};
2525

2626
pub struct InitializeReports<'s> {
@@ -318,9 +318,27 @@ impl CapnprotoPayloadEncode for ReportMetadata {
318318
type Builder<'a> = report_metadata::Builder<'a>;
319319

320320
fn encode_to_builder(&self, mut builder: Self::Builder<'_>) {
321-
let Self { id, time } = self;
321+
let Self {
322+
id,
323+
time,
324+
public_extensions,
325+
} = self;
322326
id.encode_to_builder(builder.reborrow().init_id());
323327
builder.set_time(*time);
328+
if let Some(ref extensions) = public_extensions {
329+
let mut e = builder
330+
.init_public_extensions()
331+
.init_list(usize_to_capnp_len(extensions.len()));
332+
for (i, data) in extensions
333+
.iter()
334+
.enumerate()
335+
.map(|(i, ext)| (usize_to_capnp_len(i), ext.get_encoded().unwrap()))
336+
{
337+
e.reborrow().set(i, &data);
338+
}
339+
} else {
340+
builder.init_public_extensions().set_none(());
341+
}
324342
}
325343
}
326344

@@ -331,9 +349,25 @@ impl CapnprotoPayloadDecode for ReportMetadata {
331349
where
332350
Self: Sized,
333351
{
352+
let id = <_>::decode_from_reader(reader.get_id()?)?;
353+
let time = reader.get_time();
354+
let public_extensions = match reader.get_public_extensions()?.which()? {
355+
public_extensions_list::List(list) => Some(
356+
list?
357+
.into_iter()
358+
.map(|data| {
359+
Extension::get_decoded(data?)
360+
.map_err(|e| capnp::Error::failed(e.to_string()))
361+
})
362+
.collect::<Result<Vec<_>, capnp::Error>>()?,
363+
),
364+
public_extensions_list::None(()) => None,
365+
};
366+
334367
Ok(Self {
335-
id: <_>::decode_from_reader(reader.get_id()?)?,
336-
time: reader.get_time(),
368+
id,
369+
time,
370+
public_extensions,
337371
})
338372
}
339373
}
@@ -486,3 +520,45 @@ fn to_capnp<E: ToString>(e: E) -> capnp::Error {
486520
extra: e.to_string(),
487521
}
488522
}
523+
524+
#[cfg(test)]
525+
mod test {
526+
use super::*;
527+
use crate::capnproto::{CapnprotoPayloadDecodeExt, CapnprotoPayloadEncodeExt};
528+
529+
#[test]
530+
fn report_metadata_roundtrip() {
531+
let report_metadata = ReportMetadata {
532+
id: messages::ReportId(rand::random()),
533+
time: rand::random(),
534+
public_extensions: Some(vec![
535+
Extension::Taskprov,
536+
Extension::NotImplemented {
537+
typ: 23,
538+
payload: b"some extension payload".to_vec(),
539+
},
540+
]),
541+
};
542+
543+
assert_eq!(
544+
report_metadata,
545+
ReportMetadata::decode_from_bytes(&report_metadata.encode_to_bytes()).unwrap()
546+
);
547+
}
548+
549+
#[test]
550+
fn report_metadata_roundtrip_draft09() {
551+
let report_metadata = ReportMetadata {
552+
id: messages::ReportId(rand::random()),
553+
time: rand::random(),
554+
// draft09 compatibility: Previously there was no extensions field in the report
555+
// metadata.
556+
public_extensions: None,
557+
};
558+
559+
assert_eq!(
560+
report_metadata,
561+
ReportMetadata::decode_from_bytes(&report_metadata.encode_to_bytes()).unwrap()
562+
);
563+
}
564+
}

crates/daphne/src/error/aborts.rs

Lines changed: 19 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,16 @@ impl DapAbort {
259264
})
260265
}
261266

267+
pub fn unsupported_extension(
268+
task_id: &TaskId,
269+
unknown_extensions: &[u16],
270+
) -> Result<Self, DapError> {
271+
Ok(Self::UnsupportedExtension {
272+
detail: format!("{unknown_extensions:?}"),
273+
task_id: *task_id,
274+
})
275+
}
276+
262277
fn title_and_type(&self) -> (&'static str, Option<String>) {
263278
let (title, dap_abort_type) = match self {
264279
Self::BatchInvalid { .. } => ("Batch boundary check failed", Some(self.to_string())),
@@ -300,6 +315,9 @@ impl DapAbort {
300315
Some(self.to_string()),
301316
),
302317
Self::BadRequest(..) => ("Bad request", None),
318+
Self::UnsupportedExtension { .. } => {
319+
("Unsupported extensions in report", Some(self.to_string()))
320+
}
303321
};
304322

305323
(

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)