From 1610b28e1b013f6c8f2cf3c8bbabc19697030af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Thu, 6 Aug 2026 20:40:50 -0300 Subject: [PATCH 01/13] feat: added TLS 1.2 support --- deboa-compio/Cargo.toml | 1 + .../src/client/http/conn/stream/tls/rustls.rs | 6 ++-- deboa-smol/Cargo.toml | 2 +- .../src/client/http/conn/stream/tls/rustls.rs | 6 ++-- deboa-tokio/Cargo.toml | 2 +- .../src/client/http/conn/stream/tls/rustls.rs | 6 ++-- deboa/Cargo.toml | 2 +- deboa/src/request.rs | 33 +++++++++++++++++++ 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/deboa-compio/Cargo.toml b/deboa-compio/Cargo.toml index 427015ef..d387b0b6 100644 --- a/deboa-compio/Cargo.toml +++ b/deboa-compio/Cargo.toml @@ -25,6 +25,7 @@ default = [ rust-tls = [ "compio-tls/rustls", + "rustls/tls12", "dep:rustls-native-certs", "dep:webpki-roots", ] diff --git a/deboa-compio/src/client/http/conn/stream/tls/rustls.rs b/deboa-compio/src/client/http/conn/stream/tls/rustls.rs index 6a2528ef..52b0a74e 100644 --- a/deboa-compio/src/client/http/conn/stream/tls/rustls.rs +++ b/deboa-compio/src/client/http/conn/stream/tls/rustls.rs @@ -72,7 +72,7 @@ pub(crate) fn setup_rust_tls( if skip_server_verification { use crate::client::http::conn::stream::tls::rustls::verify::SkipServerVerification; let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) @@ -83,7 +83,7 @@ pub(crate) fn setup_rust_tls( #[cfg(feature = "__webpki_rustls_verifier")] let config = { let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version"); let mut root_store = @@ -116,7 +116,7 @@ pub(crate) fn setup_rust_tls( use rustls_platform_verifier::Verifier; let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(Arc::new(verifier)) diff --git a/deboa-smol/Cargo.toml b/deboa-smol/Cargo.toml index b5112a87..5f474c40 100644 --- a/deboa-smol/Cargo.toml +++ b/deboa-smol/Cargo.toml @@ -34,7 +34,7 @@ default = [ # tls rust-tls = [ - "dep:rustls", + "rustls/tls12", "dep:rustls-native-certs", "dep:futures-rustls", "dep:rustls-pki-types", diff --git a/deboa-smol/src/client/http/conn/stream/tls/rustls.rs b/deboa-smol/src/client/http/conn/stream/tls/rustls.rs index a1718172..09fc80a7 100644 --- a/deboa-smol/src/client/http/conn/stream/tls/rustls.rs +++ b/deboa-smol/src/client/http/conn/stream/tls/rustls.rs @@ -68,7 +68,7 @@ pub(crate) fn setup_rust_tls<'a>( if skip_server_verification { use verify::SkipServerVerification; let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) @@ -79,7 +79,7 @@ pub(crate) fn setup_rust_tls<'a>( #[cfg(feature = "__webpki_rustls_verifier")] let config = { let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version"); let mut root_store = @@ -112,7 +112,7 @@ pub(crate) fn setup_rust_tls<'a>( use rustls_platform_verifier::Verifier; let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(Arc::new(verifier)) diff --git a/deboa-tokio/Cargo.toml b/deboa-tokio/Cargo.toml index 4b369923..d7a79f52 100644 --- a/deboa-tokio/Cargo.toml +++ b/deboa-tokio/Cargo.toml @@ -34,7 +34,7 @@ default = [ # tls rust-tls = [ - "dep:rustls", + "rustls/tls12", "dep:rustls-native-certs", "dep:tokio-rustls", "dep:rustls-pki-types", diff --git a/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs b/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs index a5714ce0..0c0f1a00 100644 --- a/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs +++ b/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs @@ -69,7 +69,7 @@ pub fn setup_rust_tls( if skip_server_verification { use verify::SkipServerVerification; let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(SkipServerVerification::new()) @@ -80,7 +80,7 @@ pub fn setup_rust_tls( #[cfg(feature = "__webpki_rustls_verifier")] let config = { let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version"); let mut root_store = @@ -113,7 +113,7 @@ pub fn setup_rust_tls( use rustls_platform_verifier::Verifier; let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(&[&rustls::version::TLS13]) + .with_protocol_versions(rustls::ALL_VERSIONS) .expect("Failed to set TLS version") .dangerous() .with_custom_certificate_verifier(Arc::new(verifier)) diff --git a/deboa/Cargo.toml b/deboa/Cargo.toml index 0773b11f..f47c8b52 100644 --- a/deboa/Cargo.toml +++ b/deboa/Cargo.toml @@ -47,7 +47,7 @@ url = "2.5.8" urlencoding = "2.1.3" [dev-dependencies] -caramelo = "0.1.1-beta.3" +caramelo = "0.1.2" criterion = { version = "0.8.2", features = [ "html_reports", "async", diff --git a/deboa/src/request.rs b/deboa/src/request.rs index 37e64133..e66b0053 100644 --- a/deboa/src/request.rs +++ b/deboa/src/request.rs @@ -1379,6 +1379,39 @@ impl DeboaRequest { Ok(DeboaRequest::from(url)?.method(Method::DELETE)) } + /// Create a request from parts and body. + /// + /// # Arguments + /// + /// * `parts` - The request parts. + /// * `body` - The request body. + /// + /// # Returns + /// + /// * `DeboaRequest` - The request. + /// + /// # Errors + /// + /// * `DeboaError` - If the request is invalid. + /// + #[inline] + pub fn from_parts(parts: http::request::Parts, body: HttpBody) -> Result { + let request = http::Request::from_parts(parts, body); + Ok(DeboaRequest { inner: request }) + } + + /// Convert the request into parts and body. + /// + /// # Returns + /// + /// * `(http::request::Parts, HttpBody)` - The request parts and body. + /// + #[inline] + pub fn into_parts(self) -> (http::request::Parts, HttpBody) { + self.inner + .into_parts() + } + /// Get request version at any time. /// /// # Returns From fec6b59cbf93273d2cd932bea7c7faef193cfff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Wed, 19 Aug 2026 14:07:03 -0300 Subject: [PATCH 02/13] refactor: moved TLS logic out of http traits --- Cargo.lock | 1153 ++--------------- Cargo.toml | 15 +- MIGRATION_GUIDE.md | 99 -- deboa-compio/Cargo.toml | 27 +- deboa-compio/src/cert.rs | 2 +- deboa-compio/src/client/http/conn/mod.rs | 162 ++- deboa-compio/src/client/http/conn/pool.rs | 42 +- .../src/client/http/conn/stream/mod.rs | 13 - .../src/client/http/conn/stream/plain.rs | 31 - .../src/client/http/conn/stream/tls/mod.rs | 11 - .../src/client/http/conn/stream/tls/native.rs | 87 -- .../src/client/http/conn/stream/tls/rustls.rs | 220 ---- deboa-compio/src/client/http/http1.rs | 53 +- deboa-compio/src/client/http/http2.rs | 52 +- deboa-compio/src/client/http/http3.rs | 122 +- deboa-compio/src/client/mod.rs | 1 + deboa-compio/src/client/tls/mod.rs | 9 + deboa-compio/src/client/tls/native.rs | 126 ++ deboa-compio/src/client/tls/rustls.rs | 320 +++++ deboa-compio/src/lib.rs | 32 +- deboa-compio/tests/base/get.rs | 19 +- .../src/client/http/conn/stream/plain.rs | 1 - .../src/client/http/conn/stream/tls/rustls.rs | 5 - deboa-glommio/src/client/http/http1.rs | 4 - deboa-glommio/src/client/http/http2.rs | 4 - deboa-h3/Cargo.toml | 4 +- deboa-h3/src/lib.rs | 1 + deboa-smol/Cargo.toml | 15 +- deboa-smol/src/cert.rs | 4 +- deboa-smol/src/client/http/conn/mod.rs | 197 ++- deboa-smol/src/client/http/conn/pool.rs | 40 +- deboa-smol/src/client/http/conn/stream/mod.rs | 13 - .../src/client/http/conn/stream/plain.rs | 27 - .../src/client/http/conn/stream/tls/mod.rs | 11 - .../src/client/http/conn/stream/tls/native.rs | 76 -- .../src/client/http/conn/stream/tls/rustls.rs | 213 --- deboa-smol/src/client/http/http1.rs | 54 +- deboa-smol/src/client/http/http2.rs | 54 +- deboa-smol/src/client/http/http3.rs | 127 +- deboa-smol/src/client/mod.rs | 1 + deboa-smol/src/client/tls/mod.rs | 9 + deboa-smol/src/client/tls/native.rs | 125 ++ deboa-smol/src/client/tls/rustls.rs | 326 +++++ deboa-smol/src/lib.rs | 29 +- deboa-smol/src/rt/stream.rs | 2 +- deboa-smol/tests/base/get.rs | 18 +- deboa-smol/tests/common/helpers.rs | 14 +- deboa-test-utils/src/base/get.rs | 6 +- deboa-tokio/Cargo.toml | 34 +- deboa-tokio/README.md | 2 +- deboa-tokio/src/cert.rs | 2 +- deboa-tokio/src/client/dns.rs | 4 + deboa-tokio/src/client/http/conn/mod.rs | 191 ++- deboa-tokio/src/client/http/conn/pool.rs | 58 +- .../src/client/http/conn/stream/mod.rs | 15 - .../src/client/http/conn/stream/plain.rs | 28 - .../src/client/http/conn/stream/tls/mod.rs | 27 - .../src/client/http/conn/stream/tls/native.rs | 75 -- .../src/client/http/conn/stream/tls/rustls.rs | 217 ---- deboa-tokio/src/client/http/http1.rs | 48 +- deboa-tokio/src/client/http/http2.rs | 49 +- deboa-tokio/src/client/http/http3.rs | 127 +- deboa-tokio/src/client/mod.rs | 8 +- deboa-tokio/src/client/tls/mod.rs | 9 + deboa-tokio/src/client/tls/native.rs | 125 ++ deboa-tokio/src/client/tls/rustls.rs | 327 +++++ deboa-tokio/src/lib.rs | 29 +- deboa-tokio/src/rt/stream.rs | 2 +- deboa-tokio/tests/base/get.rs | 34 +- deboa-tokio/tests/base/mod.rs | 1 + deboa-tokio/tests/common/helpers.rs | 14 +- deboa/Cargo.toml | 4 +- deboa/src/cert.rs | 2 +- deboa/src/conn.rs | 125 +- deboa/src/cookie.rs | 7 +- deboa/src/errors.rs | 28 +- deboa/src/lib.rs | 125 +- deboa/src/request.rs | 12 +- 78 files changed, 2423 insertions(+), 3282 deletions(-) delete mode 100644 MIGRATION_GUIDE.md delete mode 100644 deboa-compio/src/client/http/conn/stream/mod.rs delete mode 100644 deboa-compio/src/client/http/conn/stream/plain.rs delete mode 100644 deboa-compio/src/client/http/conn/stream/tls/mod.rs delete mode 100644 deboa-compio/src/client/http/conn/stream/tls/native.rs delete mode 100644 deboa-compio/src/client/http/conn/stream/tls/rustls.rs create mode 100644 deboa-compio/src/client/tls/mod.rs create mode 100644 deboa-compio/src/client/tls/native.rs create mode 100644 deboa-compio/src/client/tls/rustls.rs delete mode 100644 deboa-smol/src/client/http/conn/stream/mod.rs delete mode 100644 deboa-smol/src/client/http/conn/stream/plain.rs delete mode 100644 deboa-smol/src/client/http/conn/stream/tls/mod.rs delete mode 100644 deboa-smol/src/client/http/conn/stream/tls/native.rs delete mode 100644 deboa-smol/src/client/http/conn/stream/tls/rustls.rs create mode 100644 deboa-smol/src/client/tls/mod.rs create mode 100644 deboa-smol/src/client/tls/native.rs create mode 100644 deboa-smol/src/client/tls/rustls.rs delete mode 100644 deboa-tokio/src/client/http/conn/stream/mod.rs delete mode 100644 deboa-tokio/src/client/http/conn/stream/plain.rs delete mode 100644 deboa-tokio/src/client/http/conn/stream/tls/mod.rs delete mode 100644 deboa-tokio/src/client/http/conn/stream/tls/native.rs delete mode 100644 deboa-tokio/src/client/http/conn/stream/tls/rustls.rs create mode 100644 deboa-tokio/src/client/tls/mod.rs create mode 100644 deboa-tokio/src/client/tls/native.rs create mode 100644 deboa-tokio/src/client/tls/rustls.rs diff --git a/Cargo.lock b/Cargo.lock index bb2afcbd..704dc1c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,6 +159,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fn-stream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba0c4baf81a0d8ab31618ffa3ae29ceeb970a6d0d82f76130753462e39d0ea" +dependencies = [ + "futures-util", + "pin-project-lite", + "smallvec", +] + [[package]] name = "async-fs" version = "2.2.0" @@ -199,19 +210,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-native-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37dd6b179962fe4048a6f81d4c0d7ed419a21fdf49204b4c6b04971693358e79" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 2.0.20", - "tokio", - "url", -] - [[package]] name = "async-net" version = "2.0.0" @@ -279,9 +277,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "zeroize", @@ -289,9 +287,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -370,12 +368,6 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - [[package]] name = "byteorder" version = "1.5.0" @@ -415,9 +407,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.4.0" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "jobserver", @@ -482,7 +474,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -495,18 +486,6 @@ dependencies = [ "clap_lex", ] -[[package]] -name = "clap_derive" -version = "4.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 3.0.3", -] - [[package]] name = "clap_lex" version = "1.1.0" @@ -540,20 +519,16 @@ dependencies = [ [[package]] name = "compio" -version = "0.19.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5cf9e29d3c2d2a37078198795631b61d2b5c9cab68191e31526fe342d742135" +checksum = "c19d81c636b0f47aa50041d25b02d9d8289a3319727a8051209f71bea10e89d6" dependencies = [ "compio-buf", "compio-driver", "compio-fs", "compio-io", "compio-log", - "compio-macros", - "compio-net", "compio-runtime", - "compio-signal", - "compio-tls", ] [[package]] @@ -569,9 +544,9 @@ dependencies = [ [[package]] name = "compio-driver" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1f07e864382cbb51d601416dc41cb1dc99ad3a01c6745cece55e431ca547fe" +checksum = "293e8086a35f52b5002402937cf4e69b5e414917e511c29c5e7ba2cebe6ef7b3" dependencies = [ "bitflags 2.13.1", "cfg_aliases", @@ -581,9 +556,7 @@ dependencies = [ "crossbeam-queue", "flume", "futures-util", - "io-uring", "libc", - "linux-raw-sys", "mod_use", "once_cell", "pastey", @@ -598,9 +571,9 @@ dependencies = [ [[package]] name = "compio-executor" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2402a55af5e31454cd77d4b6044e2e6b8c30aa037655585ebd6505b97680468c" +checksum = "94961c5908b02bbb082e046968b8ee3d18995845a5c261376f0b0b5c95c87c2b" dependencies = [ "compio-log", "compio-send-wrapper", @@ -611,9 +584,9 @@ dependencies = [ [[package]] name = "compio-fs" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934c384c7c1dca1d68540bd0ef16186a8e7f33fab330e54652b45ee80f051ee4" +checksum = "8a374c5a03bdf7ecb42894a49c3a40c22f743f8d7dc39cc21a5dd3258d8219c7" dependencies = [ "cfg_aliases", "compio-buf", @@ -634,90 +607,27 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3a354e085c4046dd8d2d9d514cf8c15c615eb8adf6c5f41dbf7899420457e9b" dependencies = [ - "bytemuck", "compio-buf", "futures-util", - "libc", "pastey", "pin-project-lite", - "rustix", "synchrony", - "windows-sys 0.61.2", ] [[package]] name = "compio-log" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef39fff6341af7ab6c27fae9a3887e1ec618320d43f3b9c924c7a644f693a859" +checksum = "9dd867f29de59e4eff577dfeee3744cb0a264249f3faaf11945e8be79a3c3c62" dependencies = [ "tracing", ] -[[package]] -name = "compio-macros" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9ac573b3bb60fffabf47c2ce01c61536fb5eb2168d66e0375e85603e7d51614" -dependencies = [ - "darling", - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "compio-net" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "717d72ea6640bf0900ef9431fa770e0d120864bbf248cf15cb5da371cfdcff9a" -dependencies = [ - "compio-buf", - "compio-driver", - "compio-io", - "compio-runtime", - "either", - "futures-util", - "libc", - "once_cell", - "pin-project-lite", - "socket2", - "synchrony", - "widestring", - "windows-sys 0.61.2", -] - -[[package]] -name = "compio-quic" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6502920691530a27afb6e83d30177fe8b35e1548866af47931b7295413fadc79" -dependencies = [ - "cfg_aliases", - "compio-buf", - "compio-io", - "compio-log", - "compio-net", - "compio-runtime", - "flume", - "futures-util", - "h3", - "h3-datagram", - "libc", - "quinn-proto", - "rustc-hash", - "rustls", - "synchrony", - "thiserror 2.0.20", - "windows-sys 0.61.2", -] - [[package]] name = "compio-runtime" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af69014028390d92b7e37ee90036481fc1c5c4bff4dd0f6db5896a172734d3d6" +checksum = "20406f0a4e69cdd0c0a58c4a7cf51bcc965a543fb5da2fe931e970354e291ded" dependencies = [ "compio-buf", "compio-driver", @@ -743,34 +653,6 @@ dependencies = [ "loom", ] -[[package]] -name = "compio-signal" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1748b284601a5a267824b36894c7fc5e488b503f14a0a508f3b457ae018a451" -dependencies = [ - "nix 0.31.3", - "once_cell", - "slab", - "synchrony", - "windows-sys 0.61.2", -] - -[[package]] -name = "compio-tls" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177c2684878d0712ccfcf22113a198f5ddf74f0bf323801488dbb47f801a471" -dependencies = [ - "compio-buf", - "compio-io", - "futures-rustls", - "futures-util", - "native-tls", - "pin-project-lite", - "rustls", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -914,107 +796,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "cyper-core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c8847069e286c64987119637d5f08cdb71e12e85be0294fed649dc8007d32e" -dependencies = [ - "compio", - "futures-util", - "hyper", - "send_wrapper", -] - -[[package]] -name = "darling" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" -dependencies = [ - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.119", -] - -[[package]] -name = "darling_macro" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.119", -] - [[package]] name = "deboa" -version = "0.1.2" -dependencies = [ - "async-lock", - "base64", - "bytes", - "caramelo", - "cookie", - "criterion", - "futures", - "hashbrown 0.17.1", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-body-utils", - "hyper-util", - "indexmap", - "log", - "mime", - "minimime", - "multer", - "rand", - "regex", - "serde", - "tackle", - "thiserror 2.0.20", - "time", - "url", - "urlencoding", -] - -[[package]] -name = "deboa-compio" version = "0.1.3" dependencies = [ - "async-executor", "async-lock", "base64", "bytes", "caramelo", - "compio", - "compio-quic", - "compio-tls", "cookie", "criterion", - "cyper-core", - "deboa", - "deboa-h3", - "deboa-test-utils", - "easyhttpmock-vetis-compio", "futures", - "futures-util", - "h3", "hashbrown 0.17.1", "http", "http-body", @@ -1023,27 +815,18 @@ dependencies = [ "hyper-body-utils", "hyper-util", "indexmap", - "io-uring", "log", + "mime", "minimime", - "mockall", "multer", - "pin-project-lite", "rand", "regex", - "rstest", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "rustls-platform-verifier", - "send_wrapper", "serde", "tackle", "thiserror 2.0.20", "time", "url", "urlencoding", - "webpki-roots", ] [[package]] @@ -1126,19 +909,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "deboa-h3" -version = "0.1.1" -dependencies = [ - "bytes", - "compio-quic", - "deboa", - "h3", - "h3-quinn", - "http", - "hyper-body-utils", -] - [[package]] name = "deboa-macros" version = "0.1.0" @@ -1152,61 +922,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "deboa-smol" -version = "0.1.2" -dependencies = [ - "async-executor", - "async-lock", - "async-native-tls", - "base64", - "bytes", - "caramelo", - "cookie", - "criterion", - "deboa", - "deboa-h3", - "deboa-test-utils", - "easyhttpmock-vetis-smol", - "futures", - "futures-rustls", - "futures-util", - "h3", - "h3-quinn", - "hashbrown 0.17.1", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-body-utils", - "hyper-util", - "indexmap", - "log", - "macro_rules_attribute", - "minimime", - "mockall", - "multer", - "quinn", - "rand", - "regex", - "rstest", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "smol", - "smol-hyper", - "smol-macros", - "tackle", - "thiserror 2.0.20", - "time", - "url", - "urlencoding", - "webpki-roots", - "ws-framer", -] - [[package]] name = "deboa-test-utils" version = "0.1.0" @@ -1237,60 +952,6 @@ dependencies = [ "vetis", ] -[[package]] -name = "deboa-tokio" -version = "0.1.2" -dependencies = [ - "async-executor", - "async-lock", - "async-native-tls", - "base64", - "bytes", - "caramelo", - "cookie", - "criterion", - "deboa", - "deboa-h3", - "deboa-test-utils", - "easyhttpmock-vetis-tokio", - "futures", - "futures-rustls", - "futures-util", - "h3", - "h3-quinn", - "hashbrown 0.17.1", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-body-utils", - "hyper-util", - "indexmap", - "log", - "minimime", - "mockall", - "multer", - "quinn", - "rand", - "regex", - "rstest", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "tackle", - "thiserror 2.0.20", - "time", - "tokio", - "tokio-rustls", - "tokio-util", - "url", - "urlencoding", - "webpki-roots", - "ws-framer", -] - [[package]] name = "defmt" version = "1.1.1" @@ -1353,16 +1014,13 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "easyhttpmock" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "011017111116454614fb322b9e3b509017299a500ce15d8adb251dc9173cb91b" +checksum = "8978279c19ec0106d627e62568ebf17525bcdeb600ca4e8acdc16f037a9d95ec" dependencies = [ "bytes", "caramelo", "http", - "http-body-util", - "hyper", - "hyper-util", "jsonpath-rust", "once_cell", "rand", @@ -1375,59 +1033,11 @@ dependencies = [ "thiserror 2.0.20", ] -[[package]] -name = "easyhttpmock-vetis-compio" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db61a36f46e5941891a23eebcaab158fa54c5ec74a6dcf89d1d3a8962e2f9c59" -dependencies = [ - "caramelo", - "compio", - "easyhttpmock", - "http", - "http-body-util", - "rand", - "send_wrapper", - "vetis-compio", -] - -[[package]] -name = "easyhttpmock-vetis-smol" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00fd98229ee3aba909f5136035421e30cab1fccabe70a9ec6e25a33b2b4d8e81" -dependencies = [ - "caramelo", - "easyhttpmock", - "http", - "http-body-util", - "macro_rules_attribute", - "rand", - "smol", - "smol-macros", - "vetis-smol", -] - -[[package]] -name = "easyhttpmock-vetis-tokio" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be6d736454f17efcc242a1e4fdf5512d860a26053d2416d151e4c7927bd065a9" -dependencies = [ - "caramelo", - "easyhttpmock", - "http", - "http-body-util", - "rand", - "tokio", - "vetis-tokio", -] - [[package]] name = "either" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "enclose" @@ -1552,9 +1162,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flexbuffers" @@ -1602,21 +1212,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1686,7 +1281,6 @@ checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", - "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1709,17 +1303,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - [[package]] name = "futures-io" version = "0.3.34" @@ -1818,10 +1401,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1864,9 +1445,9 @@ checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "glommio-ng" -version = "0.10.0" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6c9b99577dfaf726e575122b026fef3b57e2c39f03ae042460fdb5be7a1cc8" +checksum = "f9461b07028859e56e12171f3fa2aa7a80b5eadab6864a3f1d25bac41499528e" dependencies = [ "ahash", "backtrace", @@ -1874,18 +1455,18 @@ dependencies = [ "bitflags 2.13.1", "bitmaps", "buddy-alloc", - "cc", "concurrent-queue", "crossbeam", "enclose", "flume", "futures-lite", + "glommio-ng-macros", "intrusive-collections", "io-uring", "lazy_static", "libc", "log", - "nix 0.30.1", + "nix", "pin-project-lite", "rlimit", "rustc_version", @@ -1899,11 +1480,22 @@ dependencies = [ "typenum", ] +[[package]] +name = "glommio-ng-macros" +version = "0.10.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0ed934b16e3b98b4eecd41c79cf1047207d3c06752544d81a43f8cd9037f36" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "granit-parser" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c92814c286f45b5ed1498ce9547bc9637b473eba7037f8aee6a4d5a3c1e051c" +checksum = "4ccd1be9ebf2bd5520dbfcfb72b70ef492f654061299a097056f3e73410e38ec" dependencies = [ "arraydeque", "smallvec", @@ -1911,9 +1503,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228" dependencies = [ "atomic-waker", "bytes", @@ -1928,45 +1520,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "h3" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" -dependencies = [ - "bytes", - "fastrand", - "futures-util", - "http", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "h3-datagram" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" -dependencies = [ - "bytes", - "h3", - "pin-project-lite", -] - -[[package]] -name = "h3-quinn" -version = "0.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" -dependencies = [ - "bytes", - "futures", - "h3", - "quinn", - "tokio", - "tokio-util", -] - [[package]] name = "half" version = "2.7.1" @@ -2010,12 +1563,6 @@ dependencies = [ "foldhash", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -2056,10 +1603,10 @@ dependencies = [ ] [[package]] -name = "http-serde" -version = "2.1.1" +name = "http-serde-ext" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f056c8559e3757392c8d091e796416e4649d8e49e88b8d76df6c002f05027fd" +checksum = "665c24b8e7e21688dc74edb228f07c1815bbc7ff3b48a3ee72fa20937fbde095" dependencies = [ "http", "serde", @@ -2101,15 +1648,14 @@ dependencies = [ [[package]] name = "hyper-body-utils" -version = "0.1.9" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bbcedb0160531e4026eaa915976bb1f86f724ac71e8a7df94e78679004a97a0" +checksum = "56483f9b44efa33654cf0b443557e231c85632197ada81442e9fe951b468a8e9" dependencies = [ + "async-fn-stream", "bytes", - "compio-quic", + "compio", "futures", - "h3", - "h3-quinn", "http", "http-body-util", "hyper", @@ -2216,12 +1762,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "1.1.0" @@ -2397,9 +1937,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -2431,15 +1971,6 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" -[[package]] -name = "libmimalloc-sys" -version = "0.1.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" -dependencies = [ - "cc", -] - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2448,15 +1979,15 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "local-event" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76dda8459b10a8960dfae91c1c77316fc15e7caf94f9f18794126512a8dc77e5" +checksum = "23ab4b951e96ffb2da6e25cec3d038d0e2930f4c406d1ec194b5120ae41ec6d3" [[package]] name = "lock_api" @@ -2489,12 +2020,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - [[package]] name = "macro_rules_attribute" version = "0.2.3" @@ -2544,15 +2069,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mimalloc" -version = "0.1.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" -dependencies = [ - "libmimalloc-sys", -] - [[package]] name = "mime" version = "0.3.17" @@ -2629,26 +2145,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21909324aa58f5c284d91cac514c6d081210901dc372d7c8ea6a9d7e0406097a" -[[package]] -name = "moka" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" -dependencies = [ - "async-lock", - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "event-listener", - "futures-util", - "parking_lot", - "portable-atomic", - "smallvec", - "tagptr", - "uuid", -] - [[package]] name = "multer" version = "3.1.0" @@ -2686,23 +2182,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nibble_vec" version = "0.1.0" @@ -2725,18 +2204,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2780,9 +2247,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -2844,49 +2311,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "page_size" version = "0.6.0" @@ -2903,29 +2333,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - [[package]] name = "paste" version = "1.0.15" @@ -2936,19 +2343,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" name = "pastey" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - -[[package]] -name = "peekable" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43fd2346a004ab7b18c468ad8f9554970a14872a701450155248ff5af73af53b" -dependencies = [ - "bytes", - "futures-util", - "pin-project-lite", - "tokio", -] +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" [[package]] name = "percent-encoding" @@ -2958,9 +2353,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" dependencies = [ "memchr", "ucd-trie", @@ -2968,9 +2363,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" dependencies = [ "pest", "pest_generator", @@ -2978,9 +2373,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" dependencies = [ "pest", "pest_meta", @@ -2991,9 +2386,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" dependencies = [ "pest", ] @@ -3023,9 +2418,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plotters" @@ -3071,9 +2466,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "portable-atomic-util" @@ -3086,9 +2481,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -3173,66 +2568,6 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "async-io", - "bytes", - "cfg_aliases", - "futures-io", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "smol", - "socket2", - "thiserror 2.0.20", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.20", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - [[package]] name = "quote" version = "1.0.47" @@ -3290,15 +2625,6 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core", -] - [[package]] name = "rayon" version = "1.12.0" @@ -3319,29 +2645,20 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.1", -] - [[package]] name = "ref-cast" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", @@ -3495,12 +2812,6 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - [[package]] name = "rustc_version" version = "0.4.1" @@ -3530,7 +2841,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", - "log", "once_cell", "ring", "rustls-pki-types", @@ -3557,7 +2867,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "web-time", "zeroize", ] @@ -3590,9 +2899,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "aws-lc-rs", "ring", @@ -3671,15 +2980,6 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" -dependencies = [ - "futures-core", -] - [[package]] name = "serde" version = "1.0.229" @@ -3692,9 +2992,9 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2c6921c8ce48fb1b052b457619d008834039d80325992ed3bc55bbe6c155ad" +checksum = "a1ec1f5cac0eb96063c64b28705255a7ed6e7d77f95c1d25e9f8b8c928006ce1" dependencies = [ "annotate-snippets", "base64", @@ -3909,19 +3209,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "smol-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfcaedb62e0475a6898988138995ec7b1e5d116167a72bb12c7b59d0649fbbc2" -dependencies = [ - "async-executor", - "async-io", - "async-lock", - "event-listener", - "futures-lite", -] - [[package]] name = "socket2" version = "0.6.5" @@ -3986,12 +3273,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" @@ -4060,25 +3341,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ed1c6888f08659f2071c6c8dcb312c8086cb3061be4c17bab42ccd49a22a5e0" -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "termtree" version = "0.5.1" @@ -4175,9 +3437,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -4227,33 +3489,10 @@ dependencies = [ "libc", "mio", "pin-project-lite", - "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.61.2", ] -[[package]] -name = "tokio-macros" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.3", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.19" @@ -4486,11 +3725,10 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ - "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -4545,12 +3783,6 @@ dependencies = [ "vamo", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -4559,142 +3791,28 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vetis" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a819a9e8b04d67ae7c84f473e857fdb65e8f0a9a7c39e510b877ec73452d1a" -dependencies = [ - "async-lock", - "bytes", - "futures-util", - "http", - "http-body-util", - "http-serde", - "hyper-body-utils", - "log", - "radix_trie", - "rand", - "serde", - "serde_yaml_ng", - "socket2", - "thiserror 2.0.20", - "time", - "typetag", - "url", -] - -[[package]] -name = "vetis-compio" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd264471b7f38719137dc259ccafdf8747eb0786ae4fa9a1f03b5bd410b44a0" -dependencies = [ - "async-lock", - "bytes", - "clap", - "compio", - "compio-tls", - "cyper-core", - "env_logger", - "futures-util", - "http", - "http-body-util", - "hyper", - "hyper-body-utils", - "hyper-util", - "log", - "radix_trie", - "rand", - "rustls", - "send_wrapper", - "serde", - "serde_yaml_ng", - "socket2", - "thiserror 2.0.20", - "time", - "typetag", - "url", - "vetis", -] - -[[package]] -name = "vetis-smol" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398c463fd61a9f8ae387e72e5e5d7ccf540c2b0d12c722dd50f7cea1b509eb76" -dependencies = [ - "async-lock", - "async-net", - "async-signal", - "bytes", - "clap", - "env_logger", - "futures-lite", - "futures-rustls", - "futures-util", - "http", - "http-body-util", - "hyper", - "hyper-body-utils", - "hyper-util", - "log", - "macro_rules_attribute", - "mimalloc", - "peekable", - "quinn", - "radix_trie", - "rand", - "rustls", - "serde", - "serde_yaml_ng", - "signal-hook", - "smol", - "smol-hyper", - "smol-macros", - "socket2", - "thiserror 2.0.20", - "time", - "typetag", - "url", - "vetis", -] - -[[package]] -name = "vetis-tokio" -version = "0.1.0" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998b6cc5a6afd7cf73642e492710b94b8f489a3cc00d0ebc9216de584ad55b9b" +checksum = "b408d1909a307d5849cb865f69bd73d44ec02d0035460a5365adfe37ab540fff" dependencies = [ "async-lock", "bytes", - "clap", - "env_logger", - "futures-rustls", "futures-util", "http", "http-body-util", + "http-serde-ext", "hyper", "hyper-body-utils", - "hyper-util", "log", - "mimalloc", - "moka", - "peekable", - "quinn", "radix_trie", "rand", - "regex", - "rustls", "serde", "serde_yaml_ng", "socket2", "thiserror 2.0.20", "time", - "tokio", - "tokio-rustls", - "tokio-util", "typetag", "url", - "vetis", ] [[package]] @@ -4733,9 +3851,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -4746,9 +3864,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4756,9 +3874,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -4769,28 +3887,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -4974,32 +4082,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "ws-framer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bacd6cff21323641597fe251e294f823e04292202a6e3c9721a1e8b82bea57bf" -dependencies = [ - "httparse", - "itoa", - "ws-framer-macros", -] - -[[package]] -name = "ws-framer-macros" -version = "0.1.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fe343b024b086505b3c647de6eae0c26235934cb9138a180e3ac5f97a6aeda" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn 2.0.119", -] +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xml" @@ -5091,9 +4176,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -5102,9 +4187,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -5113,13 +4198,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f207e5d0..06147367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,13 @@ resolver = "3" # Include both parts of the library in the workspace members = [ "deboa", - "deboa-h3", - "deboa-compio", + #"deboa-h3", + #"deboa-compio", "deboa-glommio", - "deboa-macros", - "deboa-smol", + #"deboa-macros", + #"deboa-smol", "deboa-test-utils", - "deboa-tokio", + #"deboa-tokio", ] [workspace.package] @@ -23,7 +23,8 @@ license = "MIT OR Apache-2.0" rust-version = "1.85.0" [workspace.dependencies] -deboa = { version = "0.1.2" } +#deboa = { path = "deboa", version = ">= 0.1.2" } +deboa = { path = "deboa" } deboa-compio = { path = "deboa-compio" } deboa-glommio = { path = "deboa-glommio" } deboa-h3 = { path = "deboa-h3", version = "^0.1.1" } @@ -33,7 +34,7 @@ deboa-macros = { path = "deboa-macros" } deboa-smol = { path = "deboa-smol" } deboa-test-utils = { path = "deboa-test-utils" } deboa-tokio = { path = "deboa-tokio" } -hyper-body-utils = { version = ">= 0.1.9" } +hyper-body-utils = { version = ">= 0.1.14", default-features = false } vamo = { version = ">= 0.0.9" } vamo-macros = { version = ">= 0.0.9" } diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md deleted file mode 100644 index ba6d0d87..00000000 --- a/MIGRATION_GUIDE.md +++ /dev/null @@ -1,99 +0,0 @@ -# Migration guide - -## From 0.0.9 to 0.1.0 - -### Breaking changes - -* Client no longer need to be mutable, you might need update all client usages -* Removed set methods from ClientBuilder -* Identity code has been refactored -* Added Certificate struct - -### Non-breaking changes - -* Alias to raw_body method on DeboaResponse -* MethodExt trait -* Added skip_cert_verification to ClientBuilder -* Deboa now supports HTTP/3 -* Support to use either native-tls or rustls via feature flags - -## From 0.0.8 to 0.0.9 - -### Breaking changes - -* Made request, response, connection and runtime traits sealed -* Removed deboa-bora crate -* Removed bora macro from deboa-macros crate -* Moved bora macro to vamo-macros crate - -### Non-breaking changes - -* Improved documentation -* Added more examples -* Deprecated Fetch trait, added FetchWith trait -* Deprecated go method, added send_with method to DeboaRequestBuilder - -## From 0.0.7 to 0.0.8 - -### Breaking changes - -* Improved error handling, added more error variants - -### Non-breaking changes - -* Added multipart support - -## From 0.0.6 to 0.0.7 - -### Non-breaking changes - -* Improved documentation -* Added more examples - -## From 0.0.5 to 0.0.6 - -### Breaking changes - -* Macro migration: `bora` macro moved to `deboa-bora`, update all macro imports. - -## From 0.0.4 to 0.0.5 - -### Breaking changes - -* Config struct was removed -* DeboaResponse allow traits to add body deserialization -* Removed builtin json support -* Added DeboaBuilder -* Added DeboaError -* Added DeboaRequest and DeboaRequestBuilder - -### Non-breaking changes - -* Catchers (interceptors) support -* HTTP2 support -* Responses decompression -* Introduced deboa_extras crate -* Introduced deboa_macro crate -* Introduced vamo crate - -## From 0.0.3 to 0.0.4 - -### Breaking changes - -* Added built-in json support -* Removed data and config params from requests -* Introduced DeboaResponse -* Removed anyhow support - -### Non-breaking changes - -* Added benchmarks -* Added unit tests -* Added integration tests - -## From 0.0.2 to 0.0.3 - -### Non-breaking changes - -* Added support to smol runtime - diff --git a/deboa-compio/Cargo.toml b/deboa-compio/Cargo.toml index d387b0b6..beb0694e 100644 --- a/deboa-compio/Cargo.toml +++ b/deboa-compio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deboa-compio" -version = "0.1.3" +version = "0.1.4" edition.workspace = true authors.workspace = true repository.workspace = true @@ -17,7 +17,9 @@ all-features = true [features] default = [ + #"http1", "http2", + #"http3", "rust-tls", "default-rustls-provider", "default-rustls-verifier", @@ -55,8 +57,7 @@ http2 = ["hyper/http2", "hyper-util/http2"] http3 = [ "deboa-h3/compio", "dep:h3", - "hyper-body-utils/http3", - "hyper-body-utils/compio", + "hyper-body-utils/compio-h3", "compio-quic/h3", "compio-quic/ring", ] @@ -66,12 +67,12 @@ async-executor = { version = "1.13.3", optional = true, default-features = false async-lock = "3.4.2" base64 = "0.23.0" bytes = { version = "1.11" } -compio-quic = { version = "0.8.0", default-features = false, optional = true } +compio-quic = { version = "0.8.2", default-features = false, optional = true } compio-tls = { version = "0.10.0", default-features = false, optional = true } cookie = "0.18.1" cyper-core = "0.9.0" deboa = { workspace = true } -deboa-h3 = { workspace = true, optional = true } +deboa-h3 = { workspace = true, optional = true, default-features = false } futures = "0.3.31" futures-util = { version = "0.3.31", default-features = false } h3 = { version = "0.0.8", optional = true, default-features = false } @@ -89,7 +90,6 @@ indexmap = "2.11.4" io-uring = "0.7.13" log = "0.4.28" minimime = "1.0.0" -mockall = "0.15.0" pin-project-lite = "0.2.17" rand = "0.10.2" regex = "1.9.6" @@ -97,40 +97,40 @@ rustls = { version = "0.23.36", optional = true, default-features = false } rustls-native-certs = { version = "0.8.0", optional = true, default-features = false } rustls-pki-types = { version = "1.13.2", optional = true, default-features = false } rustls-platform-verifier = { version = "0.7.0", optional = true, default-features = false } -send_wrapper = { version = "0.6.0", default-features = false } serde = { version = "1.0.217", features = ["derive"] } tackle = { version = "0.1.1"} thiserror = "2.0.17" -time = "0.3.53" url = "2.5.8" urlencoding = "2.1.3" webpki-roots = { version = "1.0.6", optional = true, default-features = false } [target.'cfg(target_os = "linux")'.dependencies] -compio = { version = "0.19.1", features = [ +compio = { version = "0.19.2", features = [ "fs", "io", "net", "runtime", "io-uring", "macros", + "time", "tls" ], default-features = false } [target.'cfg(all(unix, not(target_os = "linux")))'.dependencies] -compio = { version = "0.19.1", features = [ +compio = { version = "0.19.2", features = [ "fs", "io", "net", "runtime", "polling", "macros", + "time", "tls" ], default-features = false } [dev-dependencies] caramelo = "0.1.2" -compio = { version = "0.19.1", default-features = false } +compio = { version = "0.19.2", default-features = false } criterion = { version = "0.8.2", features = [ "html_reports", "async", @@ -138,10 +138,13 @@ criterion = { version = "0.8.2", features = [ "async_tokio", ] } deboa-test-utils = { workspace = true } -easyhttpmock-vetis-compio = { version = "0.1.0", features = [ +easyhttpmock-vetis-compio = { path = "../../easyhttpmock/easyhttpmock-vetis-compio", features = [ + #"http1", "http2", + #"http3", "rust-tls", ], default-features = false } futures-util = "0.3.31" multer = "3.1.0" rstest = "0.26.1" +time = { version = "0.3.53" } diff --git a/deboa-compio/src/cert.rs b/deboa-compio/src/cert.rs index 7c39bb77..8e5b8cb7 100644 --- a/deboa-compio/src/cert.rs +++ b/deboa-compio/src/cert.rs @@ -94,7 +94,7 @@ impl deboa::cert::Identity for DeboaIdentity { &self.cert } - fn ket(&self) -> &Option> { + fn key(&self) -> &Option> { &self.key } diff --git a/deboa-compio/src/client/http/conn/mod.rs b/deboa-compio/src/client/http/conn/mod.rs index 0095a432..ea858d2a 100644 --- a/deboa-compio/src/client/http/conn/mod.rs +++ b/deboa-compio/src/client/http/conn/mod.rs @@ -22,6 +22,7 @@ use deboa::request::Http1Request; use deboa::request::Http2Request; use deboa::{ conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection}, + dns::DnsResolver, errors::{DeboaError, RequestError}, response::DeboaResponse, Result, @@ -30,7 +31,7 @@ use deboa::{ use deboa_h3::compio::Http3Request; use http::{Request, Version}; use hyper_body_utils::HttpBody; -use std::marker::PhantomData; +use std::{marker::PhantomData, time::Duration}; /// Connection pooling for efficient HTTP connections. /// @@ -45,11 +46,6 @@ use std::marker::PhantomData; /// - Configurable pool size (coming soon) pub mod pool; -/// Stream module for runtime-specific stream implementations. -/// -/// This module provides stream implementations for different runtimes (Tokio, Smol, etc.). -pub(crate) mod stream; - #[cfg(feature = "http1")] pub(crate) type Http1Connection = BaseHttpConnection; #[cfg(feature = "http2")] @@ -88,20 +84,8 @@ impl DeboaConnection { pub fn http3(conn: Http3Connection) -> Self { DeboaConnection::Http3(Box::new(conn)) } -} -impl HttpConnectionDispatcher for DeboaConnection { - /// Send a request over the connection. - /// - /// # Arguments - /// - /// * `url` - The URL to send the request to. - /// * `request` - The request to send. - /// - /// # Returns - /// - /// * `Result` - The response or error. - async fn send_request(&mut self, request: Request) -> Result { + async fn send(&mut self, request: Request) -> Result { match self { #[cfg(feature = "http1")] DeboaConnection::Http1(ref mut conn) => { @@ -155,6 +139,30 @@ impl HttpConnectionDispatcher for DeboaConnection { } } +impl HttpConnectionDispatcher for DeboaConnection { + /// Send a request over the connection. + /// + /// # Arguments + /// + /// * `url` - The URL to send the request to. + /// * `request` - The request to send. + /// + /// # Returns + /// + /// * `Result` - The response or error. + async fn send_request( + &mut self, + request: Request, + timeout: Duration, + ) -> Result { + compio::time::timeout(timeout, self.send(request)) + .await + .map_err(|_| { + DeboaError::Request(RequestError::Send { message: "Request timed out".to_string() }) + })? + } +} + /// Struct that represents the connection. /// /// # Fields @@ -175,24 +183,126 @@ impl BaseHttpConnection { pub struct ConnectionFactory {} impl ConnectionFactory { - pub async fn create_connection<'a>( - protocol: &Version, + /// Create a new connection. + pub async fn create_connection<'a, D>( config: &'a ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, - ) -> Result { - let conn = match protocol { + dns_resolver: &D, + ) -> Result + where + D: DnsResolver, + { + let ips = dns_resolver + .resolve( + config + .host() + .to_string(), + config.port(), + ) + .await?; + let ips = if config + .client_bind_addr() + .is_ipv4() + { + ips.into_iter() + .filter(|ip| ip.is_ipv4()) + .collect::>() + } else { + ips.into_iter() + .filter(|ip| ip.is_ipv6()) + .collect::>() + }; + + let Some(ip) = ips.first() else { + return Err(DeboaError::Request(RequestError::Send { + message: format!("No IP addresses found for hostname: {}", config.host()), + })); + }; + + #[cfg(any(feature = "http1", feature = "http2"))] + let stream = { + use compio::net::TcpStream; + use cyper_core::HyperStream; + use deboa::errors::ConnectionError; + + let tcp_stream = TcpStream::connect(format!("{}:{}", ip, config.port())) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tcp { message: e.to_string() }) + })?; + let use_tls = config.scheme() == "https" || config.scheme() == "wss"; + if !use_tls { + HyperStream::new_plain(tcp_stream) + } else { + #[cfg(feature = "rust-tls")] + { + use crate::client::tls::rustls::tcp::connect; + use crate::client::tls::rustls::TlsConnectionBuilder; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + HyperStream::new_tls(connect(tls_config, tcp_stream, config.host()).await?) + } + + #[cfg(feature = "native-tls")] + { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + HyperStream::new_tls(stream) + } + } + }; + + let conn = match config.protocol_version() { #[cfg(feature = "http1")] &Version::HTTP_11 => { - let conn = Http1Connection::connect(config).await?; + let conn = Http1Connection::connect(stream).await?; DeboaConnection::http1(conn) } #[cfg(feature = "http2")] &Version::HTTP_2 => { - let conn = Http2Connection::connect(config).await?; + let conn = Http2Connection::connect(stream).await?; DeboaConnection::http2(conn) } #[cfg(feature = "http3")] &Version::HTTP_3 => { - let conn = Http3Connection::connect(config).await?; + let stream = { + use crate::client::tls::rustls::udp::connect; + #[cfg(feature = "rust-tls")] + use crate::client::tls::rustls::TlsConnectionBuilder; + use compio_quic::Endpoint; + use deboa::errors::ConnectionError; + use std::net::SocketAddr; + + let mut client_endpoint = + Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0)) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: e.to_string(), + }) + })?; + + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + connect( + tls_config, + &mut client_endpoint, + SocketAddr::new(*ip, config.port()), + config.host(), + ) + .await? + }; + + let conn = Http3Connection::connect(stream).await?; DeboaConnection::http3(conn) } _ => { diff --git a/deboa-compio/src/client/http/conn/pool.rs b/deboa-compio/src/client/http/conn/pool.rs index da7ded96..813de8e0 100644 --- a/deboa-compio/src/client/http/conn/pool.rs +++ b/deboa-compio/src/client/http/conn/pool.rs @@ -2,9 +2,14 @@ use crate::{ cert::{DeboaCertificate, DeboaIdentity}, client::http::conn::{ConnectionFactory, DeboaConnection}, }; -use deboa::{conn::ConnectionConfig, Result}; +use deboa::{ + conn::ConnectionConfig, + dns::DnsResolver, + errors::{ConnectionError, DeboaError}, + Result, +}; use hashbrown::HashMap; -use time::Duration; +use std::time::Duration; /// Struct that represents the HTTP connection pool. /// @@ -27,7 +32,7 @@ impl Default for HttpConnectionPool { fn default() -> Self { Self { max_idle_connections: 5, - keep_alive_duration: Duration::minutes(5), + keep_alive_duration: Duration::from_mins(5), connections: HashMap::new(), } } @@ -76,15 +81,14 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { .len() as u32 } - async fn create_connection<'a>( - &'a mut self, + async fn create_connection<'a, D>( + &mut self, config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result<&'a mut DeboaConnection> { - if self.max_idle_connections == 0 { - self.connections - .clear(); - } - + dns_resolver: &D, + ) -> Result<&mut DeboaConnection> + where + D: DnsResolver, + { let host = config.host(); if self .connections @@ -98,8 +102,20 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { } log::debug!("Creating new connection for {}", host); - let connection = - ConnectionFactory::create_connection(config.protocol_version(), config).await?; + let connection = compio::time::timeout( + config.connection_timeout(), + ConnectionFactory::create_connection(config, dns_resolver), + ) + .await + .map_err(|_| { + DeboaError::Connection(ConnectionError::Timeout { + message: format!( + "Connection to {} timed out after {:?}", + host, + config.connection_timeout() + ), + }) + })??; self.connections .insert(host.to_string(), connection); diff --git a/deboa-compio/src/client/http/conn/stream/mod.rs b/deboa-compio/src/client/http/conn/stream/mod.rs deleted file mode 100644 index e323fdda..00000000 --- a/deboa-compio/src/client/http/conn/stream/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub(crate) mod plain; -pub(crate) use plain::*; - -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) mod tls; -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) use tls::*; diff --git a/deboa-compio/src/client/http/conn/stream/plain.rs b/deboa-compio/src/client/http/conn/stream/plain.rs deleted file mode 100644 index 0fa152e6..00000000 --- a/deboa-compio/src/client/http/conn/stream/plain.rs +++ /dev/null @@ -1,31 +0,0 @@ -use compio::net::TcpStream; -use cyper_core::HyperStream; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use std::net::IpAddr; - -pub async fn create_stream(ip: IpAddr, host: &str, port: u16) -> Result { - let tcp_stream = TcpStream::connect((ip, port)).await; - let tcp_stream = match tcp_stream { - Ok(tcp_stream) => tcp_stream, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Tcp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - }; - - Ok(tcp_stream) -} - -pub(crate) async fn plain_connection( - ip: IpAddr, - host: &str, - port: u16, -) -> Result> { - let stream = create_stream(ip, host, port).await?; - Ok(HyperStream::new_plain(stream)) -} diff --git a/deboa-compio/src/client/http/conn/stream/tls/mod.rs b/deboa-compio/src/client/http/conn/stream/tls/mod.rs deleted file mode 100644 index 4632c49b..00000000 --- a/deboa-compio/src/client/http/conn/stream/tls/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(feature = "native-tls")] -mod native; - -#[cfg(feature = "rust-tls")] -mod rustls; - -#[cfg(feature = "rust-tls")] -pub(crate) use rustls::*; - -#[cfg(feature = "native-tls")] -pub(crate) use native::*; diff --git a/deboa-compio/src/client/http/conn/stream/tls/native.rs b/deboa-compio/src/client/http/conn/stream/tls/native.rs deleted file mode 100644 index 734f0e96..00000000 --- a/deboa-compio/src/client/http/conn/stream/tls/native.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{ - cert::{Certificate as DeboaCertificate, Identity as DeboaIdentity}, - client::http::conn::stream::create_stream, -}; -use compio::net::TcpStream; -use compio_tls::native_tls::TlsConnector; -use cyper_core::HyperStream; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use std::net::IpAddr; - -pub(crate) async fn tls_connection( - ip: IpAddr, - host: &str, - port: u16, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: &[&str], -) -> Result> { - let socket = create_stream(ip, host, port).await?; - let mut builder = TlsConnector::builder(); - - let builder = if skip_server_verification { - builder - .danger_accept_invalid_certs(true) - .danger_accept_invalid_hostnames(true) - } else { - &mut builder - }; - - let builder = builder.request_alpns(&alpn); - - let builder = if let Some(ca) = certificate { - let cert = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - builder.add_root_certificate(cert.unwrap()) - } else { - builder - }; - - let builder = if let Some(identity) = identity { - let ident = identity.try_into(); - if let Err(e) = ident { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - builder.identity(ident.unwrap()) - } else { - builder - }; - - let connector = builder - .build() - .map_err(|e| { - DeboaError::Connection(ConnectionError::Tls { - host: host.to_owned(), - message: e.to_string(), - }) - })?; - - let connector = compio_tls::TlsConnector::from(connector); - - let stream = connector - .connect(host, socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(HyperStream::new_tls(stream)) -} diff --git a/deboa-compio/src/client/http/conn/stream/tls/rustls.rs b/deboa-compio/src/client/http/conn/stream/tls/rustls.rs deleted file mode 100644 index 52b0a74e..00000000 --- a/deboa-compio/src/client/http/conn/stream/tls/rustls.rs +++ /dev/null @@ -1,220 +0,0 @@ -use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::stream::create_stream, -}; -use compio::net::TcpStream; -use compio_tls::TlsConnector; -use cyper_core::HyperStream; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use rustls::{ - pki_types::{CertificateDer, PrivateKeyDer, ServerName}, - ClientConfig, -}; -use std::net::IpAddr; -use std::sync::Arc; - -pub(crate) async fn tls_connection( - ip: IpAddr, - host: &str, - port: u16, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result> { - let socket = create_stream(ip, host, port).await?; - let config = setup_rust_tls(host, identity, certificate, skip_server_verification, alpn)?; - let connector = TlsConnector::from(Arc::new(config)); - let hostname = ServerName::try_from(host.to_string()); - - if let Err(e) = hostname { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: e.to_string(), - })); - } - - let stream = connector - .connect(host, socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(HyperStream::new_tls(stream)) -} - -pub(crate) fn default_provider() -> Arc { - #[cfg(feature = "__rustls_aws_lc_rs")] - let provider = rustls::crypto::aws_lc_rs::default_provider(); - #[cfg(feature = "__rustls_ring")] - let provider = rustls::crypto::ring::default_provider(); - Arc::new(provider) -} - -pub(crate) fn setup_rust_tls( - host: &str, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result { - let provider = default_provider(); - - if skip_server_verification { - use crate::client::http::conn::stream::tls::rustls::verify::SkipServerVerification; - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - return Ok(config); - } - - #[cfg(feature = "__webpki_rustls_verifier")] - let config = { - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version"); - - let mut root_store = - rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; - if let Some(ca) = certificate { - let cert = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - let result = root_store.add(cert.unwrap()); - if let Err(e) = result { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not add CA certificate to the store: {}", e), - })); - } - - config.with_root_certificates(root_store) - } else { - config.with_root_certificates(root_store) - } - }; - - #[cfg(feature = "__platform_rustls_verifier")] - let config = { - use rustls_platform_verifier::Verifier; - let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); - rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(Arc::new(verifier)) - }; - - let mut config = if let Some(id) = identity { - let pair: std::result::Result< - (CertificateDer<'static>, PrivateKeyDer<'static>), - std::io::Error, - > = id.try_into(); - if let Err(e) = pair { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - - let pair = pair.unwrap(); - - config - .with_client_auth_cert(vec![pair.0], pair.1) - .expect("Failed to set client identity") - } else { - config.with_no_client_auth() - }; - - config.enable_early_data = true; - - config.alpn_protocols = alpn; - - Ok(config) -} - -pub(crate) mod verify { - use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; - use std::sync::Arc; - - #[derive(Debug)] - pub(crate) struct SkipServerVerification(Arc); - - impl SkipServerVerification { - pub(crate) fn new() -> Arc { - let provider = super::default_provider(); - Arc::new(Self(provider)) - } - } - - impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> std::result::Result - { - Ok(rustls::client::danger::ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result - { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result - { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0 - .signature_verification_algorithms - .supported_schemes() - } - } -} diff --git a/deboa-compio/src/client/http/http1.rs b/deboa-compio/src/client/http/http1.rs index 8efd5902..991694a8 100644 --- a/deboa-compio/src/client/http/http1.rs +++ b/deboa-compio/src/client/http/http1.rs @@ -1,19 +1,14 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::alpn; -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::client::http::conn::stream::tls_connection; -use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http1Connection}, -}; +use crate::client::http::conn::{BaseHttpConnection, Http1Connection}; +use compio::net::TcpStream; +use cyper_core::HyperStream; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http1Request, Result, }; use http::version::Version; use hyper::client::conn::http1::handshake; -use hyper_body_utils::HttpBody; impl HttpConnection for Http1Connection { type Sender = Http1Request; @@ -23,42 +18,20 @@ impl HttpConnection for Http1Connection { } impl ProtoConnection for Http1Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http1Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = HyperStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_11 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(stream) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(stream.unwrap()).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; compio::runtime::spawn(async move { match conn @@ -66,7 +39,9 @@ impl ProtoConnection for Http1Connection { .await { Ok(_) => (), - Err(_err) => {} + Err(err) => { + log::error!("Error: {:#}", err) + } }; }) .detach(); diff --git a/deboa-compio/src/client/http/http2.rs b/deboa-compio/src/client/http/http2.rs index a5c27a95..2bcfa74e 100644 --- a/deboa-compio/src/client/http/http2.rs +++ b/deboa-compio/src/client/http/http2.rs @@ -1,20 +1,14 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::alpn; -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::client::http::conn::stream::tls_connection; -use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http2Connection}, -}; -use cyper_core::CompioExecutor; +use crate::client::http::conn::{BaseHttpConnection, Http2Connection}; +use compio::net::TcpStream; +use cyper_core::{CompioExecutor, HyperStream}; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http2Request, Result, }; use http::version::Version; use hyper::client::conn::http2::handshake; -use hyper_body_utils::HttpBody; impl HttpConnection for Http2Connection { type Sender = Http2Request; @@ -24,48 +18,26 @@ impl HttpConnection for Http2Connection { } impl ProtoConnection for Http2Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http2Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = HyperStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_2 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: HyperStream) -> Result { + let (sender, conn) = handshake(CompioExecutor, stream) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(CompioExecutor, stream.unwrap()).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; compio::runtime::spawn(async move { match conn.await { Ok(_) => (), Err(err) => { - println!("Error: {:#}", err); + log::error!("Error: {:#}", err) } }; }) diff --git a/deboa-compio/src/client/http/http3.rs b/deboa-compio/src/client/http/http3.rs index b0c99b7d..dd4b6e14 100644 --- a/deboa-compio/src/client/http/http3.rs +++ b/deboa-compio/src/client/http/http3.rs @@ -1,56 +1,13 @@ -use crate::{ - alpn, - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::setup_rust_tls, BaseHttpConnection, Http3Connection}, -}; -use compio_quic::{crypto::rustls::QuicClientConfig, ClientConfig, Endpoint}; +use crate::client::http::conn::{BaseHttpConnection, Http3Connection}; +use compio_quic::Connection; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, errors::{ConnectionError, DeboaError}, Result, }; use deboa_h3::compio::{Http3Request, SendRequest}; use futures::future; use http::version::Version; -use hyper_body_utils::HttpBody; -use std::{ - net::{IpAddr, SocketAddr}, - sync::Arc, -}; - -async fn lookup_and_connect( - ip: IpAddr, - host: &str, - port: u16, - client_endpoint: &Endpoint, - client_config: ClientConfig, -) -> std::result::Result { - let conn = client_endpoint.connect(SocketAddr::new(ip, port), host, Some(client_config)); - - let conn = match conn { - Ok(conn) => conn, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }; - - let conn = conn.await; - - let conn = match conn { - Ok(conn) => conn, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }; - - Ok(conn) -} impl HttpConnection for Http3Connection { type Sender = Http3Request; @@ -60,81 +17,18 @@ impl HttpConnection for Http3Connection { } impl ProtoConnection for Http3Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http3Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = Connection; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_3 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - let client_endpoint = - Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0)).await; - - if let Err(e) = client_endpoint { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let client_endpoint = client_endpoint.unwrap(); - - let tls_config = setup_rust_tls( - config.host(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - )?; - - let quic_config = QuicClientConfig::try_from(tls_config); - if let Err(e) = quic_config { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let quic_config = quic_config.unwrap(); - let client_config = ClientConfig::new(Arc::new(quic_config)); - let result = lookup_and_connect( - *config.ip(), - config.host(), - config.port(), - &client_endpoint, - client_config, - ) - .await; - - if let Err(e) = result { - return Err(e); - } - - let conn = result.unwrap(); - - let client = compio_quic::h3::client::new(conn).await; - - if let Err(e) = client { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let (mut conn, sender) = client.unwrap(); + async fn connect(stream: Self::RuntimeStream) -> Result { + let (mut conn, sender) = compio_quic::h3::client::new(stream) + .await + .map_err(|e| DeboaError::Connection(ConnectionError::Udp { message: e.to_string() }))?; compio::runtime::spawn(async move { future::poll_fn(|cx| conn.poll_close(cx)).await; diff --git a/deboa-compio/src/client/mod.rs b/deboa-compio/src/client/mod.rs index 428f5c44..5f73cf1f 100644 --- a/deboa-compio/src/client/mod.rs +++ b/deboa-compio/src/client/mod.rs @@ -1,2 +1,3 @@ pub mod dns; pub mod http; +pub mod tls; diff --git a/deboa-compio/src/client/tls/mod.rs b/deboa-compio/src/client/tls/mod.rs new file mode 100644 index 00000000..afc96df1 --- /dev/null +++ b/deboa-compio/src/client/tls/mod.rs @@ -0,0 +1,9 @@ +//! TLS transport implementations for the Deboa HTTP client. +//! +//! This module provides TLS functionality for secure HTTP connections. +//! It supports both native-tls and rustls backends. + +#[cfg(feature = "native-tls")] +pub mod native; +#[cfg(feature = "rust-tls")] +pub mod rustls; diff --git a/deboa-compio/src/client/tls/native.rs b/deboa-compio/src/client/tls/native.rs new file mode 100644 index 00000000..12b02538 --- /dev/null +++ b/deboa-compio/src/client/tls/native.rs @@ -0,0 +1,126 @@ +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use compio::net::TcpStream; +use compio_tls::{ + native_tls::TlsConnector, + native_tls::{Certificate, Identity}, + TlsStream, +}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; + +#[inline] +pub(crate) fn alpn() -> &'static [&'static str] { + &[ + #[cfg(feature = "http3")] + "h3", + #[cfg(feature = "http2")] + "h2", + #[cfg(feature = "http1")] + "http/1.1", + ] +} + +pub struct TlsConnectionBuilder<'a> { + tcp_stream: TcpStream, + host: &'a str, + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: &'a [&'a str], +} + +impl<'a> TlsConnectionBuilder<'a> { + pub fn new(tcp_stream: TcpStream, host: &'a str) -> Self { + Self { + tcp_stream, + host, + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + } + } + + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + pub fn alpn(mut self, alpn: &'a [&str]) -> Self { + self.alpn = alpn; + self + } + + pub async fn connect(self) -> Result> { + let mut builder = TlsConnector::builder(); + + let builder = if self.skip_server_verification { + builder + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + } else { + &mut builder + }; + + let builder = builder.request_alpns(self.alpn); + + let builder = if let Some(ca) = self.certificate { + let cert: Certificate = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + builder.add_root_certificate(cert) + } else { + builder + }; + + let builder = if let Some(identity) = self.identity { + let ident: Identity = identity + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + builder.identity(ident) + } else { + builder + }; + + let connector = builder + .build() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not build TLS connector: {}", e), + }) + })?; + + let connector = compio_tls::TlsConnector::from(connector); + + let stream = connector + .connect(self.host, self.tcp_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + })?; + + Ok(stream) + } +} diff --git a/deboa-compio/src/client/tls/rustls.rs b/deboa-compio/src/client/tls/rustls.rs new file mode 100644 index 00000000..0bb431c1 --- /dev/null +++ b/deboa-compio/src/client/tls/rustls.rs @@ -0,0 +1,320 @@ +//! TLS implementation using rustls + +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use rustls::{ + crypto::CryptoProvider, + pki_types::{CertificateDer, PrivateKeyDer}, + ClientConfig, +}; + +pub(crate) fn default_provider() -> CryptoProvider { + #[cfg(feature = "__rustls_aws_lc_rs")] + return rustls::crypto::aws_lc_rs::default_provider(); + #[cfg(feature = "__rustls_ring")] + return rustls::crypto::ring::default_provider(); +} + +#[inline] +pub(crate) fn alpn() -> Vec> { + vec![ + #[cfg(feature = "http3")] + b"h3".to_vec(), + #[cfg(feature = "http2")] + b"h2".to_vec(), + #[cfg(feature = "http1")] + b"http/1.1".to_vec(), + ] +} + +/// Builder for TLS connections using rustls +pub struct TlsConnectionBuilder<'a> { + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: Vec>, + provider: CryptoProvider, +} + +impl Default for TlsConnectionBuilder<'_> { + fn default() -> Self { + Self { + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + provider: default_provider(), + } + } +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Set the identity to use for the connection + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Set the certificate to use for the connection + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skip server verification + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Set the ALPN protocols to use for the connection + pub fn alpn(mut self, alpn: Vec>) -> Self { + self.alpn = alpn; + self + } + + /// Build the TLS client configuration + pub fn build_config(self) -> Result { + let client_config = { + if self.skip_server_verification { + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verify::SkipServerVerification::new( + self.provider, + )) + .with_no_client_auth() + } else { + #[cfg(feature = "__webpki_rustls_verifier")] + let config = { + let config = ClientConfig::builder_with_provider(self.provider.into()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })?; + + let mut root_store = + rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; + let config = if let Some(ca) = self.certificate { + let cert = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + + root_store + .add(cert) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!( + "Could not add CA certificate to the store: {}", + e + ), + }) + })?; + + config.with_root_certificates(root_store) + } else { + config.with_root_certificates(root_store) + }; + + config + }; + + #[cfg(feature = "__platform_rustls_verifier")] + let config = { + use rustls_platform_verifier::BuilderVerifierExt; + rustls::ClientConfig::builder_with_provider(default_provider()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })? + .with_platform_verifier() + }; + + let mut config = if let Some(id) = self.identity { + let pair: (CertificateDer<'_>, PrivateKeyDer<'_>) = id + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + + config + .with_client_auth_cert(vec![pair.0], pair.1) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set client identity: {}", e), + }) + })? + } else { + config.with_no_client_auth() + }; + + config.enable_early_data = true; + + config.alpn_protocols = self.alpn; + + config + } + }; + + Ok(client_config) + } +} + +#[cfg(any(feature = "http1", feature = "http2"))] +/// TCP connection module for TLS +pub mod tcp { + use compio::net::TcpStream; + use compio_tls::{TlsConnector, TlsStream}; + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use rustls::ClientConfig; + use std::sync::Arc; + + /// Establish a TLS connection over TCP + pub async fn connect( + config: ClientConfig, + inner_stream: TcpStream, + host: &str, + ) -> Result> { + let connector = TlsConnector::from(Arc::new(config)); + + connector + .connect(host, inner_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }) + } +} + +#[cfg(feature = "http3")] +/// UDP connection module for TLS +pub mod udp { + use compio_quic::{Connection, Endpoint}; + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use rustls::ClientConfig; + use std::{net::SocketAddr, sync::Arc}; + + /// Establish a TLS connection over UDP + pub async fn connect( + config: ClientConfig, + endpoint: &mut Endpoint, + socket_addr: SocketAddr, + host: &str, + ) -> Result { + let quic_config = + compio_quic::crypto::rustls::QuicClientConfig::try_from(config).map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not create QUIC client config: {}", e), + }) + })?; + + let client_config = compio_quic::ClientConfig::new(Arc::new(quic_config)); + + let conn = endpoint + .connect(socket_addr, host, Some(client_config)) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let conn = conn + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + Ok(conn) + } +} + +pub(crate) mod verify { + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::CryptoProvider, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + use std::sync::Arc; + + #[derive(Debug)] + pub(crate) struct SkipServerVerification(CryptoProvider); + + impl SkipServerVerification { + pub(crate) fn new(provider: CryptoProvider) -> Arc { + Arc::new(Self(provider)) + } + } + + impl ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0 + .signature_verification_algorithms + .supported_schemes() + } + } +} diff --git a/deboa-compio/src/lib.rs b/deboa-compio/src/lib.rs index a16f6148..49a4c455 100644 --- a/deboa-compio/src/lib.rs +++ b/deboa-compio/src/lib.rs @@ -22,44 +22,20 @@ compile_error!( #[cfg(all(feature = "native-tls", feature = "rust-tls"))] compile_error!("You cannot enable native-tls and rust-tls features at the same time."); +#[cfg(all(not(any(feature = "native-tls", feature = "rust-tls")), feature = "http2"))] +compile_error!("HTTP2 requires native-tls or rust-tls support."); + #[cfg(all(feature = "native-tls", feature = "http3"))] compile_error!("HTTP3 is not supported within tokio-native-tls runtime."); #[cfg(not(any(feature = "http1", feature = "http2", feature = "http3")))] compile_error!("At least one HTTP version feature must be enabled."); -#[cfg(feature = "rust-tls")] -#[inline] -pub(crate) fn alpn() -> Vec> { - vec![ - #[cfg(feature = "http2")] - b"h2".to_vec(), - #[cfg(feature = "http1")] - b"http/1.1".to_vec(), - #[cfg(feature = "http3")] - b"h3".to_vec(), - ] -} - -#[cfg(feature = "native-tls")] -#[inline] -pub(crate) fn alpn() -> &'static [&'static str] { - &[ - #[cfg(feature = "http2")] - "h2", - #[cfg(feature = "http1")] - "http/1.1", - #[cfg(feature = "http3")] - "h3", - ] -} - -use deboa::InnerClient; - use crate::{ cert::{DeboaCertificate, DeboaIdentity}, client::{dns::DefaultDnsResolver, http::conn::pool::HttpConnectionPool}, }; +use deboa::InnerClient; pub mod cert; pub mod client; diff --git a/deboa-compio/tests/base/get.rs b/deboa-compio/tests/base/get.rs index 303944c6..ff91fb24 100644 --- a/deboa-compio/tests/base/get.rs +++ b/deboa-compio/tests/base/get.rs @@ -30,28 +30,22 @@ async fn test_get_http( #[rstest] #[compio::test] async fn test_get_http_skip_verification( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { - let identity = DeboaIdentity::from_pkcs8( - deboa_test_utils::common::helpers::CLIENT_CERT, - deboa_test_utils::common::helpers::CLIENT_KEY, - ContentEncoding::DER, - ); - let client = Client::builder() .certificate(DeboaCertificate::from_slice( deboa_test_utils::common::helpers::CA_CERT, ContentEncoding::DER, )) - .identity(identity) + .skip_cert_verification(true) .build(); - deboa_test_utils::base::get::test_get_http_mutual_authentication( + deboa_test_utils::base::get::test_skip_cert_verification( &client, &mut create_server.await, protocol_version, + true, ) .await } @@ -59,12 +53,15 @@ async fn test_get_http_skip_verification( #[rstest] #[compio::test] async fn test_get_http_verify( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { + let client = Client::builder() + .skip_cert_verification(false) + .build(); + deboa_test_utils::base::get::test_skip_cert_verification( - &create_client, + &client, &mut create_server.await, protocol_version, false, diff --git a/deboa-glommio/src/client/http/conn/stream/plain.rs b/deboa-glommio/src/client/http/conn/stream/plain.rs index 6fe1f0c0..3aa23a8d 100644 --- a/deboa-glommio/src/client/http/conn/stream/plain.rs +++ b/deboa-glommio/src/client/http/conn/stream/plain.rs @@ -12,7 +12,6 @@ pub(crate) async fn create_stream(addr: IpAddr, host: &str, port: u16) -> Result Ok(tcp_stream) => tcp_stream, Err(e) => { return Err(DeboaError::Connection(ConnectionError::Tcp { - host: host.to_string(), message: format!("Could not connect to server: {}", e), })); } diff --git a/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs b/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs index df235853..ecd1fe6a 100644 --- a/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs +++ b/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs @@ -28,7 +28,6 @@ pub(crate) async fn tls_connection<'a>( if let Err(e) = hostname { return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), message: e.to_string(), })); } @@ -39,7 +38,6 @@ pub(crate) async fn tls_connection<'a>( if let Err(e) = stream { return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), message: format!("Could not connect to server: {}", e), })); } @@ -88,7 +86,6 @@ pub(crate) fn setup_rust_tls<'a>( let cert = ca.try_into(); if let Err(e) = cert { return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), message: format!("Invalid CA certificate: {}", e), })); } @@ -96,7 +93,6 @@ pub(crate) fn setup_rust_tls<'a>( let result = root_store.add(cert.unwrap()); if let Err(e) = result { return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), message: format!("Could not add CA certificate to the store: {}", e), })); } @@ -122,7 +118,6 @@ pub(crate) fn setup_rust_tls<'a>( let pair = id.try_into(); if let Err(e) = pair { return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), message: format!("Invalid client identity: {}", e), })); } diff --git a/deboa-glommio/src/client/http/http1.rs b/deboa-glommio/src/client/http/http1.rs index 5e113eca..8e064d81 100644 --- a/deboa-glommio/src/client/http/http1.rs +++ b/deboa-glommio/src/client/http/http1.rs @@ -24,11 +24,7 @@ impl HttpConnection for Http1Connection { } impl ProtoConnection for Http1Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http1Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; #[inline] fn protocol_version(&self) -> Version { diff --git a/deboa-glommio/src/client/http/http2.rs b/deboa-glommio/src/client/http/http2.rs index 7e066dd0..2b053fe2 100644 --- a/deboa-glommio/src/client/http/http2.rs +++ b/deboa-glommio/src/client/http/http2.rs @@ -25,11 +25,7 @@ impl HttpConnection for Http2Connection { } impl ProtoConnection for Http2Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http2Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; #[inline] fn protocol_version(&self) -> Version { diff --git a/deboa-h3/Cargo.toml b/deboa-h3/Cargo.toml index 44b500a8..c54d555a 100644 --- a/deboa-h3/Cargo.toml +++ b/deboa-h3/Cargo.toml @@ -12,8 +12,8 @@ rust-version.workspace = true [features] default = [] -generic = ["dep:h3", "dep:h3-quinn", "hyper-body-utils/http3", "hyper-body-utils/generic"] -compio = ["dep:h3", "dep:compio-quic", "hyper-body-utils/http3", "hyper-body-utils/compio"] +generic = ["dep:h3", "dep:h3-quinn", "hyper-body-utils/generic-h3", "hyper-body-utils/generic"] +compio = ["dep:h3", "dep:compio-quic", "hyper-body-utils/compio-h3", "hyper-body-utils/compio"] [dependencies] bytes = { version = "1.11" } diff --git a/deboa-h3/src/lib.rs b/deboa-h3/src/lib.rs index 4dfae043..b40520e5 100644 --- a/deboa-h3/src/lib.rs +++ b/deboa-h3/src/lib.rs @@ -1,5 +1,6 @@ #[cfg(feature = "generic")] pub mod generic { + use bytes::Bytes; use h3::{client::RequestStream, error::StreamError}; use h3_quinn::{OpenStreams, RecvStream}; diff --git a/deboa-smol/Cargo.toml b/deboa-smol/Cargo.toml index 5f474c40..bacf7777 100644 --- a/deboa-smol/Cargo.toml +++ b/deboa-smol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deboa-smol" -version = "0.1.2" +version = "0.1.3" edition.workspace = true authors.workspace = true repository.workspace = true @@ -59,15 +59,16 @@ __rustls_ring = ["futures-rustls/ring", "quinn/rustls-ring"] native-tls = ["async-native-tls/runtime-smol"] # protocols -http1 = ["hyper/http1", "hyper-util/http1"] -http2 = ["hyper/http2", "hyper-util/http2"] +http1 = ["rustls/tls12", "hyper/http1", "hyper-util/http1"] +http2 = ["rustls/tls12", "hyper/http2", "hyper-util/http2"] http3 = [ "deboa-h3/generic", "dep:h3", "dep:h3-quinn", + "dep:rustls", "quinn/runtime-smol", - "hyper-body-utils/http3", "hyper-body-utils/generic", + "hyper-body-utils/generic-h3", ] websockets = ["ws-framer/http", "ws-framer/alloc"] @@ -83,6 +84,7 @@ deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } futures = { version = "0.3.31", default-features = false } futures-rustls = { version = "0.26.0", optional = true, default-features = false } +futures-timeout = "0.2.1" futures-util = { version = "0.3.31", optional = true, default-features = false } h3 = { version = "0.0.8", optional = true, default-features = false } h3-quinn = { version = "0.0.10", optional = true, default-features = false } @@ -100,7 +102,6 @@ indexmap = "2.11.4" log = "0.4.32" macro_rules_attribute = { version = "0.2.2", default-features = false } minimime = "1.0.0" -mockall = "0.15.0" quinn = { version = "0.11.7", optional = true, default-features = false } rand = { version = "0.10.1", default-features = false } regex = { version = "1.12.4", default-features = false } @@ -115,7 +116,6 @@ smol-hyper = { version = "0.1.0", default-features = false } smol-macros = { version = "0.1.1", default-features = false } tackle = { version = "0.1.1"} thiserror = "2.0.17" -time = { version = "0.3.51", default-features = false } url = "2.5.8" urlencoding = "2.1.3" webpki-roots = { version = "1.0.6", optional = true, default-features = false } @@ -131,9 +131,12 @@ criterion = { version = "0.8.2", features = [ deboa-test-utils = { workspace = true } easyhttpmock-vetis-smol = { version = "0.1.0", features = [ "http1", + "http2", + "http3", "rust-tls", ], default-features = false } futures-util = "0.3.31" multer = "3.1.0" rstest = "0.26.1" smol = "2.0.2" +time = { version = "0.3.53" } diff --git a/deboa-smol/src/cert.rs b/deboa-smol/src/cert.rs index 74219170..591438d7 100644 --- a/deboa-smol/src/cert.rs +++ b/deboa-smol/src/cert.rs @@ -11,7 +11,7 @@ use async_native_tls::{Certificate as NativeCertificate, Identity as NativeIdent use deboa::cert::IdentityNativeExt; use deboa::cert::{Certificate as _, CertificateExt, ContentEncoding, IdentityExt}; #[cfg(feature = "rust-tls")] -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use futures_rustls::pki_types::{CertificateDer, PrivateKeyDer}; /// Represents a client certificate and its associated data for mutual TLS authentication. /// @@ -93,7 +93,7 @@ impl deboa::cert::Identity for DeboaIdentity { &self.cert } - fn ket(&self) -> &Option> { + fn key(&self) -> &Option> { &self.key } diff --git a/deboa-smol/src/client/http/conn/mod.rs b/deboa-smol/src/client/http/conn/mod.rs index b64798ae..ad57c9c0 100644 --- a/deboa-smol/src/client/http/conn/mod.rs +++ b/deboa-smol/src/client/http/conn/mod.rs @@ -16,21 +16,25 @@ //! - Thread-safe connection handling //! ``` use crate::cert::{DeboaCertificate, DeboaIdentity}; +#[cfg(any(feature = "http1", feature = "http2"))] +use crate::rt::stream::SmolStream; #[cfg(feature = "http1")] use deboa::request::Http1Request; #[cfg(feature = "http2")] use deboa::request::Http2Request; use deboa::{ conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection}, - errors::{DeboaError, RequestError}, + dns::DnsResolver, + errors::{ConnectionError, DeboaError, RequestError}, response::DeboaResponse, Result, }; #[cfg(feature = "http3")] use deboa_h3::generic::Http3Request; +use futures_timeout::TimeoutFutureExt; use http::{Request, Version}; use hyper_body_utils::HttpBody; -use std::marker::PhantomData; +use std::{borrow::Cow, marker::PhantomData, time::Duration}; /// Connection pooling for efficient HTTP connections. /// @@ -45,11 +49,6 @@ use std::marker::PhantomData; /// - Configurable pool size (coming soon) pub mod pool; -/// Stream module for runtime-specific stream implementations. -/// -/// This module provides stream implementations for different runtimes (Tokio, Smol, etc.). -pub(crate) mod stream; - #[cfg(feature = "http1")] pub(crate) type Http1Connection = BaseHttpConnection; #[cfg(feature = "http2")] @@ -111,20 +110,8 @@ impl DeboaConnection { pub fn http3(conn: Http3Connection) -> Self { DeboaConnection::Http3(Box::new(conn)) } -} -impl HttpConnectionDispatcher for DeboaConnection { - /// Send a request through the connection. - /// - /// # Arguments - /// - /// * `url` - The URL to send the request to. - /// * `request` - The request to send. - /// - /// # Returns - /// - /// * `Result` - The response from the server. - async fn send_request(&mut self, request: Request) -> Result { + async fn send(&mut self, request: Request) -> Result { match self { #[cfg(feature = "http1")] DeboaConnection::Http1(ref mut conn) => { @@ -178,29 +165,177 @@ impl HttpConnectionDispatcher for DeboaConnection { } } +impl HttpConnectionDispatcher for DeboaConnection { + /// Send a request through the connection. + /// + /// # Arguments + /// + /// * `url` - The URL to send the request to. + /// * `request` - The request to send. + /// + /// # Returns + /// + /// * `Result` - The response from the server. + async fn send_request( + &mut self, + request: Request, + timeout: Duration, + ) -> Result { + self.send(request) + .timeout(timeout) + .await + .map_err(|_| { + DeboaError::Request(RequestError::Send { message: "Request timed out".to_string() }) + })? + } +} + /// Connection factory. pub struct ConnectionFactory {} impl ConnectionFactory { /// Create a new connection. - pub async fn create_connection<'a>( - protocol: &Version, + pub async fn create_connection<'a, D>( config: &'a ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, - ) -> Result { - let conn = match protocol { + dns_resolver: &D, + ) -> Result + where + D: DnsResolver, + { + let ips = dns_resolver + .resolve( + config + .host() + .to_string(), + config.port(), + ) + .await?; + let ips = if config + .client_bind_addr() + .is_ipv4() + { + ips.into_iter() + .filter(|ip| ip.is_ipv4()) + .collect::>() + } else { + ips.into_iter() + .filter(|ip| ip.is_ipv6()) + .collect::>() + }; + + let Some(ip) = ips.first() else { + return Err(DeboaError::Request(RequestError::Send { + message: format!("No IP addresses found for hostname: {}", config.host()), + })); + }; + + #[cfg(any(feature = "http1", feature = "http2"))] + let conn_pair = { + let tcp_stream = smol::net::TcpStream::connect(format!("{}:{}", ip, config.port())) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tcp { message: e.to_string() }) + })?; + let use_tls = config.scheme() == "https" || config.scheme() == "wss"; + if !use_tls { + (Version::HTTP_11, SmolStream::Plain(tcp_stream)) + } else { + #[cfg(feature = "rust-tls")] + { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); + + let Some(alpn) = stream + .get_ref() + .1 + .alpn_protocol() + else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "No protocols available".to_string(), + })); + }; + + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + #[cfg(feature = "http1")] + "http1.1" => Version::HTTP_11, + #[cfg(feature = "http2")] + "h2" => Version::HTTP_2, + #[cfg(feature = "http1")] + "h3" => Version::HTTP_3, + _ => panic!("Unsupported protocol"), + }; + + (version, SmolStream::Tls(stream)) + } + + #[cfg(feature = "native-tls")] + { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + SmolStream::Tls(stream) + } + } + }; + + let conn = match conn_pair.0 { #[cfg(feature = "http1")] - &Version::HTTP_11 => { - let conn = Http1Connection::connect(config).await?; + Version::HTTP_11 => { + let conn = Http1Connection::connect(conn_pair.1).await?; DeboaConnection::http1(conn) } #[cfg(feature = "http2")] - &Version::HTTP_2 => { - let conn = Http2Connection::connect(config).await?; + Version::HTTP_2 => { + let conn = Http2Connection::connect(conn_pair.1).await?; DeboaConnection::http2(conn) } - #[cfg(all(feature = "http3", feature = "rust-tls"))] - &Version::HTTP_3 => { - let conn = Http3Connection::connect(&config).await?; + #[cfg(feature = "http3")] + Version::HTTP_3 => { + let stream = { + use crate::client::tls::rustls::udp::connect; + #[cfg(feature = "rust-tls")] + use crate::client::tls::rustls::TlsConnectionBuilder; + use deboa::errors::ConnectionError; + use quinn::Endpoint; + use std::net::SocketAddr; + + let mut client_endpoint = Endpoint::client(SocketAddr::new( + *config.client_bind_addr(), + 0, + )) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { message: e.to_string() }) + })?; + + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + connect( + tls_config, + &mut client_endpoint, + SocketAddr::new(*ip, config.port()), + config.host(), + ) + .await? + }; + + let conn = Http3Connection::connect(stream).await?; DeboaConnection::http3(conn) } _ => { diff --git a/deboa-smol/src/client/http/conn/pool.rs b/deboa-smol/src/client/http/conn/pool.rs index 2765899c..0549578c 100644 --- a/deboa-smol/src/client/http/conn/pool.rs +++ b/deboa-smol/src/client/http/conn/pool.rs @@ -2,9 +2,14 @@ use crate::{ cert::{DeboaCertificate, DeboaIdentity}, client::http::conn::{ConnectionConfig, ConnectionFactory, DeboaConnection}, }; -use deboa::Result; +use deboa::{ + dns::DnsResolver, + errors::{ConnectionError, DeboaError}, + Result, +}; +use futures_timeout::TimeoutFutureExt; use hashbrown::HashMap; -use time::Duration; +use std::time::Duration; /// Struct that represents the HTTP connection pool. /// @@ -27,7 +32,7 @@ impl Default for HttpConnectionPool { fn default() -> Self { Self { max_idle_connections: 5, - keep_alive_duration: Duration::minutes(5), + keep_alive_duration: Duration::from_mins(5), connections: HashMap::new(), } } @@ -76,15 +81,14 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { .len() as u32 } - async fn create_connection<'a>( - &'a mut self, + async fn create_connection<'a, D>( + &mut self, config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result<&'a mut Self::ConnectionDispather> { - if self.max_idle_connections == 0 { - self.connections - .clear(); - } - + dns_resolver: &D, + ) -> Result<&mut DeboaConnection> + where + D: DnsResolver, + { let host = config.host(); if self .connections @@ -98,8 +102,18 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { } log::debug!("Creating new connection for {}", host); - let connection = - ConnectionFactory::create_connection(config.protocol_version(), config).await?; + let connection = ConnectionFactory::create_connection(config, dns_resolver) + .timeout(config.connection_timeout()) + .await + .map_err(|_| { + DeboaError::Connection(ConnectionError::Timeout { + message: format!( + "Connection to {} timed out after {:?}", + host, + config.connection_timeout() + ), + }) + })??; self.connections .insert(host.to_string(), connection); diff --git a/deboa-smol/src/client/http/conn/stream/mod.rs b/deboa-smol/src/client/http/conn/stream/mod.rs deleted file mode 100644 index e323fdda..00000000 --- a/deboa-smol/src/client/http/conn/stream/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub(crate) mod plain; -pub(crate) use plain::*; - -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) mod tls; -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) use tls::*; diff --git a/deboa-smol/src/client/http/conn/stream/plain.rs b/deboa-smol/src/client/http/conn/stream/plain.rs deleted file mode 100644 index a31a24bb..00000000 --- a/deboa-smol/src/client/http/conn/stream/plain.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::rt::stream::SmolStream; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use smol::net::TcpStream; -use std::net::IpAddr; - -pub(crate) async fn create_stream(addr: IpAddr, host: &str, port: u16) -> Result { - let tcp_stream = TcpStream::connect((addr, port)).await; - let tcp_stream = match tcp_stream { - Ok(tcp_stream) => tcp_stream, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Tcp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - }; - - Ok(tcp_stream) -} - -pub(crate) async fn plain_connection(addr: IpAddr, host: &str, port: u16) -> Result { - let stream = create_stream(addr, host, port).await?; - Ok(SmolStream::Plain(stream)) -} diff --git a/deboa-smol/src/client/http/conn/stream/tls/mod.rs b/deboa-smol/src/client/http/conn/stream/tls/mod.rs deleted file mode 100644 index 4632c49b..00000000 --- a/deboa-smol/src/client/http/conn/stream/tls/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[cfg(feature = "native-tls")] -mod native; - -#[cfg(feature = "rust-tls")] -mod rustls; - -#[cfg(feature = "rust-tls")] -pub(crate) use rustls::*; - -#[cfg(feature = "native-tls")] -pub(crate) use native::*; diff --git a/deboa-smol/src/client/http/conn/stream/tls/native.rs b/deboa-smol/src/client/http/conn/stream/tls/native.rs deleted file mode 100644 index 77ed3bde..00000000 --- a/deboa-smol/src/client/http/conn/stream/tls/native.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::net::IpAddr; - -use crate::{ - cert::{Certificate as DeboaCertificate, Identity as DeboaIdentity}, - client::http::conn::stream::create_stream, - rt::stream::SmolStream, -}; -use async_native_tls::{Certificate, Identity, TlsConnector}; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; - -pub(crate) async fn tls_connection( - ip: IpAddr, - host: &str, - port: u16, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: &[&str], -) -> Result { - let socket = create_stream(ip, host, port).await?; - let builder = TlsConnector::new(); - - let builder = if skip_server_verification { - builder - .danger_accept_invalid_certs(true) - .danger_accept_invalid_hostnames(true) - } else { - builder - }; - - let builder = builder.request_alpns(&alpn); - - let builder = if let Some(ca) = certificate { - let cert: std::result::Result = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - builder.add_root_certificate(cert.unwrap()) - } else { - builder - }; - - let builder = if let Some(identity) = identity { - let ident: std::result::Result = identity.try_into(); - if let Err(e) = ident { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - builder.identity(ident.unwrap()) - } else { - builder - }; - - let stream = builder - .connect(host.to_string(), socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(SmolStream::Tls(stream)) -} diff --git a/deboa-smol/src/client/http/conn/stream/tls/rustls.rs b/deboa-smol/src/client/http/conn/stream/tls/rustls.rs deleted file mode 100644 index 09fc80a7..00000000 --- a/deboa-smol/src/client/http/conn/stream/tls/rustls.rs +++ /dev/null @@ -1,213 +0,0 @@ -use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::stream::plain::create_stream, - rt::stream::SmolStream, -}; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use futures_rustls::TlsConnector; -use rustls::{pki_types::ServerName, ClientConfig}; -use rustls_pki_types::{CertificateDer, PrivateKeyDer}; -use std::{net::IpAddr, sync::Arc}; - -pub(crate) async fn tls_connection<'a>( - ip: IpAddr, - host: &str, - port: u16, - identity: &'a Option, - certificate: &'a Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result { - let socket = create_stream(ip, host, port).await?; - let config = setup_rust_tls(host, identity, certificate, skip_server_verification, alpn)?; - let connector = TlsConnector::from(Arc::new(config)); - let hostname = ServerName::try_from(host.to_string()); - - if let Err(e) = hostname { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: e.to_string(), - })); - } - - let stream = connector - .connect(hostname.unwrap(), socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(SmolStream::Tls(Box::new(stream))) -} - -pub(crate) fn default_provider() -> Arc { - #[cfg(feature = "__rustls_aws_lc_rs")] - let provider = rustls::crypto::aws_lc_rs::default_provider(); - #[cfg(feature = "__rustls_ring")] - let provider = rustls::crypto::ring::default_provider(); - Arc::new(provider) -} - -pub(crate) fn setup_rust_tls<'a>( - host: &str, - identity: &'a Option, - certificate: &'a Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result { - let provider = default_provider(); - - if skip_server_verification { - use verify::SkipServerVerification; - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - return Ok(config); - } - - #[cfg(feature = "__webpki_rustls_verifier")] - let config = { - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version"); - - let mut root_store = - rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; - if let Some(ca) = certificate { - let cert = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - let result = root_store.add(cert.unwrap()); - if let Err(e) = result { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not add CA certificate to the store: {}", e), - })); - } - - config.with_root_certificates(root_store) - } else { - config.with_root_certificates(root_store) - } - }; - - #[cfg(feature = "__platform_rustls_verifier")] - let config = { - use rustls_platform_verifier::Verifier; - let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); - rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(Arc::new(verifier)) - }; - - let mut config = if let Some(id) = identity { - let pair = id.try_into(); - if let Err(e) = pair { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - - let pair: (CertificateDer<'static>, PrivateKeyDer<'static>) = pair.unwrap(); - - config - .with_client_auth_cert(vec![pair.0], pair.1) - .expect("Failed to set client identity") - } else { - config.with_no_client_auth() - }; - - config.enable_early_data = true; - - config.alpn_protocols = alpn; - - Ok(config) -} - -pub(crate) mod verify { - use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; - use std::sync::Arc; - - #[derive(Debug)] - pub(crate) struct SkipServerVerification(Arc); - - impl SkipServerVerification { - pub(crate) fn new() -> Arc { - let provider = super::default_provider(); - Arc::new(Self(provider)) - } - } - - impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> std::result::Result - { - Ok(rustls::client::danger::ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result - { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result - { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0 - .signature_verification_algorithms - .supported_schemes() - } - } -} diff --git a/deboa-smol/src/client/http/http1.rs b/deboa-smol/src/client/http/http1.rs index 99aa3db7..f0acceb9 100644 --- a/deboa-smol/src/client/http/http1.rs +++ b/deboa-smol/src/client/http/http1.rs @@ -1,19 +1,15 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::alpn; -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::client::http::conn::stream::tls_connection; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http1Connection}, + client::http::conn::{BaseHttpConnection, Http1Connection}, + rt::stream::SmolStream, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http1Request, Result, }; use http::version::Version; use hyper::client::conn::http1::handshake; -use hyper_body_utils::HttpBody; use smol_hyper::rt::FuturesIo; impl HttpConnection for Http1Connection { @@ -24,46 +20,20 @@ impl HttpConnection for Http1Connection { } impl ProtoConnection for Http1Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http1Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = SmolStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_11 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(any(feature = "rust-tls", feature = "native-tls"))] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(FuturesIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] - let stream = plain_connection(config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(FuturesIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; smol::spawn(async move { match conn @@ -71,7 +41,9 @@ impl ProtoConnection for Http1Connection { .await { Ok(_) => (), - Err(_err) => {} + Err(err) => { + log::error!("Error: {:#}", err) + } }; }) .detach(); diff --git a/deboa-smol/src/client/http/http2.rs b/deboa-smol/src/client/http/http2.rs index 5d9f18cc..99f2927f 100644 --- a/deboa-smol/src/client/http/http2.rs +++ b/deboa-smol/src/client/http/http2.rs @@ -1,20 +1,15 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::alpn; -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::client::http::conn::stream::tls_connection; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http2Connection}, - rt::executor::SmolExecutor, + client::http::conn::{BaseHttpConnection, Http2Connection}, + rt::{executor::SmolExecutor, stream::SmolStream}, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http2Request, Result, }; use http::version::Version; use hyper::client::conn::http2::handshake; -use hyper_body_utils::HttpBody; use smol_hyper::rt::FuturesIo; impl HttpConnection for Http2Connection { @@ -25,54 +20,27 @@ impl HttpConnection for Http2Connection { } impl ProtoConnection for Http2Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http2Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = SmolStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_2 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(any(feature = "rust-tls", feature = "native-tls"))] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(SmolExecutor::new(), FuturesIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] - let stream = plain_connection(*config.ip(), config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(SmolExecutor::new(), FuturesIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; smol::spawn(async move { match conn.await { Ok(_) => (), Err(err) => { - println!("Error: {:#}", err) + log::error!("Error: {:#}", err) } - _ => {} }; }) .detach(); diff --git a/deboa-smol/src/client/http/http3.rs b/deboa-smol/src/client/http/http3.rs index 9aabdb0b..024a4f75 100644 --- a/deboa-smol/src/client/http/http3.rs +++ b/deboa-smol/src/client/http/http3.rs @@ -1,65 +1,13 @@ -use crate::{ - alpn, - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{BaseHttpConnection, Http3Connection}, -}; +use crate::client::http::conn::{BaseHttpConnection, Http3Connection}; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, errors::{ConnectionError, DeboaError}, Result, }; use deboa_h3::generic::{Http3Request, SendRequest}; use futures::future; +use h3_quinn::Connection; use http::version::Version; -use hyper_body_utils::HttpBody; -use quinn::{crypto::rustls::QuicClientConfig, Endpoint}; -use std::{ - net::{IpAddr, SocketAddr}, - sync::Arc, -}; - -async fn lookup_and_connect( - ip: IpAddr, - host: &str, - port: u16, - client_endpoint: &Endpoint, -) -> std::result::Result { - let conn = client_endpoint.connect(SocketAddr::new(ip, port), host); - - let conn = match conn { - Ok(conn) => conn, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }; - - let conn = conn.await; - - let conn = match conn { - Ok(conn) => conn, - Err(e) => match e { - quinn::ConnectionError::TransportError(e) => { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - _ => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }, - }; - - let quinn_conn: h3_quinn::Connection = h3_quinn::Connection::new(conn); - - Ok(quinn_conn) -} impl HttpConnection for Http3Connection { type Sender = Http3Request; @@ -69,77 +17,18 @@ impl HttpConnection for Http3Connection { } impl ProtoConnection for Http3Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http3Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = Connection; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_3 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - let client_endpoint = Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0)); - - if let Err(e) = client_endpoint { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let mut client_endpoint = client_endpoint.unwrap(); - - let tls_config = setup_rust_tls( - config.host(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - )?; - - let quic_config = QuicClientConfig::try_from(tls_config); - if let Err(e) = quic_config { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let quic_config = quic_config.unwrap(); - - let client_config = quinn::ClientConfig::new(Arc::new(quic_config)); - client_endpoint.set_default_client_config(client_config); - - let result = - lookup_and_connect(*config.ip(), config.host(), config.port(), &client_endpoint).await; - - if let Err(e) = result { - return Err(e); - } - - let conn = result.unwrap(); - - let client = h3::client::new(conn).await; - - if let Err(e) = client { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let (mut conn, sender) = client.unwrap(); + async fn connect(stream: Self::RuntimeStream) -> Result { + let (mut conn, sender) = h3::client::new(stream) + .await + .map_err(|e| DeboaError::Connection(ConnectionError::Udp { message: e.to_string() }))?; smol::spawn(async move { future::poll_fn(|cx| conn.poll_close(cx)).await; diff --git a/deboa-smol/src/client/mod.rs b/deboa-smol/src/client/mod.rs index af0eb4d5..004a025a 100644 --- a/deboa-smol/src/client/mod.rs +++ b/deboa-smol/src/client/mod.rs @@ -3,5 +3,6 @@ /// This module provides DNS resolution functionality for the Deboa HTTP client.pub(crate) mod dns; pub mod dns; pub mod http; +pub mod tls; #[cfg(feature = "websockets")] pub mod ws; diff --git a/deboa-smol/src/client/tls/mod.rs b/deboa-smol/src/client/tls/mod.rs new file mode 100644 index 00000000..afc96df1 --- /dev/null +++ b/deboa-smol/src/client/tls/mod.rs @@ -0,0 +1,9 @@ +//! TLS transport implementations for the Deboa HTTP client. +//! +//! This module provides TLS functionality for secure HTTP connections. +//! It supports both native-tls and rustls backends. + +#[cfg(feature = "native-tls")] +pub mod native; +#[cfg(feature = "rust-tls")] +pub mod rustls; diff --git a/deboa-smol/src/client/tls/native.rs b/deboa-smol/src/client/tls/native.rs new file mode 100644 index 00000000..4e06feab --- /dev/null +++ b/deboa-smol/src/client/tls/native.rs @@ -0,0 +1,125 @@ +//! TLS implementation using native-tls + +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use async_native_tls::{Certificate, Identity, TlsConnector, TlsStream}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use smol::net::TcpStream; + +#[inline] +pub(crate) fn alpn() -> &'static [&'static str] { + &[ + #[cfg(feature = "http3")] + "h3", + #[cfg(feature = "http2")] + "h2", + #[cfg(feature = "http1")] + "http/1.1", + ] +} + +/// Builder for TLS connections using native-tls +pub struct TlsConnectionBuilder<'a> { + tcp_stream: TcpStream, + host: &'a str, + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: &'a [&'a str], +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Creates a new TLS connection builder + pub fn new(tcp_stream: TcpStream, host: &'a str) -> Self { + Self { + tcp_stream, + host, + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + } + } + + /// Sets the client identity for mutual TLS authentication + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Sets the server certificate for verification + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skips server certificate verification (use with caution) + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Sets the ALPN protocols to use + pub fn alpn(mut self, alpn: &'a [&str]) -> Self { + self.alpn = alpn; + self + } + + /// Establishes the TLS connection + pub async fn connect(self) -> Result> { + let builder = TlsConnector::new(); + + let builder = if self.skip_server_verification { + builder + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + } else { + builder + }; + + let builder = builder.request_alpns(self.alpn); + + let builder = if let Some(ca) = self.certificate { + let cert: Certificate = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + builder.add_root_certificate(cert) + } else { + builder + }; + + let builder = if let Some(identity) = self.identity { + let ident: Identity = identity + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + builder.identity(ident) + } else { + builder + }; + + let stream = builder + .connect( + self.host + .to_string(), + self.tcp_stream, + ) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }); + + stream + } +} diff --git a/deboa-smol/src/client/tls/rustls.rs b/deboa-smol/src/client/tls/rustls.rs new file mode 100644 index 00000000..b4b6e778 --- /dev/null +++ b/deboa-smol/src/client/tls/rustls.rs @@ -0,0 +1,326 @@ +//! TLS implementation using rustls +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use rustls::{ + crypto::CryptoProvider, + pki_types::{CertificateDer, PrivateKeyDer}, + ClientConfig, +}; + +pub(crate) fn default_provider() -> CryptoProvider { + #[cfg(feature = "__rustls_aws_lc_rs")] + return rustls::crypto::aws_lc_rs::default_provider(); + #[cfg(feature = "__rustls_ring")] + return rustls::crypto::ring::default_provider(); +} + +#[inline] +pub(crate) fn alpn() -> Vec> { + vec![ + #[cfg(feature = "http3")] + b"h3".to_vec(), + #[cfg(feature = "http2")] + b"h2".to_vec(), + #[cfg(feature = "http1")] + b"http/1.1".to_vec(), + ] +} + +/// Builder for TLS connections using rustls +pub struct TlsConnectionBuilder<'a> { + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: Vec>, + provider: CryptoProvider, +} + +impl Default for TlsConnectionBuilder<'_> { + fn default() -> Self { + Self { + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + provider: default_provider(), + } + } +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Set the identity to use for the connection + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Set the certificate to use for the connection + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skip server verification + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Set the ALPN protocols to use for the connection + pub fn alpn(mut self, alpn: Vec>) -> Self { + self.alpn = alpn; + self + } + + /// Build the TLS client configuration + pub fn build_config(self) -> Result { + let client_config = { + if self.skip_server_verification { + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verify::SkipServerVerification::new( + self.provider, + )) + .with_no_client_auth() + } else { + #[cfg(feature = "__webpki_rustls_verifier")] + let config = { + let config = ClientConfig::builder_with_provider(self.provider.into()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })?; + + let mut root_store = + rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; + let config = if let Some(ca) = self.certificate { + let cert = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + + root_store + .add(cert) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!( + "Could not add CA certificate to the store: {}", + e + ), + }) + })?; + + config.with_root_certificates(root_store) + } else { + config.with_root_certificates(root_store) + }; + + config + }; + + #[cfg(feature = "__platform_rustls_verifier")] + let config = { + use rustls_platform_verifier::BuilderVerifierExt; + rustls::ClientConfig::builder_with_provider(default_provider()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })? + .with_platform_verifier() + }; + + let mut config = if let Some(id) = self.identity { + let pair: (CertificateDer<'_>, PrivateKeyDer<'_>) = id + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + + config + .with_client_auth_cert(vec![pair.0], pair.1) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set client identity: {}", e), + }) + })? + } else { + config.with_no_client_auth() + }; + + config.enable_early_data = true; + + config.alpn_protocols = self.alpn; + + config + } + }; + + Ok(client_config) + } +} + +#[cfg(any(feature = "http1", feature = "http2"))] +/// TCP connection module for TLS +pub mod tcp { + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use futures_rustls::{client::TlsStream, TlsConnector}; + use rustls::ClientConfig; + use rustls_pki_types::ServerName; + use smol::net::TcpStream; + use std::sync::Arc; + + /// Establish a TLS connection over TCP + pub async fn connect( + config: ClientConfig, + inner_stream: TcpStream, + host: &str, + ) -> Result> { + let connector = TlsConnector::from(Arc::new(config)); + + let hostname = ServerName::try_from(host.to_string()) + .map_err(|e| DeboaError::Connection(ConnectionError::Tls { message: e.to_string() }))?; + + connector + .connect(hostname, inner_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }) + } +} + +#[cfg(feature = "http3")] +/// UDP connection module for TLS +pub mod udp { + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use h3_quinn::Connection; + use quinn::{crypto::rustls::QuicClientConfig, Endpoint}; + use rustls::ClientConfig; + use std::{net::SocketAddr, sync::Arc}; + + /// Establish a TLS connection over UDP + pub async fn connect( + config: ClientConfig, + endpoint: &mut Endpoint, + socket_addr: SocketAddr, + host: &str, + ) -> Result { + let quic_config = QuicClientConfig::try_from(config).map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not create QUIC client config: {}", e), + }) + })?; + + let client_config = quinn::ClientConfig::new(Arc::new(quic_config)); + endpoint.set_default_client_config(client_config); + + let conn = endpoint + .connect(socket_addr, host) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let conn = conn + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let quinn_conn = h3_quinn::Connection::new(conn); + + Ok(quinn_conn) + } +} + +pub(crate) mod verify { + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::CryptoProvider, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + use std::sync::Arc; + + #[derive(Debug)] + pub(crate) struct SkipServerVerification(CryptoProvider); + + impl SkipServerVerification { + pub(crate) fn new(provider: CryptoProvider) -> Arc { + Arc::new(Self(provider)) + } + } + + impl ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0 + .signature_verification_algorithms + .supported_schemes() + } + } +} diff --git a/deboa-smol/src/lib.rs b/deboa-smol/src/lib.rs index e899b76f..0ea64a39 100644 --- a/deboa-smol/src/lib.rs +++ b/deboa-smol/src/lib.rs @@ -24,6 +24,9 @@ compile_error!( #[cfg(all(feature = "native-tls", feature = "rust-tls"))] compile_error!("You cannot enable native-tls and rust-tls features at the same time."); +#[cfg(all(not(any(feature = "native-tls", feature = "rust-tls")), feature = "http2"))] +compile_error!("HTTP2 requires native-tls or rust-tls support."); + #[cfg(all(feature = "native-tls", feature = "http3"))] compile_error!("HTTP3 is not supported within native-tls runtime."); @@ -37,32 +40,6 @@ use crate::{ client::{dns::DefaultDnsResolver, http::conn::pool::HttpConnectionPool}, }; -#[cfg(feature = "rust-tls")] -#[inline] -pub(crate) fn alpn() -> Vec> { - vec![ - #[cfg(feature = "http2")] - b"h2".to_vec(), - #[cfg(feature = "http1")] - b"http/1.1".to_vec(), - #[cfg(feature = "http3")] - b"h3".to_vec(), - ] -} - -#[cfg(feature = "native-tls")] -#[inline] -pub(crate) fn alpn() -> &'static [&'static str] { - &[ - #[cfg(feature = "http2")] - "h2", - #[cfg(feature = "http1")] - "http/1.1", - #[cfg(feature = "http3")] - "h3", - ] -} - /// Certificate management module for handling SSL/TLS certificates. pub mod cert; /// Internal module for HTTP and Websockets clients implementations. diff --git a/deboa-smol/src/rt/stream.rs b/deboa-smol/src/rt/stream.rs index fca640de..8a7fd537 100644 --- a/deboa-smol/src/rt/stream.rs +++ b/deboa-smol/src/rt/stream.rs @@ -12,7 +12,7 @@ use std::{ }; /// A stream that can be either plain TCP or TLS-secured. -pub(crate) enum SmolStream { +pub enum SmolStream { /// A plain TCP connection. Plain(TcpStream), diff --git a/deboa-smol/tests/base/get.rs b/deboa-smol/tests/base/get.rs index 9b478b26..4e8b574d 100644 --- a/deboa-smol/tests/base/get.rs +++ b/deboa-smol/tests/base/get.rs @@ -32,12 +32,19 @@ async fn test_get_http( #[rstest] #[test_attr(apply(test))] async fn test_get_http_skip_verification( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { + let client = Client::builder() + .certificate(DeboaCertificate::from_slice( + deboa_test_utils::common::helpers::CA_CERT, + ContentEncoding::DER, + )) + .skip_cert_verification(true) + .build(); + deboa_test_utils::base::get::test_skip_cert_verification( - &create_client, + &client, &mut create_server.await, protocol_version, true, @@ -48,12 +55,15 @@ async fn test_get_http_skip_verification( #[rstest] #[test_attr(apply(test))] async fn test_get_http_verify( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { + let client = Client::builder() + .skip_cert_verification(false) + .build(); + deboa_test_utils::base::get::test_skip_cert_verification( - &create_client, + &client, &mut create_server.await, protocol_version, false, diff --git a/deboa-smol/tests/common/helpers.rs b/deboa-smol/tests/common/helpers.rs index eb6032f0..c256c30f 100644 --- a/deboa-smol/tests/common/helpers.rs +++ b/deboa-smol/tests/common/helpers.rs @@ -74,8 +74,12 @@ pub async fn tls_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) - .protocol_version(protocol_version()) + .interface( + interface + .parse() + .unwrap(), + ) + .protos(vec![protocol_version()]) .with_random_port() .cert(server_cert.to_vec()) .key(server_key.to_vec()) @@ -104,7 +108,11 @@ pub async fn plain_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) + .interface( + interface + .parse() + .unwrap(), + ) .protocol_version(protocol_version()) .with_random_port() .build(); diff --git a/deboa-test-utils/src/base/get.rs b/deboa-test-utils/src/base/get.rs index 52229c5b..cc13a4cc 100644 --- a/deboa-test-utils/src/base/get.rs +++ b/deboa-test-utils/src/base/get.rs @@ -60,7 +60,7 @@ where } pub async fn test_skip_cert_verification( - _client: &Client>, + client: &Client>, server: &mut EasyHttpMock, protocol_version: http::Version, skip: bool, @@ -83,9 +83,6 @@ where server .register_mock(mock) .await?; - let client: Client> = Client::builder() - .skip_cert_verification(skip) - .build(); let request = DeboaRequest::get(server.url("/posts/1"))? .version(protocol_version) @@ -107,7 +104,6 @@ where } Version::HTTP_3 => { let error = DeboaError::Connection(ConnectionError::Udp { - host: "localhost".to_string(), message: "Could not connect to server: aborted by peer: the cryptographic handshake failed: error 120: peer doesn't support any known protocol".to_string(), }); expect(response.unwrap_err()).to_be(eq(error)); diff --git a/deboa-tokio/Cargo.toml b/deboa-tokio/Cargo.toml index d7a79f52..2f691072 100644 --- a/deboa-tokio/Cargo.toml +++ b/deboa-tokio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deboa-tokio" -version = "0.1.2" +version = "0.1.3" edition.workspace = true authors.workspace = true repository.workspace = true @@ -40,6 +40,7 @@ rust-tls = [ "dep:rustls-pki-types", ] +# rustls verifiers webpki-rustls-verifier = ["__webpki_rustls_verifier"] platform-rustls-verifier = ["__platform_rustls_verifier"] @@ -53,28 +54,22 @@ aws-lc-rustls-provider = ["__rustls_aws_lc_rs"] ring-rustls-provider = ["__rustls_ring"] default-rustls-provider = ["__rustls_aws_lc_rs"] -__rustls_aws_lc_rs = [ - "futures-rustls/aws-lc-rs", - "quinn/rustls-aws-lc-rs", -] -__rustls_ring = [ - "futures-rustls/ring", - "quinn/rustls-ring", -] +__rustls_aws_lc_rs = ["rustls/aws-lc-rs", "quinn/rustls-aws-lc-rs"] +__rustls_ring = ["rustls/ring", "quinn/rustls-ring"] -# tls implementations +# native-tls implementations native-tls = ["async-native-tls/runtime-tokio"] # protocols -http1 = ["hyper/http1", "hyper-util/http1"] -http2 = ["hyper/http2", "hyper-util/http2"] +http1 = ["rustls/tls12", "hyper/http1", "hyper-util/http1"] +http2 = ["rustls/tls12", "hyper/http2", "hyper-util/http2"] http3 = [ "deboa-h3/generic", "dep:h3", "dep:h3-quinn", "quinn/runtime-tokio", - "hyper-body-utils/http3", "hyper-body-utils/generic", + "hyper-body-utils/generic-h3", ] websockets = ["ws-framer/http", "ws-framer/alloc"] @@ -83,13 +78,12 @@ websockets = ["ws-framer/http", "ws-framer/alloc"] async-executor = { version = "1.13.3", optional = true, default-features = false } async-lock = "3.4.2" async-native-tls = { version = "0.6.0", optional = true, default-features = false } -base64 = {version = "0.23.0" } +base64 = { version = "0.23.0" } bytes = { version = "1.11", default-features = false } cookie = { version = "0.18.1", default-features = false } deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } futures = "0.3.31" -futures-rustls = { version = "0.26.0", optional = true, default-features = false } futures-util = { version = "0.3.31", optional = true, default-features = false } h3 = { version = "0.0.8", optional = true, default-features = false } h3-quinn = { version = "0.0.10", optional = true, default-features = false } @@ -107,7 +101,6 @@ hyper-util = { version = "0.1.20", features = [ indexmap = "2.11.4" log = "0.4.32" minimime = "1.0.0" -mockall = "0.15.0" quinn = { version = "0.11.7", optional = true, features = [ "runtime-tokio", ], default-features = false } @@ -118,12 +111,13 @@ rustls-native-certs = { version = "0.8.4", optional = true, default-features = f rustls-pki-types = { version = "1.14.1", optional = true, default-features = false } rustls-platform-verifier = { version = "0.7.0", optional = true, default-features = false } serde = { version = "1.0.217", features = ["derive"] } -tackle = { version = "0.1.1"} +tackle = { version = "0.1.1" } thiserror = "2.0.17" time = { version = "0.3.51", default-features = false } -tokio = { version = "1.38.1", features = [ +tokio = { version = "1.53.1", features = [ "macros", "fs", + "time", ], default-features = false } tokio-rustls = { version = "0.26.4", optional = true, default-features = false } tokio-util = { version = "0.7.11", features = ["io"], default-features = false } @@ -144,8 +138,8 @@ easyhttpmock-vetis-tokio = { version = "0.1.0", features = ["http2", "rust-tls"] futures-util = "0.3.31" multer = "3.1.0" rstest = "0.26.1" -tokio = { version = "1.38.1", features = [ +tokio = { version = "1.53.1", features = [ "macros", "fs", - "net" + "net", ], default-features = false } diff --git a/deboa-tokio/README.md b/deboa-tokio/README.md index ac80e516..77c03735 100644 --- a/deboa-tokio/README.md +++ b/deboa-tokio/README.md @@ -61,7 +61,7 @@ http = "1.3.1" ## Usage -```rust +```rust, ignore use deboa::{ HttpClient, request::{DeboaRequest, FetchWith, get}, diff --git a/deboa-tokio/src/cert.rs b/deboa-tokio/src/cert.rs index 9f6700c9..2d8ff0ac 100644 --- a/deboa-tokio/src/cert.rs +++ b/deboa-tokio/src/cert.rs @@ -92,7 +92,7 @@ impl deboa::cert::Identity for DeboaIdentity { &self.cert } - fn ket(&self) -> &Option> { + fn key(&self) -> &Option> { &self.key } diff --git a/deboa-tokio/src/client/dns.rs b/deboa-tokio/src/client/dns.rs index 264983ec..55b4bf57 100644 --- a/deboa-tokio/src/client/dns.rs +++ b/deboa-tokio/src/client/dns.rs @@ -1,3 +1,7 @@ +//! DNS resolution for the Deboa HTTP client. +//! +//! This module provides DNS resolution functionality for the Deboa HTTP client. + use deboa::{ dns::DnsResolver, errors::{DeboaError::Dns, DnsError}, diff --git a/deboa-tokio/src/client/http/conn/mod.rs b/deboa-tokio/src/client/http/conn/mod.rs index 5860cac5..8c4e4629 100644 --- a/deboa-tokio/src/client/http/conn/mod.rs +++ b/deboa-tokio/src/client/http/conn/mod.rs @@ -16,13 +16,16 @@ //! - Thread-safe connection handling //! ``` use crate::cert::{DeboaCertificate, DeboaIdentity}; +#[cfg(any(feature = "http1", feature = "http2"))] +use crate::rt::stream::TokioStream; #[cfg(feature = "http1")] use deboa::request::Http1Request; #[cfg(feature = "http2")] use deboa::request::Http2Request; use deboa::{ conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection}, - errors::{DeboaError, RequestError}, + dns::DnsResolver, + errors::{ConnectionError, DeboaError, RequestError}, response::DeboaResponse, Result, }; @@ -30,7 +33,9 @@ use deboa::{ use deboa_h3::generic::Http3Request; use http::{Request, Version}; use hyper_body_utils::HttpBody; -use std::marker::PhantomData; +use std::{borrow::Cow, marker::PhantomData, time::Duration}; +#[cfg(any(feature = "http1", feature = "http2"))] +use tokio::net::TcpStream; /// Connection pooling for efficient HTTP connections. /// @@ -45,9 +50,6 @@ use std::marker::PhantomData; /// - Configurable pool size (coming soon) pub mod pool; -/// Stream module for runtime-specific stream implementations. -pub(crate) mod stream; - #[cfg(feature = "http1")] pub(crate) type Http1Connection = BaseHttpConnection; #[cfg(feature = "http2")] @@ -92,20 +94,8 @@ impl DeboaConnection { pub fn http3(conn: Http3Connection) -> Self { DeboaConnection::Http3(Box::new(conn)) } -} -impl HttpConnectionDispatcher for DeboaConnection { - /// Send a request over the connection. - /// - /// # Arguments - /// - /// * `url` - The URL to send the request to. - /// * `request` - The request to send. - /// - /// # Returns - /// - /// * `Result` - The response or error. - async fn send_request(&mut self, request: Request) -> Result { + async fn send(&mut self, request: Request) -> Result { match self { #[cfg(feature = "http1")] DeboaConnection::Http1(ref mut conn) => { @@ -159,6 +149,30 @@ impl HttpConnectionDispatcher for DeboaConnection { } } +impl HttpConnectionDispatcher for DeboaConnection { + /// Send a request over the connection. + /// + /// # Arguments + /// + /// * `url` - The URL to send the request to. + /// * `request` - The request to send. + /// + /// # Returns + /// + /// * `Result` - The response or error. + async fn send_request( + &mut self, + request: Request, + timeout: Duration, + ) -> Result { + tokio::time::timeout(timeout, self.send(request)) + .await + .map_err(|_| { + DeboaError::Request(RequestError::Send { message: "Request timed out".to_string() }) + })? + } +} + /// Struct that represents the connection. /// /// # Fields @@ -181,24 +195,145 @@ pub(crate) struct ConnectionFactory {} impl ConnectionFactory { /// Create a new connection. - pub async fn create_connection<'a>( - protocol: &Version, + pub async fn create_connection<'a, D>( config: &'a ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, - ) -> Result { - let conn = match protocol { + dns_resolver: &D, + ) -> Result + where + D: DnsResolver, + { + //TODO: consider add support to DNS HTTPS record + + let ips = dns_resolver + .resolve( + config + .host() + .to_string(), + config.port(), + ) + .await?; + let ips = if config + .client_bind_addr() + .is_ipv4() + { + ips.into_iter() + .filter(|ip| ip.is_ipv4()) + .collect::>() + } else { + ips.into_iter() + .filter(|ip| ip.is_ipv6()) + .collect::>() + }; + + let Some(ip) = ips.first() else { + return Err(DeboaError::Request(RequestError::Send { + message: format!("No IP addresses found for hostname: {}", config.host()), + })); + }; + + #[cfg(any(feature = "http1", feature = "http2"))] + let conn_pair = { + let tcp_stream = TcpStream::connect(format!("{}:{}", ip, config.port())) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tcp { message: e.to_string() }) + })?; + let use_tls = config.scheme() == "https" || config.scheme() == "wss"; + if !use_tls { + (Version::HTTP_11, TokioStream::Plain(tcp_stream)) + } else { + #[cfg(feature = "rust-tls")] + { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); + + let Some(alpn) = stream + .get_ref() + .1 + .alpn_protocol() + else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "No protocols available".to_string(), + })); + }; + + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => panic!("Unsupported protocol"), + }; + + (version, TokioStream::Tls(stream)) + } + + #[cfg(feature = "native-tls")] + { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + TokioStream::Tls(stream) + } + } + }; + + let conn = match conn_pair.0 { #[cfg(feature = "http1")] - &Version::HTTP_11 => { - let conn = Http1Connection::connect(config).await?; + Version::HTTP_11 => { + let conn = Http1Connection::connect(conn_pair.1).await?; DeboaConnection::http1(conn) } #[cfg(feature = "http2")] - &Version::HTTP_2 => { - let conn = Http2Connection::connect(config).await?; + Version::HTTP_2 => { + let conn = Http2Connection::connect(conn_pair.1).await?; DeboaConnection::http2(conn) } #[cfg(feature = "http3")] - &Version::HTTP_3 => { - let conn = Http3Connection::connect(&config).await?; + Version::HTTP_3 => { + let stream = { + use crate::client::tls::rustls::udp::connect; + #[cfg(feature = "rust-tls")] + use crate::client::tls::rustls::TlsConnectionBuilder; + use quinn::Endpoint; + use std::net::SocketAddr; + + let mut client_endpoint = Endpoint::client(SocketAddr::new( + *config.client_bind_addr(), + 0, + )) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { message: e.to_string() }) + })?; + + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + connect( + tls_config, + &mut client_endpoint, + SocketAddr::new(*ip, config.port()), + config.host(), + ) + .await? + }; + + let conn = Http3Connection::connect(stream).await?; DeboaConnection::http3(conn) } _ => { diff --git a/deboa-tokio/src/client/http/conn/pool.rs b/deboa-tokio/src/client/http/conn/pool.rs index bfaa06dd..f4156650 100644 --- a/deboa-tokio/src/client/http/conn/pool.rs +++ b/deboa-tokio/src/client/http/conn/pool.rs @@ -2,9 +2,13 @@ use crate::{ cert::{DeboaCertificate, DeboaIdentity}, client::http::conn::{ConnectionConfig, ConnectionFactory, DeboaConnection}, }; -use deboa::Result; -use std::collections::HashMap; -use time::Duration; +use deboa::{ + dns::DnsResolver, + errors::{ConnectionError, DeboaError}, + Result, +}; +use hashbrown::HashMap; +use std::time::Duration; /// Struct that represents the HTTP connection pool. /// @@ -27,7 +31,7 @@ impl Default for HttpConnectionPool { fn default() -> Self { Self { max_idle_connections: 5, - keep_alive_duration: Duration::minutes(5), + keep_alive_duration: Duration::from_mins(5), connections: HashMap::new(), } } @@ -76,36 +80,48 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { .len() as u32 } - async fn create_connection<'a>( - &'a mut self, + async fn create_connection<'a, D>( + &mut self, config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result<&'a mut DeboaConnection> { - if self.max_idle_connections == 0 { - self.connections - .clear(); - } - - let host = config.host(); + dns_resolver: &D, + ) -> Result<&mut DeboaConnection> + where + D: DnsResolver, + { + let key = format!("{}:{}", config.host(), config.port()); if self .connections - .contains_key(host) + .contains_key(&key) { - log::debug!("Connection already exists for {}, reusing.", host); + log::debug!("Connection already exists for {}, reusing.", key); return Ok(self .connections - .get_mut(host) + .get_mut(&key) .unwrap()); } - log::debug!("Creating new connection for {}", host); - let connection = - ConnectionFactory::create_connection(config.protocol_version(), config).await?; + log::debug!("Creating new connection for {}", key); + let connection = tokio::time::timeout( + config.connection_timeout(), + ConnectionFactory::create_connection(config, dns_resolver), + ) + .await + .map_err(|_| { + DeboaError::Connection(ConnectionError::Timeout { + message: format!( + "Connection to {} timed out after {:?}", + key, + config.connection_timeout() + ), + }) + })??; self.connections - .insert(host.to_string(), connection); + .insert(key.clone(), connection); + Ok(self .connections - .get_mut(host) + .get_mut(&key) .unwrap()) } } diff --git a/deboa-tokio/src/client/http/conn/stream/mod.rs b/deboa-tokio/src/client/http/conn/stream/mod.rs deleted file mode 100644 index e735122d..00000000 --- a/deboa-tokio/src/client/http/conn/stream/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Plain module for runtime-specific plain implementations. -pub(crate) mod plain; -pub(crate) use plain::*; - -/// TLS module for runtime-specific TLS implementations. -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) mod tls; -#[cfg(all( - any(feature = "rust-tls", feature = "native-tls"), - any(feature = "http1", feature = "http2", feature = "http3") -))] -pub(crate) use tls::*; diff --git a/deboa-tokio/src/client/http/conn/stream/plain.rs b/deboa-tokio/src/client/http/conn/stream/plain.rs deleted file mode 100644 index 1c852cce..00000000 --- a/deboa-tokio/src/client/http/conn/stream/plain.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::net::IpAddr; - -use crate::rt::stream::TokioStream; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use tokio::net::TcpStream; - -pub(crate) async fn create_stream(addr: IpAddr, host: &str, port: u16) -> Result { - let tcp_stream = TcpStream::connect((addr, port)).await; - let tcp_stream = match tcp_stream { - Ok(tcp_stream) => tcp_stream, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Tcp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - }; - - Ok(tcp_stream) -} - -pub(crate) async fn plain_connection(addr: IpAddr, host: &str, port: u16) -> Result { - let stream = create_stream(addr, host, port).await?; - Ok(TokioStream::Plain(stream)) -} diff --git a/deboa-tokio/src/client/http/conn/stream/tls/mod.rs b/deboa-tokio/src/client/http/conn/stream/tls/mod.rs deleted file mode 100644 index 3497239f..00000000 --- a/deboa-tokio/src/client/http/conn/stream/tls/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -#[cfg(feature = "native-tls")] -mod native; - -#[cfg(feature = "rust-tls")] -/// Internal stream handling utilities for connection establishment. -/// Provides low-level connection creation function for secure connections . -/// Used internally by the HTTP connection implementations. -/// -/// # Modules -/// -/// - `tls_connection`: Creates TLS-encrypted connections with optional client certificates -/// -/// # Examples -/// -/// ```compile_fail, rust -/// use deboa::client::conn::stream::tls_connection; -/// -/// // Create a TLS connection -/// let stream = tls_connection("example.com:443", None).await?; -/// ``` -mod rustls; - -#[cfg(feature = "rust-tls")] -pub(crate) use rustls::*; - -#[cfg(feature = "native-tls")] -pub(crate) use native::*; diff --git a/deboa-tokio/src/client/http/conn/stream/tls/native.rs b/deboa-tokio/src/client/http/conn/stream/tls/native.rs deleted file mode 100644 index c0028131..00000000 --- a/deboa-tokio/src/client/http/conn/stream/tls/native.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::{ - cert::{Certificate as DeboaCertificate, Identity as DeboaIdentity}, - client::http::conn::stream::create_stream, - rt::stream::TokioStream, -}; -use async_native_tls::{Certificate, Identity, TlsConnector}; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use std::net::IpAddr; - -pub(crate) async fn tls_connection( - ip: IpAddr, - host: &str, - port: u16, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: &[&str], -) -> Result { - let socket = create_stream(ip, host, port).await?; - let builder = TlsConnector::new(); - - let builder = if skip_server_verification { - builder - .danger_accept_invalid_certs(true) - .danger_accept_invalid_hostnames(true) - } else { - builder - }; - - let builder = builder.request_alpns(alpn); - - let builder = if let Some(ca) = certificate { - let cert: std::result::Result = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - builder.add_root_certificate(cert.unwrap()) - } else { - builder - }; - - let builder = if let Some(identity) = identity { - let ident: std::result::Result = identity.try_into(); - if let Err(e) = ident { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - builder.identity(ident.unwrap()) - } else { - builder - }; - - let stream = builder - .connect(host.to_string(), socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(TokioStream::Tls(stream)) -} diff --git a/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs b/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs deleted file mode 100644 index 0c0f1a00..00000000 --- a/deboa-tokio/src/client/http/conn/stream/tls/rustls.rs +++ /dev/null @@ -1,217 +0,0 @@ -use crate::{ - cert::{Certificate as DeboaCertificate, Identity as DeboaIdentity}, - client::http::conn::stream::create_stream, - rt::stream::TokioStream, -}; -use deboa::{ - errors::{ConnectionError, DeboaError}, - Result, -}; -use rustls::pki_types::ServerName; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls::ClientConfig; -use std::{net::IpAddr, sync::Arc}; -use tokio_rustls::TlsConnector; - -pub(crate) async fn tls_connection( - ip: IpAddr, - host: &str, - port: u16, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result { - let socket = create_stream(ip, host, port).await?; - let config = setup_rust_tls(host, identity, certificate, skip_server_verification, alpn)?; - let connector = TlsConnector::from(Arc::new(config)); - let hostname = ServerName::try_from(host.to_string()); - - if let Err(e) = hostname { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: e.to_string(), - })); - } - - let stream = connector - .connect(hostname.unwrap(), socket) - .await; - - if let Err(e) = stream { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })); - } - - let stream = stream.unwrap(); - Ok(TokioStream::Tls(Box::new(stream))) -} - -pub(crate) fn default_provider() -> Arc { - #[cfg(feature = "__rustls_aws_lc_rs")] - let provider = rustls::crypto::aws_lc_rs::default_provider(); - #[cfg(feature = "__rustls_ring")] - let provider = rustls::crypto::ring::default_provider(); - Arc::new(provider) -} - -pub fn setup_rust_tls( - host: &str, - identity: &Option, - certificate: &Option, - skip_server_verification: bool, - alpn: Vec>, -) -> Result { - let provider = default_provider(); - - if skip_server_verification { - use verify::SkipServerVerification; - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(SkipServerVerification::new()) - .with_no_client_auth(); - return Ok(config); - } - - #[cfg(feature = "__webpki_rustls_verifier")] - let config = { - let config = rustls::ClientConfig::builder_with_provider(provider) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version"); - - let mut root_store = - rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; - if let Some(ca) = certificate { - let cert = ca.try_into(); - if let Err(e) = cert { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid CA certificate: {}", e), - })); - } - - let result = root_store.add(cert.unwrap()); - if let Err(e) = result { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not add CA certificate to the store: {}", e), - })); - } - - config.with_root_certificates(root_store) - } else { - config.with_root_certificates(root_store) - } - }; - - #[cfg(feature = "__platform_rustls_verifier")] - let config = { - use rustls_platform_verifier::Verifier; - let verifier = Verifier::new(provider).expect("Failed to create platform verifier"); - rustls::ClientConfig::builder_with_provider(default_provider()) - .with_protocol_versions(rustls::ALL_VERSIONS) - .expect("Failed to set TLS version") - .dangerous() - .with_custom_certificate_verifier(Arc::new(verifier)) - }; - - let mut config = if let Some(id) = identity { - let pair: std::result::Result< - (CertificateDer<'static>, PrivateKeyDer<'static>), - std::io::Error, - > = id.try_into(); - if let Err(e) = pair { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Invalid client identity: {}", e), - })); - } - - let pair = pair.unwrap(); - - config - .with_client_auth_cert(vec![pair.0], pair.1) - .expect("Failed to set client identity") - } else { - config.with_no_client_auth() - }; - - config.enable_early_data = true; - - config.alpn_protocols = alpn; - - Ok(config) -} - -pub(crate) mod verify { - use rustls::{ - client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - pki_types::{CertificateDer, ServerName, UnixTime}, - }; - use std::sync::Arc; - - #[derive(Debug)] - pub(crate) struct SkipServerVerification(Arc); - - impl SkipServerVerification { - pub(crate) fn new() -> Arc { - let provider = super::default_provider(); - Arc::new(Self(provider)) - } - } - - impl ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> std::result::Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0 - .signature_verification_algorithms - .supported_schemes() - } - } -} diff --git a/deboa-tokio/src/client/http/http1.rs b/deboa-tokio/src/client/http/http1.rs index 484b0f6e..6dc11dce 100644 --- a/deboa-tokio/src/client/http/http1.rs +++ b/deboa-tokio/src/client/http/http1.rs @@ -1,17 +1,15 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::{alpn, client::http::conn::stream::tls_connection}; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http1Connection}, + client::http::conn::{BaseHttpConnection, Http1Connection}, + rt::stream::TokioStream, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http1Request, Result, }; use http::version::Version; use hyper::client::conn::http1::handshake; -use hyper_body_utils::HttpBody; use hyper_util::rt::TokioIo; impl HttpConnection for Http1Connection { @@ -22,46 +20,20 @@ impl HttpConnection for Http1Connection { } impl ProtoConnection for Http1Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http1Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = TokioStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_11 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(any(feature = "rust-tls", feature = "native-tls"))] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(TokioIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] - let stream = plain_connection(config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(TokioIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; tokio::spawn(async move { match conn diff --git a/deboa-tokio/src/client/http/http2.rs b/deboa-tokio/src/client/http/http2.rs index a2b37c71..069a2238 100644 --- a/deboa-tokio/src/client/http/http2.rs +++ b/deboa-tokio/src/client/http/http2.rs @@ -1,17 +1,15 @@ -#[cfg(any(feature = "rust-tls", feature = "native-tls"))] -use crate::{alpn, client::http::conn::stream::tls_connection}; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http2Connection}, + client::http::conn::{BaseHttpConnection, Http2Connection}, + rt::stream::TokioStream, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http2Request, Result, }; use http::version::Version; use hyper::client::conn::http2::handshake; -use hyper_body_utils::HttpBody; use hyper_util::rt::{TokioExecutor, TokioIo}; impl HttpConnection for Http2Connection { @@ -22,46 +20,20 @@ impl HttpConnection for Http2Connection { } impl ProtoConnection for Http2Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http2Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = TokioStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_2 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(any(feature = "rust-tls", feature = "native-tls"))] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(TokioExecutor::new(), TokioIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] - let stream = plain_connection(config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(TokioExecutor::new(), TokioIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; tokio::spawn(async move { match conn.await { @@ -69,7 +41,6 @@ impl ProtoConnection for Http2Connection { Err(err) => { println!("Error: {:#}", err) } - _ => {} }; }); diff --git a/deboa-tokio/src/client/http/http3.rs b/deboa-tokio/src/client/http/http3.rs index 0c09ddb3..9235e040 100644 --- a/deboa-tokio/src/client/http/http3.rs +++ b/deboa-tokio/src/client/http/http3.rs @@ -1,65 +1,13 @@ -use crate::{ - alpn, - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::setup_rust_tls, BaseHttpConnection, Http3Connection}, -}; +use crate::client::http::conn::{BaseHttpConnection, Http3Connection}; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, errors::{ConnectionError, DeboaError}, Result, }; use deboa_h3::generic::{Http3Request, SendRequest}; use futures::future; +use h3_quinn::Connection; use http::version::Version; -use hyper_body_utils::HttpBody; -use quinn::{crypto::rustls::QuicClientConfig, Endpoint}; -use std::{ - net::{IpAddr, SocketAddr}, - sync::Arc, -}; - -async fn lookup_and_connect( - ip: IpAddr, - host: &str, - port: u16, - client_endpoint: &Endpoint, -) -> std::result::Result { - let conn = client_endpoint.connect(SocketAddr::new(ip, port), host); - - let conn = match conn { - Ok(conn) => conn, - Err(e) => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }; - - let conn = conn.await; - - let conn = match conn { - Ok(conn) => conn, - Err(e) => match e { - quinn::ConnectionError::TransportError(e) => { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - _ => { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: host.to_string(), - message: format!("Could not connect to server: {}", e), - })) - } - }, - }; - - let quinn_conn: h3_quinn::Connection = h3_quinn::Connection::new(conn); - - Ok(quinn_conn) -} impl HttpConnection for Http3Connection { type Sender = Http3Request; @@ -69,77 +17,18 @@ impl HttpConnection for Http3Connection { } impl ProtoConnection for Http3Connection { - type ReqBody = HttpBody; - type ResBody = HttpBody; type Connection = Http3Connection; - type Identity = DeboaIdentity; - type Certificate = DeboaCertificate; + type RuntimeStream = Connection; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_3 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - let client_endpoint = Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0)); - - if let Err(e) = client_endpoint { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let mut client_endpoint = client_endpoint.unwrap(); - - let tls_config = setup_rust_tls( - config.host(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - )?; - - let quic_config = QuicClientConfig::try_from(tls_config); - if let Err(e) = quic_config { - return Err(DeboaError::Connection(ConnectionError::Tls { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let quic_config = quic_config.unwrap(); - - let client_config = quinn::ClientConfig::new(Arc::new(quic_config)); - client_endpoint.set_default_client_config(client_config); - - let result = - lookup_and_connect(*config.ip(), config.host(), config.port(), &client_endpoint).await; - - if let Err(e) = result { - return Err(e); - } - - let conn = result.unwrap(); - - let client = h3::client::new(conn).await; - - if let Err(e) = client { - return Err(DeboaError::Connection(ConnectionError::Udp { - host: config - .host() - .to_string(), - message: e.to_string(), - })); - } - - let (mut conn, sender) = client.unwrap(); + async fn connect(conn: Self::RuntimeStream) -> Result { + let (mut conn, sender) = h3::client::new(conn) + .await + .map_err(|e| DeboaError::Connection(ConnectionError::Udp { message: e.to_string() }))?; tokio::spawn(async move { future::poll_fn(|cx| conn.poll_close(cx)).await; diff --git a/deboa-tokio/src/client/mod.rs b/deboa-tokio/src/client/mod.rs index 2fe73861..5d291c2c 100644 --- a/deboa-tokio/src/client/mod.rs +++ b/deboa-tokio/src/client/mod.rs @@ -1,7 +1,9 @@ -/// DNS resolution for the Deboa HTTP client. -/// -/// This module provides DNS resolution functionality for the Deboa HTTP client. +//! Deboa HTTP client modules. +//! +//! This module provides DNS resolution, HTTP, TCP, and TLS transport functionality for the Deboa HTTP client. + pub mod dns; pub mod http; +pub mod tls; #[cfg(feature = "websockets")] pub mod ws; diff --git a/deboa-tokio/src/client/tls/mod.rs b/deboa-tokio/src/client/tls/mod.rs new file mode 100644 index 00000000..afc96df1 --- /dev/null +++ b/deboa-tokio/src/client/tls/mod.rs @@ -0,0 +1,9 @@ +//! TLS transport implementations for the Deboa HTTP client. +//! +//! This module provides TLS functionality for secure HTTP connections. +//! It supports both native-tls and rustls backends. + +#[cfg(feature = "native-tls")] +pub mod native; +#[cfg(feature = "rust-tls")] +pub mod rustls; diff --git a/deboa-tokio/src/client/tls/native.rs b/deboa-tokio/src/client/tls/native.rs new file mode 100644 index 00000000..3177eefe --- /dev/null +++ b/deboa-tokio/src/client/tls/native.rs @@ -0,0 +1,125 @@ +//! TLS implementation using native-tls +//! +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use async_native_tls::{Certificate, Identity, TlsConnector, TlsStream}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use tokio::net::TcpStream; + +#[inline] +pub(crate) fn alpn() -> &'static [&'static str] { + &[ + #[cfg(feature = "http3")] + "h3", + #[cfg(feature = "http2")] + "h2", + #[cfg(feature = "http1")] + "http/1.1", + ] +} + +/// Builder for TLS connections using native-tls +pub struct TlsConnectionBuilder<'a> { + tcp_stream: TcpStream, + host: &'a str, + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: &'a [&'a str], +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Creates a new TLS connection builder + pub fn new(tcp_stream: TcpStream, host: &'a str) -> Self { + Self { + tcp_stream, + host, + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + } + } + + /// Sets the client identity for mutual TLS authentication + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Sets the server certificate for verification + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skips server certificate verification (use with caution) + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Sets the ALPN protocols to use + pub fn alpn(mut self, alpn: &'a [&str]) -> Self { + self.alpn = alpn; + self + } + + /// Establishes the TLS connection + pub async fn connect(self) -> Result> { + let builder = TlsConnector::new(); + + let builder = if self.skip_server_verification { + builder + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + } else { + builder + }; + + let builder = builder.request_alpns(self.alpn); + + let builder = if let Some(ca) = self.certificate { + let cert: Certificate = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + builder.add_root_certificate(cert) + } else { + builder + }; + + let builder = if let Some(identity) = self.identity { + let ident: Identity = identity + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + builder.identity(ident) + } else { + builder + }; + + let stream = builder + .connect( + self.host + .to_string(), + self.tcp_stream, + ) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }); + + stream + } +} diff --git a/deboa-tokio/src/client/tls/rustls.rs b/deboa-tokio/src/client/tls/rustls.rs new file mode 100644 index 00000000..569d4bdf --- /dev/null +++ b/deboa-tokio/src/client/tls/rustls.rs @@ -0,0 +1,327 @@ +//! TLS implementation using rustls + +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use rustls::{ + crypto::CryptoProvider, + pki_types::{CertificateDer, PrivateKeyDer}, + ClientConfig, +}; + +pub(crate) fn default_provider() -> CryptoProvider { + #[cfg(feature = "__rustls_aws_lc_rs")] + return rustls::crypto::aws_lc_rs::default_provider(); + #[cfg(feature = "__rustls_ring")] + return rustls::crypto::ring::default_provider(); +} + +#[inline] +pub(crate) fn alpn() -> Vec> { + vec![ + #[cfg(feature = "http3")] + b"h3".to_vec(), + #[cfg(feature = "http2")] + b"h2".to_vec(), + #[cfg(feature = "http1")] + b"http/1.1".to_vec(), + ] +} + +/// Builder for TLS connections using rustls +pub struct TlsConnectionBuilder<'a> { + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: Vec>, + provider: CryptoProvider, +} + +impl Default for TlsConnectionBuilder<'_> { + fn default() -> Self { + Self { + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + provider: default_provider(), + } + } +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Set the identity to use for the connection + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Set the certificate to use for the connection + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skip server verification + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Set the ALPN protocols to use for the connection + pub fn alpn(mut self, alpn: Vec>) -> Self { + self.alpn = alpn; + self + } + + /// Build the TLS client configuration + pub fn build_config(self) -> Result { + let client_config = { + if self.skip_server_verification { + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verify::SkipServerVerification::new( + self.provider, + )) + .with_no_client_auth() + } else { + #[cfg(feature = "__webpki_rustls_verifier")] + let config = { + let config = ClientConfig::builder_with_provider(self.provider.into()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })?; + + let mut root_store = + rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; + let config = if let Some(ca) = self.certificate { + let cert = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + + root_store + .add(cert) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!( + "Could not add CA certificate to the store: {}", + e + ), + }) + })?; + + config.with_root_certificates(root_store) + } else { + config.with_root_certificates(root_store) + }; + + config + }; + + #[cfg(feature = "__platform_rustls_verifier")] + let config = { + use rustls_platform_verifier::BuilderVerifierExt; + rustls::ClientConfig::builder_with_provider(default_provider()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })? + .with_platform_verifier() + }; + + let mut config = if let Some(id) = self.identity { + let pair: (CertificateDer<'_>, PrivateKeyDer<'_>) = id + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + + config + .with_client_auth_cert(vec![pair.0], pair.1) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set client identity: {}", e), + }) + })? + } else { + config.with_no_client_auth() + }; + + config.enable_early_data = true; + + config.alpn_protocols = self.alpn; + + config + } + }; + + Ok(client_config) + } +} + +#[cfg(any(feature = "http1", feature = "http2"))] +/// TCP connection module for TLS +pub mod tcp { + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use rustls::ClientConfig; + use rustls_pki_types::ServerName; + use std::sync::Arc; + use tokio::net::TcpStream; + use tokio_rustls::{client::TlsStream, TlsConnector}; + + /// Establish a TLS connection over TCP + pub async fn connect( + config: ClientConfig, + inner_stream: TcpStream, + host: &str, + ) -> Result> { + let connector = TlsConnector::from(Arc::new(config)); + + let hostname = ServerName::try_from(host.to_string()) + .map_err(|e| DeboaError::Connection(ConnectionError::Tls { message: e.to_string() }))?; + + connector + .connect(hostname, inner_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }) + } +} + +#[cfg(feature = "http3")] +/// UDP connection module for TLS +pub mod udp { + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use h3_quinn::Connection; + use quinn::{crypto::rustls::QuicClientConfig, Endpoint}; + use rustls::ClientConfig; + use std::{net::SocketAddr, sync::Arc}; + + /// Establish a TLS connection over UDP + pub async fn connect( + config: ClientConfig, + endpoint: &mut Endpoint, + socket_addr: SocketAddr, + host: &str, + ) -> Result { + let quic_config = QuicClientConfig::try_from(config).map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not create QUIC client config: {}", e), + }) + })?; + + let client_config = quinn::ClientConfig::new(Arc::new(quic_config)); + endpoint.set_default_client_config(client_config); + + let conn = endpoint + .connect(socket_addr, host) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let conn = conn + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let quinn_conn = h3_quinn::Connection::new(conn); + + Ok(quinn_conn) + } +} + +pub(crate) mod verify { + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::CryptoProvider, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + use std::sync::Arc; + + #[derive(Debug)] + pub(crate) struct SkipServerVerification(CryptoProvider); + + impl SkipServerVerification { + pub(crate) fn new(provider: CryptoProvider) -> Arc { + Arc::new(Self(provider)) + } + } + + impl ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0 + .signature_verification_algorithms + .supported_schemes() + } + } +} diff --git a/deboa-tokio/src/lib.rs b/deboa-tokio/src/lib.rs index 6485cc8f..3e458c1b 100644 --- a/deboa-tokio/src/lib.rs +++ b/deboa-tokio/src/lib.rs @@ -30,38 +30,15 @@ compile_error!( #[cfg(all(feature = "native-tls", feature = "rust-tls"))] compile_error!("You cannot enable native-tls and rust-tls features at the same time."); +#[cfg(all(not(any(feature = "native-tls", feature = "rust-tls")), feature = "http2"))] +compile_error!("HTTP2 requires native-tls or rust-tls support."); + #[cfg(all(feature = "native-tls", feature = "http3"))] compile_error!("HTTP3 is not supported within native-tls runtime."); #[cfg(not(any(feature = "http1", feature = "http2", feature = "http3")))] compile_error!("At least one HTTP version feature must be enabled."); -#[cfg(feature = "rust-tls")] -#[inline] -pub(crate) fn alpn() -> Vec> { - vec![ - #[cfg(feature = "http2")] - b"h2".to_vec(), - #[cfg(feature = "http1")] - b"http/1.1".to_vec(), - #[cfg(feature = "http3")] - b"h3".to_vec(), - ] -} - -#[cfg(feature = "native-tls")] -#[inline] -pub(crate) fn alpn() -> &'static [&'static str] { - &[ - #[cfg(feature = "http2")] - "h2", - #[cfg(feature = "http1")] - "http/1.1", - #[cfg(feature = "http3")] - "h3", - ] -} - /// Certificate management module for handling SSL/TLS certificates. pub mod cert; /// Internal module for HTTP and Websockets clients implementations. diff --git a/deboa-tokio/src/rt/stream.rs b/deboa-tokio/src/rt/stream.rs index f1c77422..f4df8abd 100644 --- a/deboa-tokio/src/rt/stream.rs +++ b/deboa-tokio/src/rt/stream.rs @@ -12,7 +12,7 @@ use tokio::{ use tokio_rustls::client::TlsStream; /// Stream enum for runtime-specific stream implementations. -pub(crate) enum TokioStream { +pub enum TokioStream { /// A plain TCP connection. Plain(TcpStream), diff --git a/deboa-tokio/tests/base/get.rs b/deboa-tokio/tests/base/get.rs index 3ce9e230..aa35ef34 100644 --- a/deboa-tokio/tests/base/get.rs +++ b/deboa-tokio/tests/base/get.rs @@ -2,6 +2,8 @@ use crate::common::helpers::{create_client, create_server, protocol_version}; #[cfg(feature = "rust-tls")] use deboa::cert::IdentityExt as _; +#[cfg(feature = "native-tls")] +use deboa::cert::IdentityNativeExt as _; #[cfg(any(feature = "rust-tls", feature = "native-tls"))] use deboa::cert::{CertificateExt as _, ContentEncoding}; use deboa::TestResult; @@ -26,12 +28,26 @@ async fn test_get_http( #[rstest] #[tokio::test] async fn test_get_http_skip_verification( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { + let identity = DeboaIdentity::from_pkcs8( + deboa_test_utils::common::helpers::CLIENT_CERT, + deboa_test_utils::common::helpers::CLIENT_KEY, + ContentEncoding::DER, + ); + + let client = Client::builder() + .certificate(DeboaCertificate::from_slice( + deboa_test_utils::common::helpers::CA_CERT, + ContentEncoding::DER, + )) + .identity(identity) + .skip_cert_verification(true) + .build(); + deboa_test_utils::base::get::test_skip_cert_verification( - &create_client, + &client, &mut create_server.await, protocol_version, true, @@ -42,12 +58,15 @@ async fn test_get_http_skip_verification( #[rstest] #[tokio::test] async fn test_get_http_verify( - create_client: Client, #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { + let client = Client::builder() + .skip_cert_verification(false) + .build(); + deboa_test_utils::base::get::test_skip_cert_verification( - &create_client, + &client, &mut create_server.await, protocol_version, false, @@ -62,18 +81,11 @@ async fn test_get_http_mutual_authentication( #[future] create_server: EasyHttpMock, protocol_version: http::Version, ) -> TestResult<()> { - let identity = DeboaIdentity::from_pkcs8( - deboa_test_utils::common::helpers::CLIENT_CERT, - deboa_test_utils::common::helpers::CLIENT_KEY, - ContentEncoding::DER, - ); - let client = Client::builder() .certificate(DeboaCertificate::from_slice( deboa_test_utils::common::helpers::CA_CERT, ContentEncoding::DER, )) - .identity(identity) .build(); deboa_test_utils::base::get::test_get_http_mutual_authentication( diff --git a/deboa-tokio/tests/base/mod.rs b/deboa-tokio/tests/base/mod.rs index e464b7f8..41f735fc 100644 --- a/deboa-tokio/tests/base/mod.rs +++ b/deboa-tokio/tests/base/mod.rs @@ -8,6 +8,7 @@ mod delete; mod form; #[cfg(test)] mod get; +#[cfg(test)] mod hook; #[cfg(test)] mod patch; diff --git a/deboa-tokio/tests/common/helpers.rs b/deboa-tokio/tests/common/helpers.rs index b0e540b4..3db4b48e 100644 --- a/deboa-tokio/tests/common/helpers.rs +++ b/deboa-tokio/tests/common/helpers.rs @@ -75,8 +75,12 @@ pub async fn tls_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) - .protocol_version(protocol_version()) + .interface( + interface + .parse() + .unwrap(), + ) + .protos(vec![protocol_version()]) .with_random_port() .cert(server_cert.to_vec()) .key(server_key.to_vec()) @@ -105,7 +109,11 @@ pub async fn plain_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) + .interface( + interface + .parse() + .unwrap(), + ) .protocol_version(protocol_version()) .with_random_port() .build(); diff --git a/deboa/Cargo.toml b/deboa/Cargo.toml index f47c8b52..e3663b37 100644 --- a/deboa/Cargo.toml +++ b/deboa/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deboa" -version = "0.1.2" +version = "0.1.3" edition.workspace = true authors.workspace = true repository.workspace = true @@ -42,7 +42,6 @@ regex = { version = "1.12.4", default-features = false } serde = { version = "1.0.217", default-features = false, features = ["derive"] } tackle = { version = "0.1.1"} thiserror = "2.0.17" -time = { version = "0.3.53", default-features = false } url = "2.5.8" urlencoding = "2.1.3" @@ -56,3 +55,4 @@ criterion = { version = "0.8.2", features = [ ] } mime = { version = "0.3.17" } multer = "3.1.0" +time = { version = "0.3.53" } diff --git a/deboa/src/cert.rs b/deboa/src/cert.rs index cc0f40ae..1812dfac 100644 --- a/deboa/src/cert.rs +++ b/deboa/src/cert.rs @@ -95,7 +95,7 @@ pub trait Identity { /// # Returns /// /// * `&Option>` - The private key - fn ket(&self) -> &Option>; + fn key(&self) -> &Option>; /// Get the encoding /// /// # Returns diff --git a/deboa/src/conn.rs b/deboa/src/conn.rs index d4f3e5c2..5efc1ca9 100644 --- a/deboa/src/conn.rs +++ b/deboa/src/conn.rs @@ -3,24 +3,24 @@ //! This module provides functionality for managing HTTP connections. use crate::{ cert::{Certificate, Identity}, + dns::DnsResolver, response::DeboaResponse, Result, }; use http::{Request, Version}; -use http_body::Body; use hyper_body_utils::HttpBody; +use std::time::Duration; use std::{future::Future, net::IpAddr}; -use time::Duration; /// Builder for connection configuration. pub struct ConnectionConfigBuilder<'a, I, C> { - is_secure: bool, - ip: IpAddr, + scheme: &'a str, host: &'a str, port: u16, protocol_version: Version, - identity: Option, - certificate: Option, + connection_timeout: Duration, + identity: Option<&'a I>, + certificate: Option<&'a C>, skip_cert_verification: bool, client_bind_addr: IpAddr, } @@ -34,13 +34,11 @@ where #[allow(clippy::new_without_default)] pub fn new() -> Self { Self { - is_secure: false, - ip: "127.0.0.1" - .parse::() - .unwrap(), + scheme: "http", host: "", port: 80, protocol_version: Version::HTTP_2, + connection_timeout: Duration::from_secs(30), identity: None, certificate: None, skip_cert_verification: false, @@ -50,15 +48,9 @@ where } } - /// Set whether the connection is secure. - pub fn is_secure(mut self, is_secure: bool) -> Self { - self.is_secure = is_secure; - self - } - - /// Set the IP address for the connection. - pub fn ip(mut self, ip: IpAddr) -> Self { - self.ip = ip; + /// Set the scheme for the connection. + pub fn scheme(mut self, scheme: &'a str) -> Self { + self.scheme = scheme; self } @@ -80,14 +72,20 @@ where self } + /// Set the connection timeout for the connection. + pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { + self.connection_timeout = connection_timeout; + self + } + /// Set the identity for the connection. - pub fn identity(mut self, identity: Option) -> Self { + pub fn identity(mut self, identity: Option<&'a I>) -> Self { self.identity = identity; self } /// Set the certificate for the connection. - pub fn certificate(mut self, certificate: Option) -> Self { + pub fn certificate(mut self, certificate: Option<&'a C>) -> Self { self.certificate = certificate; self } @@ -107,11 +105,11 @@ where /// Build the connection configuration. pub fn build(self) -> ConnectionConfig<'a, I, C> { ConnectionConfig { - is_secure: self.is_secure, - ip: self.ip, + scheme: self.scheme, host: self.host, port: self.port, protocol_version: self.protocol_version, + connection_timeout: self.connection_timeout, identity: self.identity, certificate: self.certificate, skip_cert_verification: self.skip_cert_verification, @@ -122,13 +120,13 @@ where /// Connection configuration. pub struct ConnectionConfig<'a, I, C> { - is_secure: bool, - ip: IpAddr, + scheme: &'a str, host: &'a str, port: u16, protocol_version: Version, - identity: Option, - certificate: Option, + connection_timeout: Duration, + identity: Option<&'a I>, + certificate: Option<&'a C>, skip_cert_verification: bool, client_bind_addr: IpAddr, } @@ -143,14 +141,9 @@ where ConnectionConfigBuilder::new() } - /// Get whether the connection is secure. - pub fn is_secure(&self) -> bool { - self.is_secure - } - - /// Get the IP address for the connection. - pub fn ip(&self) -> &IpAddr { - &self.ip + /// Get the scheme for the connection. + pub fn scheme(&self) -> &str { + self.scheme } /// Get the host for the connection. @@ -168,14 +161,19 @@ where &self.protocol_version } + /// Get the connection timeout for the connection. + pub fn connection_timeout(&self) -> Duration { + self.connection_timeout + } + /// Get the identity for the connection. - pub fn identity(&self) -> &Option { - &self.identity + pub fn identity(&self) -> Option<&I> { + self.identity } /// Get the certificate for the connection. - pub fn certificate(&self) -> &Option { - &self.certificate + pub fn certificate(&self) -> Option<&C> { + self.certificate } /// Get whether to skip certificate verification. @@ -237,18 +235,20 @@ pub trait HttpConnectionPool { /// /// # Arguments /// - /// * `url` - The url to connect. - /// * `protocol` - The protocol to use. - /// * `retries` - The number of retries. + /// * `config` - The connection configuration. + /// * `dns_resolver` - The DNS resolver to use. /// /// # Returns /// /// * `Result<&mut Self::ConnectionDispather>` - The connection or error. /// - fn create_connection<'a>( - &'a mut self, - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> impl Future>; + fn create_connection( + &mut self, + config: &ConnectionConfig, + dns_resolver: &D, + ) -> impl Future> + where + D: DnsResolver; } /// Trait that represents the HTTP connection dispatcher. @@ -265,6 +265,7 @@ pub trait HttpConnectionDispatcher { fn send_request( &mut self, request: Request, + timeout: Duration, ) -> impl Future>; } @@ -272,32 +273,20 @@ pub trait HttpConnectionDispatcher { /// /// # Type Parameters /// -/// * `Sender` - The sender to use. -/// * `ReqBody` - The request body type. -/// * `ResBody` - The response body type. +/// * `Connection` - The connection type. +/// * `RuntimeStream` - The runtime stream type. /// pub trait ProtoConnection { - /// The request body type. - type ReqBody: Body + Unpin; - /// The response body type. - type ResBody: Body + Unpin; /// The connection type. type Connection: HttpConnection; - /// The identity type. - type Identity: crate::cert::Identity; - /// The certificate type. - type Certificate: crate::cert::Certificate; + /// The runtime stream type. + type RuntimeStream; /// Create a new connection. /// /// # Arguments /// - /// * `is_secure` - Whether the connection is secure. - /// * `host` - The host to connect. - /// * `port` - The port to connect. - /// * `identity` - The identity to use. - /// * `certificate` - The certificate to use. - /// * `skip_cert_verification` - Whether to skip certificate verification. + /// * `stream` - The runtime stream to use. /// /// # Errors /// @@ -307,9 +296,7 @@ pub trait ProtoConnection { /// /// * `Result` - The connection or error. /// - fn connect( - config: &ConnectionConfig, - ) -> impl Future>; + fn connect(stream: Self::RuntimeStream) -> impl Future>; /// Get connection protocol. /// @@ -319,9 +306,3 @@ pub trait ProtoConnection { /// fn protocol_version(&self) -> Version; } - -/// Common interface for Plain and TLS stream connections -pub trait StreamConnector { - /// Connect using ip and port - fn connect(ip: IpAddr, port: u16); -} diff --git a/deboa/src/cookie.rs b/deboa/src/cookie.rs index a6868322..25df19b7 100644 --- a/deboa/src/cookie.rs +++ b/deboa/src/cookie.rs @@ -42,12 +42,9 @@ //! assert_eq!(cookie.path(), Some(&"/".to_string())); //! assert_eq!(cookie.secure(), Some(true)); //! ``` - -use std::fmt; - -use cookie::{Cookie, Expiration}; - use crate::{errors::DeboaError, Result}; +use cookie::{Cookie, Expiration}; +use std::fmt; /// Represents an HTTP cookie with all its attributes. /// diff --git a/deboa/src/errors.rs b/deboa/src/errors.rs index fbad6d43..5a0231e7 100644 --- a/deboa/src/errors.rs +++ b/deboa/src/errors.rs @@ -158,25 +158,28 @@ pub enum RequestError { /// Error message message: String, }, + + /// Request timeout error + #[error("Request timeout: {message}")] + Timeout { + /// Error message + message: String, + }, } /// Connection error #[derive(Debug, Clone, Error, PartialEq)] pub enum ConnectionError { /// Tcp connection error - #[error("Tcp connection error: {host} {message}")] + #[error("Tcp connection error: {message}")] Tcp { - /// Host - host: String, /// Error message message: String, }, /// Tls connection error - #[error("Tls connection error: {host} {message}")] + #[error("Tls connection error: {message}")] Tls { - /// Host - host: String, /// Error message message: String, }, @@ -184,17 +187,13 @@ pub enum ConnectionError { /// Udp connection error #[error("Udp connection error: {message}")] Udp { - /// Host - host: String, /// Error message message: String, }, /// Connection handshake error - #[error("Connection handshake error: {host} {message}")] + #[error("Connection handshake error: {message}")] Handshake { - /// Host - host: String, /// Error message message: String, }, @@ -206,6 +205,13 @@ pub enum ConnectionError { message: String, }, + /// Connection timeout error + #[error("Connection timeout: {message}")] + Timeout { + /// Error message + message: String, + }, + /// Unsupported scheme error #[error("Unsupported scheme: {message}")] UnsupportedScheme { diff --git a/deboa/src/lib.rs b/deboa/src/lib.rs index 962e2228..9380dfe7 100644 --- a/deboa/src/lib.rs +++ b/deboa/src/lib.rs @@ -14,7 +14,7 @@ use std::{ future::Future, net::{IpAddr, Ipv4Addr}, ops::Shl, - sync::Arc, + time::Duration, }; use tackle::{Chain, Hook, HookFn}; @@ -109,12 +109,19 @@ where } /// Set connection timeout - pub fn connection_timeout(mut self, connection_timeout: u64) -> Self { + pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { self.inner .connection_timeout = connection_timeout; self } + /// Set request timeout + pub fn request_timeout(mut self, request_timeout: Duration) -> Self { + self.inner + .request_timeout = request_timeout; + self + } + /// Set certificate pub fn certificate(mut self, certificate: C) -> Self { self.inner @@ -137,7 +144,7 @@ where /// Set dns resolver pub fn dns_resolver(mut self, dns_resolver: R) -> Self { self.inner - .dns_resolver = Arc::new(dns_resolver); + .dns_resolver = dns_resolver; self } @@ -312,13 +319,13 @@ where /// - Automatic connection reuse when possible /// - Configurable timeouts prevent hanging requests pub struct InnerClient { - connection_timeout: u64, - request_timeout: u64, + connection_timeout: Duration, + request_timeout: Duration, identity: Option, certificate: Option, skip_cert_verification: bool, pool: RwLock

, - dns_resolver: Arc, + dns_resolver: R, bind_addr: IpAddr, } @@ -338,12 +345,23 @@ impl InnerClient { /// /// # Returns /// - /// * `u64` - The timeout. + /// * `Duration` - The timeout. /// - pub fn connection_timeout(&self) -> u64 { + pub fn connection_timeout(&self) -> Duration { self.connection_timeout } + #[inline] + /// Allow get request request timeout at any time. + /// + /// # Returns + /// + /// * `Duration` - The timeout. + /// + pub fn request_timeout(&self) -> Duration { + self.request_timeout + } + /// Allow get connection pool at any time. /// /// # Returns @@ -362,7 +380,7 @@ impl InnerClient { /// * `Arc` - The DNS resolver. /// #[inline] - pub fn dns_resolver(&self) -> &Arc { + pub fn dns_resolver(&self) -> &R { &self.dns_resolver } @@ -377,17 +395,6 @@ impl InnerClient { self.bind_addr } - /// Allow get request request timeout at any time. - /// - /// # Returns - /// - /// * `u64` - The timeout. - /// - #[inline] - pub fn request_timeout(&self) -> u64 { - self.request_timeout - } - /// Allow get certificate at any time. /// /// # Returns @@ -420,13 +427,13 @@ where fn default() -> Self { Self { bind_addr: IpAddr::V4(Ipv4Addr::UNSPECIFIED), - connection_timeout: 30, - request_timeout: 30, + connection_timeout: Duration::from_secs(30), + request_timeout: Duration::from_secs(30), identity: None, certificate: None, skip_cert_verification: false, pool: RwLock::new(P::default()), - dns_resolver: Arc::new(R::default()), + dns_resolver: R::default(), } } } @@ -442,74 +449,46 @@ where type Error = DeboaError; async fn call(&self, request: DeboaRequest) -> Result { - let resolver = self - .dns_resolver - .clone(); + info!("Building request: {} {}", request.method(), request.uri()); let uri = request .uri() .clone(); - let method = request.method(); - let host = uri - .host() - .unwrap_or("localhost"); - let port = if let Some(port) = uri.port() { port.as_u16() } else { 80u16 }; - info!("Building request: {} {}", method, uri); - let request = request.body(); - let ips = resolver - .resolve(host.to_string(), port) - .await?; - - let ips = if self - .bind_addr - .is_ipv4() - { - ips.into_iter() - .filter(|ip| ip.is_ipv4()) - .collect::>() - } else { - ips.into_iter() - .filter(|ip| ip.is_ipv6()) - .collect::>() + let Some(scheme) = uri.scheme_str() else { + return Err(DeboaError::Request(RequestError::Send { + message: "Missing scheme".to_string(), + })); }; - let Some(ip) = ips.first() else { + let Some(host) = uri.host() else { return Err(DeboaError::Request(RequestError::Send { - message: format!("No IP addresses found for hostname: {}", host), + message: "Missing host".to_string(), })); }; - let uri = request.uri(); - let scheme = uri - .scheme_str() - .unwrap_or("http"); - let (port, is_secure) = match scheme { - "https" | "wss" => ( - uri.port_u16() - .unwrap_or(443), - true, - ), - _ => ( - uri.port_u16() - .unwrap_or(80), - false, - ), - }; + let port = uri + .port_u16() + .unwrap_or({ + match scheme { + "http" => 80, + "https" => 443, + _ => 80, + } + }); let config = ConnectionConfig::builder() - .is_secure(is_secure) - .ip(*ip) + .scheme(scheme) .host(host) .port(port) .protocol_version(request.version()) .identity( self.identity - .clone(), + .as_ref(), ) .certificate( self.certificate - .clone(), + .as_ref(), ) .skip_cert_verification(self.skip_cert_verification) .client_bind_addr(self.bind_addr) @@ -521,11 +500,13 @@ where .await; let conn = pool - .create_connection(&config) + .create_connection(&config, &self.dns_resolver) .await?; + let request = request.body(); + let response = conn - .send_request(request) + .send_request(request, self.request_timeout) .await?; Ok(response) diff --git a/deboa/src/request.rs b/deboa/src/request.rs index e66b0053..e43e3c93 100644 --- a/deboa/src/request.rs +++ b/deboa/src/request.rs @@ -1220,20 +1220,12 @@ impl DeboaRequest { /// #[inline] pub fn at(url: T, method: http::Method) -> Result { - let parsed_url = url - .into_url() - .map_err(|e| { - error!("Failed to parse url: {}", e); - DeboaError::Request(RequestError::UrlParse { message: e.to_string() }) - })?; + let parsed_url = url.into_url()?; let uri = parsed_url .to_string() .parse::() - .map_err(|e| { - error!("Failed to parse uri: {}", e); - DeboaError::Request(RequestError::UrlParse { message: e.to_string() }) - })?; + .map_err(|e| DeboaError::Request(RequestError::UrlParse { message: e.to_string() }))?; let request = Request::builder() .method(method) From 572bd562143d04099371cc460f03bc86a8da7086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Wed, 19 Aug 2026 17:28:08 -0300 Subject: [PATCH 03/13] chore: move common deps info to workspace Cargo.toml --- Cargo.lock | 923 +++++++++++++++++++++++++++++++++++- Cargo.toml | 53 ++- deboa-compio/Cargo.toml | 87 ++-- deboa-h3/Cargo.toml | 8 +- deboa-macros/Cargo.toml | 10 +- deboa-smol/Cargo.toml | 84 ++-- deboa-test-utils/Cargo.toml | 21 +- deboa-tokio/Cargo.toml | 87 ++-- 8 files changed, 1114 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 704dc1c6..a4d6ae35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-native-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37dd6b179962fe4048a6f81d4c0d7ed419a21fdf49204b4c6b04971693358e79" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 2.0.20", + "tokio", + "url", +] + [[package]] name = "async-net" version = "2.0.0" @@ -368,6 +381,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + [[package]] name = "byteorder" version = "1.5.0" @@ -474,6 +493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -486,6 +506,18 @@ dependencies = [ "clap_lex", ] +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "clap_lex" version = "1.1.0" @@ -528,7 +560,11 @@ dependencies = [ "compio-fs", "compio-io", "compio-log", + "compio-macros", + "compio-net", "compio-runtime", + "compio-signal", + "compio-tls", ] [[package]] @@ -556,7 +592,9 @@ dependencies = [ "crossbeam-queue", "flume", "futures-util", + "io-uring", "libc", + "linux-raw-sys", "mod_use", "once_cell", "pastey", @@ -607,11 +645,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3a354e085c4046dd8d2d9d514cf8c15c615eb8adf6c5f41dbf7899420457e9b" dependencies = [ + "bytemuck", "compio-buf", "futures-util", + "libc", "pastey", "pin-project-lite", + "rustix", "synchrony", + "windows-sys 0.61.2", ] [[package]] @@ -623,6 +665,65 @@ dependencies = [ "tracing", ] +[[package]] +name = "compio-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc53390a911fe6b317abe66c0583723539477f81a940e124b40d462fb976674a" +dependencies = [ + "darling", + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "compio-net" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31737ec57865d2b46284def673cd21fe478c592ca05c1e040e10d457aac6c2a8" +dependencies = [ + "compio-buf", + "compio-driver", + "compio-io", + "compio-runtime", + "either", + "futures-util", + "libc", + "once_cell", + "pin-project-lite", + "socket2", + "synchrony", + "widestring", + "windows-sys 0.61.2", +] + +[[package]] +name = "compio-quic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b1501ba168bfd2cf63ffc120d81a015a1d78cc853bbd9b4f2a7cb85cd2ab9a" +dependencies = [ + "cfg_aliases", + "compio-buf", + "compio-io", + "compio-log", + "compio-net", + "compio-runtime", + "flume", + "futures-util", + "h3", + "h3-datagram", + "libc", + "quinn-proto", + "rustc-hash", + "rustls", + "synchrony", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + [[package]] name = "compio-runtime" version = "0.12.6" @@ -653,6 +754,34 @@ dependencies = [ "loom", ] +[[package]] +name = "compio-signal" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1748b284601a5a267824b36894c7fc5e488b503f14a0a508f3b457ae018a451" +dependencies = [ + "nix 0.31.3", + "once_cell", + "slab", + "synchrony", + "windows-sys 0.61.2", +] + +[[package]] +name = "compio-tls" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0177c2684878d0712ccfcf22113a198f5ddf74f0bf323801488dbb47f801a471" +dependencies = [ + "compio-buf", + "compio-io", + "futures-rustls", + "futures-util", + "native-tls", + "pin-project-lite", + "rustls", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -796,6 +925,52 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "cyper-core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c8847069e286c64987119637d5f08cdb71e12e85be0294fed649dc8007d32e" +dependencies = [ + "compio", + "futures-util", + "hyper", + "send_wrapper", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.3", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.3", +] + [[package]] name = "deboa" version = "0.1.3" @@ -829,6 +1004,57 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "deboa-compio" +version = "0.1.4" +dependencies = [ + "async-executor", + "async-lock", + "base64", + "bytes", + "caramelo", + "compio", + "compio-quic", + "compio-tls", + "cookie", + "criterion", + "cyper-core", + "deboa", + "deboa-h3", + "deboa-test-utils", + "easyhttpmock-vetis-compio", + "futures", + "futures-util", + "h3", + "hashbrown 0.17.1", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "indexmap", + "io-uring", + "log", + "minimime", + "multer", + "pin-project-lite", + "rand", + "regex", + "rstest", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "tackle", + "thiserror 2.0.20", + "time", + "url", + "urlencoding", + "webpki-roots", +] + [[package]] name = "deboa-extras" version = "0.1.0" @@ -909,6 +1135,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "deboa-h3" +version = "0.1.1" +dependencies = [ + "bytes", + "compio-quic", + "deboa", + "h3", + "h3-quinn", + "http", + "hyper-body-utils", +] + [[package]] name = "deboa-macros" version = "0.1.0" @@ -922,6 +1161,61 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "deboa-smol" +version = "0.1.3" +dependencies = [ + "async-executor", + "async-lock", + "async-native-tls", + "base64", + "bytes", + "caramelo", + "cookie", + "criterion", + "deboa", + "deboa-h3", + "deboa-test-utils", + "easyhttpmock-vetis-smol", + "futures", + "futures-rustls", + "futures-timeout", + "futures-util", + "h3", + "h3-quinn", + "hashbrown 0.17.1", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "indexmap", + "log", + "macro_rules_attribute", + "minimime", + "multer", + "quinn", + "rand", + "regex", + "rstest", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "smol", + "smol-hyper", + "smol-macros", + "tackle", + "thiserror 2.0.20", + "time", + "url", + "urlencoding", + "webpki-roots", + "ws-framer", +] + [[package]] name = "deboa-test-utils" version = "0.1.0" @@ -952,6 +1246,57 @@ dependencies = [ "vetis", ] +[[package]] +name = "deboa-tokio" +version = "0.1.3" +dependencies = [ + "async-executor", + "async-lock", + "async-native-tls", + "base64", + "bytes", + "caramelo", + "cookie", + "criterion", + "deboa", + "deboa-h3", + "deboa-test-utils", + "easyhttpmock-vetis-tokio", + "futures", + "futures-util", + "h3", + "h3-quinn", + "hashbrown 0.17.1", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "indexmap", + "log", + "minimime", + "multer", + "quinn", + "rand", + "regex", + "rstest", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "tackle", + "thiserror 2.0.20", + "tokio", + "tokio-rustls", + "tokio-util", + "url", + "urlencoding", + "webpki-roots", + "ws-framer", +] + [[package]] name = "defmt" version = "1.1.1" @@ -1033,6 +1378,54 @@ dependencies = [ "thiserror 2.0.20", ] +[[package]] +name = "easyhttpmock-vetis-compio" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c072751bd8ca548561b7b747fd8a7404b6ba3ec5b9e5e5bdac89f9a797b706" +dependencies = [ + "caramelo", + "compio", + "easyhttpmock", + "http", + "http-body-util", + "rand", + "send_wrapper", + "vetis-compio", +] + +[[package]] +name = "easyhttpmock-vetis-smol" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22271cc3b2b8406392d7829c0bc02a45b362c429ab18520ec9ae3a9e172effef" +dependencies = [ + "caramelo", + "easyhttpmock", + "http", + "http-body-util", + "macro_rules_attribute", + "rand", + "smol", + "smol-macros", + "vetis-smol", +] + +[[package]] +name = "easyhttpmock-vetis-tokio" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef9b7c4d96df390ef7577f4efe7427a713e9e0a4b24e82250f2238a9c8f55d09" +dependencies = [ + "caramelo", + "easyhttpmock", + "http", + "http-body-util", + "rand", + "tokio", + "vetis-tokio", +] + [[package]] name = "either" version = "1.18.0" @@ -1212,6 +1605,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1281,6 +1689,7 @@ checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1303,6 +1712,17 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.34" @@ -1356,11 +1776,26 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" +[[package]] +name = "futures-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58ec6ce8fe4cc0b1f82f58eec8e6d1cd08584cc40aadab74cb925ff1b2750f2" +dependencies = [ + "futures-core", + "futures-timer", + "pin-project", +] + [[package]] name = "futures-timer" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" +dependencies = [ + "gloo-timers", + "send_wrapper", +] [[package]] name = "futures-util" @@ -1401,8 +1836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1466,7 +1903,7 @@ dependencies = [ "lazy_static", "libc", "log", - "nix", + "nix 0.30.1", "pin-project-lite", "rlimit", "rustc_version", @@ -1491,6 +1928,18 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "granit-parser" version = "1.1.0" @@ -1520,6 +1969,45 @@ dependencies = [ "tracing", ] +[[package]] +name = "h3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" +dependencies = [ + "bytes", + "fastrand", + "futures-util", + "http", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-datagram" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" +dependencies = [ + "bytes", + "h3", + "pin-project-lite", +] + +[[package]] +name = "h3-quinn" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" +dependencies = [ + "bytes", + "futures", + "h3", + "quinn", + "tokio", + "tokio-util", +] + [[package]] name = "half" version = "2.7.1" @@ -1563,6 +2051,12 @@ dependencies = [ "foldhash", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.5.2" @@ -1655,10 +2149,14 @@ dependencies = [ "async-fn-stream", "bytes", "compio", + "compio-quic", "futures", + "h3", + "h3-quinn", "http", "http-body-util", "hyper", + "send_wrapper", ] [[package]] @@ -1762,6 +2260,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1971,6 +2475,15 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -2020,6 +2533,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "macro_rules_attribute" version = "0.2.3" @@ -2069,6 +2588,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mime" version = "0.3.17" @@ -2182,6 +2710,23 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nibble_vec" version = "0.1.0" @@ -2204,6 +2749,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nohash-hasher" version = "0.2.0" @@ -2311,12 +2868,49 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "openssl-probe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "page_size" version = "0.6.0" @@ -2345,6 +2939,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" +[[package]] +name = "peekable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fd2346a004ab7b18c468ad8f9554970a14872a701450155248ff5af73af53b" +dependencies = [ + "bytes", + "futures-util", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2393,6 +3000,26 @@ dependencies = [ "pest", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2568,6 +3195,66 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "async-io", + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "smol", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.47" @@ -2625,6 +3312,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.12.0" @@ -2812,6 +3508,12 @@ version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2841,6 +3543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -2867,6 +3570,7 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ + "web-time", "zeroize", ] @@ -2980,6 +3684,15 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.229" @@ -3209,6 +3922,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "smol-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcaedb62e0475a6898988138995ec7b1e5d116167a72bb12c7b59d0649fbbc2" +dependencies = [ + "async-executor", + "async-io", + "async-lock", + "event-listener", + "futures-lite", +] + [[package]] name = "socket2" version = "0.6.5" @@ -3273,6 +3999,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3341,6 +4073,19 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ed1c6888f08659f2071c6c8dcb312c8086cb3061be4c17bab42ccd49a22a5e0" +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "termtree" version = "0.5.1" @@ -3489,10 +4234,33 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.19" @@ -3783,6 +4551,12 @@ dependencies = [ "vamo", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -3815,6 +4589,120 @@ dependencies = [ "url", ] +[[package]] +name = "vetis-compio" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc90a480c41dc77f7e79d2024ae822d5ad80ccda6c94b630b85891e791ebb8" +dependencies = [ + "async-lock", + "bytes", + "clap", + "compio", + "compio-tls", + "cyper-core", + "env_logger", + "futures-util", + "http", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "log", + "radix_trie", + "rand", + "rustls", + "send_wrapper", + "serde", + "serde_yaml_ng", + "socket2", + "thiserror 2.0.20", + "time", + "typetag", + "url", + "vetis", +] + +[[package]] +name = "vetis-smol" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7266cd40ed5803f7855fa1b179aab0a2c996d28919d1a142384b5975d321e412" +dependencies = [ + "async-lock", + "async-net", + "async-signal", + "bytes", + "clap", + "env_logger", + "futures-lite", + "futures-rustls", + "futures-util", + "http", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "log", + "macro_rules_attribute", + "mimalloc", + "peekable", + "quinn", + "radix_trie", + "rand", + "rustls", + "serde", + "serde_yaml_ng", + "signal-hook", + "smol", + "smol-hyper", + "smol-macros", + "socket2", + "thiserror 2.0.20", + "time", + "typetag", + "url", + "vetis", +] + +[[package]] +name = "vetis-tokio" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6a3eb16897a150d1b9c09645166414e923ee9ee03f7d209f9de6f693a67d0d" +dependencies = [ + "async-lock", + "bytes", + "clap", + "env_logger", + "futures-lite", + "futures-rustls", + "futures-util", + "http", + "http-body-util", + "hyper", + "hyper-body-utils", + "hyper-util", + "log", + "mimalloc", + "peekable", + "quinn", + "radix_trie", + "rand", + "rustls", + "serde", + "serde_yaml_ng", + "socket2", + "thiserror 2.0.20", + "time", + "tokio", + "tokio-rustls", + "tokio-util", + "typetag", + "url", + "vetis", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -3904,6 +4792,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 = "webpki-root-certs" version = "1.0.9" @@ -4086,6 +4984,29 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" +[[package]] +name = "ws-framer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bacd6cff21323641597fe251e294f823e04292202a6e3c9721a1e8b82bea57bf" +dependencies = [ + "httparse", + "itoa", + "ws-framer-macros", +] + +[[package]] +name = "ws-framer-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fe343b024b086505b3c647de6eae0c26235934cb9138a180e3ac5f97a6aeda" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "xml" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 06147367..e78df92f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,13 @@ resolver = "3" # Include both parts of the library in the workspace members = [ "deboa", - #"deboa-h3", - #"deboa-compio", + "deboa-h3", + "deboa-compio", "deboa-glommio", - #"deboa-macros", - #"deboa-smol", + "deboa-macros", + "deboa-smol", "deboa-test-utils", - #"deboa-tokio", + "deboa-tokio", ] [workspace.package] @@ -23,8 +23,14 @@ license = "MIT OR Apache-2.0" rust-version = "1.85.0" [workspace.dependencies] -#deboa = { path = "deboa", version = ">= 0.1.2" } -deboa = { path = "deboa" } +async-executor = { version = "1.13.3", default-features = false } +async-lock = "3.4.2" +async-native-tls = { version = "0.6.0", default-features = false } +base64 = { version = "0.23.0" } +bytes = { version = "1.11" } +caramelo = "0.1.2" +cookie = { version = "0.18.1" } +deboa = { version = ">= 0.1.2" } deboa-compio = { path = "deboa-compio" } deboa-glommio = { path = "deboa-glommio" } deboa-h3 = { path = "deboa-h3", version = "^0.1.1" } @@ -34,9 +40,42 @@ deboa-macros = { path = "deboa-macros" } deboa-smol = { path = "deboa-smol" } deboa-test-utils = { path = "deboa-test-utils" } deboa-tokio = { path = "deboa-tokio" } +futures = { version = "0.3.31", default-features = false } +futures-rustls = { version = "0.26.0", default-features = false } +futures-util = { version = "0.3.31", default-features = false } +h3 = { version = "0.0.8", default-features = false } +h3-quinn = { version = "0.0.10", default-features = false } +hashbrown = "0.17.1" +http = "1" +http-body = "1" +http-body-util = "0.1" +hyper = { version = "1.8.1", features = ["client"], default-features = false } hyper-body-utils = { version = ">= 0.1.14", default-features = false } +hyper-util = { version = "0.1.20", features = [ + "client", + "client-legacy", +], default-features = false } +log = "0.4.28" +minimime = "1.0.0" +multer = "3.1.0" +pin-project-lite = "0.2.17" +rand = "0.10.2" +regex = "1.9.6" +rstest = "0.26.1" +rustls = { version = "0.23.36", default-features = false } +rustls-native-certs = { version = "0.8.0", default-features = false } +rustls-pki-types = { version = "1.13.2", default-features = false } +rustls-platform-verifier = { version = "0.7.0", default-features = false } +serde = { version = "1.0.217", features = ["derive"] } +tackle = { version = "0.1.1"} +thiserror = "2.0.20" +time = { version = "0.3.53" } +url = "2.5.8" +urlencoding = "2.1.3" vamo = { version = ">= 0.0.9" } vamo-macros = { version = ">= 0.0.9" } +webpki-roots = { version = "1.0.6" } +ws-framer = { version = "0.3.2", default-features = false } [profile.test] lto = false diff --git a/deboa-compio/Cargo.toml b/deboa-compio/Cargo.toml index beb0694e..18d66db9 100644 --- a/deboa-compio/Cargo.toml +++ b/deboa-compio/Cargo.toml @@ -13,11 +13,17 @@ publish = true rust-version.workspace = true [package.metadata.docs.rs] -all-features = true +features = [ + "http1", + "http2", + "rust-tls", + "default-rustls-provider", + "default-rustls-verifier", +] [features] default = [ - #"http1", + "http1", "http2", #"http3", "rust-tls", @@ -63,46 +69,43 @@ http3 = [ ] [dependencies] -async-executor = { version = "1.13.3", optional = true, default-features = false } -async-lock = "3.4.2" -base64 = "0.23.0" -bytes = { version = "1.11" } +async-executor = { workspace = true } +async-lock = { workspace = true } +base64 = { workspace = true } +bytes = { workspace = true } compio-quic = { version = "0.8.2", default-features = false, optional = true } compio-tls = { version = "0.10.0", default-features = false, optional = true } -cookie = "0.18.1" +cookie = { workspace = true } cyper-core = "0.9.0" deboa = { workspace = true } -deboa-h3 = { workspace = true, optional = true, default-features = false } -futures = "0.3.31" -futures-util = { version = "0.3.31", default-features = false } -h3 = { version = "0.0.8", optional = true, default-features = false } -hashbrown = "0.17.1" -http = "1" -http-body = "1" -http-body-util = "0.1" -hyper = { version = "1.8.1", features = ["client"], default-features = false } +deboa-h3 = { workspace = true, optional = true } +futures = { workspace = true, optional = true } +futures-util = { workspace = true, optional = true } +h3 = { version = "0.0.8", optional = true } +hashbrown = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } hyper-body-utils = { workspace = true, default-features = false } -hyper-util = { version = "0.1.20", features = [ - "client", - "client-legacy", -], default-features = false } +hyper-util = { workspace = true } indexmap = "2.11.4" io-uring = "0.7.13" -log = "0.4.28" -minimime = "1.0.0" -pin-project-lite = "0.2.17" -rand = "0.10.2" -regex = "1.9.6" -rustls = { version = "0.23.36", optional = true, default-features = false } -rustls-native-certs = { version = "0.8.0", optional = true, default-features = false } -rustls-pki-types = { version = "1.13.2", optional = true, default-features = false } -rustls-platform-verifier = { version = "0.7.0", optional = true, default-features = false } -serde = { version = "1.0.217", features = ["derive"] } -tackle = { version = "0.1.1"} -thiserror = "2.0.17" -url = "2.5.8" -urlencoding = "2.1.3" -webpki-roots = { version = "1.0.6", optional = true, default-features = false } +log = { workspace = true } +minimime = { workspace = true } +pin-project-lite = { workspace = true } +rand = { workspace = true } +regex = { workspace = true } +rustls = { workspace = true, optional = true } +rustls-native-certs = { workspace = true, optional = true } +rustls-pki-types = { workspace = true, optional = true } +rustls-platform-verifier = { workspace = true, optional = true } +serde = { workspace = true , features = ["derive"] } +tackle = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +urlencoding = { workspace = true } +webpki-roots = { workspace = true, optional = true, default-features = false } [target.'cfg(target_os = "linux")'.dependencies] compio = { version = "0.19.2", features = [ @@ -129,7 +132,7 @@ compio = { version = "0.19.2", features = [ ], default-features = false } [dev-dependencies] -caramelo = "0.1.2" +caramelo = { workspace = true } compio = { version = "0.19.2", default-features = false } criterion = { version = "0.8.2", features = [ "html_reports", @@ -138,13 +141,13 @@ criterion = { version = "0.8.2", features = [ "async_tokio", ] } deboa-test-utils = { workspace = true } -easyhttpmock-vetis-compio = { path = "../../easyhttpmock/easyhttpmock-vetis-compio", features = [ - #"http1", +easyhttpmock-vetis-compio = { version = "0.1.1", features = [ + "http1", "http2", #"http3", "rust-tls", ], default-features = false } -futures-util = "0.3.31" -multer = "3.1.0" -rstest = "0.26.1" -time = { version = "0.3.53" } +futures-util = { workspace = true } +multer = { workspace = true } +rstest = { workspace = true } +time = { workspace = true } diff --git a/deboa-h3/Cargo.toml b/deboa-h3/Cargo.toml index c54d555a..8e34affd 100644 --- a/deboa-h3/Cargo.toml +++ b/deboa-h3/Cargo.toml @@ -16,10 +16,10 @@ generic = ["dep:h3", "dep:h3-quinn", "hyper-body-utils/generic-h3", "hyper-body- compio = ["dep:h3", "dep:compio-quic", "hyper-body-utils/compio-h3", "hyper-body-utils/compio"] [dependencies] -bytes = { version = "1.11" } +bytes = { workspace = true } compio-quic = { version = "0.8.0", optional = true, default-features = false } deboa = { workspace = true } -h3 = { version = "0.0.8", optional = true, default-features = false } -h3-quinn = { version = "0.0.10", optional = true, default-features = false } -http = { version = "1.4.2", default-features = false } +h3 = { workspace = true, optional = true } +h3-quinn = { workspace = true, optional = true } +http = { workspace = true } hyper-body-utils = { workspace = true, optional = true, default-features = false } diff --git a/deboa-macros/Cargo.toml b/deboa-macros/Cargo.toml index 6068b4ce..0e189849 100644 --- a/deboa-macros/Cargo.toml +++ b/deboa-macros/Cargo.toml @@ -26,8 +26,8 @@ msgpack = ["deboa-extras/msgpack"] [dependencies] deboa = { workspace = true, default-features = false } deboa-extras = { workspace = true, default-features = false, optional = true } -http = "1.4.2" -proc-macro2 = "1.0.106" -quote = "1.0.45" -serde = "1.0.228" -syn = "3.0.0" +http = { workspace = true } +proc-macro2 = "1.0.107" +quote = "1.0.47" +serde = { workspace = true } +syn = "3.0.3" diff --git a/deboa-smol/Cargo.toml b/deboa-smol/Cargo.toml index bacf7777..8267cff6 100644 --- a/deboa-smol/Cargo.toml +++ b/deboa-smol/Cargo.toml @@ -16,17 +16,16 @@ rust-version.workspace = true features = [ "http1", "http2", - "http3", "rust-tls", - "native-tls", - "websockets", "default-rustls-provider", "default-rustls-verifier", ] [features] default = [ + "http1", "http2", + #"http3", "rust-tls", "default-rustls-provider", "default-rustls-verifier", @@ -74,55 +73,52 @@ http3 = [ websockets = ["ws-framer/http", "ws-framer/alloc"] [dependencies] -async-executor = { version = "1.13.3", optional = true, default-features = false } -async-lock = "3.4.2" -async-native-tls = { version = "0.6.0", optional = true, default-features = false } -base64 = "0.23.0" -bytes = { version = "1.11", default-features = false } -cookie = { version = "0.18.1", default-features = false } +async-executor = { workspace = true, optional = true } +async-lock = { workspace = true } +async-native-tls = { workspace = true, optional = true } +base64 = { workspace = true } +bytes = { workspace = true } +cookie = { workspace = true } deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } -futures = { version = "0.3.31", default-features = false } +futures = { workspace = true, optional = true } futures-rustls = { version = "0.26.0", optional = true, default-features = false } futures-timeout = "0.2.1" -futures-util = { version = "0.3.31", optional = true, default-features = false } -h3 = { version = "0.0.8", optional = true, default-features = false } -h3-quinn = { version = "0.0.10", optional = true, default-features = false } -hashbrown = "0.17.1" -http = "1" -http-body = "1" -http-body-util = "0.1" -hyper = { version = "1.10.1", features = ["client"], default-features = false } +futures-util = { workspace = true, optional = true } +h3 = { workspace = true, optional = true } +h3-quinn = { workspace = true, optional = true } +hashbrown = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } hyper-body-utils = { workspace = true, default-features = false } -hyper-util = { version = "0.1.20", features = [ - "client", - "client-legacy", -], default-features = false } +hyper-util = { workspace = true } indexmap = "2.11.4" -log = "0.4.32" +log = { workspace = true } macro_rules_attribute = { version = "0.2.2", default-features = false } -minimime = "1.0.0" -quinn = { version = "0.11.7", optional = true, default-features = false } -rand = { version = "0.10.1", default-features = false } -regex = { version = "1.12.4", default-features = false } +minimime = { workspace = true } +quinn = { version = "0.11.11", optional = true, default-features = false } +rand = { workspace = true } +regex = { workspace = true } rstest = "0.26.1" -rustls = { version = "0.23.36", optional = true, default-features = false } -rustls-native-certs = { version = "0.8.4", optional = true, default-features = false } -rustls-pki-types = { version = "1.14.1", optional = true, default-features = false } -rustls-platform-verifier = { version = "0.7.0", optional = true, default-features = false } -serde = { version = "1.0.217", features = ["derive"] } +rustls = { workspace = true, optional = true } +rustls-native-certs = { workspace = true, optional = true } +rustls-pki-types = { workspace = true, optional = true } +rustls-platform-verifier = { workspace = true, optional = true } +serde = { workspace = true , features = ["derive"] } smol = { version = "2.0.2", default-features = false } smol-hyper = { version = "0.1.0", default-features = false } smol-macros = { version = "0.1.1", default-features = false } -tackle = { version = "0.1.1"} -thiserror = "2.0.17" -url = "2.5.8" -urlencoding = "2.1.3" -webpki-roots = { version = "1.0.6", optional = true, default-features = false } -ws-framer = { version = "0.3.1", optional = true, default-features = false } +tackle = { workspace = true } +thiserror = { workspace = true } +url = { workspace = true } +urlencoding = { workspace = true } +webpki-roots = { workspace = true, optional = true, default-features = false } +ws-framer = { workspace = true, optional = true } [dev-dependencies] -caramelo = "0.1.2" +caramelo = { workspace = true } criterion = { version = "0.8.2", features = [ "html_reports", "async", @@ -132,11 +128,11 @@ deboa-test-utils = { workspace = true } easyhttpmock-vetis-smol = { version = "0.1.0", features = [ "http1", "http2", - "http3", + #"http3", "rust-tls", ], default-features = false } -futures-util = "0.3.31" -multer = "3.1.0" -rstest = "0.26.1" +futures-util = { workspace = true } +multer = { workspace = true } +rstest = { workspace = true } smol = "2.0.2" -time = { version = "0.3.53" } +time = { workspace = true } diff --git a/deboa-test-utils/Cargo.toml b/deboa-test-utils/Cargo.toml index 2c1b1db3..ca31ffdb 100644 --- a/deboa-test-utils/Cargo.toml +++ b/deboa-test-utils/Cargo.toml @@ -10,8 +10,8 @@ license.workspace = true rust-version.workspace = true [dependencies] -bytes = "1.12.1" -caramelo = "0.1.2" +bytes = { workspace = true } +caramelo = { workspace = true } ciborium = "0.2.2" deboa.workspace = true deboa-extras = { workspace = true, default-features = false, features = [ @@ -23,21 +23,18 @@ easyhttpmock = "0.1.3" env_logger = "0.11.11" fory = "1.5.0" fory-core = "1.5.0" -futures-util = "0.3.33" -http = "1.5.0" -http-body-util = "0.1" +futures-util = { workspace = true } +http = { workspace = true } +http-body-util = { workspace = true } mime = { version = "0.3.17" } multer = "3.1.0" once_cell = "1.21.4" -rand = "0.10.2" -serde = "1.0.229" -tackle = "0.1.3" -url = "2.5.8" +rand = { workspace = true } +serde = { workspace = true } +tackle = { workspace = true } +url = { workspace = true } vamo = { workspace = true, default-features = false } vamo-macros = { workspace = true, features = [ "json", ], default-features = false } vetis = "0.1.4" - -[dev-dependencies] -caramelo = "0.1.2" diff --git a/deboa-tokio/Cargo.toml b/deboa-tokio/Cargo.toml index 2f691072..c02e2988 100644 --- a/deboa-tokio/Cargo.toml +++ b/deboa-tokio/Cargo.toml @@ -16,17 +16,16 @@ rust-version.workspace = true features = [ "http1", "http2", - "http3", "rust-tls", - "native-tls", - "websockets", "default-rustls-provider", "default-rustls-verifier", ] [features] default = [ + "http1", "http2", + #"http3", "rust-tls", "default-rustls-provider", "default-rustls-verifier", @@ -75,45 +74,40 @@ http3 = [ websockets = ["ws-framer/http", "ws-framer/alloc"] [dependencies] -async-executor = { version = "1.13.3", optional = true, default-features = false } -async-lock = "3.4.2" -async-native-tls = { version = "0.6.0", optional = true, default-features = false } -base64 = { version = "0.23.0" } -bytes = { version = "1.11", default-features = false } -cookie = { version = "0.18.1", default-features = false } +async-executor = { workspace = true, optional = true } +async-lock = { workspace = true } +async-native-tls = { workspace = true, optional = true } +base64 = { workspace = true } +bytes = { workspace = true } +cookie = { workspace = true } deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } -futures = "0.3.31" -futures-util = { version = "0.3.31", optional = true, default-features = false } -h3 = { version = "0.0.8", optional = true, default-features = false } -h3-quinn = { version = "0.0.10", optional = true, default-features = false } -hashbrown = "0.17.1" -http = "1" -http-body = "1" -http-body-util = "0.1" -hyper = { version = "1.10.1", features = ["client"], default-features = false } +futures = { workspace = true, optional = true } +futures-util = { workspace = true, optional = true } +h3 = { workspace = true, optional = true } +h3-quinn = { workspace = true, optional = true } +hashbrown = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } hyper-body-utils = { workspace = true, default-features = false } -hyper-util = { version = "0.1.20", features = [ - "client", - "client-legacy", - "tokio", -], default-features = false } +hyper-util = { workspace = true } indexmap = "2.11.4" -log = "0.4.32" -minimime = "1.0.0" +log = { workspace = true } +minimime = { workspace = true } quinn = { version = "0.11.7", optional = true, features = [ "runtime-tokio", ], default-features = false } -rand = { version = "0.10.1", default-features = false } -regex = { version = "1.12.4", default-features = false } -rustls = { version = "0.23.36", optional = true, default-features = false } -rustls-native-certs = { version = "0.8.4", optional = true, default-features = false } -rustls-pki-types = { version = "1.14.1", optional = true, default-features = false } -rustls-platform-verifier = { version = "0.7.0", optional = true, default-features = false } -serde = { version = "1.0.217", features = ["derive"] } -tackle = { version = "0.1.1" } -thiserror = "2.0.17" -time = { version = "0.3.51", default-features = false } +rand = { workspace = true } +regex = { workspace = true } +rustls = { workspace = true, optional = true } +rustls-native-certs = { workspace = true, optional = true } +rustls-pki-types = { workspace = true, optional = true } +rustls-platform-verifier = { workspace = true, optional = true } +serde = { workspace = true , features = ["derive"] } +tackle = { workspace = true } +thiserror = { workspace = true } tokio = { version = "1.53.1", features = [ "macros", "fs", @@ -121,23 +115,28 @@ tokio = { version = "1.53.1", features = [ ], default-features = false } tokio-rustls = { version = "0.26.4", optional = true, default-features = false } tokio-util = { version = "0.7.11", features = ["io"], default-features = false } -url = "2.5.8" -urlencoding = "2.1.3" -webpki-roots = { version = "1.0.6", optional = true, default-features = false } -ws-framer = { version = "0.3.1", optional = true, default-features = false } +url = { workspace = true } +urlencoding = { workspace = true } +webpki-roots = { workspace = true, optional = true, default-features = false } +ws-framer = { workspace = true, optional = true, default-features = false } [dev-dependencies] -caramelo = "0.1.2" +caramelo = { workspace = true } criterion = { version = "0.8.2", features = [ "html_reports", "async", "async_tokio", ] } deboa-test-utils = { workspace = true } -easyhttpmock-vetis-tokio = { version = "0.1.0", features = ["http2", "rust-tls"], default-features = false } -futures-util = "0.3.31" -multer = "3.1.0" -rstest = "0.26.1" +easyhttpmock-vetis-tokio = { version = "0.1.0", features = [ + "http1", + "http2", + #"http3", + "rust-tls", +], default-features = false } +futures-util = { workspace = true } +multer = { workspace = true } +rstest = { workspace = true } tokio = { version = "1.53.1", features = [ "macros", "fs", From 15d471966d5e464dab540c6779a6a8277b168330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Wed, 19 Aug 2026 20:57:10 -0300 Subject: [PATCH 04/13] chore: publishing new releases --- Cargo.lock | 2 +- deboa-h3/Cargo.toml | 2 +- deboa-smol/Cargo.toml | 1 + deboa-smol/README.md | 2 +- deboa-tokio/Cargo.toml | 3 ++- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4d6ae35..d981bd23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1137,7 +1137,7 @@ dependencies = [ [[package]] name = "deboa-h3" -version = "0.1.1" +version = "0.1.2" dependencies = [ "bytes", "compio-quic", diff --git a/deboa-h3/Cargo.toml b/deboa-h3/Cargo.toml index 8e34affd..5077945a 100644 --- a/deboa-h3/Cargo.toml +++ b/deboa-h3/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "deboa-h3" -version = "0.1.1" +version = "0.1.2" edition.workspace = true authors.workspace = true repository.workspace = true diff --git a/deboa-smol/Cargo.toml b/deboa-smol/Cargo.toml index 8267cff6..8fa39443 100644 --- a/deboa-smol/Cargo.toml +++ b/deboa-smol/Cargo.toml @@ -62,6 +62,7 @@ http1 = ["rustls/tls12", "hyper/http1", "hyper-util/http1"] http2 = ["rustls/tls12", "hyper/http2", "hyper-util/http2"] http3 = [ "deboa-h3/generic", + "dep:futures", "dep:h3", "dep:h3-quinn", "dep:rustls", diff --git a/deboa-smol/README.md b/deboa-smol/README.md index f3b4369f..14769082 100644 --- a/deboa-smol/README.md +++ b/deboa-smol/README.md @@ -61,7 +61,7 @@ http = "1.3.1" ## Usage -```rust +```rust,ignore use deboa::{ Client, request::{DeboaRequest, FetchWith, get}, diff --git a/deboa-tokio/Cargo.toml b/deboa-tokio/Cargo.toml index c02e2988..89d72ff5 100644 --- a/deboa-tokio/Cargo.toml +++ b/deboa-tokio/Cargo.toml @@ -64,6 +64,7 @@ http1 = ["rustls/tls12", "hyper/http1", "hyper-util/http1"] http2 = ["rustls/tls12", "hyper/http2", "hyper-util/http2"] http3 = [ "deboa-h3/generic", + "dep:futures", "dep:h3", "dep:h3-quinn", "quinn/runtime-tokio", @@ -128,7 +129,7 @@ criterion = { version = "0.8.2", features = [ "async_tokio", ] } deboa-test-utils = { workspace = true } -easyhttpmock-vetis-tokio = { version = "0.1.0", features = [ +easyhttpmock-vetis-tokio = { version = "0.1.1", features = [ "http1", "http2", #"http3", From 62b73f7a383f46afe8dde821b2faae5c7036ae89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Wed, 19 Aug 2026 21:10:49 -0300 Subject: [PATCH 05/13] chore: some additional fixes --- deboa-compio/tests/common/helpers.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/deboa-compio/tests/common/helpers.rs b/deboa-compio/tests/common/helpers.rs index ecb1e0eb..012860e8 100644 --- a/deboa-compio/tests/common/helpers.rs +++ b/deboa-compio/tests/common/helpers.rs @@ -72,8 +72,12 @@ pub async fn tls_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) - .protocol_version(protocol_version()) + .interface( + interface + .parse() + .unwrap(), + ) + .protos(vec![protocol_version()]) .with_random_port() .cert(server_cert.to_vec()) .key(server_key.to_vec()) @@ -102,8 +106,12 @@ pub async fn plain_mock_server() -> EasyHttpMock { let vetis_adapter_config = VetisAdapterConfig::builder() .hostname(&hostname) - .interface(&interface) - .protocol_version(protocol_version()) + .interface( + interface + .parse() + .unwrap(), + ) + .protos(vec![protocol_version()]) .with_random_port() .build(); From cd6e9c204b1ee79e80cfc96ca8aca7388d84e02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Fri, 21 Aug 2026 20:22:59 -0300 Subject: [PATCH 06/13] feat: added support to QUERY method. --- deboa/src/errors.rs | 29 ++++++++++++++++++----- deboa/src/request.rs | 53 ++++++++++++++++++++++++++++++++++++++++++- deboa/src/response.rs | 11 +++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/deboa/src/errors.rs b/deboa/src/errors.rs index 5a0231e7..3e3d8fa2 100644 --- a/deboa/src/errors.rs +++ b/deboa/src/errors.rs @@ -44,8 +44,7 @@ //! } //! Ok(()) //! } -//! ``` - +//! `` use http::StatusCode; use thiserror::Error; @@ -171,28 +170,28 @@ pub enum RequestError { #[derive(Debug, Clone, Error, PartialEq)] pub enum ConnectionError { /// Tcp connection error - #[error("Tcp connection error: {message}")] + #[error("Tcp error: {message}")] Tcp { /// Error message message: String, }, /// Tls connection error - #[error("Tls connection error: {message}")] + #[error("Tls error: {message}")] Tls { /// Error message message: String, }, /// Udp connection error - #[error("Udp connection error: {message}")] + #[error("Udp error: {message}")] Udp { /// Error message message: String, }, /// Connection handshake error - #[error("Connection handshake error: {message}")] + #[error("Handshake error: {message}")] Handshake { /// Error message message: String, @@ -238,6 +237,24 @@ pub enum ContentError { }, } +/// WebSocket errors +#[derive(Debug, Clone, Error, PartialEq)] +pub enum WebSocketError { + /// Failed to send message + #[error("Failed to send message: {message}")] + SendMessage { + /// The error message + message: String, + }, + + /// Failed to receive message + #[error("Failed to receive message: {message}")] + ReceiveMessage { + /// The error message + message: String, + }, +} + /// Connection error #[derive(Debug, Clone, Error, PartialEq)] pub enum DnsError { diff --git a/deboa/src/request.rs b/deboa/src/request.rs index e43e3c93..5331c37b 100644 --- a/deboa/src/request.rs +++ b/deboa/src/request.rs @@ -240,6 +240,7 @@ impl MethodExt for Method { match self { Method::GET => DeboaRequest::get(url), Method::POST => DeboaRequest::post(url), + Method::QUERY => DeboaRequest::query(url), Method::PUT => DeboaRequest::put(url), Method::DELETE => DeboaRequest::delete(url), Method::PATCH => DeboaRequest::patch(url), @@ -258,6 +259,7 @@ impl MethodExt for &str { match self { "GET" | "get" => DeboaRequest::get(url), "POST" | "post" => DeboaRequest::post(url), + "QUERY" | "query" => DeboaRequest::query(url), "PUT" | "put" => DeboaRequest::put(url), "DELETE" | "delete" => DeboaRequest::delete(url), "PATCH" | "patch" => DeboaRequest::patch(url), @@ -434,6 +436,36 @@ pub fn post(url: T) -> Result { DeboaRequest::post(url) } +/// A utility function to create a QUERY request within DeboaRequest. +/// +/// # Arguments +/// +/// * `url` - The url to connect. +/// +/// # Returns +/// +/// * `Result` - The request builder. +/// +/// # Examples +/// +/// ```rust,compile_fail +/// use deboa::{request::query}; +/// use deboa_tokio::Client; +/// +/// let client = Client::new(); +/// +/// let request = query("https://jsonplaceholder.typicode.com/posts")? +/// .raw_body(b"{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}") +/// .build()?; +/// let response = request.send_with(&client).await?; +/// assert_eq!(response.status(), 201); +/// ``` +/// +#[inline] +pub fn query(url: T) -> Result { + DeboaRequest::query(url) +} + /// A utility function to create a PUT request within DeboaRequest. /// /// # Arguments @@ -1050,7 +1082,7 @@ impl FromStr for DeboaRequest { let mut is_reading_body = false; let method_url_regex = - Regex::new(r"(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(https?://[^\s]+)"); + Regex::new(r"(GET|POST|QUERY|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(https?://[^\s]+)"); if let Err(e) = method_url_regex { error!("Failed to parse request: {}", e); return Err(DeboaError::Request(RequestError::Parse { message: e.to_string() })); @@ -1314,6 +1346,25 @@ impl DeboaRequest { Ok(DeboaRequest::to(url)?.method(Method::POST)) } + /// Allow make a QUERY request. + /// + /// # Arguments + /// + /// * `url` - The url to be requested. + /// + /// # Returns + /// + /// * `DeboaRequestBuilder` - The request builder. + /// + /// # Panics + /// + /// * If URL is invalid + /// + #[inline] + pub fn query(url: T) -> Result { + Ok(DeboaRequest::to(url)?.method(Method::QUERY)) + } + /// Allow make a PUT request. /// /// # Arguments diff --git a/deboa/src/response.rs b/deboa/src/response.rs index 4b992c20..e859901c 100644 --- a/deboa/src/response.rs +++ b/deboa/src/response.rs @@ -503,6 +503,17 @@ impl DeboaResponse { } } + /// Allow get inner response at any time. + /// + /// # Returns + /// + /// * `DeboaBody` - The inner response. + /// + #[inline] + pub fn into_inner(self) -> Response { + self.inner + } + /// Allow get inner response body at any time. /// /// # Returns From 4355ce1c12954e39131e350d376bc8f7280136e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Fri, 21 Aug 2026 20:24:49 -0300 Subject: [PATCH 07/13] feat: added deboa-ws for websockets support --- deboa-ws/Cargo.toml | 31 ++++ deboa-ws/src/errors.rs | 16 ++ deboa-ws/src/lib.rs | 369 +++++++++++++++++++++++++++++++++++++++++ deboa-ws/src/smol.rs | 179 ++++++++++++++++++++ deboa-ws/src/tokio.rs | 154 +++++++++++++++++ 5 files changed, 749 insertions(+) create mode 100644 deboa-ws/Cargo.toml create mode 100644 deboa-ws/src/errors.rs create mode 100644 deboa-ws/src/lib.rs create mode 100644 deboa-ws/src/smol.rs create mode 100644 deboa-ws/src/tokio.rs diff --git a/deboa-ws/Cargo.toml b/deboa-ws/Cargo.toml new file mode 100644 index 00000000..3a0abb80 --- /dev/null +++ b/deboa-ws/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "deboa-ws" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +readme.workspace = true +license.workspace = true +rust-version.workspace = true + +[features] + +default = ["tokio"] +tokio = ["dep:tokio", "hyper-util/tokio"] +smol = ["dep:smol", "dep:smol-hyper"] + +[dependencies] +base64 = { workspace = true } +deboa = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true } +pin-project-lite = { workspace = true } +rand = { workspace = true } +smol = { version = "2.0.2", optional = true, default-features = false } +smol-hyper = { version = "0.1.0", optional = true, default-features = false } +thiserror = { workspace = true } +tokio = { version = "1.53.1", optional = true, features = ["io-std"] } +ws-framer = { workspace = true } diff --git a/deboa-ws/src/errors.rs b/deboa-ws/src/errors.rs new file mode 100644 index 00000000..b28fe0b5 --- /dev/null +++ b/deboa-ws/src/errors.rs @@ -0,0 +1,16 @@ +use thiserror::Error; + +#[derive(Debug, Clone, Error, PartialEq)] +pub enum WebSocketError { + #[error("Error receiving message: {message}")] + ReceiveMessage { + /// Error message + message: String, + }, + + #[error("Error sending message: {message}")] + SendMessage { + /// Error message + message: String, + }, +} diff --git a/deboa-ws/src/lib.rs b/deboa-ws/src/lib.rs new file mode 100644 index 00000000..29673f9d --- /dev/null +++ b/deboa-ws/src/lib.rs @@ -0,0 +1,369 @@ +//! WebSockets module +use std::future::Future; + +use base64::{engine::general_purpose::STANDARD, Engine}; +use deboa::{ + request::{DeboaRequest, DeboaRequestBuilder}, + url::IntoUrl, +}; +use http::{header, Method}; +use pin_project_lite::pin_project; + +use crate::errors::WebSocketError; + +pub mod errors; + +/// Smol runtime support +#[cfg(feature = "smol")] +pub mod smol; +/// Tokio runtime support +#[cfg(feature = "tokio")] +pub mod tokio; + +/// Result alias +pub type Result = std::result::Result; + +/// Message enum +/// +/// # Variants +/// +/// * `Text(String)` - A text message. +/// * `Binary(Vec)` - A binary message. +/// * `Close(u16, String)` - A close message. +/// * `Ping(Vec)` - A ping message. +/// * `Pong(Vec)` - A pong message. +#[derive(Clone)] +pub enum Message { + /// A text message + Text(String), + /// BBinary message + Binary(Vec), + /// Close message + Close(u16, String), + /// Ping message + Ping(Vec), + /// Pong reply message + Pong(Vec), +} + +/// Trait for building websocket requests +pub trait WebsocketRequestBuilder { + /// Creates a websocket request + /// + /// # Arguments + /// + /// * `url` - The URL to connect to + /// + /// # Returns + /// + /// A Result containing the DeboaRequestBuilder + /// + /// # Example + /// + /// ``` compile_fail + /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; + /// use deboa_extras::http::ws::request::{WebsocketRequestBuilder}; + /// + /// let mut client = Client::new(); + /// let request = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); + /// let response = request.send_with(&mut client).await.unwrap(); + /// let ws = response.into_websocket().unwrap(); + /// loop { + /// if let Ok(Some(message)) = ws.read_message().await { + /// println!("message: {}", message); + /// } + /// } + /// ``` + fn websocket(url: T) -> deboa::Result; +} + +impl WebsocketRequestBuilder for DeboaRequestBuilder { + fn websocket(url: T) -> deboa::Result { + let rnd: [u8; 16] = rand::random(); + let key = STANDARD.encode(rnd); + Ok(DeboaRequest::at(url, Method::GET)? + .header(header::UPGRADE, "websocket") + .header(header::CONNECTION, "Upgrade") + .header(header::SEC_WEBSOCKET_KEY, &key) + .header(header::SEC_WEBSOCKET_VERSION, "13")) + } +} + +/// Trait for converting a DeboaResponse into a WebSocket +pub trait IntoWebSocket { + type UpgradedIo; + /// Converts a DeboaResponse into a WebSocket + /// + /// # Arguments + /// + /// * `self` - The DeboaResponse to convert + /// + /// # Returns + /// + /// A Result containing the WebSocket + /// + /// # Example + /// + /// ``` compile_fail + /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; + /// use deboa_smol::client::ws::request::{WebsocketRequestBuilder}; + /// + /// let mut client = Client::new(); + /// let builder = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); + /// let response = builder + /// .send_with(&mut client) + /// .await + /// .unwrap(); + /// let websocket = response.into_websocket().unwrap(); + /// + /// loop { + /// if let Ok(Some(message)) = websocket.read_message().await { + /// println!("message: {}", message); + /// } + /// } + /// ``` + fn into_websocket(self) -> impl Future>>; +} + +pub trait WebSocketRead { + /// Reads a message from the WebSocket. + /// + /// # Returns + /// + /// A Result containing an Option or a DeboaExtrasError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// while let Some(message) = websocket.read_message().await { + /// println!("message: {}", message); + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + fn read_message(&mut self) -> impl Future>>; +} + +pub trait WebSocketWrite { + /// Writes a message to the WebSocket. + /// + /// # Arguments + /// + /// * `message` - The message to write. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaExtrasError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket + /// .write_message(protocol::Message::Text(message.to_string())) + /// .await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + /// + fn write_message(&mut self, message: Message) -> impl Future>; +} + +/// Trait for WebSockets +pub trait WebSocketExt { + /// Close connection + fn send_close(&mut self, code: u16, reason: &str) -> impl Future>; + /// Send a text message + fn send_text(&mut self, message: &str) -> impl Future>; + /// Send binary content + fn send_binary(&mut self, message: &[u8]) -> impl Future>; + /// Send ping message + fn send_ping(&mut self, message: &[u8]) -> impl Future>; + /// Send pong message + fn send_pong(&mut self, message: &[u8]) -> impl Future>; +} + +pin_project! { + /// WebSocket struct + pub struct WebSocket + { + #[pin] + inner: T, + } +} + +impl WebSocket { + /// new method + /// + /// # Arguments + /// + /// * `inner` - A inner stream. + /// + /// # Returns + /// + /// A WebSocket struct. + /// + pub fn new(inner: T) -> Self { + Self { inner } + } +} + +impl WebSocketExt for WebSocket +where + Self: WebSocketRead + WebSocketWrite, +{ + /// Sends a close frame to the WebSocket. + /// + /// # Arguments + /// + /// * `code` - The close code. + /// * `reason` - The close reason. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaExtrasError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket.send_close(1000, "Goodbye").await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + async fn send_close(&mut self, code: u16, reason: &str) -> Result<()> { + self.write_message(Message::Close(code, reason.to_string())) + .await + } + + /// Sends a text frame to the WebSocket. + /// + /// # Arguments + /// + /// * `message` - The text message to send. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaExtrasError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket.send_text("Hello").await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + async fn send_text(&mut self, message: &str) -> Result<()> { + self.write_message(Message::Text(message.to_string())) + .await + } + + /// Sends a binary frame to the WebSocket. + /// + /// # Arguments + /// + /// * `message` - The binary message to send. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket.send_binary(&[0x00, 0x01, 0x02]).await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + async fn send_binary(&mut self, message: &[u8]) -> Result<()> { + self.write_message(Message::Binary(message.to_vec())) + .await + } + + /// Sends a ping frame to the WebSocket. + /// + /// # Arguments + /// + /// * `message` - The ping message to send. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket.send_ping(&[0x00, 0x01, 0x02]).await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + async fn send_ping(&mut self, message: &[u8]) -> Result<()> { + self.write_message(Message::Ping(message.to_vec())) + .await + } + + /// Sends a pong frame to the WebSocket. + /// + /// # Arguments + /// + /// * `message` - The pong message to send. + /// + /// # Returns + /// + /// A Result indicating success or a DeboaError. + /// + /// # Examples + /// + /// ```rust, compile_fail + /// let result = websocket.send_pong(&[0x00, 0x01, 0x02]).await; + /// if result.is_err() { + /// output.send(Event::Disconnected).await; + /// break; + /// } + /// ``` + /// + /// # Panics + /// + /// This function may panic if the WebSocket frame processing fails. + /// + async fn send_pong(&mut self, message: &[u8]) -> Result<()> { + self.write_message(Message::Pong(message.to_vec())) + .await + } +} diff --git a/deboa-ws/src/smol.rs b/deboa-ws/src/smol.rs new file mode 100644 index 00000000..b14e596b --- /dev/null +++ b/deboa-ws/src/smol.rs @@ -0,0 +1,179 @@ +use crate::{ + errors::WebSocketError, IntoWebSocket, Message, Result, WebSocket, WebSocketRead, + WebSocketWrite, +}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + response::DeboaResponse, +}; +use hyper::{ + rt::ReadBuf, + upgrade::{on, Upgraded}, +}; +use smol::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use smol_hyper::rt::FuturesIo; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; +use ws_framer::{WsFrame, WsRxFramer, WsTxFramer}; + +impl IntoWebSocket for DeboaResponse { + type UpgradedIo = FuturesIo; + async fn into_websocket(self) -> deboa::Result> { + let upgraded = on(self.into_inner()) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Upgrade { message: e.to_string() }) + })?; + Ok(WebSocket::new(FuturesIo::new(upgraded))) + } +} + +impl WebSocketRead for WebSocket> { + async fn read_message(&mut self) -> Result> { + let mut rx_buf = vec![0; 10240]; + let mut rx_framer = WsRxFramer::new(&mut rx_buf); + + let bytes_read = self + .read(rx_framer.mut_buf()) + .await; + if bytes_read.is_err() { + return Err(WebSocketError::ReceiveMessage { + message: "Failed to read message".to_string(), + }); + } + + let bytes_read = bytes_read.unwrap(); + rx_framer.revolve_write_offset(bytes_read); + let res = rx_framer.process_data(); + let message = if let Some(frame) = res { + #[allow(clippy::collapsible_match)] + match frame { + WsFrame::Text(data) => Some(Message::Text(data.to_string())), + WsFrame::Binary(data) => Some(Message::Binary(data.to_vec())), + WsFrame::Close(code, reason) => Some(Message::Close(code, reason.to_string())), + WsFrame::Ping(data) => Some(Message::Ping(data.to_vec())), + _ => None, + } + } else { + None + }; + + Ok(message) + } +} + +impl WebSocketWrite for &mut WebSocket> { + async fn write_message(&mut self, message: Message) -> Result<()> { + let mut tx_buf = vec![0; 10240]; + let mut tx_framer = WsTxFramer::new(true, &mut tx_buf); + + let result = match message { + Message::Text(data) => { + self.write_all(tx_framer.frame(WsFrame::Text(&data))) + .await + } + Message::Binary(data) => { + self.write_all(tx_framer.frame(WsFrame::Binary(&data))) + .await + } + Message::Close(code, reason) => { + self.write_all(tx_framer.frame(WsFrame::Close(code, &reason))) + .await + } + Message::Ping(data) => { + self.write_all(tx_framer.frame(WsFrame::Ping(&data))) + .await + } + _ => Ok(()), + }; + + if result.is_err() { + return Err(WebSocketError::SendMessage { + message: "Failed to send frame".to_string(), + }); + } + + Ok(()) + } +} + +impl AsyncRead for WebSocket> +where + T: hyper::rt::Read, +{ + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let mut chunk = ReadBuf::new(buf); + let buf = chunk.unfilled(); + + let n = match self + .project() + .inner + .get_pin_mut() + .poll_read(cx, buf) + { + Poll::Ready(Ok(())) => chunk.filled().len(), + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => return Poll::Pending, + }; + + Poll::Ready(Ok(n)) + } +} + +impl AsyncWrite for WebSocket> +where + T: hyper::rt::Write, +{ + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + hyper::rt::Write::poll_write( + self.project() + .inner + .get_pin_mut(), + cx, + buf, + ) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + hyper::rt::Write::poll_flush( + self.project() + .inner + .get_pin_mut(), + cx, + ) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + hyper::rt::Write::poll_shutdown( + self.project() + .inner + .get_pin_mut(), + cx, + ) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + hyper::rt::Write::poll_write_vectored( + self.project() + .inner + .get_pin_mut(), + cx, + bufs, + ) + } +} diff --git a/deboa-ws/src/tokio.rs b/deboa-ws/src/tokio.rs new file mode 100644 index 00000000..22a5aff9 --- /dev/null +++ b/deboa-ws/src/tokio.rs @@ -0,0 +1,154 @@ +use crate::{ + errors::WebSocketError, IntoWebSocket, Message, Result, WebSocket, WebSocketRead, + WebSocketWrite, +}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + response::DeboaResponse, +}; +use hyper::upgrade::{on, Upgraded}; +use hyper_util::rt::TokioIo; +use std::{ + io, + pin::Pin, + task::{Context, Poll}, +}; +use tokio::io::{AsyncRead, AsyncReadExt as _, AsyncWrite, AsyncWriteExt as _, ReadBuf}; +use ws_framer::{WsFrame, WsRxFramer, WsTxFramer}; + +impl IntoWebSocket for DeboaResponse { + type UpgradedIo = TokioIo; + async fn into_websocket(self) -> deboa::Result> { + let upgraded = on(self.into_inner()) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Upgrade { message: e.to_string() }) + })?; + Ok(WebSocket::new(TokioIo::new(upgraded))) + } +} + +impl WebSocketRead for WebSocket> { + async fn read_message(&mut self) -> Result> { + let mut rx_buf = vec![0; 10240]; + let mut rx_framer = WsRxFramer::new(&mut rx_buf); + + let bytes_read = self + .inner + .read(rx_framer.mut_buf()) + .await; + if bytes_read.is_err() { + return Err(WebSocketError::ReceiveMessage { + message: "Failed to read message".to_string(), + }); + } + + let bytes_read = bytes_read.unwrap(); + rx_framer.revolve_write_offset(bytes_read); + let res = rx_framer.process_data(); + let message = if let Some(frame) = res { + #[allow(clippy::collapsible_match)] + match frame { + WsFrame::Text(data) => Some(Message::Text(data.to_string())), + WsFrame::Binary(data) => Some(Message::Binary(data.to_vec())), + WsFrame::Close(code, reason) => Some(Message::Close(code, reason.to_string())), + WsFrame::Ping(data) => Some(Message::Ping(data.to_vec())), + _ => None, + } + } else { + None + }; + + Ok(message) + } +} + +impl WebSocketWrite for &mut WebSocket> { + async fn write_message(&mut self, message: Message) -> Result<()> { + let mut tx_buf = vec![0; 10240]; + let mut tx_framer = WsTxFramer::new(true, &mut tx_buf); + + let result = match message { + Message::Text(data) => { + self.write_all(tx_framer.frame(WsFrame::Text(&data))) + .await + } + Message::Binary(data) => { + self.write_all(tx_framer.frame(WsFrame::Binary(&data))) + .await + } + Message::Close(code, reason) => { + self.write_all(tx_framer.frame(WsFrame::Close(code, &reason))) + .await + } + Message::Ping(data) => { + self.write_all(tx_framer.frame(WsFrame::Ping(&data))) + .await + } + _ => Ok(()), + }; + + if result.is_err() { + return Err(WebSocketError::SendMessage { + message: "Failed to send frame".to_string(), + }); + } + + Ok(()) + } +} + +impl AsyncRead for WebSocket> { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + self.project() + .inner + .poll_read(cx, buf) + } +} + +impl AsyncWrite for WebSocket> { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.project() + .inner + .poll_write(cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project() + .inner + .poll_flush(cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project() + .inner + .poll_shutdown(cx) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + let buf = bufs + .iter() + .find(|b| !b.is_empty()) + .map_or(&[][..], |b| &**b); + self.project() + .inner + .poll_write(cx, buf) + } + + fn is_write_vectored(&self) -> bool { + self.inner + .is_write_vectored() + } +} From bcfb426caf91af838a4592a0746086baa3282aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Fri, 21 Aug 2026 20:26:23 -0300 Subject: [PATCH 08/13] feat: added deboa-tls to centralize tls support --- deboa-tls/Cargo.toml | 52 ++++++++++++++++++++++++++++++ deboa-tls/src/lib.rs | 4 +++ deboa-tls/src/native.rs | 0 deboa-tls/src/rust/mod.rs | 68 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 deboa-tls/Cargo.toml create mode 100644 deboa-tls/src/lib.rs create mode 100644 deboa-tls/src/native.rs create mode 100644 deboa-tls/src/rust/mod.rs diff --git a/deboa-tls/Cargo.toml b/deboa-tls/Cargo.toml new file mode 100644 index 00000000..5fddba64 --- /dev/null +++ b/deboa-tls/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "deboa-tls" +version = "0.1.0" +edition.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +readme.workspace = true +license.workspace = true +rust-version.workspace = true + +[features] +default = [ + "rust-tls", + "default-rustls-provider", + "default-rustls-verifier", +] + +rust-tls = [ + "rustls/tls12", + "dep:rustls-native-certs", + "dep:futures-rustls", + "dep:rustls-pki-types", +] +native-tls= [] + +default-rustls-verifier = ["__webpki_rustls_verifier"] +webpki-rustls-verifier = ["__webpki_rustls_verifier"] +platform-rustls-verifier = ["__platform_rustls_verifier"] +__webpki_rustls_verifier = ["dep:webpki-roots"] +__platform_rustls_verifier = ["dep:rustls-platform-verifier"] + +# rustls providers +default-rustls-provider = ["__rustls_aws_lc_rs"] +no-provider = [] +aws-lc-rustls-provider = ["__rustls_aws_lc_rs"] +ring-rustls-provider = ["__rustls_ring"] + +__rustls_aws_lc_rs = ["rustls/aws-lc-rs"] +__rustls_ring = ["rustls/ring"] + +tcp = [] +quic = [] + +[dependencies] +deboa = { workspace = true } +futures-rustls = { workspace = true, optional = true } +rustls = { workspace = true, optional = true } +rustls-native-certs = { workspace = true, optional = true } +rustls-pki-types = { workspace = true, optional = true } +rustls-platform-verifier = { workspace = true, optional = true } +webpki-roots = { workspace = true, optional = true } diff --git a/deboa-tls/src/lib.rs b/deboa-tls/src/lib.rs new file mode 100644 index 00000000..14dd904a --- /dev/null +++ b/deboa-tls/src/lib.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "native-tls")] +pub mod native; +#[cfg(feature = "rust-tls")] +pub mod rust; diff --git a/deboa-tls/src/native.rs b/deboa-tls/src/native.rs new file mode 100644 index 00000000..e69de29b diff --git a/deboa-tls/src/rust/mod.rs b/deboa-tls/src/rust/mod.rs new file mode 100644 index 00000000..aa50c262 --- /dev/null +++ b/deboa-tls/src/rust/mod.rs @@ -0,0 +1,68 @@ +pub mod verify { + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::CryptoProvider, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + use std::sync::Arc; + + #[derive(Debug)] + pub struct SkipServerVerification(CryptoProvider); + + impl SkipServerVerification { + pub fn new(provider: CryptoProvider) -> Arc { + Arc::new(Self(provider)) + } + } + + impl ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0 + .signature_verification_algorithms + .supported_schemes() + } + } +} From bb4917d7a64fbb5cda60eeb36c33c1f8d90ab85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Fri, 21 Aug 2026 20:28:59 -0300 Subject: [PATCH 09/13] chore: removed websockets code from deboa-smol and deboa-tokio crates. --- deboa-smol/src/client/ws/io/mod.rs | 1 - deboa-smol/src/client/ws/io/socket.rs | 386 ------------------------- deboa-smol/src/client/ws/mod.rs | 79 ----- deboa-smol/src/client/ws/protocol.rs | 17 -- deboa-smol/src/client/ws/request.rs | 51 ---- deboa-smol/src/client/ws/response.rs | 48 --- deboa-tokio/src/client/ws/io/mod.rs | 1 - deboa-tokio/src/client/ws/io/socket.rs | 383 ------------------------ deboa-tokio/src/client/ws/mod.rs | 79 ----- deboa-tokio/src/client/ws/protocol.rs | 17 -- deboa-tokio/src/client/ws/request.rs | 51 ---- deboa-tokio/src/client/ws/response.rs | 48 --- 12 files changed, 1161 deletions(-) delete mode 100644 deboa-smol/src/client/ws/io/mod.rs delete mode 100644 deboa-smol/src/client/ws/io/socket.rs delete mode 100644 deboa-smol/src/client/ws/mod.rs delete mode 100644 deboa-smol/src/client/ws/protocol.rs delete mode 100644 deboa-smol/src/client/ws/request.rs delete mode 100644 deboa-smol/src/client/ws/response.rs delete mode 100644 deboa-tokio/src/client/ws/io/mod.rs delete mode 100644 deboa-tokio/src/client/ws/io/socket.rs delete mode 100644 deboa-tokio/src/client/ws/mod.rs delete mode 100644 deboa-tokio/src/client/ws/protocol.rs delete mode 100644 deboa-tokio/src/client/ws/request.rs delete mode 100644 deboa-tokio/src/client/ws/response.rs diff --git a/deboa-smol/src/client/ws/io/mod.rs b/deboa-smol/src/client/ws/io/mod.rs deleted file mode 100644 index d22cc845..00000000 --- a/deboa-smol/src/client/ws/io/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod socket; diff --git a/deboa-smol/src/client/ws/io/socket.rs b/deboa-smol/src/client/ws/io/socket.rs deleted file mode 100644 index 8c7d8388..00000000 --- a/deboa-smol/src/client/ws/io/socket.rs +++ /dev/null @@ -1,386 +0,0 @@ -use crate::{ - errors::{DeboaExtrasError, WebSocketError}, - ws::protocol::Message, -}; -use hyper::upgrade::Upgraded; -use pin_project_lite::pin_project; -use smol::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use smol_hyper::rt::FuturesIo; -use std::{ - future::Future, - io, - pin::Pin, - task::{Context, Poll}, -}; -use ws_framer::{WsFrame, WsRxFramer, WsTxFramer}; - -pub type UpgradedIo = FuturesIo; - -pub trait DeboaWebSocket { - type Stream; - - fn new(stream: Self::Stream) -> Self; - fn read_message(&mut self) -> impl Future, DeboaExtrasError>>; - fn write_message( - &mut self, - message: Message, - ) -> impl Future>; - fn send_close( - &mut self, - code: u16, - reason: &str, - ) -> impl Future>; - fn send_text(&mut self, message: &str) -> impl Future>; - fn send_binary(&mut self, message: &[u8]) - -> impl Future>; - fn send_ping(&mut self, message: &[u8]) -> impl Future>; - fn send_pong(&mut self, message: &[u8]) -> impl Future>; -} - -pin_project! { - /// WebSocket struct - pub struct WebSocket - { - #[pin] - stream: T, - } -} - -impl DeboaWebSocket for WebSocket { - type Stream = UpgradedIo; - - /// new method - /// - /// # Arguments - /// - /// * `stream` - A string slice that holds the stream data. - /// - /// # Returns - /// - /// A WebSocket struct. - /// - fn new(stream: Self::Stream) -> Self { - Self { stream } - } - - /// Reads a message from the WebSocket. - /// - /// # Returns - /// - /// A Result containing an Option or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// while let Some(message) = websocket.read_message().await { - /// println!("message: {}", message); - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn read_message(&mut self) -> Result, DeboaExtrasError> { - let mut rx_buf = vec![0; 10240]; - let mut rx_framer = WsRxFramer::new(&mut rx_buf); - - let bytes_read = self - .stream - .read(rx_framer.mut_buf()) - .await; - if bytes_read.is_err() { - return Err(DeboaExtrasError::WebSocket(WebSocketError::ReceiveMessage { - message: "Failed to read message".to_string(), - })); - } - - let bytes_read = bytes_read.unwrap(); - rx_framer.revolve_write_offset(bytes_read); - let res = rx_framer.process_data(); - let message = if let Some(frame) = res { - #[allow(clippy::collapsible_match)] - match frame { - WsFrame::Text(data) => Some(Message::Text(data.to_string())), - WsFrame::Binary(data) => Some(Message::Binary(data.to_vec())), - WsFrame::Close(code, reason) => Some(Message::Close(code, reason.to_string())), - WsFrame::Ping(data) => Some(Message::Ping(data.to_vec())), - _ => None, - } - } else { - None - }; - - Ok(message) - } - - /// Writes a message to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The message to write. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket - /// .write_message(protocol::Message::Text(message.to_string())) - /// .await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - /// - async fn write_message(&mut self, message: Message) -> Result<(), DeboaExtrasError> { - let mut tx_buf = vec![0; 10240]; - let mut tx_framer = WsTxFramer::new(true, &mut tx_buf); - - let result = match message { - Message::Text(data) => { - self.write_all(tx_framer.frame(WsFrame::Text(&data))) - .await - } - Message::Binary(data) => { - self.write_all(tx_framer.frame(WsFrame::Binary(&data))) - .await - } - Message::Close(code, reason) => { - self.write_all(tx_framer.frame(WsFrame::Close(code, &reason))) - .await - } - Message::Ping(data) => { - self.write_all(tx_framer.frame(WsFrame::Ping(&data))) - .await - } - _ => Ok(()), - }; - - if result.is_err() { - return Err(DeboaExtrasError::WebSocket(WebSocketError::SendMessage { - message: "Failed to send frame".to_string(), - })); - } - - Ok(()) - } - - /// Sends a close frame to the WebSocket. - /// - /// # Arguments - /// - /// * `code` - The close code. - /// * `reason` - The close reason. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_close(1000, "Goodbye").await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_close(&mut self, code: u16, reason: &str) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Close(code, reason.to_string())) - .await - } - - /// Sends a text frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The text message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_text("Hello").await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_text(&mut self, message: &str) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Text(message.to_string())) - .await - } - - /// Sends a binary frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The binary message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_binary(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_binary(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Binary(message.to_vec())) - .await - } - - /// Sends a ping frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The ping message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_ping(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_ping(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Ping(message.to_vec())) - .await - } - - /// Sends a pong frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The pong message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_pong(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_pong(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Pong(message.to_vec())) - .await - } -} - -impl AsyncRead for WebSocket> -where - T: hyper::rt::Read, -{ - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Poll::Ready(Ok(0)) - } -} - -impl AsyncWrite for WebSocket> -where - T: hyper::rt::Write, -{ - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - hyper::rt::Write::poll_write( - self.project() - .stream - .get_pin_mut(), - cx, - buf, - ) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - hyper::rt::Write::poll_flush( - self.project() - .stream - .get_pin_mut(), - cx, - ) - } - - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - hyper::rt::Write::poll_shutdown( - self.project() - .stream - .get_pin_mut(), - cx, - ) - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll> { - hyper::rt::Write::poll_write_vectored( - self.project() - .stream - .get_pin_mut(), - cx, - bufs, - ) - } -} diff --git a/deboa-smol/src/client/ws/mod.rs b/deboa-smol/src/client/ws/mod.rs deleted file mode 100644 index e3c72e99..00000000 --- a/deboa-smol/src/client/ws/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! -//! WebSocket support for deboa-extras. -//! Provides WebSocket client functionality with message encoding/decoding. -//! Supports text and binary message types with automatic serialization. -//! Includes support for various serialization formats through the serialization feature. -//! Requires the `websockets` feature to be enabled. -//! -//! -//! ## Example -//! ```rust, compile_fail -//! use deboa::{Client, Result, request::DeboaRequestBuilder}; -//! use deboa_extras::ws::{ -//! io::socket::DeboaWebSocket, -//! protocol::{self}, -//! request::WebsocketRequestBuilder, -//! response::IntoWebSocket, -//! }; -//! -//! let mut client = Client::new(); -//! -//! let websocket = DeboaRequestBuilder::websocket("wss://echo.websocket.org")? -//! .send_with(&mut client) -//! .await? -//! .into_websocket() -//! .await; -//! -//! loop { -//! select! { -//! outgoing_message = websocket.read_message() => { -//! if let Err(message) = outgoing_message { -//! println!("Failed to read message from echo server: {}", message); -//! -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! -//! match outgoing_message.unwrap() { -//! Some(message) => { -//! if let protocol::Message::Text(message) = message { -//! output -//! .send(Event::MessageReceived(Message::User( -//! format!("Server: {}", message).to_string(), -//! ))) -//! .await; -//! } -//! } -//! None => { -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! } -//! } -//! -//! incoming_message = input.next() => { -//! if let Some(message) = incoming_message { -//! let result = websocket -//! .write_message(protocol::Message::Text(message.to_string())) -//! .await; -//! if result.is_err() { -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! } -//! } -//! } -//! } -//! ``` -//! -//! ## Modules -//! -//! * `io` - WebSocket I/O operations -//! * `protocol` - WebSocket protocol handling -//! * `request` - WebSocket request building -//! * `response` - WebSocket response parsing -//! -pub mod io; -pub mod protocol; -pub mod request; -pub mod response; diff --git a/deboa-smol/src/client/ws/protocol.rs b/deboa-smol/src/client/ws/protocol.rs deleted file mode 100644 index 0f8131c7..00000000 --- a/deboa-smol/src/client/ws/protocol.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Message enum -/// -/// # Variants -/// -/// * `Text(String)` - A text message. -/// * `Binary(Vec)` - A binary message. -/// * `Close(u16, String)` - A close message. -/// * `Ping(Vec)` - A ping message. -/// * `Pong(Vec)` - A pong message. -#[derive(Clone)] -pub enum Message { - Text(String), - Binary(Vec), - Close(u16, String), - Ping(Vec), - Pong(Vec), -} diff --git a/deboa-smol/src/client/ws/request.rs b/deboa-smol/src/client/ws/request.rs deleted file mode 100644 index 363e0f8a..00000000 --- a/deboa-smol/src/client/ws/request.rs +++ /dev/null @@ -1,51 +0,0 @@ -use base64::engine::general_purpose::STANDARD; -use base64::Engine; -use deboa::{ - request::{DeboaRequest, DeboaRequestBuilder}, - url::IntoUrl, - Result, -}; -use http::{header, Method}; - -/// Trait for building websocket requests -pub trait WebsocketRequestBuilder { - /// Creates a websocket request - /// - /// # Arguments - /// - /// * `url` - The URL to connect to - /// - /// # Returns - /// - /// A Result containing the DeboaRequestBuilder - /// - /// # Example - /// - /// ``` compile_fail - /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; - /// use deboa_extras::http::ws::request::{WebsocketRequestBuilder}; - /// - /// let mut client = Client::new(); - /// let request = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); - /// let response = request.send_with(&mut client).await.unwrap(); - /// let ws = response.into_websocket().unwrap(); - /// loop { - /// if let Ok(Some(message)) = ws.read_message().await { - /// println!("message: {}", message); - /// } - /// } - /// ``` - fn websocket(url: T) -> Result; -} - -impl WebsocketRequestBuilder for DeboaRequestBuilder { - fn websocket(url: T) -> Result { - let rnd: [u8; 16] = rand::random(); - let key = STANDARD.encode(rnd); - Ok(DeboaRequest::at(url, Method::GET)? - .header(header::UPGRADE, "websocket") - .header(header::CONNECTION, "Upgrade") - .header(header::SEC_WEBSOCKET_KEY, &key) - .header(header::SEC_WEBSOCKET_VERSION, "13")) - } -} diff --git a/deboa-smol/src/client/ws/response.rs b/deboa-smol/src/client/ws/response.rs deleted file mode 100644 index 3ab71f48..00000000 --- a/deboa-smol/src/client/ws/response.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::future::Future; - -use crate::ws::io::socket::{DeboaWebSocket, UpgradedIo, WebSocket}; -use deboa::{response::DeboaResponse, Result}; - -/// Trait for converting a DeboaResponse into a WebSocket -pub trait IntoWebSocket { - /// Converts a DeboaResponse into a WebSocket - /// - /// # Arguments - /// - /// * `self` - The DeboaResponse to convert - /// - /// # Returns - /// - /// A Result containing the WebSocket - /// - /// # Example - /// - /// ``` compile_fail - /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; - /// use deboa_extras::http::ws::request::{WebsocketRequestBuilder}; - /// - /// let mut client = Client::new(); - /// let builder = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); - /// let response = builder - /// .send_with(&mut client) - /// .await - /// .unwrap(); - /// let websocket = response.into_websocket().unwrap(); - /// - /// loop { - /// if let Ok(Some(message)) = websocket.read_message().await { - /// println!("message: {}", message); - /// } - /// } - /// ``` - fn into_websocket(self) -> impl Future>>; -} - -impl IntoWebSocket for DeboaResponse { - async fn into_websocket(self) -> Result> { - let upgraded = self - .upgrade() - .await?; - Ok(WebSocket::new(upgraded)) - } -} diff --git a/deboa-tokio/src/client/ws/io/mod.rs b/deboa-tokio/src/client/ws/io/mod.rs deleted file mode 100644 index d22cc845..00000000 --- a/deboa-tokio/src/client/ws/io/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod socket; diff --git a/deboa-tokio/src/client/ws/io/socket.rs b/deboa-tokio/src/client/ws/io/socket.rs deleted file mode 100644 index 14dd0f37..00000000 --- a/deboa-tokio/src/client/ws/io/socket.rs +++ /dev/null @@ -1,383 +0,0 @@ -use crate::{ - errors::{DeboaExtrasError, WebSocketError}, - ws::protocol::Message, -}; -use hyper::upgrade::Upgraded; -use hyper_util::rt::TokioIo; -use pin_project_lite::pin_project; -use std::{ - future::Future, - io, - pin::Pin, - task::{Context, Poll}, -}; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; -use ws_framer::{WsFrame, WsRxFramer, WsTxFramer}; - -pub type UpgradedIo = TokioIo; - -pub trait DeboaWebSocket { - type Stream; - - fn new(stream: Self::Stream) -> Self; - fn read_message(&mut self) -> impl Future, DeboaExtrasError>>; - fn write_message( - &mut self, - message: Message, - ) -> impl Future>; - fn send_close( - &mut self, - code: u16, - reason: &str, - ) -> impl Future>; - fn send_text(&mut self, message: &str) -> impl Future>; - fn send_binary(&mut self, message: &[u8]) - -> impl Future>; - fn send_ping(&mut self, message: &[u8]) -> impl Future>; - fn send_pong(&mut self, message: &[u8]) -> impl Future>; -} - -pin_project! { - /// WebSocket struct - pub struct WebSocket - { - #[pin] - stream: T, - } -} - -impl DeboaWebSocket for WebSocket { - type Stream = UpgradedIo; - - /// new method - /// - /// # Arguments - /// - /// * `stream` - A string slice that holds the stream data. - /// - /// # Returns - /// - /// A WebSocket struct. - /// - fn new(stream: Self::Stream) -> Self { - Self { stream } - } - - /// Reads a message from the WebSocket. - /// - /// # Returns - /// - /// A Result containing an Option or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// while let Some(message) = websocket.read_message().await { - /// println!("message: {}", message); - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn read_message(&mut self) -> Result, DeboaExtrasError> { - let mut rx_buf = vec![0; 10240]; - let mut rx_framer = WsRxFramer::new(&mut rx_buf); - - let bytes_read = self - .stream - .read(rx_framer.mut_buf()) - .await; - if bytes_read.is_err() { - return Err(DeboaExtrasError::WebSocket(WebSocketError::ReceiveMessage { - message: "Failed to read message".to_string(), - })); - } - - let bytes_read = bytes_read.unwrap(); - rx_framer.revolve_write_offset(bytes_read); - let res = rx_framer.process_data(); - let message = if let Some(frame) = res { - #[allow(clippy::collapsible_match)] - match frame { - WsFrame::Text(data) => Some(Message::Text(data.to_string())), - WsFrame::Binary(data) => Some(Message::Binary(data.to_vec())), - WsFrame::Close(code, reason) => Some(Message::Close(code, reason.to_string())), - WsFrame::Ping(data) => Some(Message::Ping(data.to_vec())), - _ => None, - } - } else { - None - }; - - Ok(message) - } - - /// Writes a message to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The message to write. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket - /// .write_message(protocol::Message::Text(message.to_string())) - /// .await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - /// - async fn write_message(&mut self, message: Message) -> Result<(), DeboaExtrasError> { - let mut tx_buf = vec![0; 10240]; - let mut tx_framer = WsTxFramer::new(true, &mut tx_buf); - - let result = match message { - Message::Text(data) => { - self.write_all(tx_framer.frame(WsFrame::Text(&data))) - .await - } - Message::Binary(data) => { - self.write_all(tx_framer.frame(WsFrame::Binary(&data))) - .await - } - Message::Close(code, reason) => { - self.write_all(tx_framer.frame(WsFrame::Close(code, &reason))) - .await - } - Message::Ping(data) => { - self.write_all(tx_framer.frame(WsFrame::Ping(&data))) - .await - } - _ => Ok(()), - }; - - if result.is_err() { - return Err(DeboaExtrasError::WebSocket(WebSocketError::SendMessage { - message: "Failed to send frame".to_string(), - })); - } - - Ok(()) - } - - /// Sends a close frame to the WebSocket. - /// - /// # Arguments - /// - /// * `code` - The close code. - /// * `reason` - The close reason. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_close(1000, "Goodbye").await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_close(&mut self, code: u16, reason: &str) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Close(code, reason.to_string())) - .await - } - - /// Sends a text frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The text message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_text("Hello").await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_text(&mut self, message: &str) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Text(message.to_string())) - .await - } - - /// Sends a binary frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The binary message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_binary(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_binary(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Binary(message.to_vec())) - .await - } - - /// Sends a ping frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The ping message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_ping(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_ping(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Ping(message.to_vec())) - .await - } - - /// Sends a pong frame to the WebSocket. - /// - /// # Arguments - /// - /// * `message` - The pong message to send. - /// - /// # Returns - /// - /// A Result indicating success or a DeboaExtrasError. - /// - /// # Examples - /// - /// ```rust, compile_fail - /// let result = websocket.send_pong(&[0x00, 0x01, 0x02]).await; - /// if result.is_err() { - /// output.send(Event::Disconnected).await; - /// break; - /// } - /// ``` - /// - /// # Panics - /// - /// This function may panic if the WebSocket frame processing fails. - /// - async fn send_pong(&mut self, message: &[u8]) -> Result<(), DeboaExtrasError> { - self.write_message(Message::Pong(message.to_vec())) - .await - } -} - -impl AsyncRead for WebSocket { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - self.project() - .stream - .poll_read(cx, buf) - } -} - -impl AsyncWrite for WebSocket { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> std::task::Poll> { - self.project() - .stream - .poll_write(cx, buf) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project() - .stream - .poll_flush(cx) - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project() - .stream - .poll_shutdown(cx) - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll> { - let buf = bufs - .iter() - .find(|b| !b.is_empty()) - .map_or(&[][..], |b| &**b); - self.project() - .stream - .poll_write(cx, buf) - } - - fn is_write_vectored(&self) -> bool { - self.stream - .is_write_vectored() - } -} diff --git a/deboa-tokio/src/client/ws/mod.rs b/deboa-tokio/src/client/ws/mod.rs deleted file mode 100644 index e3c72e99..00000000 --- a/deboa-tokio/src/client/ws/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! -//! WebSocket support for deboa-extras. -//! Provides WebSocket client functionality with message encoding/decoding. -//! Supports text and binary message types with automatic serialization. -//! Includes support for various serialization formats through the serialization feature. -//! Requires the `websockets` feature to be enabled. -//! -//! -//! ## Example -//! ```rust, compile_fail -//! use deboa::{Client, Result, request::DeboaRequestBuilder}; -//! use deboa_extras::ws::{ -//! io::socket::DeboaWebSocket, -//! protocol::{self}, -//! request::WebsocketRequestBuilder, -//! response::IntoWebSocket, -//! }; -//! -//! let mut client = Client::new(); -//! -//! let websocket = DeboaRequestBuilder::websocket("wss://echo.websocket.org")? -//! .send_with(&mut client) -//! .await? -//! .into_websocket() -//! .await; -//! -//! loop { -//! select! { -//! outgoing_message = websocket.read_message() => { -//! if let Err(message) = outgoing_message { -//! println!("Failed to read message from echo server: {}", message); -//! -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! -//! match outgoing_message.unwrap() { -//! Some(message) => { -//! if let protocol::Message::Text(message) = message { -//! output -//! .send(Event::MessageReceived(Message::User( -//! format!("Server: {}", message).to_string(), -//! ))) -//! .await; -//! } -//! } -//! None => { -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! } -//! } -//! -//! incoming_message = input.next() => { -//! if let Some(message) = incoming_message { -//! let result = websocket -//! .write_message(protocol::Message::Text(message.to_string())) -//! .await; -//! if result.is_err() { -//! output.send(Event::Disconnected).await; -//! break; -//! } -//! } -//! } -//! } -//! } -//! ``` -//! -//! ## Modules -//! -//! * `io` - WebSocket I/O operations -//! * `protocol` - WebSocket protocol handling -//! * `request` - WebSocket request building -//! * `response` - WebSocket response parsing -//! -pub mod io; -pub mod protocol; -pub mod request; -pub mod response; diff --git a/deboa-tokio/src/client/ws/protocol.rs b/deboa-tokio/src/client/ws/protocol.rs deleted file mode 100644 index 0f8131c7..00000000 --- a/deboa-tokio/src/client/ws/protocol.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Message enum -/// -/// # Variants -/// -/// * `Text(String)` - A text message. -/// * `Binary(Vec)` - A binary message. -/// * `Close(u16, String)` - A close message. -/// * `Ping(Vec)` - A ping message. -/// * `Pong(Vec)` - A pong message. -#[derive(Clone)] -pub enum Message { - Text(String), - Binary(Vec), - Close(u16, String), - Ping(Vec), - Pong(Vec), -} diff --git a/deboa-tokio/src/client/ws/request.rs b/deboa-tokio/src/client/ws/request.rs deleted file mode 100644 index 363e0f8a..00000000 --- a/deboa-tokio/src/client/ws/request.rs +++ /dev/null @@ -1,51 +0,0 @@ -use base64::engine::general_purpose::STANDARD; -use base64::Engine; -use deboa::{ - request::{DeboaRequest, DeboaRequestBuilder}, - url::IntoUrl, - Result, -}; -use http::{header, Method}; - -/// Trait for building websocket requests -pub trait WebsocketRequestBuilder { - /// Creates a websocket request - /// - /// # Arguments - /// - /// * `url` - The URL to connect to - /// - /// # Returns - /// - /// A Result containing the DeboaRequestBuilder - /// - /// # Example - /// - /// ``` compile_fail - /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; - /// use deboa_extras::http::ws::request::{WebsocketRequestBuilder}; - /// - /// let mut client = Client::new(); - /// let request = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); - /// let response = request.send_with(&mut client).await.unwrap(); - /// let ws = response.into_websocket().unwrap(); - /// loop { - /// if let Ok(Some(message)) = ws.read_message().await { - /// println!("message: {}", message); - /// } - /// } - /// ``` - fn websocket(url: T) -> Result; -} - -impl WebsocketRequestBuilder for DeboaRequestBuilder { - fn websocket(url: T) -> Result { - let rnd: [u8; 16] = rand::random(); - let key = STANDARD.encode(rnd); - Ok(DeboaRequest::at(url, Method::GET)? - .header(header::UPGRADE, "websocket") - .header(header::CONNECTION, "Upgrade") - .header(header::SEC_WEBSOCKET_KEY, &key) - .header(header::SEC_WEBSOCKET_VERSION, "13")) - } -} diff --git a/deboa-tokio/src/client/ws/response.rs b/deboa-tokio/src/client/ws/response.rs deleted file mode 100644 index 601b7bed..00000000 --- a/deboa-tokio/src/client/ws/response.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::future::Future; - -use crate::client::ws::io::socket::{DeboaWebSocket, UpgradedIo, WebSocket}; -use deboa::{response::DeboaResponse, Result}; - -/// Trait for converting a DeboaResponse into a WebSocket -pub trait IntoWebSocket { - /// Converts a DeboaResponse into a WebSocket - /// - /// # Arguments - /// - /// * `self` - The DeboaResponse to convert - /// - /// # Returns - /// - /// A Result containing the WebSocket - /// - /// # Example - /// - /// ``` compile_fail - /// use deboa::{Client, Result, request::{IntoUrl, DeboaRequestBuilder}}; - /// use deboa_extras::http::ws::request::{WebsocketRequestBuilder}; - /// - /// let mut client = Client::new(); - /// let builder = DeboaRequestBuilder::websocket("ws://example.com").unwrap(); - /// let response = builder - /// .send_with(&mut client) - /// .await - /// .unwrap(); - /// let websocket = response.into_websocket().unwrap(); - /// - /// loop { - /// if let Ok(Some(message)) = websocket.read_message().await { - /// println!("message: {}", message); - /// } - /// } - /// ``` - fn into_websocket(self) -> impl Future>>; -} - -impl IntoWebSocket for DeboaResponse { - async fn into_websocket(self) -> Result> { - let upgraded = self - .upgrade() - .await?; - Ok(WebSocket::new(upgraded)) - } -} From 83892d2df5d50593b06cca6ad72faf162b050985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Fri, 21 Aug 2026 20:33:39 -0300 Subject: [PATCH 10/13] chore: moved duplicate tls code to deboa-tls --- Cargo.lock | 38 +++++++ Cargo.toml | 9 +- deboa-compio/src/client/http/conn/mod.rs | 116 ++++++++++++++++----- deboa-h3/Cargo.toml | 1 + deboa-h3/src/lib.rs | 6 +- deboa-smol/Cargo.toml | 6 +- deboa-smol/src/client/http/conn/mod.rs | 124 +++++++++++++++-------- deboa-smol/src/client/mod.rs | 2 - deboa-smol/src/client/tls/rustls.rs | 75 +------------- deboa-tls/src/lib.rs | 2 - deboa-tls/src/native.rs | 0 deboa-tokio/Cargo.toml | 7 +- deboa-tokio/src/client/http/conn/mod.rs | 118 ++++++++++++++------- deboa-tokio/src/client/mod.rs | 2 - deboa-tokio/src/client/tls/rustls.rs | 77 +------------- 15 files changed, 313 insertions(+), 270 deletions(-) delete mode 100644 deboa-tls/src/native.rs diff --git a/Cargo.lock b/Cargo.lock index d981bd23..e181a26e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1145,6 +1145,7 @@ dependencies = [ "h3", "h3-quinn", "http", + "http-body-util", "hyper-body-utils", ] @@ -1176,6 +1177,8 @@ dependencies = [ "deboa", "deboa-h3", "deboa-test-utils", + "deboa-tls", + "deboa-ws", "easyhttpmock-vetis-smol", "futures", "futures-rustls", @@ -1195,6 +1198,7 @@ dependencies = [ "macro_rules_attribute", "minimime", "multer", + "pin-project-lite", "quinn", "rand", "regex", @@ -1246,6 +1250,19 @@ dependencies = [ "vetis", ] +[[package]] +name = "deboa-tls" +version = "0.1.0" +dependencies = [ + "deboa", + "futures-rustls", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "webpki-roots", +] + [[package]] name = "deboa-tokio" version = "0.1.3" @@ -1261,6 +1278,8 @@ dependencies = [ "deboa", "deboa-h3", "deboa-test-utils", + "deboa-tls", + "deboa-ws", "easyhttpmock-vetis-tokio", "futures", "futures-util", @@ -1294,6 +1313,24 @@ dependencies = [ "url", "urlencoding", "webpki-roots", +] + +[[package]] +name = "deboa-ws" +version = "0.1.0" +dependencies = [ + "base64", + "deboa", + "futures-util", + "http", + "hyper", + "hyper-util", + "pin-project-lite", + "rand", + "smol", + "smol-hyper", + "thiserror 2.0.20", + "tokio", "ws-framer", ] @@ -4990,6 +5027,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bacd6cff21323641597fe251e294f823e04292202a6e3c9721a1e8b82bea57bf" dependencies = [ + "getrandom 0.4.3", "httparse", "itoa", "ws-framer-macros", diff --git a/Cargo.toml b/Cargo.toml index e78df92f..3e467a5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,9 @@ members = [ "deboa-macros", "deboa-smol", "deboa-test-utils", + "deboa-tls", "deboa-tokio", + "deboa-ws", ] [workspace.package] @@ -30,7 +32,7 @@ base64 = { version = "0.23.0" } bytes = { version = "1.11" } caramelo = "0.1.2" cookie = { version = "0.18.1" } -deboa = { version = ">= 0.1.2" } +deboa = { path = "deboa" } deboa-compio = { path = "deboa-compio" } deboa-glommio = { path = "deboa-glommio" } deboa-h3 = { path = "deboa-h3", version = "^0.1.1" } @@ -39,7 +41,9 @@ deboa-fory = { version = ">= 0.1.6" } deboa-macros = { path = "deboa-macros" } deboa-smol = { path = "deboa-smol" } deboa-test-utils = { path = "deboa-test-utils" } +deboa-tls = { path = "deboa-tls" } deboa-tokio = { path = "deboa-tokio" } +deboa-ws = { path = "deboa-ws" } futures = { version = "0.3.31", default-features = false } futures-rustls = { version = "0.26.0", default-features = false } futures-util = { version = "0.3.31", default-features = false } @@ -58,6 +62,7 @@ hyper-util = { version = "0.1.20", features = [ log = "0.4.28" minimime = "1.0.0" multer = "3.1.0" +native-tls = { version = "0.2.18" } pin-project-lite = "0.2.17" rand = "0.10.2" regex = "1.9.6" @@ -75,7 +80,7 @@ urlencoding = "2.1.3" vamo = { version = ">= 0.0.9" } vamo-macros = { version = ">= 0.0.9" } webpki-roots = { version = "1.0.6" } -ws-framer = { version = "0.3.2", default-features = false } +ws-framer = { version = "0.3.2" } [profile.test] lto = false diff --git a/deboa-compio/src/client/http/conn/mod.rs b/deboa-compio/src/client/http/conn/mod.rs index ea858d2a..aeb6eddd 100644 --- a/deboa-compio/src/client/http/conn/mod.rs +++ b/deboa-compio/src/client/http/conn/mod.rs @@ -16,6 +16,10 @@ //! - Thread-safe connection handling //! ``` use crate::cert::{DeboaCertificate, DeboaIdentity}; +#[cfg(feature = "rust-tls")] +use compio::net::TcpStream; +#[cfg(any(feature = "http1", feature = "http2"))] +use cyper_core::HyperStream; #[cfg(feature = "http1")] use deboa::request::Http1Request; #[cfg(feature = "http2")] @@ -23,7 +27,7 @@ use deboa::request::Http2Request; use deboa::{ conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection}, dns::DnsResolver, - errors::{DeboaError, RequestError}, + errors::{ConnectionError, DeboaError, RequestError}, response::DeboaResponse, Result, }; @@ -31,6 +35,9 @@ use deboa::{ use deboa_h3::compio::Http3Request; use http::{Request, Version}; use hyper_body_utils::HttpBody; +use log::info; +#[cfg(feature = "rust-tls")] +use std::borrow::Cow; use std::{marker::PhantomData, time::Duration}; /// Connection pooling for efficient HTTP connections. @@ -180,6 +187,75 @@ impl BaseHttpConnection { } } +#[cfg(feature = "rust-tls")] +async fn connect_with_rustls<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, HyperStream)> { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = connect(tls_config, tcp_stream, config.host()).await?; + + if let Some(alpn) = stream.negotiated_alpn() { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(&alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, HyperStream::new_tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((*config.protocol_version(), HyperStream::new_tls(stream))) + } +} + +#[cfg(feature = "native-tls")] +async fn connect_with_nativels<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, HyperStream)> { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + + if let Some(alpn) = stream.negotiated_alpn() { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, HyperStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((Version::HTTP_11, HyperStream::Tls(stream))) + } +} + pub struct ConnectionFactory {} impl ConnectionFactory { @@ -219,10 +295,9 @@ impl ConnectionFactory { }; #[cfg(any(feature = "http1", feature = "http2"))] - let stream = { + let conn_pair = { use compio::net::TcpStream; use cyper_core::HyperStream; - use deboa::errors::ConnectionError; let tcp_stream = TcpStream::connect(format!("{}:{}", ip, config.port())) .await @@ -231,50 +306,35 @@ impl ConnectionFactory { })?; let use_tls = config.scheme() == "https" || config.scheme() == "wss"; if !use_tls { - HyperStream::new_plain(tcp_stream) + (Version::HTTP_11, HyperStream::new_plain(tcp_stream)) } else { #[cfg(feature = "rust-tls")] { - use crate::client::tls::rustls::tcp::connect; - use crate::client::tls::rustls::TlsConnectionBuilder; - let tls_config = TlsConnectionBuilder::default() - .certificate(config.certificate()) - .identity(config.identity()) - .build_config()?; - - HyperStream::new_tls(connect(tls_config, tcp_stream, config.host()).await?) + connect_with_rustls(tcp_stream, config).await? } #[cfg(feature = "native-tls")] { - use crate::client::tls::native::TlsConnectionBuilder; - let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) - .certificate(config.certificate()) - .identity(config.identity()) - .connect() - .await?; - HyperStream::new_tls(stream) + connect_with_nativels(tcp_stream, config).await? } } }; - let conn = match config.protocol_version() { + let conn = match conn_pair.0 { #[cfg(feature = "http1")] - &Version::HTTP_11 => { - let conn = Http1Connection::connect(stream).await?; + Version::HTTP_11 => { + let conn = Http1Connection::connect(conn_pair.1).await?; DeboaConnection::http1(conn) } #[cfg(feature = "http2")] - &Version::HTTP_2 => { - let conn = Http2Connection::connect(stream).await?; + Version::HTTP_2 => { + let conn = Http2Connection::connect(conn_pair.1).await?; DeboaConnection::http2(conn) } #[cfg(feature = "http3")] - &Version::HTTP_3 => { + Version::HTTP_3 => { let stream = { - use crate::client::tls::rustls::udp::connect; - #[cfg(feature = "rust-tls")] - use crate::client::tls::rustls::TlsConnectionBuilder; + use crate::client::tls::rustls::{udp::connect, TlsConnectionBuilder}; use compio_quic::Endpoint; use deboa::errors::ConnectionError; use std::net::SocketAddr; diff --git a/deboa-h3/Cargo.toml b/deboa-h3/Cargo.toml index 5077945a..932dcdbb 100644 --- a/deboa-h3/Cargo.toml +++ b/deboa-h3/Cargo.toml @@ -22,4 +22,5 @@ deboa = { workspace = true } h3 = { workspace = true, optional = true } h3-quinn = { workspace = true, optional = true } http = { workspace = true } +http-body-util = { workspace = true } hyper-body-utils = { workspace = true, optional = true, default-features = false } diff --git a/deboa-h3/src/lib.rs b/deboa-h3/src/lib.rs index b40520e5..08b41f59 100644 --- a/deboa-h3/src/lib.rs +++ b/deboa-h3/src/lib.rs @@ -5,7 +5,8 @@ pub mod generic { use h3::{client::RequestStream, error::StreamError}; use h3_quinn::{OpenStreams, RecvStream}; use http::{Request, Response}; - use hyper_body_utils::{BodyExt, HttpBody}; + use http_body_util::BodyExt as _; + use hyper_body_utils::HttpBody; use std::marker::PhantomData; pub type QuicRequest = h3::client::SendRequest; @@ -70,7 +71,8 @@ pub mod compio { use compio_quic::{h3::OpenStreams, RecvStream}; use h3::{client::RequestStream, error::StreamError}; use http::{Request, Response}; - use hyper_body_utils::{BodyExt, HttpBody}; + use http_body_util::BodyExt as _; + use hyper_body_utils::HttpBody; use std::marker::PhantomData; pub type QuicRequest = h3::client::SendRequest; diff --git a/deboa-smol/Cargo.toml b/deboa-smol/Cargo.toml index 8fa39443..5dfb2fb2 100644 --- a/deboa-smol/Cargo.toml +++ b/deboa-smol/Cargo.toml @@ -29,6 +29,7 @@ default = [ "rust-tls", "default-rustls-provider", "default-rustls-verifier", + "websockets" ] # tls @@ -71,7 +72,7 @@ http3 = [ "hyper-body-utils/generic-h3", ] -websockets = ["ws-framer/http", "ws-framer/alloc"] +websockets = ["deboa-ws"] [dependencies] async-executor = { workspace = true, optional = true } @@ -82,6 +83,8 @@ bytes = { workspace = true } cookie = { workspace = true } deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } +deboa-tls = { workspace = true } +deboa-ws = { workspace = true, optional = true } futures = { workspace = true, optional = true } futures-rustls = { version = "0.26.0", optional = true, default-features = false } futures-timeout = "0.2.1" @@ -99,6 +102,7 @@ indexmap = "2.11.4" log = { workspace = true } macro_rules_attribute = { version = "0.2.2", default-features = false } minimime = { workspace = true } +pin-project-lite = {workspace = true, optional = true } quinn = { version = "0.11.11", optional = true, default-features = false } rand = { workspace = true } regex = { workspace = true } diff --git a/deboa-smol/src/client/http/conn/mod.rs b/deboa-smol/src/client/http/conn/mod.rs index ad57c9c0..ce11687a 100644 --- a/deboa-smol/src/client/http/conn/mod.rs +++ b/deboa-smol/src/client/http/conn/mod.rs @@ -34,6 +34,9 @@ use deboa_h3::generic::Http3Request; use futures_timeout::TimeoutFutureExt; use http::{Request, Version}; use hyper_body_utils::HttpBody; +use log::info; +#[cfg(any(feature = "http1", feature = "http2"))] +use smol::net::TcpStream; use std::{borrow::Cow, marker::PhantomData, time::Duration}; /// Connection pooling for efficient HTTP connections. @@ -190,6 +193,79 @@ impl HttpConnectionDispatcher for DeboaConnection { } } +#[cfg(feature = "rust-tls")] +async fn connect_with_rustls<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, SmolStream)> { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); + + if let Some(alpn) = stream + .get_ref() + .1 + .alpn_protocol() + { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, SmolStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((*config.protocol_version(), SmolStream::Tls(stream))) + } +} + +#[cfg(feature = "native-tls")] +async fn connect_with_nativels<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, TokioStream)> { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + + if let Some(alpn) = stream.negotiated_alpn() { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, TokioStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((*config.protocol_version(), TokioStream::Tls(stream))) + } +} + /// Connection factory. pub struct ConnectionFactory {} @@ -242,52 +318,12 @@ impl ConnectionFactory { } else { #[cfg(feature = "rust-tls")] { - use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; - let tls_config = TlsConnectionBuilder::default() - .certificate(config.certificate()) - .identity(config.identity()) - .build_config()?; - - let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); - - let Some(alpn) = stream - .get_ref() - .1 - .alpn_protocol() - else { - return Err(DeboaError::Connection(ConnectionError::Tcp { - message: "No protocols available".to_string(), - })); - }; - - let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { - return Err(DeboaError::Connection(ConnectionError::Tcp { - message: "Invalid ALPN code".to_string(), - })); - }; - - let version = match alpn_code { - #[cfg(feature = "http1")] - "http1.1" => Version::HTTP_11, - #[cfg(feature = "http2")] - "h2" => Version::HTTP_2, - #[cfg(feature = "http1")] - "h3" => Version::HTTP_3, - _ => panic!("Unsupported protocol"), - }; - - (version, SmolStream::Tls(stream)) + connect_with_rustls(tcp_stream, config).await? } #[cfg(feature = "native-tls")] { - use crate::client::tls::native::TlsConnectionBuilder; - let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) - .certificate(config.certificate()) - .identity(config.identity()) - .connect() - .await?; - SmolStream::Tls(stream) + connect_with_nativels(tcp_stream, config).await? } } }; @@ -306,9 +342,7 @@ impl ConnectionFactory { #[cfg(feature = "http3")] Version::HTTP_3 => { let stream = { - use crate::client::tls::rustls::udp::connect; - #[cfg(feature = "rust-tls")] - use crate::client::tls::rustls::TlsConnectionBuilder; + use crate::client::tls::rustls::{udp::connect, TlsConnectionBuilder}; use deboa::errors::ConnectionError; use quinn::Endpoint; use std::net::SocketAddr; diff --git a/deboa-smol/src/client/mod.rs b/deboa-smol/src/client/mod.rs index 004a025a..988b235d 100644 --- a/deboa-smol/src/client/mod.rs +++ b/deboa-smol/src/client/mod.rs @@ -4,5 +4,3 @@ pub mod dns; pub mod http; pub mod tls; -#[cfg(feature = "websockets")] -pub mod ws; diff --git a/deboa-smol/src/client/tls/rustls.rs b/deboa-smol/src/client/tls/rustls.rs index b4b6e778..a82bea17 100644 --- a/deboa-smol/src/client/tls/rustls.rs +++ b/deboa-smol/src/client/tls/rustls.rs @@ -81,9 +81,9 @@ impl<'a> TlsConnectionBuilder<'a> { if self.skip_server_verification { ClientConfig::builder() .dangerous() - .with_custom_certificate_verifier(verify::SkipServerVerification::new( - self.provider, - )) + .with_custom_certificate_verifier( + deboa_tls::rust::verify::SkipServerVerification::new(self.provider), + ) .with_no_client_auth() } else { #[cfg(feature = "__webpki_rustls_verifier")] @@ -255,72 +255,3 @@ pub mod udp { Ok(quinn_conn) } } - -pub(crate) mod verify { - use rustls::{ - client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - crypto::CryptoProvider, - pki_types::{CertificateDer, ServerName, UnixTime}, - }; - use std::sync::Arc; - - #[derive(Debug)] - pub(crate) struct SkipServerVerification(CryptoProvider); - - impl SkipServerVerification { - pub(crate) fn new(provider: CryptoProvider) -> Arc { - Arc::new(Self(provider)) - } - } - - impl ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> std::result::Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0 - .signature_verification_algorithms - .supported_schemes() - } - } -} diff --git a/deboa-tls/src/lib.rs b/deboa-tls/src/lib.rs index 14dd904a..3fe4da73 100644 --- a/deboa-tls/src/lib.rs +++ b/deboa-tls/src/lib.rs @@ -1,4 +1,2 @@ -#[cfg(feature = "native-tls")] -pub mod native; #[cfg(feature = "rust-tls")] pub mod rust; diff --git a/deboa-tls/src/native.rs b/deboa-tls/src/native.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/deboa-tokio/Cargo.toml b/deboa-tokio/Cargo.toml index 89d72ff5..843ff908 100644 --- a/deboa-tokio/Cargo.toml +++ b/deboa-tokio/Cargo.toml @@ -29,6 +29,7 @@ default = [ "rust-tls", "default-rustls-provider", "default-rustls-verifier", + "websockets" ] # tls @@ -72,7 +73,7 @@ http3 = [ "hyper-body-utils/generic-h3", ] -websockets = ["ws-framer/http", "ws-framer/alloc"] +websockets = ["deboa-ws/tokio"] [dependencies] async-executor = { workspace = true, optional = true } @@ -83,6 +84,8 @@ bytes = { workspace = true } cookie = { workspace = true } deboa = { workspace = true } deboa-h3 = { workspace = true, optional = true } +deboa-tls = { workspace = true } +deboa-ws = { workspace = true, optional = true } futures = { workspace = true, optional = true } futures-util = { workspace = true, optional = true } h3 = { workspace = true, optional = true } @@ -119,7 +122,7 @@ tokio-util = { version = "0.7.11", features = ["io"], default-features = false } url = { workspace = true } urlencoding = { workspace = true } webpki-roots = { workspace = true, optional = true, default-features = false } -ws-framer = { workspace = true, optional = true, default-features = false } + [dev-dependencies] caramelo = { workspace = true } diff --git a/deboa-tokio/src/client/http/conn/mod.rs b/deboa-tokio/src/client/http/conn/mod.rs index 8c4e4629..d6a16ce7 100644 --- a/deboa-tokio/src/client/http/conn/mod.rs +++ b/deboa-tokio/src/client/http/conn/mod.rs @@ -33,6 +33,7 @@ use deboa::{ use deboa_h3::generic::Http3Request; use http::{Request, Version}; use hyper_body_utils::HttpBody; +use log::info; use std::{borrow::Cow, marker::PhantomData, time::Duration}; #[cfg(any(feature = "http1", feature = "http2"))] use tokio::net::TcpStream; @@ -190,6 +191,82 @@ impl BaseHttpConnection { } } +#[cfg(feature = "rust-tls")] +async fn connect_with_rustls<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, TokioStream)> { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); + + if let Some(alpn) = stream + .get_ref() + .1 + .alpn_protocol() + { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => panic!("Unsupported protocol"), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, TokioStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((Version::HTTP_11, TokioStream::Tls(stream))) + } +} + +#[cfg(feature = "native-tls")] +async fn connect_with_nativetls<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, TokioStream)> { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + + if let Some(alpn) = stream + .get_ref() + .alpn_protocol() + { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => panic!("Unsupported protocol"), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, TokioStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((Version::HTTP_11, TokioStream::Tls(stream))) + } +} + /// Factory for creating connections. pub(crate) struct ConnectionFactory {} @@ -244,49 +321,12 @@ impl ConnectionFactory { } else { #[cfg(feature = "rust-tls")] { - use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; - let tls_config = TlsConnectionBuilder::default() - .certificate(config.certificate()) - .identity(config.identity()) - .build_config()?; - - let stream = Box::new(connect(tls_config, tcp_stream, config.host()).await?); - - let Some(alpn) = stream - .get_ref() - .1 - .alpn_protocol() - else { - return Err(DeboaError::Connection(ConnectionError::Tcp { - message: "No protocols available".to_string(), - })); - }; - - let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { - return Err(DeboaError::Connection(ConnectionError::Tcp { - message: "Invalid ALPN code".to_string(), - })); - }; - - let version = match alpn_code { - "http1.1" => Version::HTTP_11, - "h2" => Version::HTTP_2, - "h3" => Version::HTTP_3, - _ => panic!("Unsupported protocol"), - }; - - (version, TokioStream::Tls(stream)) + connect_with_rustls(tcp_stream, config).await? } #[cfg(feature = "native-tls")] { - use crate::client::tls::native::TlsConnectionBuilder; - let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) - .certificate(config.certificate()) - .identity(config.identity()) - .connect() - .await?; - TokioStream::Tls(stream) + connect_with_nativels(tcp_stream, config).await? } } }; diff --git a/deboa-tokio/src/client/mod.rs b/deboa-tokio/src/client/mod.rs index 5d291c2c..992b077a 100644 --- a/deboa-tokio/src/client/mod.rs +++ b/deboa-tokio/src/client/mod.rs @@ -5,5 +5,3 @@ pub mod dns; pub mod http; pub mod tls; -#[cfg(feature = "websockets")] -pub mod ws; diff --git a/deboa-tokio/src/client/tls/rustls.rs b/deboa-tokio/src/client/tls/rustls.rs index 569d4bdf..f1e8f659 100644 --- a/deboa-tokio/src/client/tls/rustls.rs +++ b/deboa-tokio/src/client/tls/rustls.rs @@ -82,9 +82,9 @@ impl<'a> TlsConnectionBuilder<'a> { if self.skip_server_verification { ClientConfig::builder() .dangerous() - .with_custom_certificate_verifier(verify::SkipServerVerification::new( - self.provider, - )) + .with_custom_certificate_verifier( + deboa_tls::rust::verify::SkipServerVerification::new(self.provider), + ) .with_no_client_auth() } else { #[cfg(feature = "__webpki_rustls_verifier")] @@ -211,7 +211,7 @@ pub mod tcp { /// UDP connection module for TLS pub mod udp { use deboa::{ - errors::{ConnectionError, DeboaError}, + errors::{http::ConnectionError, DeboaError}, Result, }; use h3_quinn::Connection; @@ -256,72 +256,3 @@ pub mod udp { Ok(quinn_conn) } } - -pub(crate) mod verify { - use rustls::{ - client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, - crypto::CryptoProvider, - pki_types::{CertificateDer, ServerName, UnixTime}, - }; - use std::sync::Arc; - - #[derive(Debug)] - pub(crate) struct SkipServerVerification(CryptoProvider); - - impl SkipServerVerification { - pub(crate) fn new(provider: CryptoProvider) -> Arc { - Arc::new(Self(provider)) - } - } - - impl ServerCertVerifier for SkipServerVerification { - fn verify_server_cert( - &self, - _end_entity: &CertificateDer<'_>, - _intermediates: &[CertificateDer<'_>], - _server_name: &ServerName<'_>, - _ocsp: &[u8], - _now: UnixTime, - ) -> std::result::Result { - Ok(ServerCertVerified::assertion()) - } - - fn verify_tls12_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls12_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn verify_tls13_signature( - &self, - message: &[u8], - cert: &CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> std::result::Result { - rustls::crypto::verify_tls13_signature( - message, - cert, - dss, - &self - .0 - .signature_verification_algorithms, - ) - } - - fn supported_verify_schemes(&self) -> Vec { - self.0 - .signature_verification_algorithms - .supported_schemes() - } - } -} From 4a1bb68ce8894138444e3c6893f5c03c51fc5be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Sat, 22 Aug 2026 10:31:00 -0300 Subject: [PATCH 11/13] fix(glommio): updated code with latest TLS api changes --- deboa-glommio/Cargo.toml | 20 +- deboa-glommio/src/cert.rs | 2 +- deboa-glommio/src/client/http/conn/mod.rs | 230 +++++++++++-- deboa-glommio/src/client/http/conn/pool.rs | 41 ++- .../src/client/http/conn/stream/tls/rustls.rs | 4 +- deboa-glommio/src/client/http/http1.rs | 46 +-- deboa-glommio/src/client/http/http2.rs | 49 +-- deboa-glommio/src/client/mod.rs | 1 + deboa-glommio/src/client/tls/mod.rs | 9 + deboa-glommio/src/client/tls/native.rs | 126 +++++++ deboa-glommio/src/client/tls/rustls.rs | 324 ++++++++++++++++++ deboa-glommio/src/rt/stream.rs | 2 +- 12 files changed, 733 insertions(+), 121 deletions(-) create mode 100644 deboa-glommio/src/client/tls/mod.rs create mode 100644 deboa-glommio/src/client/tls/native.rs create mode 100644 deboa-glommio/src/client/tls/rustls.rs diff --git a/deboa-glommio/Cargo.toml b/deboa-glommio/Cargo.toml index 99ad4ba4..63cc54b5 100644 --- a/deboa-glommio/Cargo.toml +++ b/deboa-glommio/Cargo.toml @@ -12,9 +12,20 @@ publish = false rust-version.workspace = true [features] -default = ["http1", "http2", "rust-tls", "default-rustls-provider", "default-rustls-verifier"] +default = [ + "http1", + "http2", + "rust-tls", + "default-rustls-provider", + "default-rustls-verifier", +] -rust-tls = ["dep:rustls", "dep:rustls-native-certs", "dep:futures-rustls", "dep:rustls-pki-types"] +rust-tls = [ + "dep:rustls", + "dep:rustls-native-certs", + "dep:futures-rustls", + "dep:rustls-pki-types", +] # Declared so the cfg is known and the error below is a clear message rather # than a wall of type errors. `async-native-tls` has no glommio binding — it @@ -67,7 +78,10 @@ http-body = "1" http-body-util = "0.1" hyper = { version = "1.10.1", features = ["client"], default-features = false } hyper-body-utils = { workspace = true, default-features = false } -hyper-util = { version = "0.1.20", features = ["client", "client-legacy"], default-features = false } +hyper-util = { version = "0.1.20", features = [ + "client", + "client-legacy", +], default-features = false } indexmap = "2.11.4" log = "0.4.32" macro_rules_attribute = { version = "0.2.2", default-features = false } diff --git a/deboa-glommio/src/cert.rs b/deboa-glommio/src/cert.rs index 4761e91b..13ce03c3 100644 --- a/deboa-glommio/src/cert.rs +++ b/deboa-glommio/src/cert.rs @@ -93,7 +93,7 @@ impl deboa::cert::Identity for DeboaIdentity { &self.cert } - fn ket(&self) -> &Option> { + fn key(&self) -> &Option> { &self.key } diff --git a/deboa-glommio/src/client/http/conn/mod.rs b/deboa-glommio/src/client/http/conn/mod.rs index b64798ae..7840e2ea 100644 --- a/deboa-glommio/src/client/http/conn/mod.rs +++ b/deboa-glommio/src/client/http/conn/mod.rs @@ -16,21 +16,30 @@ //! - Thread-safe connection handling //! ``` use crate::cert::{DeboaCertificate, DeboaIdentity}; +#[cfg(feature = "rust-tls")] +use crate::rt::stream::GlommioStream; #[cfg(feature = "http1")] use deboa::request::Http1Request; #[cfg(feature = "http2")] use deboa::request::Http2Request; use deboa::{ conn::{ConnectionConfig, HttpConnectionDispatcher, ProtoConnection}, - errors::{DeboaError, RequestError}, + dns::DnsResolver, + errors::{ConnectionError, DeboaError, RequestError}, response::DeboaResponse, Result, }; #[cfg(feature = "http3")] use deboa_h3::generic::Http3Request; +#[cfg(feature = "rust-tls")] +use glommio::net::TcpStream; use http::{Request, Version}; use hyper_body_utils::HttpBody; -use std::marker::PhantomData; +#[cfg(feature = "rust-tls")] +use log::info; +#[cfg(feature = "rust-tls")] +use std::borrow::Cow; +use std::{marker::PhantomData, time::Duration}; /// Connection pooling for efficient HTTP connections. /// @@ -111,20 +120,8 @@ impl DeboaConnection { pub fn http3(conn: Http3Connection) -> Self { DeboaConnection::Http3(Box::new(conn)) } -} -impl HttpConnectionDispatcher for DeboaConnection { - /// Send a request through the connection. - /// - /// # Arguments - /// - /// * `url` - The URL to send the request to. - /// * `request` - The request to send. - /// - /// # Returns - /// - /// * `Result` - The response from the server. - async fn send_request(&mut self, request: Request) -> Result { + async fn send(&mut self, request: Request) -> Result { match self { #[cfg(feature = "http1")] DeboaConnection::Http1(ref mut conn) => { @@ -178,29 +175,210 @@ impl HttpConnectionDispatcher for DeboaConnection { } } +impl HttpConnectionDispatcher for DeboaConnection { + /// Send a request through the connection. + /// + /// # Arguments + /// + /// * `request` - The request to send. + /// + /// # Returns + /// + /// * `Result` - The response from the server. + async fn send_request( + &mut self, + request: Request, + timeout: Duration, + ) -> Result { + glommio::future::timeout(timeout, self.send(request)) + .await + .map_err(|_| { + DeboaError::Request(RequestError::Send { message: "Request timed out".to_string() }) + })? + } +} + +#[cfg(feature = "rust-tls")] +async fn connect_with_rustls<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, GlommioStream)> { + use crate::client::tls::rustls::{tcp::connect, TlsConnectionBuilder}; + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + let stream = connect(tls_config, tcp_stream, config.host()).await?; + + if let Some(alpn) = stream + .get_ref() + .1 + .alpn_protocol() + { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(&alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, GlommioStream::Tls(Box::new(stream)))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((*config.protocol_version(), GlommioStream::Tls(Box::new(stream)))) + } +} + +#[cfg(feature = "native-tls")] +async fn connect_with_nativels<'a>( + tcp_stream: TcpStream, + config: &ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, +) -> Result<(Version, HyperStream)> { + use crate::client::tls::native::TlsConnectionBuilder; + let stream = TlsConnectionBuilder::new(tcp_stream, config.host()) + .certificate(config.certificate()) + .identity(config.identity()) + .connect() + .await?; + + if let Some(alpn) = stream.negotiated_alpn() { + let Cow::Borrowed(alpn_code) = String::from_utf8_lossy(alpn) else { + return Err(DeboaError::Connection(ConnectionError::Tcp { + message: "Invalid ALPN code".to_string(), + })); + }; + + let version = match alpn_code { + "http1.1" => Version::HTTP_11, + "h2" => Version::HTTP_2, + "h3" => Version::HTTP_3, + _ => *config.protocol_version(), + }; + + info!("ALPN info found, switching connection to {:?}", version); + Ok((version, HyperStream::Tls(stream))) + } else { + info!("No ALPN info available, falling back to HTTP/1.1"); + Ok((Version::HTTP_11, HyperStream::Tls(stream))) + } +} + /// Connection factory. pub struct ConnectionFactory {} impl ConnectionFactory { /// Create a new connection. - pub async fn create_connection<'a>( - protocol: &Version, + pub async fn create_connection<'a, D>( config: &'a ConnectionConfig<'a, DeboaIdentity, DeboaCertificate>, - ) -> Result { - let conn = match protocol { + dns_resolver: &D, + ) -> Result + where + D: DnsResolver, + { + let ips = dns_resolver + .resolve( + config + .host() + .to_string(), + config.port(), + ) + .await?; + let ips = if config + .client_bind_addr() + .is_ipv4() + { + ips.into_iter() + .filter(|ip| ip.is_ipv4()) + .collect::>() + } else { + ips.into_iter() + .filter(|ip| ip.is_ipv6()) + .collect::>() + }; + + let Some(ip) = ips.first() else { + return Err(DeboaError::Request(RequestError::Send { + message: format!("No IP addresses found for hostname: {}", config.host()), + })); + }; + + #[cfg(any(feature = "http1", feature = "http2"))] + let conn_pair = { + use crate::rt::stream::GlommioStream; + use glommio::net::TcpStream; + + let tcp_stream = TcpStream::connect(format!("{}:{}", ip, config.port())) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tcp { message: e.to_string() }) + })?; + let use_tls = config.scheme() == "https" || config.scheme() == "wss"; + if !use_tls { + (Version::HTTP_11, GlommioStream::Plain(tcp_stream)) + } else { + #[cfg(feature = "rust-tls")] + { + connect_with_rustls(tcp_stream, config).await? + } + + #[cfg(feature = "native-tls")] + { + connect_with_nativels(tcp_stream, config).await? + } + } + }; + + let conn = match conn_pair.0 { #[cfg(feature = "http1")] - &Version::HTTP_11 => { - let conn = Http1Connection::connect(config).await?; + Version::HTTP_11 => { + let conn = Http1Connection::connect(conn_pair.1).await?; DeboaConnection::http1(conn) } #[cfg(feature = "http2")] - &Version::HTTP_2 => { - let conn = Http2Connection::connect(config).await?; + Version::HTTP_2 => { + let conn = Http2Connection::connect(conn_pair.1).await?; DeboaConnection::http2(conn) } - #[cfg(all(feature = "http3", feature = "rust-tls"))] - &Version::HTTP_3 => { - let conn = Http3Connection::connect(&config).await?; + #[cfg(feature = "http3")] + Version::HTTP_3 => { + let stream = { + use crate::client::tls::rustls::{udp::connect, TlsConnectionBuilder}; + use compio_quic::Endpoint; + use deboa::errors::ConnectionError; + use std::net::SocketAddr; + + let mut client_endpoint = + Endpoint::client(SocketAddr::new(*config.client_bind_addr(), 0)) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: e.to_string(), + }) + })?; + + let tls_config = TlsConnectionBuilder::default() + .certificate(config.certificate()) + .identity(config.identity()) + .build_config()?; + + connect( + tls_config, + &mut client_endpoint, + SocketAddr::new(*ip, config.port()), + config.host(), + ) + .await? + }; + + let conn = Http3Connection::connect(stream).await?; DeboaConnection::http3(conn) } _ => { diff --git a/deboa-glommio/src/client/http/conn/pool.rs b/deboa-glommio/src/client/http/conn/pool.rs index 2765899c..4085d101 100644 --- a/deboa-glommio/src/client/http/conn/pool.rs +++ b/deboa-glommio/src/client/http/conn/pool.rs @@ -2,9 +2,13 @@ use crate::{ cert::{DeboaCertificate, DeboaIdentity}, client::http::conn::{ConnectionConfig, ConnectionFactory, DeboaConnection}, }; -use deboa::Result; +use deboa::{ + dns::DnsResolver, + errors::{ConnectionError, DeboaError}, + Result, +}; use hashbrown::HashMap; -use time::Duration; +use std::time::Duration; /// Struct that represents the HTTP connection pool. /// @@ -27,7 +31,7 @@ impl Default for HttpConnectionPool { fn default() -> Self { Self { max_idle_connections: 5, - keep_alive_duration: Duration::minutes(5), + keep_alive_duration: Duration::from_mins(5), connections: HashMap::new(), } } @@ -76,15 +80,14 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { .len() as u32 } - async fn create_connection<'a>( - &'a mut self, + async fn create_connection<'a, D>( + &mut self, config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result<&'a mut Self::ConnectionDispather> { - if self.max_idle_connections == 0 { - self.connections - .clear(); - } - + dns_resolver: &D, + ) -> Result<&mut DeboaConnection> + where + D: DnsResolver, + { let host = config.host(); if self .connections @@ -98,8 +101,20 @@ impl deboa::conn::HttpConnectionPool for HttpConnectionPool { } log::debug!("Creating new connection for {}", host); - let connection = - ConnectionFactory::create_connection(config.protocol_version(), config).await?; + let connection = glommio::future::timeout( + config.connection_timeout(), + ConnectionFactory::create_connection(config, dns_resolver), + ) + .await + .map_err(|_| { + DeboaError::Connection(ConnectionError::Timeout { + message: format!( + "Connection to {} timed out after {:?}", + host, + config.connection_timeout() + ), + }) + })??; self.connections .insert(host.to_string(), connection); diff --git a/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs b/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs index ecd1fe6a..62f5d4d3 100644 --- a/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs +++ b/deboa-glommio/src/client/http/conn/stream/tls/rustls.rs @@ -27,9 +27,7 @@ pub(crate) async fn tls_connection<'a>( let hostname = ServerName::try_from(host.to_string()); if let Err(e) = hostname { - return Err(DeboaError::Connection(ConnectionError::Tls { - message: e.to_string(), - })); + return Err(DeboaError::Connection(ConnectionError::Tls { message: e.to_string() })); } let stream = connector diff --git a/deboa-glommio/src/client/http/http1.rs b/deboa-glommio/src/client/http/http1.rs index 8e064d81..ee2924a5 100644 --- a/deboa-glommio/src/client/http/http1.rs +++ b/deboa-glommio/src/client/http/http1.rs @@ -1,19 +1,15 @@ -#[cfg(feature = "rust-tls")] -use crate::alpn; -#[cfg(feature = "rust-tls")] -use crate::client::http::conn::stream::tls_connection; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http1Connection}, + client::http::conn::{BaseHttpConnection, Http1Connection}, + rt::stream::GlommioStream, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http1Request, Result, }; use http::version::Version; use hyper::client::conn::http1::handshake; -use hyper_body_utils::HttpBody; use smol_hyper::rt::FuturesIo; impl HttpConnection for Http1Connection { @@ -25,41 +21,19 @@ impl HttpConnection for Http1Connection { impl ProtoConnection for Http1Connection { type Connection = Http1Connection; + type RuntimeStream = GlommioStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_11 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(feature = "rust-tls")] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: Self::RuntimeStream) -> Result { + let (sender, conn) = handshake(FuturesIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(feature = "rust-tls"))] - let stream = plain_connection(*config.ip(), config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(FuturesIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; glommio::spawn_local(async move { match conn.await { diff --git a/deboa-glommio/src/client/http/http2.rs b/deboa-glommio/src/client/http/http2.rs index 2b053fe2..4828a075 100644 --- a/deboa-glommio/src/client/http/http2.rs +++ b/deboa-glommio/src/client/http/http2.rs @@ -1,20 +1,15 @@ -#[cfg(feature = "rust-tls")] -use crate::alpn; -#[cfg(feature = "rust-tls")] -use crate::client::http::conn::stream::tls_connection; use crate::{ - cert::{DeboaCertificate, DeboaIdentity}, - client::http::conn::{stream::plain_connection, BaseHttpConnection, Http2Connection}, - rt::executor::GlommioExecutor, + client::http::conn::{BaseHttpConnection, Http2Connection}, + rt::{executor::GlommioExecutor, stream::GlommioStream}, }; use deboa::{ - conn::{ConnectionConfig, HttpConnection, ProtoConnection}, + conn::{HttpConnection, ProtoConnection}, + errors::{ConnectionError, DeboaError}, request::Http2Request, Result, }; use http::version::Version; use hyper::client::conn::http2::handshake; -use hyper_body_utils::HttpBody; use smol_hyper::rt::FuturesIo; impl HttpConnection for Http2Connection { @@ -26,47 +21,25 @@ impl HttpConnection for Http2Connection { impl ProtoConnection for Http2Connection { type Connection = Http2Connection; + type RuntimeStream = GlommioStream; #[inline] fn protocol_version(&self) -> Version { Version::HTTP_2 } - async fn connect<'a>( - config: &ConnectionConfig<'a, Self::Identity, Self::Certificate>, - ) -> Result { - #[cfg(feature = "rust-tls")] - let stream = if config.is_secure() { - tls_connection( - *config.ip(), - config.host(), - config.port(), - config.identity(), - config.certificate(), - config.skip_cert_verification(), - alpn(), - ) + async fn connect(stream: GlommioStream) -> Result { + let (sender, conn) = handshake(GlommioExecutor::new(), FuturesIo::new(stream)) .await - } else { - plain_connection(*config.ip(), config.host(), config.port()).await - }; - - #[cfg(not(feature = "rust-tls"))] - let stream = plain_connection(*config.ip(), config.host(), config.port()).await; - - if let Err(e) = stream { - return Err(e); - } - - let result = handshake(GlommioExecutor::new(), FuturesIo::new(stream.unwrap())).await; - - let (sender, conn) = result.unwrap(); + .map_err(|e| { + DeboaError::Connection(ConnectionError::Handshake { message: e.to_string() }) + })?; glommio::spawn_local(async move { match conn.await { Ok(_) => (), Err(err) => { - log::debug!("http2 connection ended: {err:#}"); + log::error!("Error: {:#}", err) } }; }) diff --git a/deboa-glommio/src/client/mod.rs b/deboa-glommio/src/client/mod.rs index 0b7c1357..988b235d 100644 --- a/deboa-glommio/src/client/mod.rs +++ b/deboa-glommio/src/client/mod.rs @@ -3,3 +3,4 @@ /// This module provides DNS resolution functionality for the Deboa HTTP client.pub(crate) mod dns; pub mod dns; pub mod http; +pub mod tls; diff --git a/deboa-glommio/src/client/tls/mod.rs b/deboa-glommio/src/client/tls/mod.rs new file mode 100644 index 00000000..afc96df1 --- /dev/null +++ b/deboa-glommio/src/client/tls/mod.rs @@ -0,0 +1,9 @@ +//! TLS transport implementations for the Deboa HTTP client. +//! +//! This module provides TLS functionality for secure HTTP connections. +//! It supports both native-tls and rustls backends. + +#[cfg(feature = "native-tls")] +pub mod native; +#[cfg(feature = "rust-tls")] +pub mod rustls; diff --git a/deboa-glommio/src/client/tls/native.rs b/deboa-glommio/src/client/tls/native.rs new file mode 100644 index 00000000..b3d061b3 --- /dev/null +++ b/deboa-glommio/src/client/tls/native.rs @@ -0,0 +1,126 @@ +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use compio_tls::{ + native_tls::TlsConnector, + native_tls::{Certificate, Identity}, + TlsStream, +}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use glommio::net::TcpStream; + +#[inline] +pub(crate) fn alpn() -> &'static [&'static str] { + &[ + #[cfg(feature = "http3")] + "h3", + #[cfg(feature = "http2")] + "h2", + #[cfg(feature = "http1")] + "http/1.1", + ] +} + +pub struct TlsConnectionBuilder<'a> { + tcp_stream: TcpStream, + host: &'a str, + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: &'a [&'a str], +} + +impl<'a> TlsConnectionBuilder<'a> { + pub fn new(tcp_stream: TcpStream, host: &'a str) -> Self { + Self { + tcp_stream, + host, + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + } + } + + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + pub fn alpn(mut self, alpn: &'a [&str]) -> Self { + self.alpn = alpn; + self + } + + pub async fn connect(self) -> Result> { + let mut builder = TlsConnector::builder(); + + let builder = if self.skip_server_verification { + builder + .danger_accept_invalid_certs(true) + .danger_accept_invalid_hostnames(true) + } else { + &mut builder + }; + + let builder = builder.request_alpns(self.alpn); + + let builder = if let Some(ca) = self.certificate { + let cert: Certificate = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + builder.add_root_certificate(cert) + } else { + builder + }; + + let builder = if let Some(identity) = self.identity { + let ident: Identity = identity + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + builder.identity(ident) + } else { + builder + }; + + let connector = builder + .build() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not build TLS connector: {}", e), + }) + })?; + + let connector = compio_tls::TlsConnector::from(connector); + + let stream = connector + .connect(self.host, self.tcp_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + })?; + + Ok(stream) + } +} diff --git a/deboa-glommio/src/client/tls/rustls.rs b/deboa-glommio/src/client/tls/rustls.rs new file mode 100644 index 00000000..cb9f273a --- /dev/null +++ b/deboa-glommio/src/client/tls/rustls.rs @@ -0,0 +1,324 @@ +//! TLS implementation using rustls + +use crate::cert::{DeboaCertificate, DeboaIdentity}; +use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, +}; +use rustls::{ + crypto::CryptoProvider, + pki_types::{CertificateDer, PrivateKeyDer}, + ClientConfig, +}; + +pub(crate) fn default_provider() -> CryptoProvider { + #[cfg(feature = "__rustls_aws_lc_rs")] + return rustls::crypto::aws_lc_rs::default_provider(); + #[cfg(feature = "__rustls_ring")] + return rustls::crypto::ring::default_provider(); +} + +#[inline] +pub(crate) fn alpn() -> Vec> { + vec![ + #[cfg(feature = "http3")] + b"h3".to_vec(), + #[cfg(feature = "http2")] + b"h2".to_vec(), + #[cfg(feature = "http1")] + b"http/1.1".to_vec(), + ] +} + +/// Builder for TLS connections using rustls +pub struct TlsConnectionBuilder<'a> { + identity: Option<&'a DeboaIdentity>, + certificate: Option<&'a DeboaCertificate>, + skip_server_verification: bool, + alpn: Vec>, + provider: CryptoProvider, +} + +impl Default for TlsConnectionBuilder<'_> { + fn default() -> Self { + Self { + identity: None, + certificate: None, + skip_server_verification: false, + alpn: alpn(), + provider: default_provider(), + } + } +} + +impl<'a> TlsConnectionBuilder<'a> { + /// Set the identity to use for the connection + pub fn identity(mut self, identity: Option<&'a DeboaIdentity>) -> Self { + self.identity = identity; + self + } + + /// Set the certificate to use for the connection + pub fn certificate(mut self, certificate: Option<&'a DeboaCertificate>) -> Self { + self.certificate = certificate; + self + } + + /// Skip server verification + pub fn skip_server_verification(mut self, skip_server_verification: bool) -> Self { + self.skip_server_verification = skip_server_verification; + self + } + + /// Set the ALPN protocols to use for the connection + pub fn alpn(mut self, alpn: Vec>) -> Self { + self.alpn = alpn; + self + } + + /// Build the TLS client configuration + pub fn build_config(self) -> Result { + let client_config = { + if self.skip_server_verification { + ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(verify::SkipServerVerification::new( + self.provider, + )) + .with_no_client_auth() + } else { + #[cfg(feature = "__webpki_rustls_verifier")] + let config = { + let config = ClientConfig::builder_with_provider(self.provider.into()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })?; + + let mut root_store = + rustls::RootCertStore { roots: webpki_roots::TLS_SERVER_ROOTS.to_vec() }; + let config = if let Some(ca) = self.certificate { + let cert = ca + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid CA certificate: {}", e), + }) + })?; + + root_store + .add(cert) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!( + "Could not add CA certificate to the store: {}", + e + ), + }) + })?; + + config.with_root_certificates(root_store) + } else { + config.with_root_certificates(root_store) + }; + + config + }; + + #[cfg(feature = "__platform_rustls_verifier")] + let config = { + use rustls_platform_verifier::BuilderVerifierExt; + rustls::ClientConfig::builder_with_provider(default_provider()) + .with_protocol_versions(rustls::ALL_VERSIONS) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set TLS version: {}", e), + }) + })? + .with_platform_verifier() + }; + + let mut config = if let Some(id) = self.identity { + let pair: (CertificateDer<'_>, PrivateKeyDer<'_>) = id + .try_into() + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Invalid client identity: {}", e), + }) + })?; + + config + .with_client_auth_cert(vec![pair.0], pair.1) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Failed to set client identity: {}", e), + }) + })? + } else { + config.with_no_client_auth() + }; + + config.enable_early_data = true; + + config.alpn_protocols = self.alpn; + + config + } + }; + + Ok(client_config) + } +} + +#[cfg(any(feature = "http1", feature = "http2"))] +/// TCP connection module for TLS +pub mod tcp { + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use futures_rustls::{client::TlsStream, TlsConnector}; + use glommio::net::TcpStream; + use rustls::ClientConfig; + use rustls_pki_types::ServerName; + use std::sync::Arc; + + /// Establish a TLS connection over TCP + pub async fn connect( + config: ClientConfig, + inner_stream: TcpStream, + host: &str, + ) -> Result> { + let connector = TlsConnector::from(Arc::new(config)); + + let host = ServerName::try_from(host.to_string()) + .map_err(|e| DeboaError::Connection(ConnectionError::Tls { message: e.to_string() }))?; + + connector + .connect(host, inner_stream) + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not connect to server: {}", e), + }) + }) + } +} + +#[cfg(feature = "http3")] +/// UDP connection module for TLS +pub mod udp { + use compio_quic::{Connection, Endpoint}; + use deboa::{ + errors::{ConnectionError, DeboaError}, + Result, + }; + use rustls::ClientConfig; + use std::{net::SocketAddr, sync::Arc}; + + /// Establish a TLS connection over UDP + pub async fn connect( + config: ClientConfig, + endpoint: &mut Endpoint, + socket_addr: SocketAddr, + host: &str, + ) -> Result { + let quic_config = + compio_quic::crypto::rustls::QuicClientConfig::try_from(config).map_err(|e| { + DeboaError::Connection(ConnectionError::Tls { + message: format!("Could not create QUIC client config: {}", e), + }) + })?; + + let client_config = compio_quic::ClientConfig::new(Arc::new(quic_config)); + + let conn = endpoint + .connect(socket_addr, host, Some(client_config)) + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + let conn = conn + .await + .map_err(|e| { + DeboaError::Connection(ConnectionError::Udp { + message: format!("Could not connect to server: {}", e), + }) + })?; + + Ok(conn) + } +} + +pub(crate) mod verify { + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::CryptoProvider, + pki_types::{CertificateDer, ServerName, UnixTime}, + }; + use std::sync::Arc; + + #[derive(Debug)] + pub(crate) struct SkipServerVerification(CryptoProvider); + + impl SkipServerVerification { + pub(crate) fn new(provider: CryptoProvider) -> Arc { + Arc::new(Self(provider)) + } + } + + impl ServerCertVerifier for SkipServerVerification { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &self + .0 + .signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0 + .signature_verification_algorithms + .supported_schemes() + } + } +} diff --git a/deboa-glommio/src/rt/stream.rs b/deboa-glommio/src/rt/stream.rs index 045c0d11..05466eef 100644 --- a/deboa-glommio/src/rt/stream.rs +++ b/deboa-glommio/src/rt/stream.rs @@ -10,7 +10,7 @@ use std::{ }; /// A stream that can be either plain TCP or TLS-secured. -pub(crate) enum GlommioStream { +pub enum GlommioStream { /// A plain TCP connection. Plain(TcpStream), From b7682c79e04d2d03b2d6d4cb31ff434d6a1339d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Sat, 22 Aug 2026 10:38:26 -0300 Subject: [PATCH 12/13] test: added new way to define mock server port --- deboa-compio/tests/common/helpers.rs | 25 ++++++++++++++++++++++++- deboa-smol/tests/common/helpers.rs | 25 ++++++++++++++++++++++++- deboa-tokio/tests/common/helpers.rs | 25 ++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/deboa-compio/tests/common/helpers.rs b/deboa-compio/tests/common/helpers.rs index 012860e8..9115dda2 100644 --- a/deboa-compio/tests/common/helpers.rs +++ b/deboa-compio/tests/common/helpers.rs @@ -78,7 +78,7 @@ pub async fn tls_mock_server() -> EasyHttpMock { .unwrap(), ) .protos(vec![protocol_version()]) - .with_random_port() + .port(free_port(&interface)) .cert(server_cert.to_vec()) .key(server_key.to_vec()) .ca(CA_CERT.to_vec()) @@ -137,3 +137,26 @@ pub async fn create_server() -> EasyHttpMock { #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] return plain_mock_server().await; } + +/// An ephemeral port the OS says is free, rather than a guessed one. +/// +/// `with_random_port()` picks `rand::random_range(9000..65535)` and +/// de-duplicates against a process-local set. Under `cargo test` that mostly +/// holds, because every test shares one process and therefore one set. Under +/// `cargo nextest run` — which is what CI uses — each test is its own process, +/// so the set protects nothing and two tests eventually roll the same number. +/// The loser dies with `Address already in use (os error 98)` before its body +/// runs, which is why the failing test is a different one each time. +/// +/// Binding port 0 asks the kernel for a port it knows is free. There is still a +/// window between dropping this listener and the server binding, but the kernel +/// will not hand the same ephemeral port to someone else inside it, which is +/// the part guessing cannot promise. +fn free_port(interface: &str) -> u16 { + let listener = std::net::TcpListener::bind((interface, 0)) + .expect("bind an ephemeral port to ask the OS for a free one"); + listener + .local_addr() + .expect("read back the bound port") + .port() +} diff --git a/deboa-smol/tests/common/helpers.rs b/deboa-smol/tests/common/helpers.rs index c256c30f..b72281e1 100644 --- a/deboa-smol/tests/common/helpers.rs +++ b/deboa-smol/tests/common/helpers.rs @@ -80,7 +80,7 @@ pub async fn tls_mock_server() -> EasyHttpMock { .unwrap(), ) .protos(vec![protocol_version()]) - .with_random_port() + .port(free_port(&interface)) .cert(server_cert.to_vec()) .key(server_key.to_vec()) .ca(CA_CERT.to_vec()) @@ -139,3 +139,26 @@ pub async fn create_server() -> EasyHttpMock { #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] return plain_mock_server().await; } + +/// An ephemeral port the OS says is free, rather than a guessed one. +/// +/// `with_random_port()` picks `rand::random_range(9000..65535)` and +/// de-duplicates against a process-local set. Under `cargo test` that mostly +/// holds, because every test shares one process and therefore one set. Under +/// `cargo nextest run` — which is what CI uses — each test is its own process, +/// so the set protects nothing and two tests eventually roll the same number. +/// The loser dies with `Address already in use (os error 98)` before its body +/// runs, which is why the failing test is a different one each time. +/// +/// Binding port 0 asks the kernel for a port it knows is free. There is still a +/// window between dropping this listener and the server binding, but the kernel +/// will not hand the same ephemeral port to someone else inside it, which is +/// the part guessing cannot promise. +fn free_port(interface: &str) -> u16 { + let listener = std::net::TcpListener::bind((interface, 0)) + .expect("bind an ephemeral port to ask the OS for a free one"); + listener + .local_addr() + .expect("read back the bound port") + .port() +} diff --git a/deboa-tokio/tests/common/helpers.rs b/deboa-tokio/tests/common/helpers.rs index 3db4b48e..c0d82678 100644 --- a/deboa-tokio/tests/common/helpers.rs +++ b/deboa-tokio/tests/common/helpers.rs @@ -81,7 +81,7 @@ pub async fn tls_mock_server() -> EasyHttpMock { .unwrap(), ) .protos(vec![protocol_version()]) - .with_random_port() + .port(free_port(&interface)) .cert(server_cert.to_vec()) .key(server_key.to_vec()) .ca(CA_CERT.to_vec()) @@ -140,3 +140,26 @@ pub async fn create_server() -> EasyHttpMock { #[cfg(not(any(feature = "rust-tls", feature = "native-tls")))] return plain_mock_server().await; } + +/// An ephemeral port the OS says is free, rather than a guessed one. +/// +/// `with_random_port()` picks `rand::random_range(9000..65535)` and +/// de-duplicates against a process-local set. Under `cargo test` that mostly +/// holds, because every test shares one process and therefore one set. Under +/// `cargo nextest run` — which is what CI uses — each test is its own process, +/// so the set protects nothing and two tests eventually roll the same number. +/// The loser dies with `Address already in use (os error 98)` before its body +/// runs, which is why the failing test is a different one each time. +/// +/// Binding port 0 asks the kernel for a port it knows is free. There is still a +/// window between dropping this listener and the server binding, but the kernel +/// will not hand the same ephemeral port to someone else inside it, which is +/// the part guessing cannot promise. +fn free_port(interface: &str) -> u16 { + let listener = std::net::TcpListener::bind((interface, 0)) + .expect("bind an ephemeral port to ask the OS for a free one"); + listener + .local_addr() + .expect("read back the bound port") + .port() +} From c836615533caa1169f6912bb25e4ef9c46a6e356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rog=C3=A9rio=20Ara=C3=BAjo?= Date: Sat, 22 Aug 2026 11:06:06 -0300 Subject: [PATCH 13/13] ci: make sure to use correct flags for darwin targets --- .cargo/config.toml | 3 +++ Cargo.lock | 4 ++-- Cargo.toml | 2 +- deboa-glommio/examples/simple.rs | 2 +- deboa-glommio/tests/round_trip.rs | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 8ce2d190..de606e3e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,9 @@ [build] rustflags = ["-C", "target-cpu=native"] +[target.aarch64-apple-darwin] +rustflags = ["-C", "target-cpu=apple-m1"] + [target.x86_64-pc-windows-gnu] rustflags = ["-C", "target-cpu=x86-64-v2"] diff --git a/Cargo.lock b/Cargo.lock index e181a26e..997899e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2550,9 +2550,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "loom" diff --git a/Cargo.toml b/Cargo.toml index 3e467a5c..cdcb3242 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,7 @@ hyper-util = { version = "0.1.20", features = [ "client", "client-legacy", ], default-features = false } -log = "0.4.28" +log = "0.4.34" minimime = "1.0.0" multer = "3.1.0" native-tls = { version = "0.2.18" } diff --git a/deboa-glommio/examples/simple.rs b/deboa-glommio/examples/simple.rs index 16711b00..38b9276e 100644 --- a/deboa-glommio/examples/simple.rs +++ b/deboa-glommio/examples/simple.rs @@ -4,7 +4,7 @@ //! cargo run -p deboa-glommio --example simple -- https://example.com //! ``` -use deboa::request::{get, FetchWith}; +use deboa::request::get; use deboa_glommio::Client; fn main() { diff --git a/deboa-glommio/tests/round_trip.rs b/deboa-glommio/tests/round_trip.rs index 0de238ab..6ae39c6f 100644 --- a/deboa-glommio/tests/round_trip.rs +++ b/deboa-glommio/tests/round_trip.rs @@ -8,7 +8,7 @@ use std::convert::Infallible; use std::net::SocketAddr; -use deboa::request::{get, FetchWith}; +use deboa::request::get; use deboa_glommio::Client; use http_body_util::Full; use hyper::body::Bytes;