diff --git a/Cargo.lock b/Cargo.lock index df6209181..129d8ec62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2957,6 +2957,7 @@ dependencies = [ "base64 0.22.1", "educe", "hex", + "janus_messages 0.8.0-prerelease-1", "num_enum", "pretty_assertions", "prio 0.17.0-alpha.0", diff --git a/aggregator/Cargo.toml b/aggregator/Cargo.toml index 4885cc901..347da7240 100644 --- a/aggregator/Cargo.toml +++ b/aggregator/Cargo.toml @@ -110,7 +110,7 @@ url.workspace = true uuid = { workspace = true, features = ["serde"] } [dev-dependencies] -janus_aggregator = { path = ".", features = ["fpvec_bounded_l2", "test-util"] } +janus_aggregator = { workspace = true, features = ["fpvec_bounded_l2", "test-util"] } janus_aggregator_core = { workspace = true, features = ["test-util"] } mockito = { workspace = true } opentelemetry_sdk = { workspace = true, features = ["testing"] } diff --git a/aggregator/src/aggregator.rs b/aggregator/src/aggregator.rs index 8f269ee03..00023e46e 100644 --- a/aggregator/src/aggregator.rs +++ b/aggregator/src/aggregator.rs @@ -47,7 +47,7 @@ use janus_aggregator_core::{ }, Datastore, Error as DatastoreError, Transaction, }, - task::{self, AggregatorTask, VerifyKey}, + task::{self, AggregatorTask, BatchMode, VerifyKey}, taskprov::PeerAggregator, }; #[cfg(feature = "fpvec_bounded_l2")] @@ -65,7 +65,7 @@ use janus_core::{ }; use janus_messages::{ batch_mode::{LeaderSelected, TimeInterval}, - taskprov::{DpMechanism, TaskConfig}, + taskprov::TaskConfig, AggregateShare, AggregateShareAad, AggregateShareReq, AggregationJobContinueReq, AggregationJobId, AggregationJobInitializeReq, AggregationJobResp, AggregationJobStep, BatchSelector, Collection, CollectionJobId, CollectionReq, Duration, ExtensionType, HpkeConfig, @@ -691,44 +691,26 @@ impl Aggregator { // TODO(#1647): Check whether task config parameters are acceptable for privacy and // availability of the system. - if let DpMechanism::Unrecognized { .. } = - task_config.vdaf_config().dp_config().dp_mechanism() - { - if !self - .cfg - .taskprov_config - .ignore_unknown_differential_privacy_mechanism - { - return Err(Error::InvalidTask( - *task_id, - OptOutReason::InvalidParameter("unrecognized DP mechanism".into()), - )); - } - } - - let vdaf_instance = - task_config - .vdaf_config() - .vdaf_type() - .try_into() - .map_err(|err: &str| { - Error::InvalidTask(*task_id, OptOutReason::InvalidParameter(err.to_string())) - })?; + let vdaf_instance = task_config.vdaf_config().try_into().map_err(|err: &str| { + Error::InvalidTask(*task_id, OptOutReason::InvalidParameter(err.to_string())) + })?; let vdaf_verify_key = peer_aggregator.derive_vdaf_verify_key(task_id, &vdaf_instance); + let task_end = task_config.task_start().add(task_config.task_duration())?; + let task = Arc::new( AggregatorTask::new( *task_id, leader_url, - task_config.query_config().query().try_into()?, + BatchMode::try_from(*task_config.batch_mode())?, vdaf_instance, vdaf_verify_key, - None, // TODO(#3636): update taskprov implementation to specify task start - Some(*task_config.task_end()), + Some(*task_config.task_start()), + Some(task_end), peer_aggregator.report_expiry_age().cloned(), - task_config.query_config().min_batch_size() as u64, - *task_config.query_config().time_precision(), + u64::from(*task_config.min_batch_size()), + *task_config.time_precision(), *peer_aggregator.tolerable_clock_skew(), task::AggregatorTaskParameters::TaskprovHelper, ) @@ -795,7 +777,8 @@ impl Aggregator { return Err(Error::UnauthorizedRequest(*task_id)); } - if self.clock.now() > *task_config.task_end() { + let task_end = task_config.task_start().add(task_config.task_duration())?; + if self.clock.now() > task_end { return Err(Error::InvalidTask(*task_id, OptOutReason::TaskEnded)); } @@ -2098,7 +2081,7 @@ impl VdafOps { if require_taskprov_extension { let valid_taskprov_extension_present = extensions - .get(&ExtensionType::Taskprov) + .get(&ExtensionType::Taskbind) .map(|data| data.is_empty()) .unwrap_or(false); if !valid_taskprov_extension_present { @@ -2117,7 +2100,7 @@ impl VdafOps { ); return Err(ReportError::InvalidMessage); } - } else if extensions.contains_key(&ExtensionType::Taskprov) { + } else if extensions.contains_key(&ExtensionType::Taskbind) { // taskprov not enabled, but the taskprov extension is present. debug!( task_id = %task.id(), diff --git a/aggregator/src/aggregator/aggregate_init_tests.rs b/aggregator/src/aggregator/aggregate_init_tests.rs index 8d2fcacf6..0f66ec5a0 100644 --- a/aggregator/src/aggregator/aggregate_init_tests.rs +++ b/aggregator/src/aggregator/aggregate_init_tests.rs @@ -401,7 +401,7 @@ async fn aggregation_job_init_unexpected_taskprov_extension() { .prepare_init_generator .clone() .with_private_extensions(Vec::from([Extension::new( - ExtensionType::Taskprov, + ExtensionType::Taskbind, Vec::new(), )])) .next(&0) diff --git a/aggregator/src/aggregator/http_handlers.rs b/aggregator/src/aggregator/http_handlers.rs index 2b750ff33..4aeb1efad 100644 --- a/aggregator/src/aggregator/http_handlers.rs +++ b/aggregator/src/aggregator/http_handlers.rs @@ -6,7 +6,7 @@ use super::{ use crate::aggregator::problem_details::{ProblemDetailsConnExt, ProblemDocument}; use async_trait::async_trait; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; -use janus_aggregator_core::{datastore::Datastore, instrumented}; +use janus_aggregator_core::{datastore::Datastore, instrumented, taskprov::taskprov_task_id}; use janus_core::{ auth_tokens::{AuthenticationToken, DAP_AUTH_HEADER}, http::extract_bearer_token, @@ -25,10 +25,9 @@ use opentelemetry::{ KeyValue, }; use prio::codec::Encode; -use ring::digest::{digest, SHA256}; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use std::{borrow::Cow, time::Duration as StdDuration}; -use std::{io::Cursor, sync::Arc}; use tracing::warn; use trillium::{Conn, Handler, KnownHeaderName, Status}; use trillium_api::{api, State, TryFromConn}; @@ -757,38 +756,37 @@ fn parse_taskprov_header( task_id: &TaskId, conn: &Conn, ) -> Result, Error> { - if aggregator.cfg.taskprov_config.enabled { - match conn.request_headers().get(TASKPROV_HEADER) { - Some(taskprov_header) => { - let task_config_encoded = - &URL_SAFE_NO_PAD.decode(taskprov_header).map_err(|_| { - Error::InvalidMessage( - Some(*task_id), - "taskprov header could not be decoded", - ) - })?; - - if task_id.as_ref() != digest(&SHA256, task_config_encoded).as_ref() { - Err(Error::InvalidMessage( - Some(*task_id), - "derived taskprov task ID does not match task config", - )) - } else { - // TODO(#1684): Parsing the taskprov header like this before we've been able - // to actually authenticate the client is undesireable. We should rework this - // such that the authorization header is handled before parsing the untrusted - // input. - Ok(Some( - TaskConfig::decode(&mut Cursor::new(task_config_encoded)) - .map_err(Error::MessageDecode)?, - )) - } - } - None => Ok(None), - } - } else { - Ok(None) + if !aggregator.cfg.taskprov_config.enabled { + return Ok(None); } + + let taskprov_header = match conn.request_headers().get(TASKPROV_HEADER) { + Some(taskprov_header) => taskprov_header, + None => return Ok(None), + }; + + let task_config_encoded = URL_SAFE_NO_PAD.decode(taskprov_header).map_err(|_| { + Error::InvalidMessage( + Some(*task_id), + "taskprov header could not be base64-decoded", + ) + })?; + + // Compute expected task ID & verify it matches the task ID from the request. + let expected_task_id = taskprov_task_id(&task_config_encoded); + if task_id != &expected_task_id { + return Err(Error::InvalidMessage( + Some(*task_id), + "derived taskprov task ID does not match task config", + )); + } + + // TODO(#1684): Parsing the taskprov header like this before we've been able to actually + // authenticate the client is undesireable. We should rework this such that the authorization + // header is handled before parsing the untrusted input. + Ok(Some( + TaskConfig::get_decoded(&task_config_encoded).map_err(Error::MessageDecode)?, + )) } struct BodyBytes(Vec); diff --git a/aggregator/src/aggregator/http_handlers/tests/hpke_config.rs b/aggregator/src/aggregator/http_handlers/tests/hpke_config.rs index 92f88e115..5e3a031f0 100644 --- a/aggregator/src/aggregator/http_handlers/tests/hpke_config.rs +++ b/aggregator/src/aggregator/http_handlers/tests/hpke_config.rs @@ -164,10 +164,7 @@ async fn hpke_config_with_taskprov() { .unwrap(); let cfg = Config { - taskprov_config: TaskprovConfig { - enabled: true, - ignore_unknown_differential_privacy_mechanism: false, - }, + taskprov_config: TaskprovConfig { enabled: true }, hpke_config_signing_key: Some(hpke_config_signing_key()), ..Default::default() }; diff --git a/aggregator/src/aggregator/taskprov_tests.rs b/aggregator/src/aggregator/taskprov_tests.rs index ff487d232..71993bb00 100644 --- a/aggregator/src/aggregator/taskprov_tests.rs +++ b/aggregator/src/aggregator/taskprov_tests.rs @@ -21,7 +21,7 @@ use janus_aggregator_core::{ test_util::{Task, TaskBuilder}, BatchMode, }, - taskprov::{test_util::PeerAggregatorBuilder, PeerAggregator}, + taskprov::{taskprov_task_id, test_util::PeerAggregatorBuilder, PeerAggregator}, test_util::noop_meter, }; use janus_core::{ @@ -33,12 +33,9 @@ use janus_core::{ vdaf::new_prio3_sum_vec_field64_multiproof_hmacsha256_aes128, }; use janus_messages::{ - batch_mode::LeaderSelected, + batch_mode::{self, LeaderSelected}, codec::{Decode, Encode}, - taskprov::{ - DpConfig, DpMechanism, Query as TaskprovQuery, QueryConfig, TaskConfig, VdafConfig, - VdafType, - }, + taskprov::{TaskConfig, VdafConfig}, AggregateShare as AggregateShareMessage, AggregateShareAad, AggregateShareReq, AggregationJobContinueReq, AggregationJobId, AggregationJobInitializeReq, AggregationJobResp, AggregationJobStep, BatchSelector, Duration, Extension, ExtensionType, Interval, @@ -50,7 +47,6 @@ use prio::{ vdaf::{dummy, Aggregator, Client, Vdaf}, }; use rand::random; -use ring::digest::{digest, SHA256}; use serde_json::json; use std::sync::Arc; use trillium::{Handler, KnownHeaderName, Status}; @@ -81,11 +77,7 @@ pub struct TaskprovTestCase { impl TaskprovTestCase<0, dummy::Vdaf> { async fn new() -> Self { let vdaf = dummy::Vdaf::new(2); - let vdaf_config = VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Fake { rounds: 2 }, - ) - .unwrap(); + let vdaf_config = VdafConfig::Fake { rounds: 2 }; let measurement = 13; let aggregation_param = dummy::AggregationParam(7); Self::with_vdaf(vdaf_config, vdaf, measurement, aggregation_param).await @@ -140,10 +132,7 @@ where TestRuntime::default(), &noop_meter(), Config { - taskprov_config: TaskprovConfig { - enabled: true, - ignore_unknown_differential_privacy_mechanism: false, - }, + taskprov_config: TaskprovConfig { enabled: true }, ..Default::default() }, ) @@ -154,25 +143,26 @@ where let time_precision = Duration::from_seconds(1); let min_batch_size = 1; - let task_end = clock.now().add(&Duration::from_hours(24).unwrap()).unwrap(); + let task_start = clock.now(); + let task_duration = Duration::from_hours(24).unwrap(); let task_config = TaskConfig::new( Vec::from("foobar".as_bytes()), "https://leader.example.com/".as_bytes().try_into().unwrap(), "https://helper.example.com/".as_bytes().try_into().unwrap(), - QueryConfig::new( - time_precision, - min_batch_size, - TaskprovQuery::LeaderSelected, - ), - task_end, + time_precision, + min_batch_size, + batch_mode::Code::LeaderSelected, + task_start, + task_duration, vdaf_config, + Vec::new(), ) .unwrap(); let task_config_encoded = task_config.get_encoded().unwrap(); - let task_id = TaskId::try_from(digest(&SHA256, &task_config_encoded).as_ref()).unwrap(); - let vdaf_instance = task_config.vdaf_config().vdaf_type().try_into().unwrap(); + let task_id = taskprov_task_id(&task_config_encoded); + let vdaf_instance = task_config.vdaf_config().try_into().unwrap(); let vdaf_verify_key = peer_aggregator.derive_vdaf_verify_key(&task_id, &vdaf_instance); let task = TaskBuilder::new( @@ -185,7 +175,8 @@ where .with_leader_aggregator_endpoint(Url::parse("https://leader.example.com/").unwrap()) .with_helper_aggregator_endpoint(Url::parse("https://helper.example.com/").unwrap()) .with_vdaf_verify_key(vdaf_verify_key) - .with_task_end(Some(task_end)) + .with_task_start(Some(task_start)) + .with_task_end(Some(task_start.add(&task_duration).unwrap())) .with_report_expiry_age(peer_aggregator.report_expiry_age().copied()) .with_min_batch_size(min_batch_size as u64) .with_time_precision(Duration::from_seconds(1)) @@ -218,7 +209,7 @@ where V::AggregationParam, ) { self.next_report_share_with_private_extensions(Vec::from([Extension::new( - ExtensionType::Taskprov, + ExtensionType::Taskbind, Vec::new(), )])) } @@ -631,27 +622,17 @@ async fn taskprov_opt_out_mismatched_task_id() { let aggregation_job_id: AggregationJobId = random(); - let task_end = test - .clock - .now() - .add(&Duration::from_hours(24).unwrap()) - .unwrap(); let another_task_config = TaskConfig::new( Vec::from("foobar".as_bytes()), "https://leader.example.com/".as_bytes().try_into().unwrap(), "https://helper.example.com/".as_bytes().try_into().unwrap(), - // Query configuration is different from the normal test case. - QueryConfig::new( - Duration::from_seconds(1), - 100, - TaskprovQuery::LeaderSelected, - ), - task_end, - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Fake { rounds: 2 }, - ) - .unwrap(), + Duration::from_seconds(1), + 100, + batch_mode::Code::LeaderSelected, + test.clock.now(), + Duration::from_hours(24).unwrap(), + VdafConfig::Fake { rounds: 2 }, + Vec::new(), ) .unwrap(); @@ -708,34 +689,22 @@ async fn taskprov_opt_out_peer_aggregator_wrong_role() { let aggregation_job_id: AggregationJobId = random(); - let task_end = test - .clock - .now() - .add(&Duration::from_hours(24).unwrap()) - .unwrap(); let another_task_config = TaskConfig::new( Vec::from("foobar".as_bytes()), // Attempt to configure leader as a helper. "https://helper.example.com/".as_bytes().try_into().unwrap(), "https://leader.example.com/".as_bytes().try_into().unwrap(), - QueryConfig::new( - Duration::from_seconds(1), - 100, - TaskprovQuery::LeaderSelected, - ), - task_end, - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Fake { rounds: 2 }, - ) - .unwrap(), + Duration::from_seconds(1), + 100, + batch_mode::Code::LeaderSelected, + test.clock.now(), + Duration::from_hours(24).unwrap(), + VdafConfig::Fake { rounds: 2 }, + Vec::new(), ) .unwrap(); let another_task_config_encoded = another_task_config.get_encoded().unwrap(); - let another_task_id: TaskId = digest(&SHA256, &another_task_config_encoded) - .as_ref() - .try_into() - .unwrap(); + let another_task_id = taskprov_task_id(&another_task_config_encoded); let auth = test .peer_aggregator @@ -786,34 +755,22 @@ async fn taskprov_opt_out_peer_aggregator_does_not_exist() { let aggregation_job_id: AggregationJobId = random(); - let task_end = test - .clock - .now() - .add(&Duration::from_hours(24).unwrap()) - .unwrap(); let another_task_config = TaskConfig::new( Vec::from("foobar".as_bytes()), // Some non-existent aggregator. "https://foobar.example.com/".as_bytes().try_into().unwrap(), "https://leader.example.com/".as_bytes().try_into().unwrap(), - QueryConfig::new( - Duration::from_seconds(1), - 100, - TaskprovQuery::LeaderSelected, - ), - task_end, - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Fake { rounds: 2 }, - ) - .unwrap(), + Duration::from_seconds(1), + 100, + batch_mode::Code::LeaderSelected, + test.clock.now(), + Duration::from_hours(24).unwrap(), + VdafConfig::Fake { rounds: 2 }, + Vec::new(), ) .unwrap(); let another_task_config_encoded = another_task_config.get_encoded().unwrap(); - let another_task_id: TaskId = digest(&SHA256, &another_task_config_encoded) - .as_ref() - .try_into() - .unwrap(); + let another_task_id = taskprov_task_id(&another_task_config_encoded); let auth = test .peer_aggregator @@ -1240,16 +1197,12 @@ async fn end_to_end_sumvec_hmac() { ParallelSumMultithreaded<_, _>, >(2, 8, 12, 14) .unwrap(); - let vdaf_config = VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3SumVecField64MultiproofHmacSha256Aes128 { - length: 12, - bits: 8, - chunk_length: 14, - proofs: 2, - }, - ) - .unwrap(); + let vdaf_config = VdafConfig::Prio3SumVecField64MultiproofHmacSha256Aes128 { + length: 12, + bits: 8, + chunk_length: 14, + proofs: 2, + }; let measurement = Vec::from([255, 1, 10, 20, 30, 0, 99, 100, 0, 0, 0, 0]); let test = TaskprovTestCase::with_vdaf(vdaf_config, vdaf, measurement, ()).await; let (auth_header_name, auth_header_value) = test diff --git a/aggregator/src/binaries/aggregator.rs b/aggregator/src/binaries/aggregator.rs index 62cbeae5f..80f556553 100644 --- a/aggregator/src/binaries/aggregator.rs +++ b/aggregator/src/binaries/aggregator.rs @@ -713,10 +713,7 @@ mod tests { ) .unwrap() .taskprov_config, - TaskprovConfig { - enabled: true, - ignore_unknown_differential_privacy_mechanism: false - }, + TaskprovConfig { enabled: true }, ); } diff --git a/aggregator/src/config.rs b/aggregator/src/config.rs index 3520440e0..1df28946a 100644 --- a/aggregator/src/config.rs +++ b/aggregator/src/config.rs @@ -149,16 +149,6 @@ pub struct TaskprovConfig { /// /// [spec]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/ pub enabled: bool, - - /// If true, will silently ignore unknown differential privacy mechanisms, and continue without - /// differential privacy noise. - /// - /// This should only be used for testing purposes. This option will be removed once full - /// differential privacy support is implemented. - /// - /// Defaults to false, i.e. opt out of tasks with unrecognized differential privacy mechanisms. - #[serde(default)] - pub ignore_unknown_differential_privacy_mechanism: bool, } /// Non-secret configuration options for Janus Job Driver jobs. diff --git a/aggregator_core/Cargo.toml b/aggregator_core/Cargo.toml index 99156f9cf..42f7b65ed 100644 --- a/aggregator_core/Cargo.toml +++ b/aggregator_core/Cargo.toml @@ -65,7 +65,7 @@ url.workspace = true [dev-dependencies] assert_matches.workspace = true -janus_aggregator_core = { path = ".", features = ["test-util"] } +janus_aggregator_core = { workspace = true, features = ["test-util"] } janus_core = { workspace = true, features = ["test-util"] } rstest.workspace = true rstest_reuse = { workspace = true } diff --git a/aggregator_core/src/datastore/tests.rs b/aggregator_core/src/datastore/tests.rs index d0c60223e..bce334d91 100644 --- a/aggregator_core/src/datastore/tests.rs +++ b/aggregator_core/src/datastore/tests.rs @@ -497,7 +497,7 @@ async fn roundtrip_report(ephemeral_datastore: EphemeralDatastore) { Vec::from([ // public extensions Extension::new(ExtensionType::Tbd, "public_extension_tbd".into()), - Extension::new(ExtensionType::Taskprov, "public_extension_taskprov".into()), + Extension::new(ExtensionType::Taskbind, "public_extension_taskbind".into()), ]), ), (), // public share @@ -505,8 +505,8 @@ async fn roundtrip_report(ephemeral_datastore: EphemeralDatastore) { // leader private extensions Extension::new(ExtensionType::Tbd, "leader_private_extension_tbd".into()), Extension::new( - ExtensionType::Taskprov, - "leader_private_extension_taskprov".into(), + ExtensionType::Taskbind, + "leader_private_extension_taskbind".into(), ), ]), dummy::InputShare::default(), // leader input share @@ -2327,8 +2327,8 @@ async fn roundtrip_report_aggregation(ephemeral_datastore: EphemeralDatastore) { )]), public_share: vdaf_transcript.public_share, leader_private_extensions: Vec::from([Extension::new( - ExtensionType::Taskprov, - "leader_private_extension_taskprov".into(), + ExtensionType::Taskbind, + "leader_private_extension_taskbind".into(), )]), leader_input_share: vdaf_transcript.leader_input_share, helper_encrypted_input_share: HpkeCiphertext::new( @@ -2854,8 +2854,8 @@ async fn create_report_aggregation_from_client_reports_table( ), (), Vec::from([Extension::new( - ExtensionType::Taskprov, - "leader_private_extension_taskprov".into(), + ExtensionType::Taskbind, + "leader_private_extension_taskbind".into(), )]), vdaf_transcript.leader_input_share, HpkeCiphertext::new( diff --git a/aggregator_core/src/task.rs b/aggregator_core/src/task.rs index 359a1190e..23690c5ee 100644 --- a/aggregator_core/src/task.rs +++ b/aggregator_core/src/task.rs @@ -8,7 +8,7 @@ use janus_core::{ time::TimeExt, vdaf::VdafInstance, }; -use janus_messages::{taskprov, AggregationJobId, Duration, HpkeConfig, Role, TaskId, Time}; +use janus_messages::{batch_mode, AggregationJobId, Duration, HpkeConfig, Role, TaskId, Time}; use rand::{distributions::Standard, random, thread_rng, Rng}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::array::TryFromSliceError; @@ -48,13 +48,13 @@ pub enum BatchMode { }, } -impl TryFrom<&taskprov::Query> for BatchMode { +impl TryFrom for BatchMode { type Error = Error; - fn try_from(value: &taskprov::Query) -> Result { + fn try_from(value: batch_mode::Code) -> Result { match value { - taskprov::Query::TimeInterval => Ok(Self::TimeInterval), - taskprov::Query::LeaderSelected => Ok(Self::LeaderSelected { + batch_mode::Code::TimeInterval => Ok(Self::TimeInterval), + batch_mode::Code::LeaderSelected => Ok(Self::LeaderSelected { batch_time_window_size: None, }), _ => Err(Error::InvalidParameter("unknown batch mode")), diff --git a/aggregator_core/src/taskprov.rs b/aggregator_core/src/taskprov.rs index 14ac71c03..b94d72ee6 100644 --- a/aggregator_core/src/taskprov.rs +++ b/aggregator_core/src/taskprov.rs @@ -4,7 +4,10 @@ use educe::Educe; use janus_core::{auth_tokens::AuthenticationToken, vdaf::VdafInstance}; use janus_messages::{Duration, HpkeConfig, Role, TaskId}; use rand::{distributions::Standard, prelude::Distribution}; -use ring::hkdf::{KeyType, Salt, HKDF_SHA256}; +use ring::{ + digest::{self, digest, Digest, SHA256}, + hkdf::{KeyType, Salt, HKDF_SHA256}, +}; use serde::{ de::{self, Visitor}, Deserialize, Serialize, Serializer, @@ -127,23 +130,6 @@ pub struct PeerAggregator { collector_auth_tokens: Vec, } -/// Salt generated by the SHA256 of the string 'dap-taskprov". See [taskprov section 3.2][1]. -/// -/// [1]: https://www.ietf.org/archive/id/draft-wang-ppm-dap-taskprov-04.html#name-deriving-the-vdaf-verificat -fn taskprov_salt() -> &'static Salt { - static SALT: LazyLock = LazyLock::new(|| { - Salt::new( - HKDF_SHA256, - &[ - 0x28, 0xb9, 0xbb, 0x4f, 0x62, 0x4f, 0x67, 0x9a, 0xc1, 0x98, 0xd9, 0x68, 0xf4, 0xb0, - 0x9e, 0xec, 0x74, 0x1, 0x7a, 0x52, 0xcb, 0x4c, 0xf6, 0x39, 0xfb, 0x83, 0xe0, 0x47, - 0x72, 0x3a, 0xf, 0xfe, - ], - ) - }); - &SALT -} - impl PeerAggregator { #[allow(clippy::too_many_arguments)] pub fn new( @@ -247,7 +233,10 @@ impl PeerAggregator { task_id: &TaskId, vdaf_instance: &VdafInstance, ) -> SecretBytes { - let prk = taskprov_salt().extract(self.verify_key_init.as_ref()); + static SALT: LazyLock = + LazyLock::new(|| Salt::new(HKDF_SHA256, digest(&SHA256, b"dap-taskprov").as_ref())); + + let prk = SALT.extract(self.verify_key_init.as_ref()); let info = [task_id.as_ref().as_slice()]; // Unwrap safety: this function only errors if the OKM length is too long @@ -272,6 +261,18 @@ impl KeyType for VdafVerifyKeyLength { } } +pub fn taskprov_task_id(encoded_task_config: &[u8]) -> TaskId { + static TASK_ID_SALT: LazyLock = + LazyLock::new(|| digest(&SHA256, b"dap-taskprov task id")); + + let mut ctx = digest::Context::new(&SHA256); + ctx.update(TASK_ID_SALT.as_ref()); + ctx.update(encoded_task_config); + let digest = ctx.finish(); + // Unwrap safety: DAP task IDs, and SHA-256 hashes, are always 32 bytes long. + TaskId::try_from(digest.as_ref()).unwrap() +} + #[cfg(feature = "test-util")] #[cfg_attr(docsrs, doc(cfg(feature = "test-util")))] pub mod test_util { diff --git a/collector/Cargo.toml b/collector/Cargo.toml index c06e1ab96..fefc0f4d8 100644 --- a/collector/Cargo.toml +++ b/collector/Cargo.toml @@ -39,6 +39,6 @@ url = { workspace = true } [dev-dependencies] assert_matches.workspace = true base64.workspace = true -janus_collector = { path = ".", features = ["fpvec_bounded_l2", "test-util"] } +janus_collector = { workspace = true, features = ["fpvec_bounded_l2", "test-util"] } janus_core = { workspace = true, features = ["fpvec_bounded_l2", "test-util"] } mockito = { workspace = true } diff --git a/core/Cargo.toml b/core/Cargo.toml index 8285509dd..a51449a76 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -76,7 +76,7 @@ url = { workspace = true } [dev-dependencies] fixed = { workspace = true } hex = { workspace = true, features = ["serde"] } # ensure this remains compatible with the non-dev dependency -janus_core = { path = ".", features = ["test-util"] } +janus_core = { workspace = true, features = ["test-util"] } mockito = { workspace = true } rstest.workspace = true rustls.workspace = true diff --git a/core/src/vdaf.rs b/core/src/vdaf.rs index e49aed07b..346472e34 100644 --- a/core/src/vdaf.rs +++ b/core/src/vdaf.rs @@ -176,16 +176,18 @@ impl VdafInstance { } } -impl TryFrom<&taskprov::VdafType> for VdafInstance { +impl TryFrom<&taskprov::VdafConfig> for VdafInstance { type Error = &'static str; - fn try_from(value: &taskprov::VdafType) -> Result { + fn try_from(value: &taskprov::VdafConfig) -> Result { match value { - taskprov::VdafType::Prio3Count => Ok(Self::Prio3Count), - taskprov::VdafType::Prio3Sum { bits } => Ok(Self::Prio3Sum { - bits: *bits as usize, + taskprov::VdafConfig::Prio3Count => Ok(Self::Prio3Count), + taskprov::VdafConfig::Prio3Sum { + max_measurement: _max_measurement, + } => Ok(Self::Prio3Sum { + bits: 32, // TODO(#3436): plumb through max_measurement once it's available }), - taskprov::VdafType::Prio3SumVec { + taskprov::VdafConfig::Prio3SumVec { bits, length, chunk_length, @@ -195,7 +197,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { chunk_length: *chunk_length as usize, dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }), - taskprov::VdafType::Prio3SumVecField64MultiproofHmacSha256Aes128 { + taskprov::VdafConfig::Prio3SumVecField64MultiproofHmacSha256Aes128 { bits, length, chunk_length, @@ -207,7 +209,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { chunk_length: *chunk_length as usize, dp_strategy: vdaf_dp_strategies::Prio3SumVec::NoDifferentialPrivacy, }), - taskprov::VdafType::Prio3Histogram { + taskprov::VdafConfig::Prio3Histogram { length, chunk_length, } => Ok(Self::Prio3Histogram { @@ -217,7 +219,7 @@ impl TryFrom<&taskprov::VdafType> for VdafInstance { }), #[cfg(feature = "test-util")] - taskprov::VdafType::Fake { rounds } => Ok(Self::Fake { rounds: *rounds }), + taskprov::VdafConfig::Fake { rounds } => Ok(Self::Fake { rounds: *rounds }), _ => Err("unknown VdafType"), } diff --git a/docs/CONFIGURING_TASKPROV.md b/docs/CONFIGURING_TASKPROV.md index 4f58ad404..1731ede74 100644 --- a/docs/CONFIGURING_TASKPROV.md +++ b/docs/CONFIGURING_TASKPROV.md @@ -1,7 +1,7 @@ # Taskprov Extension Janus has limited support for the Taskprov extension, as defined in -[draft-wang-ppm-dap-taskprov][1]. We support it largely to facilitiate +[draft-ietf-ppm-dap-taskprov][1]. We support it largely to facilitiate integrations with Divvi Up partners who have requested support for the extension. @@ -12,7 +12,7 @@ respond by validating and provisioning the task. The exact mechanism of how the task configuration is provided depends on the version of the implemented spec draft. -[1]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/ +[1]: https://datatracker.ietf.org/doc/draft-ietf-ppm-dap-taskprov/ ## Compatibility @@ -21,11 +21,14 @@ implemented on any branches missing from this list. | Git branch | Taskprov draft version | Conforms to protocol? | Status | | ---------- | ------------- | --------------------- | ------ | -| `release/0.subscriber-01` | [`draft-wang-ppm-dap-taskprov-04`][2] | Helper only | Unmaintained as of November 1, 2023 | -| `release/0.5` | [`draft-wang-ppm-dap-taskprov-04`][2] | Helper only | Supported | -| `main` | [`draft-wang-ppm-dap-taskprov-06`][2] | Helper only | Supported | - -[2]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/06/ +| `release/0.subscriber-01` | [`draft-wang-ppm-dap-taskprov-04`][2] | Helper only | Unsupported as of November 1, 2023 | +| `release/0.5` | [`draft-wang-ppm-dap-taskprov-04`][2] | Helper only | Unsupported as of June 24, 2024 | +| `release/0.7` | [`draft-wang-ppm-dap-taskprov-06`][3] | Helper only | Supported | +| `main` | [`draft-ietf-ppm-dap-taskprov-01`][4] | Helper only | Supported | + +[2]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/04/ +[3]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/06/ +[4]: https://datatracker.ietf.org/doc/draft-ietf-ppm-dap-taskprov/01/ ## Operational Considerations diff --git a/interop_binaries/Cargo.toml b/interop_binaries/Cargo.toml index 4e89acf1c..88ffacffa 100644 --- a/interop_binaries/Cargo.toml +++ b/interop_binaries/Cargo.toml @@ -52,5 +52,5 @@ url.workspace = true [dev-dependencies] janus_core = { workspace = true, features = ["test-util", "fpvec_bounded_l2"] } -janus_interop_binaries = { path = ".", features = ["fpvec_bounded_l2", "test-util"] } +janus_interop_binaries = { workspace = true, features = ["fpvec_bounded_l2", "test-util"] } reqwest = { workspace = true, default-features = false, features = ["json"] } diff --git a/messages/Cargo.toml b/messages/Cargo.toml index 403cb0e73..66bc5c947 100644 --- a/messages/Cargo.toml +++ b/messages/Cargo.toml @@ -28,5 +28,6 @@ url = { workspace = true } [dev-dependencies] assert_matches.workspace = true +janus_messages = { workspace = true, features = ["test-util"] } pretty_assertions.workspace = true serde_test.workspace = true diff --git a/messages/src/lib.rs b/messages/src/lib.rs index fd873225b..b27925640 100644 --- a/messages/src/lib.rs +++ b/messages/src/lib.rs @@ -928,7 +928,7 @@ impl Decode for Extension { #[non_exhaustive] pub enum ExtensionType { Tbd = 0, - Taskprov = 0xFF00, + Taskbind = 0xFF00, } impl Encode for ExtensionType { diff --git a/messages/src/taskprov.rs b/messages/src/taskprov.rs index 181dab233..c7af4f5d8 100644 --- a/messages/src/taskprov.rs +++ b/messages/src/taskprov.rs @@ -2,8 +2,9 @@ //! //! [1]: https://datatracker.ietf.org/doc/draft-wang-ppm-dap-taskprov/ -use crate::{Duration, Error, Time, Url}; +use crate::{batch_mode, Duration, Error, Time, Url}; use anyhow::anyhow; +use num_enum::TryFromPrimitive; use prio::codec::{ decode_u16_items, decode_u8_items, encode_u16_items, encode_u8_items, CodecError, Decode, Encode, @@ -14,28 +15,41 @@ use std::{fmt::Debug, io::Cursor}; /// Provided by taskprov participants in all requests incident to task execution. #[derive(Clone, Debug, PartialEq, Eq)] pub struct TaskConfig { - /// Opaque info specific for a task. + /// Opaque info specific for this task. task_info: Vec, /// Leader DAP API endpoint. leader_aggregator_endpoint: Url, /// Helper DAP API endpoint. helper_aggregator_endpoint: Url, - /// Determines the properties that all batches for this task must have. - query_config: QueryConfig, - /// Time up to which Clients are expected to upload to this task. - task_end: Time, + /// Time precision of this task. + time_precision: Duration, + /// The minimum batch size for this task. + min_batch_size: u32, + /// Determines the batch mode for this task. + batch_mode: batch_mode::Code, + /// The earliest timestamp that will be accepted for this task. + task_start: Time, + /// The duration of the task. + task_duration: Duration, /// Determines VDAF type and all properties. vdaf_config: VdafConfig, + /// Taskbind extensions. + extensions: Vec, } impl TaskConfig { + #[allow(clippy::too_many_arguments)] pub fn new( task_info: Vec, leader_aggregator_endpoint: Url, helper_aggregator_endpoint: Url, - query_config: QueryConfig, - task_end: Time, + time_precision: Duration, + min_batch_size: u32, + batch_mode: batch_mode::Code, + task_start: Time, + task_duration: Duration, vdaf_config: VdafConfig, + extensions: Vec, ) -> Result { if task_info.is_empty() { return Err(Error::InvalidParameter("task_info must not be empty")); @@ -45,9 +59,13 @@ impl TaskConfig { task_info, leader_aggregator_endpoint, helper_aggregator_endpoint, - query_config, - task_end, + time_precision, + min_batch_size, + batch_mode, + task_start, + task_duration, vdaf_config, + extensions, }) } @@ -63,17 +81,33 @@ impl TaskConfig { &self.helper_aggregator_endpoint } - pub fn query_config(&self) -> &QueryConfig { - &self.query_config + pub fn time_precision(&self) -> &Duration { + &self.time_precision + } + + pub fn min_batch_size(&self) -> &u32 { + &self.min_batch_size } - pub fn task_end(&self) -> &Time { - &self.task_end + pub fn batch_mode(&self) -> &batch_mode::Code { + &self.batch_mode + } + + pub fn task_start(&self) -> &Time { + &self.task_start + } + + pub fn task_duration(&self) -> &Duration { + &self.task_duration } pub fn vdaf_config(&self) -> &VdafConfig { &self.vdaf_config } + + pub fn extensions(&self) -> &[TaskbindExtension] { + &self.extensions + } } impl Encode for TaskConfig { @@ -81,21 +115,35 @@ impl Encode for TaskConfig { encode_u8_items(bytes, &(), &self.task_info)?; self.leader_aggregator_endpoint.encode(bytes)?; self.helper_aggregator_endpoint.encode(bytes)?; - encode_u16_items(bytes, &(), &self.query_config.get_encoded()?)?; - self.task_end.encode(bytes)?; - encode_u16_items(bytes, &(), &self.vdaf_config.get_encoded()?)?; + self.time_precision.encode(bytes)?; + self.min_batch_size.encode(bytes)?; + self.batch_mode.encode(bytes)?; + (0u16).encode(bytes)?; // batch_config length (batch_config always empty currently) + self.task_start.encode(bytes)?; + self.task_duration.encode(bytes)?; + self.vdaf_config.encode(bytes)?; + encode_u16_items(bytes, &(), &self.extensions)?; Ok(()) } fn encoded_len(&self) -> Option { - Some( - (1 + self.task_info.len()) - + self.leader_aggregator_endpoint.encoded_len()? - + self.helper_aggregator_endpoint.encoded_len()? - + (2 + self.query_config.encoded_len()?) - + self.task_end.encoded_len()? - + (2 + self.vdaf_config.encoded_len()?), - ) + let mut len = (1 + self.task_info.len()) + + self.leader_aggregator_endpoint.encoded_len()? + + self.helper_aggregator_endpoint.encoded_len()? + + self.time_precision.encoded_len()? + + self.min_batch_size.encoded_len()? + + (self.batch_mode.encoded_len()? + 2) + + self.task_start.encoded_len()? + + self.task_duration.encoded_len()? + + self.vdaf_config.encoded_len()?; + + // Extensions. + len += 2; + for extension in &self.extensions { + len += extension.encoded_len()?; + } + + Some(len) } } @@ -109,259 +157,153 @@ impl Decode for TaskConfig { } let leader_aggregator_endpoint = Url::decode(bytes)?; let helper_aggregator_endpoint = Url::decode(bytes)?; - let query_config = QueryConfig::get_decoded(&decode_u16_items(&(), bytes)?)?; - let task_end = Time::decode(bytes)?; - let vdaf_config = VdafConfig::get_decoded(&decode_u16_items(&(), bytes)?)?; + let time_precision = Duration::decode(bytes)?; + let min_batch_size = u32::decode(bytes)?; + let batch_mode = batch_mode::Code::decode(bytes)?; + let batch_config_len = u16::decode(bytes)?; + if batch_config_len != 0 { + return Err(CodecError::Other( + anyhow!("batch_config length is not zero").into(), + )); + }; + let task_start = Time::decode(bytes)?; + let task_duration = Duration::decode(bytes)?; + let vdaf_config = VdafConfig::decode(bytes)?; + let extensions = decode_u16_items(&(), bytes)?; Ok(Self { task_info, leader_aggregator_endpoint, helper_aggregator_endpoint, - query_config, - task_end, - vdaf_config, - }) - } -} - -/// All properties that batches for a task must have. Properties are as defined -/// in DAP[1]. -/// -/// [1]: https://www.ietf.org/archive/id/draft-ietf-ppm-dap-05.html#name-queries -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct QueryConfig { - /// Used by clients to truncate report timestamps. - time_precision: Duration, - /// The smallest number of reports that a batch can include. - min_batch_size: u32, - /// The batch mode along with associated parameters. - query: Query, -} - -impl QueryConfig { - pub fn new(time_precision: Duration, min_batch_size: u32, query: Query) -> Self { - Self { - time_precision, - min_batch_size, - query, - } - } - - pub fn time_precision(&self) -> &Duration { - &self.time_precision - } - - pub fn min_batch_size(&self) -> u32 { - self.min_batch_size - } - - pub fn query(&self) -> &Query { - &self.query - } -} - -impl Encode for QueryConfig { - fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { - self.time_precision.encode(bytes)?; - self.min_batch_size.encode(bytes)?; - self.query.encode(bytes)?; - Ok(()) - } - - fn encoded_len(&self) -> Option { - Some( - self.time_precision.encoded_len()? - + self.min_batch_size.encoded_len()? - + self.query.encoded_len()?, - ) - } -} - -impl Decode for QueryConfig { - fn decode(bytes: &mut Cursor<&[u8]>) -> Result { - let time_precision = Duration::decode(bytes)?; - let min_batch_size = u32::decode(bytes)?; - let query = Query::decode(bytes)?; - - Ok(Self { time_precision, min_batch_size, - query, - }) - } -} - -/// A batch mode and its associated parameter(s). -/// -/// The redefinition of Query relative to the parent mod is because the type of Query is not known -/// at compile time. For queries of unknown type, using the parent mod would require attempting -/// decoding for each batch mode until success. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum Query { - Reserved, - TimeInterval, - LeaderSelected, -} - -impl Query { - const RESERVED: u8 = 0; - const TIME_INTERVAL: u8 = 1; - const LEADER_SELECTED: u8 = 2; -} - -impl Encode for Query { - fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { - match self { - Query::Reserved => Query::RESERVED.encode(bytes), - Query::TimeInterval => Query::TIME_INTERVAL.encode(bytes), - Query::LeaderSelected => Query::LEADER_SELECTED.encode(bytes), - } - } - - fn encoded_len(&self) -> Option { - Some(1) - } -} - -impl Decode for Query { - fn decode(bytes: &mut Cursor<&[u8]>) -> Result { - let batch_mode = u8::decode(bytes)?; - Ok(match batch_mode { - Query::RESERVED => Query::Reserved, - Query::TIME_INTERVAL => Query::TimeInterval, - Query::LEADER_SELECTED => Query::LeaderSelected, - val => { - return Err(CodecError::Other( - anyhow!("unexpected BatchMode value {}", val).into(), - )) - } - }) - } -} - -/// Describes all VDAF parameters. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct VdafConfig { - dp_config: DpConfig, - vdaf_type: VdafType, -} - -impl VdafConfig { - pub fn new(dp_config: DpConfig, vdaf_type: VdafType) -> Result { - Ok(Self { - dp_config, - vdaf_type, - }) - } - - pub fn dp_config(&self) -> &DpConfig { - &self.dp_config - } - - pub fn vdaf_type(&self) -> &VdafType { - &self.vdaf_type - } -} - -impl Encode for VdafConfig { - fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { - encode_u16_items(bytes, &(), &self.dp_config.get_encoded()?)?; - self.vdaf_type.encode(bytes) - } - - fn encoded_len(&self) -> Option { - Some((2 + self.dp_config.encoded_len()?) + self.vdaf_type.encoded_len()?) - } -} - -impl Decode for VdafConfig { - fn decode(bytes: &mut Cursor<&[u8]>) -> Result { - let dp_config = DpConfig::get_decoded(&decode_u16_items(&(), bytes)?)?; - let vdaf_type = VdafType::decode(bytes)?; - - Ok(Self { - dp_config, - vdaf_type, + batch_mode, + task_start, + task_duration, + vdaf_config, + extensions, }) } } +/// Tasprov message indicating a VDAF configuration. This type corresponds to (and encodes/decodes +/// as) a concatenation of the type code, a 2-byte length field, and one of the VdafConfig messages +/// defined in the taskprov specification. #[derive(Debug, Clone, PartialEq, Eq)] -#[repr(u32)] #[non_exhaustive] -pub enum VdafType { +pub enum VdafConfig { + // Specified in VDAF/taskprov. + Reserved, Prio3Count, Prio3Sum { - /// Bit length of the summand. - bits: u8, + /// Largest summand. + max_measurement: u32, }, Prio3SumVec { - /// Number of summands. + /// Length of the vector. length: u32, /// Bit length of each summand. bits: u8, /// Size of each proof chunk. chunk_length: u32, }, - Prio3SumVecField64MultiproofHmacSha256Aes128 { - /// Number of summands. + Prio3Histogram { + /// Number of buckets. length: u32, - /// Bit length of each summand. - bits: u8, /// Size of each proof chunk. chunk_length: u32, - /// Number of proofs. - proofs: u8, }, - Prio3Histogram { - /// Number of buckets. + Prio3MultihotCountVec { + /// Length of the vector. length: u32, /// Size of each proof chunk. chunk_length: u32, + /// Largest vector weight. + max_weight: u32, + }, + Poplar1 { + /// Bit length of the input string. + bits: u16, }, + // "Reserved for private use" space [0xFFFF0000 - 0xFFFFFFFF] /// A fake, no-op VDAF, which uses an aggregation parameter and a variable number of rounds. #[cfg(feature = "test-util")] #[cfg_attr(docsrs, doc(cfg(feature = "test-util")))] Fake { rounds: u32, }, + Prio3SumVecField64MultiproofHmacSha256Aes128 { + /// Number of summands. + length: u32, + /// Bit length of each summand. + bits: u8, + /// Size of each proof chunk. + chunk_length: u32, + /// Number of proofs. + proofs: u8, + }, } -impl VdafType { - const PRIO3COUNT: u32 = 0x00000000; - const PRIO3SUM: u32 = 0x00000001; - const PRIO3SUMVEC: u32 = 0x00000002; - const PRIO3HISTOGRAM: u32 = 0x00000003; - const PRIO3SUMVECFIELD64MULTIPROOFHMACSHA256AES128: u32 = 0xFFFF1003; - +impl VdafConfig { + // Specified in VDAF. + const RESERVED: u32 = 0x00000000; + const PRIO3_COUNT: u32 = 0x00000001; + const PRIO3_SUM: u32 = 0x00000002; + const PRIO3_SUM_VEC: u32 = 0x00000003; + const PRIO3_HISTOGRAM: u32 = 0x00000004; + const PRIO3_MULTIHOT_COUNT_VEC: u32 = 0x00000005; + const POPLAR1: u32 = 0x00000006; + + // "Reserved for private use" space [0xFFFF0000 - 0xFFFFFFFF] #[cfg(feature = "test-util")] - const FAKE: u32 = 0xFFFF0000; // Chosen from the "reserved for private use" space. + const FAKE: u32 = 0xFFFF0000; + const PRIO3_SUM_VEC_FIELD64_MULTIPROOF_HMAC_SHA256_AES128: u32 = 0xFFFF1003; fn vdaf_type_code(&self) -> u32 { match self { - Self::Prio3Count => Self::PRIO3COUNT, - Self::Prio3Sum { .. } => Self::PRIO3SUM, - Self::Prio3SumVec { .. } => Self::PRIO3SUMVEC, + Self::Reserved => Self::RESERVED, + Self::Prio3Count => Self::PRIO3_COUNT, + Self::Prio3Sum { .. } => Self::PRIO3_SUM, + Self::Prio3SumVec { .. } => Self::PRIO3_SUM_VEC, + Self::Prio3Histogram { .. } => Self::PRIO3_HISTOGRAM, + Self::Prio3MultihotCountVec { .. } => Self::PRIO3_MULTIHOT_COUNT_VEC, + Self::Poplar1 { .. } => Self::POPLAR1, + + #[cfg(feature = "test-util")] + Self::Fake { .. } => Self::FAKE, Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { .. } => { - Self::PRIO3SUMVECFIELD64MULTIPROOFHMACSHA256AES128 + Self::PRIO3_SUM_VEC_FIELD64_MULTIPROOF_HMAC_SHA256_AES128 } - Self::Prio3Histogram { .. } => Self::PRIO3HISTOGRAM, + } + } + + fn vdaf_config_len(&self) -> u16 { + match self { + Self::Reserved => 0, + Self::Prio3Count => 0, + Self::Prio3Sum { .. } => 4, + Self::Prio3SumVec { .. } => 9, + Self::Prio3Histogram { .. } => 8, + Self::Prio3MultihotCountVec { .. } => 12, + Self::Poplar1 { .. } => 2, #[cfg(feature = "test-util")] - Self::Fake { .. } => Self::FAKE, + Self::Fake { .. } => 4, + Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { .. } => 10, } } } -impl Encode for VdafType { +impl Encode for VdafConfig { fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { self.vdaf_type_code().encode(bytes)?; + self.vdaf_config_len().encode(bytes)?; match self { + Self::Reserved => (), Self::Prio3Count => (), - Self::Prio3Sum { bits } => { - bits.encode(bytes)?; + Self::Prio3Sum { max_measurement } => { + max_measurement.encode(bytes)?; } Self::Prio3SumVec { length, @@ -372,79 +314,90 @@ impl Encode for VdafType { bits.encode(bytes)?; chunk_length.encode(bytes)?; } - Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { + Self::Prio3Histogram { length, - bits, chunk_length, - proofs, } => { length.encode(bytes)?; - bits.encode(bytes)?; chunk_length.encode(bytes)?; - proofs.encode(bytes)?; } - Self::Prio3Histogram { + Self::Prio3MultihotCountVec { length, chunk_length, + max_weight, } => { length.encode(bytes)?; chunk_length.encode(bytes)?; + max_weight.encode(bytes)?; + } + Self::Poplar1 { bits } => { + bits.encode(bytes)?; } #[cfg(feature = "test-util")] Self::Fake { rounds } => { rounds.encode(bytes)?; } + Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { + length, + bits, + chunk_length, + proofs, + } => { + length.encode(bytes)?; + bits.encode(bytes)?; + chunk_length.encode(bytes)?; + proofs.encode(bytes)?; + } } Ok(()) } fn encoded_len(&self) -> Option { - Some( - 4 + match self { - Self::Prio3Count => 0, - Self::Prio3Sum { .. } => 1, - Self::Prio3SumVec { .. } => 9, - Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { .. } => 10, - Self::Prio3Histogram { .. } => 8, - - #[cfg(feature = "test-util")] - Self::Fake { .. } => 4, - }, - ) + Some(4 + 2 + usize::from(self.vdaf_config_len())) } } -impl Decode for VdafType { +impl Decode for VdafConfig { fn decode(bytes: &mut Cursor<&[u8]>) -> Result { let vdaf_type_code = u32::decode(bytes)?; + let vdaf_config_len = u16::decode(bytes)?; let vdaf_type = match vdaf_type_code { - Self::PRIO3COUNT => Self::Prio3Count, - Self::PRIO3SUM => Self::Prio3Sum { - bits: u8::decode(bytes)?, + Self::RESERVED => Self::Reserved, + Self::PRIO3_COUNT => Self::Prio3Count, + Self::PRIO3_SUM => Self::Prio3Sum { + max_measurement: u32::decode(bytes)?, }, - Self::PRIO3SUMVEC => Self::Prio3SumVec { + Self::PRIO3_SUM_VEC => Self::Prio3SumVec { length: u32::decode(bytes)?, bits: u8::decode(bytes)?, chunk_length: u32::decode(bytes)?, }, - Self::PRIO3SUMVECFIELD64MULTIPROOFHMACSHA256AES128 => { - Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { - length: u32::decode(bytes)?, - bits: u8::decode(bytes)?, - chunk_length: u32::decode(bytes)?, - proofs: u8::decode(bytes)?, - } - } - Self::PRIO3HISTOGRAM => Self::Prio3Histogram { + Self::PRIO3_HISTOGRAM => Self::Prio3Histogram { length: u32::decode(bytes)?, chunk_length: u32::decode(bytes)?, }, + Self::PRIO3_MULTIHOT_COUNT_VEC => Self::Prio3MultihotCountVec { + length: u32::decode(bytes)?, + chunk_length: u32::decode(bytes)?, + max_weight: u32::decode(bytes)?, + }, + Self::POPLAR1 => Self::Poplar1 { + bits: u16::decode(bytes)?, + }, #[cfg(feature = "test-util")] Self::FAKE => Self::Fake { rounds: u32::decode(bytes)?, }, + Self::PRIO3_SUM_VEC_FIELD64_MULTIPROOF_HMAC_SHA256_AES128 => { + Self::Prio3SumVecField64MultiproofHmacSha256Aes128 { + length: u32::decode(bytes)?, + bits: u8::decode(bytes)?, + chunk_length: u32::decode(bytes)?, + proofs: u8::decode(bytes)?, + } + } val => { return Err(CodecError::Other( @@ -453,574 +406,444 @@ impl Decode for VdafType { } }; + if vdaf_config_len != vdaf_type.vdaf_config_len() { + return Err(CodecError::Other( + anyhow!( + "VDAF config length prefix ({}) does not match expected value ({})", + vdaf_config_len, + vdaf_type.vdaf_config_len() + ) + .into(), + )); + } + Ok(vdaf_type) } } -/// Parameters for Differential Privacy. This is mostly unspecified at the moment. -/// See [draft-irtf-cfrg-vdaf/#94][1] for discussion. -/// -/// [1]: https://github.com/cfrg/draft-irtf-cfrg-vdaf/issues/94 +/// Taskprov message indicating an extension to a taskprov configuration. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct DpConfig { - dp_mechanism: DpMechanism, +pub struct TaskbindExtension { + extension_type: TaskbindExtensionType, + extension_data: Vec, } -impl DpConfig { - pub fn new(dp_mechanism: DpMechanism) -> Self { - Self { dp_mechanism } +impl TaskbindExtension { + /// Construct an extension from its type and payload. + pub fn new(extension_type: TaskbindExtensionType, extension_data: Vec) -> Self { + Self { + extension_type, + extension_data, + } + } + + /// Returns the type of this extension. + pub fn extension_type(&self) -> &TaskbindExtensionType { + &self.extension_type } - pub fn dp_mechanism(&self) -> &DpMechanism { - &self.dp_mechanism + /// Returns the unparsed data representing this extension. + pub fn extension_data(&self) -> &[u8] { + &self.extension_data } } -impl Encode for DpConfig { +impl Encode for TaskbindExtension { fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { - self.dp_mechanism.encode(bytes) + self.extension_type.encode(bytes)?; + encode_u16_items(bytes, &(), &self.extension_data) } fn encoded_len(&self) -> Option { - self.dp_mechanism.encoded_len() + // Type, length prefix, and extension data. + Some(self.extension_type.encoded_len()? + 2 + self.extension_data.len()) } } -impl Decode for DpConfig { +impl Decode for TaskbindExtension { fn decode(bytes: &mut Cursor<&[u8]>) -> Result { + let extension_type = TaskbindExtensionType::decode(bytes)?; + let extension_data = decode_u16_items(&(), bytes)?; + Ok(Self { - dp_mechanism: DpMechanism::decode(bytes)?, + extension_type, + extension_data, }) } } -#[derive(Clone, Debug, PartialEq, Eq)] -#[repr(u8)] +/// Taskprov message indicating the type of a taskbind extension. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, TryFromPrimitive)] +#[repr(u16)] #[non_exhaustive] -pub enum DpMechanism { - Reserved, - None, - Unrecognized { codepoint: u8, payload: Vec }, -} - -impl DpMechanism { - const RESERVED: u8 = 0; - const NONE: u8 = 1; +pub enum TaskbindExtensionType { + Reserved = 0x0000, } -impl Encode for DpMechanism { +impl Encode for TaskbindExtensionType { fn encode(&self, bytes: &mut Vec) -> Result<(), CodecError> { - match self { - Self::Reserved => Self::RESERVED.encode(bytes)?, - Self::None => Self::NONE.encode(bytes)?, - Self::Unrecognized { codepoint, payload } => { - codepoint.encode(bytes)?; - bytes.extend_from_slice(payload); - } - }; - Ok(()) + (*self as u16).encode(bytes) } fn encoded_len(&self) -> Option { - match self { - DpMechanism::Reserved | DpMechanism::None => Some(1), - DpMechanism::Unrecognized { - codepoint: _, - payload, - } => Some(1 + payload.len()), - } + Some(2) } } -impl Decode for DpMechanism { +impl Decode for TaskbindExtensionType { fn decode(bytes: &mut Cursor<&[u8]>) -> Result { - let dp_mechanism_code = u8::decode(bytes)?; - let dp_mechanism = match dp_mechanism_code { - Self::RESERVED => Self::Reserved, - Self::NONE => Self::None, - codepoint => { - let position = usize::try_from(bytes.position()) - .map_err(|_| CodecError::Other(anyhow!("cursor position overflow").into()))?; - let inner = bytes.get_ref(); - let payload = inner[position..].to_vec(); - bytes - .set_position(u64::try_from(inner.len()).map_err(|_| { - CodecError::Other(anyhow!("cursor length overflow").into()) - })?); - Self::Unrecognized { codepoint, payload } - } - }; - - Ok(dp_mechanism) + let val = u16::decode(bytes)?; + Self::try_from(val).map_err(|_| { + CodecError::Other(anyhow!("unexpected TaskbindExtensionType value ({})", val).into()) + }) } } #[cfg(test)] mod tests { - use super::*; - use crate::roundtrip_encoding; + use crate::{ + batch_mode, roundtrip_encoding, + taskprov::{TaskConfig, TaskbindExtension, TaskbindExtensionType, VdafConfig}, + Duration, Time, Url, + }; use assert_matches::assert_matches; + use prio::codec::{CodecError, Decode as _}; #[test] - fn roundtrip_dp_config() { + fn roundtrip_task_config() { roundtrip_encoding(&[ ( - DpConfig::new(DpMechanism::Reserved), - concat!( - "00", // dp_mechanism - ), - ), - ( - DpConfig::new(DpMechanism::None), + TaskConfig::new( + "foobar".as_bytes().to_vec(), + Url::try_from("https://example.com/".as_ref()).unwrap(), + Url::try_from("https://another.example.com/".as_ref()).unwrap(), + Duration::from_seconds(3600), + 10000, + batch_mode::Code::TimeInterval, + Time::from_seconds_since_epoch(1000000), + Duration::from_seconds(100000), + VdafConfig::Prio3Count, + Vec::new(), + ) + .unwrap(), concat!( - "01", // dp_mechanism + concat!( + // task_info + "06", // length + "666F6F626172" // opaque data + ), + concat!( + // leader_aggregator_url + "0014", // length + "68747470733A2F2F6578616D706C652E636F6D2F" // contents + ), + concat!( + // helper_aggregator_url + "001C", // length + "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents + ), + "0000000000000E10", // time_precision + "00002710", // min_batch_size + "01", // batch_mode + concat!( + // batch_config + "0000", // length + ), + "00000000000F4240", // task_start + "00000000000186A0", // task_duration + "00000001", // vdaf_type + concat!( + // vdaf_config + "0000", // length + ), + concat!( + // extensions + "0000", // length + ), ), ), ( - DpConfig::new(DpMechanism::Unrecognized { - codepoint: 0xff, - payload: Vec::from([0xde, 0xad, 0xbe, 0xef]), - }), - concat!( - "FF", // dp_mechanism - "DEADBEEF" // uninterpreted DpConfig contents - ), + TaskConfig::new( + "f".as_bytes().to_vec(), + Url::try_from("https://example.com/".as_ref()).unwrap(), + Url::try_from("https://another.example.com/".as_ref()).unwrap(), + Duration::from_seconds(1000), + 1000, + batch_mode::Code::LeaderSelected, + Time::from_seconds_since_epoch(10000000), + Duration::from_seconds(50000), + VdafConfig::Prio3Sum { + max_measurement: 0xFF, + }, + Vec::from([TaskbindExtension::new( + TaskbindExtensionType::Reserved, + Vec::from("0123"), + )]), + ) + .unwrap(), + concat!( + concat!( + // task_info + "01", // length + "66" // opaque data + ), + concat!( + // leader_aggregator_url + "0014", // length + "68747470733A2F2F6578616D706C652E636F6D2F" // contents + ), + concat!( + // helper_aggregator_url + "001C", // length + "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents + ), + "00000000000003E8", // time_precision + "000003E8", // min_batch_size + "02", // batch_mode + concat!( + // batch_config + "0000", // length + ), + "0000000000989680", // task_start + "000000000000C350", // task_duration + "00000002", // vdaf_type + concat!( + // vdaf_config + "0004", // vdaf_config length + "000000FF", // max_measurement + ), + concat!( + // extensions + "0008", // length + concat!( + "0000", // extension_type + "0004", // extension_data length + "30313233", // extension_data + ), + ), + ), ), - ]) + ]); + + // Empty task_info. + assert_matches!( + TaskConfig::get_decoded( + &hex::decode(concat!( + concat!( + // task_info + "00", // length + "" // opaque data + ), + concat!( + // leader_aggregator_url + "0014", // length + "68747470733A2F2F6578616D706C652E636F6D2F" // contents + ), + concat!( + // helper_aggregator_url + "001C", // length + "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents + ), + "0000000000000E10", // time_precision + "00002710", // min_batch_size + "01", // batch_mode + concat!( + // batch_config + "0000", // length + ), + "00000000000F4240", // task_start + "00000000000186A0", // task_duration + "00000001", // vdaf_type + concat!( + // vdaf_config + "0000", // length + ), + concat!( + // extensions + "0000", // length + ), + )) + .unwrap(), + ), + Err(CodecError::Other(_)) + ); } #[test] - fn roundtrip_vdaf_type() { + fn roundtrip_vdaf_config() { roundtrip_encoding(&[ ( - VdafType::Prio3Count, + VdafConfig::Reserved, concat!( - "00000000", // vdaf_type_code + "00000000", // vdaf_type + "0000", // vdaf_config length + "", // vdaf_config ), ), ( - VdafType::Prio3Sum { bits: u8::MIN }, + VdafConfig::Prio3Count, concat!( - "00000001", // vdaf_type_code - "00" // bits + "00000001", // vdaf_type + "0000", // vdaf_config length + "", // vdaf_config ), ), ( - VdafType::Prio3Sum { bits: 0x80 }, + VdafConfig::Prio3Sum { + max_measurement: u32::MIN, + }, concat!( - "00000001", // vdaf_type_code - "80" // bits + "00000002", // vdaf_type + "0004", // vdaf_config length + concat!( + // vdaf_config + "00000000", // max_measurement + ), ), ), ( - VdafType::Prio3Sum { bits: u8::MAX }, + VdafConfig::Prio3Sum { + max_measurement: 0xFF, + }, concat!( - "00000001", // vdaf_type_code - "FF", // bits + "00000002", // vdaf_type + "0004", // vdaf_config length + concat!( + // vdaf_config + "000000FF", // max_measurement + ), ), ), ( - VdafType::Prio3SumVec { - bits: 8, - length: 12, - chunk_length: 14, + VdafConfig::Prio3Sum { + max_measurement: u32::MAX, }, concat!( - "00000002", // vdaf_type_code - "0000000C", // length - "08", // bits - "0000000E" // chunk_length + "00000002", // vdaf_type + "0004", // vdaf_config length + concat!( + // vdaf_config + "FFFFFFFF", // max_measurement + ), ), ), ( - VdafType::Prio3SumVecField64MultiproofHmacSha256Aes128 { - bits: 8, + VdafConfig::Prio3SumVec { length: 12, + bits: 8, chunk_length: 14, - proofs: 2, }, concat!( - "FFFF1003", // vdaf_type_code - "0000000C", // length - "08", // bits - "0000000E", // chunk_length - "02" // proofs + "00000003", // vdaf_type + "0009", // vdaf_config length + concat!( + // vdaf_config + "0000000C", // length + "08", // bits + "0000000E" // chunk_length + ), ), ), ( - VdafType::Prio3Histogram { + VdafConfig::Prio3Histogram { length: 256, chunk_length: 18, }, concat!( - "00000003", // vdaf_type_code - "00000100", // length - "00000012", // chunk_length - ), - ), - ]) - } - - #[test] - fn roundtrip_vdaf_config() { - roundtrip_encoding(&[ - ( - VdafConfig::new(DpConfig::new(DpMechanism::None), VdafType::Prio3Count).unwrap(), - concat!( + "00000004", // vdaf_type + "0008", // vdaf_config length concat!( - // dp_config - "0001", // dp_config length - "01", // dp_mechanism - ), - concat!( - // vdaf_type - "00000000", // vdaf_type_code + // vdaf_config + "00000100", // length + "00000012", // chunk_length ), ), ), ( - VdafConfig::new( - DpConfig::new(DpMechanism::Unrecognized { - codepoint: 0xFF, - payload: Vec::from([0xDE, 0xAD, 0xBE, 0xEF]), - }), - VdafType::Prio3Count, - ) - .unwrap(), + VdafConfig::Prio3MultihotCountVec { + length: 256, + chunk_length: 18, + max_weight: 14, + }, concat!( - // dp_config + "00000005", // vdaf_type + "000C", // vdaf_config length concat!( - "0005", // dp_config length - "FF", // dp_mechanism - "DEADBEEF" // rest of unrecognized DpConfig + // vdaf_config + "00000100", // length + "00000012", // chunk_length + "0000000E", // max_weight ), - // vdaf_type - concat!( - "00000000" // vdaf_type_code - ) ), ), ( - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3Sum { bits: 0x42 }, - ) - .unwrap(), + VdafConfig::Poplar1 { bits: 32 }, concat!( + "00000006", // vdaf_type + "0002", // vdaf_config length concat!( - // dp_config - "0001", // dp_config length - "01", // dp_mechanism - ), - concat!( - // vdaf_type - "00000001", // vdaf_type_code - "42", // bits + // vdaf_config + "0020", // bits ), ), ), ( - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3SumVec { - length: 12, - bits: 8, - chunk_length: 14, - }, - ) - .unwrap(), + VdafConfig::Fake { rounds: 15 }, concat!( + "FFFF0000", // vdaf_type + "0004", // vdaf_config length concat!( - // dp_config - "0001", // dp_config length - "01", // dp_mechanism + // vdaf_config + "0000000F", // rounds ), - concat!( - // vdaf_type - "00000002", // vdaf_type_code - "0000000C", // length - "08", // bits - "0000000E", // chunk_length - ) ), ), ( - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3SumVecField64MultiproofHmacSha256Aes128 { - length: 12, - bits: 8, - chunk_length: 14, - proofs: 2, - }, - ) - .unwrap(), + VdafConfig::Prio3SumVecField64MultiproofHmacSha256Aes128 { + length: 12, + bits: 8, + chunk_length: 14, + proofs: 2, + }, concat!( + "FFFF1003", // vdaf_type + "000A", // vdaf_config length concat!( - // dp_config - "0001", // dp_config length - "01", // dp_mechanism - ), - concat!( - // vdaf_type - "FFFF1003", // vdaf_type_code + // vdaf_config "0000000C", // length "08", // bits "0000000E", // chunk_length "02" // proofs - ) - ), - ), - ( - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3Histogram { - length: 10, - chunk_length: 4, - }, - ) - .unwrap(), - concat!( - concat!( - // dp_config - "0001", //dp_config length - "01", // dp_mechanism - ), - concat!( - // vdaf_type - "00000003", // vdaf_type_code - "0000000A", // length - "00000004", // chunk_length ), ), ), - ]); - } - - #[test] - fn roundtrip_query_config() { - roundtrip_encoding(&[ - ( - QueryConfig::new(Duration::from_seconds(0x3C), 0x24, Query::TimeInterval), - concat!( - "000000000000003C", // time_precision - "00000024", // min_batch_size - "01", // batch_mode - ), - ), - ( - QueryConfig::new( - Duration::from_seconds(u64::MIN), - u32::MIN, - Query::LeaderSelected, - ), - concat!( - "0000000000000000", // time_precision - "00000000", // min_batch_size - "02", // batch_mode - ), - ), - ( - QueryConfig::new(Duration::from_seconds(0x3C), 0x24, Query::LeaderSelected), - concat!( - "000000000000003C", // time_precision - "00000024", // min_batch_size - "02", // batch_mode - ), - ), - ( - QueryConfig::new( - Duration::from_seconds(u64::MAX), - u32::MAX, - Query::LeaderSelected, - ), - concat!( - "FFFFFFFFFFFFFFFF", // time_precision - "FFFFFFFF", // min_batch_size - "02", // batch_mode - ), - ), ]) } #[test] - fn roundtrip_query() { + fn roundtrip_taskbind_extension() { roundtrip_encoding(&[ ( - Query::TimeInterval, + TaskbindExtension::new(TaskbindExtensionType::Reserved, Vec::new()), concat!( - "01", // batch_mode + "0000", // extension_type + "0000", // extension_data length + "", // extension_data ), ), ( - Query::LeaderSelected, + TaskbindExtension::new(TaskbindExtensionType::Reserved, Vec::from("0123")), concat!( - "02", // batch_mode + "0000", // extension_type + "0004", // extension_data length + "30313233", // extension_data ), ), - ]) + ]); } #[test] - fn roundtrip_task_config() { - roundtrip_encoding(&[ - ( - TaskConfig::new( - "foobar".as_bytes().to_vec(), - Url::try_from("https://example.com/".as_ref()).unwrap(), - Url::try_from("https://another.example.com/".as_ref()).unwrap(), - QueryConfig::new( - Duration::from_seconds(0xAAAA), - 0xCCCC, - Query::LeaderSelected, - ), - Time::from_seconds_since_epoch(0xEEEE), - VdafConfig::new(DpConfig::new(DpMechanism::None), VdafType::Prio3Count) - .unwrap(), - ) - .unwrap(), - concat!( - concat!( - // task_info - "06", // length - "666F6F626172" // opaque data - ), - concat!( - // leader_aggregator_url - "0014", // length - "68747470733A2F2F6578616D706C652E636F6D2F" // contents - ), - concat!( - // helper_aggregator_url - "001C", // length - "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents - ), - concat!( - // query_config - "000D", // query_config length - "000000000000AAAA", // time_precision - "0000CCCC", // min_batch_size - "02", // batch_mode - ), - "000000000000EEEE", // task_end - concat!( - // vdaf_config - "0007", // vdaf_config length - concat!( - // dp_config - "0001", // dp_config length - "01", // dp_config - ), - concat!( - // vdaf_type - "00000000", // vdaf_type_code - ), - ), - ), - ), - ( - TaskConfig::new( - "f".as_bytes().to_vec(), - Url::try_from("https://example.com/".as_ref()).unwrap(), - Url::try_from("https://another.example.com/".as_ref()).unwrap(), - QueryConfig::new(Duration::from_seconds(0xAAAA), 0xCCCC, Query::TimeInterval), - Time::from_seconds_since_epoch(0xEEEE), - VdafConfig::new( - DpConfig::new(DpMechanism::None), - VdafType::Prio3Histogram { - length: 10, - chunk_length: 4, - }, - ) - .unwrap(), - ) - .unwrap(), - concat!( - concat!( - // task_info - "01", // length - "66" // opaque data - ), - concat!( - // leader_aggregator_url - "0014", // length - "68747470733A2F2F6578616D706C652E636F6D2F" // contents - ), - concat!( - // helper_aggregator_url - "001C", // length - "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents - ), - concat!( - // query_config - "000D", // query_config length - "000000000000AAAA", // time_precision - "0000CCCC", // min_batch_size - "01", // batch_mode - ), - "000000000000EEEE", // task_end - concat!( - // vdaf_config - "000F", // vdaf_config length - concat!( - // dp_config - "0001", // dp_config length - "01", // dp_mechanism - ), - concat!( - // vdaf_type - "00000003", // vdaf_type_code - "0000000A", // length - "00000004", // chunk_length - ), - ), - ), - ), - ]); - - // Empty task_info. - assert_matches!( - TaskConfig::get_decoded( - &hex::decode(concat!( - concat!( - // task_info - "00", // length - ), - concat!( - // leader_aggregator_url - "0014", // length - "68747470733A2F2F6578616D706C652E636F6D2F" // contents - ), - concat!( - // helper_aggregator_url - "001C", // length - "68747470733A2F2F616E6F746865722E6578616D706C652E636F6D2F" // contents - ), - concat!( - // query_config - "000D", // query_config length - "000000000000AAAA", // time_precision - "0000CCCC", // min_batch_size - "01", // batch_mode - ), - "000000000000EEEE", // task_end - concat!( - // vdaf_config - "0007", // vdaf_config length - concat!( - // dp_config - "0001", // dp_config length - "01", // dp_config - ), - concat!( - // vdaf_type - "00000000", // vdaf_type_code - ), - ), - )) - .unwrap(), - ), - Err(CodecError::Other(_)) - ); + fn roundtrip_taskbind_extension_type() { + roundtrip_encoding(&[(TaskbindExtensionType::Reserved, "0000")]); } } diff --git a/messages/src/tests/upload.rs b/messages/src/tests/upload.rs index a0bfa782f..be2c0aabc 100644 --- a/messages/src/tests/upload.rs +++ b/messages/src/tests/upload.rs @@ -32,7 +32,7 @@ fn roundtrip_extension() { ), ), ( - Extension::new(ExtensionType::Taskprov, Vec::from("0123")), + Extension::new(ExtensionType::Taskbind, Vec::from("0123")), concat!( "FF00", // extension_type concat!( @@ -49,7 +49,7 @@ fn roundtrip_extension() { fn roundtrip_extension_type() { roundtrip_encoding(&[ (ExtensionType::Tbd, "0000"), - (ExtensionType::Taskprov, "FF00"), + (ExtensionType::Taskbind, "FF00"), ]) }