diff --git a/Cargo.lock b/Cargo.lock index e121b146..6b23e212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -62,6 +62,45 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.17", + "time 0.3.45", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -118,9 +157,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ - "getrandom", + "getrandom 0.2.17", "instant", - "rand", + "rand 0.8.5", ] [[package]] @@ -152,9 +191,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "block-buffer" @@ -259,11 +298,14 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.71" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ + "find-msvc-tools", "jobserver", + "libc", + "shlex", ] [[package]] @@ -272,6 +314,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.23" @@ -282,7 +330,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.43", "wasm-bindgen", "winapi", ] @@ -374,11 +422,21 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "coveralls-api" @@ -511,7 +569,7 @@ checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", - "openssl-probe", + "openssl-probe 0.1.4", "openssl-sys", "schannel", "socket2 0.4.10", @@ -605,6 +663,29 @@ dependencies = [ "gzip-header", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -683,6 +764,12 @@ dependencies = [ "instant", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + [[package]] name = "flate2" version = "1.0.25" @@ -824,13 +911,29 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "r-efi", + "wasip2", + "wasm-bindgen", ] [[package]] @@ -860,7 +963,7 @@ dependencies = [ "libc", "libgit2-sys", "log", - "openssl-probe", + "openssl-probe 0.1.4", "openssl-sys", "url", ] @@ -932,6 +1035,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + [[package]] name = "http" version = "1.1.0" @@ -1282,19 +1391,21 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1306,9 +1417,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libgit2-sys" @@ -1392,6 +1503,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "matchers" version = "0.0.1" @@ -1438,6 +1555,12 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1464,7 +1587,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -1477,10 +1600,10 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.4", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.9.1", "security-framework-sys", "tempfile", ] @@ -1498,6 +1621,33 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -1545,11 +1695,26 @@ dependencies = [ "memchr", ] +[[package]] +name = "octets" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba5d98c13e549f6f26f7d70808688ab85c6b42eb350ba2524d96c24ebde27e9" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" -version = "1.18.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -1563,7 +1728,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.10.0", "cfg-if", "foreign-types", "libc", @@ -1589,6 +1754,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.103" @@ -1624,6 +1795,16 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1696,6 +1877,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.14" @@ -1735,6 +1922,61 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.5", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.5", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.35" @@ -1744,6 +1986,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -1751,8 +1999,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1762,7 +2020,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1771,7 +2039,16 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom", + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -1799,6 +2076,19 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time 0.3.45", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1888,6 +2178,20 @@ dependencies = [ "winreg", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rust_engineio" version = "0.6.0" @@ -1905,11 +2209,12 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", + "thiserror 1.0.63", "tokio", "tokio-tungstenite", "tungstenite", "url", + "wtransport", ] [[package]] @@ -1925,12 +2230,12 @@ dependencies = [ "futures-util", "log", "native-tls", - "rand", + "rand 0.8.5", "rust_engineio", "serde", "serde_json", "serial_test", - "thiserror", + "thiserror 1.0.63", "tokio", "url", ] @@ -1941,6 +2246,12 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1950,6 +2261,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.37.25" @@ -1970,28 +2290,74 @@ version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.3", "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.0", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -2037,7 +2403,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.3", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2045,9 +2424,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -2064,18 +2443,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2142,6 +2531,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -2151,11 +2551,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -2195,6 +2601,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.107" @@ -2241,7 +2653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.3", "system-configuration-sys", ] @@ -2293,7 +2705,16 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.63", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -2307,6 +2728,17 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -2326,6 +2758,37 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" + +[[package]] +name = "time-macros" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -2346,6 +2809,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -2456,9 +2934,21 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -2518,9 +3008,9 @@ dependencies = [ "httparse", "log", "native-tls", - "rand", + "rand 0.8.5", "sha1", - "thiserror", + "thiserror 1.0.63", "url", "utf-8", ] @@ -2533,9 +3023,9 @@ checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-width" @@ -2543,6 +3033,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.4" @@ -2617,12 +3113,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2630,27 +3120,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "wasm-bindgen" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ - "bumpalo", - "log", + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.89", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -2668,9 +3155,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2678,22 +3165,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn 2.0.89", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" @@ -2718,6 +3208,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2898,6 +3398,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "write16" version = "1.0.0" @@ -2910,6 +3416,69 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wtransport" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5e745c8789c20095c9061d292098d4106660efe2d172efd8ae7a369fe28e3e" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "sha2", + "socket2 0.5.5", + "thiserror 2.0.17", + "time 0.3.45", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser", +] + +[[package]] +name = "wtransport-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a09d89a8dba201c2439d9d5eca55a0faa08909d69da50decdb5ec00be0ac504" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.17", + "time 0.3.45", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time 0.3.45", +] + [[package]] name = "yoke" version = "0.7.5" @@ -2955,6 +3524,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/engineio/Cargo.toml b/engineio/Cargo.toml index b38c5a6c..35c406c9 100644 --- a/engineio/Cargo.toml +++ b/engineio/Cargo.toml @@ -30,6 +30,8 @@ async-stream = "0.3.6" thiserror = "1.0" native-tls = "0.2.12" url = "2.5.4" +# WebTransport support (optional) +wtransport = { version = "0.6", optional = true, features = ["dangerous-configuration"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["async_tokio"] } @@ -54,3 +56,4 @@ bench = false default = ["async"] async-callbacks = [] async = ["async-callbacks"] +webtransport = ["wtransport"] diff --git a/engineio/src/asynchronous/async_transports/mod.rs b/engineio/src/asynchronous/async_transports/mod.rs index 725b22c6..db435c62 100644 --- a/engineio/src/asynchronous/async_transports/mod.rs +++ b/engineio/src/asynchronous/async_transports/mod.rs @@ -2,7 +2,11 @@ mod polling; mod websocket; mod websocket_general; mod websocket_secure; +#[cfg(feature = "webtransport")] +mod webtransport; pub use self::polling::PollingTransport; pub use self::websocket::WebsocketTransport; pub use self::websocket_secure::WebsocketSecureTransport; +#[cfg(feature = "webtransport")] +pub use self::webtransport::WebTransportTransport; diff --git a/engineio/src/asynchronous/async_transports/webtransport.rs b/engineio/src/asynchronous/async_transports/webtransport.rs new file mode 100644 index 00000000..1979e524 --- /dev/null +++ b/engineio/src/asynchronous/async_transports/webtransport.rs @@ -0,0 +1,315 @@ +//! WebTransport transport implementation for Engine.IO +//! +//! This module provides WebTransport support as an alternative to WebSocket +//! for Engine.IO connections. WebTransport offers lower latency and supports +//! both reliable streams and unreliable datagrams over HTTP/3. + +use std::fmt::Debug; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Poll; + +use crate::asynchronous::transport::AsyncTransport; +use crate::error::Result; +use crate::{Error, Packet, PacketId}; +use async_stream::try_stream; +use async_trait::async_trait; +use base64::Engine as Base64Engine; +use bytes::{BufMut, Bytes, BytesMut}; +use futures_util::{ready, FutureExt, Stream}; +use http::HeaderMap; +use tokio::sync::{Mutex, RwLock}; +use url::Url; +use wtransport::{ClientConfig, Endpoint, Connection, RecvStream, SendStream}; + +/// Internal generator type for the receive stream +type StreamGenerator = Pin> + Send + Sync>>; + +/// An asynchronous WebTransport transport type. +/// Uses HTTP/3 with QUIC for low-latency bidirectional communication. +pub struct WebTransportTransport { + connection: Arc, + sender: Arc>, + receiver: Arc>, + generator: Arc>, + base_url: Arc>, +} + +/// Creates a stream generator from a RecvStream +fn create_stream_generator(receiver: Arc>) -> StreamGenerator { + Box::pin(try_stream! { + loop { + let mut guard = receiver.lock().await; + let mut buf = vec![0u8; 4096]; + match guard.read(&mut buf).await { + Ok(Some(n)) if n > 0 => { + buf.truncate(n); + yield Bytes::from(buf); + } + Ok(_) => break, + Err(e) => { + Err(e)?; + break; + } + } + } + }) +} + +impl WebTransportTransport { + /// Creates a new WebTransport connection to the given URL. + /// + /// # Arguments + /// * `base_url` - The URL to connect to (https:// scheme) + /// * `headers` - Optional HTTP headers (note: WebTransport has limited header support) + pub async fn new(base_url: Url, _headers: Option) -> Result { + let mut url = base_url; + url.query_pairs_mut().append_pair("transport", "webtransport"); + + // Ensure HTTPS scheme for WebTransport + if url.scheme() != "https" { + url.set_scheme("https").map_err(|_| Error::InvalidUrlScheme("WebTransport requires https".to_string()))?; + } + + // Create WebTransport client config + // For development, we skip certificate verification + // In production, proper certificate validation should be used + let config = ClientConfig::builder() + .with_bind_default() + .with_no_cert_validation() + .build(); + + let endpoint = Endpoint::client(config)?; + + // Connect to the WebTransport server + let connection = endpoint.connect(&url.to_string()).await?; + + // Open a bidirectional stream for Engine.IO communication + let (send_stream, recv_stream) = connection.open_bi().await?.await?; + + let receiver = Arc::new(Mutex::new(recv_stream)); + let generator = create_stream_generator(Arc::clone(&receiver)); + + Ok(WebTransportTransport { + connection: Arc::new(connection), + sender: Arc::new(Mutex::new(send_stream)), + receiver, + generator: Arc::new(Mutex::new(generator)), + base_url: Arc::new(RwLock::new(url)), + }) + } + + /// Creates a new WebTransport connection with custom TLS configuration. + /// + /// # Arguments + /// * `base_url` - The URL to connect to + /// * `config` - Custom WebTransport client configuration + pub async fn with_config(base_url: Url, config: ClientConfig) -> Result { + let mut url = base_url; + url.query_pairs_mut().append_pair("transport", "webtransport"); + + if url.scheme() != "https" { + url.set_scheme("https").map_err(|_| Error::InvalidUrlScheme("WebTransport requires https".to_string()))?; + } + + let endpoint = Endpoint::client(config)?; + let connection = endpoint.connect(&url.to_string()).await?; + let (send_stream, recv_stream) = connection.open_bi().await?.await?; + + let receiver = Arc::new(Mutex::new(recv_stream)); + let generator = create_stream_generator(Arc::clone(&receiver)); + + Ok(WebTransportTransport { + connection: Arc::new(connection), + sender: Arc::new(Mutex::new(send_stream)), + receiver, + generator: Arc::new(Mutex::new(generator)), + base_url: Arc::new(RwLock::new(url)), + }) + } + + /// Sends probe packet to ensure connection is valid, then sends upgrade request. + /// Used when upgrading from polling to WebTransport. + pub(crate) async fn upgrade(&self) -> Result<()> { + let mut sender = self.sender.lock().await; + let mut receiver = self.receiver.lock().await; + + // Send probe packet + let probe_packet = Packet::new(PacketId::Ping, Bytes::from("probe")); + let probe_bytes = Bytes::from(probe_packet); + sender.write_all(&probe_bytes).await?; + + // Read response + let mut buf = vec![0u8; 1024]; + let n = receiver.read(&mut buf).await?.ok_or(Error::IncompletePacket())?; + let response = Bytes::copy_from_slice(&buf[..n]); + + // Verify probe response + let expected = Bytes::from(Packet::new(PacketId::Pong, Bytes::from("probe"))); + if response != expected { + return Err(Error::InvalidPacket()); + } + + // Send upgrade packet + let upgrade_packet = Packet::new(PacketId::Upgrade, Bytes::from("")); + let upgrade_bytes = Bytes::from(upgrade_packet); + sender.write_all(&upgrade_bytes).await?; + + Ok(()) + } + + /// Polls for the next message from the WebTransport stream. + pub(crate) async fn poll_next(&self) -> Result> { + let mut receiver = self.receiver.lock().await; + + // Read into a buffer + let mut buf = vec![0u8; 4096]; + match receiver.read(&mut buf).await? { + Some(n) if n > 0 => { + buf.truncate(n); + Ok(Some(Bytes::from(buf))) + } + _ => Ok(None), + } + } + + /// Gets the underlying connection for advanced operations like datagrams. + pub fn connection(&self) -> &Connection { + &self.connection + } + + /// Sends an unreliable datagram (for audio/video streaming). + /// Datagrams may be lost but have lower latency than streams. + pub async fn send_datagram(&self, data: &[u8]) -> Result<()> { + self.connection.send_datagram(data)?; + Ok(()) + } + + /// Receives an unreliable datagram. + pub async fn receive_datagram(&self) -> Result { + let datagram = self.connection.receive_datagram().await?; + Ok(Bytes::from(datagram.to_vec())) + } +} + +#[async_trait] +impl AsyncTransport for WebTransportTransport { + async fn emit(&self, data: Bytes, is_binary_att: bool) -> Result<()> { + let mut sender = self.sender.lock().await; + + let message = if is_binary_att { + // For binary attachments, prefix with 'b' and base64 encode + let encoded = base64::engine::general_purpose::STANDARD.encode(&data); + let mut msg = BytesMut::with_capacity(encoded.len() + 1); + msg.put_u8(b'b'); + msg.put(encoded.as_bytes()); + msg.freeze() + } else { + data + }; + + sender.write_all(&message).await?; + Ok(()) + } + + async fn base_url(&self) -> Result { + Ok(self.base_url.read().await.clone()) + } + + async fn set_base_url(&self, base_url: Url) -> Result<()> { + let mut url = base_url; + if !url + .query_pairs() + .any(|(k, v)| k == "transport" && v == "webtransport") + { + url.query_pairs_mut().append_pair("transport", "webtransport"); + } + if url.scheme() != "https" { + url.set_scheme("https").map_err(|_| Error::InvalidUrlScheme("WebTransport requires https".to_string()))?; + } + *self.base_url.write().await = url; + Ok(()) + } +} + +impl Stream for WebTransportTransport { + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + // Lock the generator and poll it + let mut lock = ready!(Box::pin(self.generator.lock()).poll_unpin(cx)); + lock.as_mut().poll_next(cx) + } +} + +impl Clone for WebTransportTransport { + fn clone(&self) -> Self { + // Create a new generator from the shared receiver + let generator = create_stream_generator(Arc::clone(&self.receiver)); + WebTransportTransport { + connection: Arc::clone(&self.connection), + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + generator: Arc::new(Mutex::new(generator)), + base_url: Arc::clone(&self.base_url), + } + } +} + +impl Debug for WebTransportTransport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WebTransportTransport") + .field( + "base_url", + &self + .base_url + .try_read() + .map_or("Currently not available".to_owned(), |url| url.to_string()), + ) + .finish() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::ENGINE_IO_VERSION; + use std::str::FromStr; + + // Note: These tests require a WebTransport-capable server + // The test server should support the Engine.IO protocol over WebTransport + + #[tokio::test] + #[ignore] // Requires WebTransport server + async fn webtransport_transport_base_url() -> Result<()> { + let url = "https://localhost:4433/engine.io/?EIO=".to_string() + + &ENGINE_IO_VERSION.to_string(); + let transport = WebTransportTransport::new(Url::from_str(&url)?, None).await?; + + let base = transport.base_url().await?; + assert!(base.query_pairs().any(|(k, v)| k == "transport" && v == "webtransport")); + assert_eq!(base.scheme(), "https"); + + Ok(()) + } + + #[tokio::test] + #[ignore] // Requires WebTransport server + async fn webtransport_set_base_url() -> Result<()> { + let url = "https://localhost:4433/engine.io/?EIO=".to_string() + + &ENGINE_IO_VERSION.to_string(); + let transport = WebTransportTransport::new(Url::from_str(&url)?, None).await?; + + transport.set_base_url(Url::parse("http://127.0.0.1")?).await?; + let base = transport.base_url().await?; + + // Should have been converted to https + assert_eq!(base.scheme(), "https"); + assert!(base.query_pairs().any(|(k, v)| k == "transport" && v == "webtransport")); + + Ok(()) + } +} diff --git a/engineio/src/asynchronous/client/builder.rs b/engineio/src/asynchronous/client/builder.rs index 50dc9be2..d89d02dc 100644 --- a/engineio/src/asynchronous/client/builder.rs +++ b/engineio/src/asynchronous/client/builder.rs @@ -10,6 +10,8 @@ use crate::{ packet::HandshakePacket, Error, Packet, ENGINE_IO_VERSION, }; +#[cfg(feature = "webtransport")] +use crate::asynchronous::async_transports::WebTransportTransport; use bytes::Bytes; use futures_util::{future::BoxFuture, StreamExt}; use native_tls::TlsConnector; @@ -287,4 +289,73 @@ impl ClientBuilder { .iter() .any(|upgrade| upgrade.to_lowercase() == *"websocket")) } + + /// Checks the handshake to see if WebTransport upgrades are allowed + #[cfg(feature = "webtransport")] + fn webtransport_upgrade(&mut self) -> Result { + if self.handshake.is_none() { + return Ok(false); + } + + Ok(self + .handshake + .as_ref() + .unwrap() + .upgrades + .iter() + .any(|upgrade| upgrade.to_lowercase() == *"webtransport")) + } + + /// Build socket with a WebTransport transport + /// + /// WebTransport provides lower latency than WebSocket by using HTTP/3 + QUIC. + /// This method creates a new WebTransport connection and performs the handshake. + /// + /// Note: The URL scheme should be "https" for WebTransport connections. + #[cfg(feature = "webtransport")] + pub async fn build_webtransport(mut self) -> Result { + let headers = if let Some(map) = self.headers.clone() { + Some(map.try_into()?) + } else { + None + }; + + // WebTransport requires HTTPS + if self.url.scheme() != "https" { + return Err(Error::InvalidUrlScheme( + "WebTransport requires https scheme".to_string(), + )); + } + + let mut transport = WebTransportTransport::new(self.url.clone(), headers).await?; + + if self.handshake.is_some() { + transport.upgrade().await?; + } else { + self.handshake_with_transport(&mut transport).await?; + } + + // SAFETY: handshake function called previously. + Ok(Client::new(InnerSocket::new( + transport.into(), + self.handshake.unwrap(), + self.on_close, + self.on_data, + self.on_error, + self.on_open, + self.on_packet, + ))) + } + + /// Build socket with WebTransport if allowed by handshake, otherwise fall back to websocket + #[cfg(feature = "webtransport")] + pub async fn build_webtransport_with_upgrade(mut self) -> Result { + self.handshake().await?; + + if self.webtransport_upgrade()? { + self.build_webtransport().await + } else { + Err(Error::IllegalWebTransportUpgrade()) + } + } } diff --git a/engineio/src/asynchronous/transport.rs b/engineio/src/asynchronous/transport.rs index 7d33376f..929c882d 100644 --- a/engineio/src/asynchronous/transport.rs +++ b/engineio/src/asynchronous/transport.rs @@ -7,6 +7,8 @@ use std::{pin::Pin, time::SystemTime}; use url::Url; use super::async_transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport}; +#[cfg(feature = "webtransport")] +use super::async_transports::WebTransportTransport; #[async_trait] pub trait AsyncTransport: Stream> + Unpin { @@ -39,6 +41,8 @@ pub enum AsyncTransportType { Polling(PollingTransport), Websocket(WebsocketTransport), WebsocketSecure(WebsocketSecureTransport), + #[cfg(feature = "webtransport")] + WebTransport(WebTransportTransport), } impl From for AsyncTransportType { @@ -59,6 +63,13 @@ impl From for AsyncTransportType { } } +#[cfg(feature = "webtransport")] +impl From for AsyncTransportType { + fn from(transport: WebTransportTransport) -> Self { + AsyncTransportType::WebTransport(transport) + } +} + #[cfg(feature = "async")] impl AsyncTransportType { pub fn as_transport(&self) -> &(dyn AsyncTransport + Send) { @@ -66,6 +77,8 @@ impl AsyncTransportType { AsyncTransportType::Polling(transport) => transport, AsyncTransportType::Websocket(transport) => transport, AsyncTransportType::WebsocketSecure(transport) => transport, + #[cfg(feature = "webtransport")] + AsyncTransportType::WebTransport(transport) => transport, } } @@ -74,6 +87,8 @@ impl AsyncTransportType { AsyncTransportType::Polling(transport) => Box::pin(transport), AsyncTransportType::Websocket(transport) => Box::pin(transport), AsyncTransportType::WebsocketSecure(transport) => Box::pin(transport), + #[cfg(feature = "webtransport")] + AsyncTransportType::WebTransport(transport) => Box::pin(transport), } } } diff --git a/engineio/src/error.rs b/engineio/src/error.rs index 440fe10a..83d3f3b6 100644 --- a/engineio/src/error.rs +++ b/engineio/src/error.rs @@ -6,6 +6,15 @@ use std::str::Utf8Error; use thiserror::Error; use tungstenite::Error as TungsteniteError; use url::ParseError as UrlParseError; +#[cfg(feature = "webtransport")] +use wtransport::error::{ + ConnectingError as WtConnectingError, + ConnectionError as WtConnectionError, + SendDatagramError as WtSendDatagramError, + StreamOpeningError as WtStreamOpeningError, + StreamReadError as WtStreamReadError, + StreamWriteError as WtStreamWriteError, +}; /// Enumeration of all possible errors in the `socket.io` context. #[derive(Error, Debug)] @@ -54,6 +63,27 @@ pub enum Error { InvalidHeaderValueFromReqwest(#[from] reqwest::header::InvalidHeaderValue), #[error("The server did not send a PING packet in time")] PingTimeout(), + #[cfg(feature = "webtransport")] + #[error("WebTransport connection error: {0}")] + WebTransportConnectionError(#[from] WtConnectionError), + #[cfg(feature = "webtransport")] + #[error("WebTransport connecting error: {0}")] + WebTransportConnectingError(#[from] WtConnectingError), + #[cfg(feature = "webtransport")] + #[error("WebTransport stream opening error: {0}")] + WebTransportStreamError(#[from] WtStreamOpeningError), + #[cfg(feature = "webtransport")] + #[error("WebTransport datagram send error: {0}")] + WebTransportDatagramError(#[from] WtSendDatagramError), + #[cfg(feature = "webtransport")] + #[error("Server did not allow upgrading to WebTransport")] + IllegalWebTransportUpgrade(), + #[cfg(feature = "webtransport")] + #[error("WebTransport stream read error: {0}")] + WebTransportStreamReadError(#[from] WtStreamReadError), + #[cfg(feature = "webtransport")] + #[error("WebTransport stream write error: {0}")] + WebTransportStreamWriteError(#[from] WtStreamWriteError), } pub(crate) type Result = std::result::Result;