├── h3 ├── LICENSE ├── src │ ├── webtransport │ │ ├── mod.rs │ │ └── session_id.rs │ ├── proto │ │ ├── mod.rs │ │ ├── push.rs │ │ ├── coding.rs │ │ └── varint.rs │ ├── client │ │ ├── mod.rs │ │ └── builder.rs │ ├── qpack │ │ ├── parse_error.rs │ │ ├── prefix_string │ │ │ ├── bitwin.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── field.rs │ │ ├── tests.rs │ │ └── prefix_int.rs │ ├── error │ │ ├── mod.rs │ │ ├── internal_error.rs │ │ └── codes.rs │ ├── ext.rs │ ├── lib.rs │ ├── server │ │ ├── mod.rs │ │ └── builder.rs │ ├── shared_state.rs │ ├── buf.rs │ └── tests │ │ └── mod.rs └── Cargo.toml ├── h3-quinn ├── LICENSE ├── Cargo.toml ├── README.md └── src │ └── datagram.rs ├── h3-datagram ├── LICENSE ├── src │ ├── lib.rs │ ├── client.rs │ ├── server.rs │ ├── quic_traits.rs │ ├── datagram.rs │ └── datagram_handler.rs ├── readme.md └── Cargo.toml ├── h3-webtransport ├── LICENSE ├── readme.md ├── src │ └── lib.rs └── Cargo.toml ├── fuzz ├── .gitignore ├── corpus │ └── fuzz_varint │ │ ├── 2695fb64cfb30ad72f32f5864e85f426349b74dc │ │ ├── 83125cc87a7b47bc7f6d49185ceb9585f409bead │ │ ├── 9a78211436f6d425ec38f5c4e02270801f3524f8 │ │ ├── ac9231da4082430afe8f4d40127814c613648d8e │ │ ├── 05dbe5663b80045fadf7b034f8e2aff60f3a7650 │ │ ├── 06be154b8785e183da98f1097de03378dec4c040 │ │ ├── 074a556fe49e356f482237e7dada61ca88205d09 │ │ ├── 07d07581ce26c5ba9bdb1f4e6fa2ec9a83348e91 │ │ ├── 07f8f275a05b0dff9b81e0957a1e4bd250c11bb4 │ │ ├── 0bd87ad361966694c1e37b5f35265cba9c55649d │ │ ├── 0f46533384ece35d94a9fba6b2200817256f583b │ │ ├── 0f4d4798761e3b22377802a4f65d671cc599f5bc │ │ ├── 1154e21f9b2c9ce472a65d1413d087df6139c9fa │ │ ├── 1830d1813ca62bf30d01a78f9d9769f0c42c70b1 │ │ ├── 1a93a3c22977c30760b53e4df4c533dabdf75304 │ │ ├── 1e4cc1d0b2a8a3ece5d8d9d68663dec6931eda33 │ │ ├── 2621e19d935ef85e86ac6820ea7255d3df9c6cd4 │ │ ├── 28442a0d140eebb7598457fbacd7e819f11fc887 │ │ ├── 2bf809711fe7ff3b06d578a99c2be1291d515dbc │ │ ├── 2f1b1a27fc52ed945783a7469c5315bf1e13722c │ │ ├── 2f9a1f7945c755ec45c034c6ca8f2643fa037411 │ │ ├── 303a18ef9307249358253e03a1ac0c714da4af70 │ │ ├── 3309e8a207dc835833cfd254b744ec0696342f27 │ │ ├── 33a243d628d76748d5436665a16e21c3c46cb814 │ │ ├── 34090d70dcdbee300109ee70d64a0bf627701027 │ │ ├── 34b5239a7f2185fbc73557c22b433e5850b4badc │ │ ├── 3a7885343257c4daad268f5b1934a826a1c53c68 │ │ ├── 3dcca884e398b0b3f0482c026b352b84ed7c716d │ │ ├── 3e023e5df0d59d64f42ab9f5ad89ecd2a0ab38b7 │ │ ├── 4072a44b17469f11cf1119579e549d95adb60695 │ │ ├── 428d56e4bdcd603e9a420d42cd9d87fd3698ef0d │ │ ├── 44cd247a1f3480ba331dcfe7540e4cbfc9548a20 │ │ ├── 46e5b0e24d7c4cdd47d2c3fc815f4c3d8ef6549e │ │ ├── 4819d7fc9f7d485cfe09402dfc85d71005980934 │ │ ├── 4b04f909eb2b5a2d652931368e0aab127bdc096e │ │ ├── 52538a80094f7b62948fd31e68fd17a315d8dc91 │ │ ├── 5555abc46f500aede6ee215e75dd4e4ff75cc729 │ │ ├── 56c9fcd9bf53e0a04c76186133b07344590bacaf │ │ ├── 57287827583cbb5a52b5fd74b1af2a299ceadb44 │ │ ├── 59b354e1be703f8532d12f9a57cdcb673f037dcc │ │ ├── 5ec10d338c397a4dde72c0bc45999b92c43e795c │ │ ├── 65cc90263dec0020ceabc727d33aa587e57fc175 │ │ ├── 668824f36ef6ef3ccb5e7db28669f62c29de4aba │ │ ├── 67749a2911c910c1e6b350047f1ecb614096a6d3 │ │ ├── 678d273418f58ac643b42aa5fbf6f4686c02faec │ │ ├── 68dcb6bfce4a84a58ea17326523b925fb371354e │ │ ├── 6af47c5e032c79fa9b557a8e24ff84461a2362bd │ │ ├── 6b580d9ce83327dbd0f52ac3949cb7c273ea3822 │ │ ├── 6d09033d9cabf4be617ebcd83cd8170e7f91720a │ │ ├── 7084c77693211dd27cce1a604873f5c92f9ca55f │ │ ├── 712dd1f53e53b3174e0f54b851151a958dc1c240 │ │ ├── 7243e3a79a0d4bc52f44207652eed4d0aea41c52 │ │ ├── 736e9a66d196ef004702bc6ace3c2af3cdd4717d │ │ ├── 73b74736664ad85828ce1be2e29fb4a68d24402b │ │ ├── 74513fa7e55a9396e1841ec4ac84f7fad0c57e16 │ │ ├── 773b297817ea5921527a25da52ed2192a90beb71 │ │ ├── 787bef1f01133454c80c267bded9d23a84b7b8a2 │ │ ├── 7d95bdd8514904a55a7b6a9a7a16d721f685b2cf │ │ ├── 8220668e727165c01e984415f8221183f2f1e2a6 │ │ ├── 83c74a72be602c1af64f40c3d1307b9201e36265 │ │ ├── 83f3d8f8ceb5732d961c42894a61e4c6fbb4745c │ │ ├── 84046e7e673b9e88133a7de460f28789be0ddd20 │ │ ├── 859366bbd30ea84562c37faf4af3ba3b4bbed3a2 │ │ ├── 861aedaea120f9f80f64a36e6467e780e88fa493 │ │ ├── 8642759cf26e9020195e726fdf607fbe011439d0 │ │ ├── 86875bb9309fe67a067775f61b78bf1d2c298997 │ │ ├── 887fb4e7d2eff0a43f7c061a38f2c0010921dfd3 │ │ ├── 88f3c8e6e819aa83da1ce48353b2bddaad759fb9 │ │ ├── 8b3f361ff3add0f0b5181d1b4565ef97ed36c8eb │ │ ├── 8bb0f17ae2d716c42c83c225c38e07cb0bef16e0 │ │ ├── 8c2975ae46264fd229593fbc72dbcc36739a1eac │ │ ├── 8e9774faccd71983c83a67dd04459f6ff68bde52 │ │ ├── 93c4cb3f2193b404a2cd713434a5c4cc563151ba │ │ ├── 94108451f94018cbaed1f4913759c9af14c6969f │ │ ├── 9ccda137ee54ee862b657df458f4aabb495162ac │ │ ├── 9cfceef0f039fd5cc7e7fe72dfbe33e87521ed33 │ │ ├── 9e274418d86f267e9a4bf459aded615c27c589c6 │ │ ├── 9f6e8a6a28994cc6479049e7d9dca7c779415327 │ │ ├── 9fc493a78af9e8289b24df2d690b822d4007a3fc │ │ ├── a1fca3a494b9d842105041e09b52dcddd59a3d51 │ │ ├── a40494f65e76f66b47c337e75431f4010f74f493 │ │ ├── a7c00395b2d0f3b7fe9ee068d25d44e1cedf1301 │ │ ├── b0c12ea324dd2592ef7d07757407f5983106ab24 │ │ ├── b1958cd1032c91560273c9a71847ab9a83e5f172 │ │ ├── b2edab71f2e01fe860bb15244358814af71c6347 │ │ ├── b5c8135971f910d7a6fcd089d7891c6a21dc6614 │ │ ├── b7b4a18d25e44aa7591979c3057df957d6a735b1 │ │ ├── b88002381964743d7670b9c81f5511b3fc15da71 │ │ ├── c2bd9cb907c2ded45e927bfd17d08a50b7633c40 │ │ ├── c52edfbc6af730b35a9b8f512c158e97ae4f704b │ │ ├── c67db1bd691a24d326b748d707c4dc0e4878b3e0 │ │ ├── cb327960c3f79523c7759e992eff326bf2f4ef8b │ │ ├── d011c59a517c0daa0143e1c766d8b87a41a7fa12 │ │ ├── d02fe69a3d62e25014d44e2d7517754ca7d8afa8 │ │ ├── d0d5b0b2490054fdd6c1ea835c0161c071bb67f5 │ │ ├── d4a41b3f6c0d26fc93c21cac1abe64588da8fb1f │ │ ├── d7dcf33f17a0b7d6cdb1d7103fdb31113ce69384 │ │ ├── da1de687a1475889ce25381ed7eb7393b7a8d58d │ │ ├── dbf5a58cf0cfbf387674323820191e9837860174 │ │ ├── dbf812d2b0d8d3be48f5ce91255b15b168383534 │ │ ├── de77a4fb39028332b50b8eac039f92a08f83b168 │ │ ├── eaeca33ae6a5414285bb521349377907f9c2afbd │ │ ├── ecc9928ced516610601d3474c4191fdf74d0df13 │ │ ├── eeba87f83dd1f064c070fcc76fb96d7555fa065b │ │ ├── efbbba15b60e877915efdb6c403bb8a9f19e70dc │ │ ├── f07d5fc54ab668426f71ea031653863c66904f0a │ │ ├── f494f5c76076f8b6061dcdea7ce26803c77967aa │ │ ├── f4a01768090d97a8011fd742282fa5b06d0e85c8 │ │ ├── f63fbdde088cd5eb595003148fc1f5c09f0d311a │ │ ├── f6858e75a3db428d4b81c5ff9f7c6d56f88425fe │ │ ├── f6ca834d5ac1e3b6985b7b8f0177ea2512c0f932 │ │ ├── f73ac24bb74c8d2089da2935b83c32d98c963f03 │ │ ├── f80149c2a396ceca659facb32f808272b5852108 │ │ ├── fa4c33f649fcab2701b0b655a879ada2b0dc364c │ │ ├── fc8599c0c21d9d6e301e0ed82b16aeb389d204f7 │ │ └── fd54567dce80d6a21fc95ff0e3602fad8267b73c ├── fuzz_targets │ └── fuzz_varint.rs └── Cargo.toml ├── examples ├── ca.cert ├── server.cert ├── server.key ├── launch_chrome.sh ├── Cargo.toml └── readme.md ├── .duvet ├── .gitignore ├── todos │ └── rfc9114 │ │ ├── 7.2.7.toml │ │ ├── 7.1.toml │ │ ├── 10.8.toml │ │ ├── 10.9.toml │ │ ├── 5.3.toml │ │ ├── 4.3.1.toml │ │ ├── 4.1.1.toml │ │ ├── 4.3.toml │ │ ├── 8.1.toml │ │ ├── 4.2.1.toml │ │ ├── 10.5.1.toml │ │ ├── 4.1.2.toml │ │ ├── 7.2.8.toml │ │ ├── 4.2.toml │ │ ├── 5.1.toml │ │ ├── 8.toml │ │ ├── 6.2.toml │ │ ├── 9.toml │ │ ├── 6.2.2.toml │ │ ├── 10.5.toml │ │ ├── 5.2.toml │ │ ├── 7.2.3.toml │ │ ├── 7.2.5.toml │ │ ├── 4.1.toml │ │ ├── 4.6.toml │ │ ├── 4.4.toml │ │ └── 7.2.4.2.toml ├── exceptions │ └── rfc9114 │ │ ├── 5.1.toml │ │ ├── 3.1.2.toml │ │ ├── 5.4.toml │ │ ├── 4.1.2.toml │ │ ├── 6.2.1.toml │ │ ├── 4.2.toml │ │ ├── 10.4.toml │ │ ├── 3.1.1.toml │ │ ├── 9.toml │ │ ├── 10.6.toml │ │ ├── 4.4.toml │ │ ├── 4.3.1.toml │ │ ├── 6.2.toml │ │ ├── 11.2.3.toml │ │ ├── 11.2.4.toml │ │ ├── 4.6.toml │ │ ├── 3.2.toml │ │ ├── 5.2.toml │ │ ├── 11.2.1.toml │ │ ├── 11.2.2.toml │ │ ├── 3.3.toml │ │ ├── 3.1.toml │ │ ├── 4.1.toml │ │ └── 4.1.1.toml └── config.toml ├── changelog-h3-datagram.md ├── ci ├── compliance │ └── report.sh ├── example_test.sh └── h3spec.sh ├── changelog-h3-webtransport.md ├── Cargo.toml ├── .gitignore ├── docs └── PUBLISH.md ├── LICENSE ├── changelog-h3-quinn.md ├── CONTRIBUTING.md ├── .github ├── actions │ └── compliance │ │ └── action.yml └── workflows │ └── CI.yml ├── changelog-h3.md └── README.md /h3/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /h3-quinn/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /h3-datagram/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /h3-webtransport/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | artifacts 4 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/2695fb64cfb30ad72f32f5864e85f426349b74dc: -------------------------------------------------------------------------------- 1 | MA -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/83125cc87a7b47bc7f6d49185ceb9585f409bead: -------------------------------------------------------------------------------- 1 | MA -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9a78211436f6d425ec38f5c4e02270801f3524f8: -------------------------------------------------------------------------------- 1 | @ -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/ac9231da4082430afe8f4d40127814c613648d8e: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/ca.cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/examples/ca.cert -------------------------------------------------------------------------------- /h3/src/webtransport/mod.rs: -------------------------------------------------------------------------------- 1 | mod session_id; 2 | pub use session_id::SessionId; 3 | -------------------------------------------------------------------------------- /.duvet/.gitignore: -------------------------------------------------------------------------------- 1 | reports/ 2 | # extract requirements on demand 3 | requirements/ 4 | -------------------------------------------------------------------------------- /examples/server.cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/examples/server.cert -------------------------------------------------------------------------------- /examples/server.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/examples/server.key -------------------------------------------------------------------------------- /changelog-h3-datagram.md: -------------------------------------------------------------------------------- 1 | ### v0.0.2 (2025-05-06) 2 | * cleanup datagram traits 3 | * use new error types 4 | 5 | ### (2025-03-15) 6 | 7 | initial release -------------------------------------------------------------------------------- /ci/compliance/report.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | duvet report 6 | 7 | echo "compliance report available in '.duvet/reports/report.html'" 8 | -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/05dbe5663b80045fadf7b034f8e2aff60f3a7650: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/05dbe5663b80045fadf7b034f8e2aff60f3a7650 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/06be154b8785e183da98f1097de03378dec4c040: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/06be154b8785e183da98f1097de03378dec4c040 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/074a556fe49e356f482237e7dada61ca88205d09: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/074a556fe49e356f482237e7dada61ca88205d09 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/07d07581ce26c5ba9bdb1f4e6fa2ec9a83348e91: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/07d07581ce26c5ba9bdb1f4e6fa2ec9a83348e91 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/07f8f275a05b0dff9b81e0957a1e4bd250c11bb4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/07f8f275a05b0dff9b81e0957a1e4bd250c11bb4 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/0bd87ad361966694c1e37b5f35265cba9c55649d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/0bd87ad361966694c1e37b5f35265cba9c55649d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/0f46533384ece35d94a9fba6b2200817256f583b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/0f46533384ece35d94a9fba6b2200817256f583b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/0f4d4798761e3b22377802a4f65d671cc599f5bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/0f4d4798761e3b22377802a4f65d671cc599f5bc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/1154e21f9b2c9ce472a65d1413d087df6139c9fa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/1154e21f9b2c9ce472a65d1413d087df6139c9fa -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/1830d1813ca62bf30d01a78f9d9769f0c42c70b1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/1830d1813ca62bf30d01a78f9d9769f0c42c70b1 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/1a93a3c22977c30760b53e4df4c533dabdf75304: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/1a93a3c22977c30760b53e4df4c533dabdf75304 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/1e4cc1d0b2a8a3ece5d8d9d68663dec6931eda33: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/1e4cc1d0b2a8a3ece5d8d9d68663dec6931eda33 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/2621e19d935ef85e86ac6820ea7255d3df9c6cd4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/2621e19d935ef85e86ac6820ea7255d3df9c6cd4 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/28442a0d140eebb7598457fbacd7e819f11fc887: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/28442a0d140eebb7598457fbacd7e819f11fc887 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/2bf809711fe7ff3b06d578a99c2be1291d515dbc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/2bf809711fe7ff3b06d578a99c2be1291d515dbc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/2f1b1a27fc52ed945783a7469c5315bf1e13722c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/2f1b1a27fc52ed945783a7469c5315bf1e13722c -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/2f9a1f7945c755ec45c034c6ca8f2643fa037411: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/2f9a1f7945c755ec45c034c6ca8f2643fa037411 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/303a18ef9307249358253e03a1ac0c714da4af70: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/303a18ef9307249358253e03a1ac0c714da4af70 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/3309e8a207dc835833cfd254b744ec0696342f27: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/3309e8a207dc835833cfd254b744ec0696342f27 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/33a243d628d76748d5436665a16e21c3c46cb814: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/33a243d628d76748d5436665a16e21c3c46cb814 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/34090d70dcdbee300109ee70d64a0bf627701027: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/34090d70dcdbee300109ee70d64a0bf627701027 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/34b5239a7f2185fbc73557c22b433e5850b4badc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/34b5239a7f2185fbc73557c22b433e5850b4badc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/3a7885343257c4daad268f5b1934a826a1c53c68: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/3a7885343257c4daad268f5b1934a826a1c53c68 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/3dcca884e398b0b3f0482c026b352b84ed7c716d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/3dcca884e398b0b3f0482c026b352b84ed7c716d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/3e023e5df0d59d64f42ab9f5ad89ecd2a0ab38b7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/3e023e5df0d59d64f42ab9f5ad89ecd2a0ab38b7 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/4072a44b17469f11cf1119579e549d95adb60695: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/4072a44b17469f11cf1119579e549d95adb60695 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/428d56e4bdcd603e9a420d42cd9d87fd3698ef0d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/428d56e4bdcd603e9a420d42cd9d87fd3698ef0d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/44cd247a1f3480ba331dcfe7540e4cbfc9548a20: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/44cd247a1f3480ba331dcfe7540e4cbfc9548a20 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/46e5b0e24d7c4cdd47d2c3fc815f4c3d8ef6549e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/46e5b0e24d7c4cdd47d2c3fc815f4c3d8ef6549e -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/4819d7fc9f7d485cfe09402dfc85d71005980934: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/4819d7fc9f7d485cfe09402dfc85d71005980934 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/4b04f909eb2b5a2d652931368e0aab127bdc096e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/4b04f909eb2b5a2d652931368e0aab127bdc096e -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/52538a80094f7b62948fd31e68fd17a315d8dc91: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/52538a80094f7b62948fd31e68fd17a315d8dc91 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/5555abc46f500aede6ee215e75dd4e4ff75cc729: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/5555abc46f500aede6ee215e75dd4e4ff75cc729 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/56c9fcd9bf53e0a04c76186133b07344590bacaf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/56c9fcd9bf53e0a04c76186133b07344590bacaf -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/57287827583cbb5a52b5fd74b1af2a299ceadb44: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/57287827583cbb5a52b5fd74b1af2a299ceadb44 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/59b354e1be703f8532d12f9a57cdcb673f037dcc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/59b354e1be703f8532d12f9a57cdcb673f037dcc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/5ec10d338c397a4dde72c0bc45999b92c43e795c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/5ec10d338c397a4dde72c0bc45999b92c43e795c -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/65cc90263dec0020ceabc727d33aa587e57fc175: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/65cc90263dec0020ceabc727d33aa587e57fc175 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/668824f36ef6ef3ccb5e7db28669f62c29de4aba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/668824f36ef6ef3ccb5e7db28669f62c29de4aba -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/67749a2911c910c1e6b350047f1ecb614096a6d3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/67749a2911c910c1e6b350047f1ecb614096a6d3 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/678d273418f58ac643b42aa5fbf6f4686c02faec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/678d273418f58ac643b42aa5fbf6f4686c02faec -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/68dcb6bfce4a84a58ea17326523b925fb371354e: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/68dcb6bfce4a84a58ea17326523b925fb371354e -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/6af47c5e032c79fa9b557a8e24ff84461a2362bd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/6af47c5e032c79fa9b557a8e24ff84461a2362bd -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/6b580d9ce83327dbd0f52ac3949cb7c273ea3822: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/6b580d9ce83327dbd0f52ac3949cb7c273ea3822 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/6d09033d9cabf4be617ebcd83cd8170e7f91720a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/6d09033d9cabf4be617ebcd83cd8170e7f91720a -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/7084c77693211dd27cce1a604873f5c92f9ca55f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/7084c77693211dd27cce1a604873f5c92f9ca55f -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/712dd1f53e53b3174e0f54b851151a958dc1c240: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/712dd1f53e53b3174e0f54b851151a958dc1c240 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/7243e3a79a0d4bc52f44207652eed4d0aea41c52: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/7243e3a79a0d4bc52f44207652eed4d0aea41c52 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/736e9a66d196ef004702bc6ace3c2af3cdd4717d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/736e9a66d196ef004702bc6ace3c2af3cdd4717d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/73b74736664ad85828ce1be2e29fb4a68d24402b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/73b74736664ad85828ce1be2e29fb4a68d24402b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/74513fa7e55a9396e1841ec4ac84f7fad0c57e16: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/74513fa7e55a9396e1841ec4ac84f7fad0c57e16 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/773b297817ea5921527a25da52ed2192a90beb71: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/773b297817ea5921527a25da52ed2192a90beb71 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/787bef1f01133454c80c267bded9d23a84b7b8a2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/787bef1f01133454c80c267bded9d23a84b7b8a2 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/7d95bdd8514904a55a7b6a9a7a16d721f685b2cf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/7d95bdd8514904a55a7b6a9a7a16d721f685b2cf -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8220668e727165c01e984415f8221183f2f1e2a6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8220668e727165c01e984415f8221183f2f1e2a6 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/83c74a72be602c1af64f40c3d1307b9201e36265: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/83c74a72be602c1af64f40c3d1307b9201e36265 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/83f3d8f8ceb5732d961c42894a61e4c6fbb4745c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/83f3d8f8ceb5732d961c42894a61e4c6fbb4745c -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/84046e7e673b9e88133a7de460f28789be0ddd20: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/84046e7e673b9e88133a7de460f28789be0ddd20 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/859366bbd30ea84562c37faf4af3ba3b4bbed3a2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/859366bbd30ea84562c37faf4af3ba3b4bbed3a2 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/861aedaea120f9f80f64a36e6467e780e88fa493: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/861aedaea120f9f80f64a36e6467e780e88fa493 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8642759cf26e9020195e726fdf607fbe011439d0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8642759cf26e9020195e726fdf607fbe011439d0 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/86875bb9309fe67a067775f61b78bf1d2c298997: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/86875bb9309fe67a067775f61b78bf1d2c298997 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/887fb4e7d2eff0a43f7c061a38f2c0010921dfd3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/887fb4e7d2eff0a43f7c061a38f2c0010921dfd3 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/88f3c8e6e819aa83da1ce48353b2bddaad759fb9: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/88f3c8e6e819aa83da1ce48353b2bddaad759fb9 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8b3f361ff3add0f0b5181d1b4565ef97ed36c8eb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8b3f361ff3add0f0b5181d1b4565ef97ed36c8eb -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8bb0f17ae2d716c42c83c225c38e07cb0bef16e0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8bb0f17ae2d716c42c83c225c38e07cb0bef16e0 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8c2975ae46264fd229593fbc72dbcc36739a1eac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8c2975ae46264fd229593fbc72dbcc36739a1eac -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/8e9774faccd71983c83a67dd04459f6ff68bde52: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/8e9774faccd71983c83a67dd04459f6ff68bde52 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/93c4cb3f2193b404a2cd713434a5c4cc563151ba: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/93c4cb3f2193b404a2cd713434a5c4cc563151ba -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/94108451f94018cbaed1f4913759c9af14c6969f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/94108451f94018cbaed1f4913759c9af14c6969f -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9ccda137ee54ee862b657df458f4aabb495162ac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/9ccda137ee54ee862b657df458f4aabb495162ac -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9cfceef0f039fd5cc7e7fe72dfbe33e87521ed33: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/9cfceef0f039fd5cc7e7fe72dfbe33e87521ed33 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9e274418d86f267e9a4bf459aded615c27c589c6: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/9e274418d86f267e9a4bf459aded615c27c589c6 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9f6e8a6a28994cc6479049e7d9dca7c779415327: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/9f6e8a6a28994cc6479049e7d9dca7c779415327 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/9fc493a78af9e8289b24df2d690b822d4007a3fc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/9fc493a78af9e8289b24df2d690b822d4007a3fc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/a1fca3a494b9d842105041e09b52dcddd59a3d51: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/a1fca3a494b9d842105041e09b52dcddd59a3d51 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/a40494f65e76f66b47c337e75431f4010f74f493: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/a40494f65e76f66b47c337e75431f4010f74f493 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/a7c00395b2d0f3b7fe9ee068d25d44e1cedf1301: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/a7c00395b2d0f3b7fe9ee068d25d44e1cedf1301 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b0c12ea324dd2592ef7d07757407f5983106ab24: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b0c12ea324dd2592ef7d07757407f5983106ab24 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b1958cd1032c91560273c9a71847ab9a83e5f172: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b1958cd1032c91560273c9a71847ab9a83e5f172 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b2edab71f2e01fe860bb15244358814af71c6347: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b2edab71f2e01fe860bb15244358814af71c6347 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b5c8135971f910d7a6fcd089d7891c6a21dc6614: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b5c8135971f910d7a6fcd089d7891c6a21dc6614 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b7b4a18d25e44aa7591979c3057df957d6a735b1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b7b4a18d25e44aa7591979c3057df957d6a735b1 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/b88002381964743d7670b9c81f5511b3fc15da71: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/b88002381964743d7670b9c81f5511b3fc15da71 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/c2bd9cb907c2ded45e927bfd17d08a50b7633c40: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/c2bd9cb907c2ded45e927bfd17d08a50b7633c40 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/c52edfbc6af730b35a9b8f512c158e97ae4f704b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/c52edfbc6af730b35a9b8f512c158e97ae4f704b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/c67db1bd691a24d326b748d707c4dc0e4878b3e0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/c67db1bd691a24d326b748d707c4dc0e4878b3e0 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/cb327960c3f79523c7759e992eff326bf2f4ef8b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/cb327960c3f79523c7759e992eff326bf2f4ef8b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/d011c59a517c0daa0143e1c766d8b87a41a7fa12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/d011c59a517c0daa0143e1c766d8b87a41a7fa12 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/d02fe69a3d62e25014d44e2d7517754ca7d8afa8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/d02fe69a3d62e25014d44e2d7517754ca7d8afa8 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/d0d5b0b2490054fdd6c1ea835c0161c071bb67f5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/d0d5b0b2490054fdd6c1ea835c0161c071bb67f5 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/d4a41b3f6c0d26fc93c21cac1abe64588da8fb1f: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/d4a41b3f6c0d26fc93c21cac1abe64588da8fb1f -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/d7dcf33f17a0b7d6cdb1d7103fdb31113ce69384: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/d7dcf33f17a0b7d6cdb1d7103fdb31113ce69384 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/da1de687a1475889ce25381ed7eb7393b7a8d58d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/da1de687a1475889ce25381ed7eb7393b7a8d58d -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/dbf5a58cf0cfbf387674323820191e9837860174: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/dbf5a58cf0cfbf387674323820191e9837860174 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/dbf812d2b0d8d3be48f5ce91255b15b168383534: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/dbf812d2b0d8d3be48f5ce91255b15b168383534 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/de77a4fb39028332b50b8eac039f92a08f83b168: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/de77a4fb39028332b50b8eac039f92a08f83b168 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/eaeca33ae6a5414285bb521349377907f9c2afbd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/eaeca33ae6a5414285bb521349377907f9c2afbd -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/ecc9928ced516610601d3474c4191fdf74d0df13: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/ecc9928ced516610601d3474c4191fdf74d0df13 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/eeba87f83dd1f064c070fcc76fb96d7555fa065b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/eeba87f83dd1f064c070fcc76fb96d7555fa065b -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/efbbba15b60e877915efdb6c403bb8a9f19e70dc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/efbbba15b60e877915efdb6c403bb8a9f19e70dc -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f07d5fc54ab668426f71ea031653863c66904f0a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f07d5fc54ab668426f71ea031653863c66904f0a -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f494f5c76076f8b6061dcdea7ce26803c77967aa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f494f5c76076f8b6061dcdea7ce26803c77967aa -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f4a01768090d97a8011fd742282fa5b06d0e85c8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f4a01768090d97a8011fd742282fa5b06d0e85c8 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f63fbdde088cd5eb595003148fc1f5c09f0d311a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f63fbdde088cd5eb595003148fc1f5c09f0d311a -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f6858e75a3db428d4b81c5ff9f7c6d56f88425fe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f6858e75a3db428d4b81c5ff9f7c6d56f88425fe -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f6ca834d5ac1e3b6985b7b8f0177ea2512c0f932: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f6ca834d5ac1e3b6985b7b8f0177ea2512c0f932 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f73ac24bb74c8d2089da2935b83c32d98c963f03: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f73ac24bb74c8d2089da2935b83c32d98c963f03 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/f80149c2a396ceca659facb32f808272b5852108: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/f80149c2a396ceca659facb32f808272b5852108 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/fa4c33f649fcab2701b0b655a879ada2b0dc364c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/fa4c33f649fcab2701b0b655a879ada2b0dc364c -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/fc8599c0c21d9d6e301e0ed82b16aeb389d204f7: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/fc8599c0c21d9d6e301e0ed82b16aeb389d204f7 -------------------------------------------------------------------------------- /fuzz/corpus/fuzz_varint/fd54567dce80d6a21fc95ff0e3602fad8267b73c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperium/h3/HEAD/fuzz/corpus/fuzz_varint/fd54567dce80d6a21fc95ff0e3602fad8267b73c -------------------------------------------------------------------------------- /h3/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod coding; 2 | #[allow(dead_code)] 3 | pub mod frame; 4 | #[allow(dead_code)] 5 | pub mod headers; 6 | pub mod push; 7 | pub mod stream; 8 | pub mod varint; 9 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.2.7.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.2.7" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | A server MUST NOT send a MAX_PUSH_ID frame. 6 | ''' 7 | -------------------------------------------------------------------------------- /changelog-h3-webtransport.md: -------------------------------------------------------------------------------- 1 | # v0.1.2 (2025-05-06) 2 | * use new h3 error types 3 | * some code cleanup 4 | 5 | # v0.1.1 6 | * update dependencies 7 | * move datagram logic to its own crate -------------------------------------------------------------------------------- /h3-datagram/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod datagram; 3 | pub mod datagram_handler; 4 | pub mod quic_traits; 5 | pub mod server; 6 | 7 | pub use h3::quic::ConnectionErrorIncoming; 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "h3", 4 | "h3-quinn", 5 | "h3-webtransport", 6 | "h3-datagram", 7 | 8 | # Internal 9 | "examples", 10 | ] 11 | resolver = "2" 12 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Each frame's payload MUST contain exactly the fields identified in 6 | its description. 7 | ''' 8 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/10.8.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.8" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | An implementation MUST ensure that the length of a 6 | frame exactly matches the length of the fields it contains. 7 | ''' 8 | -------------------------------------------------------------------------------- /h3-webtransport/readme.md: -------------------------------------------------------------------------------- 1 | # H3 Webtransport 2 | 3 | Implementation of Webtransport protocol as extension of the h3 crate 4 | 5 | # Status 6 | This crate is still in experimental. The API is subject to change. It may contain bugs and is not yet complete. Use with caution. 7 | -------------------------------------------------------------------------------- /h3/src/client/mod.rs: -------------------------------------------------------------------------------- 1 | //! HTTP/3 client 2 | 3 | mod connection; 4 | mod stream; 5 | 6 | mod builder; 7 | 8 | pub use builder::builder; 9 | pub use builder::new; 10 | pub use builder::Builder; 11 | pub use connection::{Connection, SendRequest}; 12 | pub use stream::RequestStream; 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Cargo 2 | Cargo.lock 3 | debug/ 4 | target/ 5 | 6 | # rustfmt 7 | **/*.rs.bk 8 | 9 | # IDE 10 | .idea 11 | .vscode 12 | 13 | # macOS 14 | .DS_Store 15 | ._* 16 | 17 | # Example Certificates 18 | localhost-key.pem 19 | localhost.crt 20 | localhost.key 21 | localhost.pem 22 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/10.9.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.9" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | The use of 0-RTT with HTTP/3 creates an exposure to replay attack. 6 | The anti-replay mitigations in [HTTP-REPLAY] MUST be applied when 7 | using HTTP/3 with 0-RTT. 8 | ''' 9 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/5.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | A gateway MAY 6 | maintain connections in anticipation of need rather than incur the 7 | latency cost of connection establishment to servers. 8 | ''' 9 | reason = ''' 10 | This is a suggestion for API gateways. 11 | ''' 12 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/3.1.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-3.1.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Prior to making requests for an origin whose scheme is not "https", 6 | the client MUST ensure the server is willing to serve that scheme. 7 | ''' 8 | reason = ''' 9 | The requirement pertains to the high-level implementation. 10 | ''' 11 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/5.4.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.4" 2 | 3 | [[exception]] 4 | quote = ''' 5 | If a connection terminates without a GOAWAY frame, clients MUST 6 | assume that any request that was sent, whether in whole or in part, 7 | might have been processed. 8 | ''' 9 | reason = ''' 10 | h3 does not need to do anything specific. 11 | ''' 12 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.1.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Intermediaries that process HTTP requests or responses (i.e., any 6 | intermediary not acting as a tunnel) MUST NOT forward a malformed 7 | request or response. 8 | ''' 9 | reason = ''' 10 | h3 is not meant to be used for intermediaries. 11 | ''' 12 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/5.3.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.3" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Before closing the connection, a GOAWAY frame MAY be sent to allow 6 | the client to retry some requests. Including the GOAWAY frame in the 7 | same packet as the QUIC CONNECTION_CLOSE frame improves the chances 8 | of the frame being received by clients. 9 | ''' 10 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_varint.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | #[path = "../../h3/src/proto/varint.rs"] 4 | mod varint; 5 | #[path = "../../h3/src/proto/coding.rs"] 6 | mod coding; 7 | 8 | use libfuzzer_sys::fuzz_target; 9 | use varint::VarInt; 10 | use bytes::Bytes; 11 | 12 | fuzz_target!(|data: &[u8]| { 13 | let mut input = Bytes::from(data.to_vec()); 14 | let _ = VarInt::decode(&mut input); 15 | }); 16 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.3.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.3.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | The authority MUST NOT include the 6 | deprecated userinfo subcomponent for URIs of scheme "http" or 7 | "https". 8 | ''' 9 | 10 | [[TODO]] 11 | quote = ''' 12 | Clients that 13 | generate HTTP/3 requests directly SHOULD use the :authority 14 | pseudo-header field instead of the Host header field. 15 | ''' 16 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/6.2.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-6.2.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Because the contents of the control stream are used to manage the 6 | behavior of other streams, endpoints SHOULD provide enough flow- 7 | control credit to keep the peer's control stream from becoming 8 | blocked. 9 | ''' 10 | reason = ''' 11 | h3 does not handle flow control credit acquiring. 12 | ''' 13 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | An intermediary transforming an HTTP/1.x message to HTTP/3 MUST 6 | remove connection-specific header fields as discussed in 7 | Section 7.6.1 of [HTTP], or their messages will be treated by other 8 | HTTP/3 endpoints as malformed. 9 | ''' 10 | reason = ''' 11 | h3 is not meant to be used for intermediaries. 12 | ''' 13 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.1.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Clients MUST NOT use the 6 | H3_REQUEST_REJECTED error code, except when a server has requested 7 | closure of the request stream with this error code. 8 | ''' 9 | 10 | [[TODO]] 11 | quote = ''' 12 | However, if 13 | a stream is cancelled after receiving a partial response, the 14 | response SHOULD NOT be used. 15 | ''' 16 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.3.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.3" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Endpoints MUST treat a request or response that contains 6 | undefined or invalid pseudo-header fields as malformed. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | Any request or response that contains a 12 | pseudo-header field that appears in a header section after a regular 13 | header field MUST be treated as malformed. 14 | ''' 15 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/8.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-8.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Error codes of the format 0x1f * N + 0x21 for non-negative integer 6 | values of N are reserved to exercise the requirement that unknown 7 | error codes be treated as equivalent to H3_NO_ERROR (Section 9). 8 | Implementations SHOULD select an error code from this space with some 9 | probability when they would have sent H3_NO_ERROR. 10 | ''' 11 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.2.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.2.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | If a decompressed field 6 | section contains multiple cookie field lines, these MUST be 7 | concatenated into a single byte string using the two-byte delimiter 8 | of "; " (ASCII 0x3b, 0x20) before being passed into a context other 9 | than HTTP/2 or HTTP/3, such as an HTTP/1.1 connection, or a generic 10 | HTTP server application. 11 | ''' 12 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/10.5.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.5.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | An endpoint can use the SETTINGS_MAX_FIELD_SECTION_SIZE 6 | (Section 4.2.2) setting to advise peers of limits that might apply on 7 | the size of field sections. This setting is only advisory, so 8 | endpoints MAY choose to send field sections that exceed this limit 9 | and risk having the request or response being treated as malformed. 10 | ''' 11 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/10.4.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.4" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Where multiple tenants share space on the same server, that server 6 | MUST ensure that tenants are not able to push representations of 7 | resources that they do not have authority over. 8 | ''' 9 | reason = ''' 10 | This pertains to the high-level server implementation, as the 11 | authority of the data being sent is transparent to h3. 12 | ''' 13 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "h3-fuzz" 3 | version = "0.0.0" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2021" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = "0.4" 13 | bytes = "1.5" 14 | 15 | # Prevent this from interfering with workspaces 16 | [workspace] 17 | members = ["."] 18 | 19 | [[bin]] 20 | name = "fuzz_varint" 21 | path = "fuzz_targets/fuzz_varint.rs" 22 | test = false 23 | doc = false 24 | -------------------------------------------------------------------------------- /.duvet/config.toml: -------------------------------------------------------------------------------- 1 | '$schema' = "https://awslabs.github.io/duvet/config/v0.4.0.json" 2 | 3 | [[source]] 4 | pattern = "h3/**/*.rs" 5 | 6 | [[specification]] 7 | source = "https://www.rfc-editor.org/rfc/rfc9114" 8 | 9 | [report.html] 10 | enabled = true 11 | issue-link = "https://github.com/hyperium/h3/issues" 12 | blob-link = "https://github.com/hyperium/h3/blob/${{ SHA || GITHUB_SHA || 'master' }}" 13 | 14 | # Enable snapshots to prevent requirement coverage regressions 15 | [report.snapshot] 16 | enabled = true 17 | -------------------------------------------------------------------------------- /h3-webtransport/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Provides the client and server support for WebTransport sessions. 2 | //! 3 | //! # Relevant Links 4 | //! WebTransport: 5 | //! WebTransport over HTTP/3: 6 | #![deny(missing_docs)] 7 | 8 | /// Server side WebTransport session support 9 | pub mod server; 10 | /// Webtransport stream types 11 | pub mod stream; 12 | 13 | pub use h3::webtransport::SessionId; 14 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/3.1.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-3.1.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | On receipt of an Alt-Svc record indicating HTTP/3 support, a client 6 | MAY attempt to establish a QUIC connection to the indicated host and 7 | port; if this connection is successful, the client can send HTTP 8 | requests using the mapping described in this document. 9 | ''' 10 | reason = ''' 11 | Connection establishment is handled by the user and the QUIC 12 | component. 13 | ''' 14 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.1.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Malformed requests or responses that are 6 | detected MUST be treated as a stream error of type H3_MESSAGE_ERROR. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | For malformed requests, a server MAY send an HTTP response indicating 12 | the error prior to closing or resetting the stream. 13 | ''' 14 | 15 | [[TODO]] 16 | quote = ''' 17 | Clients MUST NOT 18 | accept a malformed response. 19 | ''' 20 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.2.8.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.2.8" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Endpoints MUST 6 | NOT consider these frames to have any meaning upon receipt. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | Frame types that were used in HTTP/2 where there is no corresponding 12 | HTTP/3 frame have also been reserved (Section 11.2.1). These frame 13 | types MUST NOT be sent, and their receipt MUST be treated as a 14 | connection error of type H3_FRAME_UNEXPECTED. 15 | ''' 16 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | An endpoint MUST NOT generate 6 | an HTTP/3 field section containing connection-specific fields; any 7 | message containing connection-specific fields MUST be treated as 8 | malformed. 9 | ''' 10 | 11 | [[TODO]] 12 | quote = ''' 13 | The only exception to this is the TE header field, which MAY be 14 | present in an HTTP/3 request header; when it is, it MUST NOT contain 15 | any value other than "trailers". 16 | ''' 17 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/5.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | HTTP/3 implementations will need to open a new HTTP/3 6 | connection for new requests if the existing connection has been idle 7 | for longer than the idle timeout negotiated during the QUIC 8 | handshake, and they SHOULD do so if approaching the idle timeout; see 9 | Section 10.1 of [QUIC-TRANSPORT]. 10 | ''' 11 | 12 | [[TODO]] 13 | quote = ''' 14 | Servers SHOULD 15 | NOT actively keep connections open. 16 | ''' 17 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/8.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-8" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | An endpoint MAY choose to treat a stream error as a connection error 6 | under certain circumstances, closing the entire connection in 7 | response to a condition on a single stream. 8 | ''' 9 | 10 | [[TODO]] 11 | quote = ''' 12 | Because new error codes can be defined without negotiation (see 13 | Section 9), use of an error code in an unexpected context or receipt 14 | of an unknown error code MUST be treated as equivalent to 15 | H3_NO_ERROR. 16 | ''' 17 | -------------------------------------------------------------------------------- /h3/src/qpack/parse_error.rs: -------------------------------------------------------------------------------- 1 | use super::{prefix_int, prefix_string}; 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum ParseError { 5 | Integer(prefix_int::Error), 6 | String(prefix_string::Error), 7 | InvalidPrefix(u8), 8 | InvalidBase(isize), 9 | } 10 | 11 | impl From for ParseError { 12 | fn from(e: prefix_int::Error) -> Self { 13 | ParseError::Integer(e) 14 | } 15 | } 16 | 17 | impl From for ParseError { 18 | fn from(e: prefix_string::Error) -> Self { 19 | ParseError::String(e) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/6.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-6.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Implementations MAY send stream types before knowing whether the peer 6 | supports them. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | However, stream types that could modify the state or 12 | semantics of existing protocol components, including QPACK or other 13 | extensions, MUST NOT be sent until the peer is known to support them. 14 | ''' 15 | 16 | [[TODO]] 17 | quote = ''' 18 | A receiver MUST tolerate unidirectional streams being 19 | closed or reset prior to the reception of the unidirectional stream 20 | header. 21 | ''' 22 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/9.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-9" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Extensions that could change the semantics of existing protocol 6 | components MUST be negotiated before being used. 7 | ''' 8 | reason = ''' 9 | The user should ensure that the order is correct, as h3 does not 10 | handle protocol negotiations. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | If a setting is used for extension negotiation, the default 16 | value MUST be defined in such a fashion that the extension is 17 | disabled if the setting is omitted. 18 | ''' 19 | reason = ''' 20 | The requirement applies to extension specifications. 21 | ''' 22 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/10.6.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.6" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Implementations communicating on a secure channel MUST NOT compress 6 | content that includes both confidential and attacker-controlled data 7 | unless separate compression contexts are used for each source of 8 | data. 9 | ''' 10 | reason = ''' 11 | This should be enforced by the user, as h3 may not be able to 12 | determine the nature of the content. 13 | ''' 14 | 15 | [[exception]] 16 | quote = ''' 17 | Compression MUST NOT be used if the source of data cannot be 18 | reliably determined. 19 | ''' 20 | reason = ''' 21 | This is up to the user to decide. 22 | ''' 23 | -------------------------------------------------------------------------------- /h3-datagram/readme.md: -------------------------------------------------------------------------------- 1 | # H3 Datagram 2 | 3 | this crate provides an implementation of the [h3-datagram](https://datatracker.ietf.org/doc/html/rfc9297) spec that works with the h3 crate. 4 | 5 | # Status 6 | This crate is still in experimental. The API is subject to change. It may contain bugs and is not yet complete. Use with caution. 7 | 8 | ## Usage 9 | As stated in the [rfc](https://datatracker.ietf.org/doc/html/rfc9297#abstract) this is intended to be used for protocol extensions like [Web-Transport](https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/) and not directly by applications. 10 | 11 | > HTTP Datagrams and the Capsule Protocol are intended for use by HTTP extensions, not applications. -------------------------------------------------------------------------------- /examples/launch_chrome.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | SPKI=`openssl x509 -inform der -in localhost.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64` 6 | 7 | echo "Got cert key $SPKI" 8 | 9 | echo "Opening google chrome" 10 | 11 | case `uname` in 12 | (*Linux*) google-chrome --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ;; 13 | (*Darwin*) open -a "Google Chrome" --args --origin-to-force-quic-on=127.0.0.1:4433 --ignore-certificate-errors-spki-list=$SPKI --enable-logging --v=1 ;; 14 | esac 15 | 16 | ## Logs are stored to ~/Library/Application Support/Google/Chrome/chrome_debug.log 17 | -------------------------------------------------------------------------------- /h3/src/qpack/prefix_string/bitwin.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Default, PartialEq, Clone)] 2 | pub struct BitWindow { 3 | pub byte: u32, 4 | pub bit: u32, 5 | pub count: u32, 6 | } 7 | 8 | impl BitWindow { 9 | pub fn new() -> Self { 10 | Self::default() 11 | } 12 | 13 | pub fn forwards(&mut self, step: u32) { 14 | self.bit += self.count; 15 | 16 | self.byte += self.bit / 8; 17 | self.bit %= 8; 18 | 19 | self.count = step; 20 | } 21 | 22 | pub fn opposite_bit_window(&self) -> BitWindow { 23 | BitWindow { 24 | byte: self.byte, 25 | bit: self.bit, 26 | count: 8 - (self.bit % 8), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/9.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-9" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Implementations MUST ignore unknown or unsupported values in all 6 | extensible protocol elements. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | Implementations MUST discard data or 12 | abort reading on unidirectional streams that have unknown or 13 | unsupported types. 14 | ''' 15 | 16 | [[TODO]] 17 | quote = ''' 18 | However, where a known frame type is required to be in 19 | a specific location, such as the SETTINGS frame as the first frame of 20 | the control stream (see Section 6.2.1), an unknown frame type does 21 | not satisfy that requirement and SHOULD be treated as an error. 22 | ''' 23 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.4.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.4" 2 | 3 | [[exception]] 4 | quote = ''' 5 | TCP connections that remain half closed in a single 6 | direction are not invalid, but are often handled poorly by servers, 7 | so clients SHOULD NOT close a stream for sending while they still 8 | expect to receive data from the target of the CONNECT. 9 | ''' 10 | reason = ''' 11 | h3 is not meant to be used for handling proxy. 12 | ''' 13 | 14 | [[exception]] 15 | quote = ''' 16 | In all these cases, if the 17 | underlying TCP implementation permits it, the proxy SHOULD send a TCP 18 | segment with the RST bit set. 19 | ''' 20 | reason = ''' 21 | h3 is not meant to be used for handling proxy. 22 | ''' 23 | -------------------------------------------------------------------------------- /h3-datagram/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "h3-datagram" 3 | version = "0.0.2" 4 | edition = "2021" 5 | documentation = "https://docs.rs/h3-datagram" 6 | repository = "https://github.com/hyperium/h3" 7 | readme = "readme.md" 8 | description = "rfc9297 extension for the h3 crate" 9 | keywords = ["http3", "quic", "rfc9297"] 10 | categories = ["network-programming", "web-programming"] 11 | license = "MIT" 12 | 13 | 14 | [dependencies] 15 | #h3 = { path = "../h3" } 16 | bytes = "1.4" 17 | tracing = { version = "0.1.40", optional = true } 18 | 19 | [features] 20 | tracing = ["dep:tracing"] 21 | 22 | [dependencies.h3] 23 | version = "0.0.8" 24 | path = "../h3" 25 | features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes"] 26 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.3.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.3.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | To ensure that the HTTP/1.1 request line can be reproduced 6 | accurately, this pseudo-header field MUST be omitted when 7 | translating from an HTTP/1.1 request that has a request target in 8 | a method-specific form; see Section 7.1 of [HTTP]. 9 | ''' 10 | reason = ''' 11 | h3 is not meant to be used for intermediaries. 12 | ''' 13 | 14 | [[exception]] 15 | quote = ''' 16 | An 17 | intermediary that converts an HTTP/3 request to HTTP/1.1 MUST 18 | create a Host field if one is not present in a request by copying 19 | the value of the :authority pseudo-header field. 20 | ''' 21 | reason = ''' 22 | h3 is not meant to be used for intermediaries. 23 | ''' 24 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/6.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-6.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Each endpoint needs to create at least one unidirectional stream for 6 | the HTTP control stream. QPACK requires two additional 7 | unidirectional streams, and other extensions might require further 8 | streams. Therefore, the transport parameters sent by both clients 9 | and servers MUST allow the peer to create at least three 10 | unidirectional streams. These transport parameters SHOULD also 11 | provide at least 1,024 bytes of flow-control credit to each 12 | unidirectional stream. 13 | ''' 14 | reason = ''' 15 | The user should ensure that the transport parameters satisfy the 16 | requirements, as h3 does not handle connection establishment. 17 | ''' 18 | -------------------------------------------------------------------------------- /h3/src/error/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the error handling logic and types for the h3 crate. 2 | 3 | mod codes; 4 | 5 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 6 | pub mod connection_error_creators; 7 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 8 | pub(crate) mod connection_error_creators; 9 | 10 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 11 | pub mod internal_error; 12 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 13 | pub(crate) mod internal_error; 14 | 15 | // Todo better module names 16 | #[allow(clippy::module_inception)] 17 | mod error; 18 | 19 | pub use codes::Code; 20 | pub use error::{ConnectionError, LocalError, StreamError}; 21 | -------------------------------------------------------------------------------- /ci/example_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | # Change to the repository root directory 5 | cd "$(dirname "$0")/.." 6 | 7 | # Start the server in the background 8 | echo "Starting server..." 9 | cargo run --example server -- --listen=[::]:4433 --cert=examples/server.cert --key=examples/server.key & 10 | SERVER_PID=$! 11 | 12 | # Wait for the server to start 13 | sleep 2 14 | 15 | # Function to clean up server process on exit 16 | cleanup() { 17 | echo "Cleaning up server process..." 18 | kill $SERVER_PID 2>/dev/null || true 19 | } 20 | 21 | # Set up cleanup on script exit 22 | trap cleanup EXIT 23 | 24 | # Run the client 25 | echo "Running client..." 26 | cargo run --example client -- https://localhost:4433 --ca=examples/ca.cert 27 | 28 | # If we got here, the test succeeded 29 | echo "Server and client connected successfully!" 30 | exit 0 -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/6.2.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-6.2.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Only servers can push; if a server receives a client-initiated push 6 | stream, this MUST be treated as a connection error of type 7 | H3_STREAM_CREATION_ERROR. 8 | ''' 9 | 10 | [[TODO]] 11 | quote = ''' 12 | A client SHOULD NOT abort reading on a push stream prior to reading 13 | the push stream header, as this could lead to disagreement between 14 | client and server on which push IDs have already been consumed. 15 | ''' 16 | 17 | [[TODO]] 18 | quote = ''' 19 | Each push ID MUST only be used once in a push stream header. 20 | ''' 21 | 22 | [[TODO]] 23 | quote = ''' 24 | If a 25 | client detects that a push stream header includes a push ID that was 26 | used in another push stream header, the client MUST treat this as a 27 | connection error of type H3_ID_ERROR. 28 | ''' 29 | -------------------------------------------------------------------------------- /h3/src/qpack/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::{ 2 | decoder::{decode_stateless, Decoded, DecoderError}, 3 | encoder::{encode_stateless, EncoderError}, 4 | field::HeaderField, 5 | }; 6 | 7 | mod block; 8 | mod dynamic; 9 | mod field; 10 | mod parse_error; 11 | mod static_; 12 | mod stream; 13 | mod vas; 14 | 15 | mod decoder; 16 | mod encoder; 17 | 18 | mod prefix_int; 19 | mod prefix_string; 20 | 21 | #[cfg(test)] 22 | mod tests; 23 | 24 | #[derive(Debug)] 25 | pub enum Error { 26 | Encoder(EncoderError), 27 | Decoder(DecoderError), 28 | } 29 | 30 | impl std::error::Error for Error {} 31 | 32 | impl std::fmt::Display for Error { 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 34 | match self { 35 | Error::Encoder(e) => write!(f, "Encoder {}", e), 36 | Error::Decoder(e) => write!(f, "Decoder {}", e), 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/10.5.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-10.5" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | A client that accepts server push SHOULD limit the number 6 | of push IDs it issues at a time. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | All these features -- i.e., server push, unknown protocol elements, 12 | field compression -- have legitimate uses. These features become a 13 | burden only when they are used unnecessarily or to excess. 14 | 15 | An endpoint that does not monitor such behavior exposes itself to a 16 | risk of denial-of-service attack. Implementations SHOULD track the 17 | use of these features and set limits on their use. 18 | ''' 19 | 20 | [[TODO]] 21 | quote = ''' 22 | An endpoint MAY 23 | treat activity that is suspicious as a connection error of type 24 | H3_EXCESSIVE_LOAD, but false positives will result in disrupting 25 | valid connections and requests. 26 | ''' 27 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/11.2.3.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-11.2.3" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Use of values that are registered in the "HTTP/2 Error Code" registry 6 | is discouraged, and expert reviewers MAY reject such registrations. 7 | ''' 8 | reason = ''' 9 | The requirement applies to IANA registration. 10 | ''' 11 | 12 | [[exception]] 13 | quote = ''' 14 | Permanent registrations in 15 | this registry MUST include the following field: 16 | 17 | Name: A name for the error code. 18 | ''' 19 | reason = ''' 20 | The requirement applies to IANA registration. 21 | ''' 22 | 23 | [[exception]] 24 | quote = ''' 25 | Each code of the format 0x1f * N + 0x21 for non-negative integer 26 | values of N (that is, 0x21, 0x40, ..., through 0x3ffffffffffffffe) 27 | MUST NOT be assigned by IANA and MUST NOT appear in the listing of 28 | assigned values. 29 | ''' 30 | reason = ''' 31 | The requirement applies to IANA registration. 32 | ''' 33 | -------------------------------------------------------------------------------- /h3-quinn/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "h3-quinn" 3 | version = "0.0.10" 4 | rust-version = "1.74" 5 | authors = ["Jean-Christophe BEGUE "] 6 | edition = "2021" 7 | documentation = "https://docs.rs/h3-quinn" 8 | repository = "https://github.com/hyperium/h3" 9 | readme = "README.md" 10 | description = "QUIC transport implementation based on Quinn." 11 | keywords = ["http3", "quic", "h3"] 12 | categories = ["network-programming", "web-programming"] 13 | license = "MIT" 14 | 15 | [dependencies] 16 | h3 = { version = "0.0.8", path = "../h3" } 17 | bytes = "1" 18 | quinn = { version = "0.11.7", default-features = false, features = [ 19 | "futures-io", 20 | ] } 21 | tokio-util = { version = "0.7.9" } 22 | futures-util = { version = "0.3.28", default-features = false } 23 | h3-datagram = {version = "0.0.2", path = "../h3-datagram", optional = true } 24 | tracing = { version = "0.1.40", optional = true } 25 | 26 | [features] 27 | tracing = ["dep:tracing"] 28 | datagram = ["dep:h3-datagram"] 29 | -------------------------------------------------------------------------------- /h3-webtransport/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "h3-webtransport" 3 | version = "0.1.2" 4 | edition = "2021" 5 | documentation = "https://docs.rs/h3-webtransport" 6 | repository = "https://github.com/hyperium/h3" 7 | readme = "readme.md" 8 | description = "webtransport extension for h3" 9 | keywords = ["http3", "quic", "webtransport"] 10 | categories = ["network-programming", "web-programming"] 11 | license = "MIT" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | bytes = "1" 17 | futures-util = { version = "0.3", default-features = false } 18 | http = "1" 19 | pin-project-lite = { version = "0.2", default-features = false } 20 | tracing = "0.1.37" 21 | tokio = { version = "1.28", default-features = false } 22 | h3-datagram = { version = "0.0.2", path = "../h3-datagram" } 23 | 24 | [dependencies.h3] 25 | version = "0.0.8" 26 | path = "../h3" 27 | features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes"] 28 | -------------------------------------------------------------------------------- /docs/PUBLISH.md: -------------------------------------------------------------------------------- 1 | # How to publish a new release 2 | 3 | Even when a new version might affect multiple crates in this repository, 4 | each should release should be handled individually. 5 | 6 | A collaborator follows these steps to publish: 7 | 8 | 1. Create a new pull request with: 9 | 1. The version in the `Cargo.toml` updated. 10 | 2. The `CHANGELOG.md` contains what notable changes from the last version. 11 | 3. Name the commit message as simply "$crate vX.Y.Z". 12 | 2. Another collaborator approves the pull request, but does not merge. 13 | 3. The original collaborator merges (rebase) the pull request. 14 | 4. Update locally to the merged branch. 15 | 5. Run `cargo publish -p h3` (or other crate name). 16 | 6. Tag the release: `git tag h3-vX.Y.Z`, with the appropriate crate and 17 | version. 18 | 7. Push the tag to GitHub: `git push upstream h3-v.X.Y.Z`. 19 | 8. Go to the GitHub Releases page, and draft a new release based on the tag. 20 | Include the contents from the changelog. 21 | 22 | And celebrate, you're done! 23 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/5.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | Upon sending a GOAWAY frame, the endpoint 6 | SHOULD explicitly cancel (see Sections 4.1.1 and 7.2.3) any requests 7 | or pushes that have identifiers greater than or equal to the one 8 | indicated, in order to clean up transport state for the affected 9 | streams. 10 | ''' 11 | 12 | [[TODO]] 13 | quote = ''' 14 | The endpoint SHOULD continue to do so as more requests or 15 | pushes arrive. 16 | ''' 17 | 18 | [[TODO]] 19 | quote = ''' 20 | Endpoints MUST NOT initiate new requests or promise new pushes on the 21 | connection after receipt of a GOAWAY frame from the peer. 22 | ''' 23 | 24 | [[TODO]] 25 | quote = ''' 26 | Servers MAY reject individual requests on streams below the 27 | indicated ID if these requests were not processed. 28 | ''' 29 | 30 | [[TODO]] 31 | quote = ''' 32 | An endpoint that completes a 33 | graceful shutdown SHOULD use the H3_NO_ERROR error code when closing 34 | the connection. 35 | ''' 36 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/11.2.4.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-11.2.4" 2 | 3 | [[exception]] 4 | quote = ''' 5 | In addition to common fields as described in Section 11.2, permanent 6 | registrations in this registry MUST include the following fields: 7 | 8 | Stream Type: A name or label for the stream type. 9 | ''' 10 | reason = ''' 11 | The requirement applies to IANA registration. 12 | ''' 13 | 14 | [[exception]] 15 | quote = ''' 16 | Specifications for permanent registrations MUST include a description 17 | of the stream type, including the layout and semantics of the stream 18 | contents. 19 | ''' 20 | reason = ''' 21 | The requirement applies to IANA registration. 22 | ''' 23 | 24 | [[exception]] 25 | quote = ''' 26 | Each code of the format 0x1f * N + 0x21 for non-negative integer 27 | values of N (that is, 0x21, 0x40, ..., through 0x3ffffffffffffffe) 28 | MUST NOT be assigned by IANA and MUST NOT appear in the listing of 29 | assigned values. 30 | ''' 31 | reason = ''' 32 | The requirement applies to IANA registration. 33 | ''' 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 h3 authors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /h3/src/proto/push.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::fmt::{self, Display}; 3 | 4 | use super::varint::VarInt; 5 | 6 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 7 | pub struct PushId(pub(crate) u64); 8 | 9 | #[derive(Debug, PartialEq)] 10 | pub struct InvalidPushId(u64); 11 | 12 | impl TryFrom for PushId { 13 | type Error = InvalidPushId; 14 | fn try_from(v: u64) -> Result { 15 | match VarInt::try_from(v) { 16 | Ok(id) => Ok(id.into()), 17 | Err(_) => Err(InvalidPushId(v)), 18 | } 19 | } 20 | } 21 | 22 | impl Display for InvalidPushId { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | write!(f, "invalid push id: {:x}", self.0) 25 | } 26 | } 27 | 28 | impl From for PushId { 29 | fn from(v: VarInt) -> Self { 30 | Self(v.0) 31 | } 32 | } 33 | 34 | impl From for VarInt { 35 | fn from(v: PushId) -> Self { 36 | Self(v.0) 37 | } 38 | } 39 | 40 | impl fmt::Display for PushId { 41 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 42 | write!(f, "push {}", self.0) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /changelog-h3-quinn.md: -------------------------------------------------------------------------------- 1 | ### v0.0.10 (2025-05-06) 2 | * use new h3 error types 3 | * use new datagram traits 4 | 5 | ### v0.0.9 (2025-03-18) 6 | * fix private field usage from quinn 7 | 8 | ### v0.0.8 (2025-03-15) 9 | * avoid copying data when sending 10 | * new datagram feature 11 | 12 | ### v0.0.7 (2024-07-01) 13 | * Consolidate quic trait redundancy 14 | * make types Sync 15 | * new tracing feature 16 | 17 | ### v0.0.6 (2024-05-20) 18 | * use quinn 0.11 19 | 20 | ### v0.0.5 (2024-01-24) 21 | 22 | * Update to `http` v1. 23 | 24 | ### v0.0.4 (2023-10-23) 25 | 26 | * Fix `cargo doc` warning ([3ef7c1a](https://github.com/hyperium/h3/commit/3ef7c1a37b635e8446322d8f8d3a68580a208ad8)) 27 | * Initial WebTransport support ([22da938](https://github.com/hyperium/h3/commit/22da9387f19d724852b3bf1dfd7e66f0fd45cb81)) 28 | 29 | 30 | ### v0.0.3 (2023-05-16) 31 | 32 | * Update dependencies (quinn 0.10 and rustls 0.21) ([fabf614](https://github.com/hyperium/h3/commit/fabf6149c9ee57d7803ea5fb4426f895cbb5d244)) 33 | 34 | ### v0.0.2 (2023-04-11) 35 | 36 | #### Bug Fixes 37 | 38 | * support quinn 0.9 ([49301f1](https://github.com/hyperium/h3/commit/49301f18e15d3acffc2a8d8bea1a8038c5f3fe6d)) 39 | 40 | 41 | ### v0.0.1 (2023-03-09) 42 | 43 | initial release 44 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.6.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.6" 2 | 3 | [[exception]] 4 | quote = ''' 5 | If the client has not yet 6 | validated the connection for the origin indicated by the pushed 7 | request, it MUST perform the same verification process it would do 8 | before sending a request for that origin on the connection; see 9 | Section 3.3. 10 | ''' 11 | reason = ''' 12 | h3 is not aware of connection establishment details. 13 | ''' 14 | 15 | [[exception]] 16 | quote = ''' 17 | If this verification fails, the client MUST NOT 18 | consider the server authoritative for that origin. 19 | ''' 20 | reason = ''' 21 | h3 is not aware of connection establishment details. 22 | ''' 23 | 24 | [[exception]] 25 | quote = ''' 26 | These 27 | associations do not affect the operation of the protocol, but they 28 | MAY be considered by user agents when deciding how to use pushed 29 | resources. 30 | ''' 31 | reason = ''' 32 | For maximum flexibility, this should be left to the user to decide. 33 | ''' 34 | 35 | [[exception]] 36 | quote = ''' 37 | Pushed responses that are not cacheable MUST NOT be stored by any 38 | HTTP cache. They MAY be made available to the application 39 | separately. 40 | ''' 41 | reason = ''' 42 | h3 does not handle caching. 43 | ''' 44 | -------------------------------------------------------------------------------- /h3-quinn/README.md: -------------------------------------------------------------------------------- 1 | # h3-quinn 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../LICENSE) 4 | [![CI](https://github.com/hyperium/h3/workflows/CI/badge.svg)](https://github.com/hyperium/h3/actions?query=workflow%3ACI) 5 | [![Crates.io](https://img.shields.io/crates/v/h3-quinn.svg)](https://crates.io/crates/h3-quinn) 6 | [![Documentation](https://docs.rs/h3-quinn/badge.svg)](https://docs.rs/h3-quinn) 7 | 8 | QUIC transport implementation for [h3](https://github.com/hyperium/h3) based on [Quinn](https://github.com/quinn-rs/quinn). 9 | 10 | ## Overview 11 | 12 | `h3-quinn` provides the integration between the `h3` HTTP/3 implementation and the `quinn` QUIC transport library. This creates a fully functional HTTP/3 client and server using Quinn as the underlying QUIC implementation. 13 | 14 | ## Features 15 | 16 | - Complete implementation of the `h3` QUIC transport traits 17 | - Full support for HTTP/3 client and server functionality 18 | - Optional tracing support 19 | - Optional datagram support 20 | 21 | ## License 22 | 23 | This project is licensed under the [MIT license](../LICENSE). 24 | 25 | ## See Also 26 | 27 | - [h3](https://github.com/hyperium/h3) - The core HTTP/3 implementation 28 | - [Quinn](https://github.com/quinn-rs/quinn) - The QUIC implementation used by this crate 29 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/3.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-3.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | The use 6 | of other QUIC transport versions with HTTP/3 MAY be defined by future 7 | specifications. 8 | ''' 9 | reason = ''' 10 | This is a suggestion of future spec development. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | HTTP/3 clients MUST support a mechanism to indicate the 16 | target host to the server during the TLS handshake. 17 | ''' 18 | reason = ''' 19 | This pertains to the TLS implementation, as h3 does not inspect TLS 20 | messages. 21 | ''' 22 | 23 | [[exception]] 24 | quote = ''' 25 | If the server is 26 | identified by a domain name ([DNS-TERMS]), clients MUST send the 27 | Server Name Indication (SNI; [RFC6066]) TLS extension unless an 28 | alternative mechanism to indicate the target host is used. 29 | ''' 30 | reason = ''' 31 | This pertains to the TLS implementation, as h3 does not inspect TLS 32 | messages. 33 | ''' 34 | 35 | [[exception]] 36 | quote = ''' 37 | During connection establishment, HTTP/3 support is indicated by 38 | selecting the ALPN token "h3" in the TLS handshake. Support for 39 | other application-layer protocols MAY be offered in the same 40 | handshake. 41 | ''' 42 | reason = ''' 43 | h3 does not handle protocol negotiation. 44 | ''' 45 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.2.3.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.2.3" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | When a client sends a CANCEL_PUSH frame, it is indicating that it 6 | does not wish to receive the promised resource. The server SHOULD 7 | abort sending the resource, but the mechanism to do so depends on the 8 | state of the corresponding push stream. 9 | ''' 10 | 11 | [[TODO]] 12 | quote = ''' 13 | If the push stream is 14 | open, the server SHOULD abruptly terminate that stream. 15 | ''' 16 | 17 | [[TODO]] 18 | quote = ''' 19 | If the push 20 | stream has already ended, the server MAY still abruptly terminate the 21 | stream or MAY take no action. 22 | ''' 23 | 24 | [[TODO]] 25 | quote = ''' 26 | Regardless of 27 | whether a push stream has been opened, a server SHOULD send a 28 | CANCEL_PUSH frame when it determines that promise will not be 29 | fulfilled. 30 | ''' 31 | 32 | [[TODO]] 33 | quote = ''' 34 | A client SHOULD NOT send a CANCEL_PUSH frame 35 | when it has already received a corresponding push stream. 36 | ''' 37 | 38 | [[TODO]] 39 | quote = ''' 40 | A push 41 | stream could arrive after a client has sent a CANCEL_PUSH frame, 42 | because a server might not have processed the CANCEL_PUSH. The 43 | client SHOULD abort reading the stream with an error code of 44 | H3_REQUEST_CANCELLED. 45 | ''' 46 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.2.5.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.2.5" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | A server MUST NOT use a push ID that is larger than the client has 6 | provided in a MAX_PUSH_ID frame (Section 7.2.7). 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | A server MAY use the same push ID in multiple PUSH_PROMISE frames. 12 | If so, the decompressed request header sets MUST contain the same 13 | fields in the same order, and both the name and the value in each 14 | field MUST be exact matches. 15 | ''' 16 | 17 | [[TODO]] 18 | quote = ''' 19 | Clients SHOULD compare the request 20 | header sections for resources promised multiple times. If a client 21 | receives a push ID that has already been promised and detects a 22 | mismatch, it MUST respond with a connection error of type 23 | H3_GENERAL_PROTOCOL_ERROR. If the decompressed field sections match 24 | exactly, the client SHOULD associate the pushed content with each 25 | stream on which a PUSH_PROMISE frame was received. 26 | ''' 27 | 28 | [[TODO]] 29 | quote = ''' 30 | Allowing duplicate references to the same push ID is primarily to 31 | reduce duplication caused by concurrent requests. A server SHOULD 32 | avoid reusing a push ID over a long period. 33 | ''' 34 | 35 | [[TODO]] 36 | quote = ''' 37 | A client MUST NOT send a PUSH_PROMISE frame. 38 | ''' 39 | -------------------------------------------------------------------------------- /h3-datagram/src/client.rs: -------------------------------------------------------------------------------- 1 | //! client API 2 | 3 | use std::marker::PhantomData; 4 | 5 | use bytes::Buf; 6 | use h3::{ 7 | client::Connection, 8 | quic::{self}, 9 | }; 10 | 11 | use crate::{ 12 | datagram_handler::{DatagramReader, DatagramSender, HandleDatagramsExt}, 13 | quic_traits::DatagramConnectionExt, 14 | }; 15 | 16 | impl HandleDatagramsExt for Connection 17 | where 18 | B: Buf, 19 | C: quic::Connection + DatagramConnectionExt, 20 | { 21 | fn get_datagram_sender( 22 | &self, 23 | stream_id: quic::StreamId, 24 | ) -> crate::datagram_handler::DatagramSender< 25 | >::SendDatagramHandler, 26 | B, 27 | > { 28 | DatagramSender { 29 | handler: self.inner.conn.send_datagram_handler(), 30 | _marker: PhantomData, 31 | shared_state: self.inner.shared.clone(), 32 | stream_id, 33 | } 34 | } 35 | 36 | fn get_datagram_reader( 37 | &self, 38 | ) -> crate::datagram_handler::DatagramReader< 39 | >::RecvDatagramHandler, 40 | > { 41 | DatagramReader { 42 | handler: self.inner.conn.recv_datagram_handler(), 43 | shared_state: self.inner.shared.clone(), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /h3/src/webtransport/session_id.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use crate::proto::{ 4 | coding::{Decode, Encode}, 5 | stream::{InvalidStreamId, StreamId}, 6 | varint::VarInt, 7 | }; 8 | 9 | /// Identifies a WebTransport session 10 | /// 11 | /// The session id is the same as the stream id of the CONNECT request. 12 | #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 13 | pub struct SessionId(u64); 14 | impl SessionId { 15 | pub(crate) fn from_varint(id: VarInt) -> SessionId { 16 | Self(id.0) 17 | } 18 | 19 | pub(crate) fn into_inner(self) -> u64 { 20 | self.0 21 | } 22 | } 23 | 24 | impl TryFrom for SessionId { 25 | type Error = InvalidStreamId; 26 | fn try_from(v: u64) -> Result { 27 | if v > VarInt::MAX.0 { 28 | return Err(InvalidStreamId(v)); 29 | } 30 | Ok(Self(v)) 31 | } 32 | } 33 | 34 | impl Encode for SessionId { 35 | fn encode(&self, buf: &mut B) { 36 | VarInt::from_u64(self.0).unwrap().encode(buf); 37 | } 38 | } 39 | 40 | impl Decode for SessionId { 41 | fn decode(buf: &mut B) -> crate::proto::coding::Result { 42 | Ok(Self(VarInt::decode(buf)?.into_inner())) 43 | } 44 | } 45 | 46 | impl From for SessionId { 47 | fn from(value: StreamId) -> Self { 48 | Self(value.index()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /h3-datagram/src/server.rs: -------------------------------------------------------------------------------- 1 | //! server API 2 | 3 | use std::marker::PhantomData; 4 | 5 | use bytes::Buf; 6 | use h3::{ 7 | quic::{self}, 8 | server::Connection, 9 | }; 10 | 11 | use crate::{ 12 | datagram_handler::{DatagramReader, DatagramSender, HandleDatagramsExt}, 13 | quic_traits::DatagramConnectionExt, 14 | }; 15 | 16 | impl HandleDatagramsExt for Connection 17 | where 18 | B: Buf, 19 | C: quic::Connection + DatagramConnectionExt, 20 | { 21 | /// Get the datagram sender 22 | fn get_datagram_sender( 23 | &self, 24 | stream_id: quic::StreamId, 25 | ) -> crate::datagram_handler::DatagramSender< 26 | >::SendDatagramHandler, 27 | B, 28 | > { 29 | DatagramSender { 30 | handler: self.inner.conn.send_datagram_handler(), 31 | _marker: PhantomData, 32 | shared_state: self.inner.shared.clone(), 33 | stream_id, 34 | } 35 | } 36 | 37 | /// Get the datagram reader 38 | fn get_datagram_reader( 39 | &self, 40 | ) -> crate::datagram_handler::DatagramReader< 41 | >::RecvDatagramHandler, 42 | > { 43 | DatagramReader { 44 | handler: self.inner.conn.recv_datagram_handler(), 45 | shared_state: self.inner.shared.clone(), 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/5.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-5.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | The server sends a client- 6 | initiated bidirectional stream ID; the client sends a push ID. 7 | Requests or pushes with the indicated identifier or greater are 8 | rejected (Section 4.1.1) by the sender of the GOAWAY. This 9 | identifier MAY be zero if no requests or pushes were processed. 10 | ''' 11 | reason = ''' 12 | This is left to the user to decide. 13 | ''' 14 | 15 | [[exception]] 16 | quote = ''' 17 | Clients 18 | MAY establish a new connection to send additional requests. 19 | ''' 20 | reason = ''' 21 | This is left to the user to decide. 22 | ''' 23 | 24 | [[exception]] 25 | quote = ''' 26 | Servers SHOULD send a GOAWAY frame when the closing of a connection 27 | is known in advance, even if the advance notice is small, so that the 28 | remote peer can know whether or not a request has been partially 29 | processed. 30 | ''' 31 | reason = ''' 32 | The user should initiate this, as h3 is unable to anticipate when to 33 | close a connection. 34 | ''' 35 | 36 | [[exception]] 37 | quote = ''' 38 | Once all accepted requests and pushes have been processed, the 39 | endpoint can permit the connection to become idle, or it MAY initiate 40 | an immediate closure of the connection. 41 | ''' 42 | reason = ''' 43 | The user should decide how to handle this, as h3 should not know 44 | anything about message processing. 45 | ''' 46 | -------------------------------------------------------------------------------- /ci/h3spec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LOGFILE=h3server.log 4 | if ! [ -e "h3spec-linux-x86_64" ] ; then 5 | # if we don't already have a h3spec executable, wget it from github 6 | wget https://github.com/kazu-yamamoto/h3spec/releases/download/v0.1.13/h3spec-linux-x86_64 7 | chmod +x h3spec-linux-x86_64 8 | fi 9 | 10 | # Build the server example 11 | cargo build --example server 12 | 13 | # Start the server example 14 | ./target/debug/examples/server --listen=[::]:4433 &> $LOGFILE & 15 | SERVER_PID=$! 16 | 17 | sleep 1s 18 | 19 | # Run the test 20 | ./h3spec-linux-x86_64 localhost 4433 -n \ 21 | --skip "/QUIC servers/MUST send missing_extension TLS alert if the quic_transport_parameters extension does not included [TLS 8.2]/" \ 22 | --skip "/HTTP/3 servers/MUST send H3_FRAME_UNEXPECTED if CANCEL_PUSH is received in a request stream [HTTP/3 7.2.5]/" \ 23 | --skip "/HTTP/3 servers/MUST send QPACK_ENCODER_STREAM_ERROR if a new dynamic table capacity value exceeds the limit [QPACK 4.1.3]/" \ 24 | --skip "/HTTP/3 servers/MUST send QPACK_DECODER_STREAM_ERROR if Insert Count Increment is 0 [QPACK 4.4.3]/" \ 25 | --skip "/HTTP/3 servers/MUST send H3_MESSAGE_ERROR if a pseudo-header is duplicated [HTTP/3 4.1.1]/" \ 26 | 27 | H3SPEC_STATUS=$? 28 | 29 | if [ "${H3SPEC_STATUS}" -eq 0 ]; then 30 | echo "h3spec passed!" 31 | else 32 | echo "h3spec failed! Server Logs:" 33 | cat $LOGFILE 34 | fi 35 | kill "${SERVER_PID}" 36 | exit "${H3SPEC_STATUS}" 37 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | publish = false 5 | edition = "2021" 6 | 7 | # If you copy one of the examples into a new project, you should be using 8 | # [dependencies] instead. 9 | [dev-dependencies] 10 | anyhow = "1.0" 11 | bytes = "1" 12 | futures = "0.3" 13 | h3 = { path = "../h3", features = ["tracing"] } 14 | h3-quinn = { path = "../h3-quinn", features = ["tracing", "datagram"] } 15 | h3-webtransport = { path = "../h3-webtransport" } 16 | http = "1" 17 | quinn = { version = "0.11", default-features = false, features = [ 18 | "runtime-tokio", 19 | "rustls", 20 | "ring", 21 | ] } 22 | rcgen = { version = "0.14" } 23 | rustls = { version = "0.23", default-features = false, features = [ 24 | "logging", 25 | "ring", 26 | "std", 27 | ] } 28 | rustls-native-certs = "0.8" 29 | structopt = "0.3" 30 | tokio = { version = "1.27", features = ["full"] } 31 | tracing = "0.1.37" 32 | tracing-subscriber = { version = "0.3", default-features = false, features = [ 33 | "fmt", 34 | "ansi", 35 | "env-filter", 36 | "time", 37 | "tracing-log", 38 | ] } 39 | octets = "0.3.0" 40 | 41 | tracing-tree = { version = "0.4" } 42 | h3-datagram = { path = "../h3-datagram" } 43 | 44 | [features] 45 | tree = [] 46 | 47 | [[example]] 48 | name = "client" 49 | path = "client.rs" 50 | 51 | [[example]] 52 | name = "server" 53 | path = "server.rs" 54 | 55 | [[example]] 56 | name = "webtransport_server" 57 | path = "webtransport_server.rs" 58 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | On a given stream, receipt of multiple requests or receipt of an 6 | additional HTTP response following a final HTTP response MUST be 7 | treated as malformed. 8 | ''' 9 | 10 | [[TODO]] 11 | quote = ''' 12 | A server MAY send one or more PUSH_PROMISE frames before, after, or 13 | interleaved with the frames of a response message. 14 | ''' 15 | 16 | [[TODO]] 17 | quote = ''' 18 | PUSH_PROMISE frames are not permitted on push streams; 19 | a pushed response that includes PUSH_PROMISE frames MUST be treated 20 | as a connection error of type H3_FRAME_UNEXPECTED. 21 | ''' 22 | 23 | [[TODO]] 24 | quote = ''' 25 | Frames of unknown types (Section 9), including reserved frames 26 | (Section 7.2.8) MAY be sent on a request or push stream before, 27 | after, or interleaved with other frames described in this section. 28 | ''' 29 | 30 | [[TODO]] 31 | quote = ''' 32 | Transfer codings (see Section 7 of [HTTP/1.1]) are not defined for 33 | HTTP/3; the Transfer-Encoding header field MUST NOT be used. 34 | ''' 35 | 36 | [[TODO]] 37 | quote = ''' 38 | A response MAY consist of multiple messages when and only when one or 39 | more interim responses (1xx; see Section 15.2 of [HTTP]) precede a 40 | final response to the same request. 41 | ''' 42 | 43 | [[TODO]] 44 | quote = ''' 45 | Unless using the CONNECT method (see 46 | Section 4.4), clients MUST NOT make stream closure dependent on 47 | receiving a response to their request. 48 | ''' 49 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/11.2.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-11.2.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | If an entry is present in 6 | only one registry, every effort SHOULD be made to avoid assigning the 7 | corresponding value to an unrelated operation. 8 | ''' 9 | reason = ''' 10 | The requirement applies to IANA registration. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | Expert reviewers MAY 16 | reject unrelated registrations that would conflict with the same 17 | value in the corresponding registry. 18 | ''' 19 | reason = ''' 20 | The requirement applies to IANA registration. 21 | ''' 22 | 23 | [[exception]] 24 | quote = ''' 25 | In addition to common fields as described in Section 11.2, permanent 26 | registrations in this registry MUST include the following field: 27 | 28 | Frame Type: A name or label for the frame type. 29 | ''' 30 | reason = ''' 31 | The requirement applies to IANA registration. 32 | ''' 33 | 34 | [[exception]] 35 | quote = ''' 36 | Specifications of frame types MUST include a description of the frame 37 | layout and its semantics, including any parts of the frame that are 38 | conditionally present. 39 | ''' 40 | reason = ''' 41 | The requirement applies to IANA registration. 42 | ''' 43 | 44 | [[exception]] 45 | quote = ''' 46 | Each code of the format 0x1f * N + 0x21 for non-negative integer 47 | values of N (that is, 0x21, 0x40, ..., through 0x3ffffffffffffffe) 48 | MUST NOT be assigned by IANA and MUST NOT appear in the listing of 49 | assigned values. 50 | ''' 51 | reason = ''' 52 | The requirement applies to IANA registration. 53 | ''' 54 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/11.2.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-11.2.2" 2 | 3 | [[exception]] 4 | quote = ''' 5 | If an entry is present in only one registry, every 6 | effort SHOULD be made to avoid assigning the corresponding value to 7 | an unrelated operation. 8 | ''' 9 | reason = ''' 10 | The requirement applies to IANA registration. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | Expert reviewers MAY reject unrelated 16 | registrations that would conflict with the same value in the 17 | corresponding registry. 18 | ''' 19 | reason = ''' 20 | The requirement applies to IANA registration. 21 | ''' 22 | 23 | [[exception]] 24 | quote = ''' 25 | In addition to common fields as described in Section 11.2, permanent 26 | registrations in this registry MUST include the following fields: 27 | 28 | Setting Name: A symbolic name for the setting. Specifying a setting 29 | name is optional. 30 | ''' 31 | reason = ''' 32 | The requirement applies to IANA registration. 33 | ''' 34 | 35 | [[exception]] 36 | quote = ''' 37 | Default: The value of the setting unless otherwise indicated. A 38 | default SHOULD be the most restrictive possible value. 39 | ''' 40 | reason = ''' 41 | The requirement applies to IANA registration. 42 | ''' 43 | 44 | [[exception]] 45 | quote = ''' 46 | Each code of the format 0x1f * N + 0x21 for non-negative integer 47 | values of N (that is, 0x21, 0x40, ..., through 0x3ffffffffffffffe) 48 | MUST NOT be assigned by IANA and MUST NOT appear in the listing of 49 | assigned values. 50 | ''' 51 | reason = ''' 52 | The requirement applies to IANA registration. 53 | ''' 54 | -------------------------------------------------------------------------------- /h3/src/ext.rs: -------------------------------------------------------------------------------- 1 | //! Extensions for the HTTP/3 protocol. 2 | 3 | use std::str::FromStr; 4 | 5 | /// Describes the `:protocol` pseudo-header for extended connect 6 | /// 7 | /// See: 8 | #[derive(Copy, PartialEq, Debug, Clone)] 9 | pub struct Protocol(ProtocolInner); 10 | 11 | impl Protocol { 12 | /// WebTransport protocol 13 | pub const WEB_TRANSPORT: Protocol = Protocol(ProtocolInner::WebTransport); 14 | /// RFC 9298 protocol 15 | pub const CONNECT_UDP: Protocol = Protocol(ProtocolInner::ConnectUdp); 16 | /// RFC 9484 protocol 17 | pub const CONNECT_IP: Protocol = Protocol(ProtocolInner::ConnectIp); 18 | 19 | /// Return a &str representation of the `:protocol` pseudo-header value 20 | #[inline] 21 | pub fn as_str(&self) -> &str { 22 | match self.0 { 23 | ProtocolInner::WebTransport => "webtransport", 24 | ProtocolInner::ConnectUdp => "connect-udp", 25 | ProtocolInner::ConnectIp => "connect-ip", 26 | } 27 | } 28 | } 29 | 30 | #[derive(Copy, PartialEq, Debug, Clone)] 31 | enum ProtocolInner { 32 | WebTransport, 33 | ConnectUdp, 34 | ConnectIp, 35 | } 36 | 37 | /// Error when parsing the protocol 38 | pub struct InvalidProtocol; 39 | 40 | impl FromStr for Protocol { 41 | type Err = InvalidProtocol; 42 | 43 | fn from_str(s: &str) -> Result { 44 | match s { 45 | "webtransport" => Ok(Self(ProtocolInner::WebTransport)), 46 | "connect-udp" => Ok(Self(ProtocolInner::ConnectUdp)), 47 | "connect-ip" => Ok(Self(ProtocolInner::ConnectIp)), 48 | _ => Err(InvalidProtocol), 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/3.3.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-3.3" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Once a connection to a server endpoint exists, this connection MAY be 6 | reused for requests with multiple different URI authority components. 7 | ''' 8 | reason = ''' 9 | h3 does not have to do anything specific, as we cannot anticipate when 10 | will the client decide to reuse a connection. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | To use an existing connection for a new origin, clients MUST validate 16 | the certificate presented by the server for the new origin server 17 | using the process described in Section 4.3.4 of [HTTP]. 18 | ''' 19 | reason = ''' 20 | This should be handled by the user, as h3 does not inspect certificate 21 | messages. 22 | ''' 23 | 24 | [[exception]] 25 | quote = ''' 26 | If the certificate is not acceptable with regard to the new origin 27 | for any reason, the connection MUST NOT be reused and a new 28 | connection SHOULD be established for the new origin. 29 | ''' 30 | reason = ''' 31 | This should be handled by the user, as h3 does not inspect certificate 32 | messages. 33 | ''' 34 | 35 | [[exception]] 36 | quote = ''' 37 | If the reason 38 | the certificate cannot be verified might apply to other origins 39 | already associated with the connection, the client SHOULD revalidate 40 | the server certificate for those origins. 41 | ''' 42 | reason = ''' 43 | This should be handled by the user, as h3 does not inspect certificate 44 | messages. 45 | ''' 46 | 47 | [[exception]] 48 | quote = ''' 49 | A client MAY open 50 | multiple HTTP/3 connections to the same IP address and UDP port using 51 | different transport or TLS configurations but SHOULD avoid creating 52 | multiple connections with the same configuration. 53 | ''' 54 | reason = ''' 55 | This pertains to the user of h3. 56 | ''' 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thank you for the interest in contributing to `h3`. All important information are below. 2 | 3 | # Duvet 4 | The [`duvet`][] crate is used in `h3` to track [spec][] compliance. 5 | `duvet` does that via spec citations in the code, as customized comments. 6 | The comments should be kept up-to-date with the code. 7 | 8 | [duvet]: https://crates.io/crates/duvet 9 | 10 | ## General 11 | Duvet tracks all keywords (MUST, MUST NOT, SHOULD, SHOULD NOT and MAY) from an RFC. 12 | You can see those in the [report][] on the menu on the left side. There are all sections of the [spec][]. On each section the paragraphs with the keywords are highlighted. 13 | 14 | Duvet sees all citations of each of these paragraphs and their status. At the bottom of each section there is a summary of all the keywords with their status. 15 | 16 | 17 | [spec]: https://www.rfc-editor.org/rfc/rfc9114 18 | [report]: https://hyper.rs/h3/ci/compliance/report.html#/ 19 | 20 | ## Citations 21 | 22 | There are three ways to keep track of all the requirements: 23 | 24 | 1. In line citations as comments in the source code 25 | 2. Citations in .toml files 26 | 3. Issues in the repository 27 | 28 | 29 | The citations can be created with a click on the highlighted sections in the sections. 30 | There are different citation types with different meanings. 31 | ### Citation 32 | Standard citation in the source code where the requirement is implemented. 33 | 34 | ### Implication 35 | Citation where the related code is self-evident. This means no Test is required. 36 | 37 | ### Test 38 | Indicates that the related code is a test for the requirement. 39 | 40 | ### Exception 41 | Is the marker that the requirement is not applied to the crate. For example those which are applied to IANA registration. 42 | 43 | ### Todo 44 | Is the marker that the requirement still needs to be fulfilled. 45 | -------------------------------------------------------------------------------- /h3/src/proto/coding.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BufMut}; 2 | 3 | use super::varint::VarInt; 4 | 5 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 6 | pub struct UnexpectedEnd(pub usize); 7 | 8 | pub type Result = ::std::result::Result; 9 | 10 | // Trait for encoding / decoding helpers on basic types, such as `u16`, for 11 | // example: `buf.decode::()?`. 12 | // This enables to return `UnexpectedEnd` instead of panicking as the `Buf` 13 | // impls do when there is not enough bytes. 14 | 15 | pub trait Encode { 16 | fn encode(&self, buf: &mut B); 17 | } 18 | 19 | pub trait Decode: Sized { 20 | fn decode(buf: &mut B) -> Result; 21 | } 22 | 23 | impl Encode for u8 { 24 | fn encode(&self, buf: &mut B) { 25 | buf.put_u8(*self); 26 | } 27 | } 28 | 29 | impl Decode for u8 { 30 | fn decode(buf: &mut B) -> Result { 31 | if buf.remaining() < 1 { 32 | return Err(UnexpectedEnd(1)); 33 | } 34 | Ok(buf.get_u8()) 35 | } 36 | } 37 | 38 | pub trait BufExt { 39 | #[allow(dead_code)] 40 | fn get(&mut self) -> Result; 41 | #[allow(dead_code)] 42 | fn get_var(&mut self) -> Result; 43 | } 44 | 45 | impl BufExt for T { 46 | fn get(&mut self) -> Result { 47 | U::decode(self) 48 | } 49 | 50 | fn get_var(&mut self) -> Result { 51 | Ok(VarInt::decode(self)?.into_inner()) 52 | } 53 | } 54 | 55 | pub trait BufMutExt { 56 | #[allow(dead_code)] 57 | fn write(&mut self, x: T); 58 | #[allow(dead_code)] 59 | fn write_var(&mut self, x: u64); 60 | } 61 | 62 | impl BufMutExt for T { 63 | fn write(&mut self, x: U) { 64 | x.encode(self); 65 | } 66 | 67 | fn write_var(&mut self, x: u64) { 68 | VarInt::from_u64(x).unwrap().encode(self); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /h3/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "h3" 3 | version = "0.0.8" 4 | rust-version = "1.74" 5 | authors = [ 6 | "Sean McArthur ", 7 | "Jean-Christophe BEGUE ", 8 | ] 9 | license = "MIT" 10 | edition = "2021" 11 | documentation = "https://docs.rs/h3" 12 | repository = "https://github.com/hyperium/h3" 13 | readme = "../README.md" 14 | description = "An async HTTP/3 implementation." 15 | keywords = ["http3", "quic", "h3"] 16 | categories = [ 17 | "network-programming", 18 | "web-programming::http-client", 19 | "web-programming::http-server", 20 | ] 21 | 22 | [features] 23 | i-implement-a-third-party-backend-and-opt-into-breaking-changes = [] 24 | tracing = ["dep:tracing"] 25 | 26 | [dependencies] 27 | bytes = "1" 28 | futures-util = { version = "0.3", default-features = false, features = ["io"] } 29 | http = "1" 30 | tokio = { version = "1", features = ["sync"] } 31 | pin-project-lite = { version = "0.2", default-features = false } 32 | tracing = {version = "0.1.40", optional = true} 33 | fastrand = "2.0.1" 34 | 35 | [dev-dependencies] 36 | assert_matches = "1.5.0" 37 | futures-util = { version = "0.3", default-features = false, features = ["io"] } 38 | proptest = "1" 39 | quinn = { version = "0.11", default-features = false, features = [ 40 | "runtime-tokio", 41 | "rustls", 42 | "ring", 43 | ] } 44 | quinn-proto = { version = "0.11", default-features = false } 45 | rcgen = "0.14" 46 | rustls = { version = "0.23", default-features = false, features = ["logging", "ring", "std"] } 47 | tokio = { version = "1", features = ["rt", "macros", "io-util", "io-std"] } 48 | tracing-subscriber = { version = "0.3", default-features = false, features = [ 49 | "fmt", 50 | "ansi", 51 | "env-filter", 52 | "time", 53 | "tracing-log", 54 | ] } 55 | futures = { version = "0.3.28" } 56 | tokio-util = { version = "0.7.9" } 57 | h3-datagram = {path = "../h3-datagram" } 58 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.6.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.6" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | A server SHOULD use push IDs sequentially, beginning from 6 | zero. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | A client MUST treat receipt of a push stream as a connection 12 | error of type H3_ID_ERROR when no MAX_PUSH_ID frame has been sent or 13 | when the stream references a push ID that is greater than the maximum 14 | push ID. 15 | ''' 16 | 17 | [[TODO]] 18 | quote = ''' 19 | When the 20 | same push ID is promised on multiple request streams, the 21 | decompressed request field sections MUST contain the same fields in 22 | the same order, and both the name and the value in each field MUST be 23 | identical. 24 | ''' 25 | 26 | [[TODO]] 27 | quote = ''' 28 | Not all requests can be pushed. A server MAY push requests that have 29 | the following properties: 30 | 31 | * cacheable; see Section 9.2.3 of [HTTP] 32 | * safe; see Section 9.2.1 of [HTTP] 33 | * does not include request content or a trailer section 34 | ''' 35 | 36 | [[TODO]] 37 | quote = ''' 38 | The server MUST include a value in the :authority pseudo-header field 39 | for which the server is authoritative. 40 | ''' 41 | 42 | [[TODO]] 43 | quote = ''' 44 | Clients SHOULD send a CANCEL_PUSH frame upon receipt of a 45 | PUSH_PROMISE frame carrying a request that is not cacheable, is not 46 | known to be safe, that indicates the presence of request content, or 47 | for which it does not consider the server authoritative. Any 48 | corresponding responses MUST NOT be used or cached. 49 | ''' 50 | 51 | [[TODO]] 52 | quote = ''' 53 | The server SHOULD send PUSH_PROMISE frames 54 | prior to sending HEADERS or DATA frames that reference the promised 55 | responses. This reduces the chance that a client requests a resource 56 | that will be pushed by the server. 57 | ''' 58 | 59 | [[TODO]] 60 | quote = ''' 61 | Clients SHOULD abort reading and discard data 62 | already read from push streams if no corresponding PUSH_PROMISE frame 63 | is processed in a reasonable amount of time. 64 | ''' 65 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/4.4.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.4" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | A CONNECT request MUST be constructed as follows: 6 | 7 | * The :method pseudo-header field is set to "CONNECT" 8 | * The :scheme and :path pseudo-header fields are omitted 9 | * The :authority pseudo-header field contains the host and port to connect to (equivalent to the authority-form of the request-target of CONNECT requests; see Section 7.1 of [HTTP]). 10 | ''' 11 | 12 | [[TODO]] 13 | quote = ''' 14 | Once the CONNECT method has completed, only DATA frames are permitted 15 | to be sent on the stream. Extension frames MAY be used if 16 | specifically permitted by the definition of the extension. 17 | ''' 18 | 19 | [[TODO]] 20 | quote = ''' 21 | Receipt 22 | of any other known frame type MUST be treated as a connection error 23 | of type H3_FRAME_UNEXPECTED. 24 | ''' 25 | 26 | [[TODO]] 27 | quote = ''' 28 | TCP connections that remain half closed in a single 29 | direction are not invalid, but are often handled poorly by servers, 30 | so clients SHOULD NOT close a stream for sending while they still 31 | expect to receive data from the target of the CONNECT. 32 | ''' 33 | 34 | [[TODO]] 35 | quote = ''' 36 | Correspondingly, if a proxy detects an error with the stream or the 37 | QUIC connection, it MUST close the TCP connection. 38 | ''' 39 | 40 | [[TODO]] 41 | quote = ''' 42 | If the proxy 43 | detects that the client has reset the stream or aborted reading from 44 | the stream, it MUST close the TCP connection. 45 | ''' 46 | 47 | [[TODO]] 48 | quote = ''' 49 | If the stream is reset 50 | or reading is aborted by the client, a proxy SHOULD perform the same 51 | operation on the other direction in order to ensure that both 52 | directions of the stream are cancelled. 53 | ''' 54 | 55 | [[TODO]] 56 | quote = ''' 57 | Since CONNECT creates a tunnel to an arbitrary server, proxies that 58 | support CONNECT SHOULD restrict its use to a set of known ports or a 59 | list of safe request targets; see Section 9.3.6 of [HTTP] for more 60 | details. 61 | ''' 62 | -------------------------------------------------------------------------------- /h3/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! HTTP/3 client and server 2 | #![deny(missing_docs, clippy::self_named_module_files)] 3 | #![allow(clippy::derive_partial_eq_without_eq)] 4 | 5 | pub mod client; 6 | 7 | mod config; 8 | //pub mod error; 9 | pub mod ext; 10 | pub mod quic; 11 | 12 | pub mod server; 13 | 14 | //pub use error::Error; 15 | 16 | mod buf; 17 | 18 | mod shared_state; 19 | 20 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 21 | pub use shared_state::{ConnectionState, SharedState}; 22 | 23 | pub mod error; 24 | 25 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 26 | #[allow(missing_docs)] 27 | pub mod connection; 28 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 29 | #[allow(missing_docs)] 30 | pub mod frame; 31 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 32 | #[allow(missing_docs)] 33 | pub mod proto; 34 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 35 | #[allow(dead_code, missing_docs)] 36 | pub mod qpack; 37 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 38 | #[allow(missing_docs)] 39 | pub mod stream; 40 | #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] 41 | #[allow(missing_docs)] 42 | pub mod webtransport; 43 | 44 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 45 | mod connection; 46 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 47 | mod frame; 48 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 49 | mod proto; 50 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 51 | #[allow(dead_code)] 52 | mod qpack; 53 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 54 | mod stream; 55 | #[cfg(not(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes"))] 56 | mod webtransport; 57 | 58 | #[cfg(test)] 59 | mod tests; 60 | #[cfg(test)] 61 | extern crate self as h3; 62 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | This is a quick starting guide to run the example. 3 | 4 | ## Start the Server 5 | To start the example server you can run following command: 6 | 7 | ```bash 8 | > cargo run --example server -- --listen=127.0.0.1:4433 9 | ``` 10 | 11 | This will start an the HTTP/3 server, listening to 127.0.0.1 on port 4433. 12 | This also generates a self-signed certificate for encryption. 13 | 14 | ## Start the client 15 | To start the example client you can run following command: 16 | 17 | ```bash 18 | > cargo run --example client -- https://localhost:4433 19 | ``` 20 | 21 | This sends an HTTP request to the server. 22 | 23 | ## Add some content to the Server 24 | So that the server responds something you can provide a directory with content files. 25 | 26 | ```bash 27 | > cargo run --example server -- --listen=127.0.0.1:4433 --dir=content/root 28 | ``` 29 | 30 | To start the client simply put the file name behind the URI: 31 | 32 | ```bash 33 | > cargo run --example client -- https://localhost:4433/index.html 34 | ``` 35 | 36 | ## Test against the Browser 37 | The first step is to run the server. 38 | For Browsers to work the server have to listen to ipv6 (`--listen=[::]:4433 `). 39 | Also the browser need a valid certificate (`--cert=examples/cert.der --key=examples/key.der`). 40 | 41 | ```bash 42 | > cargo run --example server -- --listen=[::]:4433 --dir=examples/root --cert=examples/cert.der --key=examples/key.der 43 | ``` 44 | 45 | Then run chromium and force it to use Quic. 46 | ```bash 47 | > chromium --enable-quic --quic-version=h3 --origin-to-force-quic-on=localhost:4433 48 | ``` 49 | 50 | Now you can navigate to files in the `root` folder for example `https://localhost:4433/index.html`. 51 | 52 | ## Debugging 53 | The example [example client](client.rs) can generate a `SSLKEYLOGFILE` to see the traffic unencrypted in tools like Wireshark. 54 | To set this up just set the `SSLKEYLOGFILE` environment variable to a file path and follow this [tutorial](https://wiki.wireshark.org/TLS#using-the-pre-master-secret). 55 | Then use the example client with the `--keylogfile=true` option to enable this. 56 | -------------------------------------------------------------------------------- /h3-datagram/src/quic_traits.rs: -------------------------------------------------------------------------------- 1 | //! QUIC Transport traits 2 | //! 3 | //! This module includes traits and types meant to allow being generic over any 4 | //! QUIC implementation. 5 | 6 | use core::task; 7 | use std::task::Poll; 8 | 9 | use bytes::Buf; 10 | use h3::quic::ConnectionErrorIncoming; 11 | 12 | use crate::datagram::EncodedDatagram; 13 | 14 | /// Connection Extension trait for a DatagramHandler type defined by the quic implementation 15 | pub trait DatagramConnectionExt { 16 | /// The type of the Datagram send Handler 17 | type SendDatagramHandler: SendDatagram; 18 | 19 | /// The type of the Datagram receive Handler 20 | type RecvDatagramHandler: RecvDatagram; 21 | 22 | /// Get the send datagram handler 23 | fn send_datagram_handler(&self) -> Self::SendDatagramHandler; 24 | 25 | /// Get the receive datagram handler 26 | fn recv_datagram_handler(&self) -> Self::RecvDatagramHandler; 27 | } 28 | 29 | /// Extends the `Connection` trait for sending datagrams 30 | /// 31 | /// See: 32 | pub trait SendDatagram { 33 | /// Send a datagram 34 | fn send_datagram>>( 35 | &mut self, 36 | data: T, 37 | ) -> Result<(), SendDatagramErrorIncoming>; 38 | } 39 | 40 | /// Extends the `Connection` trait for receiving datagrams 41 | /// 42 | /// See: 43 | pub trait RecvDatagram { 44 | /// The buffer type 45 | type Buffer: Buf; 46 | 47 | /// Poll the connection for incoming datagrams. 48 | fn poll_incoming_datagram( 49 | &mut self, 50 | cx: &mut task::Context<'_>, 51 | ) -> Poll>; 52 | } 53 | 54 | /// Types of errors when sending a datagram. 55 | #[derive(Debug)] 56 | pub enum SendDatagramErrorIncoming { 57 | /// The peer is not accepting datagrams 58 | /// 59 | /// This can be because the peer does not support it or disabled it or any other reason. 60 | NotAvailable, 61 | /// The datagram is too large to send 62 | TooLarge, 63 | /// Connection error 64 | ConnectionError(ConnectionErrorIncoming), 65 | } 66 | -------------------------------------------------------------------------------- /h3/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides methods to create a http/3 Server. 2 | //! 3 | //! It allows to accept incoming requests, and send responses. 4 | //! 5 | //! # Examples 6 | //! 7 | //! ## Simple example 8 | //! ```rust 9 | //! async fn doc(conn: C) 10 | //! where 11 | //! C: h3::quic::Connection + 'static, 12 | //! >::BidiStream: Send + 'static 13 | //! { 14 | //! let mut server_builder = h3::server::builder(); 15 | //! // Build the Connection 16 | //! let mut h3_conn = server_builder.build(conn).await.unwrap(); 17 | //! loop { 18 | //! // Accept incoming requests 19 | //! match h3_conn.accept().await { 20 | //! Ok(Some(resolver)) => { 21 | //! // spawn a new task to handle the request 22 | //! tokio::spawn(async move { 23 | //! // get the request 24 | //! let (req, mut stream) = resolver.resolve_request().await.unwrap(); 25 | //! // build a http response 26 | //! let response = http::Response::builder().status(http::StatusCode::OK).body(()).unwrap(); 27 | //! // send the response to the wire 28 | //! stream.send_response(response).await.unwrap(); 29 | //! // send some date 30 | //! stream.send_data(bytes::Bytes::from("test")).await.unwrap(); 31 | //! // finnish the stream 32 | //! stream.finish().await.unwrap(); 33 | //! }); 34 | //! } 35 | //! Ok(None) => { 36 | //! // break if no Request is accepted 37 | //! break; 38 | //! } 39 | //! Err(err) => { 40 | //! break; 41 | //! } 42 | //! } 43 | //! } 44 | //! } 45 | //! ``` 46 | //! 47 | //! ## File server 48 | //! A ready-to-use example of a file server is available [here](https://github.com/hyperium/h3/blob/master/examples/server.rs) 49 | 50 | mod builder; 51 | mod connection; 52 | mod request; 53 | mod stream; 54 | 55 | pub use builder::builder; 56 | pub use builder::Builder; 57 | pub use connection::Connection; 58 | pub use request::RequestResolver; 59 | pub use stream::RequestStream; 60 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/3.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-3.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Upon receiving a 6 | server certificate in the TLS handshake, the client MUST verify that 7 | the certificate is an acceptable match for the URI's origin server 8 | using the process described in Section 4.3.4 of [HTTP]. 9 | ''' 10 | reason = ''' 11 | This should be enforced by the TLS implementation, as h3 does not 12 | inspect certificate messages. 13 | ''' 14 | 15 | [[exception]] 16 | quote = ''' 17 | If the 18 | certificate cannot be verified with respect to the URI's origin 19 | server, the client MUST NOT consider the server authoritative for 20 | that origin. 21 | ''' 22 | reason = ''' 23 | This should be enforced by the TLS implementation, as h3 does not 24 | inspect certificate messages. 25 | ''' 26 | 27 | [[exception]] 28 | quote = ''' 29 | A client MAY attempt access to a resource with an "https" URI by 30 | resolving the host identifier to an IP address, establishing a QUIC 31 | connection to that address on the indicated port (including 32 | validation of the server certificate as described above), and sending 33 | an HTTP/3 request message targeting the URI to the server over that 34 | secured connection. 35 | ''' 36 | reason = ''' 37 | h3 does not need to do anything specific, as this is only a 38 | description of the expected behavior of a HTTP/3 client. 39 | ''' 40 | 41 | [[exception]] 42 | quote = ''' 43 | Connectivity problems (e.g., blocking UDP) can result in a failure to 44 | establish a QUIC connection; clients SHOULD attempt to use TCP-based 45 | versions of HTTP in this case. 46 | ''' 47 | reason = ''' 48 | Downgrading should be handled by other components of the 49 | implementation, as h3 is meant to be a dedicated QUIC-based HTTP 50 | implementation. 51 | ''' 52 | 53 | [[exception]] 54 | quote = ''' 55 | Servers MAY serve HTTP/3 on any UDP port; an alternative service 56 | advertisement always includes an explicit port, and URIs contain 57 | either an explicit port or a default port associated with the scheme. 58 | ''' 59 | reason = ''' 60 | h3 does not constrain which port the server will be listening on. It 61 | is up to the user to decide. 62 | 63 | The alt-svc advertisement should be handled by the user, as h3 only 64 | handles QUIC traffic. 65 | ''' 66 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Because some messages are large or unbounded, endpoints 6 | SHOULD begin processing partial HTTP messages once enough of the 7 | message has been received to make progress. 8 | ''' 9 | reason = ''' 10 | This should be initiated by the user, as h3 cannot determine when the 11 | received message is enough. 12 | ''' 13 | 14 | [[exception]] 15 | quote = ''' 16 | When the server does 17 | not need to receive the remainder of the request, it MAY abort 18 | reading the request stream, send a complete response, and cleanly 19 | close the sending part of the stream. The error code H3_NO_ERROR 20 | SHOULD be used when requesting that the client stop sending on the 21 | request stream. 22 | ''' 23 | reason = ''' 24 | This should be initiated by the user, as h3 cannot determine if the 25 | remainder of the request is needed. 26 | ''' 27 | 28 | [[exception]] 29 | quote = ''' 30 | Clients MUST NOT discard complete responses as a 31 | result of having their request terminated abruptly, though clients 32 | can always discard responses at their discretion for other reasons. 33 | ''' 34 | reason = ''' 35 | h3 does not discard complete responses, but h3 has no control over 36 | whether the user will do. 37 | ''' 38 | 39 | [[exception]] 40 | quote = ''' 41 | If the server sends a partial or complete response but does not abort 42 | reading the request, clients SHOULD continue sending the content of 43 | the request and close the stream normally. 44 | ''' 45 | reason = ''' 46 | h3 does not need to do anything specific. 47 | ''' 48 | 49 | 50 | [[exception]] 51 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1" 52 | quote = ''' 53 | After sending a final 54 | response, the server MUST close the stream for sending. 55 | ''' 56 | reason = ''' 57 | Users of h3 should close the stream for sending after sending a final response. As documented in the server docs. 58 | ''' 59 | 60 | [[exception]] 61 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1" 62 | quote = ''' 63 | After sending a request, a client MUST 64 | close the stream for sending. 65 | ''' 66 | reason = ''' 67 | Users of h3 should close the stream for sending after sending a request. As documented in the client docs. 68 | ''' 69 | 70 | 71 | -------------------------------------------------------------------------------- /.github/actions/compliance/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Spec compliance report' 2 | description: 'Generate compliance report and publish on Github pages' 3 | 4 | inputs: 5 | report-script: 6 | description: 'Path to script that generates a compliance report' 7 | required: true 8 | h3-dir: 9 | description: 'Path to the directory where h3 is cloned' 10 | required: false 11 | default: ${{ github.workspace }} 12 | 13 | runs: 14 | using: "composite" 15 | steps: 16 | - name: Install Rust toolchain 17 | uses: actions-rust-lang/setup-rust-toolchain@v1 18 | with: 19 | toolchain: stable 20 | 21 | - name: Cache builds 22 | uses: camshaft/rust-cache@v1 23 | 24 | - name: Install Duvet w/ caching 25 | uses: camshaft/install@v1 26 | with: 27 | crate: duvet 28 | 29 | - name: Set variables 30 | shell: bash 31 | run: | 32 | echo "SHA=`git rev-parse --short HEAD`" >> $GITHUB_ENV 33 | 34 | # create gh-pages as an orphan branch if branch does not exist 35 | - name: Checkout gh-pages 36 | shell: bash 37 | run: | 38 | git config --local user.name "github-actions[bot]" 39 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 40 | 41 | if git ls-remote --exit-code --heads origin gh-pages; then 42 | cp -r .duvet .github ci h3 target 43 | git switch gh-pages 44 | cp -r target/.duvet target/.github target/ci target/h3 . 45 | else 46 | git checkout --orphan gh-pages 47 | git reset 48 | fi 49 | 50 | - name: Generate report 51 | working-directory: ${{ inputs.h3-dir }} 52 | shell: bash 53 | run: | 54 | ${{ inputs.report-script }} 55 | 56 | # commit only when there are changes 57 | - name: Commit report changes 58 | working-directory: ${{ inputs.h3-dir }} 59 | shell: bash 60 | run: | 61 | cp .duvet/reports/* ci/compliance/ 62 | git add ci/compliance/*.html 63 | git diff --staged --quiet || \ 64 | git commit -m "${{ github.triggering_actor }}-${SHA}-${{ github.job }}#${{ github.run_number }}" 65 | 66 | # publish report only when pushing to master 67 | - name: Push to gh-pages 68 | if: github.ref == 'refs/heads/master' 69 | uses: ad-m/github-push-action@v1 70 | with: 71 | github_token: ${{ github.token }} 72 | branch: gh-pages 73 | force: true 74 | -------------------------------------------------------------------------------- /.duvet/todos/rfc9114/7.2.4.2.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-7.2.4.2" 2 | 3 | [[TODO]] 4 | quote = ''' 5 | An HTTP implementation MUST NOT send frames or requests that would be 6 | invalid based on its current understanding of the peer's settings. 7 | ''' 8 | 9 | [[TODO]] 10 | quote = ''' 11 | Each endpoint SHOULD use 12 | these initial values to send messages before the peer's SETTINGS 13 | frame has arrived, as packets carrying the settings can be lost or 14 | delayed. 15 | ''' 16 | 17 | [[TODO]] 18 | quote = ''' 19 | Clients SHOULD 20 | NOT wait indefinitely for SETTINGS to arrive before sending requests, 21 | but they SHOULD process received datagrams in order to increase the 22 | likelihood of processing SETTINGS before sending the first request. 23 | ''' 24 | 25 | [[TODO]] 26 | quote = ''' 27 | A client MUST comply 28 | with stored settings -- or default values if no values are stored -- 29 | when attempting 0-RTT. 30 | ''' 31 | 32 | [[TODO]] 33 | quote = ''' 34 | Once a server has provided new settings, 35 | clients MUST comply with those values. 36 | ''' 37 | 38 | [[TODO]] 39 | quote = ''' 40 | If the 41 | server cannot determine that the settings remembered by a client are 42 | compatible with its current settings, it MUST NOT accept 0-RTT data. 43 | Remembered settings are compatible if a client complying with those 44 | settings would not violate the server's current settings. 45 | ''' 46 | 47 | [[TODO]] 48 | quote = ''' 49 | A server MAY accept 0-RTT and subsequently provide different settings 50 | in its SETTINGS frame. 51 | ''' 52 | 53 | [[TODO]] 54 | quote = ''' 55 | If 0-RTT data is accepted by the server, its 56 | SETTINGS frame MUST NOT reduce any limits or alter any values that 57 | might be violated by the client with its 0-RTT data. 58 | ''' 59 | 60 | [[TODO]] 61 | quote = ''' 62 | The server MUST 63 | include all settings that differ from their default values. 64 | ''' 65 | 66 | [[TODO]] 67 | quote = ''' 68 | If a 69 | server accepts 0-RTT but then sends settings that are not compatible 70 | with the previously specified settings, this MUST be treated as a 71 | connection error of type H3_SETTINGS_ERROR. 72 | ''' 73 | 74 | [[TODO]] 75 | quote = ''' 76 | If a server accepts 77 | 0-RTT but then sends a SETTINGS frame that omits a setting value that 78 | the client understands (apart from reserved setting identifiers) that 79 | was previously specified to have a non-default value, this MUST be 80 | treated as a connection error of type H3_SETTINGS_ERROR. 81 | ''' 82 | -------------------------------------------------------------------------------- /changelog-h3.md: -------------------------------------------------------------------------------- 1 | ### v0.0.8 (2025-05-06) 2 | * fix integer overflow when parsing qpack prefixed integers 3 | * introduce new user facing error types 4 | * introduce new quic traits facing error types 5 | * `server::Connection::accept` now returns a `RequestResolver` instead of direct resolving the request to avoid head of line blocking 6 | * h3-datagram traits cleanup 7 | * some fixes in error handling 8 | 9 | ### v0.0.7 (2025-03-13) 10 | * Expose poll_recv_trailers APIs 11 | * Avoiding extra allocation for shared error 12 | * Added .id() for client RequestStream 13 | * move datagram to separate crate 14 | * Client ability to stop streams with error code 15 | * Add extended CONNECT setting for client conn 16 | 17 | ### v0.0.6 (2024-07-01) 18 | * Consolidate quic trait redundancy 19 | * start qpack streams 20 | * send grease stream in the background 21 | * new tracing feature 22 | 23 | ### v0.0.5 (2024-05-20) 24 | * add `poll_recv_data()` for server 25 | * use 2021 edition 26 | * some cleanups 27 | 28 | ### v0.0.4 (2024-01-24) 29 | 30 | * Update to `http` v1.0 31 | * Fix `try_recv` potentially hanging 32 | * Fix prefixed integers on 32bit targets 33 | 34 | ### v0.0.3 (2023-10-23) 35 | 36 | * Split out a `Settings` struct from `Config` ([a57ed22](https://github.com/hyperium/h3/commit/a57ed224ac5d17a635eb71eb6f83c1196f581a51)) 37 | * Add a test-only send_settings config option ([3991dca](https://github.com/hyperium/h3/commit/3991dcaf3801595e49d0bb7fb1649b4cf50292b7)) 38 | * Expose setting to disable grease ([dccb3cd](https://github.com/hyperium/h3/commit/dccb3cdae9d5a9d720fae5f774b53f0bd8a16019)) 39 | * bugfix: Actually encode extensions in header ([a38b194](https://github.com/hyperium/h3/commit/a38b194a2f00dc0b2b60564c299093204d349d7e)) 40 | * Initial support for RFC 9298 "Proxying UDP in HTTP" ([5a87580](https://github.com/hyperium/h3/commit/5a87580bd060b6a7d4dc625e990526d6998fda5c)) 41 | * Bump H3_DATAGRAM setting ID according to RFC9297 ([58c8e5c](https://github.com/hyperium/h3/commit/58c8e5cecb2b0c367d738989fe9c505936bc5ff3)) 42 | * Fix `cargo doc` warning ([3ef7c1a](https://github.com/hyperium/h3/commit/3ef7c1a37b635e8446322d8f8d3a68580a208ad8)) 43 | * Initial WebTransport support (in h3 is just some necessary code to support a WebTransport crate which contains most of the WebTransport implementation) ([22da938](https://github.com/hyperium/h3/commit/22da9387f19d724852b3bf1dfd7e66f0fd45cb81)) 44 | 45 | 46 | ### v0.0.2 (2023-04-11) 47 | 48 | #### Bug Fixes 49 | 50 | * distinguish push and stream ids ([da29aea](https://github.com/hyperium/h3/commit/da29aea305d61146664189346b3718458cb9f4d6)) 51 | 52 | 53 | ### v0.0.1 (2023-03-09) 54 | 55 | initial release 56 | -------------------------------------------------------------------------------- /.duvet/exceptions/rfc9114/4.1.1.toml: -------------------------------------------------------------------------------- 1 | target = "https://www.rfc-editor.org/rfc/rfc9114#section-4.1.1" 2 | 3 | [[exception]] 4 | quote = ''' 5 | Once a request stream has been opened, the request MAY be cancelled 6 | by either endpoint. 7 | ''' 8 | reason = ''' 9 | h3 does enable the user to cancel requests. It is up to the user to 10 | decide when to do it. 11 | ''' 12 | 13 | [[exception]] 14 | quote = ''' 15 | When possible, it is RECOMMENDED that servers 16 | send an HTTP response with an appropriate status code rather than 17 | cancelling a request it has already begun processing. 18 | ''' 19 | reason = ''' 20 | The user should handle this, as h3 should not know anything about 21 | request processing. 22 | ''' 23 | 24 | [[exception]] 25 | quote = ''' 26 | When the server cancels a request without performing any application 27 | processing, the request is considered "rejected". The server SHOULD 28 | abort its response stream with the error code H3_REQUEST_REJECTED. 29 | ''' 30 | reason = ''' 31 | The user should handle this, as h3 should not know anything about 32 | request processing. 33 | ''' 34 | 35 | [[exception]] 36 | quote = ''' 37 | Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests 38 | that were partially or fully processed. 39 | ''' 40 | reason = ''' 41 | The user should handle this, as h3 should not know anything about 42 | request processing. 43 | ''' 44 | 45 | [[exception]] 46 | quote = ''' 47 | When a server abandons a 48 | response after partial processing, it SHOULD abort its response 49 | stream with the error code H3_REQUEST_CANCELLED. 50 | ''' 51 | reason = ''' 52 | The user should initiate this, as h3 should not know anything about 53 | request processing. 54 | ''' 55 | 56 | [[exception]] 57 | quote = ''' 58 | Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel 59 | requests. 60 | ''' 61 | reason = ''' 62 | The user should initiate this, as h3 should not decide when to cancel 63 | requests. 64 | ''' 65 | 66 | [[exception]] 67 | quote = ''' 68 | Upon receipt of this error code, a server MAY abruptly 69 | terminate the response using the error code H3_REQUEST_REJECTED if no 70 | processing was performed. 71 | ''' 72 | reason = ''' 73 | The user should initiate this, as h3 should not know anything about 74 | request processing. 75 | ''' 76 | 77 | [[exception]] 78 | quote = ''' 79 | If a stream is cancelled after receiving a complete response, the 80 | client MAY ignore the cancellation and use the response. 81 | ''' 82 | reason = ''' 83 | It should be up to the user to decide. 84 | ''' 85 | 86 | [[exception]] 87 | quote = ''' 88 | Only idempotent actions such as GET, 89 | PUT, or DELETE can be safely retried; a client SHOULD NOT 90 | automatically retry a request with a non-idempotent method unless it 91 | has some means to know that the request semantics are idempotent 92 | independent of the method or some means to detect that the original 93 | request was never applied. 94 | ''' 95 | reason = ''' 96 | This should be left to the user to decide. 97 | ''' 98 | -------------------------------------------------------------------------------- /h3/src/shared_state.rs: -------------------------------------------------------------------------------- 1 | //! This module represents the shared state of the h3 connection 2 | 3 | use std::{ 4 | borrow::Cow, 5 | sync::{atomic::AtomicBool, OnceLock}, 6 | }; 7 | 8 | use futures_util::task::AtomicWaker; 9 | 10 | use crate::{config::Settings, error::internal_error::ErrorOrigin}; 11 | 12 | #[derive(Debug)] 13 | /// This struct represents the shared state of the h3 connection and the stream structs 14 | pub struct SharedState { 15 | /// The settings, sent by the peer 16 | settings: OnceLock, 17 | /// The connection error 18 | connection_error: OnceLock, 19 | /// The connection is closing 20 | closing: AtomicBool, 21 | /// Waker for the connection 22 | waker: AtomicWaker, 23 | } 24 | 25 | impl Default for SharedState { 26 | fn default() -> Self { 27 | Self { 28 | settings: OnceLock::new(), 29 | connection_error: OnceLock::new(), 30 | closing: AtomicBool::new(false), 31 | waker: AtomicWaker::new(), 32 | } 33 | } 34 | } 35 | 36 | impl ConnectionState for SharedState { 37 | fn shared_state(&self) -> &SharedState { 38 | self 39 | } 40 | } 41 | 42 | /// This trait can be implemented for all types which have a shared state 43 | pub trait ConnectionState { 44 | /// Get the shared state 45 | fn shared_state(&self) -> &SharedState; 46 | /// Get the connection error if the connection is in error state because of another task 47 | /// 48 | /// Return the error as an Err variant if it is set in order to allow using ? in the calling function 49 | fn get_conn_error(&self) -> Option { 50 | self.shared_state().connection_error.get().cloned() 51 | } 52 | 53 | /// tries to set the connection error 54 | fn set_conn_error(&self, error: ErrorOrigin) -> ErrorOrigin { 55 | let err = self 56 | .shared_state() 57 | .connection_error 58 | .get_or_init(move || error); 59 | err.clone() 60 | } 61 | 62 | /// set the connection error and wake the connection 63 | fn set_conn_error_and_wake>(&self, error: T) -> ErrorOrigin { 64 | let err = self.set_conn_error(error.into()); 65 | self.waker().wake(); 66 | err 67 | } 68 | 69 | /// Get the settings 70 | fn settings(&self) -> Cow<'_, Settings> { 71 | //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.4.2 72 | //# Each endpoint SHOULD use 73 | //# these initial values to send messages before the peer's SETTINGS 74 | //# frame has arrived, as packets carrying the settings can be lost or 75 | //# delayed. 76 | self.shared_state() 77 | .settings 78 | .get() 79 | .map(Cow::Borrowed) 80 | .unwrap_or_default() 81 | } 82 | /// Set the connection to closing 83 | fn set_closing(&self) { 84 | self.shared_state() 85 | .closing 86 | .store(true, std::sync::atomic::Ordering::Relaxed); 87 | } 88 | /// Check if the connection is closing 89 | fn is_closing(&self) -> bool { 90 | self.shared_state() 91 | .closing 92 | .load(std::sync::atomic::Ordering::Relaxed) 93 | } 94 | /// Set the settings 95 | fn set_settings(&self, settings: Settings) { 96 | let _ = self.shared_state().settings.set(settings); 97 | } 98 | 99 | /// Returns the waker for the connection 100 | fn waker(&self) -> &AtomicWaker { 101 | &self.shared_state().waker 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /h3/src/qpack/field.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | fmt::{Display, Formatter}, 4 | }; 5 | 6 | /** 7 | * https://tools.ietf.org/html/rfc7541 8 | * 4.1. Calculating Table Size 9 | */ 10 | pub const ESTIMATED_OVERHEAD_BYTES: usize = 32; 11 | 12 | #[derive(Debug, PartialEq, Clone, Hash, Eq)] 13 | pub struct HeaderField { 14 | pub name: Cow<'static, [u8]>, 15 | pub value: Cow<'static, [u8]>, 16 | } 17 | 18 | impl HeaderField { 19 | pub fn new(name: T, value: S) -> HeaderField 20 | where 21 | T: Into>, 22 | S: Into>, 23 | { 24 | HeaderField { 25 | name: Cow::Owned(name.into()), 26 | value: Cow::Owned(value.into()), 27 | } 28 | } 29 | 30 | pub fn mem_size(&self) -> usize { 31 | self.name.len() + self.value.len() + ESTIMATED_OVERHEAD_BYTES 32 | } 33 | 34 | pub fn with_value(&self, value: T) -> Self 35 | where 36 | T: Into>, 37 | { 38 | Self { 39 | name: self.name.clone(), 40 | value: Cow::Owned(value.into()), 41 | } 42 | } 43 | 44 | pub fn into_inner(self) -> (Cow<'static, [u8]>, Cow<'static, [u8]>) { 45 | (self.name, self.value) 46 | } 47 | } 48 | 49 | impl AsRef for HeaderField { 50 | fn as_ref(&self) -> &Self { 51 | self 52 | } 53 | } 54 | 55 | impl Display for HeaderField { 56 | fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { 57 | write!( 58 | f, 59 | "\"{}\": \"{}\"", 60 | String::from_utf8_lossy(&self.name), 61 | String::from_utf8_lossy(&self.value) 62 | )?; 63 | Ok(()) 64 | } 65 | } 66 | 67 | impl From for String { 68 | fn from(field: HeaderField) -> String { 69 | format!( 70 | "{}\t{}", 71 | String::from_utf8_lossy(&field.name), 72 | String::from_utf8_lossy(&field.value) 73 | ) 74 | } 75 | } 76 | 77 | impl From<(N, V)> for HeaderField 78 | where 79 | N: AsRef<[u8]>, 80 | V: AsRef<[u8]>, 81 | { 82 | fn from(header: (N, V)) -> Self { 83 | let (name, value) = header; 84 | Self { 85 | // FIXME: could avoid allocation if HeaderField had a lifetime 86 | name: Cow::Owned(Vec::from(name.as_ref())), 87 | value: Cow::Owned(Vec::from(value.as_ref())), 88 | } 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | 96 | /** 97 | * https://tools.ietf.org/html/rfc7541#section-4.1 98 | * "The size of an entry is the sum of its name's length in octets (as 99 | * defined in Section 5.2), its value's length in octets, and 32." 100 | * "The size of an entry is calculated using the length of its name and 101 | * value without any Huffman encoding applied." 102 | */ 103 | #[test] 104 | fn test_field_size_is_offset_by_32() { 105 | let field = HeaderField { 106 | name: Cow::Borrowed(b"Name"), 107 | value: Cow::Borrowed(b"Value"), 108 | }; 109 | assert_eq!(field.mem_size(), 4 + 5 + 32); 110 | } 111 | 112 | #[test] 113 | fn with_value() { 114 | let field = HeaderField { 115 | name: Cow::Borrowed(b"Name"), 116 | value: Cow::Borrowed(b"Value"), 117 | }; 118 | assert_eq!( 119 | field.with_value("New value"), 120 | HeaderField { 121 | name: Cow::Borrowed(b"Name"), 122 | value: Cow::Borrowed(b"New value"), 123 | } 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /h3-quinn/src/datagram.rs: -------------------------------------------------------------------------------- 1 | //! Support for the h3-datagram crate. 2 | //! 3 | //! This module implements the traits defined in h3-datagram for the quinn crate. 4 | 5 | use std::future::Future; 6 | use std::task::{ready, Poll}; 7 | 8 | use futures_util::{stream, StreamExt}; 9 | use h3_datagram::datagram::EncodedDatagram; 10 | use h3_datagram::quic_traits::{ 11 | DatagramConnectionExt, RecvDatagram, SendDatagram, SendDatagramErrorIncoming, 12 | }; 13 | 14 | use h3_datagram::ConnectionErrorIncoming; 15 | 16 | use bytes::{Buf, Bytes}; 17 | use quinn::{ReadDatagram, SendDatagramError}; 18 | 19 | use crate::{convert_connection_error, BoxStreamSync, Connection}; 20 | 21 | /// A Struct which allows to send datagrams over a QUIC connection. 22 | pub struct SendDatagramHandler { 23 | conn: quinn::Connection, 24 | } 25 | 26 | impl SendDatagram for SendDatagramHandler { 27 | fn send_datagram>>( 28 | &mut self, 29 | data: T, 30 | ) -> Result<(), SendDatagramErrorIncoming> { 31 | let mut buf: EncodedDatagram = data.into(); 32 | self.conn 33 | .send_datagram(buf.copy_to_bytes(buf.remaining())) 34 | .map_err(convert_send_datagram_error) 35 | } 36 | } 37 | 38 | /// A Struct which allows to receive datagrams over a QUIC connection. 39 | pub struct RecvDatagramHandler { 40 | datagrams: BoxStreamSync<'static, as Future>::Output>, 41 | } 42 | 43 | impl RecvDatagram for RecvDatagramHandler { 44 | type Buffer = Bytes; 45 | fn poll_incoming_datagram( 46 | &mut self, 47 | cx: &mut core::task::Context<'_>, 48 | ) -> std::task::Poll> { 49 | Poll::Ready( 50 | ready!(self.datagrams.poll_next_unpin(cx)) 51 | .expect("self. datagrams never returns None") 52 | .map_err(convert_connection_error), 53 | ) 54 | } 55 | } 56 | 57 | impl DatagramConnectionExt for Connection { 58 | type SendDatagramHandler = SendDatagramHandler; 59 | type RecvDatagramHandler = RecvDatagramHandler; 60 | 61 | fn send_datagram_handler(&self) -> Self::SendDatagramHandler { 62 | SendDatagramHandler { 63 | conn: self.conn.clone(), 64 | } 65 | } 66 | 67 | fn recv_datagram_handler(&self) -> Self::RecvDatagramHandler { 68 | RecvDatagramHandler { 69 | datagrams: Box::pin(stream::unfold(self.conn.clone(), |conn| async { 70 | Some((conn.read_datagram().await, conn)) 71 | })), 72 | } 73 | } 74 | } 75 | 76 | fn convert_send_datagram_error(error: SendDatagramError) -> SendDatagramErrorIncoming { 77 | match error { 78 | SendDatagramError::UnsupportedByPeer | SendDatagramError::Disabled => { 79 | SendDatagramErrorIncoming::NotAvailable 80 | } 81 | SendDatagramError::TooLarge => SendDatagramErrorIncoming::TooLarge, 82 | SendDatagramError::ConnectionLost(e) => SendDatagramErrorIncoming::ConnectionError( 83 | convert_h3_error_to_datagram_error(convert_connection_error(e)), 84 | ), 85 | } 86 | } 87 | 88 | fn convert_h3_error_to_datagram_error( 89 | error: h3::quic::ConnectionErrorIncoming, 90 | ) -> h3_datagram::ConnectionErrorIncoming { 91 | match error { 92 | ConnectionErrorIncoming::ApplicationClose { error_code } => { 93 | h3_datagram::ConnectionErrorIncoming::ApplicationClose { error_code } 94 | } 95 | ConnectionErrorIncoming::Timeout => h3_datagram::ConnectionErrorIncoming::Timeout, 96 | ConnectionErrorIncoming::InternalError(err) => { 97 | h3_datagram::ConnectionErrorIncoming::InternalError(err) 98 | } 99 | ConnectionErrorIncoming::Undefined(error) => { 100 | h3_datagram::ConnectionErrorIncoming::Undefined(error) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # h3 2 | 3 | An async HTTP/3 implementation. 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 6 | [![CI](https://github.com/hyperium/h3/workflows/CI/badge.svg)](https://github.com/hyperium/h3/actions?query=workflow%3ACI) 7 | [![Discord chat](https://img.shields.io/discord/500028886025895936.svg?logo=discord)](https://discord.gg/q5mVhMD) 8 | 9 | This crate provides an [HTTP/3][spec] implementation that is generic over a provided QUIC transport. This allows the project to focus on just HTTP/3, while letting users pick their QUIC implementation based on their specific needs. It includes client and server APIs. Check the original [design][] for more details. 10 | 11 | [spec]: https://www.rfc-editor.org/rfc/rfc9114 12 | [design]: docs/PROPOSAL.md 13 | 14 | ## Status 15 | 16 | The `h3` crate is still very experimental. While the client and servers do work, there may still be bugs. And the API could change as we continue to explore. That said, we eagerly welcome contributions, trying it out in test environments, and using at your own risk. 17 | 18 | The eventual goal is to use `h3` as an internal dependency of [hyper][]. 19 | 20 | [hyper]: https://hyper.rs 21 | 22 | ### Duvet 23 | This crate uses the [duvet crate][] to check compliance of the [spec][]. 24 | The generated [report][] displays the current status of the requirements of the spec. 25 | 26 | Get more information about this tool in the [contributing][] document. 27 | 28 | [duvet crate]: https://crates.io/crates/duvet 29 | [spec]: https://www.rfc-editor.org/rfc/rfc9114 30 | [report]: https://hyper.rs/h3/ci/compliance/report.html#/ 31 | [contributing]: CONTRIBUTING.md 32 | 33 | ## Features 34 | 35 | * HTTP/3 client and server implementation 36 | * Async only API 37 | * QUIC transport abstraction via traits in the [`quic`](./h3/src/quic.rs) module 38 | * Runtime independent (h3 does not spawn tasks and works with any runtime) 39 | * Supported QUIC implementations to date are 40 | [Quinn](https://github.com/quinn-rs/quinn) ([h3-quinn](./h3-quinn/)) 41 | , [s2n-quic](https://github.com/aws/s2n-quic) 42 | ([s2n-quic-h3](https://github.com/aws/s2n-quic/tree/main/quic/s2n-quic-h3)) 43 | and [MsQuic](https://github.com/microsoft/msquic) 44 | ([h3-msquic-async](https://github.com/masa-koz/msquic-async-rs/tree/main/h3-msquic-async)) 45 | 46 | ## Overview 47 | 48 | * **h3** HTTP/3 implementation 49 | * **h3-quinn** QUIC transport implementation based on [Quinn](https://github.com/quinn-rs/quinn/) 50 | 51 | ## Getting Started 52 | 53 | The [examples](./examples) directory can help get started in two ways: 54 | 55 | - There are ready-to-use `client` and `server` binaries to interact with _other_ HTTP/3 peers. Check the README in that directory. 56 | - The source code of those examples can help teach how to use `h3` as either a client or a server. 57 | 58 | ### Server 59 | 60 | You can find a full server example in [`examples/server.rs`](./examples/server.rs) 61 | 62 | ### Client 63 | 64 | You can find a full client example in [`examples/client.rs`](./examples/client.rs) 65 | 66 | ## QUIC Generic 67 | 68 | As mentioned, the goal of this library is to be generic over a QUIC implementation. To that effect, integrations with QUIC libraries exist: 69 | 70 | - [`h3-quinn`](./h3-quinn/): in this same repository. 71 | - [`s2n-quic-h3`](https://github.com/aws/s2n-quic/tree/main/quic/s2n-quic-h3) 72 | - [`h3-msquic-async`](https://github.com/masa-koz/msquic-async-rs/tree/main/h3-msquic-async) 73 | 74 | ## Interoperability 75 | 76 | This crate as well as the quic implementations are tested ([quinn](https://github.com/quinn-rs/quinn-interop), [s2n-quic](https://github.com/aws/s2n-quic/tree/main/scripts/interop), [MsQuic](https://github.com/microsoft/msquic/tree/main/src/tools/interop)) for interoperability and performance in the [quic-interop-runner](https://github.com/marten-seemann/quic-interop-runner). 77 | You can see the results at (https://interop.seemann.io/). 78 | 79 | ## License 80 | 81 | h3 is provided under the MIT license. See [LICENSE](LICENSE). 82 | -------------------------------------------------------------------------------- /h3/src/error/internal_error.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the internal error type, which is used to represent errors, which have not yet affected the connection state 2 | 3 | use std::error::Error; 4 | 5 | use crate::{frame::FrameProtocolError, quic::ConnectionErrorIncoming}; 6 | 7 | use super::codes::Code; 8 | use std::fmt::Display; 9 | 10 | /// This error type represents an internal error type, which is used 11 | /// to represent errors, which have not yet affected the connection state 12 | /// 13 | /// This error type is generated from the error types of h3s submodules or by the modules itself. 14 | /// 15 | /// This error type is used in functions which handle a http3 connection state 16 | #[derive(Debug, Clone, Hash)] 17 | pub struct InternalConnectionError { 18 | /// The error code 19 | pub(crate) code: Code, 20 | /// The error message 21 | pub(crate) message: String, 22 | } 23 | 24 | impl InternalConnectionError { 25 | /// Create a new internal connection error 26 | pub fn new(code: Code, message: String) -> Self { 27 | Self { code, message } 28 | } 29 | /// Creates a new internal connection error from a frame error 30 | pub fn got_frame_error(value: FrameProtocolError) -> Self { 31 | match value { 32 | FrameProtocolError::InvalidStreamId(id) => InternalConnectionError { 33 | code: Code::H3_ID_ERROR, 34 | message: format!("invalid stream id: {}", id), 35 | }, 36 | FrameProtocolError::InvalidPushId(id) => InternalConnectionError { 37 | code: Code::H3_ID_ERROR, 38 | message: format!("invalid push id: {}", id), 39 | }, 40 | FrameProtocolError::Settings(error) => InternalConnectionError { 41 | // TODO: Check spec which error code to return when a bad settings frame arrives on a stream which is not allowed to have settings 42 | // At the moment, because the Frame is parsed before the stream type is checked, the H3_SETTINGS_ERROR is returned. 43 | // Same for the InvalidStreamId and InvalidPushId 44 | code: Code::H3_SETTINGS_ERROR, 45 | message: error.to_string(), 46 | }, 47 | //= https://www.rfc-editor.org/rfc/rfc9114#section-7.2.8 48 | //# These frame 49 | //# types MUST NOT be sent, and their receipt MUST be treated as a 50 | //# connection error of type H3_FRAME_UNEXPECTED. 51 | FrameProtocolError::ForbiddenFrame(number) => InternalConnectionError { 52 | code: Code::H3_FRAME_UNEXPECTED, 53 | message: format!("received a forbidden frame with number {}", number), 54 | }, 55 | //= https://www.rfc-editor.org/rfc/rfc9114#section-7.1 56 | //# A frame payload that contains additional bytes 57 | //# after the identified fields or a frame payload that terminates before 58 | //# the end of the identified fields MUST be treated as a connection 59 | //# error of type H3_FRAME_ERROR. 60 | FrameProtocolError::InvalidFrameValue | FrameProtocolError::Malformed => InternalConnectionError { 61 | code: Code::H3_FRAME_ERROR, 62 | message: "frame payload that contains additional bytes after the identified fields or a frame payload that terminates before the end of the identified fields".to_string(), 63 | }, 64 | } 65 | } 66 | } 67 | 68 | /// Error type which combines different internal errors 69 | #[derive(Debug, Clone)] 70 | pub enum ErrorOrigin { 71 | /// Internal Error 72 | Internal(InternalConnectionError), 73 | /// Quick layer error 74 | Quic(ConnectionErrorIncoming), 75 | } 76 | 77 | impl Display for ErrorOrigin { 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 79 | match self { 80 | ErrorOrigin::Internal(error) => write!(f, "Internal Error: {}", error.message), 81 | ErrorOrigin::Quic(error) => write!(f, "Quic Error: {:?}", error), 82 | } 83 | } 84 | } 85 | 86 | impl Error for ErrorOrigin {} 87 | 88 | impl From for ErrorOrigin { 89 | fn from(error: InternalConnectionError) -> Self { 90 | ErrorOrigin::Internal(error) 91 | } 92 | } 93 | 94 | impl From for ErrorOrigin { 95 | fn from(error: ConnectionErrorIncoming) -> Self { 96 | ErrorOrigin::Quic(error) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /h3-datagram/src/datagram.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use h3::{ 3 | error::{internal_error::InternalConnectionError, Code}, 4 | proto::varint::VarInt, 5 | quic::StreamId, 6 | }; 7 | 8 | /// HTTP datagram frames 9 | /// See: 10 | #[derive(Debug, Clone)] 11 | pub struct Datagram { 12 | /// Stream id divided by 4 13 | stream_id: StreamId, 14 | /// The data contained in the datagram 15 | payload: B, 16 | } 17 | 18 | impl Datagram 19 | where 20 | B: Buf, 21 | { 22 | /// Creates a new datagram frame 23 | // TODO: remove for MSRV >= 1.87 https://github.com/rust-lang/rust/issues/128101 24 | #[allow(unknown_lints, clippy::manual_is_multiple_of)] 25 | pub fn new(stream_id: StreamId, payload: B) -> Self { 26 | assert!( 27 | stream_id.into_inner() % 4 == 0, 28 | "StreamId is not divisible by 4" 29 | ); 30 | // StreamId will be divided by 4 when encoding the Datagram header 31 | Self { stream_id, payload } 32 | } 33 | 34 | /// Decodes a datagram frame from the QUIC datagram 35 | pub fn decode(mut buf: B) -> Result { 36 | let q_stream_id = VarInt::decode(&mut buf).map_err(|_| { 37 | InternalConnectionError::new(Code::H3_DATAGRAM_ERROR, "invalid stream id".to_string()) 38 | })?; 39 | 40 | //= https://www.rfc-editor.org/rfc/rfc9297#section-2.1 41 | // Quarter Stream ID: A variable-length integer that contains the value of the client-initiated bidirectional 42 | // stream that this datagram is associated with divided by four (the division by four stems 43 | // from the fact that HTTP requests are sent on client-initiated bidirectional streams, 44 | // which have stream IDs that are divisible by four). The largest legal QUIC stream ID 45 | // value is 262-1, so the largest legal value of the Quarter Stream ID field is 260-1. 46 | // Receipt of an HTTP/3 Datagram that includes a larger value MUST be treated as an HTTP/3 47 | // connection error of type H3_DATAGRAM_ERROR (0x33). 48 | let stream_id = StreamId::try_from(u64::from(q_stream_id) * 4).map_err(|_| { 49 | InternalConnectionError::new(Code::H3_DATAGRAM_ERROR, "invalid stream id".to_string()) 50 | })?; 51 | 52 | let payload = buf; 53 | 54 | Ok(Self { stream_id, payload }) 55 | } 56 | 57 | #[inline] 58 | /// Returns the associated stream id of the datagram 59 | pub fn stream_id(&self) -> StreamId { 60 | self.stream_id 61 | } 62 | 63 | #[inline] 64 | /// Returns the datagram payload 65 | pub fn payload(&self) -> &B { 66 | &self.payload 67 | } 68 | 69 | /// Encode the datagram to wire format 70 | pub fn encode(self) -> EncodedDatagram { 71 | let mut buffer = [0; VarInt::MAX_SIZE]; 72 | let varint = VarInt::from(self.stream_id) / 4; 73 | varint.encode(&mut buffer.as_mut_slice()); 74 | EncodedDatagram { 75 | stream_id: [0; VarInt::MAX_SIZE], 76 | len: varint.size(), 77 | pos: 0, 78 | payload: self.payload, 79 | } 80 | } 81 | 82 | /// Returns the datagram payload 83 | pub fn into_payload(self) -> B { 84 | self.payload 85 | } 86 | } 87 | 88 | #[derive(Debug)] 89 | pub struct EncodedDatagram { 90 | /// Encoded datagram stream ID as Varint 91 | stream_id: [u8; VarInt::MAX_SIZE], 92 | /// Length of the varint 93 | len: usize, 94 | /// Position of the stream_id buffer 95 | pos: usize, 96 | /// Datagram Payload 97 | payload: B, 98 | } 99 | 100 | /// Implementation of [`Buf`] for [`Datagram`] 101 | impl Buf for EncodedDatagram 102 | where 103 | B: Buf, 104 | { 105 | fn remaining(&self) -> usize { 106 | self.len - self.pos + self.payload.remaining() 107 | } 108 | 109 | fn chunk(&self) -> &[u8] { 110 | if self.len - self.pos > 0 { 111 | &self.stream_id[self.pos..self.len] 112 | } else { 113 | self.payload.chunk() 114 | } 115 | } 116 | 117 | fn advance(&mut self, mut cnt: usize) { 118 | let remaining_header = self.len - self.pos; 119 | if remaining_header > 0 { 120 | let advanced = usize::min(cnt, remaining_header); 121 | self.pos += advanced; 122 | cnt -= advanced; 123 | } 124 | self.payload.advance(cnt); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /h3-datagram/src/datagram_handler.rs: -------------------------------------------------------------------------------- 1 | //! Traits which define the user API for datagrams. 2 | //! These traits are implemented for the client and server types in the `h3` crate. 3 | 4 | use std::{error::Error, fmt::Display, future::poll_fn, marker::PhantomData, sync::Arc}; 5 | 6 | use crate::{ 7 | datagram::Datagram, 8 | quic_traits::{DatagramConnectionExt, RecvDatagram, SendDatagram, SendDatagramErrorIncoming}, 9 | }; 10 | use bytes::Buf; 11 | use h3::{ 12 | error::{connection_error_creators::CloseStream, ConnectionError, StreamError}, 13 | quic::{self, StreamId}, 14 | ConnectionState, SharedState, 15 | }; 16 | 17 | /// Gives the ability to send datagrams. 18 | #[derive(Debug)] 19 | pub struct DatagramSender, B: Buf> { 20 | pub(crate) handler: H, 21 | pub(crate) _marker: PhantomData, 22 | pub(crate) shared_state: Arc, 23 | pub(crate) stream_id: StreamId, 24 | } 25 | 26 | impl ConnectionState for DatagramSender 27 | where 28 | H: SendDatagram, 29 | B: Buf, 30 | { 31 | fn shared_state(&self) -> &SharedState { 32 | self.shared_state.as_ref() 33 | } 34 | } 35 | 36 | impl DatagramSender 37 | where 38 | H: SendDatagram, 39 | B: Buf, 40 | { 41 | /// Sends a datagram 42 | pub fn send_datagram(&mut self, data: B) -> Result<(), SendDatagramError> { 43 | let encoded_datagram = Datagram::new(self.stream_id, data); 44 | match self.handler.send_datagram(encoded_datagram.encode()) { 45 | Ok(()) => Ok(()), 46 | Err(e) => Err(self.handle_send_datagram_error(e)), 47 | } 48 | } 49 | 50 | fn handle_send_datagram_error( 51 | &mut self, 52 | error: SendDatagramErrorIncoming, 53 | ) -> SendDatagramError { 54 | match error { 55 | SendDatagramErrorIncoming::NotAvailable => SendDatagramError::NotAvailable, 56 | SendDatagramErrorIncoming::TooLarge => SendDatagramError::TooLarge, 57 | SendDatagramErrorIncoming::ConnectionError(error) => { 58 | self.set_conn_error_and_wake(error.clone()); 59 | SendDatagramError::ConnectionError(ConnectionError::Remote(error)) 60 | } 61 | } 62 | } 63 | } 64 | 65 | #[derive(Debug)] 66 | pub struct DatagramReader { 67 | pub(crate) handler: H, 68 | pub(crate) shared_state: Arc, 69 | } 70 | 71 | impl ConnectionState for DatagramReader 72 | where 73 | H: RecvDatagram, 74 | { 75 | fn shared_state(&self) -> &SharedState { 76 | self.shared_state.as_ref() 77 | } 78 | } 79 | 80 | impl CloseStream for DatagramReader where H: RecvDatagram {} 81 | 82 | impl DatagramReader 83 | where 84 | H: RecvDatagram, 85 | { 86 | /// Reads an incoming datagram 87 | pub async fn read_datagram(&mut self) -> Result, StreamError> { 88 | match poll_fn(|cx| self.handler.poll_incoming_datagram(cx)).await { 89 | Ok(datagram) => Datagram::decode(datagram) 90 | .map_err(|err| self.handle_connection_error_on_stream(err)), 91 | Err(err) => Err(self.handle_quic_stream_error( 92 | quic::StreamErrorIncoming::ConnectionErrorIncoming { 93 | connection_error: err, 94 | }, 95 | )), 96 | } 97 | } 98 | } 99 | 100 | pub trait HandleDatagramsExt: ConnectionState 101 | where 102 | B: Buf, 103 | C: quic::Connection + DatagramConnectionExt, 104 | { 105 | /// Sends a datagram 106 | fn get_datagram_sender(&self, stream_id: StreamId) 107 | -> DatagramSender; 108 | /// Reads an incoming datagram 109 | fn get_datagram_reader(&self) -> DatagramReader; 110 | } 111 | 112 | /// Types of errors when sending a datagram. 113 | #[derive(Debug)] 114 | #[non_exhaustive] 115 | pub enum SendDatagramError { 116 | /// The peer is not accepting datagrams on the quic layer 117 | /// 118 | /// This can be because the peer does not support it or disabled it or any other reason. 119 | #[non_exhaustive] 120 | NotAvailable, 121 | /// The datagram is too large to send 122 | #[non_exhaustive] 123 | TooLarge, 124 | /// Connection error 125 | #[non_exhaustive] 126 | ConnectionError(ConnectionError), 127 | } 128 | 129 | impl Display for SendDatagramError { 130 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 131 | match self { 132 | SendDatagramError::NotAvailable => write!(f, "Datagrams are not available"), 133 | SendDatagramError::TooLarge => write!(f, "Datagram is too large"), 134 | SendDatagramError::ConnectionError(e) => write!(f, "Connection error: {}", e), 135 | } 136 | } 137 | } 138 | 139 | impl Error for SendDatagramError {} 140 | -------------------------------------------------------------------------------- /h3/src/client/builder.rs: -------------------------------------------------------------------------------- 1 | //! HTTP/3 client builder 2 | 3 | use std::{ 4 | marker::PhantomData, 5 | sync::{atomic::AtomicUsize, Arc}, 6 | }; 7 | 8 | use bytes::{Buf, Bytes}; 9 | 10 | use crate::{ 11 | config::Config, 12 | connection::ConnectionInner, 13 | error::ConnectionError, 14 | quic::{self}, 15 | shared_state::SharedState, 16 | }; 17 | 18 | use super::connection::{Connection, SendRequest}; 19 | 20 | /// Start building a new HTTP/3 client 21 | pub fn builder() -> Builder { 22 | Builder::new() 23 | } 24 | 25 | /// Create a new HTTP/3 client with default settings 26 | pub async fn new( 27 | conn: C, 28 | ) -> Result<(Connection, SendRequest), ConnectionError> 29 | where 30 | C: quic::Connection, 31 | O: quic::OpenStreams, 32 | { 33 | //= https://www.rfc-editor.org/rfc/rfc9114#section-3.3 34 | //= type=implication 35 | //# Clients SHOULD NOT open more than one HTTP/3 connection to a given IP 36 | //# address and UDP port, where the IP address and port might be derived 37 | //# from a URI, a selected alternative service ([ALTSVC]), a configured 38 | //# proxy, or name resolution of any of these. 39 | Builder::new().build(conn).await 40 | } 41 | 42 | /// HTTP/3 client builder 43 | /// 44 | /// Set the configuration for a new client. 45 | /// 46 | /// # Examples 47 | /// ```rust 48 | /// # use h3::quic; 49 | /// # async fn doc(quic: C) 50 | /// # where 51 | /// # C: quic::Connection, 52 | /// # O: quic::OpenStreams, 53 | /// # B: bytes::Buf, 54 | /// # { 55 | /// let h3_conn = h3::client::builder() 56 | /// .max_field_section_size(8192) 57 | /// .build(quic) 58 | /// .await 59 | /// .expect("Failed to build connection"); 60 | /// # } 61 | /// ``` 62 | pub struct Builder { 63 | config: Config, 64 | } 65 | 66 | impl Builder { 67 | pub(super) fn new() -> Self { 68 | Builder { 69 | config: Default::default(), 70 | } 71 | } 72 | 73 | // Not public API, just used in unit tests 74 | #[doc(hidden)] 75 | #[cfg(test)] 76 | pub fn send_settings(&mut self, value: bool) -> &mut Self { 77 | self.config.send_settings = value; 78 | self 79 | } 80 | 81 | /// Set the maximum header size this client is willing to accept 82 | /// 83 | /// See [header size constraints] section of the specification for details. 84 | /// 85 | /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints 86 | pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { 87 | self.config.settings.max_field_section_size = value; 88 | self 89 | } 90 | 91 | /// Just like in HTTP/2, HTTP/3 also uses the concept of "grease" 92 | /// to prevent potential interoperability issues in the future. 93 | /// In HTTP/3, the concept of grease is used to ensure that the protocol can evolve 94 | /// and accommodate future changes without breaking existing implementations. 95 | pub fn send_grease(&mut self, enabled: bool) -> &mut Self { 96 | self.config.send_grease = enabled; 97 | self 98 | } 99 | 100 | /// Indicates that the client supports HTTP/3 datagrams 101 | /// 102 | /// See: 103 | pub fn enable_datagram(&mut self, enabled: bool) -> &mut Self { 104 | self.config.settings.enable_datagram = enabled; 105 | self 106 | } 107 | 108 | /// Enables the extended CONNECT protocol required for various HTTP/3 extensions. 109 | pub fn enable_extended_connect(&mut self, value: bool) -> &mut Self { 110 | self.config.settings.enable_extended_connect = value; 111 | self 112 | } 113 | 114 | /// Create a new HTTP/3 client from a `quic` connection 115 | pub async fn build( 116 | &mut self, 117 | quic: C, 118 | ) -> Result<(Connection, SendRequest), ConnectionError> 119 | where 120 | C: quic::Connection, 121 | O: quic::OpenStreams, 122 | B: Buf, 123 | { 124 | let open = quic.opener(); 125 | let shared = SharedState::default(); 126 | 127 | let conn_state = Arc::new(shared); 128 | 129 | let inner = ConnectionInner::new(quic, conn_state.clone(), self.config).await?; 130 | let send_request = SendRequest { 131 | open, 132 | conn_state, 133 | max_field_section_size: self.config.settings.max_field_section_size, 134 | sender_count: Arc::new(AtomicUsize::new(1)), 135 | send_grease_frame: self.config.send_grease, 136 | _buf: PhantomData, 137 | }; 138 | 139 | Ok(( 140 | Connection { 141 | inner, 142 | sent_closing: None, 143 | recv_closing: None, 144 | }, 145 | send_request, 146 | )) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /h3/src/buf.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::io::IoSlice; 3 | 4 | use bytes::{Buf, Bytes}; 5 | 6 | #[derive(Debug)] 7 | pub(crate) struct BufList { 8 | bufs: VecDeque, 9 | } 10 | 11 | impl BufList { 12 | pub(crate) fn new() -> BufList { 13 | BufList { 14 | bufs: VecDeque::new(), 15 | } 16 | } 17 | 18 | #[inline] 19 | #[allow(dead_code)] 20 | pub(crate) fn push(&mut self, buf: T) { 21 | debug_assert!(buf.has_remaining()); 22 | self.bufs.push_back(buf); 23 | } 24 | 25 | pub fn cursor(&self) -> Cursor<'_, T> { 26 | Cursor { 27 | buf: self, 28 | pos_total: 0, 29 | index: 0, 30 | pos_front: 0, 31 | } 32 | } 33 | } 34 | 35 | impl BufList { 36 | pub fn take_first_chunk(&mut self) -> Option { 37 | self.bufs.pop_front() 38 | } 39 | 40 | pub fn take_chunk(&mut self, max_len: usize) -> Option { 41 | let chunk = self 42 | .bufs 43 | .front_mut() 44 | .map(|chunk| chunk.split_to(usize::min(max_len, chunk.remaining()))); 45 | 46 | if let Some(front) = self.bufs.front() { 47 | if front.remaining() == 0 { 48 | let _ = self.bufs.pop_front(); 49 | } 50 | } 51 | chunk 52 | } 53 | 54 | pub fn push_bytes(&mut self, buf: &mut T) 55 | where 56 | T: Buf, 57 | { 58 | debug_assert!(buf.has_remaining()); 59 | self.bufs.push_back(buf.copy_to_bytes(buf.remaining())) 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | impl From for BufList { 65 | fn from(b: T) -> Self { 66 | let mut buf = Self::new(); 67 | buf.push(b); 68 | buf 69 | } 70 | } 71 | 72 | impl Buf for BufList { 73 | #[inline] 74 | fn remaining(&self) -> usize { 75 | self.bufs.iter().map(|buf| buf.remaining()).sum() 76 | } 77 | 78 | #[inline] 79 | fn chunk(&self) -> &[u8] { 80 | self.bufs.front().map(Buf::chunk).unwrap_or_default() 81 | } 82 | 83 | #[inline] 84 | fn advance(&mut self, mut cnt: usize) { 85 | while cnt > 0 { 86 | { 87 | let front = &mut self.bufs[0]; 88 | let rem = front.remaining(); 89 | if rem > cnt { 90 | front.advance(cnt); 91 | return; 92 | } else { 93 | front.advance(rem); 94 | cnt -= rem; 95 | } 96 | } 97 | self.bufs.pop_front(); 98 | } 99 | } 100 | 101 | #[inline] 102 | fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize { 103 | if dst.is_empty() { 104 | return 0; 105 | } 106 | let mut vecs = 0; 107 | for buf in &self.bufs { 108 | vecs += buf.chunks_vectored(&mut dst[vecs..]); 109 | if vecs == dst.len() { 110 | break; 111 | } 112 | } 113 | vecs 114 | } 115 | } 116 | 117 | pub struct Cursor<'a, B> { 118 | buf: &'a BufList, 119 | pos_total: usize, // position amongst all bytes 120 | pos_front: usize, // position in the current front buffer 121 | index: usize, // current front buffer index 122 | } 123 | 124 | impl<'a, B: Buf> Cursor<'a, B> { 125 | pub fn position(&self) -> usize { 126 | self.pos_total 127 | } 128 | } 129 | 130 | impl<'a, B: Buf> Buf for Cursor<'a, B> { 131 | #[inline] 132 | fn remaining(&self) -> usize { 133 | self.buf.remaining() - self.pos_total 134 | } 135 | 136 | #[inline] 137 | fn chunk(&self) -> &[u8] { 138 | &self.buf.bufs[self.index].chunk()[self.pos_front..] 139 | } 140 | 141 | #[inline] 142 | fn advance(&mut self, mut cnt: usize) { 143 | assert!(cnt <= self.buf.remaining() - self.pos_total); 144 | while cnt > 0 { 145 | { 146 | let front = &self.buf.bufs[self.index]; 147 | let rem = front.remaining() - self.pos_front; 148 | if rem > cnt { 149 | self.pos_total += cnt; 150 | self.pos_front += cnt; 151 | return; 152 | } else { 153 | self.pos_total += rem; 154 | self.pos_front = 0; 155 | cnt -= rem; 156 | } 157 | } 158 | self.index += 1; 159 | } 160 | } 161 | 162 | #[inline] 163 | fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize { 164 | self.buf.chunks_vectored(dst) 165 | } 166 | } 167 | 168 | #[cfg(test)] 169 | mod tests { 170 | use super::*; 171 | use bytes::Bytes; 172 | 173 | #[test] 174 | fn cursor_advance() { 175 | let buf = BufList::from(Bytes::from_static(&[1u8, 2, 3, 4])); 176 | let mut cur = buf.cursor(); 177 | cur.advance(2); 178 | assert_eq!(cur.remaining(), 2); 179 | cur.advance(2); 180 | assert_eq!(cur.remaining(), 0); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /h3/src/qpack/prefix_string/mod.rs: -------------------------------------------------------------------------------- 1 | mod bitwin; 2 | mod decode; 3 | mod encode; 4 | 5 | use std::convert::TryInto; 6 | use std::fmt; 7 | use std::num::TryFromIntError; 8 | 9 | use bytes::{Buf, BufMut}; 10 | 11 | pub use self::bitwin::BitWindow; 12 | 13 | pub use self::{ 14 | decode::{Error as HuffmanDecodingError, HpackStringDecode}, 15 | encode::{Error as HuffmanEncodingError, HpackStringEncode}, 16 | }; 17 | 18 | use crate::proto::coding::BufMutExt; 19 | use crate::qpack::prefix_int::{self, Error as IntegerError}; 20 | 21 | #[derive(Debug, PartialEq)] 22 | pub enum Error { 23 | UnexpectedEnd, 24 | Integer(IntegerError), 25 | HuffmanDecoding(HuffmanDecodingError), 26 | HuffmanEncoding(HuffmanEncodingError), 27 | BufSize(TryFromIntError), 28 | } 29 | 30 | impl std::fmt::Display for Error { 31 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 32 | match self { 33 | Error::UnexpectedEnd => write!(f, "unexpected end"), 34 | Error::Integer(e) => write!(f, "could not parse integer: {}", e), 35 | Error::HuffmanDecoding(e) => write!(f, "Huffman decode failed: {:?}", e), 36 | Error::HuffmanEncoding(e) => write!(f, "Huffman encode failed: {:?}", e), 37 | Error::BufSize(_) => write!(f, "number in buffer wrong size"), 38 | } 39 | } 40 | } 41 | 42 | pub fn decode(size: u8, buf: &mut B) -> Result, Error> { 43 | let (flags, len) = prefix_int::decode(size - 1, buf)?; 44 | let len: usize = len.try_into()?; 45 | if buf.remaining() < len { 46 | return Err(Error::UnexpectedEnd); 47 | } 48 | 49 | let payload = buf.copy_to_bytes(len); 50 | let value = if flags & 1 == 0 { 51 | payload.into_iter().collect() 52 | } else { 53 | let mut decoded = Vec::new(); 54 | for byte in payload.into_iter().collect::>().hpack_decode() { 55 | decoded.push(byte?); 56 | } 57 | decoded 58 | }; 59 | Ok(value) 60 | } 61 | 62 | pub fn encode(size: u8, flags: u8, value: &[u8], buf: &mut B) -> Result<(), Error> { 63 | let encoded = Vec::from(value).hpack_encode()?; 64 | prefix_int::encode(size - 1, flags << 1 | 1, encoded.len().try_into()?, buf); 65 | for byte in encoded { 66 | buf.write(byte); 67 | } 68 | Ok(()) 69 | } 70 | 71 | impl From for Error { 72 | fn from(error: HuffmanEncodingError) -> Self { 73 | Error::HuffmanEncoding(error) 74 | } 75 | } 76 | 77 | impl From for Error { 78 | fn from(error: IntegerError) -> Self { 79 | match error { 80 | IntegerError::UnexpectedEnd => Error::UnexpectedEnd, 81 | e => Error::Integer(e), 82 | } 83 | } 84 | } 85 | 86 | impl From for Error { 87 | fn from(error: HuffmanDecodingError) -> Self { 88 | Error::HuffmanDecoding(error) 89 | } 90 | } 91 | 92 | impl From for Error { 93 | fn from(error: TryFromIntError) -> Self { 94 | Error::BufSize(error) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use assert_matches::assert_matches; 102 | use std::io::Cursor; 103 | 104 | #[test] 105 | fn codec_6() { 106 | let mut buf = Vec::new(); 107 | encode(6, 0b01, b"name without ref", &mut buf).unwrap(); 108 | let mut read = Cursor::new(&buf); 109 | assert_eq!( 110 | &buf, 111 | &[ 112 | 0b0110_1100, 113 | 168, 114 | 116, 115 | 149, 116 | 79, 117 | 6, 118 | 76, 119 | 231, 120 | 181, 121 | 42, 122 | 88, 123 | 89, 124 | 127 125 | ] 126 | ); 127 | assert_eq!(decode(6, &mut read).unwrap(), b"name without ref"); 128 | } 129 | 130 | #[test] 131 | fn codec_8() { 132 | let mut buf = Vec::new(); 133 | encode(8, 0b01, b"name with ref", &mut buf).unwrap(); 134 | let mut read = Cursor::new(&buf); 135 | assert_eq!( 136 | &buf, 137 | &[0b1000_1010, 168, 116, 149, 79, 6, 76, 234, 88, 89, 127] 138 | ); 139 | assert_eq!(decode(8, &mut read).unwrap(), b"name with ref"); 140 | } 141 | 142 | #[test] 143 | fn codec_8_empty() { 144 | let mut buf = Vec::new(); 145 | encode(8, 0b01, b"", &mut buf).unwrap(); 146 | let mut read = Cursor::new(&buf); 147 | assert_eq!(&buf, &[0b1000_0000]); 148 | assert_eq!(decode(8, &mut read).unwrap(), b""); 149 | } 150 | 151 | #[test] 152 | fn decode_non_huffman() { 153 | let buf = vec![0b0100_0011, b'b', b'a', b'r']; 154 | let mut read = Cursor::new(&buf); 155 | assert_eq!(decode(6, &mut read).unwrap(), b"bar"); 156 | } 157 | 158 | #[test] 159 | fn decode_too_short() { 160 | let buf = vec![0b0100_0011, b'b', b'a']; 161 | let mut read = Cursor::new(&buf); 162 | assert_matches!(decode(6, &mut read), Err(Error::UnexpectedEnd)); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /h3/src/error/codes.rs: -------------------------------------------------------------------------------- 1 | //! HTTP/3 error codes. 2 | 3 | use std::fmt::{self}; 4 | 5 | /// An HTTP/3 "application error code". 6 | #[derive(PartialEq, Eq, Hash, Clone, Copy)] 7 | pub struct Code { 8 | code: u64, 9 | } 10 | 11 | impl Code { 12 | /// Numerical error code 13 | /// 14 | /// See 15 | /// and 16 | pub const fn value(&self) -> u64 { 17 | self.code 18 | } 19 | } 20 | 21 | impl PartialEq for Code { 22 | fn eq(&self, other: &u64) -> bool { 23 | *other == self.code 24 | } 25 | } 26 | 27 | // ===== impl Code ===== 28 | 29 | macro_rules! codes { 30 | ( 31 | $( 32 | $(#[$docs:meta])* 33 | ($num:expr, $name:ident); 34 | )+ 35 | ) => { 36 | impl Code { 37 | $( 38 | $(#[$docs])* 39 | pub const $name: Code = Code{code: $num}; 40 | )+ 41 | } 42 | 43 | impl fmt::Debug for Code { 44 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 45 | match self.code { 46 | $( 47 | $num => f.write_str(stringify!($name)), 48 | )+ 49 | other => write!(f, "{:#x}", other), 50 | } 51 | } 52 | } 53 | 54 | impl fmt::Display for Code{ 55 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 56 | match self.code { 57 | $( 58 | $num => f.write_str(stringify!($name)), 59 | )+ 60 | other => write!(f, "{:#x}", other), 61 | } 62 | } 63 | } 64 | } 65 | 66 | 67 | } 68 | 69 | codes! { 70 | /// Datagram or capsule parse error 71 | /// See: 72 | (0x33, H3_DATAGRAM_ERROR); 73 | /// No error. This is used when the connection or stream needs to be 74 | /// closed, but there is no error to signal. 75 | (0x100, H3_NO_ERROR); 76 | 77 | /// Peer violated protocol requirements in a way that does not match a more 78 | /// specific error code, or endpoint declines to use the more specific 79 | /// error code. 80 | (0x101, H3_GENERAL_PROTOCOL_ERROR); 81 | 82 | /// An internal error has occurred in the HTTP stack. 83 | (0x102, H3_INTERNAL_ERROR); 84 | 85 | /// The endpoint detected that its peer created a stream that it will not 86 | /// accept. 87 | (0x103, H3_STREAM_CREATION_ERROR); 88 | 89 | /// A stream required by the HTTP/3 connection was closed or reset. 90 | (0x104, H3_CLOSED_CRITICAL_STREAM); 91 | 92 | /// A frame was received that was not permitted in the current state or on 93 | /// the current stream. 94 | (0x105, H3_FRAME_UNEXPECTED); 95 | 96 | /// A frame that fails to satisfy layout requirements or with an invalid 97 | /// size was received. 98 | (0x106, H3_FRAME_ERROR); 99 | 100 | /// The endpoint detected that its peer is exhibiting a behavior that might 101 | /// be generating excessive load. 102 | (0x107, H3_EXCESSIVE_LOAD); 103 | 104 | /// A Stream ID or Push ID was used incorrectly, such as exceeding a limit, 105 | /// reducing a limit, or being reused. 106 | (0x108, H3_ID_ERROR); 107 | 108 | /// An endpoint detected an error in the payload of a SETTINGS frame. 109 | (0x109, H3_SETTINGS_ERROR); 110 | 111 | /// No SETTINGS frame was received at the beginning of the control stream. 112 | (0x10a, H3_MISSING_SETTINGS); 113 | 114 | /// A server rejected a request without performing any application 115 | /// processing. 116 | (0x10b, H3_REQUEST_REJECTED); 117 | 118 | /// The request or its response (including pushed response) is cancelled. 119 | (0x10c, H3_REQUEST_CANCELLED); 120 | 121 | /// The client's stream terminated without containing a fully-formed 122 | /// request. 123 | (0x10d, H3_REQUEST_INCOMPLETE); 124 | 125 | /// An HTTP message was malformed and cannot be processed. 126 | (0x10e, H3_MESSAGE_ERROR); 127 | 128 | /// The TCP connection established in response to a CONNECT request was 129 | /// reset or abnormally closed. 130 | (0x10f, H3_CONNECT_ERROR); 131 | 132 | /// The requested operation cannot be served over HTTP/3. The peer should 133 | /// retry over HTTP/1.1. 134 | (0x110, H3_VERSION_FALLBACK); 135 | 136 | /// The decoder failed to interpret an encoded field section and is not 137 | /// able to continue decoding that field section. 138 | (0x200, QPACK_DECOMPRESSION_FAILED); 139 | 140 | /// The decoder failed to interpret an encoder instruction received on the 141 | /// encoder stream. 142 | (0x201, QPACK_ENCODER_STREAM_ERROR); 143 | 144 | /// The encoder failed to interpret a decoder instruction received on the 145 | /// decoder stream. 146 | (0x202, QPACK_DECODER_STREAM_ERROR); 147 | } 148 | 149 | impl From for u64 { 150 | fn from(code: Code) -> u64 { 151 | code.code 152 | } 153 | } 154 | 155 | impl From for Code { 156 | fn from(code: u64) -> Code { 157 | Code { code } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /h3/src/server/builder.rs: -------------------------------------------------------------------------------- 1 | //! Builder of HTTP/3 server connections. 2 | //! 3 | //! Use this struct to create a new [`Connection`]. 4 | //! Settings for the [`Connection`] can be provided here. 5 | //! 6 | //! # Example 7 | //! 8 | //! ```rust 9 | //! fn doc(conn: C) 10 | //! where 11 | //! C: h3::quic::Connection, 12 | //! B: bytes::Buf, 13 | //! { 14 | //! let mut server_builder = h3::server::builder(); 15 | //! // Set the maximum header size 16 | //! server_builder.max_field_section_size(1000); 17 | //! // do not send grease types 18 | //! server_builder.send_grease(false); 19 | //! // Build the Connection 20 | //! let mut h3_conn = server_builder.build(conn); 21 | //! } 22 | //! ``` 23 | 24 | use std::{collections::HashSet, result::Result, sync::Arc}; 25 | 26 | use bytes::Buf; 27 | 28 | use tokio::sync::mpsc; 29 | 30 | use crate::{ 31 | config::Config, 32 | connection::ConnectionInner, 33 | error::ConnectionError, 34 | quic::{self}, 35 | shared_state::SharedState, 36 | }; 37 | 38 | use super::connection::Connection; 39 | 40 | /// Create a builder of HTTP/3 server connections 41 | /// 42 | /// This function creates a [`Builder`] that carries settings that can 43 | /// be shared between server connections. 44 | pub fn builder() -> Builder { 45 | Builder::new() 46 | } 47 | 48 | /// Builder of HTTP/3 server connections. 49 | pub struct Builder { 50 | pub(crate) config: Config, 51 | } 52 | 53 | impl Builder { 54 | /// Creates a new [`Builder`] with default settings. 55 | pub(super) fn new() -> Self { 56 | Builder { 57 | config: Default::default(), 58 | } 59 | } 60 | 61 | // Not public API, just used in unit tests 62 | #[doc(hidden)] 63 | #[cfg(test)] 64 | pub fn send_settings(&mut self, value: bool) -> &mut Self { 65 | self.config.send_settings = value; 66 | self 67 | } 68 | 69 | /// Set the maximum header size this client is willing to accept 70 | /// 71 | /// See [header size constraints] section of the specification for details. 72 | /// 73 | /// [header size constraints]: https://www.rfc-editor.org/rfc/rfc9114.html#name-header-size-constraints 74 | pub fn max_field_section_size(&mut self, value: u64) -> &mut Self { 75 | self.config.settings.max_field_section_size = value; 76 | self 77 | } 78 | 79 | /// Send grease values to the Client. 80 | /// See [setting](https://www.rfc-editor.org/rfc/rfc9114.html#settings-parameters), [frame](https://www.rfc-editor.org/rfc/rfc9114.html#frame-reserved) and [stream](https://www.rfc-editor.org/rfc/rfc9114.html#stream-grease) for more information. 81 | #[inline] 82 | pub fn send_grease(&mut self, value: bool) -> &mut Self { 83 | self.config.send_grease = value; 84 | self 85 | } 86 | 87 | /// Indicates to the peer that WebTransport is supported. 88 | /// 89 | /// See: [establishing a webtransport session](https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.1) 90 | /// 91 | /// 92 | /// **Server**: 93 | /// Supporting for webtransport also requires setting `enable_extended_connect` `enable_datagram` 94 | /// and `max_webtransport_sessions`. 95 | #[inline] 96 | pub fn enable_webtransport(&mut self, value: bool) -> &mut Self { 97 | self.config.settings.enable_webtransport = value; 98 | self 99 | } 100 | 101 | /// Enables the extended CONNECT protocol required for various HTTP/3 extensions. 102 | pub fn enable_extended_connect(&mut self, value: bool) -> &mut Self { 103 | self.config.settings.enable_extended_connect = value; 104 | self 105 | } 106 | 107 | /// Limits the maximum number of WebTransport sessions 108 | pub fn max_webtransport_sessions(&mut self, value: u64) -> &mut Self { 109 | self.config.settings.max_webtransport_sessions = value; 110 | self 111 | } 112 | 113 | /// Indicates that the client or server supports HTTP/3 datagrams 114 | /// 115 | /// See: 116 | pub fn enable_datagram(&mut self, value: bool) -> &mut Self { 117 | self.config.settings.enable_datagram = value; 118 | self 119 | } 120 | } 121 | 122 | impl Builder { 123 | /// Build an HTTP/3 connection from a QUIC connection 124 | /// 125 | /// This method creates a [`Connection`] instance with the settings in the [`Builder`]. 126 | pub async fn build(&self, conn: C) -> Result, ConnectionError> 127 | where 128 | C: quic::Connection, 129 | B: Buf, 130 | { 131 | let (sender, receiver) = mpsc::unbounded_channel(); 132 | let shared = SharedState::default(); 133 | 134 | Ok(Connection { 135 | inner: ConnectionInner::new(conn, Arc::new(shared), self.config).await?, 136 | max_field_section_size: self.config.settings.max_field_section_size, 137 | request_end_send: sender, 138 | request_end_recv: receiver, 139 | ongoing_streams: HashSet::new(), 140 | sent_closing: None, 141 | recv_closing: None, 142 | last_accepted_stream: None, 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /h3/src/qpack/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::qpack::decoder::Decoder; 2 | use crate::qpack::encoder::Encoder; 3 | use crate::qpack::{dynamic::DynamicTable, Decoded, DecoderError, HeaderField}; 4 | use std::io::Cursor; 5 | 6 | pub mod helpers { 7 | use crate::qpack::{dynamic::DynamicTable, HeaderField}; 8 | 9 | pub const TABLE_SIZE: usize = 4096; 10 | 11 | pub fn build_table() -> DynamicTable { 12 | let mut table = DynamicTable::new(); 13 | table.set_max_size(TABLE_SIZE).unwrap(); 14 | table.set_max_blocked(100).unwrap(); 15 | table 16 | } 17 | 18 | pub fn build_table_with_size(n_field: usize) -> DynamicTable { 19 | let mut table = DynamicTable::new(); 20 | table.set_max_size(TABLE_SIZE).unwrap(); 21 | table.set_max_blocked(100).unwrap(); 22 | 23 | for i in 0..n_field { 24 | table 25 | .put(HeaderField::new(format!("foo{}", i + 1), "bar")) 26 | .unwrap(); 27 | } 28 | 29 | table 30 | } 31 | } 32 | 33 | #[test] 34 | fn codec_basic_get() { 35 | let mut encoder = Encoder::default(); 36 | let mut decoder = Decoder::from(DynamicTable::new()); 37 | 38 | let mut block_buf = vec![]; 39 | let mut enc_buf = vec![]; 40 | let mut dec_buf = vec![]; 41 | 42 | let header = vec![ 43 | HeaderField::new(":method", "GET"), 44 | HeaderField::new(":path", "/"), 45 | HeaderField::new("foo", "bar"), 46 | ]; 47 | 48 | encoder 49 | .encode(42, &mut block_buf, &mut enc_buf, header.clone()) 50 | .unwrap(); 51 | 52 | let mut enc_cur = Cursor::new(&mut enc_buf); 53 | decoder.on_encoder_recv(&mut enc_cur, &mut dec_buf).unwrap(); 54 | 55 | let mut block_cur = Cursor::new(&mut block_buf); 56 | let Decoded { fields, .. } = decoder.decode_header(&mut block_cur).unwrap(); 57 | assert_eq!(fields, header); 58 | 59 | let mut dec_cur = Cursor::new(&mut dec_buf); 60 | encoder.on_decoder_recv(&mut dec_cur).unwrap(); 61 | } 62 | 63 | const TABLE_SIZE: usize = 4096; 64 | #[test] 65 | fn blocked_header() { 66 | let mut enc_table = DynamicTable::new(); 67 | enc_table.set_max_size(TABLE_SIZE).unwrap(); 68 | enc_table.set_max_blocked(100).unwrap(); 69 | let mut encoder = Encoder::from(enc_table); 70 | let mut dec_table = DynamicTable::new(); 71 | dec_table.set_max_size(TABLE_SIZE).unwrap(); 72 | dec_table.set_max_blocked(100).unwrap(); 73 | let decoder = Decoder::from(dec_table); 74 | 75 | let mut block_buf = vec![]; 76 | let mut enc_buf = vec![]; 77 | 78 | encoder 79 | .encode( 80 | 42, 81 | &mut block_buf, 82 | &mut enc_buf, 83 | &[HeaderField::new("foo", "bar")], 84 | ) 85 | .unwrap(); 86 | 87 | let mut block_cur = Cursor::new(&mut block_buf); 88 | assert_eq!( 89 | decoder.decode_header(&mut block_cur), 90 | Err(DecoderError::MissingRefs(1)) 91 | ); 92 | } 93 | 94 | #[test] 95 | fn codec_table_size_0() { 96 | let mut enc_table = DynamicTable::new(); 97 | let mut dec_table = DynamicTable::new(); 98 | 99 | let mut block_buf = vec![]; 100 | let mut enc_buf = vec![]; 101 | let mut dec_buf = vec![]; 102 | 103 | let header = vec![ 104 | HeaderField::new(":method", "GET"), 105 | HeaderField::new(":path", "/"), 106 | HeaderField::new("foo", "bar"), 107 | ]; 108 | 109 | dec_table.set_max_size(0).unwrap(); 110 | enc_table.set_max_size(0).unwrap(); 111 | 112 | let mut encoder = Encoder::from(enc_table); 113 | let mut decoder = Decoder::from(dec_table); 114 | 115 | encoder 116 | .encode(42, &mut block_buf, &mut enc_buf, header.clone()) 117 | .unwrap(); 118 | 119 | let mut enc_cur = Cursor::new(&mut enc_buf); 120 | decoder.on_encoder_recv(&mut enc_cur, &mut dec_buf).unwrap(); 121 | 122 | let mut block_cur = Cursor::new(&mut block_buf); 123 | let Decoded { fields, .. } = decoder.decode_header(&mut block_cur).unwrap(); 124 | assert_eq!(fields, header); 125 | 126 | let mut dec_cur = Cursor::new(&mut dec_buf); 127 | encoder.on_decoder_recv(&mut dec_cur).unwrap(); 128 | } 129 | 130 | #[test] 131 | fn codec_table_full() { 132 | let mut enc_table = DynamicTable::new(); 133 | let mut dec_table = DynamicTable::new(); 134 | 135 | let mut block_buf = vec![]; 136 | let mut enc_buf = vec![]; 137 | let mut dec_buf = vec![]; 138 | 139 | let header = vec![ 140 | HeaderField::new("foo", "bar"), 141 | HeaderField::new("foo1", "bar1"), 142 | ]; 143 | 144 | dec_table.set_max_size(42).unwrap(); 145 | enc_table.set_max_size(42).unwrap(); 146 | 147 | let mut encoder = Encoder::from(enc_table); 148 | let mut decoder = Decoder::from(dec_table); 149 | 150 | encoder 151 | .encode(42, &mut block_buf, &mut enc_buf, header.clone()) 152 | .unwrap(); 153 | 154 | let mut enc_cur = Cursor::new(&mut enc_buf); 155 | let mut block_cur = Cursor::new(&mut block_buf); 156 | 157 | decoder.on_encoder_recv(&mut enc_cur, &mut dec_buf).unwrap(); 158 | let Decoded { fields, .. } = decoder.decode_header(&mut block_cur).unwrap(); 159 | assert_eq!(fields, header); 160 | 161 | let mut dec_cur = Cursor::new(&mut dec_buf); 162 | encoder.on_decoder_recv(&mut dec_cur).unwrap(); 163 | } 164 | -------------------------------------------------------------------------------- /h3/src/qpack/prefix_int.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use bytes::{Buf, BufMut}; 4 | 5 | use crate::proto::coding::{self, BufExt, BufMutExt}; 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub enum Error { 9 | Overflow, 10 | UnexpectedEnd, 11 | } 12 | 13 | impl std::fmt::Display for Error { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { 15 | match self { 16 | Error::Overflow => write!(f, "value overflow"), 17 | Error::UnexpectedEnd => write!(f, "unexpected end"), 18 | } 19 | } 20 | } 21 | 22 | pub fn decode(size: u8, buf: &mut B) -> Result<(u8, u64), Error> { 23 | assert!(size <= 8); 24 | let mut first = buf.get::()?; 25 | 26 | // NOTE: following casts to u8 intend to trim the most significant bits, they are used as a 27 | // workaround for shiftoverflow errors when size == 8. 28 | let flags = ((first as usize) >> size) as u8; 29 | let mask = 0xFF >> (8 - size); 30 | first &= mask; 31 | 32 | // if first < 2usize.pow(size) - 1 33 | if first < mask { 34 | return Ok((flags, first as u64)); 35 | } 36 | 37 | let mut value = mask as u64; 38 | let mut power = 0usize; 39 | loop { 40 | let byte = buf.get::()? as u64; 41 | value += (byte & 127) << power; 42 | power += 7; 43 | 44 | if byte & 128 == 0 { 45 | break; 46 | } 47 | 48 | if power >= MAX_POWER { 49 | return Err(Error::Overflow); 50 | } 51 | } 52 | 53 | Ok((flags, value)) 54 | } 55 | 56 | pub fn encode(size: u8, flags: u8, value: u64, buf: &mut B) { 57 | assert!(size <= 8); 58 | // NOTE: following casts to u8 intend to trim the most significant bits, they are used as a 59 | // workaround for shiftoverflow errors when size == 8. 60 | let mask = !(0xFF << size) as u8; 61 | let flags = ((flags as usize) << size) as u8; 62 | 63 | // if value < 2usize.pow(size) - 1 64 | if value < (mask as u64) { 65 | buf.write(flags | value as u8); 66 | return; 67 | } 68 | 69 | buf.write(mask | flags); 70 | let mut remaining = value - mask as u64; 71 | 72 | while remaining >= 128 { 73 | let rest = (remaining % 128) as u8; 74 | buf.write(rest + 128); 75 | remaining /= 128; 76 | } 77 | buf.write(remaining as u8); 78 | } 79 | 80 | const MAX_POWER: usize = 9 * 7; 81 | 82 | impl From for Error { 83 | fn from(_: coding::UnexpectedEnd) -> Self { 84 | Error::UnexpectedEnd 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod test { 90 | use assert_matches::assert_matches; 91 | use std::io::Cursor; 92 | 93 | use crate::qpack::prefix_int::Error; 94 | 95 | fn check_codec(size: u8, flags: u8, value: u64, data: &[u8]) { 96 | let mut buf = Vec::new(); 97 | super::encode(size, flags, value, &mut buf); 98 | assert_eq!(buf, data); 99 | let mut read = Cursor::new(&buf); 100 | assert_eq!((flags, value), super::decode(size, &mut read).unwrap()); 101 | } 102 | 103 | #[test] 104 | fn codec_5_bits() { 105 | check_codec(5, 0b101, 10, &[0b1010_1010]); 106 | check_codec(5, 0b101, 0, &[0b1010_0000]); 107 | check_codec(5, 0b010, 1337, &[0b0101_1111, 154, 10]); 108 | check_codec(5, 0b010, 31, &[0b0101_1111, 0]); 109 | check_codec( 110 | 5, 111 | 0b010, 112 | 0x80_00_00_00_00_00_00_1E, 113 | &[95, 255, 255, 255, 255, 255, 255, 255, 255, 127], 114 | ); 115 | } 116 | 117 | #[test] 118 | fn codec_8_bits() { 119 | check_codec(8, 0, 42, &[0b0010_1010]); 120 | check_codec(8, 0, 424_242, &[255, 179, 240, 25]); 121 | check_codec( 122 | 8, 123 | 0, 124 | 0x80_00_00_00_00_00_00_FE, 125 | &[255, 255, 255, 255, 255, 255, 255, 255, 255, 127], 126 | ); 127 | } 128 | 129 | #[test] 130 | #[should_panic] 131 | fn size_too_big_value() { 132 | let mut buf = vec![]; 133 | super::encode(9, 1, 1, &mut buf); 134 | } 135 | 136 | #[test] 137 | #[should_panic] 138 | fn size_too_big_of_size() { 139 | let buf = vec![]; 140 | let mut read = Cursor::new(&buf); 141 | super::decode(9, &mut read).unwrap(); 142 | } 143 | 144 | #[cfg(target_pointer_width = "64")] 145 | #[test] 146 | fn overflow() { 147 | let buf = vec![255, 128, 254, 255, 255, 255, 255, 255, 255, 255, 255, 1]; 148 | let mut read = Cursor::new(&buf); 149 | assert!(super::decode(8, &mut read).is_err()); 150 | } 151 | 152 | #[test] 153 | fn number_never_ends_with_0x80() { 154 | check_codec(4, 0b0001, 143, &[31, 128, 1]); 155 | } 156 | #[test] 157 | fn overflow2() { 158 | let buf = vec![95, 225, 255, 255, 255, 255, 255, 255, 255, 255, 1]; 159 | let mut read = Cursor::new(&buf); 160 | let x = super::decode(5, &mut read); 161 | assert_matches!(x, Err(Error::Overflow)); 162 | } 163 | 164 | #[test] 165 | fn allow_62_bit() { 166 | // This is the maximum value that can be encoded in with a flag size of 7 bits 167 | // The value is requires more than 62 bits so the spec is fulfilled 168 | let buf = vec![3, 255, 255, 255, 255, 255, 255, 255, 255, 127]; 169 | let mut read = Cursor::new(&buf); 170 | let (flag, value) = super::decode(1, &mut read).expect("Value is allowed to be parsed"); 171 | assert_eq!(flag, 1); 172 | assert_eq!(value, 9223372036854775808); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | toolchain_style: stable 11 | toolchain_msrv: 1.74.1 12 | toolchain_h3_quinn_msrv: 1.74.1 13 | toolchain_doc: nightly-2025-04-02 14 | toolchain_lint: stable 15 | toolchain_fuzz: nightly-2025-04-02 16 | 17 | jobs: 18 | ci-pass: 19 | name: CI is green 20 | runs-on: ubuntu-latest 21 | needs: 22 | - style 23 | - lint 24 | - msrv 25 | - msrv_h3_quinn 26 | - test 27 | - doc 28 | - fuzz 29 | - examples 30 | - compliance 31 | steps: 32 | - run: exit 0 33 | 34 | style: 35 | name: Check Style 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v5 39 | - name: Install Rust ${{ env.toolchain_style }} 40 | uses: actions-rust-lang/setup-rust-toolchain@v1 41 | with: 42 | toolchain: ${{ env.toolchain_style }} 43 | components: rustfmt 44 | - run: cargo fmt --all -- --check 45 | 46 | lint: 47 | name: Lint 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v5 51 | - name: Install Rust ${{ env.toolchain_lint }} 52 | uses: actions-rust-lang/setup-rust-toolchain@v1 53 | with: 54 | toolchain: ${{ env.toolchain_lint }} 55 | components: clippy 56 | - run: cargo clippy 57 | 58 | msrv: 59 | name: Check MSRV 60 | needs: [style] 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v5 64 | - name: Install Rust ${{ env.toolchain_msrv }} 65 | uses: actions-rust-lang/setup-rust-toolchain@v1 66 | with: 67 | toolchain: ${{ env.toolchain_msrv }} 68 | - run: cargo check -p h3 69 | 70 | msrv_h3_quinn: 71 | name: Check MSRV of `h3-quinn` 72 | needs: [style] 73 | runs-on: ubuntu-latest 74 | steps: 75 | - uses: actions/checkout@v5 76 | - name: Install Rust ${{ env.toolchain_h3_quinn_msrv }} 77 | uses: actions-rust-lang/setup-rust-toolchain@v1 78 | with: 79 | toolchain: ${{ env.toolchain_h3_quinn_msrv }} 80 | - run: cargo check -p h3-quinn 81 | 82 | test: 83 | name: Test ${{ matrix.toolchain }} ${{ matrix.os }} ${{ matrix.target }} 84 | needs: [style] 85 | strategy: 86 | matrix: 87 | os: [ubuntu-latest] 88 | toolchain: [stable, beta] 89 | features: [i-implement-a-third-party-backend-and-opt-into-breaking-changes, tracing, 'tracing,i-implement-a-third-party-backend-and-opt-into-breaking-changes'] 90 | target: [x86_64-unknown-linux-gnu] 91 | include: 92 | # Add a 32-bit target test configuration 93 | - os: ubuntu-latest 94 | toolchain: stable 95 | features: i-implement-a-third-party-backend-and-opt-into-breaking-changes 96 | target: i686-unknown-linux-gnu 97 | runs-on: ${{ matrix.os }} 98 | steps: 99 | - uses: actions/checkout@v5 100 | # Add this step for 32-bit build support 101 | - name: Install 32-bit development libraries 102 | if: matrix.target == 'i686-unknown-linux-gnu' 103 | run: | 104 | sudo dpkg --add-architecture i386 105 | sudo apt-get update 106 | sudo apt-get install -y gcc-multilib libc6-dev-i386 107 | - name: Install Rust ${{ matrix.toolchain }} 108 | uses: actions-rust-lang/setup-rust-toolchain@v1 109 | with: 110 | toolchain: ${{ matrix.toolchain }} 111 | target: ${{ matrix.target }} 112 | - run: cargo test --features ${{ matrix.features }} --target ${{ matrix.target }} 113 | env: #[cfg(feature = "datagram")] unexpected `cfg` condition value: `datagram` 114 | RUSTFLAGS: "-A unexpected_cfgs" 115 | - name: h3Spec 116 | run: ./ci/h3spec.sh 117 | if: matrix.toolchain == 'stable' 118 | 119 | doc: 120 | name: Build docs 121 | needs: [test] 122 | runs-on: ubuntu-latest 123 | steps: 124 | - uses: actions/checkout@v5 125 | - name: Install Rust ${{ env.toolchain_doc }} 126 | uses: actions-rust-lang/setup-rust-toolchain@v1 127 | with: 128 | toolchain: ${{ env.toolchain_doc }} 129 | - run: cargo rustdoc -p h3 -- -D intra-doc-link-resolution-failure 130 | 131 | fuzz: 132 | name: Fuzz test 133 | needs: [test] 134 | runs-on: ubuntu-latest 135 | steps: 136 | - uses: actions/checkout@v5 137 | - name: Install Rust ${{ env.toolchain_fuzz }} 138 | uses: actions-rust-lang/setup-rust-toolchain@v1 139 | with: 140 | toolchain: ${{ env.toolchain_fuzz }} 141 | - name: Install cargo-fuzz 142 | uses: camshaft/install@v1 143 | with: 144 | crate: cargo-fuzz 145 | - run: cargo fuzz run fuzz_varint -- -runs=1 146 | 147 | compliance: 148 | name: Compliance report 149 | needs: [test] 150 | runs-on: ubuntu-latest 151 | steps: 152 | - uses: actions/checkout@v5 153 | with: 154 | persist-credentials: false 155 | fetch-depth: 0 156 | - name: Generate compliance report 157 | uses: ./.github/actions/compliance 158 | with: 159 | report-script: ${{ github.workspace }}/ci/compliance/report.sh 160 | 161 | examples: 162 | name: Run Examples 163 | needs: [test] 164 | runs-on: ubuntu-latest 165 | steps: 166 | - uses: actions/checkout@v5 167 | - name: Install Rust stable 168 | uses: actions-rust-lang/setup-rust-toolchain@v1 169 | with: 170 | toolchain: stable 171 | - name: Run server and client examples test 172 | run: ./ci/example_test.sh 173 | -------------------------------------------------------------------------------- /h3/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // This is to avoid an import loop: 2 | // h3 tests depend on having private access to the crate. 3 | // They must be part of the crate so as not to break privacy. 4 | // They also depend on h3_quinn which depends on the crate. 5 | // Having a dev-dependency on h3_quinn would work as far as cargo is 6 | // concerned, but quic traits wouldn't match between the "h3" crate that 7 | // comes before h3_quinn and the one that comes after and runs the tests 8 | #[path = "../../../h3-quinn/src/lib.rs"] 9 | mod h3_quinn; 10 | 11 | mod connection; 12 | mod request; 13 | 14 | use std::{ 15 | convert::TryInto, 16 | net::{Ipv6Addr, ToSocketAddrs}, 17 | sync::Arc, 18 | time::Duration, 19 | }; 20 | 21 | use bytes::{Buf, Bytes}; 22 | use http::Request; 23 | use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig}; 24 | use rustls::pki_types::{CertificateDer, PrivateKeyDer}; 25 | 26 | use crate::quic; 27 | use h3_quinn::{quinn::TransportConfig, Connection}; 28 | 29 | pub fn init_tracing() { 30 | let _ = tracing_subscriber::fmt() 31 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 32 | .with_span_events(tracing_subscriber::fmt::format::FmtSpan::FULL) 33 | .with_test_writer() 34 | .try_init(); 35 | } 36 | 37 | /// This accepts an incoming request. After the bidirectional stream is started it will not poll the 38 | /// connection or receive further requests until the first headers are received. 39 | /// Only use this for testing purposes. 40 | async fn get_stream_blocking, B: Buf>( 41 | incoming: &mut crate::server::Connection, 42 | ) -> Option<(Request<()>, crate::server::RequestStream)> { 43 | let request_resolver = incoming.accept().await.ok()??; 44 | let (request, stream) = request_resolver.resolve_request().await.ok()?; 45 | Some((request, stream)) 46 | } 47 | 48 | pub struct Pair { 49 | port: u16, 50 | cert: CertificateDer<'static>, 51 | key: PrivateKeyDer<'static>, 52 | config: Arc, 53 | } 54 | 55 | impl Default for Pair { 56 | fn default() -> Self { 57 | let (cert, key) = build_certs(); 58 | Self { 59 | cert, 60 | key, 61 | port: 0, 62 | config: Arc::new(TransportConfig::default()), 63 | } 64 | } 65 | } 66 | 67 | impl Pair { 68 | pub fn with_timeout(&mut self, duration: Duration) { 69 | Arc::get_mut(&mut self.config) 70 | .unwrap() 71 | .max_idle_timeout(Some( 72 | duration.try_into().expect("idle timeout duration invalid"), 73 | )) 74 | .initial_rtt(Duration::from_millis(10)); 75 | } 76 | 77 | pub fn server_inner(&mut self) -> h3_quinn::Endpoint { 78 | let mut crypto = rustls::ServerConfig::builder_with_provider(Arc::new( 79 | rustls::crypto::ring::default_provider(), 80 | )) 81 | .with_protocol_versions(&[&rustls::version::TLS13]) 82 | .unwrap() 83 | .with_no_client_auth() 84 | .with_single_cert(vec![self.cert.clone()], self.key.clone_key()) 85 | .unwrap(); 86 | crypto.max_early_data_size = u32::MAX; 87 | crypto.alpn_protocols = vec![b"h3".to_vec()]; 88 | 89 | let mut server_config = h3_quinn::quinn::ServerConfig::with_crypto(Arc::new( 90 | QuicServerConfig::try_from(crypto).unwrap(), 91 | )); 92 | server_config.transport = self.config.clone(); 93 | let endpoint = 94 | h3_quinn::quinn::Endpoint::server(server_config, "[::]:0".parse().unwrap()).unwrap(); 95 | 96 | self.port = endpoint.local_addr().unwrap().port(); 97 | 98 | endpoint 99 | } 100 | 101 | pub fn server(&mut self) -> Server { 102 | let endpoint = self.server_inner(); 103 | Server { endpoint } 104 | } 105 | 106 | pub async fn client_inner(&self) -> quinn::Connection { 107 | let addr = (Ipv6Addr::LOCALHOST, self.port) 108 | .to_socket_addrs() 109 | .unwrap() 110 | .next() 111 | .unwrap(); 112 | 113 | let mut root_cert_store = rustls::RootCertStore::empty(); 114 | root_cert_store.add(self.cert.clone()).unwrap(); 115 | let mut crypto = rustls::ClientConfig::builder_with_provider(Arc::new( 116 | rustls::crypto::ring::default_provider(), 117 | )) 118 | .with_protocol_versions(&[&rustls::version::TLS13]) 119 | .unwrap() 120 | .with_root_certificates(root_cert_store) 121 | .with_no_client_auth(); 122 | crypto.enable_early_data = true; 123 | crypto.alpn_protocols = vec![b"h3".to_vec()]; 124 | 125 | let client_config = h3_quinn::quinn::ClientConfig::new(Arc::new( 126 | QuicClientConfig::try_from(crypto).unwrap(), 127 | )); 128 | 129 | let mut client_endpoint = 130 | h3_quinn::quinn::Endpoint::client("[::]:0".parse().unwrap()).unwrap(); 131 | client_endpoint.set_default_client_config(client_config); 132 | client_endpoint 133 | .connect(addr, "localhost") 134 | .unwrap() 135 | .await 136 | .unwrap() 137 | } 138 | 139 | pub async fn client(&self) -> h3_quinn::Connection { 140 | Connection::new(self.client_inner().await) 141 | } 142 | } 143 | 144 | pub struct Server { 145 | pub endpoint: h3_quinn::Endpoint, 146 | } 147 | 148 | impl Server { 149 | pub async fn next(&mut self) -> impl quic::Connection { 150 | Connection::new(self.endpoint.accept().await.unwrap().await.unwrap()) 151 | } 152 | } 153 | 154 | pub fn build_certs() -> (CertificateDer<'static>, PrivateKeyDer<'static>) { 155 | let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap(); 156 | ( 157 | cert.cert.into(), 158 | PrivateKeyDer::Pkcs8(cert.signing_key.serialize_der().into()), 159 | ) 160 | } 161 | -------------------------------------------------------------------------------- /h3/src/proto/varint.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::TryInto, fmt, ops::Div}; 2 | 3 | use bytes::{Buf, BufMut}; 4 | 5 | pub use super::coding::UnexpectedEnd; 6 | 7 | /// An integer less than 2^62 8 | /// 9 | /// Values of this type are suitable for encoding as QUIC variable-length integer. 10 | // It would be neat if we could express to Rust that the top two bits are available for use as enum 11 | // discriminants 12 | #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] 13 | pub struct VarInt(pub(crate) u64); 14 | 15 | impl Div for VarInt { 16 | type Output = Self; 17 | 18 | fn div(self, rhs: u64) -> Self::Output { 19 | Self(self.0 / rhs) 20 | } 21 | } 22 | 23 | impl VarInt { 24 | /// The largest representable value 25 | pub const MAX: VarInt = VarInt((1 << 62) - 1); 26 | /// The largest encoded value length 27 | pub const MAX_SIZE: usize = 8; 28 | 29 | /// Construct a `VarInt` infallibly 30 | pub const fn from_u32(x: u32) -> Self { 31 | VarInt(x as u64) 32 | } 33 | 34 | /// Succeeds iff `x` < 2^62 35 | pub fn from_u64(x: u64) -> Result { 36 | if x < 2u64.pow(62) { 37 | Ok(VarInt(x)) 38 | } else { 39 | Err(VarIntBoundsExceeded(x)) 40 | } 41 | } 42 | 43 | /// Create a VarInt without ensuring it's in range 44 | /// 45 | /// # Safety 46 | /// 47 | /// `x` must be less than 2^62. 48 | pub const unsafe fn from_u64_unchecked(x: u64) -> Self { 49 | VarInt(x) 50 | } 51 | 52 | /// Extract the integer value 53 | pub const fn into_inner(self) -> u64 { 54 | self.0 55 | } 56 | 57 | /// Compute the number of bytes needed to encode this value 58 | pub fn size(self) -> usize { 59 | let x = self.0; 60 | if x < 2u64.pow(6) { 61 | 1 62 | } else if x < 2u64.pow(14) { 63 | 2 64 | } else if x < 2u64.pow(30) { 65 | 4 66 | } else if x < 2u64.pow(62) { 67 | 8 68 | } else { 69 | unreachable!("malformed VarInt"); 70 | } 71 | } 72 | 73 | /// Length of an encoded value from its first byte 74 | pub fn encoded_size(first: u8) -> usize { 75 | 2usize.pow((first >> 6) as u32) 76 | } 77 | 78 | pub fn decode(r: &mut B) -> Result { 79 | if !r.has_remaining() { 80 | return Err(UnexpectedEnd(0)); 81 | } 82 | let mut buf = [0; 8]; 83 | buf[0] = r.get_u8(); 84 | let tag = buf[0] >> 6; 85 | buf[0] &= 0b0011_1111; 86 | let x = match tag { 87 | 0b00 => u64::from(buf[0]), 88 | 0b01 => { 89 | if r.remaining() < 1 { 90 | return Err(UnexpectedEnd(1)); 91 | } 92 | r.copy_to_slice(&mut buf[1..2]); 93 | u64::from(u16::from_be_bytes(buf[..2].try_into().unwrap())) 94 | } 95 | 0b10 => { 96 | if r.remaining() < 3 { 97 | return Err(UnexpectedEnd(2)); 98 | } 99 | r.copy_to_slice(&mut buf[1..4]); 100 | u64::from(u32::from_be_bytes(buf[..4].try_into().unwrap())) 101 | } 102 | 0b11 => { 103 | if r.remaining() < 7 { 104 | return Err(UnexpectedEnd(3)); 105 | } 106 | r.copy_to_slice(&mut buf[1..8]); 107 | u64::from_be_bytes(buf) 108 | } 109 | _ => unreachable!(), 110 | }; 111 | Ok(VarInt(x)) 112 | } 113 | 114 | pub fn encode(&self, w: &mut B) { 115 | let x = self.0; 116 | if x < 2u64.pow(6) { 117 | w.put_u8(x as u8); 118 | } else if x < 2u64.pow(14) { 119 | w.put_u16(0b01 << 14 | x as u16); 120 | } else if x < 2u64.pow(30) { 121 | w.put_u32(0b10 << 30 | x as u32); 122 | } else if x < 2u64.pow(62) { 123 | w.put_u64(0b11 << 62 | x); 124 | } else { 125 | unreachable!("malformed VarInt") 126 | } 127 | } 128 | } 129 | 130 | impl From for u64 { 131 | fn from(x: VarInt) -> u64 { 132 | x.0 133 | } 134 | } 135 | 136 | impl From for VarInt { 137 | fn from(x: u8) -> Self { 138 | VarInt(x.into()) 139 | } 140 | } 141 | 142 | impl From for VarInt { 143 | fn from(x: u16) -> Self { 144 | VarInt(x.into()) 145 | } 146 | } 147 | 148 | impl From for VarInt { 149 | fn from(x: u32) -> Self { 150 | VarInt(x.into()) 151 | } 152 | } 153 | 154 | impl std::convert::TryFrom for VarInt { 155 | type Error = VarIntBoundsExceeded; 156 | /// Succeeds iff `x` < 2^62 157 | fn try_from(x: u64) -> Result { 158 | VarInt::from_u64(x) 159 | } 160 | } 161 | 162 | impl std::convert::TryFrom for VarInt { 163 | type Error = VarIntBoundsExceeded; 164 | /// Succeeds iff `x` < 2^62 165 | fn try_from(x: usize) -> Result { 166 | VarInt::try_from(x as u64) 167 | } 168 | } 169 | 170 | impl fmt::Debug for VarInt { 171 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 172 | self.0.fmt(f) 173 | } 174 | } 175 | 176 | impl fmt::Display for VarInt { 177 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 178 | self.0.fmt(f) 179 | } 180 | } 181 | 182 | pub trait BufExt { 183 | #[allow(dead_code)] 184 | fn get_var(&mut self) -> Result; 185 | } 186 | 187 | impl BufExt for T { 188 | fn get_var(&mut self) -> Result { 189 | Ok(VarInt::decode(self)?.into_inner()) 190 | } 191 | } 192 | 193 | pub trait BufMutExt { 194 | #[allow(dead_code)] 195 | fn write_var(&mut self, x: u64); 196 | } 197 | 198 | impl BufMutExt for T { 199 | fn write_var(&mut self, x: u64) { 200 | VarInt::from_u64(x).unwrap().encode(self); 201 | } 202 | } 203 | /// Error returned when constructing a `VarInt` from a value >= 2^62 204 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 205 | pub struct VarIntBoundsExceeded(pub(crate) u64); 206 | --------------------------------------------------------------------------------