diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d6845ef22..de5f00baa 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -34,6 +34,33 @@ jobs: - run: cargo test --manifest-path fuzz/Cargo.toml if: ${{ matrix.rust }} == "stable" + wasm_build: + name: Build wasm32 + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Add wasm target + run: rustup target add wasm32-unknown-unknown + + - name: wasm32 build (quinn) + run: cargo build -p iroh-quinn --no-default-features --features log,ring,rustls,wasm,runtime-wasm --target wasm32-unknown-unknown + env: + RUST_BACKTRACE: 1 + + - name: wasm32 build (quinn-proto) + run: cargo build -p iroh-quinn-proto --features wasm --target wasm32-unknown-unknown + env: + RUST_BACKTRACE: 1 + - name: wasm32 build (quinn-udp) + run: cargo build -p iroh-quinn-udp --no-default-features --features log --target wasm32-unknown-unknown + env: + RUST_BACKTRACE: 1 + msrv: runs-on: ubuntu-latest env: @@ -42,7 +69,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: mozilla-actions/sccache-action@v0.0.4 - - uses: dtolnay/rust-toolchain@1.63.0 + - uses: dtolnay/rust-toolchain@1.73.0 - run: cargo check --lib --all-features -p iroh-quinn-udp -p iroh-quinn-proto -p iroh-quinn lint: diff --git a/quinn-proto/Cargo.toml b/quinn-proto/Cargo.toml index 1f496e1b7..6f3bffa07 100644 --- a/quinn-proto/Cargo.toml +++ b/quinn-proto/Cargo.toml @@ -2,7 +2,7 @@ name = "iroh-quinn-proto" version = "0.10.8" edition = "2021" -rust-version = "1.63" +rust-version = "1.73" license = "MIT OR Apache-2.0" repository = "https://github.com/quinn-rs/quinn" description = "State machine for the QUIC transport protocol" @@ -18,11 +18,12 @@ maintenance = { status = "experimental" } [features] default = ["tls-rustls", "log"] -tls-rustls = ["rustls", "ring"] +tls-rustls = ["rustls", "ring", "rustls-pki-types"] # Provides `ClientConfig::with_native_roots()` convenience method native-certs = ["rustls-native-certs"] # Write logs via the `log` crate when no `tracing` subscriber exists log = ["tracing/log"] +wasm = ["getrandom/js", "ring?/wasm32_unknown_unknown_js", "rustls-pki-types?/web", "dep:web-time"] [dependencies] arbitrary = { version = "1.0.1", features = ["derive"], optional = true } @@ -30,12 +31,15 @@ bytes = "1" rustc-hash = "1.1" rand = "0.8" ring = { version = "0.17", optional = true } -rustls = { version = "0.21.0", default-features = false, features = ["quic"], optional = true } +rustls = { git = "https://github.com/matheus23/rustls", branch = "0.21-wasm", default-features = false, features = ["quic"], optional = true } rustls-native-certs = { version = "0.6", optional = true } slab = "0.4" thiserror = "1.0.21" tinyvec = { version = "1.1", features = ["alloc"] } tracing = "0.1.10" +getrandom = { version = "0.2", default-features = false } +rustls-pki-types = { version = "1.7", optional = true } +web-time = { version = "1", optional = true } [dev-dependencies] assert_matches = "1.1" diff --git a/quinn-proto/src/cid_generator.rs b/quinn-proto/src/cid_generator.rs index 583ff7611..657605727 100644 --- a/quinn-proto/src/cid_generator.rs +++ b/quinn-proto/src/cid_generator.rs @@ -1,9 +1,7 @@ -use std::time::Duration; - use rand::RngCore; use crate::shared::ConnectionId; -use crate::MAX_CID_SIZE; +use crate::{Duration, MAX_CID_SIZE}; /// Generates connection IDs for incoming connections pub trait ConnectionIdGenerator: Send { diff --git a/quinn-proto/src/config.rs b/quinn-proto/src/config.rs index 0e095d307..95270ec06 100644 --- a/quinn-proto/src/config.rs +++ b/quinn-proto/src/config.rs @@ -1,4 +1,4 @@ -use std::{fmt, num::TryFromIntError, sync::Arc, time::Duration}; +use std::{fmt, num::TryFromIntError, sync::Arc}; use thiserror::Error; @@ -9,7 +9,8 @@ use crate::{ cid_generator::{ConnectionIdGenerator, RandomConnectionIdGenerator}, congestion, crypto::{self, HandshakeTokenKey, HmacKey}, - VarInt, VarIntBoundsExceeded, DEFAULT_SUPPORTED_VERSIONS, INITIAL_MTU, MAX_UDP_PAYLOAD, + Duration, VarInt, VarIntBoundsExceeded, DEFAULT_SUPPORTED_VERSIONS, INITIAL_MTU, + MAX_UDP_PAYLOAD, }; /// Parameters governing the core QUIC state machine @@ -79,8 +80,8 @@ impl TransportConfig { /// idle timeout can result in permanently hung futures! /// /// ``` - /// # use std::{convert::TryInto, time::Duration}; - /// # use iroh_quinn_proto::{TransportConfig, VarInt, VarIntBoundsExceeded}; + /// # use std::{convert::TryInto}; + /// # use iroh_quinn_proto::{TransportConfig, VarInt, VarIntBoundsExceeded, Duration}; /// # fn main() -> Result<(), VarIntBoundsExceeded> { /// let mut config = TransportConfig::default(); /// @@ -863,8 +864,8 @@ impl From for ConfigError { /// constructed by converting directly from `VarInt`, or using `TryFrom`. /// /// ``` -/// # use std::{convert::TryFrom, time::Duration}; -/// # use iroh_quinn_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt}; +/// # use std::{convert::TryFrom}; +/// # use iroh_quinn_proto::{IdleTimeout, VarIntBoundsExceeded, VarInt, Duration}; /// # fn main() -> Result<(), VarIntBoundsExceeded> { /// // A `VarInt`-encoded value in milliseconds /// let timeout = IdleTimeout::from(VarInt::from_u32(10_000)); diff --git a/quinn-proto/src/congestion.rs b/quinn-proto/src/congestion.rs index 41d24566d..f26ebd918 100644 --- a/quinn-proto/src/congestion.rs +++ b/quinn-proto/src/congestion.rs @@ -1,8 +1,8 @@ //! Logic for controlling the rate at which data is sent use crate::connection::RttEstimator; +use crate::Instant; use std::any::Any; -use std::time::Instant; mod bbr; mod cubic; diff --git a/quinn-proto/src/congestion/bbr/bw_estimation.rs b/quinn-proto/src/congestion/bbr/bw_estimation.rs index 479e24120..8796f4572 100644 --- a/quinn-proto/src/congestion/bbr/bw_estimation.rs +++ b/quinn-proto/src/congestion/bbr/bw_estimation.rs @@ -1,7 +1,7 @@ use std::fmt::{Debug, Display, Formatter}; -use std::time::{Duration, Instant}; use super::min_max::MinMax; +use crate::{Duration, Instant}; #[derive(Clone, Debug)] pub(crate) struct BandwidthEstimation { diff --git a/quinn-proto/src/congestion/bbr/mod.rs b/quinn-proto/src/congestion/bbr/mod.rs index 07dde798d..4399a6f93 100644 --- a/quinn-proto/src/congestion/bbr/mod.rs +++ b/quinn-proto/src/congestion/bbr/mod.rs @@ -1,13 +1,13 @@ use std::any::Any; use std::fmt::Debug; use std::sync::Arc; -use std::time::{Duration, Instant}; use rand::{Rng, SeedableRng}; use crate::congestion::bbr::bw_estimation::BandwidthEstimation; use crate::congestion::bbr::min_max::MinMax; use crate::connection::RttEstimator; +use crate::{Duration, Instant}; use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE}; diff --git a/quinn-proto/src/congestion/cubic.rs b/quinn-proto/src/congestion/cubic.rs index 7b6d41d6b..953d238c6 100644 --- a/quinn-proto/src/congestion/cubic.rs +++ b/quinn-proto/src/congestion/cubic.rs @@ -1,9 +1,8 @@ use std::any::Any; use std::sync::Arc; -use std::time::{Duration, Instant}; use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE}; -use crate::connection::RttEstimator; +use crate::{connection::RttEstimator, Duration, Instant}; use std::cmp; /// CUBIC Constants. diff --git a/quinn-proto/src/congestion/new_reno.rs b/quinn-proto/src/congestion/new_reno.rs index 0b085c377..8c65b9eb8 100644 --- a/quinn-proto/src/congestion/new_reno.rs +++ b/quinn-proto/src/congestion/new_reno.rs @@ -1,9 +1,8 @@ use std::any::Any; use std::sync::Arc; -use std::time::Instant; use super::{Controller, ControllerFactory, BASE_DATAGRAM_SIZE}; -use crate::connection::RttEstimator; +use crate::{connection::RttEstimator, Instant}; /// A simple, standard congestion controller #[derive(Debug, Clone)] diff --git a/quinn-proto/src/connection/cid_state.rs b/quinn-proto/src/connection/cid_state.rs index e12459379..d5db419c1 100644 --- a/quinn-proto/src/connection/cid_state.rs +++ b/quinn-proto/src/connection/cid_state.rs @@ -1,13 +1,10 @@ //! Maintain the state of local connection IDs -use std::{ - collections::VecDeque, - time::{Duration, Instant}, -}; +use std::collections::VecDeque; use rustc_hash::FxHashSet; use tracing::{debug, trace}; -use crate::{shared::IssuedCid, TransportError}; +use crate::{shared::IssuedCid, Duration, Instant, TransportError}; /// Local connection ID management pub(super) struct CidState { diff --git a/quinn-proto/src/connection/mod.rs b/quinn-proto/src/connection/mod.rs index a2b14441c..6ae0b7aad 100644 --- a/quinn-proto/src/connection/mod.rs +++ b/quinn-proto/src/connection/mod.rs @@ -5,7 +5,6 @@ use std::{ fmt, io, mem, net::{IpAddr, SocketAddr}, sync::Arc, - time::{Duration, Instant}, }; use bytes::{Bytes, BytesMut}; @@ -30,8 +29,9 @@ use crate::{ }, token::ResetToken, transport_parameters::TransportParameters, - Dir, EndpointConfig, Frame, Side, StreamId, Transmit, TransportError, TransportErrorCode, - VarInt, MAX_STREAM_COUNT, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, TIMER_GRANULARITY, + Dir, Duration, EndpointConfig, Frame, Instant, Side, StreamId, Transmit, TransportError, + TransportErrorCode, VarInt, MAX_STREAM_COUNT, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, + TIMER_GRANULARITY, }; mod assembler; diff --git a/quinn-proto/src/connection/mtud.rs b/quinn-proto/src/connection/mtud.rs index 438d9b4f6..27391b62b 100644 --- a/quinn-proto/src/connection/mtud.rs +++ b/quinn-proto/src/connection/mtud.rs @@ -1,5 +1,4 @@ -use crate::{packet::SpaceId, MtuDiscoveryConfig, MAX_UDP_PAYLOAD}; -use std::time::Instant; +use crate::{packet::SpaceId, Instant, MtuDiscoveryConfig, MAX_UDP_PAYLOAD}; use tracing::trace; /// Implements Datagram Packetization Layer Path Maximum Transmission Unit Discovery @@ -484,9 +483,8 @@ const BINARY_SEARCH_MINIMUM_CHANGE: u16 = 20; mod tests { use super::*; use crate::packet::SpaceId; - use crate::MAX_UDP_PAYLOAD; + use crate::{Duration, MAX_UDP_PAYLOAD}; use assert_matches::assert_matches; - use std::time::Duration; fn default_mtud() -> MtuDiscovery { let config = MtuDiscoveryConfig::default(); diff --git a/quinn-proto/src/connection/pacing.rs b/quinn-proto/src/connection/pacing.rs index f77889faf..e84dc2c15 100644 --- a/quinn-proto/src/connection/pacing.rs +++ b/quinn-proto/src/connection/pacing.rs @@ -1,9 +1,9 @@ //! Pacing of packet transmissions. -use std::time::{Duration, Instant}; - use tracing::warn; +use crate::{Duration, Instant}; + /// A simple token-bucket pacer /// /// The pacer's capacity is derived on a fraction of the congestion window diff --git a/quinn-proto/src/connection/packet_builder.rs b/quinn-proto/src/connection/packet_builder.rs index 266bd96b4..67e81f0ad 100644 --- a/quinn-proto/src/connection/packet_builder.rs +++ b/quinn-proto/src/connection/packet_builder.rs @@ -1,5 +1,3 @@ -use std::time::Instant; - use bytes::{Bytes, BytesMut}; use rand::Rng; use tracing::{trace, trace_span}; @@ -8,7 +6,7 @@ use super::{spaces::SentPacket, Connection, SentFrames}; use crate::{ frame::{self, Close}, packet::{Header, LongType, PacketNumber, PartialEncode, SpaceId, FIXED_BIT}, - TransportError, TransportErrorCode, + Instant, TransportError, TransportErrorCode, }; pub(super) struct PacketBuilder { diff --git a/quinn-proto/src/connection/paths.rs b/quinn-proto/src/connection/paths.rs index 074b0f255..f7037c673 100644 --- a/quinn-proto/src/connection/paths.rs +++ b/quinn-proto/src/connection/paths.rs @@ -1,7 +1,9 @@ -use std::{cmp, net::SocketAddr, time::Duration, time::Instant}; +use std::{cmp, net::SocketAddr}; use super::{mtud::MtuDiscovery, pacing::Pacer}; -use crate::{config::MtuDiscoveryConfig, congestion, packet::SpaceId, TIMER_GRANULARITY}; +use crate::{ + config::MtuDiscoveryConfig, congestion, packet::SpaceId, Duration, Instant, TIMER_GRANULARITY, +}; /// Description of a particular network path pub(super) struct PathData { diff --git a/quinn-proto/src/connection/spaces.rs b/quinn-proto/src/connection/spaces.rs index 4a3a986ff..24f516ff6 100644 --- a/quinn-proto/src/connection/spaces.rs +++ b/quinn-proto/src/connection/spaces.rs @@ -3,7 +3,6 @@ use std::{ collections::{BTreeMap, VecDeque}, mem, ops::{Index, IndexMut}, - time::{Duration, Instant}, }; use rustc_hash::FxHashSet; @@ -11,7 +10,7 @@ use rustc_hash::FxHashSet; use super::assembler::Assembler; use crate::{ connection::StreamsState, crypto::Keys, frame, packet::SpaceId, range_set::ArrayRangeSet, - shared::IssuedCid, Dir, StreamId, VarInt, + shared::IssuedCid, Dir, Duration, Instant, StreamId, VarInt, }; pub(super) struct PacketSpace { diff --git a/quinn-proto/src/connection/stats.rs b/quinn-proto/src/connection/stats.rs index 9e67cb907..77d857226 100644 --- a/quinn-proto/src/connection/stats.rs +++ b/quinn-proto/src/connection/stats.rs @@ -1,7 +1,6 @@ //! Connection statistics -use crate::{frame::Frame, Dir}; -use std::time::Duration; +use crate::{frame::Frame, Dir, Duration}; /// Statistics about UDP datagrams transmitted or received on a connection #[derive(Default, Debug, Copy, Clone)] diff --git a/quinn-proto/src/connection/timer.rs b/quinn-proto/src/connection/timer.rs index d4f9e3487..7b1725cf8 100644 --- a/quinn-proto/src/connection/timer.rs +++ b/quinn-proto/src/connection/timer.rs @@ -1,4 +1,4 @@ -use std::time::Instant; +use crate::Instant; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub(crate) enum Timer { diff --git a/quinn-proto/src/endpoint.rs b/quinn-proto/src/endpoint.rs index 42db70848..051a6479f 100644 --- a/quinn-proto/src/endpoint.rs +++ b/quinn-proto/src/endpoint.rs @@ -5,7 +5,6 @@ use std::{ net::{IpAddr, SocketAddr}, ops::{Index, IndexMut}, sync::Arc, - time::{Instant, SystemTime}, }; use bytes::{BufMut, Bytes, BytesMut}; @@ -28,8 +27,8 @@ use crate::{ EndpointEventInner, IssuedCid, }, transport_parameters::TransportParameters, - ResetToken, RetryToken, Side, Transmit, TransportConfig, TransportError, INITIAL_MTU, - MAX_CID_SIZE, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, + Instant, ResetToken, RetryToken, Side, SystemTime, Transmit, TransportConfig, TransportError, + INITIAL_MTU, MAX_CID_SIZE, MIN_INITIAL_SIZE, RESET_TOKEN_SIZE, }; /// The main entry point to the library diff --git a/quinn-proto/src/lib.rs b/quinn-proto/src/lib.rs index 1c7f476b2..9aed52860 100644 --- a/quinn-proto/src/lib.rs +++ b/quinn-proto/src/lib.rs @@ -24,7 +24,6 @@ use std::{ fmt, net::{IpAddr, SocketAddr}, ops, - time::Duration, }; mod cid_queue; @@ -286,6 +285,12 @@ pub struct Transmit { pub src_ip: Option, } +// Deal with time +#[cfg(not(all(target_family = "wasm", target_os = "unknown")))] +pub use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; +#[cfg(all(target_family = "wasm", target_os = "unknown", feature = "wasm"))] +pub use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH}; + // // Useful internal constants // diff --git a/quinn-proto/src/shared.rs b/quinn-proto/src/shared.rs index e670f1d6f..336421b3d 100644 --- a/quinn-proto/src/shared.rs +++ b/quinn-proto/src/shared.rs @@ -1,8 +1,8 @@ -use std::{fmt, net::SocketAddr, time::Instant}; +use std::{fmt, net::SocketAddr}; use bytes::{Buf, BufMut, BytesMut}; -use crate::{coding::BufExt, packet::PartialDecode, ResetToken, MAX_CID_SIZE}; +use crate::{coding::BufExt, packet::PartialDecode, Instant, ResetToken, MAX_CID_SIZE}; /// Events sent from an Endpoint to a Connection #[derive(Debug)] diff --git a/quinn-proto/src/token.rs b/quinn-proto/src/token.rs index 1da2e453c..fa7c50f69 100644 --- a/quinn-proto/src/token.rs +++ b/quinn-proto/src/token.rs @@ -1,7 +1,6 @@ use std::{ fmt, io, net::{IpAddr, SocketAddr}, - time::{Duration, SystemTime, UNIX_EPOCH}, }; use bytes::BufMut; @@ -10,7 +9,7 @@ use crate::{ coding::{BufExt, BufMutExt}, crypto::{CryptoError, HandshakeTokenKey, HmacKey}, shared::ConnectionId, - RESET_TOKEN_SIZE, + Duration, SystemTime, RESET_TOKEN_SIZE, UNIX_EPOCH, }; pub(crate) struct RetryToken<'a> { diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml index 378f187a6..50b024235 100644 --- a/quinn-udp/Cargo.toml +++ b/quinn-udp/Cargo.toml @@ -2,7 +2,7 @@ name = "iroh-quinn-udp" version = "0.4.2" edition = "2021" -rust-version = "1.63" +rust-version = "1.73" license = "MIT OR Apache-2.0" repository = "https://github.com/quinn-rs/quinn" description = "UDP sockets with ECN information for the QUIC transport protocol" @@ -14,9 +14,10 @@ workspace = ".." all-features = true [features] -default = ["log"] +default = ["log", "network"] # Write logs via the `log` crate when no `tracing` subscriber exists log = ["tracing/log"] +network = ["dep:socket2"] [badges] maintenance = { status = "experimental" } @@ -24,7 +25,7 @@ maintenance = { status = "experimental" } [dependencies] bytes = "1" libc = "0.2.113" -socket2 = "0.5" +socket2 = { version = "0.5", optional = true } tracing = "0.1.10" [target.'cfg(windows)'.dependencies] diff --git a/quinn-udp/src/fallback.rs b/quinn-udp/src/fallback.rs index 28e4cb3f4..ab5c61bbd 100644 --- a/quinn-udp/src/fallback.rs +++ b/quinn-udp/src/fallback.rs @@ -1,11 +1,14 @@ +#[cfg(feature = "network")] use std::{ io::{self, IoSliceMut}, sync::Mutex, time::Instant, }; +#[cfg(feature = "network")] use proto::Transmit; +#[cfg(feature = "network")] use super::{log_sendmsg_error, RecvMeta, UdpSockRef, UdpState, IO_ERROR_LOG_INTERVAL}; /// Fallback UDP socket interface that stubs out all special functionality @@ -14,10 +17,12 @@ use super::{log_sendmsg_error, RecvMeta, UdpSockRef, UdpState, IO_ERROR_LOG_INTE /// reduced performance compared to that enabled by some target-specific interfaces. #[derive(Debug)] pub struct UdpSocketState { + #[cfg(feature = "network")] last_send_error: Mutex, } impl UdpSocketState { + #[cfg(feature = "network")] pub fn new() -> Self { let now = Instant::now(); Self { @@ -25,10 +30,12 @@ impl UdpSocketState { } } + #[cfg(feature = "network")] pub fn configure(socket: UdpSockRef<'_>) -> io::Result<()> { socket.0.set_nonblocking(true) } + #[cfg(feature = "network")] pub fn send( &self, socket: UdpSockRef<'_>, @@ -67,6 +74,7 @@ impl UdpSocketState { Ok(sent) } + #[cfg(feature = "network")] pub fn recv( &self, socket: UdpSockRef<'_>, @@ -92,6 +100,7 @@ impl UdpSocketState { } } +#[cfg(feature = "network")] impl Default for UdpSocketState { fn default() -> Self { Self::new() diff --git a/quinn-udp/src/lib.rs b/quinn-udp/src/lib.rs index b01363b39..a54abebd4 100644 --- a/quinn-udp/src/lib.rs +++ b/quinn-udp/src/lib.rs @@ -2,18 +2,20 @@ #![warn(unreachable_pub)] #![warn(clippy::use_self)] +use std::{ + net::{IpAddr, Ipv6Addr, SocketAddr}, + sync::atomic::{AtomicUsize, Ordering}, +}; + #[cfg(unix)] use std::os::unix::io::AsFd; #[cfg(windows)] use std::os::windows::io::AsSocket; -#[cfg(not(windows))] +#[cfg(all(not(windows), feature = "network"))] use std::sync::atomic::AtomicBool; +#[cfg(feature = "network")] use std::{ - net::{IpAddr, Ipv6Addr, SocketAddr}, - sync::{ - atomic::{AtomicUsize, Ordering}, - Mutex, - }, + sync::Mutex, time::{Duration, Instant}, }; @@ -59,7 +61,7 @@ pub struct UdpState { /// If enabled, we assume that old kernel is used and switch to fallback mode. /// In particular, we do not use IP_TOS cmsg_type in this case, /// which is not supported on Linux <3.13 and results in not sending the UDP packet at all. - #[cfg(not(windows))] + #[cfg(all(not(windows), feature = "network"))] sendmsg_einval: AtomicBool, } @@ -89,7 +91,7 @@ impl UdpState { /// Returns true if we previously got an EINVAL error from `sendmsg` or `sendmmsg` syscall. #[inline] - #[cfg(not(windows))] + #[cfg(all(not(windows), feature = "network"))] fn sendmsg_einval(&self) -> bool { self.sendmsg_einval.load(Ordering::Relaxed) } @@ -103,7 +105,8 @@ impl UdpState { target_os = "ios", target_os = "openbsd", target_os = "netbsd" - )) + )), + feature = "network" ))] fn set_sendmsg_einval(&self) { self.sendmsg_einval.store(true, Ordering::Relaxed) @@ -156,12 +159,14 @@ pub struct Transmit { } /// Log at most 1 IO error per minute +#[cfg(feature = "network")] const IO_ERROR_LOG_INTERVAL: Duration = std::time::Duration::from_secs(60); /// Logs a warning message when sendmsg fails /// /// Logging will only be performed if at least [`IO_ERROR_LOG_INTERVAL`] /// has elapsed since the last error was logged. +#[cfg(feature = "network")] fn log_sendmsg_error( last_send_error: &Mutex, err: impl core::fmt::Debug, @@ -182,9 +187,10 @@ fn log_sendmsg_error( /// On Unix, constructible via `From`. On Windows, constructible via `From`. // Wrapper around socket2 to avoid making it a public dependency and incurring stability risk +#[cfg(feature = "network")] pub struct UdpSockRef<'a>(socket2::SockRef<'a>); -#[cfg(unix)] +#[cfg(all(unix, feature = "network"))] impl<'s, S> From<&'s S> for UdpSockRef<'s> where S: AsFd, @@ -194,7 +200,7 @@ where } } -#[cfg(windows)] +#[cfg(all(windows, feature = "network"))] impl<'s, S> From<&'s S> for UdpSockRef<'s> where S: AsSocket, diff --git a/quinn/Cargo.toml b/quinn/Cargo.toml index 713fb6733..22671264f 100644 --- a/quinn/Cargo.toml +++ b/quinn/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["quic"] categories = [ "network-programming", "asynchronous" ] workspace = ".." edition = "2021" -rust-version = "1.63" +rust-version = "1.73" [package.metadata.docs.rs] all-features = true @@ -20,11 +20,14 @@ default = ["native-certs", "tls-rustls", "runtime-tokio", "log"] lock_tracking = [] # Provides `ClientConfig::with_native_roots()` convenience method native-certs = ["proto/native-certs"] -tls-rustls = ["rustls", "proto/tls-rustls", "ring"] +tls-rustls = ["rustls", "proto/tls-rustls", "ring", "rustls-pki-types"] # Enables `Endpoint::client` and `Endpoint::server` conveniences ring = ["proto/ring"] -runtime-tokio = ["tokio/time", "tokio/rt", "tokio/net"] -runtime-async-std = ["async-io", "async-std"] +runtime-tokio = ["tokio/time", "tokio/rt", "tokio/net", "udp/network"] +runtime-async-std = ["async-io", "async-std", "udp/network"] +runtime-wasm = ["dep:wasmtimer", "dep:wasm-bindgen-futures"] +wasm = ["getrandom/js", "proto/wasm", "ring?/wasm32_unknown_unknown_js", "rustls-pki-types?/web"] + # Write logs via the `log` crate when no `tracing` subscriber exists log = ["tracing/log", "proto/log", "udp/log"] @@ -41,11 +44,16 @@ futures-io = { version = "0.3.19", optional = true } rustc-hash = "1.1" pin-project-lite = "0.2" proto = { package = "iroh-quinn-proto", path = "../quinn-proto", version = "0.10.7", default-features = false } -rustls = { version = "0.21.0", default-features = false, features = ["quic"], optional = true } +rustls = { git = "https://github.com/matheus23/rustls", branch = "0.21-wasm", default-features = false, features = ["quic"], optional = true } thiserror = "1.0.21" tracing = "0.1.10" tokio = { version = "1.28.1", features = ["sync"] } udp = { package = "iroh-quinn-udp", path = "../quinn-udp", version = "0.4", default-features = false } +getrandom = { version = "0.2", default-features = false } +ring = { version = "0.17", optional = true } +rustls-pki-types = { version = "1.7", optional = true } +wasmtimer = { version = "0.2", optional = true } +wasm-bindgen-futures = { version = "0.4", optional = true } [dev-dependencies] anyhow = "1.0.22" diff --git a/quinn/src/connection.rs b/quinn/src/connection.rs index 43c4e6674..4abe6ee7d 100644 --- a/quinn/src/connection.rs +++ b/quinn/src/connection.rs @@ -6,13 +6,15 @@ use std::{ pin::Pin, sync::{Arc, Weak}, task::{Context, Poll, Waker}, - time::{Duration, Instant}, }; use crate::runtime::{AsyncTimer, Runtime}; use bytes::Bytes; use pin_project_lite::pin_project; -use proto::{ConnectionError, ConnectionHandle, ConnectionStats, Dir, StreamEvent, StreamId}; +use proto::{ + ConnectionError, ConnectionHandle, ConnectionStats, Dir, Duration, Instant, StreamEvent, + StreamId, +}; use rustc_hash::FxHashMap; use thiserror::Error; use tokio::sync::{futures::Notified, mpsc, oneshot, Notify}; diff --git a/quinn/src/endpoint.rs b/quinn/src/endpoint.rs index 45a80ea7c..cace91bd9 100644 --- a/quinn/src/endpoint.rs +++ b/quinn/src/endpoint.rs @@ -9,14 +9,16 @@ use std::{ str, sync::{Arc, Mutex}, task::{Context, Poll, Waker}, - time::Instant, }; -use crate::runtime::{default_runtime, AsyncUdpSocket, Runtime}; +#[cfg(not(feature = "wasm"))] +use crate::runtime::default_runtime; +use crate::runtime::{AsyncUdpSocket, Runtime}; use bytes::{Bytes, BytesMut}; use pin_project_lite::pin_project; use proto::{ - self as proto, ClientConfig, ConnectError, ConnectionHandle, DatagramEvent, ServerConfig, + self as proto, ClientConfig, ConnectError, ConnectionHandle, DatagramEvent, Instant, + ServerConfig, }; use rustc_hash::FxHashMap; use tokio::sync::{futures::Notified, mpsc, Notify}; @@ -51,7 +53,7 @@ impl Endpoint { /// IPv6 address on Windows will not by default be able to communicate with IPv4 /// addresses. Portable applications should bind an address that matches the family they wish to /// communicate within. - #[cfg(feature = "ring")] + #[cfg(all(feature = "ring", not(feature = "wasm")))] pub fn client(addr: SocketAddr) -> io::Result { let socket = std::net::UdpSocket::bind(addr)?; let runtime = default_runtime() @@ -70,7 +72,7 @@ impl Endpoint { /// IPv6 address on Windows will not by default be able to communicate with IPv4 /// addresses. Portable applications should bind an address that matches the family they wish to /// communicate within. - #[cfg(feature = "ring")] + #[cfg(all(feature = "ring", not(feature = "wasm")))] pub fn server(config: ServerConfig, addr: SocketAddr) -> io::Result { let socket = std::net::UdpSocket::bind(addr)?; let runtime = default_runtime() @@ -84,6 +86,7 @@ impl Endpoint { } /// Construct an endpoint with arbitrary configuration and socket + #[cfg(not(feature = "wasm"))] pub fn new( config: EndpointConfig, server_config: Option, @@ -203,6 +206,7 @@ impl Endpoint { /// connections and connections to servers unreachable from the new address will be lost. /// /// On error, the old UDP socket is retained. + #[cfg(not(feature = "wasm"))] pub fn rebind(&self, socket: std::net::UdpSocket) -> io::Result<()> { let addr = socket.local_addr()?; let socket = self.runtime.wrap_udp_socket(socket)?; diff --git a/quinn/src/lib.rs b/quinn/src/lib.rs index 20befa731..604555865 100644 --- a/quinn/src/lib.rs +++ b/quinn/src/lib.rs @@ -41,8 +41,6 @@ #![warn(unreachable_pub)] #![warn(clippy::use_self)] -use std::time::Duration; - macro_rules! ready { ($e:expr $(,)?) => { match $e { @@ -62,7 +60,7 @@ mod work_limiter; pub use proto::{ congestion, crypto, ApplicationClose, Chunk, ClientConfig, ConfigError, ConnectError, - ConnectionClose, ConnectionError, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, + ConnectionClose, ConnectionError, Duration, EndpointConfig, IdleTimeout, MtuDiscoveryConfig, ServerConfig, StreamId, Transmit, TransportConfig, VarInt, }; pub use udp; @@ -77,6 +75,8 @@ pub use crate::recv_stream::{ReadError, ReadExactError, ReadToEndError, RecvStre pub use crate::runtime::AsyncStdRuntime; #[cfg(feature = "runtime-tokio")] pub use crate::runtime::TokioRuntime; +#[cfg(all(target_family = "wasm", feature = "runtime-wasm"))] +pub use crate::runtime::WasmRuntime; pub use crate::runtime::{default_runtime, AsyncTimer, AsyncUdpSocket, Runtime}; pub use crate::send_stream::{SendStream, StoppedError, WriteError}; diff --git a/quinn/src/mutex.rs b/quinn/src/mutex.rs index dc276d118..c29944971 100644 --- a/quinn/src/mutex.rs +++ b/quinn/src/mutex.rs @@ -6,10 +6,8 @@ use std::{ #[cfg(feature = "lock_tracking")] mod tracking { use super::*; - use std::{ - collections::VecDeque, - time::{Duration, Instant}, - }; + use proto::{Duration, Instant}; + use std::collections::VecDeque; use tracing::warn; #[derive(Debug)] diff --git a/quinn/src/runtime.rs b/quinn/src/runtime.rs index 9bcb73600..2881bf5d1 100644 --- a/quinn/src/runtime.rs +++ b/quinn/src/runtime.rs @@ -6,9 +6,9 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::Instant, }; +use proto::Instant; use udp::{RecvMeta, Transmit, UdpState}; /// Abstracts I/O and timer operations for runtime independence @@ -18,6 +18,7 @@ pub trait Runtime: Send + Sync + Debug + 'static { /// Drive `future` to completion in the background fn spawn(&self, future: Pin + Send>>); /// Convert `t` into the socket type used by this runtime + #[cfg(not(feature = "wasm"))] fn wrap_udp_socket(&self, t: std::net::UdpSocket) -> io::Result>; } @@ -65,6 +66,7 @@ pub trait AsyncUdpSocket: Send + Debug + 'static { /// If `runtime-tokio` is enabled and this function is called from within a Tokio runtime context, /// then `TokioRuntime` is returned. Otherwise, if `runtime-async-std` is enabled, `AsyncStdRuntime` /// is returned. Otherwise, `None` is returned. +#[allow(unreachable_code)] pub fn default_runtime() -> Option> { #[cfg(feature = "runtime-tokio")] { @@ -77,8 +79,11 @@ pub fn default_runtime() -> Option> { { return Some(Arc::new(AsyncStdRuntime)); } + #[cfg(all(target_family = "wasm", feature = "wasm"))] + { + return Some(Arc::new(WasmRuntime)); + } - #[cfg(not(feature = "runtime-async-std"))] None } @@ -91,3 +96,7 @@ pub use self::tokio::TokioRuntime; mod async_std; #[cfg(feature = "runtime-async-std")] pub use self::async_std::AsyncStdRuntime; +#[cfg(all(target_family = "wasm", feature = "wasm"))] +mod wasm; +#[cfg(all(target_family = "wasm", feature = "wasm"))] +pub use self::wasm::WasmRuntime; diff --git a/quinn/src/runtime/async_std.rs b/quinn/src/runtime/async_std.rs index da01201b7..eaf062394 100644 --- a/quinn/src/runtime/async_std.rs +++ b/quinn/src/runtime/async_std.rs @@ -23,6 +23,7 @@ impl Runtime for AsyncStdRuntime { async_std::task::spawn(future); } + #[cfg(not(feature = "wasm"))] fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result> { udp::UdpSocketState::configure((&sock).into())?; Ok(Box::new(UdpSocket { diff --git a/quinn/src/runtime/tokio.rs b/quinn/src/runtime/tokio.rs index d8ac4ac10..b0fb3081c 100644 --- a/quinn/src/runtime/tokio.rs +++ b/quinn/src/runtime/tokio.rs @@ -1,17 +1,13 @@ use std::{ future::Future, - io, pin::Pin, task::{Context, Poll}, time::Instant, }; -use tokio::{ - io::Interest, - time::{sleep_until, Sleep}, -}; +use tokio::time::{sleep_until, Sleep}; -use super::{AsyncTimer, AsyncUdpSocket, Runtime}; +use super::{AsyncTimer, Runtime}; /// A Quinn runtime for Tokio #[derive(Debug)] @@ -26,7 +22,11 @@ impl Runtime for TokioRuntime { tokio::spawn(future); } - fn wrap_udp_socket(&self, sock: std::net::UdpSocket) -> io::Result> { + #[cfg(not(feature = "wasm"))] + fn wrap_udp_socket( + &self, + sock: std::net::UdpSocket, + ) -> std::io::Result> { udp::UdpSocketState::configure((&sock).into())?; Ok(Box::new(UdpSocket { io: tokio::net::UdpSocket::from_std(sock)?, @@ -45,23 +45,25 @@ impl AsyncTimer for Sleep { } #[derive(Debug)] +#[cfg(not(feature = "wasm"))] struct UdpSocket { io: tokio::net::UdpSocket, inner: udp::UdpSocketState, } -impl AsyncUdpSocket for UdpSocket { +#[cfg(not(feature = "wasm"))] +impl super::AsyncUdpSocket for UdpSocket { fn poll_send( &self, state: &udp::UdpState, cx: &mut Context, transmits: &[udp::Transmit], - ) -> Poll> { + ) -> Poll> { let inner = &self.inner; let io = &self.io; loop { ready!(io.poll_send_ready(cx))?; - if let Ok(res) = io.try_io(Interest::WRITABLE, || { + if let Ok(res) = io.try_io(tokio::io::Interest::WRITABLE, || { inner.send(io.into(), state, transmits) }) { return Poll::Ready(Ok(res)); @@ -74,10 +76,10 @@ impl AsyncUdpSocket for UdpSocket { cx: &mut Context, bufs: &mut [std::io::IoSliceMut<'_>], meta: &mut [udp::RecvMeta], - ) -> Poll> { + ) -> Poll> { loop { ready!(self.io.poll_recv_ready(cx))?; - if let Ok(res) = self.io.try_io(Interest::READABLE, || { + if let Ok(res) = self.io.try_io(tokio::io::Interest::READABLE, || { self.inner.recv((&self.io).into(), bufs, meta) }) { return Poll::Ready(Ok(res)); @@ -85,7 +87,7 @@ impl AsyncUdpSocket for UdpSocket { } } - fn local_addr(&self) -> io::Result { + fn local_addr(&self) -> std::io::Result { self.io.local_addr() } diff --git a/quinn/src/runtime/wasm.rs b/quinn/src/runtime/wasm.rs new file mode 100644 index 000000000..b82a436df --- /dev/null +++ b/quinn/src/runtime/wasm.rs @@ -0,0 +1,46 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use wasmtimer::tokio::{sleep_until, Sleep}; + +use crate::{AsyncTimer, Runtime}; +use proto::Instant; + +#[cfg(feature = "wasm")] +fn instant_to_wasmtimer_instant(input: Instant) -> wasmtimer::std::Instant { + let now = Instant::now(); + let remaining = input.checked_duration_since(now).unwrap_or_default(); + let target = wasmtimer::std::Instant::now() + remaining; + target +} + +/// A runtime for WASM browser. +#[derive(Debug)] +pub struct WasmRuntime; + +impl Runtime for WasmRuntime { + fn new_timer(&self, t: Instant) -> Pin> { + let t = instant_to_wasmtimer_instant(t); + Box::pin(MySleep(Box::pin(sleep_until(t.into())))) + } + + fn spawn(&self, future: Pin + Send>>) { + wasm_bindgen_futures::spawn_local(future); + } +} + +#[derive(Debug)] +struct MySleep(Pin>); + +impl AsyncTimer for MySleep { + fn reset(mut self: Pin<&mut Self>, t: Instant) { + let t = instant_to_wasmtimer_instant(t); + Sleep::reset(self.0.as_mut(), t.into()) + } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { + Future::poll(self.0.as_mut(), cx) + } +} diff --git a/quinn/src/work_limiter.rs b/quinn/src/work_limiter.rs index 3ea1b1daf..7ed0fdbbc 100644 --- a/quinn/src/work_limiter.rs +++ b/quinn/src/work_limiter.rs @@ -1,4 +1,4 @@ -use std::time::{Duration, Instant}; +use proto::{Duration, Instant}; /// Limits the amount of time spent on a certain type of work in a cycle /// diff --git a/quinn/tests/many_connections.rs b/quinn/tests/many_connections.rs index 1a18e6c5b..738bf5dfb 100644 --- a/quinn/tests/many_connections.rs +++ b/quinn/tests/many_connections.rs @@ -2,11 +2,11 @@ use std::{ convert::TryInto, sync::{Arc, Mutex}, - time::Duration, }; use crc::Crc; use iroh_quinn::{ConnectionError, ReadError, TransportConfig, WriteError}; +use proto::Duration; use rand::{self, RngCore}; use tokio::runtime::Builder;