diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13caad64..7215e796 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,8 +35,11 @@ jobs: ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install cargo-all-features + run: cargo install cargo-all-features + - name: Build - run: cargo build --verbose --all-features + run: cargo build-all-features --verbose - name: Linting run: cargo clippy --verbose --all-features - name: Check formatting diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9fa22700..a9fc774a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,5 +48,14 @@ jobs: ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Run testsuite - run: cargo test --verbose --features "async" + - name: Install cargo-all-features + run: cargo install cargo-all-features + + - name: Run unit tests + run: cargo test-all-features --verbose --lib --examples + + - name: Run doc tests + run: cargo test-all-features --verbose --doc + + - name: Run integration tests + run: cargo test --verbose --features "async" --benches --tests diff --git a/Cargo.lock b/Cargo.lock index b7879aae..86d4a0b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -259,11 +259,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.71" +version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" dependencies = [ "jobserver", + "libc", + "shlex", ] [[package]] @@ -813,13 +815,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -997,6 +999,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1121,7 +1140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24fddda5af7e54bf7da53067d6e802dbcc381d0a8eef629df528e3ebf68755cb" dependencies = [ "hermit-abi 0.3.9", - "rustix 0.38.1", + "rustix 0.38.25", "windows-sys 0.48.0", ] @@ -1142,9 +1161,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1166,9 +1185,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libgit2-sys" @@ -1227,9 +1246,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1318,7 +1337,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] @@ -1417,7 +1436,7 @@ version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", @@ -1443,6 +1462,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +[[package]] +name = "openssl-src" +version = "300.3.2+3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a211a18d945ef7e648cc6e0058f4c548ee46aab922ea203e0d30e966ea23647b" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.103" @@ -1451,6 +1479,7 @@ checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -1714,6 +1743,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -1724,7 +1754,9 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -1732,6 +1764,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -1739,9 +1772,40 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rust_engineio" version = "0.6.0" @@ -1757,6 +1821,8 @@ dependencies = [ "lazy_static", "native-tls", "reqwest", + "rustls", + "rustls-pemfile", "serde", "serde_json", "thiserror", @@ -1764,6 +1830,7 @@ dependencies = [ "tokio-tungstenite", "tungstenite", "url", + "webpki", ] [[package]] @@ -1777,10 +1844,12 @@ dependencies = [ "bytes", "cargo-tarpaulin", "futures-util", + "getrandom", "log", "native-tls", "rand", "rust_engineio", + "rustls", "serde", "serde_json", "serial_test", @@ -1820,17 +1889,44 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.1" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc6396159432b5c8490d4e301d8c705f61860b8b6c863bf79942ce5401968f3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.3", + "linux-raw-sys 0.4.14", "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -1843,9 +1939,20 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] [[package]] name = "ryu" @@ -2005,6 +2112,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.5" @@ -2037,6 +2150,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2049,6 +2174,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.107" @@ -2231,6 +2362,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -2240,9 +2382,14 @@ dependencies = [ "futures-util", "log", "native-tls", + "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", "tokio-native-tls", + "tokio-rustls", "tungstenite", + "webpki-roots", ] [[package]] @@ -2367,6 +2514,8 @@ dependencies = [ "log", "native-tls", "rand", + "rustls", + "rustls-pki-types", "sha1", "thiserror", "url", @@ -2406,6 +2555,18 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" @@ -2468,12 +2629,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2569,6 +2724,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2748,3 +2922,9 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/engineio/Cargo.toml b/engineio/Cargo.toml index 6822b2a8..ef0fbad3 100644 --- a/engineio/Cargo.toml +++ b/engineio/Cargo.toml @@ -16,19 +16,24 @@ all-features = true [dependencies] base64 = "0.22.0" bytes = "1" -reqwest = { version = "0.12.4", features = ["blocking", "native-tls", "stream"] } +reqwest = { version = "0.12.4", features = ["blocking", "stream"] } adler32 = "1.2.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" http = "1.1.0" -tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] } +tokio-tungstenite = { version = "0.21.0", features = [] } tungstenite = "0.21.0" tokio = "1.40.0" futures-util = { version = "0.3", default-features = false, features = ["sink"] } async-trait = "0.1.81" async-stream = "0.3.5" thiserror = "1.0" -native-tls = "0.2.12" +native-tls = { version = "^0.2", optional = true } +rustls = { version = "^0.22", optional = true } +# rustls-pemfile is only needed for unit tests +rustls-pemfile = { version = "2", optional = true } +# webpki is only needed for unit tests +webpki = { version = "0.21", optional = true } url = "2.5.2" [dev-dependencies] @@ -51,6 +56,39 @@ harness = false bench = false [features] -default = ["async"] +default = ["async", "native-tls"] async-callbacks = [] async = ["async-callbacks"] +native-tls = ["tokio-tungstenite/native-tls", "_native-tls"] +native-tls-vendored = ["tokio-tungstenite/native-tls-vendored", "_native-tls"] +rustls-tls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots", "_rustls-tls"] +rustls-tls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots", "_rustls-tls"] +# These features are for internal use only +_native-tls = ["dep:native-tls", "reqwest/native-tls"] +_rustls-tls = ["dep:rustls", "reqwest/rustls-tls", "dep:rustls-pemfile", "dep:webpki"] + +# This is an internal feature to allow us to build using cargo-all-features and guaranteing that we always have one TLS implementation +# Do not use this feature directly +_fallback-tls = ["native-tls"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)'] } + +[package.metadata.cargo-all-features] +skip_optional_dependencies = true +skip_feature_sets = [ + # Don't allow both at once + ["_native-tls", "_rustls-tls"], + # No need to compile with both native-tls options + ["native-tls", "native-tls-vendored"], + # No need to compile with both rustls options + ["rustls-tls-native-roots", "rustls-tls-webpki-roots"], + # Don't compile with both native-tls and rustls + ["native-tls", "rustls-tls-native-roots"], + ["native-tls", "rustls-tls-webpki-roots"], + ["native-tls-vendored", "rustls-tls-native-roots"], + ["native-tls-vendored", "rustls-tls-webpki-roots"], +] +always_include_features = ["_fallback-tls"] +# Don't use the internal features in the build matrix +denylist = ["_native-tls", "_rustls-tls"] diff --git a/engineio/src/asynchronous/async_socket.rs b/engineio/src/asynchronous/async_socket.rs index 8718e0ea..42cc101c 100644 --- a/engineio/src/asynchronous/async_socket.rs +++ b/engineio/src/asynchronous/async_socket.rs @@ -12,8 +12,10 @@ use bytes::Bytes; use futures_util::{stream, Stream, StreamExt}; use tokio::{runtime::Handle, sync::Mutex, time::Instant}; +#[cfg(feature = "async-callbacks")] +use super::callback::OptionalCallback; use crate::{ - asynchronous::{callback::OptionalCallback, transport::AsyncTransportType}, + asynchronous::transport::AsyncTransportType, error::Result, packet::{HandshakePacket, Payload}, Error, Packet, PacketId, @@ -24,37 +26,38 @@ pub struct Socket { handle: Handle, transport: Arc>, transport_raw: AsyncTransportType, - on_close: OptionalCallback<()>, - on_data: OptionalCallback, - on_error: OptionalCallback, - on_open: OptionalCallback<()>, - on_packet: OptionalCallback, connected: Arc, last_ping: Arc>, last_pong: Arc>, connection_data: Arc, max_ping_timeout: u64, + // Bellow are the fields that are only used when the async-callbacks feature is enabled. + #[cfg(feature = "async-callbacks")] + on_close: OptionalCallback<()>, + #[cfg(feature = "async-callbacks")] + on_data: OptionalCallback, + #[cfg(feature = "async-callbacks")] + on_error: OptionalCallback, + #[cfg(feature = "async-callbacks")] + on_open: OptionalCallback<()>, + #[cfg(feature = "async-callbacks")] + on_packet: OptionalCallback, } impl Socket { pub(crate) fn new( transport: AsyncTransportType, handshake: HandshakePacket, - on_close: OptionalCallback<()>, - on_data: OptionalCallback, - on_error: OptionalCallback, - on_open: OptionalCallback<()>, - on_packet: OptionalCallback, + #[cfg(feature = "async-callbacks")] on_close: OptionalCallback<()>, + #[cfg(feature = "async-callbacks")] on_data: OptionalCallback, + #[cfg(feature = "async-callbacks")] on_error: OptionalCallback, + #[cfg(feature = "async-callbacks")] on_open: OptionalCallback<()>, + #[cfg(feature = "async-callbacks")] on_packet: OptionalCallback, ) -> Self { let max_ping_timeout = handshake.ping_interval + handshake.ping_timeout; Socket { handle: Handle::current(), - on_close, - on_data, - on_error, - on_open, - on_packet, transport: Arc::new(Mutex::new(transport.clone())), transport_raw: transport, connected: Arc::new(AtomicBool::default()), @@ -62,6 +65,16 @@ impl Socket { last_pong: Arc::new(Mutex::new(Instant::now())), connection_data: Arc::new(handshake), max_ping_timeout, + #[cfg(feature = "async-callbacks")] + on_close, + #[cfg(feature = "async-callbacks")] + on_data, + #[cfg(feature = "async-callbacks")] + on_error, + #[cfg(feature = "async-callbacks")] + on_open, + #[cfg(feature = "async-callbacks")] + on_packet, } } @@ -71,6 +84,7 @@ impl Socket { // SAFETY: Has valid handshake due to type self.connected.store(true, Ordering::Release); + #[cfg(feature = "async-callbacks")] if let Some(on_open) = self.on_open.as_ref() { let on_open = on_open.clone(); self.handle.spawn(async move { on_open(()).await }); @@ -85,18 +99,22 @@ impl Socket { Ok(()) } - /// A helper method that distributes + /// A helper method that distributes the incoming packets to the appropriate callbacks. pub(super) async fn handle_incoming_packet(&self, packet: Packet) -> Result<()> { // check for the appropriate action or callback + #[cfg(feature = "async-callbacks")] self.handle_packet(packet.clone()); match packet.packet_id { PacketId::MessageBinary => { + #[cfg(feature = "async-callbacks")] self.handle_data(packet.data.clone()); } PacketId::Message => { + #[cfg(feature = "async-callbacks")] self.handle_data(packet.data.clone()); } PacketId::Close => { + #[cfg(feature = "async-callbacks")] self.handle_close(); } PacketId::Upgrade => { @@ -144,6 +162,7 @@ impl Socket { } pub async fn disconnect(&self) -> Result<()> { + #[cfg(feature = "async-callbacks")] if let Some(on_close) = self.on_close.as_ref() { let on_close = on_close.clone(); self.handle.spawn(async move { on_close(()).await }); @@ -188,6 +207,7 @@ impl Socket { /// Calls the error callback with a given message. #[inline] + #[cfg(feature = "async-callbacks")] fn call_error_callback(&self, text: String) { if let Some(on_error) = self.on_error.as_ref() { let on_error = on_error.clone(); @@ -195,6 +215,9 @@ impl Socket { } } + #[cfg(not(feature = "async-callbacks"))] + fn call_error_callback(&self, _text: String) {} + // Check if the underlying transport client is connected. pub(crate) fn is_connected(&self) -> bool { self.connected.load(Ordering::Acquire) @@ -221,6 +244,7 @@ impl Socket { } } + #[cfg(feature = "async-callbacks")] pub(crate) fn handle_packet(&self, packet: Packet) { if let Some(on_packet) = self.on_packet.as_ref() { let on_packet = on_packet.clone(); @@ -228,6 +252,7 @@ impl Socket { } } + #[cfg(feature = "async-callbacks")] pub(crate) fn handle_data(&self, data: Bytes) { if let Some(on_data) = self.on_data.as_ref() { let on_data = on_data.clone(); @@ -235,6 +260,7 @@ impl Socket { } } + #[cfg(feature = "async-callbacks")] pub(crate) fn handle_close(&self) { if let Some(on_close) = self.on_close.as_ref() { let on_close = on_close.clone(); @@ -278,17 +304,22 @@ impl Socket { #[cfg_attr(tarpaulin, ignore)] impl Debug for Socket { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Socket") + let mut debug = f.debug_struct("Socket"); + let debug = debug .field("transport", &self.transport) + .field("connected", &self.connected) + .field("last_ping", &self.last_ping) + .field("last_pong", &self.last_pong) + .field("connection_data", &self.connection_data); + + #[cfg(feature = "async-callbacks")] + debug .field("on_close", &self.on_close) .field("on_data", &self.on_data) .field("on_error", &self.on_error) .field("on_open", &self.on_open) - .field("on_packet", &self.on_packet) - .field("connected", &self.connected) - .field("last_ping", &self.last_ping) - .field("last_pong", &self.last_pong) - .field("connection_data", &self.connection_data) - .finish() + .field("on_packet", &self.on_packet); + + debug.finish() } } diff --git a/engineio/src/asynchronous/async_transports/polling.rs b/engineio/src/asynchronous/async_transports/polling.rs index d88f1e89..4aff5954 100644 --- a/engineio/src/asynchronous/async_transports/polling.rs +++ b/engineio/src/asynchronous/async_transports/polling.rs @@ -5,7 +5,6 @@ use base64::{engine::general_purpose, Engine as _}; use bytes::{BufMut, Bytes, BytesMut}; use futures_util::{Stream, StreamExt}; use http::HeaderMap; -use native_tls::TlsConnector; use reqwest::{Client, ClientBuilder, Response}; use std::fmt::Debug; use std::time::SystemTime; @@ -14,6 +13,7 @@ use tokio::sync::RwLock; use url::Url; use crate::asynchronous::generator::StreamGenerator; +use crate::TlsConfig; use crate::{asynchronous::transport::AsyncTransport, error::Result, Error}; /// An asynchronous polling type. Makes use of the nonblocking reqwest types and @@ -28,7 +28,7 @@ pub struct PollingTransport { impl PollingTransport { pub fn new( base_url: Url, - tls_config: Option, + tls_config: Option, opening_headers: Option, ) -> Self { let client = match (tls_config, opening_headers) { diff --git a/engineio/src/asynchronous/async_transports/websocket_secure.rs b/engineio/src/asynchronous/async_transports/websocket_secure.rs index cfd707a0..0e073152 100644 --- a/engineio/src/asynchronous/async_transports/websocket_secure.rs +++ b/engineio/src/asynchronous/async_transports/websocket_secure.rs @@ -9,7 +9,6 @@ use bytes::Bytes; use futures_util::Stream; use futures_util::StreamExt; use http::HeaderMap; -use native_tls::TlsConnector; use tokio::sync::RwLock; use tokio_tungstenite::connect_async_tls_with_config; use tokio_tungstenite::Connector; @@ -17,6 +16,7 @@ use tungstenite::client::IntoClientRequest; use url::Url; use super::websocket_general::AsyncWebsocketGeneralTransport; +use crate::TlsConfig; /// An asynchronous websocket transport type. /// This type only allows for secure websocket @@ -32,7 +32,7 @@ impl WebsocketSecureTransport { /// Tls connector and an URL. pub(crate) async fn new( base_url: Url, - tls_config: Option, + tls_config: Option, headers: Option, ) -> Result { let mut url = base_url; @@ -56,7 +56,12 @@ impl WebsocketSecureTransport { req, None, /*disable_nagle=*/ false, + #[cfg(all(feature = "_native-tls", not(feature = "_rustls-tls")))] tls_config.map(Connector::NativeTls), + #[cfg(feature = "_rustls-tls")] + tls_config.map(Arc::new).map(Connector::Rustls), + #[cfg(not(any(feature = "_native-tls", feature = "_rustls-tls")))] + compile_error!("Either feature `_native-tls` or `_rustls-tls` must be enabled"), ) .await?; diff --git a/engineio/src/asynchronous/client/async_client.rs b/engineio/src/asynchronous/client/async_client.rs index 99b0d0dd..0d5c9c1c 100644 --- a/engineio/src/asynchronous/client/async_client.rs +++ b/engineio/src/asynchronous/client/async_client.rs @@ -92,10 +92,10 @@ impl Debug for Client { mod test { use super::*; + use crate::TlsConfig; use crate::{asynchronous::ClientBuilder, header::HeaderMap, packet::PacketId, Error}; use bytes::Bytes; use futures_util::StreamExt; - use native_tls::TlsConnector; use url::Url; /// The purpose of this test is to check whether the Client is properly cloneable or not. @@ -384,7 +384,7 @@ mod test { let _ = builder(url.clone()) .tls_config( - TlsConnector::builder() + TlsConfig::builder() .danger_accept_invalid_certs(true) .build() .unwrap(), diff --git a/engineio/src/asynchronous/client/builder.rs b/engineio/src/asynchronous/client/builder.rs index 50dc9be2..87228325 100644 --- a/engineio/src/asynchronous/client/builder.rs +++ b/engineio/src/asynchronous/client/builder.rs @@ -8,11 +8,10 @@ use crate::{ error::Result, header::HeaderMap, packet::HandshakePacket, - Error, Packet, ENGINE_IO_VERSION, + Error, Packet, TlsConfig, ENGINE_IO_VERSION, }; use bytes::Bytes; use futures_util::{future::BoxFuture, StreamExt}; -use native_tls::TlsConnector; use url::Url; use super::Client; @@ -20,7 +19,7 @@ use super::Client; #[derive(Clone, Debug)] pub struct ClientBuilder { url: Url, - tls_config: Option, + tls_config: Option, headers: Option, handshake: Option, on_error: OptionalCallback, @@ -54,7 +53,7 @@ impl ClientBuilder { } /// Specify transport's tls config - pub fn tls_config(mut self, tls_config: TlsConnector) -> Self { + pub fn tls_config(mut self, tls_config: TlsConfig) -> Self { self.tls_config = Some(tls_config); self } diff --git a/engineio/src/asynchronous/transport.rs b/engineio/src/asynchronous/transport.rs index 7d33376f..ca520c8a 100644 --- a/engineio/src/asynchronous/transport.rs +++ b/engineio/src/asynchronous/transport.rs @@ -59,7 +59,6 @@ impl From for AsyncTransportType { } } -#[cfg(feature = "async")] impl AsyncTransportType { pub fn as_transport(&self) -> &(dyn AsyncTransport + Send) { match self { diff --git a/engineio/src/client/client.rs b/engineio/src/client/client.rs index dc22ff77..b37614de 100644 --- a/engineio/src/client/client.rs +++ b/engineio/src/client/client.rs @@ -7,9 +7,8 @@ use crate::error::{Error, Result}; use crate::header::HeaderMap; use crate::packet::{HandshakePacket, Packet, PacketId}; use crate::transports::{PollingTransport, WebsocketSecureTransport, WebsocketTransport}; -use crate::ENGINE_IO_VERSION; +use crate::{TlsConfig, ENGINE_IO_VERSION}; use bytes::Bytes; -use native_tls::TlsConnector; use std::convert::TryFrom; use std::convert::TryInto; use std::fmt::Debug; @@ -30,7 +29,7 @@ pub struct Client { #[derive(Clone, Debug)] pub struct ClientBuilder { url: Url, - tls_config: Option, + tls_config: Option, headers: Option, handshake: Option, on_error: OptionalCallback, @@ -64,7 +63,7 @@ impl ClientBuilder { } /// Specify transport's tls config - pub fn tls_config(mut self, tls_config: TlsConnector) -> Self { + pub fn tls_config(mut self, tls_config: TlsConfig) -> Self { self.tls_config = Some(tls_config); self } @@ -377,7 +376,7 @@ impl<'a> Iterator for Iter<'a> { #[cfg(test)] mod test { - use crate::packet::PacketId; + use crate::{packet::PacketId, test::tls_connector}; use super::*; @@ -650,10 +649,7 @@ mod test { let _ = builder(url.clone()) .tls_config( - TlsConnector::builder() - .danger_accept_invalid_certs(true) - .build() - .unwrap(), + tls_connector()? ) .build()?; let _ = builder(url).headers(headers).build()?; diff --git a/engineio/src/lib.rs b/engineio/src/lib.rs index 2ac5787d..3d92e4cc 100644 --- a/engineio/src/lib.rs +++ b/engineio/src/lib.rs @@ -99,27 +99,59 @@ pub use client::{Client, ClientBuilder}; pub use error::Error; pub use packet::{Packet, PacketId}; +// Re-export TLS configurations. This is the same as the socket.io logic. +// Needed in both crates so the compiler can know this is a re-export. +#[cfg(all(feature = "_native-tls", not(feature = "_rustls-tls")))] +#[doc(hidden)] +pub(crate) use native_tls::TlsConnector as TlsConfig; +#[doc(hidden)] +#[cfg(feature = "_rustls-tls")] +pub(crate) use rustls::ClientConfig as TlsConfig; + +// Both native-tls and rustls is not supported at the same time +#[cfg(not(feature = "_fallback-tls"))] +#[cfg(all(feature = "_native-tls", feature = "_rustls-tls"))] +compile_error!("Both native-tls and rustls features are enabled. Please enable only one of them."); + +#[cfg(not(any(feature = "_native-tls", feature = "_rustls-tls")))] +compile_error!("No TLS feature is enabled. Please enable either native-tls or rustls."); + #[cfg(test)] pub(crate) mod test { use super::*; + #[cfg(feature = "_native-tls")] use native_tls::TlsConnector; const CERT_PATH: &str = "../ci/cert/ca.crt"; + #[cfg(all(feature = "_native-tls", not(feature = "_rustls-tls")))] use native_tls::Certificate; use std::fs::File; use std::io::Read; - pub(crate) fn tls_connector() -> error::Result { + pub(crate) fn tls_connector() -> error::Result { let cert_path = std::env::var("CA_CERT_PATH").unwrap_or_else(|_| CERT_PATH.to_owned()); let mut cert_file = File::open(cert_path)?; let mut buf = vec![]; cert_file.read_to_end(&mut buf)?; - let cert: Certificate = Certificate::from_pem(&buf[..]).unwrap(); - Ok(TlsConnector::builder() - // ONLY USE FOR TESTING! - .danger_accept_invalid_hostnames(true) - .add_root_certificate(cert) - .build() - .unwrap()) + #[cfg(all(feature = "_native-tls", not(feature = "_rustls-tls")))] + { + let cert: Certificate = Certificate::from_pem(&buf[..]).unwrap(); + Ok(TlsConnector::builder() + // ONLY USE FOR TESTING! + .danger_accept_invalid_hostnames(true) + .add_root_certificate(cert) + .build() + .unwrap()) + } + #[cfg(feature = "_rustls-tls")] + { + let mut root_store = rustls::RootCertStore::empty(); + for cert in rustls_pemfile::certs(&mut buf.as_slice()) { + root_store.add(cert.expect("Invalid PEM cert")).expect("Failed to add cert to store"); + } + let mut config = rustls::ClientConfig::builder().with_root_certificates(root_store).with_no_client_auth(); + config.enable_sni = false; + Ok(config) + } } /// The `engine.io` server for testing runs on port 4201 const SERVER_URL: &str = "http://localhost:4201"; diff --git a/engineio/src/transports/polling.rs b/engineio/src/transports/polling.rs index 26aee87b..c2c2ffd8 100644 --- a/engineio/src/transports/polling.rs +++ b/engineio/src/transports/polling.rs @@ -1,8 +1,8 @@ use crate::error::{Error, Result}; use crate::transport::Transport; +use crate::TlsConfig; use base64::{engine::general_purpose, Engine as _}; use bytes::{BufMut, Bytes, BytesMut}; -use native_tls::TlsConnector; use reqwest::{ blocking::{Client, ClientBuilder}, header::HeaderMap, @@ -21,7 +21,7 @@ impl PollingTransport { /// Creates an instance of `PollingTransport`. pub fn new( base_url: Url, - tls_config: Option, + tls_config: Option, opening_headers: Option, ) -> Self { let client = match (tls_config, opening_headers) { diff --git a/engineio/src/transports/websocket_secure.rs b/engineio/src/transports/websocket_secure.rs index 345e0880..3291d787 100644 --- a/engineio/src/transports/websocket_secure.rs +++ b/engineio/src/transports/websocket_secure.rs @@ -5,11 +5,10 @@ use crate::{ }, error::Result, transport::Transport, - Error, + Error, TlsConfig, }; use bytes::Bytes; use http::HeaderMap; -use native_tls::TlsConnector; use std::{sync::Arc, time::Duration}; use tokio::runtime::Runtime; use url::Url; @@ -24,7 +23,7 @@ impl WebsocketSecureTransport { /// Creates an instance of `WebsocketSecureTransport`. pub fn new( base_url: Url, - tls_config: Option, + tls_config: Option, headers: Option, ) -> Result { let runtime = tokio::runtime::Builder::new_current_thread() diff --git a/socketio/Cargo.toml b/socketio/Cargo.toml index fc6064fd..1f89bb4f 100644 --- a/socketio/Cargo.toml +++ b/socketio/Cargo.toml @@ -14,7 +14,7 @@ license = "MIT" all-features = true [dependencies] -rust_engineio = { version = "0.6.0", path = "../engineio" } +rust_engineio = { version = "0.6.0", path = "../engineio", default-features = false} base64 = "0.22.0" bytes = "1" backoff = "0.4" @@ -22,13 +22,15 @@ rand = "0.8.5" adler32 = "1.2.0" serde_json = "1.0" thiserror = "1.0" -native-tls = "0.2.12" url = "2.5.2" tokio = { version = "1.40.0", optional = true } futures-util = { version = "0.3", default-features = false, features = ["sink"], optional = true } async-stream = { version = "0.3.5", optional = true } log = "0.4.22" serde = "1.0.209" +native-tls = { version = "0.2", optional = true } +rustls = { version = "0.22", optional = true } +getrandom = "0.2.10" [dev-dependencies] cargo-tarpaulin = "0.18.5" @@ -40,11 +42,48 @@ version = "1.40.0" features = ["macros", "rt-multi-thread"] [features] -default = [] +default = ["native-tls"] async-callbacks = ["rust_engineio/async-callbacks"] async = ["async-callbacks", "rust_engineio/async", "tokio", "futures-util", "async-stream"] +native-tls = ["rust_engineio/native-tls", "_native-tls"] +native-tls-vendored = ["rust_engineio/native-tls-vendored", "_native-tls"] +rustls-tls-native-roots = ["rust_engineio/rustls-tls-native-roots", "_rustls-tls"] +rustls-tls-webpki-roots = ["rust_engineio/rustls-tls-webpki-roots", "_rustls-tls"] +_native-tls = ["dep:native-tls"] +_rustls-tls = ["dep:rustls"] + +# This is an internal feature to allow us to build using cargo-all-features and guaranteing that we always have one TLS implementation +# Do not use this feature directly +_fallback-tls = ["native-tls", "rust_engineio/_fallback-tls"] [[example]] name = "async" path = "examples/async.rs" -required-features = ["async"] +required-features = ["async", "native-tls"] + +[[example]] +name = "secure" +path = "examples/secure.rs" +required-features = ["native-tls"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin)'] } + +[package.metadata.cargo-all-features] +skip_optional_dependencies = true +skip_feature_sets = [ + # Don't allow both at once + ["_native-tls", "_rustls-tls"], + # No need to compile with both native-tls options + ["native-tls", "native-tls-vendored"], + # No need to compile with both rustls options + ["rustls-tls-native-roots", "rustls-tls-webpki-roots"], + # Don't compile with both native-tls and rustls + ["native-tls", "rustls-tls-native-roots"], + ["native-tls", "rustls-tls-webpki-roots"], + ["native-tls-vendored", "rustls-tls-native-roots"], + ["native-tls-vendored", "rustls-tls-webpki-roots"], +] +always_include_features = ["_fallback-tls"] +# Don't use the internal features in the build matrix +denylist = ["_native-tls", "_rustls-tls"] diff --git a/socketio/src/asynchronous/client/builder.rs b/socketio/src/asynchronous/client/builder.rs index 44710e19..1b4f6cc1 100644 --- a/socketio/src/asynchronous/client/builder.rs +++ b/socketio/src/asynchronous/client/builder.rs @@ -1,6 +1,5 @@ use futures_util::future::BoxFuture; use log::trace; -use native_tls::TlsConnector; use rust_engineio::{ asynchronous::ClientBuilder as EngineIoClientBuilder, header::{HeaderMap, HeaderValue}, @@ -8,7 +7,7 @@ use rust_engineio::{ use std::collections::HashMap; use url::Url; -use crate::{error::Result, Event, Payload, TransportType}; +use crate::{error::Result, Event, Payload, TransportType, TlsConfig}; use super::{ callback::{ @@ -28,7 +27,7 @@ pub struct ClientBuilder { pub(crate) on_any: Option>, pub(crate) on_reconnect: Option>, pub(crate) namespace: String, - tls_config: Option, + tls_config: Option, opening_headers: Option, transport_type: TransportType, pub(crate) auth: Option, @@ -285,7 +284,7 @@ impl ClientBuilder { /// .await; /// } /// ``` - pub fn tls_config(mut self, tls_config: TlsConnector) -> Self { + pub fn tls_config(mut self, tls_config: TlsConfig) -> Self { self.tls_config = Some(tls_config); self } diff --git a/socketio/src/client/builder.rs b/socketio/src/client/builder.rs index 724971f0..e7241d8e 100644 --- a/socketio/src/client/builder.rs +++ b/socketio/src/client/builder.rs @@ -1,8 +1,7 @@ use super::super::{event::Event, payload::Payload}; use super::callback::Callback; use super::client::Client; -use crate::RawClient; -use native_tls::TlsConnector; +use crate::{RawClient, TlsConfig}; use rust_engineio::client::ClientBuilder as EngineIoClientBuilder; use rust_engineio::header::{HeaderMap, HeaderValue}; use url::Url; @@ -37,7 +36,7 @@ pub struct ClientBuilder { on: Arc>>>, on_any: Arc>>>, namespace: String, - tls_config: Option, + tls_config: Option, opening_headers: Option, transport_type: TransportType, auth: Option, @@ -227,7 +226,7 @@ impl ClientBuilder { /// .connect(); /// /// ``` - pub fn tls_config(mut self, tls_config: TlsConnector) -> Self { + pub fn tls_config(mut self, tls_config: TlsConfig) -> Self { self.tls_config = Some(tls_config); self } diff --git a/socketio/src/client/raw_client.rs b/socketio/src/client/raw_client.rs index 0686683f..7dd93796 100644 --- a/socketio/src/client/raw_client.rs +++ b/socketio/src/client/raw_client.rs @@ -418,10 +418,24 @@ mod test { use super::*; use crate::{client::TransportType, payload::Payload, ClientBuilder}; use bytes::Bytes; - use native_tls::TlsConnector; use serde_json::json; use std::time::Duration; + #[cfg(feature = "_native-tls")] + fn tls_config() -> native_tls::TlsConnector { + native_tls::TlsConnector::builder() + .use_sni(true) + .build() + .expect("Found illegal configuration") + } + + #[cfg(feature = "_rustls-tls")] + fn tls_config() -> rustls::ClientConfig { + rustls::ClientConfig::builder() + .with_root_certificates(rustls::RootCertStore::empty()) + .with_no_client_auth() + } + #[test] fn socket_io_integration() -> Result<()> { let url = crate::test::socket_io_server(); @@ -474,14 +488,9 @@ mod test { // test socket build logic let socket_builder = ClientBuilder::new(url); - let tls_connector = TlsConnector::builder() - .use_sni(true) - .build() - .expect("Found illegal configuration"); - let socket = socket_builder .namespace("/admin") - .tls_config(tls_connector) + .tls_config(tls_config()) .opening_header("accept-encoding", "application/json") .on("test", |str, _| println!("Received: {:#?}", str)) .on("message", |payload, _| println!("{:#?}", payload)) @@ -515,14 +524,9 @@ mod test { // test socket build logic let socket_builder = ClientBuilder::new(url); - let tls_connector = TlsConnector::builder() - .use_sni(true) - .build() - .expect("Found illegal configuration"); - let socket = socket_builder .namespace("/admin") - .tls_config(tls_connector) + .tls_config(tls_config()) .opening_header("accept-encoding", "application/json") .on("test", |str, _| println!("Received: {:#?}", str)) .on("message", |payload, _| println!("{:#?}", payload)) diff --git a/socketio/src/lib.rs b/socketio/src/lib.rs index b913eb4d..f3ca50b4 100644 --- a/socketio/src/lib.rs +++ b/socketio/src/lib.rs @@ -199,6 +199,23 @@ pub use client::{ClientBuilder, RawClient, TransportType}; #[deprecated(since = "0.3.0-alpha-2", note = "Socket renamed to Client")] pub use client::{ClientBuilder as SocketBuilder, RawClient as Socket}; +// Re-export TLS configurations. This is the same as the engine.io logic. +// Needed here so it knows it's actually a re-import, not a new type. +#[cfg(all(feature = "_native-tls", not(feature = "_rustls-tls")))] +#[doc(hidden)] +pub use native_tls::TlsConnector as TlsConfig; +#[doc(hidden)] +#[cfg(feature = "_rustls-tls")] +pub use rustls::ClientConfig as TlsConfig; + +// Both native-tls and rustls is not supported at the same time +#[cfg(not(feature = "_fallback-tls"))] +#[cfg(all(feature = "_native-tls", feature = "_rustls-tls"))] +compile_error!("Both native-tls and rustls features are enabled. Please enable only one of them."); + +#[cfg(not(any(feature = "_native-tls", feature = "_rustls-tls")))] +compile_error!("No TLS feature is enabled. Please enable either native-tls or rustls."); + #[cfg(test)] pub(crate) mod test { use url::Url;