Repository: cloudflare/daphne
Pull request #518 (2024-03-01 18:38:07)

Remove bearer token serialization hacks


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index e3b4469..84da488 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -780,6 +780,7 @@ dependencies = [
  "hex",
  "http",
  "hyper",
+ "paste",
  "prio",
  "prometheus",
  "rand",
diff --git a/daphne/src/auth.rs b/daphne/src/auth.rs
index 91e114f..1b71d0d 100644
--- a/daphne/src/auth.rs
+++ b/daphne/src/auth.rs
@@ -15,6 +15,7 @@ use serde::{Deserialize, Serialize};
 /// A bearer token used for authorizing DAP requests.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 #[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
+#[serde(transparent)]
 pub struct BearerToken {
     raw: String,
 }
diff --git a/daphne/src/constants.rs b/daphne/src/constants.rs
index bab3d30..ebf5668 100644
--- a/daphne/src/constants.rs
+++ b/daphne/src/constants.rs
@@ -63,29 +63,41 @@ impl DapMediaType {
         let (content_type, _) = content_type.split_once(';').unwrap_or((content_type, ""));
         let media_type = match (version, content_type) {
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_CONT_REQ)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_CONT_REQ) => {
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_CONT_REQ) => {
                 Self::AggregationJobContinueReq
             }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_CONT_RESP) => {
                 Self::Draft02AggregateContinueResp
             }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_INIT_REQ)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_INIT_REQ) => Self::AggregationJobInitReq,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_INIT_REQ) => {
+                Self::AggregationJobInitReq
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_INIT_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_JOB_RESP) => Self::AggregationJobResp,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_JOB_RESP) => {
+                Self::AggregationJobResp
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_AGG_SHARE_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_AGG_SHARE) => Self::AggregateShare,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_AGG_SHARE) => {
+                Self::AggregateShare
+            }
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_COLLECT_RESP)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_COLLECTION) => Self::Collection,
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_COLLECTION) => Self::Collection,
             (DapVersion::Draft02, DRAFT02_MEDIA_TYPE_HPKE_CONFIG)
-            | (DapVersion::DraftLatest, MEDIA_TYPE_HPKE_CONFIG_LIST) => Self::HpkeConfigList,
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_AGG_SHARE_REQ) => {
-                Self::AggregateShareReq
+            | (DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_HPKE_CONFIG_LIST) => {
+                Self::HpkeConfigList
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_COLLECT_REQ) => {
-                Self::CollectReq
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                MEDIA_TYPE_AGG_SHARE_REQ,
+            ) => Self::AggregateShareReq,
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                MEDIA_TYPE_COLLECT_REQ,
+            ) => Self::CollectReq,
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, MEDIA_TYPE_REPORT) => {
+                Self::Report
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, MEDIA_TYPE_REPORT) => Self::Report,
             (_, _) => return None,
         };
         Some(media_type)
@@ -97,35 +109,44 @@ impl DapMediaType {
             (DapVersion::Draft02, Self::AggregationJobInitReq) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_INIT_REQ)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobInitReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobInitReq) => {
                 Some(MEDIA_TYPE_AGG_JOB_INIT_REQ)
             }
             (DapVersion::Draft02, Self::AggregationJobResp) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_INIT_RESP)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobResp) => Some(MEDIA_TYPE_AGG_JOB_RESP),
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobResp) => {
+                Some(MEDIA_TYPE_AGG_JOB_RESP)
+            }
             (DapVersion::Draft02, Self::AggregationJobContinueReq) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_CONT_REQ)
             }
-            (DapVersion::DraftLatest, Self::AggregationJobContinueReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregationJobContinueReq) => {
                 Some(MEDIA_TYPE_AGG_JOB_CONT_REQ)
             }
             (DapVersion::Draft02, Self::Draft02AggregateContinueResp) => {
                 Some(DRAFT02_MEDIA_TYPE_AGG_CONT_RESP)
             }
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::AggregateShareReq) => {
-                Some(MEDIA_TYPE_AGG_SHARE_REQ)
-            }
+            (
+                DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest,
+                Self::AggregateShareReq,
+            ) => Some(MEDIA_TYPE_AGG_SHARE_REQ),
             (DapVersion::Draft02, Self::AggregateShare) => Some(DRAFT02_MEDIA_TYPE_AGG_SHARE_RESP),
-            (DapVersion::DraftLatest, Self::AggregateShare) => Some(MEDIA_TYPE_AGG_SHARE),
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::CollectReq) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::AggregateShare) => {
+                Some(MEDIA_TYPE_AGG_SHARE)
+            }
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, Self::CollectReq) => {
                 Some(MEDIA_TYPE_COLLECT_REQ)
             }
             (DapVersion::Draft02, Self::Collection) => Some(DRAFT02_MEDIA_TYPE_COLLECT_RESP),
-            (DapVersion::DraftLatest, Self::Collection) => Some(MEDIA_TYPE_COLLECTION),
+            (DapVersion::Draft09 | DapVersion::Latest, Self::Collection) => {
+                Some(MEDIA_TYPE_COLLECTION)
+            }
             (DapVersion::Draft02, Self::HpkeConfigList) => Some(DRAFT02_MEDIA_TYPE_HPKE_CONFIG),
-            (DapVersion::DraftLatest, Self::HpkeConfigList) => Some(MEDIA_TYPE_HPKE_CONFIG_LIST),
-            (DapVersion::Draft02 | DapVersion::DraftLatest, Self::Report) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Self::HpkeConfigList) => {
+                Some(MEDIA_TYPE_HPKE_CONFIG_LIST)
+            }
+            (DapVersion::Draft02 | DapVersion::Draft09 | DapVersion::Latest, Self::Report) => {
                 Some(MEDIA_TYPE_REPORT)
             }
             (_, Self::Draft02AggregateContinueResp) => None,
@@ -137,7 +158,7 @@ impl DapMediaType {
     pub(crate) fn agg_job_cont_resp_for_version(version: DapVersion) -> Self {
         match version {
             DapVersion::Draft02 => Self::Draft02AggregateContinueResp,
-            DapVersion::DraftLatest => Self::AggregationJobResp,
+            DapVersion::Draft09 | DapVersion::Latest => Self::AggregationJobResp,
         }
     }
 }
@@ -207,64 +228,58 @@ mod test {
 
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-hpke-config-list"
+                DapVersion::Draft09,
+                "application/dap-hpke-config-list",
             ),
             Some(DapMediaType::HpkeConfigList)
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-init-req"
             ),
             Some(DapMediaType::AggregationJobInitReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-resp"
             ),
             Some(DapMediaType::AggregationJobResp),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-continue-req"
             ),
             Some(DapMediaType::AggregationJobContinueReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregate-share-req"
             ),
             Some(DapMediaType::AggregateShareReq),
         );
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregate-share"
             ),
             Some(DapMediaType::AggregateShare),
         );
         assert_eq!(
-            DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-collect-req"
-            ),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "application/dap-collect-req"),
             Some(DapMediaType::CollectReq),
         );
         assert_eq!(
-            DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
-                "application/dap-collection"
-            ),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "application/dap-collection"),
             Some(DapMediaType::Collection),
         );
 
         // Invalid media type
         assert_eq!(
-            DapMediaType::from_str_for_version(DapVersion::DraftLatest, "blah-blah-blah"),
+            DapMediaType::from_str_for_version(DapVersion::Draft09, "blah-blah-blah"),
             None,
         );
     }
