Repository: cloudflare/daphne
Pull request #659 (2024-08-26 14:34:26)

Rewrite workers to use the axum router
the `workers` library supports using types from the `http` library instead of its own reimplementation of these. This means we can use axum to do the routing for us.

This comes with a few advantages, one of which is proper use of middleware to implement authorization and timings.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0c70510..c2f2fec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -95,9 +95,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
 [[package]]
 name = "anstream"
-version = "0.6.14"
+version = "0.6.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
 dependencies = [
  "anstyle",
  "anstyle-parse",
@@ -110,33 +110,33 @@ dependencies = [
 
 [[package]]
 name = "anstyle"
-version = "1.0.7"
+version = "1.0.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
 
 [[package]]
 name = "anstyle-parse"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
 dependencies = [
  "utf8parse",
 ]
 
 [[package]]
 name = "anstyle-query"
-version = "1.1.0"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
 dependencies = [
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "anstyle-wincon"
-version = "3.0.3"
+version = "3.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
 dependencies = [
  "anstyle",
  "windows-sys 0.52.0",
@@ -195,13 +195,13 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
 
 [[package]]
 name = "async-trait"
-version = "0.1.80"
+version = "0.1.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
+checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -217,13 +217,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
 dependencies = [
  "async-trait",
- "axum-core",
+ "axum-core 0.3.4",
  "bitflags 1.3.2",
  "bytes",
  "futures-util",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "itoa",
  "matchit",
  "memchr",
@@ -242,6 +242,33 @@ dependencies = [
  "tower-service",
 ]
 
+[[package]]
+name = "axum"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
+dependencies = [
+ "async-trait",
+ "axum-core 0.4.3",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "sync_wrapper 1.0.1",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
 [[package]]
 name = "axum-core"
 version = "0.3.4"
@@ -259,6 +286,50 @@ dependencies = [
  "tower-service",
 ]
 
+[[package]]
+name = "axum-core"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper 0.1.2",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-extra"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
+dependencies = [
+ "axum 0.7.5",
+ "axum-core 0.4.3",
+ "bytes",
+ "futures-util",
+ "headers",
+ "http 1.1.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "serde",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
 [[package]]
 name = "az"
 version = "1.2.1"
@@ -360,9 +431,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
 
 [[package]]
 name = "bytemuck"
-version = "1.16.1"
+version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e"
+checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31"
 
 [[package]]
 name = "byteorder"
@@ -372,9 +443,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.6.0"
+version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
 
 [[package]]
 name = "cap"
@@ -408,9 +479,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
 
 [[package]]
 name = "cc"
-version = "1.0.101"
+version = "1.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d"
+checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932"
+dependencies = [
+ "shlex",
+]
 
 [[package]]
 name = "cfg-if"
@@ -453,7 +527,7 @@ dependencies = [
  "js-sys",
  "num-traits",
  "wasm-bindgen",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -496,9 +570,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.7"
+version = "4.5.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
+checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -506,9 +580,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.7"
+version = "4.5.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
 dependencies = [
  "anstream",
  "anstyle",
@@ -518,27 +592,27 @@ dependencies = [
 
 [[package]]
 name = "clap_derive"
-version = "4.5.5"
+version = "4.5.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6"
+checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
 dependencies = [
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
 name = "clap_lex"
-version = "0.7.1"
+version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
 
 [[package]]
 name = "colorchoice"
-version = "1.0.1"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
 
 [[package]]
 name = "config"
@@ -575,6 +649,12 @@ version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
+[[package]]
+name = "constcat"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f2e5af989b1955b092db01462980c0a286217f86817e12b2c09aea46bd03651"
+
 [[package]]
 name = "core-foundation"
 version = "0.9.4"
@@ -587,15 +667,15 @@ dependencies = [
 
 [[package]]
 name = "core-foundation-sys"
-version = "0.8.6"
+version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.12"
+version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
+checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
 dependencies = [
  "libc",
 ]
@@ -723,7 +803,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -794,7 +874,7 @@ version = "0.3.0"
 dependencies = [
  "anyhow",
  "assert_matches",
- "axum",
+ "axum 0.6.20",
  "clap",
  "config",
  "daphne",
@@ -803,7 +883,7 @@ dependencies = [
  "hex",
  "hpke-rs",
  "http 0.2.12",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "mappable-rc",
  "p256",
  "paste",
@@ -852,13 +932,20 @@ dependencies = [
 name = "daphne-worker"
 version = "0.3.0"
 dependencies = [
+ "axum 0.7.5",
+ "axum-extra",
  "bincode",
+ "bytes",
  "chrono",
+ "constcat",
  "daphne",
  "daphne-service-utils",
  "futures",
  "getrandom",
+ "headers",
  "hex",
+ "http 1.1.0",
+ "http-body-util",
  "paste",
  "prio",
  "prometheus",
@@ -869,10 +956,12 @@ dependencies = [
  "serde",
  "serde-wasm-bindgen 0.6.5",
  "serde_json",
+ "tower-service",
  "tracing",
  "tracing-core",
  "tracing-subscriber",
  "url",
+ "wasm-streams",
  "worker",
 ]
 
@@ -981,7 +1070,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -1064,9 +1153,9 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "2.1.0"
+version = "2.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
 
 [[package]]
 name = "ff"
@@ -1098,9 +1187,9 @@ dependencies = [
 
 [[package]]
 name = "fixed"
-version = "1.27.0"
+version = "1.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048"
+checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a"
 dependencies = [
  "az",
  "bytemuck",
@@ -1200,7 +1289,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -1328,6 +1417,30 @@ version = "0.14.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 
+[[package]]
+name = "headers"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
+dependencies = [
+ "base64 0.21.7",
+ "bytes",
+ "headers-core",
+ "http 1.1.0",
+ "httpdate",
+ "mime",
+ "sha1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
+dependencies = [
+ "http 1.1.0",
+]
+
 [[package]]
 name = "heck"
 version = "0.5.0"
@@ -1340,6 +1453,12 @@ version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
 
+[[package]]
+name = "hermit-abi"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
+
 [[package]]
 name = "hex"
 version = "0.4.3"
@@ -1453,9 +1572,9 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
  "http 1.1.0",
@@ -1470,7 +1589,7 @@ dependencies = [
  "bytes",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "pin-project-lite",
 ]
 
@@ -1488,9 +1607,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "0.14.29"
+version = "0.14.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33"
+checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
 dependencies = [
  "bytes",
  "futures-channel",
@@ -1512,15 +1631,15 @@ dependencies = [
 
 [[package]]
 name = "hyper"
-version = "1.3.1"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "httparse",
  "itoa",
  "pin-project-lite",
@@ -1537,7 +1656,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
 dependencies = [
  "futures-util",
  "http 0.2.12",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "rustls 0.21.12",
  "tokio",
  "tokio-rustls 0.24.1",
@@ -1551,9 +1670,9 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
 dependencies = [
  "futures-util",
  "http 1.1.0",
- "hyper 1.3.1",
+ "hyper 1.4.1",
  "hyper-util",
- "rustls 0.23.10",
+ "rustls 0.23.12",
  "rustls-pki-types",
  "tokio",
  "tokio-rustls 0.26.0",
@@ -1568,7 +1687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
  "bytes",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "native-tls",
  "tokio",
  "tokio-native-tls",
@@ -1576,16 +1695,16 @@ dependencies = [
 
 [[package]]
 name = "hyper-util"
-version = "0.1.5"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56"
+checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
 dependencies = [
  "bytes",
  "futures-channel",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
- "hyper 1.3.1",
+ "http-body 1.0.1",
+ "hyper 1.4.1",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -1629,9 +1748,9 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "2.2.6"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
 dependencies = [
  "equivalent",
  "hashbrown 0.14.5",
@@ -1654,20 +1773,20 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
 [[package]]
 name = "is-terminal"
-version = "0.4.12"
+version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
 dependencies = [
- "hermit-abi",
+ "hermit-abi 0.4.0",
  "libc",
  "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "is_terminal_polyfill"
-version = "1.70.0"
+version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
 
 [[package]]
 name = "itertools"
@@ -1695,9 +1814,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "js-sys"
-version = "0.3.69"
+version = "0.3.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -1730,9 +1849,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
 
 [[package]]
 name = "libc"
-version = "0.2.155"
+version = "0.2.158"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
 
 [[package]]
 name = "linked-hash-map"
@@ -1758,9 +1877,9 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.4.21"
+version = "0.4.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 
 [[package]]
 name = "mappable-rc"
@@ -1812,13 +1931,14 @@ dependencies = [
 
 [[package]]
 name = "mio"
-version = "0.8.11"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
 dependencies = [
+ "hermit-abi 0.3.9",
  "libc",
  "wasi",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
@@ -1860,9 +1980,9 @@ dependencies = [
 
 [[package]]
 name = "num-bigint"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
 dependencies = [
  "num-integer",
  "num-traits",
@@ -1917,21 +2037,11 @@ dependencies = [
  "autocfg",
 ]
 
-[[package]]
-name = "num_cpus"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
 [[package]]
 name = "object"
-version = "0.36.0"
+version = "0.36.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
+checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
 dependencies = [
  "memchr",
 ]
@@ -1953,9 +2063,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
 [[package]]
 name = "oorandom"
-version = "11.1.3"
+version = "11.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
 
 [[package]]
 name = "opaque-debug"
@@ -1986,7 +2096,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -2076,7 +2186,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -2118,9 +2228,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "pest"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8"
+checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
 dependencies = [
  "memchr",
  "thiserror",
@@ -2129,9 +2239,9 @@ dependencies = [
 
 [[package]]
 name = "pest_derive"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459"
+checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
 dependencies = [
  "pest",
  "pest_generator",
@@ -2139,22 +2249,22 @@ dependencies = [
 
 [[package]]
 name = "pest_generator"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687"
+checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
 dependencies = [
  "pest",
  "pest_meta",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
 name = "pest_meta"
-version = "2.7.10"
+version = "2.7.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd"
+checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
 dependencies = [
  "once_cell",
  "pest",
@@ -2178,7 +2288,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -2268,9 +2378,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.17"
+version = "0.2.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
 
 [[package]]
 name = "primeorder"
@@ -2283,9 +2396,9 @@ dependencies = [
 
 [[package]]
 name = "prio"
-version = "0.16.5"
+version = "0.16.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d81f366140fb9dfdaf6f3be9b937bf598e5dbf8aafb9dbd182b1f3be196bc2"
+checksum = "4f86ecfd264aa77241b69044a91e7627b7cfdf88fb771835a7663eeec4543f75"
 dependencies = [
  "aes",
  "bitvec",
@@ -2344,16 +2457,17 @@ checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 
 [[package]]
 name = "quinn"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad"
+checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
 dependencies = [
  "bytes",
  "pin-project-lite",
  "quinn-proto",
  "quinn-udp",
  "rustc-hash",
- "rustls 0.23.10",
+ "rustls 0.23.12",
+ "socket2",
  "thiserror",
  "tokio",
  "tracing",
@@ -2361,15 +2475,15 @@ dependencies = [
 
 [[package]]
 name = "quinn-proto"
-version = "0.11.3"
+version = "0.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe"
+checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
 dependencies = [
  "bytes",
  "rand",
  "ring",
  "rustc-hash",
- "rustls 0.23.10",
+ "rustls 0.23.12",
  "slab",
  "thiserror",
  "tinyvec",
@@ -2378,9 +2492,9 @@ dependencies = [
 
 [[package]]
 name = "quinn-udp"
-version = "0.5.2"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46"
+checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
 dependencies = [
  "libc",
  "once_cell",
@@ -2391,9 +2505,9 @@ dependencies = [
 
 [[package]]
 name = "quote"
-version = "1.0.36"
+version = "1.0.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
 dependencies = [
  "proc-macro2",
 ]
@@ -2468,18 +2582,18 @@ dependencies = [
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.2"
+version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd"
+checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
 dependencies = [
  "bitflags 2.6.0",
 ]
 
 [[package]]
 name = "regex"
-version = "1.10.5"
+version = "1.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
+checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2533,7 +2647,7 @@ dependencies = [
  "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "hyper-rustls 0.24.2",
  "ipnet",
  "js-sys",
@@ -2562,9 +2676,9 @@ dependencies = [
 
 [[package]]
 name = "reqwest"
-version = "0.12.5"
+version = "0.12.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
+checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
 dependencies = [
  "base64 0.22.1",
  "bytes",
@@ -2572,9 +2686,9 @@ dependencies = [
  "futures-core",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "http-body-util",
- "hyper 1.3.1",
+ "hyper 1.4.1",
  "hyper-rustls 0.27.2",
  "hyper-util",
  "ipnet",
@@ -2585,8 +2699,8 @@ dependencies = [
  "percent-encoding",
  "pin-project-lite",
  "quinn",
- "rustls 0.23.10",
- "rustls-pemfile 2.1.2",
+ "rustls 0.23.12",
+ "rustls-pemfile 2.1.3",
  "rustls-pki-types",
  "serde",
  "serde_json",
@@ -2600,7 +2714,7 @@ dependencies = [
  "wasm-bindgen-futures",
  "web-sys",
  "webpki-roots 0.26.3",
- "winreg 0.52.0",
+ "windows-registry",
 ]
 
 [[package]]
@@ -2618,7 +2732,7 @@ dependencies = [
  "h2",
  "http 0.2.12",
  "http-body 0.4.6",
- "hyper 0.14.29",
+ "hyper 0.14.30",
  "hyper-tls",
  "ipnet",
  "js-sys",
@@ -2695,9 +2809,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
 [[package]]
 name = "rustc-hash"
-version = "1.1.0"
+version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
 
 [[package]]
 name = "rustc_version"
@@ -2744,28 +2858,15 @@ dependencies = [
 
 [[package]]
 name = "rustls"
-version = "0.22.4"
+version = "0.23.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
+checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
 dependencies = [
  "log",
- "ring",
- "rustls-pki-types",
- "rustls-webpki 0.102.4",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls"
-version = "0.23.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
-dependencies = [
  "once_cell",
  "ring",
  "rustls-pki-types",
- "rustls-webpki 0.102.4",
+ "rustls-webpki 0.102.6",
  "subtle",
  "zeroize",
 ]
@@ -2793,9 +2894,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-pemfile"
-version = "2.1.2"
+version = "2.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
+checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
 dependencies = [
  "base64 0.22.1",
  "rustls-pki-types",
@@ -2803,9 +2904,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-pki-types"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
+checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
 
 [[package]]
 name = "rustls-webpki"
@@ -2819,9 +2920,9 @@ dependencies = [
 
 [[package]]
 name = "rustls-webpki"
-version = "0.102.4"
+version = "0.102.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
 dependencies = [
  "ring",
  "rustls-pki-types",
@@ -2890,9 +2991,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
 dependencies = [
  "bitflags 2.6.0",
  "core-foundation",
@@ -2903,9 +3004,9 @@ dependencies = [
 
 [[package]]
 name = "security-framework-sys"
-version = "2.11.0"
+version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
+checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -2924,7 +3025,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00421ed8fa0c995f07cde48ba6c89e80f2b312f74ff637326f392fbfd23abe02"
 dependencies = [
  "httpdate",
- "reqwest 0.12.5",
+ "reqwest 0.12.7",
  "rustls 0.21.12",
  "sentry-backtrace",
  "sentry-contexts",
@@ -3028,9 +3129,9 @@ dependencies = [
 
 [[package]]
 name = "serde"
-version = "1.0.203"
+version = "1.0.209"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
 dependencies = [
  "serde_derive",
 ]
@@ -3059,22 +3160,23 @@ dependencies = [
 
 [[package]]
 name = "serde_derive"
-version = "1.0.203"
+version = "1.0.209"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.118"
+version = "1.0.127"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
+checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
 dependencies = [
  "itoa",
+ "memchr",
  "ryu",
  "serde",
 ]
@@ -3101,6 +3203,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
 [[package]]
 name = "sha2"
 version = "0.10.8"
@@ -3131,6 +3244,12 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
 [[package]]
 name = "signature"
 version = "2.2.0"
@@ -3207,7 +3326,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -3229,9 +3348,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.68"
+version = "2.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
+checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3249,6 +3368,9 @@ name = "sync_wrapper"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+dependencies = [
+ "futures-core",
+]
 
 [[package]]
 name = "synstructure"
@@ -3291,34 +3413,35 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
 
 [[package]]
 name = "tempfile"
-version = "3.10.1"
+version = "3.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
 dependencies = [
  "cfg-if",
  "fastrand",
+ "once_cell",
  "rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "thiserror"
-version = "1.0.61"
+version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.61"
+version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -3374,9 +3497,9 @@ dependencies = [
 
 [[package]]
 name = "tinyvec"
-version = "1.6.1"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
 dependencies = [
  "tinyvec_macros",
 ]
@@ -3406,35 +3529,34 @@ checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
 name = "tokio"
-version = "1.38.0"
+version = "1.39.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
+checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
 dependencies = [
  "backtrace",
  "bytes",
  "libc",
  "mio",
- "num_cpus",
  "pin-project-lite",
  "socket2",
  "tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
 ]
 
 [[package]]
 name = "tokio-macros"
-version = "2.3.0"
+version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -3463,7 +3585,7 @@ version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
 dependencies = [
- "rustls 0.23.10",
+ "rustls 0.23.12",
  "rustls-pki-types",
  "tokio",
 ]
@@ -3508,15 +3630,15 @@ dependencies = [
 
 [[package]]
 name = "tower-layer"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
 
 [[package]]
 name = "tower-service"
-version = "0.3.2"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
 
 [[package]]
 name = "tracing"
@@ -3538,7 +3660,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
@@ -3643,9 +3765,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.4"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
 
 [[package]]
 name = "universal-hash"
@@ -3665,16 +3787,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
 
 [[package]]
 name = "ureq"
-version = "2.9.7"
+version = "2.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd"
+checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a"
 dependencies = [
  "base64 0.22.1",
  "log",
  "once_cell",
- "rustls 0.22.4",
+ "rustls 0.23.12",
  "rustls-pki-types",
- "rustls-webpki 0.102.4",
  "url",
  "webpki-roots 0.26.3",
 ]
@@ -3699,9 +3820,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uuid"
-version = "1.9.1"
+version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439"
+checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
 dependencies = [
  "serde",
 ]
@@ -3720,9 +3841,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
 [[package]]
 name = "version_check"
-version = "0.9.4"
+version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
 [[package]]
 name = "walkdir"
@@ -3751,34 +3872,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
 dependencies = [
  "cfg-if",
+ "once_cell",
  "wasm-bindgen-macro",
 ]
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
 dependencies = [
  "bumpalo",
  "log",
  "once_cell",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.42"
+version = "0.4.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
+checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -3788,9 +3910,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -3798,22 +3920,22 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.93"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
 
 [[package]]
 name = "wasm-streams"
@@ -3830,9 +3952,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.69"
+version = "0.3.70"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -3881,11 +4003,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
 [[package]]
 name = "winapi-util"
-version = "0.1.8"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
 dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -3901,7 +4023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
 dependencies = [
  "windows-core",
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -3910,7 +4032,37 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -3928,7 +4080,16 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
 dependencies = [
- "windows-targets 0.52.5",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
 ]
 
 [[package]]
@@ -3948,18 +4109,18 @@ dependencies = [
 
 [[package]]
 name = "windows-targets"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
 dependencies = [
- "windows_aarch64_gnullvm 0.52.5",
- "windows_aarch64_msvc 0.52.5",
- "windows_i686_gnu 0.52.5",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
  "windows_i686_gnullvm",
- "windows_i686_msvc 0.52.5",
- "windows_x86_64_gnu 0.52.5",
- "windows_x86_64_gnullvm 0.52.5",
- "windows_x86_64_msvc 0.52.5",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
 ]
 
 [[package]]
@@ -3970,9 +4131,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
 
 [[package]]
 name = "windows_aarch64_msvc"
@@ -3982,9 +4143,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
 
 [[package]]
 name = "windows_i686_gnu"
@@ -3994,15 +4155,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
 
 [[package]]
 name = "windows_i686_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
 
 [[package]]
 name = "windows_i686_msvc"
@@ -4012,9 +4173,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
 
 [[package]]
 name = "windows_x86_64_gnu"
@@ -4024,9 +4185,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
@@ -4036,9 +4197,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
 
 [[package]]
 name = "windows_x86_64_msvc"
@@ -4048,9 +4209,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.52.5"
+version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winreg"
@@ -4071,21 +4232,11 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "winreg"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
-dependencies = [
- "cfg-if",
- "windows-sys 0.48.0",
-]
-
 [[package]]
 name = "worker"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cda58fc54ac88c54387c3a48f46fa066ccb48f2d81f7514dc6c240a2a13bf4e"
+checksum = "c3bd73bd2ea409ae91df99293cbed8b892d39c4c0df5039b646be7586df62c6b"
 dependencies = [
  "async-trait",
  "bytes",
@@ -4093,7 +4244,7 @@ dependencies = [
  "futures-channel",
  "futures-util",
  "http 1.1.0",
- "http-body 1.0.0",
+ "http-body 1.0.1",
  "js-sys",
  "matchit",
  "pin-project",
@@ -4129,14 +4280,14 @@ dependencies = [
 
 [[package]]
 name = "worker-macros"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc9b615cb8673627508ceab81cd3a6080ff53d8de696bd5f5a08ac8bb1491a9c"
+checksum = "0bbf47d65e77652febb28abedac18b317d8dfe4e57f0d8d9998c4e991fca8e23"
 dependencies = [
  "async-trait",
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
  "wasm-bindgen",
  "wasm-bindgen-futures",
  "wasm-bindgen-macro-support",
@@ -4145,9 +4296,9 @@ dependencies = [
 
 [[package]]
 name = "worker-sys"
-version = "0.3.3"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ef1e184943d021cf4511d29ddf411a8e75ee511d4fc587f207b60ddcb9698a2"
+checksum = "a4fbb72a85a6509e5ac5dcd1361543468be089ff5ea5c932043b6d0aeac7b6a5"
 dependencies = [
  "cfg-if",
  "js-sys",
@@ -4211,6 +4362,27 @@ dependencies = [
  "time",
 ]
 
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.76",
+]
+
 [[package]]
 name = "zeroize"
 version = "1.8.1"
@@ -4228,7 +4400,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.68",
+ "syn 2.0.76",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 02d1047..db79208 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,25 +32,30 @@ debug = 1
 anyhow = "1.0.86"
 assert_matches = "1.5.0"
 async-trait = "0.1.80"
-axum = "0.6"
+axum = { version = "0.7.5", default-features = false }
+axum-extra = "0.9"
 base64 = "0.21.7"
 # TODO(mendess): delete. Left here for legacy_req_parse.
 bincode = "1.3.3"
+bytes = "1"
 cap = "0.1.2"
 capnp = "0.18.13"
 cfg-if = "1.0.0"
 chrono = { version = "0.4.38", default-features = false, features = ["clock", "wasmbind"] }
 clap = { version = "4.5.7", features = ["derive"] }
 config = "0.13.4"
+constcat = "0.5.0"
 criterion = { version = "0.5.1", features = ["async_tokio"] }
 deepsize = { version = "0.2.0" }
 futures = "0.3.30"
 getrandom = "0.2.15"
 hex = { version = "0.4.3", features = ["serde"] }
+headers = "0.4"
 hpke-rs = "0.2.0"
 hpke-rs-crypto = "0.2.0"
 hpke-rs-rust-crypto = "0.2.0"
-http = "0.2"
+http = "1"
+http-body-util = "0.1"
 hyper = "0.14.29"
 itertools = "0.12.1"
 matchit = "0.7.3"
@@ -73,12 +78,14 @@ strum = { version = "0.26.3", features = ["derive"] }
 thiserror = "1.0.61"
 tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
 tower = "0.4.13"
+tower-service = "0.3"
 tracing = "0.1.40"
 tracing-core = "0.1.32"
 tracing-subscriber = "0.3.18"
 url = { version = "2.5.2", features = ["serde"] }
 webpki = "0.22.4"
-worker = "0.3.3"
+worker = { version = "0.3.3", features = ["http"] }
+wasm-streams = "0.4"
 x509-parser = "0.15.1"
 
 [workspace.dependencies.sentry]
diff --git a/crates/daphne-server/Cargo.toml b/crates/daphne-server/Cargo.toml
index 30a964c..4e0067e 100644
--- a/crates/daphne-server/Cargo.toml
+++ b/crates/daphne-server/Cargo.toml
@@ -13,12 +13,12 @@ repository.workspace = true
 description = "Workers backend for Daphne"
 
 [dependencies]
-axum.workspace = true
+axum = "0.6.0"
 daphne = { path = "../daphne" }
 daphne-service-utils = { path = "../daphne-service-utils", features = ["durable_requests"] }
 futures.workspace = true
 hex.workspace = true
-http.workspace = true
+http = "0.2"
 hyper.workspace = true
 mappable-rc = "0.1.1"
 p256.workspace = true
diff --git a/crates/daphne-server/docker/example-service.Dockerfile b/crates/daphne-server/docker/example-service.Dockerfile
index 5c22ef0..0358d86 100644
--- a/crates/daphne-server/docker/example-service.Dockerfile
+++ b/crates/daphne-server/docker/example-service.Dockerfile
@@ -1,7 +1,7 @@
 # Copyright (c) 2024 Cloudflare, Inc. All rights reserved.
 # SPDX-License-Identifier: BSD-3-Clause
 
-FROM rust:1.76-bookworm AS builder
+FROM rust:1.80.1-bookworm AS builder
 
 RUN apt update && \
     apt install -y \
diff --git a/crates/daphne-service-utils/src/durable_requests/mod.rs b/crates/daphne-service-utils/src/durable_requests/mod.rs
index 394f768..a4f220f 100644
--- a/crates/daphne-service-utils/src/durable_requests/mod.rs
+++ b/crates/daphne-service-utils/src/durable_requests/mod.rs
@@ -284,6 +284,15 @@ where
     }
 }
 
+impl<P> DurableRequest<P>
+where
+    P: Default + AsRef<[u8]>,
+{
+    pub fn take_body(&mut self) -> P {
+        std::mem::take(&mut self.body)
+    }
+}
+
 #[cfg(test)]
 mod test {
     use daphne::{DapBatchBucket, DapVersion};
diff --git a/crates/daphne-worker/Cargo.toml b/crates/daphne-worker/Cargo.toml
index 46dfccf..bb7a340 100644
--- a/crates/daphne-worker/Cargo.toml
+++ b/crates/daphne-worker/Cargo.toml
@@ -16,13 +16,20 @@ description = "Workers backend for Daphne"
 crate-type = ["cdylib", "rlib"]
 
 [dependencies]
+axum.workspace = true
+axum-extra = { workspace = true, features = ["typed-header"] }
 # TODO(mendess): delete
 bincode.workspace = true
+bytes.workspace = true
 chrono = { workspace = true, default-features = false, features = ["clock", "wasmbind"] }
+constcat.workspace = true
 daphne = { path = "../daphne", features = ["prometheus"] }
 futures = { workspace = true, optional = true }
 getrandom = { workspace = true, features = ["js"] }
+headers.workspace = true
 hex.workspace = true
+http-body-util.workspace = true
+http.workspace = true
 prio.workspace = true
 prometheus.workspace = true
 rand.workspace = true
@@ -35,7 +42,9 @@ tracing-core.workspace = true
 tracing-subscriber = { workspace = true, features = ["env-filter", "json"]}
 tracing.workspace = true
 url.workspace = true
+wasm-streams.workspace = true
 worker.workspace = true
+tower-service.workspace = true
 
 [dependencies.daphne-service-utils]
 path = "../daphne-service-utils"
diff --git a/crates/daphne-worker/src/lib.rs b/crates/daphne-worker/src/lib.rs
index c0f6bc3..28e2a96 100644
--- a/crates/daphne-worker/src/lib.rs
+++ b/crates/daphne-worker/src/lib.rs
@@ -13,6 +13,10 @@ use tracing::error;
 use worker::Error;
 
 pub use crate::tracing_utils::initialize_tracing;
+pub use axum::{
+    body::Body,
+    response::{IntoResponse, Response},
+};
 pub use daphne::DapRequest;
 
 pub(crate) fn int_err<S: ToString>(s: S) -> Error {
diff --git a/crates/daphne-worker/src/storage_proxy/middleware.rs b/crates/daphne-worker/src/storage_proxy/middleware.rs
new file mode 100644
index 0000000..7291e10
--- /dev/null
+++ b/crates/daphne-worker/src/storage_proxy/middleware.rs
@@ -0,0 +1,119 @@
+// Copyright (c) 2024 Cloudflare, Inc. All rights reserved.
+// SPDX-License-Identifier: BSD-3-Clause
+
+use std::{
+    sync::{Arc, OnceLock},
+    time::Duration,
+};
+
+use axum::{
+    extract::{Path, State},
+    middleware::Next,
+    response::IntoResponse,
+};
+use axum_extra::{
+    headers::{authorization::Bearer, Authorization},
+    TypedHeader,
+};
+use daphne::messages::constant_time_eq;
+use http::{Method, StatusCode};
+use tower_service::Service;
+
+use super::RequestContext;
+
+/// Performs bearer token auth of a request.
+pub async fn bearer_auth(
+    ctx: State<Arc<RequestContext>>,
+    bearer: TypedHeader<Authorization<Bearer>>,
+    request: axum::extract::Request,
+    mut next: Next,
+) -> axum::response::Response {
+    static TRUSTED_TOKEN: OnceLock<Option<String>> = OnceLock::new();
+
+    let Some(trusted_token) = TRUSTED_TOKEN.get_or_init(|| {
+        ctx.env
+            .var("DAPHNE_SERVER_AUTH_TOKEN")
+            .ok()
+            .map(|t| t.to_string())
+    }) else {
+        tracing::warn!("trusted bearer token not configured");
+        return (
+            StatusCode::INTERNAL_SERVER_ERROR,
+            "Authorization token for storage proxy is not configured",
+        )
+            .into_response();
+    };
+
+    if !constant_time_eq(bearer.token().as_bytes(), trusted_token.as_bytes()) {
+        return (StatusCode::UNAUTHORIZED, "Incorrect authorization token").into_response();
+    }
+
+    match next.call(request.map(axum::body::Body::new)).await {
+        Ok(r) => r,
+        Err(infalible) => match infalible {},
+    }
+}
+
+#[worker::send]
+pub async fn time_kv_requests(
+    ctx: State<Arc<RequestContext>>,
+    method: Method,
+    request: axum::extract::Request,
+    mut next: Next,
+) -> axum::response::Response {
+    let start = worker::Date::now();
+    let response = match next.call(request).await {
+        Ok(response) => response,
+        Err(infallible) => match infallible {},
+    };
+    let elapsed = elapsed(&start);
+
+    let op = match method {
+        Method::GET => "kv_get",
+        Method::POST => "kv_put",
+        Method::PUT => "kv_put_if_not_exists",
+        Method::DELETE => "kv_delete",
+        method => {
+            tracing::warn!(?method, status = ?response.status(), "unexpected method in kv request");
+            "unknown"
+        }
+    };
+    let status = if response.status().is_success() {
+        "success"
+    } else {
+        "error"
+    };
+    ctx.metrics
+        .kv_request_time_seconds_observe(op, status, elapsed);
+
+    response
+}
+
+#[worker::send]
+pub async fn time_do_requests(
+    ctx: State<Arc<RequestContext>>,
+    Path(uri): Path<String>,
+    request: axum::extract::Request,
+    mut next: Next,
+) -> axum::response::Response {
+    let start = worker::Date::now();
+    let response = match next.call(request).await {
+        Ok(response) => response,
+        Err(infallible) => match infallible {},
+    };
+    let elapsed = elapsed(&start);
+    ctx.metrics.durable_request_time_seconds_observe(
+        &uri,
+        if response.status().is_success() {
+            "success"
+        } else {
+            "error"
+        },
+        elapsed,
+    );
+    response
+}
+
+fn elapsed(date: &worker::Date) -> Duration {
+    Duration::from_millis(worker::Date::now().as_millis() - date.as_millis())
+}
diff --git a/crates/daphne-worker/src/storage_proxy/mod.rs b/crates/daphne-worker/src/storage_proxy/mod.rs
index ae1a26b..97d4a9f 100644
--- a/crates/daphne-worker/src/storage_proxy/mod.rs
+++ b/crates/daphne-worker/src/storage_proxy/mod.rs
@@ -70,104 +70,110 @@
 //! [to_uri]: daphne_service_utils::durable_requests::bindings::DurableMethod::to_uri
 
 mod metrics;
+mod middleware;
 
-use std::{sync::OnceLock, time::Duration};
+use std::{sync::Arc, time::Duration};
 
 pub use self::metrics::Metrics;
-use daphne::auth::BearerToken;
+use axum::{
+    extract::{Path, State},
+    middleware::from_fn_with_state,
+    response::{IntoResponse, Response},
+    routing,
+};
+use axum_extra::TypedHeader;
+use bytes::Bytes;
 use daphne::messages::Time;
 use daphne_service_utils::durable_requests::{
     DurableRequest, ObjectIdFrom, DO_PATH_PREFIX, KV_PATH_PREFIX,
 };
 use daphne_service_utils::http_headers::STORAGE_PROXY_PUT_KV_EXPIRATION;
+use headers::Header;
+use http::{HeaderMap, StatusCode};
 use prometheus::Registry;
+use tower_service::Service;
 use tracing::warn;
 use url::Url;
-use worker::{js_sys::Uint8Array, Delay, Env, Request, RequestInit, Response};
+use worker::{
+    js_sys::Uint8Array, Date, Delay, Env, HttpRequest, HttpResponse, Request, RequestInit,
+};
+
 const KV_BINDING_DAP_CONFIG: &str = "DAP_CONFIG";
 
-struct RequestContext<'e> {
-    req: Request,
-    env: &'e Env,
+struct RequestContext {
+    env: Env,
     metrics: Metrics,
 }
 
-/// Check if the request's authorization. If unauthorized, return the reason why.
-fn unauthorized_reason(ctx: &RequestContext) -> Option<worker::Result<Response>> {
-    static TRUSTED_TOKEN: OnceLock<Option<BearerToken>> = OnceLock::new();
+struct Error(worker::Error);
 
-    let access_denied = |reason| Response::error(format!("Unauthorized: {reason}"), 401);
-    let auth = match ctx.req.headers().get("Authorization") {
-        Ok(Some(auth)) => auth,
-        Ok(None) => return Some(access_denied("missing Authorization header")),
-        Err(e) => return Some(Err(e)),
-    };
-    let Some(provided_token) = auth.strip_prefix("Bearer ").map(BearerToken::from) else {
-        return Some(access_denied("Authorization header has unexpected prefix"));
-    };
-    let Some(trusted_token) = TRUSTED_TOKEN.get_or_init(|| {
-        ctx.env
-            .var("DAPHNE_SERVER_AUTH_TOKEN")
-            .ok()
-            .map(|t| t.to_string())
-            .map(BearerToken::from)
-    }) else {
-        tracing::warn!("trusted bearer token not configured");
-        return Some(Response::error(
-            "Authorization token for storage proxy is not configured",
-            500,
-        ));
-    };
-    if &provided_token != trusted_token {
-        return Some(access_denied("Incorrect authorization token"));
+impl From<worker::kv::KvError> for Error {
+    fn from(value: worker::kv::KvError) -> Self {
+        Self(worker::Error::from(value))
     }
+}
 
-    None
+impl From<worker::Error> for Error {
+    fn from(value: worker::Error) -> Self {
+        Self(value)
+    }
+}
+
+impl IntoResponse for Error {
+    fn into_response(self) -> axum::response::Response {
+        (StatusCode::INTERNAL_SERVER_ERROR, self.0.to_string()).into_response()
+    }
 }
 
 /// Handle a proxy request. This is the entry point of the Worker.
-#[allow(clippy::no_effect_underscore_binding)]
-pub async fn handle_request(
-    req: Request,
-    env: &Env,
-    registry: &Registry,
-) -> worker::Result<Response> {
-    let mut ctx = RequestContext {
+pub async fn handle_request(req: HttpRequest, env: Env, registry: &Registry) -> Response {
+    let ctx = Arc::new(RequestContext {
         metrics: Metrics::new(registry),
-        req,
         env,
-    };
-
-    if let Some(error_response) = unauthorized_reason(&ctx) {
-        return error_response;
-    }
-
-    let path = ctx.req.path();
-    if let Some(uri) = path
-        .strip_prefix(KV_PATH_PREFIX)
-        .and_then(|s| s.strip_prefix('/'))
-    {
-        handle_kv_request(&mut ctx, uri).await
-    } else if let Some(uri) = path.strip_prefix(DO_PATH_PREFIX) {
-        handle_do_request(&mut ctx, uri).await
-    } else {
-        #[cfg(feature = "test-utils")]
-        if let Some("") = path.strip_prefix(daphne_service_utils::durable_requests::PURGE_STORAGE) {
-            return storage_purge(&ctx).await;
-        } else if let Some("") =
-            path.strip_prefix(daphne_service_utils::durable_requests::STORAGE_READY)
-        {
-            return Response::ok("");
-        }
-
-        tracing::error!("path {path:?} was invalid");
-        Response::error("invalid base path", 400)
-    }
+    });
+
+    let router = axum::Router::new()
+        .route(
+            constcat::concat!(KV_PATH_PREFIX, "/*path"),
+            routing::get(kv_get)
+                .post(kv_put)
+                .put(kv_put_if_not_exists)
+                .delete(kv_delete)
+                .route_layer(from_fn_with_state(
+                    ctx.clone(),
+                    middleware::time_kv_requests,
+                )),
+        )
+        .route(
+            constcat::concat!(DO_PATH_PREFIX, "/*path"),
+            routing::any(handle_do_request).layer(from_fn_with_state(
+                ctx.clone(),
+                middleware::time_do_requests,
+            )),
+        );
+
+    #[cfg(feature = "test-utils")]
+    let router = router
+        .route(
+            daphne_service_utils::durable_requests::PURGE_STORAGE,
+            routing::any(storage_purge),
+        )
+        .route(
+            daphne_service_utils::durable_requests::STORAGE_READY,
+            routing::any(StatusCode::OK),
+        );
+
+    let mut router = router
+        .layer(from_fn_with_state(ctx.clone(), middleware::bearer_auth))
+        .with_state(ctx);
+    router.call(req).await.unwrap()
 }
 
-#[cfg(feature = "test-utils")]
 /// Clear all storage. Only available to tests
-async fn storage_purge(ctx: &RequestContext<'_>) -> worker::Result<Response> {
+#[cfg(feature = "test-utils")]
+#[tracing::instrument(skip(ctx))]
+#[worker::send]
+async fn storage_purge(ctx: State<Arc<RequestContext>>) -> impl IntoResponse + 'static {
     use daphne_service_utils::durable_requests::bindings::{DurableMethod, TestStateCleaner};
 
     let kv_delete = async {
@@ -181,7 +187,7 @@ async fn storage_purge(ctx: &RequestContext<'_>) -> worker::Result<Response> {
 
     let do_delete = async {
         let req = Request::new_with_init(
-            &format!("https://fake-host{}", TestStateCleaner::DeleteAll.to_uri(),),
+            &format!("https://fake-host{}", TestStateCleaner::DeleteAll.to_uri()),
             RequestInit::new().with_method(worker::Method::Post),
         )?;
 
@@ -193,168 +199,170 @@ async fn storage_purge(ctx: &RequestContext<'_>) -> worker::Result<Response> {
             .await
     };
 
-    futures::try_join!(kv_delete, do_delete)?;
-    Response::empty()
+    futures::try_join!(kv_delete, do_delete)
+        .map(|_| ())
+        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())
 }
 
-fn parse_expiration_header(ctx: &RequestContext) -> Result<Option<Time>, worker::Error> {
-    let expiration_header = ctx.req.headers().get(STORAGE_PROXY_PUT_KV_EXPIRATION)?;
-    expiration_header
-        .map(|expiration| {
-            expiration.parse::<Time>().map_err(|e| {
-                worker::Error::RustError(format!("Failed to parse expiration header: {e:?}"))
-            })
-        })
-        .transpose()
-}
+#[derive(Debug)]
+struct ExpirationHeader(Time);
 
-fn elapsed(date: &worker::Date) -> Duration {
-    Duration::from_millis(worker::Date::now().as_millis() - date.as_millis())
-}
+impl Header for ExpirationHeader {
+    fn name() -> &'static http::HeaderName {
+        static HEADER_NAME: http::HeaderName =
+            http::HeaderName::from_static(STORAGE_PROXY_PUT_KV_EXPIRATION);
 
-/// Handle a kv request.
-async fn handle_kv_request(ctx: &mut RequestContext<'_>, key: &str) -> worker::Result<Response> {
-    let start = worker::Date::now();
+        &HEADER_NAME
+    }
 
-    match ctx.req.method() {
-        worker::Method::Get => {
-            let bytes = ctx
-                .env
-                .kv(KV_BINDING_DAP_CONFIG)
-                .inspect_err(|_| {
-                    ctx.metrics
-                        .kv_request_time_seconds_observe("read", "error", elapsed(&start))
-                })?
-                .get(key)
-                .bytes()
-                .await?;
+    fn encode<E: Extend<http::HeaderValue>>(&self, values: &mut E) {
+        values.extend(http::HeaderValue::from_str(&self.0.to_string()))
+    }
+
+    fn decode<'i, I>(values: &mut I) -> Result<Self, headers::Error>
+    where
+        Self: Sized,
+        I: Iterator<Item = &'i http::HeaderValue>,
+    {
+        values
+            .next()
+            .and_then(|v| v.to_str().ok())
+            .and_then(|v| v.parse().ok())
+            .map(Self)
+            .ok_or_else(headers::Error::invalid)
+    }
+}
 
-            let elapsed = elapsed(&start);
+impl ExpirationHeader {
+    fn at_least_60s_from_now(self) -> Self {
+        // KV wont let you request an expiration that isn't at least 60 seconds into the
+        // future. If you try to do so, it will return a 400. The problem is, the only error
+        // the worker API returns is a JsValue that might contain a string that might
+        // explain that.
+        //
+        // In order to avoid parsing the error message we opt to just "hardcode" the
+        // expiration to be, at least 65 seconds from now. Effectively expiring the value as soon
+        // as possible. The extra 5 seconds are just in case we take a really long time from here to
+        // the request.
+        let now_plus_65_seconds = (Date::now().as_millis() / 1000) + 65;
+        Self(u64::max(self.0, now_plus_65_seconds))
+    }
+}
 
-            if let Some(bytes) = bytes {
-                ctx.metrics
-                    .kv_request_time_seconds_observe("read", "success", elapsed);
+#[tracing::instrument(skip(ctx))]
+#[worker::send]
+async fn kv_get(
+    ctx: State<Arc<RequestContext>>,
+    Path(key): Path<String>,
+) -> Result<impl IntoResponse, Error> {
+    let bytes = ctx.env.kv(KV_BINDING_DAP_CONFIG)?.get(&key).bytes().await?;
 
-                Response::from_bytes(bytes)
-            } else {
-                ctx.metrics
-                    .kv_request_time_seconds_observe("read", "not_found", elapsed);
+    if let Some(bytes) = bytes {
+        Ok((StatusCode::OK, bytes).into_response())
+    } else {
+        Ok((StatusCode::NOT_FOUND, "value not found").into_response())
+    }
+}
 
-                Response::error("value not found", 404)
+#[tracing::instrument(skip(ctx, body))]
+#[worker::send]
+async fn kv_put(
+    ctx: State<Arc<RequestContext>>,
+    expiration: Option<TypedHeader<ExpirationHeader>>,
+    Path(key): Path<String>,
+    body: Bytes,
+) -> Result<impl IntoResponse, Error> {
+    let expiration = expiration.map(|TypedHeader(header)| header);
+
+    match ctx.env.kv(KV_BINDING_DAP_CONFIG)?.put_bytes(&key, &body) {
+        Ok(mut put) => {
+            if let Some(expiration_unix_timestamp) = expiration {
+                put = put.expiration(expiration_unix_timestamp.at_least_60s_from_now().0);
             }
+            if let Err(error) = put.execute().await {
+                tracing::warn!(
+                    ?error,
+                    "Swallowed error from KV POST, this will hopefully retry later"
+                );
+            }
+        }
+        Err(error) => {
+            tracing::warn!(
+                ?error,
+                "Swallowed error from KV POST creation, this will hopefully retry later"
+            );
         }
-        worker::Method::Post => {
-            let expiration_unix_timestamp = parse_expiration_header(ctx)?;
-
-            match ctx
-                .env
-                .kv(KV_BINDING_DAP_CONFIG)?
-                .put_bytes(key, &ctx.req.bytes().await?)
-            {
-                Ok(mut put) => {
-                    if let Some(expiration_unix_timestamp) = expiration_unix_timestamp {
-                        ctx.metrics.kv_request_time_seconds_observe(
-                            "post",
-                            "success",
-                            elapsed(&start),
-                        );
-
-                        put = put.expiration(expiration_unix_timestamp);
-                    }
-                    if let Err(error) = put.execute().await {
-                        ctx.metrics.kv_request_time_seconds_observe(
-                            "post",
-                            "error_execute_post",
-                            elapsed(&start),
-                        );
-
-                        tracing::warn!(
-                            ?error,
-                            "Swallowed error from KV POST, this will hopefully retry later"
-                        );
-                    }
+    }
+
+    Ok(StatusCode::OK.into_response())
+}
+
+#[tracing::instrument(skip(ctx, body))]
+#[worker::send]
+async fn kv_put_if_not_exists(
+    ctx: State<Arc<RequestContext>>,
+    expiration: Option<TypedHeader<ExpirationHeader>>,
+    Path(key): Path<String>,
+    body: Bytes,
+) -> Result<impl IntoResponse, Error> {
+    let expiration = expiration.map(|TypedHeader(header)| header);
+
+    let kv = ctx.env.kv(KV_BINDING_DAP_CONFIG)?;
+    if kv
+        .list()
+        .prefix(key.clone())
+        .execute()
+        .await?
+        .keys
+        .into_iter()
+        .any(|k| k.name == key)
+    {
+        Ok(StatusCode::CONFLICT.into_response())
+    } else {
+        match kv.put_bytes(&key, &body) {
+            Ok(mut put) => {
+                if let Some(expiration_unix_timestamp) = expiration {
+                    put = put.expiration(expiration_unix_timestamp.at_least_60s_from_now().0);
                 }
-                Err(error) => {
+                if let Err(error) = put.execute().await {
                     tracing::warn!(
                         ?error,
-                        "Swallowed error from KV POST creation, this will hopefully retry later"
+                        "Swallowed error from KV PUT, this will hopefully retry later"
                     );
                 }
             }
-
-            Response::empty()
-        }
-
-        worker::Method::Put => {
-            let expiration_unix_timestamp = parse_expiration_header(ctx)?;
-
-            let kv = ctx.env.kv(KV_BINDING_DAP_CONFIG)?;
-            if kv
-                .list()
-                .prefix(key.into())
-                .execute()
-                .await?
-                .keys
-                .into_iter()
-                .any(|k| k.name == key)
-            {
-                ctx.metrics.kv_request_time_seconds_observe(
-                    "put",
-                    "error_conflict",
-                    elapsed(&start),
+            Err(error) => {
+                tracing::warn!(
+                    ?error,
+                    "Swallowed error from KV PUT creation, this will hopefully retry later"
                 );
-
-                Response::error(String::new(), 409 /* Conflict */)
-            } else {
-                match kv.put_bytes(key, &ctx.req.bytes().await?) {
-                    Ok(mut put) => {
-                        if let Some(expiration_unix_timestamp) = expiration_unix_timestamp {
-                            ctx.metrics.kv_request_time_seconds_observe(
-                                "put",
-                                "success",
-                                elapsed(&start),
-                            );
-
-                            put = put.expiration(expiration_unix_timestamp);
-                        }
-                        if let Err(error) = put.execute().await {
-                            ctx.metrics.kv_request_time_seconds_observe(
-                                "put",
-                                "error_execute_put",
-                                elapsed(&start),
-                            );
-
-                            tracing::warn!(
-                                ?error,
-                                "Swallowed error from KV PUT, this will hopefully retry later"
-                            );
-                        }
-                    }
-                    Err(error) => {
-                        tracing::warn!(
-                            ?error,
-                            "Swallowed error from KV PUT creation, this will hopefully retry later"
-                        );
-                    }
-                }
-
-                Response::empty()
             }
         }
-        worker::Method::Delete => {
-            ctx.env.kv(KV_BINDING_DAP_CONFIG)?.delete(key).await?;
 
-            ctx.metrics
-                .kv_request_time_seconds_observe("delete", "success", elapsed(&start));
-
-            Response::empty()
-        }
-        _ => Response::error(String::new(), 405 /* Method not allowed */),
+        Ok(StatusCode::OK.into_response())
     }
 }
 
+#[tracing::instrument(skip(ctx))]
+#[worker::send]
+async fn kv_delete(
+    ctx: State<Arc<RequestContext>>,
+    Path(key): Path<String>,
+) -> Result<impl IntoResponse, Error> {
+    ctx.env.kv(KV_BINDING_DAP_CONFIG)?.delete(&key).await?;
+
+    Ok(StatusCode::OK.into_response())
+}
+
 /// Handle a durable object request
-async fn handle_do_request(ctx: &mut RequestContext<'_>, uri: &str) -> worker::Result<Response> {
+#[tracing::instrument(skip(ctx, headers, body))]
+#[worker::send]
+async fn handle_do_request(
+    ctx: State<Arc<RequestContext>>,
+    headers: HeaderMap,
+    Path(uri): Path<String>,
+    body: Bytes,
+) -> Result<impl IntoResponse, Error> {
     const RETRY_DELAYS: &[Duration] = &[
         Duration::from_millis(100),
         Duration::from_millis(500),
@@ -362,35 +370,37 @@ async fn handle_do_request(ctx: &mut RequestContext<'_>, uri: &str) -> worker::R
         Duration::from_millis(3_000),
     ];
 
-    let buf = ctx.req.bytes().await.map_err(|e| {
-        tracing::error!(error = ?e, "failed to get bytes");
-        e
-    })?;
-    tracing::debug!(len = buf.len(), "deserializing do request");
-    let parsed_req = DurableRequest::try_from(&buf)
+    let durable_request = DurableRequest::try_from(body.as_ref())
         .map_err(|e| worker::Error::RustError(format!("invalid format: {e:?}")))?;
 
-    let binding = ctx.env.durable_object(&parsed_req.binding)?;
+    let http_request = {
+        let mut do_req = RequestInit::new();
+        do_req.with_method(worker::Method::Post);
+        do_req.with_headers(headers.into());
+        tracing::debug!(len = body.len(), "deserializing do request");
 
-    let mut do_req = RequestInit::new();
-    do_req.with_method(worker::Method::Post);
-    do_req.with_headers(ctx.req.headers().clone());
-    if let body @ [_a, ..] = parsed_req.body() {
-        let buffer =
-            Uint8Array::new_with_length(body.len().try_into().map_err(|_| {
+        {
+            let body = durable_request.body();
+            let buffer = Uint8Array::new_with_length(body.len().try_into().map_err(|_| {
                 worker::Error::RustError(format!("buffer is too long {}", body.len()))
             })?);
-        buffer.copy_from(body);
-        do_req.with_body(Some(buffer.into()));
-    }
-    let url = Url::parse("https://fake-host/").unwrap().join(uri).unwrap();
-    let do_req = Request::new_with_init(url.as_str(), &do_req)?;
+            // TODO: avoid this copy
+            buffer.copy_from(body);
+            do_req.with_body(Some(buffer.into()));
+        }
+        let url = Url::parse("https://fake-host/")
+            .unwrap()
+            .join(&uri)
+            .unwrap();
+        Request::new_with_init(url.as_str(), &do_req)?
+    };
 
-    let obj = match &parsed_req.id {
-        ObjectIdFrom::Name(name) => binding.id_from_name(name)?,
-        ObjectIdFrom::Hex(hex) => binding.id_from_string(hex)?,
+    let binding = ctx.env.durable_object(&durable_request.binding)?;
+    let obj = match &durable_request.id {
+        ObjectIdFrom::Name(name) => binding.id_from_name(name.as_str())?,
+        ObjectIdFrom::Hex(hex) => binding.id_from_string(hex.as_str())?,
     };
-    let attempts = if parsed_req.retry {
+    let attempts = if durable_request.retry {
         RETRY_DELAYS.len() + 1
     } else {
         1
@@ -405,31 +415,19 @@ async fn handle_do_request(ctx: &mut RequestContext<'_>, uri: &str) -> worker::R
             obj.get_stub()
         }?;
 
-        let start = worker::Date::now();
-
-        match stub.fetch_with_request(do_req.clone()?).await {
+        match stub.fetch_with_request(http_request.clone()?).await {
             Ok(ok) => {
-                let elapsed = elapsed(&start);
-
-                ctx.metrics
-                    .durable_request_time_seconds_observe(uri, "success", elapsed);
-
                 ctx.metrics.durable_request_retry_count_inc(
                     (attempt - 1).try_into().unwrap(),
-                    &parsed_req.binding,
-                    uri,
+                    &durable_request.binding,
+                    &uri,
                 );
-                return Ok(ok);
+                return Ok(HttpResponse::try_from(ok).unwrap());
             }
             Err(error) => {
-                let elapsed = elapsed(&start);
-
-                ctx.metrics
-                    .durable_request_time_seconds_observe(uri, "error", elapsed);
-
                 if attempt < attempts {
                     warn!(
-                        binding = parsed_req.binding,
+                        binding = durable_request.binding,
                         path = uri,
                         attempt,
                         error = ?error,
@@ -439,8 +437,8 @@ async fn handle_do_request(ctx: &mut RequestContext<'_>, uri: &str) -> worker::R
                     attempt += 1;
                 } else {
                     ctx.metrics
-                        .durable_request_retry_count_inc(-1, &parsed_req.binding, uri);
-                    return Err(error);
+                        .durable_request_retry_count_inc(-1, &durable_request.binding, &uri);
+                    return Err(error.into());
                 }
             }
         }
diff --git a/crates/daphne/src/pine/flp.rs b/crates/daphne/src/pine/flp.rs
index a0a34b7..a19cce5 100644
--- a/crates/daphne/src/pine/flp.rs
+++ b/crates/daphne/src/pine/flp.rs
@@ -44,10 +44,7 @@ use prio::{
 };
 use rand::Rng;
 
-use super::{
-    chunk_count, f64_to_field, field_to_f64, PineConfig, NUM_WR_SUCCESSES, NUM_WR_TESTS,
-    USAGE_WR_JOINT_RAND,
-};
+use super::{chunk_count, f64_to_field, field_to_f64, PineConfig, USAGE_WR_JOINT_RAND};
 
 /// The main FLP used to check each measurement's validity.
 ///
@@ -71,7 +68,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         &self,
         gradient: impl Iterator<Item = f64>,
         wr_joint_rand_seed: &Seed<SEED_SIZE>,
-    ) -> Result<(Vec<F>, [F; NUM_WR_TESTS]), VdafError> {
+    ) -> Result<(Vec<F>, Vec<F>), VdafError> {
         let mut meas = Vec::with_capacity(self.input_len());
         self.append_encoded_gradient(&mut meas, gradient)?;
         let wr_test_results =
@@ -88,7 +85,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         meas: &mut Vec<F>,
         gradient: impl Iterator<Item = f64>,
     ) -> Result<(), VdafError> {
-        let two_to_frac_bits = f64::from(1 << self.cfg.frac_bits);
+        let two_to_frac_bits = f64::from(1 << self.cfg.param.frac_bits);
 
         // Encode the gradient and compute the squared norm.
         let mut sq_norm = F::zero();
@@ -100,7 +97,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
             gradient_len += 1;
         }
 
-        if gradient_len != self.cfg.dimension {
+        if gradient_len != self.cfg.param.dimension {
             return Err(VdafError::Uncategorized(
                 "gradient encoding failed: gradient has unexpected dimension".into(),
             ));
@@ -134,15 +131,15 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         &self,
         meas: &mut Vec<F>,
         wr_joint_rand_seed: &Seed<SEED_SIZE>,
-    ) -> Result<[F; NUM_WR_TESTS], VdafError> {
-        let wr_test_results =
-            self.run_wr_tests::<X, SEED_SIZE>(&meas[..self.cfg.dimension], wr_joint_rand_seed);
+    ) -> Result<Vec<F>, VdafError> {
+        let wr_test_results = self
+            .run_wr_tests::<X, SEED_SIZE>(&meas[..self.cfg.param.dimension], wr_joint_rand_seed);
 
         let mut wr_success_count = 0;
-        for wr_test_result in wr_test_results {
+        for wr_test_result in &wr_test_results {
             // Append the range-checked test result.
             let (in_range, v, _u) = range_checked(
-                wr_test_result,
+                *wr_test_result,
                 -self.cfg.wr_test_bound + F::one(),
                 self.cfg.wr_test_bound,
             );
@@ -152,7 +149,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
             }
 
             // Append the success bit.
-            if in_range && wr_success_count < NUM_WR_SUCCESSES {
+            if in_range && wr_success_count < self.cfg.param.num_wr_successes {
                 wr_success_count += 1;
                 meas.push(F::one());
             } else {
@@ -160,7 +157,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
             }
         }
 
-        if wr_success_count != NUM_WR_SUCCESSES {
+        if wr_success_count != self.cfg.param.num_wr_successes {
             return Err(VdafError::Uncategorized(
                 "append wraparound tests failed: insufficient number of successes".into(),
             ));
@@ -175,14 +172,14 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         &self,
         gradient: &[F],
         wr_joint_rand_seed: &Seed<SEED_SIZE>,
-    ) -> [F; NUM_WR_TESTS] {
-        debug_assert_eq!(gradient.len(), self.cfg.dimension);
+    ) -> Vec<F> {
+        debug_assert_eq!(gradient.len(), self.cfg.param.dimension);
         let mut xof = X::seed_stream(wr_joint_rand_seed, &self.cfg.dst(USAGE_WR_JOINT_RAND), &[]);
-        let rand_len_per_test = chunk_count(4, self.cfg.dimension);
-        let mut rand = vec![0_u8; rand_len_per_test * NUM_WR_TESTS];
+        let rand_len_per_test = chunk_count(4, self.cfg.param.dimension);
+        let mut rand = vec![0_u8; rand_len_per_test * self.cfg.param.num_wr_tests];
         xof.fill(&mut rand[..]);
 
-        let mut wr_test_results = [F::zero(); NUM_WR_TESTS];
+        let mut wr_test_results = vec![F::zero(); self.cfg.param.num_wr_tests];
         for (wr_test_result, rand_per_test) in wr_test_results
             .iter_mut()
             .zip(rand.chunks(rand_len_per_test))
@@ -225,7 +222,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         shares_inv: F,
     ) -> Result<F, FlpError> {
         debug_assert_eq!(bit_checked.len(), self.cfg.bit_checked_len);
-        debug_assert_eq!(buf.capacity(), self.cfg.chunk_len * 2);
+        debug_assert_eq!(buf.capacity(), self.cfg.param.chunk_len * 2);
         buf.clear();
 
         // Construct a polynomial from the bits and evaluate it at `r_bit_check`. The
@@ -243,7 +240,7 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
         // the reference implementation.
         let mut r_power = F::one();
         bit_checked
-            .chunks(self.cfg.chunk_len)
+            .chunks(self.cfg.param.chunk_len)
             .map(|chunk| {
                 for x in chunk {
                     buf.push(r_power * *x);
@@ -289,10 +286,10 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
     ) -> Result<(F, F), FlpError> {
         debug_assert_eq!(
             wr_test_bits.len(),
-            NUM_WR_TESTS * (self.cfg.wr_test_bits + 1)
+            self.cfg.param.num_wr_tests * (self.cfg.wr_test_bits + 1)
         );
-        debug_assert_eq!(wr_test_results.len(), NUM_WR_TESTS,);
-        debug_assert_eq!(buf.capacity(), self.cfg.chunk_len * 2);
+        debug_assert_eq!(wr_test_results.len(), self.cfg.param.num_wr_tests);
+        debug_assert_eq!(buf.capacity(), self.cfg.param.chunk_len * 2);
         buf.clear();
 
         // For each test, add up the success bits (i.e., each `wr_test_g`).
@@ -320,14 +317,14 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
             wr_success_count += wr_test_g;
             r_power *= r_wr_test;
 
-            if buf.len() == self.cfg.chunk_len * 2 {
+            if buf.len() == self.cfg.param.chunk_len * 2 {
                 wr_tests_result += gadget[0].call(buf)?;
                 buf.clear();
             }
         }
 
-        if buf.len() % self.cfg.chunk_len * 2 != 0 {
-            for _ in buf.len()..self.cfg.chunk_len * 2 {
+        if buf.len() % self.cfg.param.chunk_len * 2 != 0 {
+            for _ in buf.len()..self.cfg.param.chunk_len * 2 {
                 buf.push(F::zero());
             }
             wr_tests_result += gadget[0].call(buf)?;
@@ -337,7 +334,11 @@ impl<F: FftFriendlyFieldElement> PineType<F> {
             wr_tests_result,
             // The success count is equal to the expected value.
             wr_success_count
-                - F::from(F::Integer::try_from(NUM_WR_SUCCESSES).unwrap()) * shares_inv,
+                - F::from(
+                    F::Integer::try_from(self.cfg.param.num_wr_successes).map_err(|_| {
+                        FlpError::Valid("num_wr_successes is too large for field".to_string())
+                    })?,
+                ) * shares_inv,
         ))
     }
 
@@ -373,7 +374,7 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
     }
 
     fn decode_result(&self, data: &[F], _num_measurements: usize) -> Result<Vec<f64>, FlpError> {
-        let two_to_frac_bits = f64::from(1 << self.cfg.frac_bits);
+        let two_to_frac_bits = f64::from(1 << self.cfg.param.frac_bits);
 
         data.iter()
             .map(|encoded| field_to_f64(*encoded, two_to_frac_bits))
@@ -384,7 +385,7 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
     fn gadget(&self) -> Vec<Box<dyn Gadget<F>>> {
         vec![Box::new(ParallelSum::new(
             Mul::new(self.cfg.gadget_calls),
-            self.cfg.chunk_len,
+            self.cfg.param.chunk_len,
         ))]
     }
 
@@ -395,7 +396,7 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
         joint_rand: &[F],
         num_shares: usize,
     ) -> Result<F, FlpError> {
-        let mut buf = Vec::with_capacity(self.cfg.chunk_len * 2);
+        let mut buf = Vec::with_capacity(self.cfg.param.chunk_len * 2);
         let shares_inv = F::from(F::Integer::try_from(num_shares).unwrap()).inv();
 
         // Unpack the encoded measurement. It is composed of the following components:
@@ -411,15 +412,16 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
         //   whether the test succeeded (i.e., the result is in the specified range).
         //
         // * For each wraparound test, the result `wr_test_result`.
-        let (_encoded_gradient, rest) = meas.split_at(self.cfg.dimension);
+        let (_encoded_gradient, rest) = meas.split_at(self.cfg.param.dimension);
         let (bit_checked, rest) = rest.split_at(self.cfg.bit_checked_len);
-        let (wr_test_results, rest) = rest.split_at(NUM_WR_TESTS);
+        let (wr_test_results, rest) = rest.split_at(self.cfg.param.num_wr_tests);
         assert!(rest.is_empty());
 
         // Unpack the bit checked values.
         let (sq_norm_v_bits, rest) = bit_checked.split_at(self.cfg.sq_norm_bits);
         let (sq_norm_u_bits, rest) = rest.split_at(self.cfg.sq_norm_bits);
-        let (wr_test_bits, rest) = rest.split_at(NUM_WR_TESTS * (self.cfg.wr_test_bits + 1));
+        let (wr_test_bits, rest) =
+            rest.split_at(self.cfg.param.num_wr_tests * (self.cfg.wr_test_bits + 1));
         assert!(rest.is_empty());
 
         // Unpack the joint randomness.
@@ -452,7 +454,7 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
     }
 
     fn truncate(&self, mut input: Vec<F>) -> Result<Vec<F>, FlpError> {
-        input.truncate(self.cfg.dimension);
+        input.truncate(self.cfg.param.dimension);
         Ok(input)
     }
 
@@ -461,15 +463,15 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
     }
 
     fn proof_len(&self) -> usize {
-        2 * self.cfg.chunk_len + 2 * ((1 + self.cfg.gadget_calls).next_power_of_two() - 1) + 1
+        2 * self.cfg.param.chunk_len + 2 * ((1 + self.cfg.gadget_calls).next_power_of_two() - 1) + 1
     }
 
     fn verifier_len(&self) -> usize {
-        2 * self.cfg.chunk_len + 2
+        2 * self.cfg.param.chunk_len + 2
     }
 
     fn output_len(&self) -> usize {
-        self.cfg.dimension
+        self.cfg.param.dimension
     }
 
     fn joint_rand_len(&self) -> usize {
@@ -477,7 +479,7 @@ impl<F: FftFriendlyFieldElement> Type for PineType<F> {
     }
 
     fn prove_rand_len(&self) -> usize {
-        self.cfg.chunk_len * 2
+        self.cfg.param.chunk_len * 2
     }
 
     fn query_rand_len(&self) -> usize {
@@ -521,7 +523,7 @@ impl<F: FftFriendlyFieldElement> Type for PineTypeSquaredNormEqual<F> {
                 vec![F::zero(), F::zero(), F::one()],
                 self.cfg.gadget_calls_sq_norm_equal,
             ),
-            self.cfg.chunk_len_sq_norm_equal,
+            self.cfg.param.chunk_len_sq_norm_equal,
         ))]
     }
 
@@ -532,8 +534,8 @@ impl<F: FftFriendlyFieldElement> Type for PineTypeSquaredNormEqual<F> {
         _joint_rand: &[F],
         _num_shares: usize,
     ) -> Result<F, FlpError> {
-        let mut buf = Vec::with_capacity(self.cfg.chunk_len_sq_norm_equal);
-        let (encoded_gradient, rest) = meas.split_at(self.cfg.dimension);
+        let mut buf = Vec::with_capacity(self.cfg.param.chunk_len_sq_norm_equal);
+        let (encoded_gradient, rest) = meas.split_at(self.cfg.param.dimension);
         let (sq_norm_v_bits, _rest) = rest.split_at(self.cfg.sq_norm_bits);
 
         // Compute the squared norm of the gradient.
@@ -541,7 +543,7 @@ impl<F: FftFriendlyFieldElement> Type for PineTypeSquaredNormEqual<F> {
         // This corresponds to the third call to `parallel_sum()` in the validity circuit of the
         // reference implementation.
         let sq_norm = encoded_gradient
-            .chunks(self.cfg.chunk_len_sq_norm_equal)
+            .chunks(self.cfg.param.chunk_len_sq_norm_equal)
             .map(|chunk| {
                 buf.extend(chunk);
                 for _ in buf.len()..buf.capacity() {
@@ -563,13 +565,13 @@ impl<F: FftFriendlyFieldElement> Type for PineTypeSquaredNormEqual<F> {
     }
 
     fn proof_len(&self) -> usize {
-        self.cfg.chunk_len_sq_norm_equal
+        self.cfg.param.chunk_len_sq_norm_equal
             + 2 * ((1 + self.cfg.gadget_calls_sq_norm_equal).next_power_of_two() - 1)
             + 1
     }
 
     fn verifier_len(&self) -> usize {
-        self.cfg.chunk_len_sq_norm_equal + 2
+        self.cfg.param.chunk_len_sq_norm_equal + 2
     }
 
     fn joint_rand_len(&self) -> usize {
@@ -577,7 +579,7 @@ impl<F: FftFriendlyFieldElement> Type for PineTypeSquaredNormEqual<F> {
     }
 
     fn prove_rand_len(&self) -> usize {
-        self.cfg.chunk_len_sq_norm_equal
+        self.cfg.param.chunk_len_sq_norm_equal
     }
 
     fn query_rand_len(&self) -> usize {
@@ -621,7 +623,7 @@ mod tests {
         pine.flp
             .append_encoded_gradient(&mut meas, gradient.clone())
             .unwrap();
-        let (encoded_gradient, rest) = meas.split_at(pine.flp.cfg.dimension);
+        let (encoded_gradient, rest) = meas.split_at(pine.flp.cfg.param.dimension);
         let (sq_norm_v_bits, rest) = rest.split_at(pine.flp.cfg.sq_norm_bits);
         let (sq_norm_u_bits, rest) = rest.split_at(pine.flp.cfg.sq_norm_bits);
         assert!(rest.is_empty());
@@ -660,7 +662,7 @@ mod tests {
             .flp
             .encode_with_wr_joint_rand::<XofTurboShake128, 16>(gradient, &Seed::generate().unwrap())
             .unwrap();
-        let (_encoded_gradient, rest) = input.split_at(pine.flp.cfg.dimension);
+        let (_encoded_gradient, rest) = input.split_at(pine.flp.cfg.param.dimension);
         let (bit_checked, rest) = rest.split_at(pine.flp.cfg.bit_checked_len);
         assert!(rest.is_empty());
 
@@ -795,7 +797,7 @@ mod tests {
         input.extend_from_slice(&wr_test_results);
 
         // Flip the first bit of the first wraparound result.
-        let i = pine.flp.cfg.dimension + (2 * pine.flp.cfg.sq_norm_bits);
+        let i = pine.flp.cfg.param.dimension + (2 * pine.flp.cfg.sq_norm_bits);
         input[i] = if input[i] == Field128::one() {
             Field128::zero()
         } else {
@@ -820,8 +822,9 @@ mod tests {
         input.extend_from_slice(&wr_test_results);
 
         // Flip the first wraparound result success bit.
-        let i =
-            pine.flp.cfg.dimension + (2 * pine.flp.cfg.sq_norm_bits) + pine.flp.cfg.wr_test_bits;
+        let i = pine.flp.cfg.param.dimension
+            + (2 * pine.flp.cfg.sq_norm_bits)
+            + pine.flp.cfg.wr_test_bits;
         input[i] = if input[i] == Field128::one() {
             Field128::zero()
         } else {
diff --git a/crates/daphne/src/pine/mod.rs b/crates/daphne/src/pine/mod.rs
index 0779f21..f3dc915 100644
--- a/crates/daphne/src/pine/mod.rs
+++ b/crates/daphne/src/pine/mod.rs
@@ -23,8 +23,6 @@ use prio::{
 use flp::{PineType, PineTypeSquaredNormEqual};
 
 const ALPHA: f64 = 8.7;
-const NUM_WR_TESTS: usize = 100;
-const NUM_WR_SUCCESSES: usize = 100;
 
 const DST_SIZE: usize = 7;
 
@@ -59,12 +57,16 @@ impl Pine128 {
         chunk_len_sq_norm_equal: usize,
     ) -> Result<Self, VdafError> {
         Self::new(
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            1,
+            &PineParam {
+                norm_bound,
+                dimension,
+                frac_bits,
+                chunk_len,
+                chunk_len_sq_norm_equal,
+                num_proofs: 1,
+                num_wr_tests: 100,
+                num_wr_successes: 100,
+            },
             0xffff_ffff,
         )
     }
@@ -82,38 +84,54 @@ impl Pine64 {
         chunk_len_sq_norm_equal: usize,
     ) -> Result<Self, VdafError> {
         Self::new(
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            2,
+            &PineParam {
+                norm_bound,
+                dimension,
+                frac_bits,
+                chunk_len,
+                chunk_len_sq_norm_equal,
+                num_proofs: 2,
+                num_wr_tests: 100,
+                num_wr_successes: 100,
+            },
             0xffff_ffff,
         )
     }
 }
 
+#[derive(
+    Clone, Copy, Debug, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize,
+)]
+#[serde(rename_all = "snake_case")]
+#[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
+pub struct PineParam {
+    pub(crate) norm_bound: u64,
+    pub(crate) dimension: usize,
+    pub(crate) frac_bits: usize,
+    pub(crate) chunk_len: usize,
+    pub(crate) chunk_len_sq_norm_equal: usize,
+    pub(crate) num_proofs: u8,
+    pub(crate) num_wr_tests: usize,
+    pub(crate) num_wr_successes: usize,
+}
+
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub(crate) struct PineConfig<F> {
     // PINE parameters
-    dimension: usize,
-    frac_bits: usize,
+    param: PineParam,
     sq_norm_bound: F,
     sq_norm_bits: usize,
     wr_test_bound: F,
     wr_test_bits: usize,
-    num_proofs: u8,
     algorithm_id: u32,
 
-    // FLP parameters for the main circuit
-    chunk_len: usize,
+    // Additional parameters for the main circuit
     bit_checked_len: usize,
     encoded_gradient_len: usize,
     encoded_input_len: usize,
     gadget_calls: usize,
 
-    // FLP parameters for the squared-norm equality check
-    chunk_len_sq_norm_equal: usize,
+    // Additional parameters for the squared-norm equality check
     gadget_calls_sq_norm_equal: usize,
 }
 
@@ -127,7 +145,7 @@ impl<F> PineConfig<F> {
     }
 
     pub(crate) fn input_len(&self) -> usize {
-        self.encoded_input_len + NUM_WR_TESTS
+        self.encoded_input_len + self.param.num_wr_tests
     }
 }
 
@@ -152,17 +170,15 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize> Pine<F, X, SEED_SIZE
     /// * `num_proofs`: The number of FLP proofs to generate.
     ///
     /// * `algorithm_id`: The VDAF algorithm ID.
-    pub(crate) fn new(
-        norm_bound: u64,
-        dimension: usize,
-        frac_bits: usize,
-        chunk_len: usize,
-        chunk_len_sq_norm_equal: usize,
-        num_proofs: u8,
-        algorithm_id: u32,
-    ) -> Result<Self, VdafError> {
-        if norm_bound == 0
-            || F::Integer::try_from(usize::try_from(norm_bound).map_err(|e| {
+    pub(crate) fn new(param: &PineParam, algorithm_id: u32) -> Result<Self, VdafError> {
+        if param.num_wr_tests < param.num_wr_successes {
+            return Err(VdafError::Uncategorized(
+                "init failed: num_wr_successes is larger than num_wr_tests".to_string(),
+            ));
+        }
+
+        if param.norm_bound == 0
+            || F::Integer::try_from(usize::try_from(param.norm_bound).map_err(|e| {
                 VdafError::Uncategorized(format!(
                     "init failed: error while converting norm bound to usize: {e}"
                 ))
@@ -179,25 +195,25 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize> Pine<F, X, SEED_SIZE
         }
 
         // 24 is the largest number of fractional bits that we know we can support.
-        if frac_bits > 24 {
+        if param.frac_bits > 24 {
             return Err(VdafError::Uncategorized(
                 "init failed: too many fractional bits".into(),
             ));
         }
 
-        if dimension == 0 {
+        if param.dimension == 0 {
             return Err(VdafError::Uncategorized(
                 "init failed: 0-dimension inputs are invalid".into(),
             ));
         }
 
-        if frac_bits > 128 {
+        if param.frac_bits > 128 {
             return Err(VdafError::Uncategorized(
                 "init failed: number of fractional bits must not exceed 128".into(),
             ));
         }
 
-        let Some(sq_norm_bound_int) = norm_bound.checked_mul(norm_bound) else {
+        let Some(sq_norm_bound_int) = param.norm_bound.checked_mul(param.norm_bound) else {
             return Err(VdafError::Uncategorized(
                 "init failed: squared norm is too large for u64".into(),
             ));
@@ -223,7 +239,7 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize> Pine<F, X, SEED_SIZE
             #[allow(clippy::cast_possible_truncation)]
             #[allow(clippy::cast_sign_loss)]
             let wr_test_bound_int =
-                (((norm_bound as f64) * ALPHA).ceil() as u64 + 1).next_power_of_two();
+                (((param.norm_bound as f64) * ALPHA).ceil() as u64 + 1).next_power_of_two();
             let wr_test_bits = bits(2 * wr_test_bound_int - 1);
             let wr_test_bound = usize::try_from(wr_test_bound_int).map_err(|e| {
                 VdafError::Uncategorized(format!(
@@ -243,36 +259,33 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize> Pine<F, X, SEED_SIZE
         //
         // Length of the FLP input prefix that is used to derive the wraparound
         // joint randomness.
-        let encoded_gradient_len = dimension + 2 * sq_norm_bits;
+        let encoded_gradient_len = param.dimension + 2 * sq_norm_bits;
 
         // Length of the FLP input prefx that includes everything but the wraparound
         // check results.
-        let encoded_input_len = encoded_gradient_len + (1 + wr_test_bits) * NUM_WR_TESTS;
+        let encoded_input_len = encoded_gradient_len + (1 + wr_test_bits) * param.num_wr_tests;
 
         // Length of the bit-checked portion of the encoded measurement.
-        let bit_checked_len = 2 * sq_norm_bits + (1 + wr_test_bits) * NUM_WR_TESTS;
+        let bit_checked_len = 2 * sq_norm_bits + (1 + wr_test_bits) * param.num_wr_tests;
 
         // Number of gadget calls. The gadget is used for the bit checks, wraparound tests, and
         // squared norm computation.
-        let gadget_calls =
-            chunk_count(chunk_len, bit_checked_len) + chunk_count(chunk_len, NUM_WR_TESTS);
-        let gadget_calls_sq_norm_equal = chunk_count(chunk_len_sq_norm_equal, dimension);
+        let gadget_calls = chunk_count(param.chunk_len, bit_checked_len)
+            + chunk_count(param.chunk_len, param.num_wr_tests);
+        let gadget_calls_sq_norm_equal =
+            chunk_count(param.chunk_len_sq_norm_equal, param.dimension);
 
         let cfg = PineConfig {
-            dimension,
-            frac_bits,
+            param: *param,
             sq_norm_bound,
             sq_norm_bits,
             wr_test_bound,
             wr_test_bits,
-            num_proofs,
             algorithm_id,
-            chunk_len,
             bit_checked_len,
             encoded_gradient_len,
             encoded_input_len,
             gadget_calls,
-            chunk_len_sq_norm_equal,
             gadget_calls_sq_norm_equal,
         };
 
diff --git a/crates/daphne/src/pine/msg.rs b/crates/daphne/src/pine/msg.rs
index 3d87ee7..5b0b7e1 100644
--- a/crates/daphne/src/pine/msg.rs
+++ b/crates/daphne/src/pine/msg.rs
@@ -160,7 +160,7 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize>
                 proofs_share: iter::repeat_with(|| F::decode(bytes))
                     .take(
                         pine.flp_sq_norm_equal.proof_len()
-                            + pine.flp.proof_len() * usize::from(pine.flp.cfg.num_proofs),
+                            + pine.flp.proof_len() * usize::from(pine.flp.cfg.param.num_proofs),
                     )
                     .collect::<Result<Vec<_>, _>>()?,
                 wr_blind: Seed::decode(bytes)?,
diff --git a/crates/daphne/src/pine/vdaf.rs b/crates/daphne/src/pine/vdaf.rs
index 3f81be8..f0931bd 100644
--- a/crates/daphne/src/pine/vdaf.rs
+++ b/crates/daphne/src/pine/vdaf.rs
@@ -50,7 +50,7 @@ impl<F: FftFriendlyFieldElement, X, const SEED_SIZE: usize>
         bytes: &mut std::io::Cursor<&[u8]>,
     ) -> Result<Self, CodecError> {
         iter::repeat_with(|| F::decode(bytes))
-            .take(pine.flp.cfg.dimension)
+            .take(pine.flp.cfg.param.dimension)
             .collect::<Result<Vec<_>, _>>()
             .map(PineVec)
     }
@@ -137,10 +137,10 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize> Pine
 
     fn helper_proof_share(&self, seed: &[u8; SEED_SIZE]) -> Vec<F> {
         let mut xof = X::init(seed, &self.flp.cfg.dst(USAGE_PROOF_SHARE));
-        xof.update(&[self.flp.cfg.num_proofs, 1]); // num_proofs, agg_id
+        xof.update(&[self.flp.cfg.param.num_proofs, 1]); // num_proofs, agg_id
         xof.into_seed_stream().into_field_vec(
             self.flp_sq_norm_equal.proof_len()
-                + self.flp.proof_len() * usize::from(self.flp.cfg.num_proofs),
+                + self.flp.proof_len() * usize::from(self.flp.cfg.param.num_proofs),
         )
     }
 }
@@ -263,17 +263,19 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize> Pine
                 X::seed_stream(
                     &vf_joint_rand_seed,
                     &self.flp.cfg.dst(USAGE_VF_JOINT_RAND),
-                    &[self.flp.cfg.num_proofs],
+                    &[self.flp.cfg.param.num_proofs],
+                )
+                .into_field_vec(
+                    self.flp.joint_rand_len() * usize::from(self.flp.cfg.param.num_proofs),
                 )
-                .into_field_vec(self.flp.joint_rand_len() * usize::from(self.flp.cfg.num_proofs))
             };
 
             let prove_rands = {
                 let mut xof = X::init(&prove_seed, &self.flp.cfg.dst(USAGE_PROVE_RAND));
-                xof.update(&[self.flp.cfg.num_proofs]);
+                xof.update(&[self.flp.cfg.param.num_proofs]);
                 xof.into_seed_stream().into_field_vec(
                     self.flp_sq_norm_equal.prove_rand_len()
-                        + self.flp.prove_rand_len() * usize::from(self.flp.cfg.num_proofs),
+                        + self.flp.prove_rand_len() * usize::from(self.flp.cfg.param.num_proofs),
                 )
             };
             let (pr_sq_norm_equal, pr) =
@@ -281,7 +283,7 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize> Pine
 
             let mut proofs = Vec::with_capacity(
                 self.flp_sq_norm_equal.proof_len()
-                    + self.flp.proof_len() * usize::from(self.flp.cfg.num_proofs),
+                    + self.flp.proof_len() * usize::from(self.flp.cfg.param.num_proofs),
             );
 
             proofs.append(&mut self.flp_sq_norm_equal.prove(&meas, pr_sq_norm_equal, &[])?);
@@ -424,7 +426,7 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize>
         // Run the wraparound tests.
         {
             let wr_test_results = self.flp.run_wr_tests::<X, SEED_SIZE>(
-                &meas_share[..self.flp.cfg.dimension],
+                &meas_share[..self.flp.cfg.param.dimension],
                 &corrected_wr_joint_rand_seed,
             );
 
@@ -451,18 +453,20 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize>
                 X::seed_stream(
                     &corrected_vf_joint_rand_seed,
                     &self.flp.cfg.dst(USAGE_VF_JOINT_RAND),
-                    &[self.flp.cfg.num_proofs],
+                    &[self.flp.cfg.param.num_proofs],
+                )
+                .into_field_vec(
+                    self.flp.joint_rand_len() * usize::from(self.flp.cfg.param.num_proofs),
                 )
-                .into_field_vec(self.flp.joint_rand_len() * usize::from(self.flp.cfg.num_proofs))
             };
 
             let query_rands = {
                 let mut xof = X::init(verify_key, &self.flp.cfg.dst(USAGE_QUERY_RAND));
-                xof.update(&[self.flp.cfg.num_proofs]);
+                xof.update(&[self.flp.cfg.param.num_proofs]);
                 xof.update(nonce);
                 xof.into_seed_stream().into_field_vec(
                     self.flp_sq_norm_equal.query_rand_len()
-                        + self.flp.query_rand_len() * usize::from(self.flp.cfg.num_proofs),
+                        + self.flp.query_rand_len() * usize::from(self.flp.cfg.param.num_proofs),
                 )
             };
             let (qr_sq_norm_equal, qr) =
@@ -470,7 +474,7 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize>
 
             let mut verifiers_share = Vec::with_capacity(
                 self.flp_sq_norm_equal.verifier_len()
-                    + self.flp.verifier_len() * usize::from(self.flp.cfg.num_proofs),
+                    + self.flp.verifier_len() * usize::from(self.flp.cfg.param.num_proofs),
             );
 
             verifiers_share.append(&mut self.flp_sq_norm_equal.query(
@@ -598,7 +602,7 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize>
         _agg_param: &(),
         out_shares: M,
     ) -> Result<PineVec<F>, VdafError> {
-        let mut agg_share = PineVec(vec![F::zero(); self.flp.cfg.dimension]);
+        let mut agg_share = PineVec(vec![F::zero(); self.flp.cfg.param.dimension]);
         for out_share in out_shares {
             agg_share.accumulate(&out_share)?;
         }
@@ -617,7 +621,7 @@ impl<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usize> Coll
         agg_shares: M,
         _num_measurements: usize,
     ) -> Result<Vec<f64>, VdafError> {
-        let two_to_frac_bits = f64::from(1 << self.flp.cfg.frac_bits);
+        let two_to_frac_bits = f64::from(1 << self.flp.cfg.param.frac_bits);
         let [mut agg_result, agg_share_1] =
             agg_shares
                 .into_iter()
diff --git a/crates/daphne/src/vdaf/pine.rs b/crates/daphne/src/vdaf/pine.rs
index 911c2d1..1c6acfa 100644
--- a/crates/daphne/src/vdaf/pine.rs
+++ b/crates/daphne/src/vdaf/pine.rs
@@ -4,7 +4,7 @@
 use crate::{
     fatal_error,
     messages::taskprov::VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-    pine::{msg, vdaf::PinePrepState, Pine},
+    pine::{msg, vdaf::PinePrepState, Pine, PineParam},
     vdaf::{prep_finish, prep_finish_from_shares, unshard},
     DapAggregateResult, DapMeasurement,
 };
@@ -22,46 +22,20 @@ use serde::{Deserialize, Serialize};
 
 type Pine64HmacSha256Aes128 = Pine<Field64, XofHmacSha256Aes128, 32>;
 
-/// [Pine](crate::pine::Pine) parameters.
-#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
-#[serde(rename_all = "snake_case")]
-#[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
-pub struct PineConfig {
-    pub norm_bound: u64,
-    pub dimension: usize,
-    pub frac_bits: usize,
-    pub chunk_len: usize,
-    pub chunk_len_sq_norm_equal: usize,
-    pub var: PineVariant,
-}
-
 impl std::fmt::Display for PineConfig {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
-        let var_suffix = match var {
-            PineVariant::Field64HmacSha256Aes128 => "Field64HmacSha256Aes128",
+        let (param, name) = match self {
+            Self::Field64HmacSha256Aes128 { param } => (param, "Field64HmacSha256Aes128"),
         };
-
-        write!(
-            f,
-            "Pine{var_suffix}({norm_bound},{dimension},{frac_bits},{chunk_len},{chunk_len_sq_norm_equal})"
-        )
+        write!(f, "Pine{name}({param:?})")
     }
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
 #[serde(rename_all = "snake_case")]
 #[cfg_attr(any(test, feature = "test-utils"), derive(deepsize::DeepSizeOf))]
-pub enum PineVariant {
-    Field64HmacSha256Aes128,
+pub enum PineConfig {
+    Field64HmacSha256Aes128 { param: PineParam },
 }
 
 impl PineConfig {
@@ -70,35 +44,19 @@ impl PineConfig {
         measurement: DapMeasurement,
         nonce: &[u8; 16],
     ) -> Result<(Vec<u8>, [Vec<u8>; 2]), VdafError> {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
         let DapMeasurement::F64Vec(gradient) = &measurement else {
             return Err(VdafError::Dap(fatal_error!(
                 err = "unexpected measurement type"
             )));
         };
 
-        match var {
-            PineVariant::Field64HmacSha256Aes128 => {
-                let vdaf = Pine64HmacSha256Aes128::new(
-                    *norm_bound,
-                    *dimension,
-                    *frac_bits,
-                    *chunk_len,
-                    *chunk_len_sq_norm_equal,
-                    2,
-                    VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-                )
-                .map_err(|e| {
-                    VdafError::Dap(fatal_error!(err = ?e, "failed to create pine field"))
-                })?;
+        match self {
+            PineConfig::Field64HmacSha256Aes128 { param } => {
+                let vdaf =
+                    Pine64HmacSha256Aes128::new(param, VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128)
+                        .map_err(|e| {
+                        VdafError::Dap(fatal_error!(err = ?e, "invalid pine parameters"))
+                    })?;
                 shard_then_encode(&vdaf, gradient, nonce)
             }
         }
@@ -112,29 +70,13 @@ impl PineConfig {
         public_share_data: &[u8],
         input_share_data: &[u8],
     ) -> Result<(VdafPrepState, VdafPrepMessage), VdafError> {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
-        match (var, verify_key) {
-            (PineVariant::Field64HmacSha256Aes128, VdafVerifyKey::L32(verify_key)) => {
-                let vdaf = Pine64HmacSha256Aes128::new(
-                    *norm_bound,
-                    *dimension,
-                    *frac_bits,
-                    *chunk_len,
-                    *chunk_len_sq_norm_equal,
-                    2,
-                    VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-                )
-                .map_err(
-                    |e| VdafError::Dap(fatal_error!(err = ?e, "failed to create pine from norm_bound({norm_bound}), dimension{dimension}, frac_bits({frac_bits}), chunk_len({chunk_len})"))
-                )?;
+        match (self, verify_key) {
+            (PineConfig::Field64HmacSha256Aes128 { param }, VdafVerifyKey::L32(verify_key)) => {
+                let vdaf =
+                    Pine64HmacSha256Aes128::new(param, VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128)
+                        .map_err(|e| {
+                        VdafError::Dap(fatal_error!(err = ?e, "invalid pine parameters"))
+                    })?;
                 let (state, share) = prep_init(
                     vdaf,
                     verify_key,
@@ -161,33 +103,17 @@ impl PineConfig {
         host_share: VdafPrepMessage,
         peer_share_data: &[u8],
     ) -> Result<(VdafAggregateShare, Vec<u8>), VdafError> {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
-        match (var, host_state, host_share) {
+        match (self, host_state, host_share) {
             (
-                PineVariant::Field64HmacSha256Aes128,
+                PineConfig::Field64HmacSha256Aes128 { param },
                 VdafPrepState::Pine64HmacSha256Aes128(state),
                 VdafPrepMessage::Pine64HmacSha256Aes128(share),
             ) => {
-                let vdaf = Pine64HmacSha256Aes128::new(
-                    *norm_bound,
-                    *dimension,
-                    *frac_bits,
-                    *chunk_len,
-                    *chunk_len_sq_norm_equal,
-                    2,
-                    VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-                )
-                .map_err(
-                    |e| VdafError::Dap(fatal_error!(err = ?e, "failed to create pine from norm_bound({norm_bound}), dimension{dimension}, frac_bits({frac_bits}), chunk_len({chunk_len})")),
-                )?;
+                let vdaf =
+                    Pine64HmacSha256Aes128::new(param, VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128)
+                        .map_err(|e| {
+                        VdafError::Dap(fatal_error!(err = ?e, "invalid pine parameters"))
+                    })?;
                 let (out_share, outbound) =
                     prep_finish_from_shares(&vdaf, agg_id, state, share, peer_share_data)?;
                 let agg_share = VdafAggregateShare::Field64(prio::vdaf::AggregateShare::from(
@@ -206,32 +132,16 @@ impl PineConfig {
         host_state: VdafPrepState,
         peer_message_data: &[u8],
     ) -> Result<VdafAggregateShare, VdafError> {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
-        match (var, host_state) {
+        match (self, host_state) {
             (
-                PineVariant::Field64HmacSha256Aes128,
+                PineConfig::Field64HmacSha256Aes128 { param },
                 VdafPrepState::Pine64HmacSha256Aes128(state),
             ) => {
-                let vdaf = Pine64HmacSha256Aes128::new(
-                    *norm_bound,
-                    *dimension,
-                    *frac_bits,
-                    *chunk_len,
-                    *chunk_len_sq_norm_equal,
-                    2,
-                    VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-                )
-                .map_err(
-                    |e| VdafError::Dap(fatal_error!(err = ?e, "failed to create pine from norm_bound({norm_bound}), dimension{dimension}, frac_bits({frac_bits}), chunk_len({chunk_len})"))
-                )?;
+                let vdaf =
+                    Pine64HmacSha256Aes128::new(param, VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128)
+                        .map_err(|e| {
+                        VdafError::Dap(fatal_error!(err = ?e, "invalid pine parameters"))
+                    })?;
                 let out_share = prep_finish(&vdaf, state, peer_message_data)?;
                 let agg_share = VdafAggregateShare::Field64(prio::vdaf::AggregateShare::from(
                     prio::vdaf::OutputShare::from(out_share.0),
@@ -249,29 +159,13 @@ impl PineConfig {
         num_measurements: usize,
         agg_shares: M,
     ) -> Result<DapAggregateResult, VdafError> {
-        let PineConfig {
-            norm_bound,
-            dimension,
-            frac_bits,
-            chunk_len,
-            chunk_len_sq_norm_equal,
-            var,
-        } = self;
-
-        match var {
-            PineVariant::Field64HmacSha256Aes128 => {
-                let vdaf = Pine64HmacSha256Aes128::new(
-                    *norm_bound,
-                    *dimension,
-                    *frac_bits,
-                    *chunk_len,
-                    *chunk_len_sq_norm_equal,
-                    2,
-                    VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128,
-                )
-                .map_err(
-                    |e| VdafError::Dap(fatal_error!(err = ?e, "failed to create pine from norm_bound({norm_bound}), dimension{dimension}, frac_bits({frac_bits}), chunk_len({chunk_len})"))
-                )?;
+        match self {
+            PineConfig::Field64HmacSha256Aes128 { param } => {
+                let vdaf =
+                    Pine64HmacSha256Aes128::new(param, VDAF_TYPE_PINE_FIELD64_HMAC_SHA256_AES128)
+                        .map_err(|e| {
+                        VdafError::Dap(fatal_error!(err = ?e, "invalid pine parameters"))
+                    })?;
                 let agg_res = unshard(&vdaf, num_measurements, agg_shares)?;
                 Ok(DapAggregateResult::F64Vec(agg_res))
             }
@@ -300,21 +194,25 @@ fn prep_init<F: FftFriendlyFieldElement, X: Xof<SEED_SIZE>, const SEED_SIZE: usi
 #[cfg(test)]
 mod test {
     use crate::{
-        async_test_versions, hpke::HpkeKemId, testing::AggregationJobTest, vdaf::VdafConfig,
-        DapAggregateResult, DapAggregationParam, DapMeasurement, DapVersion,
+        async_test_versions, hpke::HpkeKemId, pine::PineParam, testing::AggregationJobTest,
+        vdaf::VdafConfig, DapAggregateResult, DapAggregationParam, DapMeasurement, DapVersion,
     };
 
-    use super::{PineConfig, PineVariant};
+    use super::PineConfig;
 
     async fn roundtrip_field64_hmac_sha256_aes128(version: DapVersion) {
         let mut t = AggregationJobTest::new(
-            &VdafConfig::Pine(PineConfig {
-                norm_bound: 32_000,
-                dimension: 1_000,
-                frac_bits: 20,
-                chunk_len: 10,
-                chunk_len_sq_norm_equal: 50,
-                var: PineVariant::Field64HmacSha256Aes128,
+            &VdafConfig::Pine(PineConfig::Field64HmacSha256Aes128 {
+                param: PineParam {
+                    norm_bound: 32_000,
+                    dimension: 1_000,
+                    frac_bits: 20,
+                    chunk_len: 10,
+                    chunk_len_sq_norm_equal: 50,
+                    num_proofs: 2,
+                    num_wr_tests: 100,
+                    num_wr_successes: 100,
+                },
             }),
             HpkeKemId::X25519HkdfSha256,
             version,


Test patch:
diff --git a/crates/daphne-worker-test/docker/runtests.Dockerfile b/crates/daphne-worker-test/docker/runtests.Dockerfile
index cfb2fe2..3e8d5b6 100644
--- a/crates/daphne-worker-test/docker/runtests.Dockerfile
+++ b/crates/daphne-worker-test/docker/runtests.Dockerfile
@@ -1,7 +1,7 @@
 # Copyright (c) 2024 Cloudflare, Inc. All rights reserved.
 # SPDX-License-Identifier: BSD-3-Clause
 
-FROM rust:1.76-bookworm
+FROM rust:1.80.1-bookworm
 
 WORKDIR /tmp/dap_test
 
diff --git a/crates/daphne-worker-test/docker/storage-proxy.Dockerfile b/crates/daphne-worker-test/docker/storage-proxy.Dockerfile
index d8577cc..e31fd3d 100644
--- a/crates/daphne-worker-test/docker/storage-proxy.Dockerfile
+++ b/crates/daphne-worker-test/docker/storage-proxy.Dockerfile
@@ -1,7 +1,7 @@
 # Copyright (c) 2024 Cloudflare, Inc. All rights reserved.
 # SPDX-License-Identifier: BSD-3-Clause
 
-FROM rust:1.76-bookworm AS builder
+FROM rust:1.80.1-bookworm AS builder
 RUN apt update && apt install -y capnproto clang cmake
 
 # Pre-install worker-build and Rust's wasm32 target to speed up our custom build command
diff --git a/crates/daphne-worker-test/src/lib.rs b/crates/daphne-worker-test/src/lib.rs
index f58f717..7141264 100644
--- a/crates/daphne-worker-test/src/lib.rs
+++ b/crates/daphne-worker-test/src/lib.rs
@@ -3,7 +3,7 @@
 
 use daphne_worker::initialize_tracing;
 use tracing::info;
-use worker::{event, Env, Request, Response, Result};
+use worker::{event, Env, HttpRequest};
 
 mod durable;
 mod utils;
@@ -12,7 +12,11 @@ mod utils;
 static CAP: cap::Cap<std::alloc::System> = cap::Cap::new(std::alloc::System, 65_000_000);
 
 #[event(fetch, respond_with_errors)]
-pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
+pub async fn main(
+    req: HttpRequest,
+    env: Env,
+    _ctx: worker::Context,
+) -> worker::Result<daphne_worker::Response> {
     // Optionally, get more helpful error messages written to the console in the case of a panic.
     utils::set_panic_hook();
 
@@ -20,7 +24,7 @@ pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Respo
     // before we do anything likely to fail.
     initialize_tracing(&env);
 
-    info!(method = ?req.method(), "{}", req.path());
+    info!(method = ?req.method(), "{}", req.uri().path());
 
-    daphne_worker::storage_proxy::handle_request(req, &env, &prometheus::Registry::new()).await
+    Ok(daphne_worker::storage_proxy::handle_request(req, env, &prometheus::Registry::new()).await)
 }
diff --git a/crates/daphne/src/pine/test_vec/mod.rs b/crates/daphne/src/pine/test_vec/mod.rs
index c7a41e0..4b16da4 100644
--- a/crates/daphne/src/pine/test_vec/mod.rs
+++ b/crates/daphne/src/pine/test_vec/mod.rs
@@ -9,7 +9,7 @@ use prio::{
 };
 use serde::Deserialize;
 
-use crate::pine::{Pine, ALPHA, NUM_WR_SUCCESSES, NUM_WR_TESTS};
+use crate::pine::{Pine, ALPHA};
 
 #[derive(Deserialize)]
 struct Encoded(#[serde(with = "hex")] Vec<u8>);
@@ -63,10 +63,10 @@ impl<F: FftFriendlyFieldElement> Pine<F, XofTurboShake128, 16> {
         {
             assert_eq!(test_vec.alpha, ALPHA);
         }
-        assert_eq!(test_vec.num_wr_checks, NUM_WR_TESTS);
-        assert_eq!(test_vec.num_wr_successes, NUM_WR_SUCCESSES);
+        assert_eq!(test_vec.num_wr_checks, 100);
+        assert_eq!(test_vec.num_wr_successes, 100);
         assert_eq!(test_vec.shares, 2);
-        assert_eq!(test_vec.proofs, usize::from(self.flp.cfg.num_proofs));
+        assert_eq!(test_vec.proofs, usize::from(self.flp.cfg.param.num_proofs));
 
         let mut out_shares_0 = Vec::new();
         let mut out_shares_1 = Vec::new();