From 5204dbc19c06765c1d31d6124a261c28dfafec5f Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Fri, 14 Apr 2023 13:32:02 -0700 Subject: [PATCH] Refactor and restructure the WebTransport code. Who knows if it actually works. --- player/src/index.ts | 2 +- server/Cargo.lock | 452 +++++++++++++++++++++++++++++++++++++++++- server/Cargo.toml | 7 +- server/src/error.rs | 44 ++++ server/src/lib.rs | 3 + server/src/main.rs | 35 +++- server/src/server.rs | 332 +++++++++++++++++++++++++++++++ server/src/session.rs | 104 ++++++++++ 8 files changed, 972 insertions(+), 7 deletions(-) create mode 100644 server/src/error.rs create mode 100644 server/src/lib.rs create mode 100644 server/src/server.rs create mode 100644 server/src/session.rs diff --git a/player/src/index.ts b/player/src/index.ts index e691ab5..89e2e22 100644 --- a/player/src/index.ts +++ b/player/src/index.ts @@ -11,7 +11,7 @@ for (let c = 0; c < fingerprintHex.length-1; c += 2) { const params = new URLSearchParams(window.location.search) -const url = params.get("url") || "https://localhost:4443/watch" +const url = params.get("url") || "https://127.0.0.1:4443/watch" const canvas = document.querySelector("canvas#video")! const player = new Player({ diff --git a/server/Cargo.lock b/server/Cargo.lock index 6110308..035e890 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2,12 +2,87 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bumpalo" version = "3.12.0" @@ -26,6 +101,48 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + [[package]] name = "cmake" version = "0.1.50" @@ -35,6 +152,96 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "js-sys" version = "0.3.61" @@ -62,6 +269,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + [[package]] name = "log" version = "0.4.17" @@ -71,6 +284,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.45.0", +] + [[package]] name = "octets" version = "0.2.0" @@ -117,6 +348,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "ring" version = "0.16.20" @@ -132,6 +380,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.37.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "serde" version = "1.0.160" @@ -162,6 +424,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.109" @@ -173,6 +441,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "unicode-ident" version = "1.0.8" @@ -186,12 +474,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] -name = "warp-server" +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "warp" version = "0.1.0" dependencies = [ + "clap", + "env_logger", + "log", + "mio", "quiche", + "ring", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.84" @@ -213,7 +518,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -235,7 +540,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -272,8 +577,149 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/server/Cargo.toml b/server/Cargo.toml index d4dcbb7..c5f4afd 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "warp-server" +name = "warp" version = "0.1.0" edition = "2021" @@ -7,3 +7,8 @@ edition = "2021" [dependencies] quiche = { git = "https://github.com/n8o/quiche.git", branch = "master" } # WebTransport fork +clap = { version = "4.0", features = [ "derive" ] } +log = { version = "0.4", features = ["std"] } +mio = { version = "0.8", features = ["net", "os-poll"] } +env_logger = "0.9.3" +ring = "0.16" \ No newline at end of file diff --git a/server/src/error.rs b/server/src/error.rs new file mode 100644 index 0000000..ef10de2 --- /dev/null +++ b/server/src/error.rs @@ -0,0 +1,44 @@ +use std::io; +use quiche::h3::webtransport; + +#[derive(Debug)] +pub enum Error { + Io(io::Error), + Quiche(quiche::Error), + WebTransport(webtransport::Error), + Server(Server), +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} + +impl From for Error { + fn from(err: quiche::Error) -> Error { + Error::Quiche(err) + } +} + +impl From for Error { + fn from(err: webtransport::Error) -> Error { + Error::WebTransport(err) + } +} + +// Custom server error messages. +#[derive(Debug)] +pub enum Server { + InvalidToken, + InvalidConnectionID, + UnknownConnectionID, +} + +impl From for Error { + fn from(err: Server) -> Error { + Error::Server(err) + } +} + +pub type Result = std::result::Result; \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs new file mode 100644 index 0000000..241d22a --- /dev/null +++ b/server/src/lib.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod server; +pub mod session; \ No newline at end of file diff --git a/server/src/main.rs b/server/src/main.rs index e7a11a9..233d2cc 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,3 +1,34 @@ -fn main() { - println!("Hello, world!"); +use warp::server::Server; + +use clap::Parser; + +/// Search for a pattern in a file and display the lines that contain it. +#[derive(Parser)] +struct Cli { + /// Listen on this address + #[arg(short, long, default_value = "127.0.0.1:4443")] + addr: String, + + /// Use the certificate file at this path + #[arg(short, long, default_value = "../cert/localhost.crt")] + cert: String, + + /// Use the private key at this path + #[arg(short, long, default_value = "../cert/localhost.key")] + key: String, } + +fn main() { + let args = Cli::parse(); + + let server_config = warp::server::Config{ + addr: args.addr, + cert: args.cert, + key: args.key, + }; + + let mut server = Server::new(server_config).unwrap(); + loop { + server.poll().unwrap() + } +} \ No newline at end of file diff --git a/server/src/server.rs b/server/src/server.rs new file mode 100644 index 0000000..b3f2359 --- /dev/null +++ b/server/src/server.rs @@ -0,0 +1,332 @@ +use crate::session; +use crate::error; + +use session::Session; +use error::{Error, Result}; + +use std::{io, net}; +use log; + +use quiche::h3::webtransport; + +const MAX_DATAGRAM_SIZE: usize = 1350; + +pub struct Server { + // IO stuff + socket: mio::net::UdpSocket, + poll: mio::Poll, + events: mio::Events, + + // QUIC stuff + quic: quiche::Config, + sessions: session::Map, + seed: ring::hmac::Key, // connection ID seed +} + +pub struct Config { + pub addr: String, + pub cert: String, + pub key: String, +} + +impl Server { + pub fn new(config: Config) -> io::Result { + // Listen on the provided socket address + let addr = config.addr.parse().unwrap(); + let mut socket = mio::net::UdpSocket::bind(addr).unwrap(); + + // Setup the event loop. + let poll = mio::Poll::new().unwrap(); + let events = mio::Events::with_capacity(1024); + let sessions = session::Map::new(); + + poll.registry().register( + &mut socket, + mio::Token(0), + mio::Interest::READABLE, + ).unwrap(); + + // Generate random values for connection IDs. + let rng = ring::rand::SystemRandom::new(); + let seed = ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap(); + + // Create the configuration for the QUIC connections. + let mut quic = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap(); + quic.load_cert_chain_from_pem_file(&config.cert).unwrap(); + quic.load_priv_key_from_pem_file(&config.key).unwrap(); + quic.set_application_protos(quiche::h3::APPLICATION_PROTOCOL).unwrap(); + quic.set_max_idle_timeout(5000); + quic.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE); + quic.set_max_send_udp_payload_size(MAX_DATAGRAM_SIZE); + quic.set_initial_max_data(10_000_000); + quic.set_initial_max_stream_data_bidi_local(1_000_000); + quic.set_initial_max_stream_data_bidi_remote(1_000_000); + quic.set_initial_max_stream_data_uni(1_000_000); + quic.set_initial_max_streams_bidi(100); + quic.set_initial_max_streams_uni(100); + quic.set_disable_active_migration(true); + quic.enable_early_data(); + quic.enable_dgram(true, 65536, 65536); + + Ok(Server { + socket, + poll, + events, + + quic, + sessions, + seed + }) + } + + pub fn poll(&mut self) -> io::Result<()> { + self.receive().unwrap(); + self.send().unwrap(); + self.cleanup().unwrap(); + + Ok(()) + } + + fn receive(&mut self) -> io::Result<()> { + // Find the shorter timeout from all the active connections. + // + // TODO: use event loop that properly supports timers + let timeout = self.sessions.values().filter_map(|c| c.conn.timeout()).min(); + + self.poll.poll(&mut self.events, timeout).unwrap(); + + // If the event loop reported no events, it means that the timeout + // has expired, so handle it without attempting to read packets. We + // will then proceed with the send loop. + if self.events.is_empty() { + self.sessions.values_mut().for_each(|session| { + session.conn.on_timeout() + }); + + return Ok(()) + } + + // Read incoming UDP packets from the socket and feed them to quiche, + // until there are no more packets to read. + loop { + match self.receive_once() { + Err(Error::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => return Ok(()), + Err(e) => log::error!("{:?}", e), + Ok(_) => (), + } + } + } + + fn receive_once(&mut self) -> Result<()> { + let mut src= [0; MAX_DATAGRAM_SIZE]; + + let (len, from) = self.socket.recv_from(&mut src).unwrap(); + let src = &mut src[..len]; + + let info = quiche::RecvInfo { + to: self.socket.local_addr().unwrap(), + from, + }; + + // Lookup a connection based on the packet's connection ID. If there + // is no connection matching, create a new one. + let pair = match self.accept(src, from).unwrap() { + Some(v) => v, + None => return Ok(()), + }; + + let conn = &mut pair.conn; + + // Process potentially coalesced packets. + conn.recv(src, info).unwrap(); + + // Create a new HTTP/3 connection as soon as the QUIC connection + // is established. + if (conn.is_in_early_data() || conn.is_established()) && pair.session.is_none() { + let session = webtransport::ServerSession::with_transport(conn).unwrap(); + pair.session = Some(session); + } + + // The `poll` can pull out the events that occurred according to the data passed here. + for (_, session) in self.sessions.iter_mut() { + session.poll().unwrap(); + } + + Ok(()) + } + + fn accept(&mut self, src: &mut [u8], from: net::SocketAddr) -> error::Result> { + // Parse the QUIC packet's header. + let hdr = quiche::Header::from_slice(src, quiche::MAX_CONN_ID_LEN).unwrap(); + + let conn_id = ring::hmac::sign(&self.seed, &hdr.dcid); + let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN]; + let conn_id = conn_id.to_vec().into(); + + if self.sessions.contains_key(&hdr.dcid) { + let pair = self.sessions.get_mut(&hdr.dcid).unwrap(); + return Ok(Some(pair)) + } else if self.sessions.contains_key(&conn_id) { + let pair = self.sessions.get_mut(&conn_id).unwrap(); + return Ok(Some(pair)); + } + + if hdr.ty != quiche::Type::Initial { + return Err(error::Server::UnknownConnectionID.into()) + } + + let mut dst = [0; MAX_DATAGRAM_SIZE]; + + if !quiche::version_is_supported(hdr.version) { + let len = quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut dst).unwrap(); + let dst= &dst[..len]; + + self.socket.send_to(dst, from).unwrap(); + return Ok(None) + } + + let mut scid = [0; quiche::MAX_CONN_ID_LEN]; + scid.copy_from_slice(&conn_id); + + let scid = quiche::ConnectionId::from_ref(&scid); + + // Token is always present in Initial packets. + let token = hdr.token.as_ref().unwrap(); + + // Do stateless retry if the client didn't send a token. + if token.is_empty() { + let new_token = mint_token(&hdr, &from); + + let len = quiche::retry( + &hdr.scid, + &hdr.dcid, + &scid, + &new_token, + hdr.version, + &mut dst, + ) + .unwrap(); + + let dst= &dst[..len]; + + self.socket.send_to(dst, from).unwrap(); + return Ok(None) + } + + let odcid = validate_token(&from, token); + + // The token was not valid, meaning the retry failed, so + // drop the packet. + if odcid.is_none() { + return Err(error::Server::InvalidToken.into()) + } + + if scid.len() != hdr.dcid.len() { + return Err(error::Server::InvalidConnectionID.into()) + } + + // Reuse the source connection ID we sent in the Retry packet, + // instead of changing it again. + let conn_id= hdr.dcid.clone(); + let local_addr = self.socket.local_addr().unwrap(); + + let conn = + quiche::accept(&conn_id, odcid.as_ref(), local_addr, from, &mut self.quic) + .unwrap(); + + self.sessions.insert( + conn_id.clone(), + Session { + conn, + session: None, + }, + ); + + let pair = self.sessions.get_mut(&conn_id).unwrap(); + Ok(Some(pair)) + } + + fn send(&mut self) -> io::Result<()> { + let mut pkt = [0; MAX_DATAGRAM_SIZE]; + + // Generate outgoing QUIC packets for all active connections and send + // them on the UDP socket, until quiche reports that there are no more + // packets to be sent. + for session in self.sessions.values_mut() { + loop { + let (size , info) = session.conn.send(&mut pkt).unwrap(); + let pkt = &pkt[..size]; + + match self.socket.send_to(&pkt, info.to) { + Err(err) if err.kind() == io::ErrorKind::WouldBlock => break, + Err(err) => return Err(err), + Ok(_) => (), + } + } + } + + Ok(()) + } + + fn cleanup(&mut self) -> io::Result<()> { + // Garbage collect closed connections. + self.sessions.retain(|_, session| !session.conn.is_closed() ); + Ok(()) + } +} + +/// Generate a stateless retry token. +/// +/// The token includes the static string `"quiche"` followed by the IP address +/// of the client and by the original destination connection ID generated by the +/// client. +/// +/// Note that this function is only an example and doesn't do any cryptographic +/// authenticate of the token. *It should not be used in production system*. +fn mint_token(hdr: &quiche::Header, src: &std::net::SocketAddr) -> Vec { + let mut token = Vec::new(); + + token.extend_from_slice(b"quiche"); + + let addr = match src.ip() { + std::net::IpAddr::V4(a) => a.octets().to_vec(), + std::net::IpAddr::V6(a) => a.octets().to_vec(), + }; + + token.extend_from_slice(&addr); + token.extend_from_slice(&hdr.dcid); + + token +} + +/// Validates a stateless retry token. +/// +/// This checks that the ticket includes the `"quiche"` static string, and that +/// the client IP address matches the address stored in the ticket. +/// +/// Note that this function is only an example and doesn't do any cryptographic +/// authenticate of the token. *It should not be used in production system*. +fn validate_token<'a>( + src: &std::net::SocketAddr, token: &'a [u8], +) -> Option> { + if token.len() < 6 { + return None; + } + + if &token[..6] != b"quiche" { + return None; + } + + let token = &token[6..]; + + let addr = match src.ip() { + std::net::IpAddr::V4(a) => a.octets().to_vec(), + std::net::IpAddr::V6(a) => a.octets().to_vec(), + }; + + if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() { + return None; + } + + Some(quiche::ConnectionId::from_ref(&token[addr.len()..])) +} \ No newline at end of file diff --git a/server/src/session.rs b/server/src/session.rs new file mode 100644 index 0000000..e06bcfa --- /dev/null +++ b/server/src/session.rs @@ -0,0 +1,104 @@ +use crate::error; +use error::Result; + +use std::collections::HashMap; +use quiche::h3::webtransport; + +pub struct Session { + pub conn: quiche::Connection, + pub session: Option, +} + +pub type Map = HashMap, Session>; + +impl Session { + // Process any updates to a session. + pub fn poll(&mut self) -> Result<()> { + let session = match &mut self.session { + Some(s) => s, + None => return Ok(()), + }; + + loop { + let event = match session.poll(&mut self.conn) { + Err(webtransport::Error::Done) => return Ok(()), + Err(e) => return Err(e.into()), + Ok(e) => e, + }; + + match event { + webtransport::ServerEvent::ConnectRequest(_req) => { + // you can handle request with + // req.authority() + // req.path() + // and you can validate this request with req.origin() + session.accept_connect_request(&mut self.conn, None).unwrap(); + }, + webtransport::ServerEvent::StreamData(stream_id) => { + let mut buf = vec![0; 10000]; + while let Ok(len) = + session.recv_stream_data(&mut self.conn, stream_id, &mut buf) + { + let stream_data = &buf[0..len]; + + // handle stream_data + if (stream_id & 0x2) == 0 { + // bidirectional stream + // you can send data through this stream. + session + .send_stream_data(&mut self.conn, stream_id, stream_data) + .unwrap(); + } else { + // you cannot send data through client-initiated-unidirectional-stream. + // so, open new server-initiated-unidirectional-stream, and send data + // through it. + let new_stream_id = + session.open_stream(&mut self.conn, false).unwrap(); + session + .send_stream_data(&mut self.conn, new_stream_id, stream_data) + .unwrap(); + } + } + } + + webtransport::ServerEvent::StreamFinished(_stream_id) => { + // A WebTrnasport stream finished, handle it. + } + + webtransport::ServerEvent::Datagram => { + let mut buf = vec![0; 1500]; + while let Ok((in_session, offset, total)) = + session.recv_dgram(&mut self.conn, &mut buf) + { + if in_session { + let dgram = &buf[offset..total]; + dbg!(std::string::String::from_utf8_lossy(dgram)); + // handle this dgram + + // for instance, you can write echo-server like following + session.send_dgram(&mut self.conn, dgram).unwrap(); + } else { + // this dgram is not related to current WebTransport session. ignore. + } + } + } + + webtransport::ServerEvent::SessionReset(_e) => { + // Peer reset session stream, handle it. + } + + webtransport::ServerEvent::SessionFinished => { + // Peer finish session stream, handle it. + } + + webtransport::ServerEvent::SessionGoAway => { + // Peer signalled it is going away, handle it. + } + + webtransport::ServerEvent::Other(_stream_id, _event) => { + // Original h3::Event which is not related to WebTransport. + } + } + } + } +} \ No newline at end of file