@@ -273,30 +288,27 @@ mod test {
     fn round_trip() {
         for (version, media_type) in [
             (DapVersion::Draft02, DapMediaType::AggregationJobInitReq),
-            (DapVersion::DraftLatest, DapMediaType::AggregationJobInitReq),
+            (DapVersion::Draft09, DapMediaType::AggregationJobInitReq),
             (DapVersion::Draft02, DapMediaType::AggregationJobResp),
-            (DapVersion::DraftLatest, DapMediaType::AggregationJobResp),
+            (DapVersion::Draft09, DapMediaType::AggregationJobResp),
             (DapVersion::Draft02, DapMediaType::AggregationJobContinueReq),
-            (
-                DapVersion::DraftLatest,
-                DapMediaType::AggregationJobContinueReq,
-            ),
+            (DapVersion::Draft09, DapMediaType::AggregationJobContinueReq),
             (
                 DapVersion::Draft02,
                 DapMediaType::Draft02AggregateContinueResp,
             ),
             (DapVersion::Draft02, DapMediaType::AggregateShareReq),
-            (DapVersion::DraftLatest, DapMediaType::AggregateShareReq),
+            (DapVersion::Draft09, DapMediaType::AggregateShareReq),
             (DapVersion::Draft02, DapMediaType::AggregateShare),
-            (DapVersion::DraftLatest, DapMediaType::AggregateShare),
+            (DapVersion::Draft09, DapMediaType::AggregateShare),
             (DapVersion::Draft02, DapMediaType::CollectReq),
-            (DapVersion::DraftLatest, DapMediaType::CollectReq),
+            (DapVersion::Draft09, DapMediaType::CollectReq),
             (DapVersion::Draft02, DapMediaType::Collection),
-            (DapVersion::DraftLatest, DapMediaType::Collection),
+            (DapVersion::Draft09, DapMediaType::Collection),
             (DapVersion::Draft02, DapMediaType::HpkeConfigList),
-            (DapVersion::DraftLatest, DapMediaType::HpkeConfigList),
+            (DapVersion::Draft09, DapMediaType::HpkeConfigList),
             (DapVersion::Draft02, DapMediaType::Report),
-            (DapVersion::DraftLatest, DapMediaType::Report),
+            (DapVersion::Draft09, DapMediaType::Report),
         ] {
             assert_eq!(
                 media_type
@@ -319,7 +331,7 @@ mod test {
 
         assert_eq!(
             DapMediaType::AggregationJobResp,
-            DapMediaType::agg_job_cont_resp_for_version(DapVersion::DraftLatest)
+            DapMediaType::agg_job_cont_resp_for_version(DapVersion::Draft09)
         );
     }
 
@@ -327,7 +339,7 @@ mod test {
     fn media_type_parsing_ignores_content_type_paramters() {
         assert_eq!(
             DapMediaType::from_str_for_version(
-                DapVersion::DraftLatest,
+                DapVersion::Draft09,
                 "application/dap-aggregation-job-init-req;version=09",
             ),
             Some(DapMediaType::AggregationJobInitReq),
diff --git a/daphne/src/lib.rs b/daphne/src/lib.rs
index b2bfe94..8f9916d 100644
--- a/daphne/src/lib.rs
+++ b/daphne/src/lib.rs
@@ -101,7 +101,10 @@ pub enum DapVersion {
 
     #[serde(rename = "v09")]
     #[default]
-    DraftLatest,
+    Draft09,
+
+    #[serde(rename = "v10")]
+    Latest,
 }
 
 impl FromStr for DapVersion {
@@ -109,7 +112,8 @@ impl FromStr for DapVersion {
     fn from_str(version: &str) -> Result<Self, Self::Err> {
         match version {
             "v02" => Ok(DapVersion::Draft02),
-            "v09" => Ok(DapVersion::DraftLatest),
+            "v09" => Ok(DapVersion::Draft09),
+            "v10" => Ok(DapVersion::Latest),
             _ => Err(DapAbort::version_unknown()),
         }
     }
@@ -119,7 +123,8 @@ impl AsRef<str> for DapVersion {
     fn as_ref(&self) -> &str {
         match self {
             DapVersion::Draft02 => "v02",
-            DapVersion::DraftLatest => "v09",
+            DapVersion::Draft09 => "v09",
+            DapVersion::Latest => "v10",
         }
     }
 }
@@ -493,7 +498,9 @@ impl DapTaskParameters {
         .unwrap();
 
         let (taskprov_advertisement, taskprov_report_extension_payload) = match self.version {
-            DapVersion::DraftLatest => (Some(encode_base64url(&encoded_taskprov_config)), None),
+            DapVersion::Draft09 | DapVersion::Latest => {
+                (Some(encode_base64url(&encoded_taskprov_config)), None)
+            }
             // draft02 compatibility: The taskprov config is advertised in an HTTP header in
             // the latest draft. In draft02, it is carried by a report extension.
             DapVersion::Draft02 => (None, Some(encoded_taskprov_config)),
@@ -1111,7 +1118,7 @@ pub struct DapRequest<S> {
 impl<S> Default for DapRequest<S> {
     fn default() -> Self {
         Self {
-            version: DapVersion::DraftLatest,
+            version: DapVersion::Draft09,
             media_type: None,
             task_id: Default::default(),
             resource: Default::default(),
@@ -1204,7 +1211,7 @@ pub struct DapLeaderProcessTelemetry {
 #[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
 pub enum MetaAggregationJobId {
     Draft02(Draft02AggregationJobId),
-    DraftLatest(AggregationJobId),
+    Draft09(AggregationJobId),
 }
 
 macro_rules! generate_from_id_impls_for_meta_agg_job_id {
@@ -1224,7 +1231,7 @@ macro_rules! generate_from_id_impls_for_meta_agg_job_id {
 }
 
 generate_from_id_impls_for_meta_agg_job_id!(Draft02 => Draft02AggregationJobId);
-generate_from_id_impls_for_meta_agg_job_id!(DraftLatest => AggregationJobId);
+generate_from_id_impls_for_meta_agg_job_id!(Draft09 => AggregationJobId);
 
 impl MetaAggregationJobId {
     /// Generate a random ID of the type required for the version.
@@ -1232,7 +1239,7 @@ impl MetaAggregationJobId {
         let mut rng = thread_rng();
         match version {
             DapVersion::Draft02 => Self::Draft02(Draft02AggregationJobId(rng.gen())),
-            DapVersion::DraftLatest => Self::DraftLatest(AggregationJobId(rng.gen())),
+            DapVersion::Draft09 | DapVersion::Latest => Self::Draft09(AggregationJobId(rng.gen())),
         }
     }
 
@@ -1241,7 +1248,7 @@ impl MetaAggregationJobId {
     pub(crate) fn for_request_payload(&self) -> Option<Draft02AggregationJobId> {
         match self {
             Self::Draft02(agg_job_id) => Some(*agg_job_id),
-            Self::DraftLatest(..) => None,
+            Self::Draft09(..) => None,
         }
     }
 
@@ -1251,7 +1258,7 @@ impl MetaAggregationJobId {
         match self {
             // In draft02, the aggregation job ID is not determined until the payload is parsed.
             Self::Draft02(..) => DapResource::Undefined,
-            Self::DraftLatest(agg_job_id) => DapResource::AggregationJob(*agg_job_id),
+            Self::Draft09(agg_job_id) => DapResource::AggregationJob(*agg_job_id),
         }
     }
 
@@ -1259,7 +1266,7 @@ impl MetaAggregationJobId {
     pub fn to_hex(&self) -> String {
         match self {
             Self::Draft02(agg_job_id) => agg_job_id.to_hex(),
-            Self::DraftLatest(agg_job_id) => agg_job_id.to_hex(),
+            Self::Draft09(agg_job_id) => agg_job_id.to_hex(),
         }
     }
 
@@ -1267,7 +1274,7 @@ impl MetaAggregationJobId {
     pub fn to_base64url(&self) -> String {
         match self {
             Self::Draft02(agg_job_id) => agg_job_id.to_base64url(),
-            Self::DraftLatest(agg_job_id) => agg_job_id.to_base64url(),
+            Self::Draft09(agg_job_id) => agg_job_id.to_base64url(),
         }
     }
 }
diff --git a/daphne/src/messages/mod.rs b/daphne/src/messages/mod.rs
index d049690..4bae849 100644
--- a/daphne/src/messages/mod.rs
+++ b/daphne/src/messages/mod.rs
@@ -173,7 +173,7 @@ impl TaskId {
     pub fn for_request_payload(&self, version: &DapVersion) -> Option<TaskId> {
         match version {
             DapVersion::Draft02 => Some(*self),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         }
     }
 }
@@ -223,11 +223,15 @@ impl ParameterizedEncode<DapVersion> for Extension {
             Self::Taskprov { draft02_payload } => {
                 EXTENSION_TASKPROV.encode(bytes)?;
                 match (version, draft02_payload) {
-                    (DapVersion::DraftLatest, None) => {
+                    (DapVersion::Draft09 | DapVersion::Latest, None) => {
                         encode_u16_prefixed(*version, bytes, |_, _| Ok(()))?;
                     }
                     (DapVersion::Draft02, Some(payload)) => encode_u16_bytes(bytes, payload)?,
-                    _ => unreachable!("unhandled version {version:?}"),
+                    _ => {
+                        return Err(CodecError::Other(
+                            format!("unhandled version: {version:?}").into(),
+                        ));
+                    }
                 }
             }
             Self::NotImplemented { typ, payload } => {
@@ -246,7 +250,7 @@ impl ParameterizedDecode<DapVersion> for Extension {
     ) -> Result<Self, CodecError> {
         let typ = u16::decode(bytes)?;
         match (version, typ) {
-            (DapVersion::DraftLatest, EXTENSION_TASKPROV) => {
+            (DapVersion::Draft09 | DapVersion::Latest, EXTENSION_TASKPROV) => {
                 decode_u16_prefixed(*version, bytes, |_version, inner, _len| <()>::decode(inner))?;
                 Ok(Self::Taskprov {
                     draft02_payload: None,
@@ -283,11 +287,15 @@ impl ParameterizedEncode<DapVersion> for ReportMetadata {
         self.id.encode(bytes)?;
         self.time.encode(bytes)?;
         match (version, &self.draft02_extensions) {
-            (DapVersion::DraftLatest, None) => (),
+            (DapVersion::Draft09 | DapVersion::Latest, None) => (),
             (DapVersion::Draft02, Some(extensions)) => {
                 encode_u16_items(bytes, version, extensions)?;
             }
-            _ => unreachable!("extensions should be set in (and only in) draft02"),
+            _ => {
+                return Err(CodecError::Other(
+                    "extensions should be set in (and only in) draft02".into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -303,7 +311,7 @@ impl ParameterizedDecode<DapVersion> for ReportMetadata {
             time: Time::decode(bytes)?,
             draft02_extensions: match version {
                 DapVersion::Draft02 => Some(decode_u16_items(version, bytes)?),
-                DapVersion::DraftLatest => None,
+                DapVersion::Draft09 | DapVersion::Latest => None,
             },
         };
 
@@ -338,7 +346,7 @@ impl ParameterizedEncode<DapVersion> for Report {
         encode_u32_bytes(bytes, &self.public_share)?;
         match version {
             DapVersion::Draft02 => encode_u32_items(bytes, &(), &self.encrypted_input_shares)?,
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.encrypted_input_shares[0].encode(bytes)?;
                 self.encrypted_input_shares[1].encode(bytes)?;
             }
@@ -365,7 +373,7 @@ impl ParameterizedDecode<DapVersion> for Report {
                 DapVersion::Draft02 => decode_u32_items(&(), bytes)?
                     .try_into()
                     .map_err(|_| CodecError::UnexpectedValue)?,
-                DapVersion::DraftLatest => [
+                DapVersion::Draft09 | DapVersion::Latest => [
                     HpkeCiphertext::decode(bytes)?,
                     HpkeCiphertext::decode(bytes)?,
                 ],
@@ -526,7 +534,7 @@ impl Default for BatchSelector {
 #[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
 pub struct PrepareInit {
     pub report_share: ReportShare,
-    pub draft_latest_payload: Option<Vec<u8>>,
+    pub draft09_payload: Option<Vec<u8>>,
 }
 
 impl ParameterizedEncode<DapVersion> for PrepareInit {
@@ -536,12 +544,16 @@ impl ParameterizedEncode<DapVersion> for PrepareInit {
         bytes: &mut Vec<u8>,
     ) -> Result<(), CodecError> {
         self.report_share.encode_with_param(version, bytes)?;
-        match (version, &self.draft_latest_payload) {
+        match (version, &self.draft09_payload) {
             (DapVersion::Draft02, None) => (),
-            (DapVersion::DraftLatest, Some(payload)) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(payload)) => {
                 encode_u32_bytes(bytes, payload)?;
             }
-            _ => unreachable!("unhandled version {version:?}"),
+            _ => {
+                return Err(CodecError::Other(
+                    format!("unhandled version: {version:?}").into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -553,14 +565,14 @@ impl ParameterizedDecode<DapVersion> for PrepareInit {
         bytes: &mut Cursor<&[u8]>,
     ) -> Result<Self, CodecError> {
         let report_share = ReportShare::decode_with_param(version, bytes)?;
-        let draft_latest_payload = match version {
+        let draft09_payload = match version {
             DapVersion::Draft02 => None,
-            DapVersion::DraftLatest => Some(decode_u32_bytes(bytes)?),
+            DapVersion::Draft09 | DapVersion::Latest => Some(decode_u32_bytes(bytes)?),
         };
 
         Ok(Self {
             report_share,
-            draft_latest_payload,
+            draft09_payload,
         })
     }
 }
@@ -593,7 +605,7 @@ impl ParameterizedEncode<DapVersion> for AggregationJobInitReq {
                     .encode(bytes)?;
                 encode_u16_bytes(bytes, &self.agg_param)?;
             }
-            DapVersion::DraftLatest => encode_u32_bytes(bytes, &self.agg_param)?,
+            DapVersion::Draft09 | DapVersion::Latest => encode_u32_bytes(bytes, &self.agg_param)?,
         };
         self.part_batch_sel.encode(bytes)?;
         encode_u32_items(bytes, version, &self.prep_inits)?;
@@ -612,7 +624,7 @@ impl ParameterizedDecode<DapVersion> for AggregationJobInitReq {
                 Some(Draft02AggregationJobId::decode(bytes)?),
                 decode_u16_bytes(bytes)?,
             ),
-            DapVersion::DraftLatest => (None, None, decode_u32_bytes(bytes)?),
+            DapVersion::Draft09 | DapVersion::Latest => (None, None, decode_u32_bytes(bytes)?),
         };
 
         Ok(Self {
@@ -651,7 +663,7 @@ impl ParameterizedEncode<DapVersion> for AggregationJobContinueReq {
                     .ok_or_else(|| CodecError::Other("draft02: missing task ID".into()))?
                     .encode(bytes)?;
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.round
                     .as_ref()
                     .ok_or_else(|| CodecError::Other("missing round".into()))?
@@ -674,7 +686,7 @@ impl ParameterizedDecode<DapVersion> for AggregationJobContinueReq {
                 Some(Draft02AggregationJobId::decode(bytes)?),
                 None,
             ),
-            DapVersion::DraftLatest => (None, None, Some(u16::decode(bytes)?)),
+            DapVersion::Draft09 | DapVersion::Latest => (None, None, Some(u16::decode(bytes)?)),
         };
         Ok(Self {
             draft02_task_id,
@@ -1005,12 +1017,12 @@ impl ParameterizedEncode<DapVersion> for CollectionReq {
                     .ok_or_else(|| CodecError::Other("draft02: missing task ID".into()))?
                     .encode(bytes)?;
             }
-            DapVersion::DraftLatest => {}
+            DapVersion::Draft09 | DapVersion::Latest => {}
         }
         self.query.encode_with_param(version, bytes)?;
         match version {
             DapVersion::Draft02 => encode_u16_bytes(bytes, &self.agg_param)?,
-            DapVersion::DraftLatest => encode_u32_bytes(bytes, &self.agg_param)?,
+            DapVersion::Draft09 | DapVersion::Latest => encode_u32_bytes(bytes, &self.agg_param)?,
         };
         Ok(())
     }
@@ -1023,14 +1035,14 @@ impl ParameterizedDecode<DapVersion> for CollectionReq {
     ) -> Result<Self, CodecError> {
         let draft02_task_id = match version {
             DapVersion::Draft02 => Some(TaskId::decode(bytes)?),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         };
         Ok(Self {
             draft02_task_id,
             query: Query::decode_with_param(version, bytes)?,
             agg_param: match version {
                 DapVersion::Draft02 => decode_u16_bytes(bytes)?,
-                DapVersion::DraftLatest => decode_u32_bytes(bytes)?,
+                DapVersion::Draft09 | DapVersion::Latest => decode_u32_bytes(bytes)?,
             },
         })
     }
@@ -1044,7 +1056,7 @@ impl ParameterizedDecode<DapVersion> for CollectionReq {
 pub struct Collection {
     pub part_batch_sel: PartialBatchSelector,
     pub report_count: u64,
-    pub draft_latest_interval: Option<Interval>, // Not set in draft02
+    pub draft09_interval: Option<Interval>, // Not set in draft02
     pub encrypted_agg_shares: [HpkeCiphertext; 2],
 }
 
@@ -1056,16 +1068,20 @@ impl ParameterizedEncode<DapVersion> for Collection {
     ) -> Result<(), CodecError> {
         self.part_batch_sel.encode(bytes)?;
         self.report_count.encode(bytes)?;
-        match (version, &self.draft_latest_interval) {
+        match (version, &self.draft09_interval) {
             (DapVersion::Draft02, None) => {
                 encode_u32_items(bytes, &(), &self.encrypted_agg_shares)?;
             }
-            (DapVersion::DraftLatest, Some(interval)) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(interval)) => {
                 interval.encode(bytes)?;
                 self.encrypted_agg_shares[0].encode(bytes)?;
                 self.encrypted_agg_shares[1].encode(bytes)?;
             }
-            _ => unreachable!("unhandled variant for version {version:?}"),
+            _ => {
+                return Err(CodecError::Other(
+                    format!("unhandled variant for version {version:?}").into(),
+                ))
+            }
         };
         Ok(())
     }
@@ -1078,14 +1094,14 @@ impl ParameterizedDecode<DapVersion> for Collection {
     ) -> Result<Self, CodecError> {
         let part_batch_sel = PartialBatchSelector::decode(bytes)?;
         let report_count = u64::decode(bytes)?;
-        let (draft_latest_interval, encrypted_agg_shares) = match version {
+        let (draft09_interval, encrypted_agg_shares) = match version {
             DapVersion::Draft02 => (
                 None,
                 decode_u32_items(&(), bytes)?
                     .try_into()
                     .map_err(|_| CodecError::UnexpectedValue)?,
             ),
-            DapVersion::DraftLatest => (
+            DapVersion::Draft09 | DapVersion::Latest => (
                 Some(Interval::decode(bytes)?),
                 [
                     HpkeCiphertext::decode(bytes)?,
@@ -1097,7 +1113,7 @@ impl ParameterizedDecode<DapVersion> for Collection {
         Ok(Self {
             part_batch_sel,
             report_count,
-            draft_latest_interval,
+            draft09_interval,
             encrypted_agg_shares,
         })
     }
@@ -1130,7 +1146,7 @@ impl ParameterizedEncode<DapVersion> for AggregateShareReq {
                 self.batch_sel.encode_with_param(version, bytes)?;
                 encode_u16_bytes(bytes, &self.agg_param)?;
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.batch_sel.encode_with_param(version, bytes)?;
                 encode_u32_bytes(bytes, &self.agg_param)?;
             }
@@ -1152,7 +1168,7 @@ impl ParameterizedDecode<DapVersion> for AggregateShareReq {
                 BatchSelector::decode_with_param(version, bytes)?,
                 decode_u16_bytes(bytes)?,
             ),
-            DapVersion::DraftLatest => (
+            DapVersion::Draft09 | DapVersion::Latest => (
                 None,
                 BatchSelector::decode_with_param(version, bytes)?,
                 decode_u32_bytes(bytes)?,
@@ -1481,7 +1497,7 @@ mod test {
                 time: 1_637_364_244,
                 draft02_extensions: match version {
                     DapVersion::Draft02 => Some(Vec::new()),
-                    DapVersion::DraftLatest => None,
+                    DapVersion::Draft09 | DapVersion::Latest => None,
                 },
             },
             public_share: b"public share".to_vec(),
@@ -1554,7 +1570,7 @@ mod test {
                                 payload: b"ciphertext".to_vec(),
                             },
                         },
-                        draft_latest_payload: None,
+                        draft09_payload: None,
                     },
                     PrepareInit {
                         report_share: ReportShare {
@@ -1570,7 +1586,7 @@ mod test {
                                 payload: b"ciphertext".to_vec(),
                             },
                         },
-                        draft_latest_payload: None,
+                        draft09_payload: None,
                     },
                 ],
             },
@@ -1601,7 +1617,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: None,
+                    draft09_payload: None,
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -1617,7 +1633,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: None,
+                    draft09_payload: None,
                 },
             ],
         };
@@ -1651,7 +1667,7 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: Some(b"prep share".to_vec()),
+                    draft09_payload: Some(b"prep share".to_vec()),
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -1667,16 +1683,14 @@ mod test {
                             payload: b"ciphertext".to_vec(),
                         },
                     },
-                    draft_latest_payload: Some(b"prep share".to_vec()),
+                    draft09_payload: Some(b"prep share".to_vec()),
                 },
             ],
         };
 
         let got = AggregationJobInitReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
@@ -1740,10 +1754,8 @@ mod test {
         };
 
         let got = AggregationJobContinueReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
@@ -1815,10 +1827,8 @@ mod test {
             checksum: [0; 32],
         };
         let got = AggregateShareReq::get_decoded_with_param(
-            &DapVersion::DraftLatest,
-            &want
-                .get_encoded_with_param(&DapVersion::DraftLatest)
-                .unwrap(),
+            &DapVersion::Draft09,
+            &want.get_encoded_with_param(&DapVersion::Draft09).unwrap(),
         )
         .unwrap();
         assert_eq!(got, want);
diff --git a/daphne/src/messages/taskprov.rs b/daphne/src/messages/taskprov.rs
index d011041..ab7b8bc 100644
--- a/daphne/src/messages/taskprov.rs
+++ b/daphne/src/messages/taskprov.rs
@@ -92,7 +92,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for VdafTypeVar {
                     num_proofs: u8::decode(bytes)?,
                 })
             }
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 4];
                 bytes.read_exact(&mut param)?;
                 Ok(Self::NotImplemented {
@@ -102,7 +102,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for VdafTypeVar {
             }
             // draft02 compatibility: We don't recognize the VDAF type, which means the rest of
             // this message is not decodable. We must abort.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 Err(CodecError::UnexpectedValue)
             }
         }
@@ -140,7 +140,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for DpConfig {
         let dp_mechanism = u8::decode(bytes)?;
         match (version, bytes_left, dp_mechanism) {
             (.., DP_MECHANISM_NONE) => Ok(Self::None),
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 1];
                 bytes.read_exact(&mut param)?;
                 Ok(Self::NotImplemented {
@@ -150,7 +150,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for DpConfig {
             }
             // draft02 compatibility: We must abort because unimplemented DP mechansims can't be
             // decoded.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 Err(CodecError::UnexpectedValue)
             }
         }
@@ -268,7 +268,7 @@ impl ParameterizedEncode<DapVersion> for QueryConfig {
         self.time_precision.encode(bytes)?;
         self.max_batch_query_count.encode(bytes)?;
         self.min_batch_size.encode(bytes)?;
-        if *version == DapVersion::DraftLatest {
+        if matches!(version, DapVersion::Draft09 | DapVersion::Latest) {
             self.encode_query_type(bytes)?;
         }
         match &self.var {
@@ -290,7 +290,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
         bytes: &mut Cursor<&[u8]>,
     ) -> Result<Self, CodecError> {
         let query_type = match version {
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
             DapVersion::Draft02 => Some(Ok(u8::decode(bytes)?)),
         };
         let time_precision = Duration::decode(bytes)?;
@@ -302,7 +302,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
             (.., QUERY_TYPE_FIXED_SIZE) => QueryConfigVar::FixedSize {
                 max_batch_size: u32::decode(bytes)?,
             },
-            (DapVersion::DraftLatest, Some(bytes_left), ..) => {
+            (DapVersion::Draft09 | DapVersion::Latest, Some(bytes_left), ..) => {
                 let mut param = vec![0; bytes_left - 15];
                 bytes.read_exact(&mut param)?;
 
@@ -313,7 +313,7 @@ impl ParameterizedDecode<(DapVersion, Option<usize>)> for QueryConfig {
             }
             // draft02 compatibility: We must abort because unimplemented query configurations
             // can't be decoded.
-            (DapVersion::Draft02, ..) | (DapVersion::DraftLatest, None, ..) => {
+            (DapVersion::Draft02, ..) | (DapVersion::Draft09 | DapVersion::Latest, None, ..) => {
                 return Err(CodecError::UnexpectedValue)
             }
         };
@@ -351,7 +351,7 @@ impl ParameterizedEncode<DapVersion> for TaskConfig {
                 &(),
                 &[self.leader_url.clone(), self.helper_url.clone()],
             )?,
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 self.leader_url.encode(bytes)?;
                 self.helper_url.encode(bytes)?;
             }
@@ -377,7 +377,9 @@ impl ParameterizedDecode<DapVersion> for TaskConfig {
             DapVersion::Draft02 => decode_u16_items(&(), bytes)?
                 .try_into()
                 .map_err(|_| CodecError::UnexpectedValue)?, // Expect exactly two Aggregator endpoints.
-            DapVersion::DraftLatest => [UrlBytes::decode(bytes)?, UrlBytes::decode(bytes)?],
+            DapVersion::Draft09 | DapVersion::Latest => {
+                [UrlBytes::decode(bytes)?, UrlBytes::decode(bytes)?]
+            }
         };
         let query_config = taskprov_decode_u16_prefixed(*version, bytes, |version, inner, len| {
             // We need to know the length of the `QueryConfig` in order to decode variants we don't
@@ -414,7 +416,7 @@ fn taskprov_encode_u16_prefixed(
     e: impl Fn(DapVersion, &mut Vec<u8>) -> Result<(), CodecError>,
 ) -> Result<(), CodecError> {
     match version {
-        DapVersion::DraftLatest => encode_u16_prefixed(version, bytes, e),
+        DapVersion::Draft09 | DapVersion::Latest => encode_u16_prefixed(version, bytes, e),
         // draft02 compatibility: No length prefix is used.
         DapVersion::Draft02 => e(version, bytes),
     }
@@ -426,7 +428,7 @@ fn taskprov_decode_u16_prefixed<O>(
     d: impl Fn(DapVersion, &mut Cursor<&[u8]>, Option<usize>) -> Result<O, CodecError>,
 ) -> Result<O, CodecError> {
     match version {
-        DapVersion::DraftLatest => decode_u16_prefixed(version, bytes, d),
+        DapVersion::Draft09 | DapVersion::Latest => decode_u16_prefixed(version, bytes, d),
         // draft02 compatibility: No length prefix is used.
         DapVersion::Draft02 => d(version, bytes, None),
     }
@@ -519,12 +521,12 @@ mod tests {
             },
         };
         let encoded = query_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             QueryConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded,
             )
             .unwrap(),
@@ -573,12 +575,12 @@ mod tests {
             param: b"dp mechanism param".to_vec(),
         };
         let encoded = dp_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             DpConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded,
             )
             .unwrap(),
@@ -643,7 +645,7 @@ mod tests {
     test_versions! { roundtrip_vdaf_config_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128 }
 
     #[test]
-    fn roundtrip_vdaf_config_not_implemented_draftlatest() {
+    fn roundtrip_vdaf_config_not_implemented_draft09() {
         let vdaf_config = VdafConfig {
             dp_config: DpConfig::None,
             var: VdafTypeVar::NotImplemented {
@@ -652,12 +654,12 @@ mod tests {
             },
         };
         let encoded = vdaf_config
-            .get_encoded_with_param(&DapVersion::DraftLatest)
+            .get_encoded_with_param(&DapVersion::Draft09)
             .unwrap();
 
         assert_eq!(
             VdafConfig::get_decoded_with_param(
-                &(DapVersion::DraftLatest, Some(encoded.len())),
+                &(DapVersion::Draft09, Some(encoded.len())),
                 &encoded
             )
             .unwrap(),
diff --git a/daphne/src/protocol/aggregator.rs b/daphne/src/protocol/aggregator.rs
index fe1d8ad..54697d0 100644
--- a/daphne/src/protocol/aggregator.rs
+++ b/daphne/src/protocol/aggregator.rs
@@ -35,9 +35,8 @@ use std::{
 };
 
 use super::{
-    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT_LATEST, CTX_INPUT_SHARE_DRAFT02,
-    CTX_INPUT_SHARE_DRAFT_LATEST, CTX_ROLE_CLIENT, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
-    CTX_ROLE_LEADER,
+    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT09, CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT09,
+    CTX_ROLE_CLIENT, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER, CTX_ROLE_LEADER,
 };
 
 // Ping-pong message framing as defined in draft-irtf-cfrg-vdaf-08, Section 5.8. We do not
@@ -126,7 +125,7 @@ impl EarlyReportStateConsumed {
 
         let input_share_text = match task_config.version {
             DapVersion::Draft02 => CTX_INPUT_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_INPUT_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_INPUT_SHARE_DRAFT09,
         };
         let n: usize = input_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
@@ -162,9 +161,9 @@ impl EarlyReportStateConsumed {
 
         // draft02 compatibility: The plaintext is passed to the VDAF directly. In the latest
         // draft, the plaintext also encodes the report extensions.
-        let (input_share, draft_latest_extensions) = match task_config.version {
+        let (input_share, draft09_extensions) = match task_config.version {
             DapVersion::Draft02 => (encoded_input_share, None),
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 match PlaintextInputShare::get_decoded_with_param(
                     &task_config.version,
                     &encoded_input_share,
@@ -183,7 +182,7 @@ impl EarlyReportStateConsumed {
         // Handle report extensions.
         {
             let extensions = match task_config.version {
-                DapVersion::DraftLatest => draft_latest_extensions.as_ref().unwrap(),
+                DapVersion::Draft09 | DapVersion::Latest => draft09_extensions.as_ref().unwrap(),
                 DapVersion::Draft02 => state.metadata.draft02_extensions.as_ref().unwrap(),
             };
 
@@ -204,7 +203,7 @@ impl EarlyReportStateConsumed {
                     }
 
                     // Reject reports with unrecognized extensions.
-                    (DapVersion::DraftLatest, ..) => {
+                    (DapVersion::Draft09 | DapVersion::Latest, ..) => {
                         return Ok(Self::Rejected {
                             metadata: state.metadata,
                             failure: TransitionFailure::InvalidMessage,
@@ -460,9 +459,9 @@ impl DapTaskConfig {
                 } => {
                     // draft02 compatibility: In the latest version, the Leader sends the Helper
                     // its initial prep share in the first request.
-                    let (draft02_prep_share, draft_latest_payload) = match self.version {
+                    let (draft02_prep_share, draft09_payload) = match self.version {
                         DapVersion::Draft02 => (Some(prep_share), None),
-                        DapVersion::DraftLatest => {
+                        DapVersion::Draft09 | DapVersion::Latest => {
                             let mut outbound = Vec::with_capacity(
                                 prep_share
                                     .encoded_len_with_param(&self.version)
@@ -490,7 +489,7 @@ impl DapTaskConfig {
                             public_share: state.public_share,
                             encrypted_input_share: helper_share,
                         },
-                        draft_latest_payload,
+                        draft09_payload,
                     });
                 }
 
@@ -555,7 +554,7 @@ impl DapTaskConfig {
                     ReportState {
                         metadata: prep_init.report_share.report_metadata,
                         public_share: prep_init.report_share.public_share,
-                        draft_latest_prep_init_payload: prep_init.draft_latest_payload,
+                        draft_latest_prep_init_payload: prep_init.draft09_payload,
                     },
                     prep_init.report_share.encrypted_input_share,
                 )
@@ -599,7 +598,7 @@ impl DapTaskConfig {
                 initialized_reports,
                 metrics,
             )),
-            DapVersion::DraftLatest => self.draft_latest_handle_agg_job_init_req(
+            DapVersion::Draft09 | DapVersion::Latest => self.draft09_handle_agg_job_init_req(
                 task_id,
                 report_status,
                 part_batch_sel,
@@ -665,7 +664,7 @@ impl DapTaskConfig {
         )
     }
 
-    fn draft_latest_handle_agg_job_init_req(
+    fn draft09_handle_agg_job_init_req(
         &self,
         task_id: &TaskId,
         report_status: &HashMap<ReportId, ReportProcessedStatus>,
@@ -789,8 +788,8 @@ impl DapTaskConfig {
             DapVersion::Draft02 => self
                 .draft02_handle_agg_job_resp(task_id, agg_job_id, state, agg_job_resp, metrics)
                 .map_err(Into::into),
-            DapVersion::DraftLatest => {
-                self.draft_latest_handle_agg_job_resp(task_id, state, agg_job_resp, metrics)
+            DapVersion::Draft09 | DapVersion::Latest => {
+                self.draft09_handle_agg_job_resp(task_id, state, agg_job_resp, metrics)
             }
         }
     }
@@ -916,7 +915,7 @@ impl DapTaskConfig {
         ))
     }
 
-    fn draft_latest_handle_agg_job_resp(
+    fn draft09_handle_agg_job_resp(
         &self,
         task_id: &TaskId,
         state: DapAggregationJobState,
@@ -1299,7 +1298,7 @@ fn produce_encrypted_agg_share(
 
     let agg_share_text = match version {
         DapVersion::Draft02 => CTX_AGG_SHARE_DRAFT02,
-        DapVersion::DraftLatest => CTX_AGG_SHARE_DRAFT_LATEST,
+        DapVersion::Draft09 | DapVersion::Latest => CTX_AGG_SHARE_DRAFT09,
     };
     let n: usize = agg_share_text.len();
     let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/client.rs b/daphne/src/protocol/client.rs
index 7c14196..a3355cf 100644
--- a/daphne/src/protocol/client.rs
+++ b/daphne/src/protocol/client.rs
@@ -17,7 +17,7 @@ use prio::codec::{Encode, ParameterizedEncode};
 use rand::prelude::*;
 
 use super::{
-    CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT_LATEST, CTX_ROLE_CLIENT, CTX_ROLE_HELPER,
+    CTX_INPUT_SHARE_DRAFT02, CTX_INPUT_SHARE_DRAFT09, CTX_ROLE_CLIENT, CTX_ROLE_HELPER,
     CTX_ROLE_LEADER,
 };
 
@@ -84,8 +84,8 @@ impl VdafConfig {
             return Err(fatal_error!(err = "unexpected number of HPKE configs"));
         }
 
-        let (draft02_extensions, mut draft_latest_plaintext_input_share) = match version {
-            DapVersion::DraftLatest => (
+        let (draft02_extensions, mut draft09_plaintext_input_share) = match version {
+            DapVersion::Draft09 | DapVersion::Latest => (
                 None,
                 Some(PlaintextInputShare {
                     extensions,
@@ -102,7 +102,7 @@ impl VdafConfig {
         };
 
         let encoded_input_shares = input_shares.into_iter().map(|input_share| {
-            if let Some(ref mut plaintext_input_share) = draft_latest_plaintext_input_share {
+            if let Some(ref mut plaintext_input_share) = draft09_plaintext_input_share {
                 plaintext_input_share.payload = input_share;
                 plaintext_input_share.get_encoded_with_param(&version)
             } else {
@@ -112,7 +112,7 @@ impl VdafConfig {
 
         let input_share_text = match version {
             DapVersion::Draft02 => CTX_INPUT_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_INPUT_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_INPUT_SHARE_DRAFT09,
         };
         let n: usize = input_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/collector.rs b/daphne/src/protocol/collector.rs
index c85fc44..b2ada99 100644
--- a/daphne/src/protocol/collector.rs
+++ b/daphne/src/protocol/collector.rs
@@ -13,7 +13,7 @@ use crate::{
 use prio::codec::Encode;
 
 use super::{
-    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT_LATEST, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
+    CTX_AGG_SHARE_DRAFT02, CTX_AGG_SHARE_DRAFT09, CTX_ROLE_COLLECTOR, CTX_ROLE_HELPER,
     CTX_ROLE_LEADER,
 };
 
@@ -52,7 +52,7 @@ impl VdafConfig {
 
         let agg_share_text = match version {
             DapVersion::Draft02 => CTX_AGG_SHARE_DRAFT02,
-            DapVersion::DraftLatest => CTX_AGG_SHARE_DRAFT_LATEST,
+            DapVersion::Draft09 | DapVersion::Latest => CTX_AGG_SHARE_DRAFT09,
         };
         let n: usize = agg_share_text.len();
         let mut info = Vec::with_capacity(n + 2);
diff --git a/daphne/src/protocol/mod.rs b/daphne/src/protocol/mod.rs
index 8d57c4d..5b8d10f 100644
--- a/daphne/src/protocol/mod.rs
+++ b/daphne/src/protocol/mod.rs
@@ -6,9 +6,9 @@ mod client;
 mod collector;
 
 const CTX_INPUT_SHARE_DRAFT02: &[u8] = b"dap-02 input share";
-const CTX_INPUT_SHARE_DRAFT_LATEST: &[u8] = b"dap-09 input share";
+const CTX_INPUT_SHARE_DRAFT09: &[u8] = b"dap-09 input share";
 const CTX_AGG_SHARE_DRAFT02: &[u8] = b"dap-02 aggregate share";
-const CTX_AGG_SHARE_DRAFT_LATEST: &[u8] = b"dap-09 aggregate share";
+const CTX_AGG_SHARE_DRAFT09: &[u8] = b"dap-09 aggregate share";
 const CTX_ROLE_COLLECTOR: u8 = 0;
 const CTX_ROLE_CLIENT: u8 = 1;
 const CTX_ROLE_LEADER: u8 = 2;
@@ -418,7 +418,7 @@ mod test {
                         public_share: report0.public_share,
                         encrypted_input_share: report0.encrypted_input_shares[1].clone(),
                     },
-                    draft_latest_payload: Some(b"malformed payload".to_vec()),
+                    draft09_payload: Some(b"malformed payload".to_vec()),
                 },
                 PrepareInit {
                     report_share: ReportShare {
@@ -426,7 +426,7 @@ mod test {
                         public_share: report1.public_share,
                         encrypted_input_share: report1.encrypted_input_shares[1].clone(),
                     },
-                    draft_latest_payload: Some(b"malformed payload".to_vec()),
+                    draft09_payload: Some(b"malformed payload".to_vec()),
                 },
             ],
         };
@@ -658,18 +658,12 @@ mod test {
 
     #[tokio::test]
     async fn agg_job_init_req_skip_vdaf_prep_error_draft09() {
-        let t = AggregationJobTest::new(
-            TEST_VDAF,
-            HpkeKemId::X25519HkdfSha256,
-            DapVersion::DraftLatest,
-        );
+        let t =
+            AggregationJobTest::new(TEST_VDAF, HpkeKemId::X25519HkdfSha256, DapVersion::Draft09);
         let mut reports = t.produce_reports(vec![DapMeasurement::U64(1), DapMeasurement::U64(1)]);
         reports.insert(
             1,
-            t.produce_invalid_report_vdaf_prep_failure(
-                DapMeasurement::U64(1),
-                DapVersion::DraftLatest,
-            ),
+            t.produce_invalid_report_vdaf_prep_failure(DapMeasurement::U64(1), DapVersion::Draft09),
         );
 
         let (leader_state, agg_job_init_req) = t
@@ -908,7 +902,7 @@ mod test {
             DapVersion::Draft02 => true,
             // In the latest version we're meant to reject reports containing unrecognized
             // extensions.
-            DapVersion::DraftLatest => false,
+            DapVersion::Draft09 | DapVersion::Latest => false,
         };
         assert_eq!(consumed_report.is_ready(), expect_ready);
     }
diff --git a/daphne/src/roles/aggregator.rs b/daphne/src/roles/aggregator.rs
index 7cbe78a..ea8c66e 100644
--- a/daphne/src/roles/aggregator.rs
+++ b/daphne/src/roles/aggregator.rs
@@ -196,7 +196,7 @@ where
             .as_ref()
             .get_encoded()
             .map_err(DapError::encoding)?,
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let hpke_config_list = HpkeConfigList {
                 hpke_configs: vec![hpke_config.as_ref().clone()],
             };
diff --git a/daphne/src/roles/helper.rs b/daphne/src/roles/helper.rs
index bf51d22..7b78773 100644
--- a/daphne/src/roles/helper.rs
+++ b/daphne/src/roles/helper.rs
@@ -170,7 +170,7 @@ pub async fn handle_agg_job_init_req<'req, S: Sync, A: DapHelper<S>>(
             agg_job_resp
         }
 
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let agg_job_resp = finish_agg_job_and_aggregate(
                 aggregator,
                 task_id,
@@ -462,10 +462,12 @@ fn resolve_agg_job_id<'id, S>(
         (DapVersion::Draft02, DapResource::Undefined, Some(agg_job_id)) => {
             Ok(MetaAggregationJobId::Draft02(*agg_job_id))
         }
-        (DapVersion::DraftLatest, DapResource::AggregationJob(agg_job_id), None) => {
-            Ok(MetaAggregationJobId::DraftLatest(*agg_job_id))
-        }
-        (DapVersion::DraftLatest, DapResource::Undefined, None) => {
+        (
+            DapVersion::Draft09 | DapVersion::Latest,
+            DapResource::AggregationJob(agg_job_id),
+            None,
+        ) => Ok(MetaAggregationJobId::Draft09(*agg_job_id)),
+        (DapVersion::Draft09 | DapVersion::Latest, DapResource::Undefined, None) => {
             Err(DapAbort::BadRequest("undefined resource".into()))
         }
         _ => unreachable!("unhandled resource {:?}", req.resource),
diff --git a/daphne/src/roles/leader/mod.rs b/daphne/src/roles/leader/mod.rs
index 3ff08b0..1fc93bb 100644
--- a/daphne/src/roles/leader/mod.rs
+++ b/daphne/src/roles/leader/mod.rs
@@ -300,9 +300,10 @@ pub async fn handle_coll_job_req<S: Sync, A: DapLeader<S>>(
     // from the request path.
     let collect_job_id = match (req.version, &req.resource) {
         (DapVersion::Draft02, DapResource::Undefined) => None,
-        (DapVersion::DraftLatest, DapResource::CollectionJob(ref collect_job_id)) => {
-            Some(*collect_job_id)
-        }
+        (
+            DapVersion::Draft09 | DapVersion::Latest,
+            DapResource::CollectionJob(ref collect_job_id),
+        ) => Some(*collect_job_id),
         (_, DapResource::Undefined) => {
             return Err(DapAbort::BadRequest("missing collection ID".into()).into())
         }
@@ -562,9 +563,9 @@ async fn run_coll_job<S: Sync, A: DapLeader<S>>(
         .map_err(|e| DapAbort::from_codec_error(e, *task_id))?;
     // In the latest draft, the Collection message includes the smallest quantized time
     // interval containing all reports in the batch.
-    let draft_latest_interval = match task_config.version {
+    let draft09_interval = match task_config.version {
         DapVersion::Draft02 => None,
-        DapVersion::DraftLatest => {
+        DapVersion::Draft09 | DapVersion::Latest => {
             let low = task_config.quantized_time_lower_bound(leader_agg_share.min_time);
             let high = task_config.quantized_time_upper_bound(leader_agg_share.max_time);
             Some(Interval {
@@ -583,7 +584,7 @@ async fn run_coll_job<S: Sync, A: DapLeader<S>>(
     let collection = Collection {
         part_batch_sel: batch_sel.clone().into(),
         report_count: leader_agg_share.report_count,
-        draft_latest_interval,
+        draft09_interval,
         encrypted_agg_shares: [leader_enc_agg_share, agg_share_resp.encrypted_agg_share],
     };
     aggregator
diff --git a/daphne/src/roles/mod.rs b/daphne/src/roles/mod.rs
index 5f91a25..7788ea4 100644
--- a/daphne/src/roles/mod.rs
+++ b/daphne/src/roles/mod.rs
@@ -153,7 +153,7 @@ async fn resolve_taskprov<S: Sync>(
 mod test {
     use super::{aggregator, helper, leader, DapAggregator, DapAuthorizedSender, DapLeader};
     use crate::{
-        assert_metrics_include, async_test_versions,
+        assert_metrics_include, async_test_version, async_test_versions,
         auth::BearerToken,
         constants::DapMediaType,
         hpke::{HpkeDecrypter, HpkeKemId, HpkeReceiverConfig},
@@ -187,7 +187,7 @@ mod test {
     fn empty_report_extensions_for_version(version: DapVersion) -> Option<Vec<Extension>> {
         match version {
             DapVersion::Draft02 => Some(Vec::new()),
-            DapVersion::DraftLatest => None,
+            DapVersion::Draft09 | DapVersion::Latest => None,
         }
     }
 
@@ -1458,7 +1458,7 @@ mod test {
         let collection = Collection {
             part_batch_sel: PartialBatchSelector::TimeInterval,
             report_count: 0,
-            draft_latest_interval: if version == DapVersion::Draft02 {
+            draft09_interval: if version == DapVersion::Draft02 {
                 None
             } else {
                 Some(Interval {
@@ -1828,7 +1828,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -1865,7 +1865,7 @@ mod test {
 
                 Query::FixedSizeByBatchId { batch_id }
             }
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         };
         leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, task_id).await)
             .await
@@ -1877,7 +1877,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -1946,7 +1946,7 @@ mod test {
                     test_measurement.clone(),
                     vec![Extension::Taskprov {
                         draft02_payload: match version {
-                            DapVersion::DraftLatest => None,
+                            DapVersion::Draft09 | DapVersion::Latest => None,
                             DapVersion::Draft02 => taskprov_report_extension_payload.clone(),
                         },
                     }],
@@ -1971,7 +1971,7 @@ mod test {
             DapVersion::Draft02 => Query::FixedSizeByBatchId {
                 batch_id: t.leader.current_batch(&task_id).await.unwrap(),
             },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         };
         leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, &task_id).await)
             .await
@@ -1983,7 +1983,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         };
 
         assert_metrics_include!(t.helper_registry, {
@@ -2011,10 +2011,9 @@ mod test {
 
     async_test_versions! { e2e_taskprov_prio2 }
 
-    #[tokio::test]
-    async fn e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128_draft09() {
+    async fn e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128(version: DapVersion) {
         e2e_taskprov(
-            DapVersion::DraftLatest,
+            version,
             VdafConfig::Prio3(Prio3Config::SumVecField64MultiproofHmacSha256Aes128 {
                 bits: 1,
                 length: 10,
@@ -2026,6 +2025,9 @@ mod test {
         .await;
     }
 
+    async_test_version! { e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128, Draft09 }
+    async_test_version! { e2e_taskprov_prio3_sum_vec_field64_multiproof_hmac_sha256_aes128, Latest }
+
     fn early_metadata_checks(version: DapVersion) {
         let t = Test::new(version);
         let mut rng = thread_rng();
@@ -2141,7 +2143,7 @@ mod test {
 
                     Query::FixedSizeByBatchId { batch_id }
                 }
-                DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+                DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
             };
             leader::handle_coll_job_req(&*t.leader, &t.gen_test_coll_job_req(query, task_id).await)
                 .await
@@ -2154,7 +2156,7 @@ mod test {
 
         let agg_job_req_count = match version {
             DapVersion::Draft02 => 2,
-            DapVersion::DraftLatest => 1,
+            DapVersion::Draft09 | DapVersion::Latest => 1,
         } * 2;
 
         assert_metrics_include!(t.helper_registry, {
@@ -2175,9 +2177,11 @@ mod test {
 
     // TODO(cjpatton) Test collecting the batch multiple times per the "heavy hitters" mode of
     // operation for Mastic.
+    //
+    // TODO(cjpatton) Create a test for "attribute based metrics" for draft09.
     #[tokio::test]
     async fn heavy_hitters() {
-        let t = Test::new(DapVersion::DraftLatest);
+        let t = Test::new(DapVersion::Latest);
         let task_id = &t.heavy_hitters_task_id;
         let task_config = t
             .leader
diff --git a/daphne/src/taskprov.rs b/daphne/src/taskprov.rs
index d9ff0e9..9ec8572 100644
--- a/daphne/src/taskprov.rs
+++ b/daphne/src/taskprov.rs
@@ -147,7 +147,7 @@ fn get_taskprov_task_config<S>(
             )
         })?)
     } else if let Some(metadata) = report_metadata_advertisement {
-        if req.version == DapVersion::DraftLatest {
+        if matches!(req.version, DapVersion::Draft09 | DapVersion::Latest) {
             return Ok(None);
         }
         let taskprovs: Vec<&Extension> = metadata
@@ -234,7 +234,7 @@ impl VdafConfig {
                 })?,
             }),
             (
-                DapVersion::DraftLatest,
+                DapVersion::Draft09 | DapVersion::Latest,
                 VdafTypeVar::Prio3SumVecField64MultiproofHmacSha256Aes128 {
                     bits,
                     length,
@@ -684,7 +684,7 @@ mod test {
             (DapVersion::Draft02, Err(DapAbort::InvalidMessage { detail, .. })) => {
                 assert_eq!(detail, "codec error: unexpected value");
             }
-            (DapVersion::DraftLatest, Err(DapAbort::InvalidTask { detail, .. })) => {
+            (DapVersion::Draft09, Err(DapAbort::InvalidTask { detail, .. })) => {
                 assert_eq!(detail, "unimplemented VDAF type (1337)");
             }
             (_, r) => panic!("unexpected result: {r:?} ({version})"),
diff --git a/daphne/src/vdaf/mastic.rs b/daphne/src/vdaf/mastic.rs
index b88db1f..df7a1d5 100644
--- a/daphne/src/vdaf/mastic.rs
+++ b/daphne/src/vdaf/mastic.rs
@@ -243,19 +243,18 @@ mod test {
 
     use super::*;
     use crate::{
-        hpke::HpkeKemId, testing::AggregationJobTest, vdaf::VdafConfig, DapAggregateResult,
-        DapMeasurement, DapVersion,
+        async_test_version, hpke::HpkeKemId, testing::AggregationJobTest, vdaf::VdafConfig,
+        DapAggregateResult, DapMeasurement, DapVersion,
     };
 
-    #[tokio::test]
-    async fn roundtrip_count() {
+    async fn roundtrip_count(version: DapVersion) {
         let mut t = AggregationJobTest::new(
             &VdafConfig::Mastic {
                 input_size: 4,
                 weight_config: MasticWeightConfig::Count,
             },
             HpkeKemId::X25519HkdfSha256,
-            DapVersion::DraftLatest,
+            version,
         );
         let got = t
             .roundtrip(
@@ -293,4 +292,7 @@ mod test {
 
         assert_eq!(got, DapAggregateResult::U64Vec(vec![1, 2]));
     }
+
+    async_test_version! { roundtrip_count, Draft09 }
+    async_test_version! { roundtrip_count, Latest }
 }
diff --git a/daphne_server/Cargo.toml b/daphne_server/Cargo.toml
index e560f61..371f7ef 100644
--- a/daphne_server/Cargo.toml
+++ b/daphne_server/Cargo.toml
@@ -41,6 +41,7 @@ clap = { version = "4.4.18", features = ["derive"] }
 config = "0.13.4"
 daphne = { path = "../daphne", features = ["test-utils"] }
 daphne_service_utils = { path = "../daphne_service_utils", features = ["prometheus"] }
+paste.workspace = true
 prometheus.workspace = true
 rand.workspace = true
 tower = "0.4.13"
diff --git a/daphne_server/src/lib.rs b/daphne_server/src/lib.rs
index 38dff1a..bd2a216 100644
--- a/daphne_server/src/lib.rs
+++ b/daphne_server/src/lib.rs
@@ -55,7 +55,7 @@ mod storage_proxy_connection;
 ///     report_shard_count: 4,
 ///     base_url: None,
 ///     taskprov: None,
-///     default_version: DapVersion::DraftLatest,
+///     default_version: DapVersion::Draft09,
 ///     report_storage_epoch_duration: 300,
 ///     report_storage_max_future_time_skew: 300,
 /// };
@@ -83,7 +83,6 @@ pub struct App {
 #[derive(Debug, Serialize, Deserialize)]
 pub struct StorageProxyConfig {
     pub url: Url,
-    #[serde(with = "transparent_auth_token")]
     pub auth_token: BearerToken,
 }
 
@@ -121,41 +120,3 @@ impl App {
         Kv::new(&self.storage_proxy_config, &self.http, &self.cache)
     }
 }
-
-mod transparent_auth_token {
-    //! For backwards compatibility reasons we can't add `#[serde(transparent)]` to
-    //! [`BearerToken`], as such we have to have a custom serializer for this field in order to
-    //! make the config file less verbose.
-    //!
-    //! # Example
-    //! Without the serializer
-    //! ```yaml
-    //! auth_token:
-    //!     raw: 'the-token'
-    //! ```
-    //!
-    //! With the serializer
-    //! ```yaml
-    //! auth_token: 'the-token'
-    //! ```
-    //!
-    //! TODO(mendes): Once the `dap_prototype` is removed we can make the change to remove this.
-
-    use daphne::auth::BearerToken;
-    use serde::{Deserialize, Deserializer, Serializer};
-
-    pub fn serialize<S>(value: &BearerToken, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        serializer.serialize_str(value.as_ref())
-    }
-
-    pub fn deserialize<'de, D>(deserializer: D) -> Result<BearerToken, D::Error>
-    where
-        D: Deserializer<'de>,
-    {
-        let s = String::deserialize(deserializer)?;
-        Ok(BearerToken::from(s))
-    }
-}
diff --git a/daphne_server/src/router/helper.rs b/daphne_server/src/router/helper.rs
index 81479d7..e53e901 100644
--- a/daphne_server/src/router/helper.rs
+++ b/daphne_server/src/router/helper.rs
@@ -56,7 +56,7 @@ where
                 app.server_metrics(),
                 match req.version {
                     daphne::DapVersion::Draft02 => StatusCode::OK,
-                    daphne::DapVersion::DraftLatest => StatusCode::CREATED,
+                    daphne::DapVersion::Draft09 | daphne::DapVersion::Latest => StatusCode::CREATED,
                 },
             )
         }
diff --git a/daphne_server/src/router/leader.rs b/daphne_server/src/router/leader.rs
index dec24df..b86876a 100644
--- a/daphne_server/src/router/leader.rs
+++ b/daphne_server/src/router/leader.rs
@@ -110,7 +110,7 @@ where
             AppendHeaders([(header::LOCATION, uri.as_str())]),
         )
             .into_response(),
-        (Ok(_), DapVersion::DraftLatest) => StatusCode::CREATED.into_response(),
+        (Ok(_), DapVersion::Draft09 | DapVersion::Latest) => StatusCode::CREATED.into_response(),
         (Err(e), _) => AxumDapResponse::new_error(e, app.server_metrics()).into_response(),
     }
 }
diff --git a/daphne_server/src/router/mod.rs b/daphne_server/src/router/mod.rs
index 48f2251..f306cdf 100644
--- a/daphne_server/src/router/mod.rs
+++ b/daphne_server/src/router/mod.rs
@@ -299,7 +299,7 @@ where
                     collect_job_id.map_or(DapResource::Undefined, DapResource::CollectionJob);
                 (task_id, resource)
             }
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let resource = match media_type {
                     Some(
                         DapMediaType::AggregationJobInitReq
@@ -354,12 +354,12 @@ mod test {
         Router,
     };
     use daphne::{
+        async_test_version, async_test_versions,
         messages::{AggregationJobId, Base64Encode, TaskId},
         DapRequest, DapResource, DapVersion,
     };
     use daphne_service_utils::auth::DaphneAuth;
     use futures::future::BoxFuture;
-    use prio::codec::Encode;
     use rand::{thread_rng, Rng};
     use tokio::sync::mpsc::{self, Sender};
     use tower::ServiceExt;
@@ -426,45 +426,33 @@ mod test {
         }
     }
 
-    #[tokio::test]
-    async fn parse_latest_version() {
+    async fn parse_version(version: DapVersion) {
         let test = test_router();
 
         let req = test(
             Request::builder()
-                .uri("/v09/parse-version")
+                .uri(format!("/{version}/parse-version"))
                 .body(Body::empty())
                 .unwrap(),
         )
         .await;
 
-        assert_eq!(req.version, DapVersion::DraftLatest);
+        assert_eq!(req.version, version);
     }
 
-    #[tokio::test]
-    async fn parse_draft02_version() {
-        let test = test_router();
-
-        let req = test(
-            Request::builder()
-                .uri("/v02/parse-version")
-                .body("1".repeat(32))
-                .unwrap(),
-        )
-        .await;
+    async_test_versions! { parse_version }
 
-        assert_eq!(req.version, DapVersion::Draft02);
-    }
-
-    #[tokio::test]
-    async fn parse_task_id_latest_version() {
+    async fn parse_task_id(version: DapVersion) {
         let test = test_router();
 
         let task_id = TaskId(thread_rng().gen());
 
         let req = test(
             Request::builder()
-                .uri(format!("/v09/{}/parse-task-id", task_id.to_base64url()))
+                .uri(format!(
+                    "/{version}/{}/parse-task-id",
+                    task_id.to_base64url()
+                ))
                 .body(Body::empty())
                 .unwrap(),
         )
@@ -473,29 +461,9 @@ mod test {
         assert_eq!(req.task_id, Some(task_id));
     }
 
-    #[tokio::test]
-    async fn parse_task_id_draft02_version() {
-        let test = test_router();
+    async_test_versions! { parse_task_id }
 
-        let task_id = TaskId(thread_rng().gen());
-
-        let req = test(
-            Request::builder()
-                .uri("/v02/parse-version")
-                .body({
-                    let mut v = Vec::new();
-                    task_id.encode(&mut v).unwrap();
-                    Body::from(v)
-                })
-                .unwrap(),
-        )
-        .await;
-
-        assert_eq!(req.task_id, Some(task_id));
-    }
-
-    #[tokio::test]
-    async fn parse_agg_job_id_latest_version() {
+    async fn parse_agg_job_id(version: DapVersion) {
         let test = test_router();
 
         let agg_job_id = AggregationJobId(thread_rng().gen());
@@ -503,7 +471,7 @@ mod test {
         let req = test(
             Request::builder()
                 .uri(format!(
-                    "/v09/{}/parse-agg-job-id",
+                    "/{version}/{}/parse-agg-job-id",
                     agg_job_id.to_base64url()
                 ))
                 .header(CONTENT_TYPE, "application/dap-aggregation-job-init-req")
@@ -514,4 +482,7 @@ mod test {
 
         assert_eq!(req.resource, DapResource::AggregationJob(agg_job_id));
     }
+
+    async_test_version! { parse_agg_job_id, Draft09 }
+    async_test_version! { parse_agg_job_id, Latest }
 }
diff --git a/daphne_service_utils/src/auth.rs b/daphne_service_utils/src/auth.rs
index 5e455d5..eef7f78 100644
--- a/daphne_service_utils/src/auth.rs
+++ b/daphne_service_utils/src/auth.rs
@@ -91,14 +91,13 @@ impl AsRef<BearerToken> for DaphneAuth {
 }
 
 #[derive(Debug, Deserialize, Serialize, Clone)]
-#[serde(try_from = "SerializedDaphneWorkerAuthMethod")]
 pub struct DaphneWorkerAuthMethod {
     /// Expected bearer token.
-    #[serde(default)]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub bearer_token: Option<BearerToken>,
 
     /// Details of trusted TLS client certificates.
-    #[serde(default)]
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub cf_tls_client_auth: Option<Vec<TlsCertInfo>>,
 }
 
@@ -128,25 +127,6 @@ impl AsRef<BearerToken> for DaphneWorkerAuthMethod {
     }
 }
 
-#[derive(Deserialize, Serialize)]
-#[serde(rename_all = "snake_case")]
-struct SerializedDaphneWorkerAuthMethod {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    bearer_token: Option<String>,
-
-    #[serde(skip_serializing_if = "Option::is_none")]
-    cf_tls_client_auth: Option<Vec<TlsCertInfo>>,
-}
-
-impl From<SerializedDaphneWorkerAuthMethod> for DaphneWorkerAuthMethod {
-    fn from(serialized: SerializedDaphneWorkerAuthMethod) -> Self {
-        Self {
-            bearer_token: serialized.bearer_token.map(BearerToken::from),
-            cf_tls_client_auth: serialized.cf_tls_client_auth,
-        }
-    }
-}
-
 #[cfg(test)]
 mod test {
     use super::{BearerToken, DaphneWorkerAuthMethod, TlsCertInfo};
diff --git a/daphne_worker/src/storage_proxy.rs b/daphne_worker/src/storage_proxy.rs
index c0b79c3..f7d5ddc 100644
--- a/daphne_worker/src/storage_proxy.rs
+++ b/daphne_worker/src/storage_proxy.rs
@@ -49,7 +49,7 @@
 //!     bindings::AggregateStore::Merge,
 //!     // some mock data here
 //!     (
-//!         daphne::DapVersion::DraftLatest,
+//!         daphne::DapVersion::Draft09,
 //!         "some-task-id-in-hex",
 //!         &daphne::DapBatchBucket::TimeInterval { batch_window: 50 }
 //!     ),


Test patch:
diff --git a/daphne/src/testing.rs b/daphne/src/testing.rs
index 5ed16c1..617ae9c 100644
--- a/daphne/src/testing.rs
+++ b/daphne/src/testing.rs
@@ -499,7 +499,7 @@ impl AggregationJobTest {
 //
 // and
 //
-//     something_draftlatest
+//     something_draft09
 //
 // that called something(version) with the appropriate version.
 //
@@ -522,7 +522,7 @@ macro_rules! test_versions {
     ($($fname:ident),*) => {
         $(
             $crate::test_version! { $fname, Draft02 }
-            $crate::test_version! { $fname, DraftLatest }
+            $crate::test_version! { $fname, Draft09 }
         )*
     };
 }
@@ -544,7 +544,8 @@ macro_rules! async_test_versions {
     ($($fname:ident),*) => {
         $(
             $crate::async_test_version! { $fname, Draft02 }
-            $crate::async_test_version! { $fname, DraftLatest }
+            $crate::async_test_version! { $fname, Draft09 }
+            $crate::async_test_version! { $fname, Latest }
         )*
     };
 }
diff --git a/daphne_worker_test/tests/e2e/e2e.rs b/daphne_worker_test/tests/e2e/e2e.rs
index a8af3ea..5f8eba6 100644
--- a/daphne_worker_test/tests/e2e/e2e.rs
+++ b/daphne_worker_test/tests/e2e/e2e.rs
@@ -289,7 +289,7 @@ async fn leader_upload(version: DapVersion) {
     );
     let builder = match t.version {
         DapVersion::Draft02 => client.post(url.as_str()),
-        DapVersion::DraftLatest => client.put(url.as_str()),
+        DapVersion::Draft09 | DapVersion::Latest => client.put(url.as_str()),
     };
     let resp = builder
         .body(
@@ -300,7 +300,7 @@ async fn leader_upload(version: DapVersion) {
                     time: t.now,
                     draft02_extensions: match version {
                         DapVersion::Draft02 => Some(Vec::default()),
-                        DapVersion::DraftLatest => None,
+                        DapVersion::Draft09 | DapVersion::Latest => None,
                     },
                 },
                 public_share: b"public share".to_vec(),
@@ -621,7 +621,7 @@ async fn leader_collect_ok(version: DapVersion) {
 
     if version != DapVersion::Draft02 {
         // Check that the time interval for the reports is correct.
-        let interval = collection.draft_latest_interval.as_ref().unwrap();
+        let interval = collection.draft09_interval.as_ref().unwrap();
         let low = t.task_config.quantized_time_lower_bound(time_min);
         let high = t.task_config.quantized_time_upper_bound(time_max);
         assert!(low < high);
@@ -1026,7 +1026,7 @@ async_test_versions! { leader_collect_abort_overlapping_batch_interval }
 
 #[tokio::test]
 async fn fixed_size() {
-    let version = DapVersion::DraftLatest;
+    let version = DapVersion::Draft09;
     let t = TestRunner::fixed_size(version).await;
     let path = t.upload_path();
     let client = TestRunner::http_client();
@@ -1064,7 +1064,7 @@ async fn fixed_size() {
         draft02_task_id: t.collect_task_id_field(),
         query: match version {
             DapVersion::Draft02 => Query::FixedSizeByBatchId { batch_id },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         },
         agg_param: agg_param.get_encoded().unwrap(),
     };
@@ -1164,7 +1164,7 @@ async fn fixed_size() {
         draft02_task_id: t.collect_task_id_field(),
         query: match version {
             DapVersion::Draft02 => Query::FixedSizeByBatchId { batch_id },
-            DapVersion::DraftLatest => Query::FixedSizeCurrentBatch,
+            DapVersion::Draft09 | DapVersion::Latest => Query::FixedSizeCurrentBatch,
         },
         agg_param: agg_param.get_encoded().unwrap(),
     };
@@ -1255,7 +1255,7 @@ async fn leader_collect_taskprov_ok(version: DapVersion) {
     for _ in 0..t.task_config.min_batch_size {
         let extensions = vec![Extension::Taskprov {
             draft02_payload: match version {
-                DapVersion::DraftLatest => None,
+                DapVersion::Draft09 | DapVersion::Latest => None,
                 DapVersion::Draft02 => taskprov_report_extension_payload.clone(),
             },
         }];
diff --git a/daphne_worker_test/tests/e2e/test_runner.rs b/daphne_worker_test/tests/e2e/test_runner.rs
index 40efab2..3f4d417 100644
--- a/daphne_worker_test/tests/e2e/test_runner.rs
+++ b/daphne_worker_test/tests/e2e/test_runner.rs
@@ -79,12 +79,8 @@ impl TestRunner {
 
         // When running in a local development environment, override the hostname of each
         // aggregator URL with 127.0.0.1.
-        let version_path = match version {
-            DapVersion::Draft02 => "v02",
-            DapVersion::DraftLatest => "v09",
-        };
-        let mut leader_url = Url::parse(&format!("http://leader:8787/{version_path}/")).unwrap();
-        let mut helper_url = Url::parse(&format!("http://helper:8788/{version_path}/")).unwrap();
+        let mut leader_url = Url::parse(&format!("http://leader:8787/{version}/")).unwrap();
+        let mut helper_url = Url::parse(&format!("http://helper:8788/{version}/")).unwrap();
         println!("leader_url = {leader_url}");
         if let Ok(env) = std::env::var("DAP_DEPLOYMENT") {
             if env == "dev" {
@@ -295,7 +291,7 @@ impl TestRunner {
                 HpkeConfig::get_decoded(&raw_leader_hpke_config).unwrap(),
                 HpkeConfig::get_decoded(&raw_helper_hpke_config).unwrap(),
             ],
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let mut leader_hpke_config_list =
                     HpkeConfigList::get_decoded(&raw_leader_hpke_config).unwrap();
                 let mut helper_hpke_config_list =
@@ -710,14 +706,16 @@ impl TestRunner {
     pub fn upload_path_for_task(&self, id: &TaskId) -> String {
         match self.version {
             DapVersion::Draft02 => "upload".to_string(),
-            DapVersion::DraftLatest => format!("tasks/{}/reports", id.to_base64url()),
+            DapVersion::Draft09 | DapVersion::Latest => {
+                format!("tasks/{}/reports", id.to_base64url())
+            }
         }
     }
 
     pub fn collect_path_for_task(&self, task_id: &TaskId) -> String {
         match self.version {
             DapVersion::Draft02 => "collect".to_string(),
-            DapVersion::DraftLatest => {
+            DapVersion::Draft09 | DapVersion::Latest => {
                 let collection_job_id = CollectionJobId(thread_rng().gen());
                 format!(
                     "tasks/{}/collection_jobs/{}",