diff --git a/Cargo.lock b/Cargo.lock
index 7bd01a7c9a..f1e1e7ed5f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1718,7 +1718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c54e03a951783e8b327515db3f2a2fd0e3bed362a96b066f341ce66ed49b4ead"
dependencies = [
"data-encoding",
- "syn 1.0.109",
+ "syn 3.0.6",
]
[[package]]
@@ -2186,7 +2186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4532,7 +4532,7 @@ dependencies = [
"pin-project-lite",
"rustc-hash 2.1.3",
"rustls",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"thiserror 2.0.21",
"tokio",
"tokio-stream",
@@ -4558,7 +4558,6 @@ dependencies = [
"n0-qlog",
"rand 0.10.3",
"rand_pcg",
- "ring",
"rustc-hash 2.1.3",
"rustls",
"rustls-pki-types",
@@ -4579,7 +4578,7 @@ checksum = "b8dc8cf6fa35ba82ba89a4db24661068a75b7432abfb1ff3d74993dedfc2c9f0"
dependencies = [
"cfg_aliases",
"libc",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"tracing",
"windows-sys 0.61.2",
]
@@ -4785,6 +4784,7 @@ dependencies = [
"anyhow",
"bytes",
"chrono",
+ "criterion",
"dns-lookup",
"flume",
"futures",
@@ -4825,8 +4825,8 @@ dependencies = [
"url",
"usage-rs",
"web-transport-iroh",
- "web-transport-moq",
- "web-transport-proto",
+ "web-transport-proto 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-transport-quinn",
"web-transport-trait",
"webpki-roots",
"x509-parser",
@@ -4881,7 +4881,7 @@ dependencies = [
"tracing",
"url",
"web-transport-moq",
- "web-transport-proto",
+ "web-transport-proto 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"web-transport-trait",
]
@@ -5411,7 +5411,7 @@ dependencies = [
"pin-project-lite",
"rustc-hash 2.1.3",
"rustls",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"thiserror 2.0.21",
"tokio",
"tokio-stream",
@@ -5454,7 +5454,7 @@ checksum = "4dc50afea61aff926e150a36c31f06094608848e114a3fe667d76ec233163b1c"
dependencies = [
"cfg_aliases",
"libc",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"tracing",
"windows-sys 0.61.2",
]
@@ -6845,6 +6845,18 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "qlog"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f15b83c59e6b945f2261c95a1dd9faf239187f32ff0a96af1d1d28c4557f919"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serde_with",
+ "smallvec",
+]
+
[[package]]
name = "qmux"
version = "0.6.1"
@@ -6860,7 +6872,7 @@ dependencies = [
"tokio-rustls",
"tokio-tungstenite 0.30.0",
"tracing",
- "web-transport-proto",
+ "web-transport-proto 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"web-transport-trait",
]
@@ -6895,7 +6907,7 @@ dependencies = [
"quinn-udp",
"rustc-hash 2.1.3",
"rustls",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"thiserror 2.0.21",
"tokio",
"tracing",
@@ -6910,14 +6922,17 @@ checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc"
dependencies = [
"aws-lc-rs",
"bytes",
+ "fastbloom",
"getrandom 0.4.3",
"lru-slab",
+ "qlog",
"rand 0.10.3",
"rand_pcg",
"ring",
"rustc-hash 2.1.3",
"rustls",
"rustls-pki-types",
+ "rustls-platform-verifier",
"slab",
"thiserror 2.0.21",
"tinyvec",
@@ -6934,9 +6949,9 @@ dependencies = [
"cfg_aliases",
"libc",
"once_cell",
- "socket2 0.5.10",
+ "socket2 0.6.5",
"tracing",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -7378,7 +7393,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.12.1",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -7437,7 +7452,7 @@ dependencies = [
"security-framework",
"security-framework-sys",
"webpki-root-certs",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -8046,6 +8061,9 @@ name = "smallvec"
version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
+dependencies = [
+ "serde",
+]
[[package]]
name = "smawk"
@@ -8590,10 +8608,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
- "getrandom 0.3.4",
+ "getrandom 0.4.3",
"once_cell",
"rustix 1.1.5",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -9861,7 +9879,7 @@ dependencies = [
"tokio",
"tracing",
"url",
- "web-transport-proto",
+ "web-transport-proto 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"web-transport-trait",
]
@@ -9882,7 +9900,7 @@ dependencies = [
"tokio",
"tracing",
"url",
- "web-transport-proto",
+ "web-transport-proto 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"web-transport-trait",
]
@@ -9900,11 +9918,43 @@ dependencies = [
"url",
]
+[[package]]
+name = "web-transport-proto"
+version = "0.6.2"
+source = "git+https://github.com/moq-dev/web-transport?rev=c979d8c237753fae08086acd8c357e6ae385739a#c979d8c237753fae08086acd8c357e6ae385739a"
+dependencies = [
+ "bytes",
+ "http",
+ "sfv",
+ "thiserror 2.0.21",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "web-transport-quinn"
+version = "0.13.0"
+source = "git+https://github.com/moq-dev/web-transport?rev=c979d8c237753fae08086acd8c357e6ae385739a#c979d8c237753fae08086acd8c357e6ae385739a"
+dependencies = [
+ "bytes",
+ "futures",
+ "http",
+ "kio 0.5.9",
+ "quinn",
+ "rustls",
+ "rustls-native-certs",
+ "thiserror 2.0.21",
+ "tokio",
+ "tracing",
+ "url",
+ "web-transport-proto 0.6.2 (git+https://github.com/moq-dev/web-transport?rev=c979d8c237753fae08086acd8c357e6ae385739a)",
+ "web-transport-trait",
+]
+
[[package]]
name = "web-transport-trait"
version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f1e801f997864ab0074afb1c7d3262df46d5d9ad9b78274c47694544db2dba7"
+source = "git+https://github.com/moq-dev/web-transport?rev=c979d8c237753fae08086acd8c357e6ae385739a#c979d8c237753fae08086acd8c357e6ae385739a"
dependencies = [
"bytes",
]
@@ -10172,7 +10222,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 28369f6eee..9b64a30737 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -160,7 +160,7 @@ moq-rtmp = { version = "0.3.9", path = "rs/moq-rtmp" }
moq-sock = { version = "0.2.0", path = "rs/moq-sock" }
moq-srt = { version = "0.3.9", path = "rs/moq-srt" }
moq-stats = { version = "0.2.9", path = "rs/moq-stats" }
-moq-tokio = { version = "0.19.20", path = "rs/moq-tokio", default-features = false }
+moq-tokio = { version = "0.19.20", path = "rs/moq-tokio", default-features = false, features = ["aws-lc-rs"] }
# Default features off on moq-transcode and moq-video so each workspace consumer
# chooses native codecs, OpenH264, and rendering explicitly. Both crates still
# provide working native plus software defaults when depended on directly.
@@ -229,17 +229,20 @@ usage = { package = "usage-rs", version = "6.11.1" }
uuid = { version = "1.26", features = ["v7"] }
web-async = { version = "0.1.5", features = ["tracing"] }
web-transport-iroh = "0.8"
-# default-features off so the QUIC crypto provider is chosen by moq-tokio's
-# aws-lc-rs / ring features.
-# The WebTransport adapter released with moq-noq, from the same repository.
+# The WebTransport adapter used by the experimental io_uring backend.
web-transport-moq = { version = "2.0", default-features = false }
web-transport-proto = "0.6"
+web-transport-quinn = { version = "0.13", default-features = false }
web-transport-trait = "0.5"
web-transport-wasm = "0.7"
x11rb = { version = "0.14.0", features = ["randr", "xfixes"] }
zeroize = { version = "1", features = ["derive"] }
[patch.crates-io]
+# Preserve raw QUIC close codes until moq-dev/web-transport#409 is released.
+web-transport-quinn = { git = "https://github.com/moq-dev/web-transport", rev = "c979d8c237753fae08086acd8c357e6ae385739a" }
+# The adapter's workspace dependency must share the trait identity with registry consumers.
+web-transport-trait = { git = "https://github.com/moq-dev/web-transport", rev = "c979d8c237753fae08086acd8c357e6ae385739a" }
# web-transport-iroh depends on kio from crates.io. Without
# this, the lock carries a second kio whose pin drifts behind the member, and the
# next publish that reaches both fails to resolve a single version (#3551).
diff --git a/doc/bin/cli.md b/doc/bin/cli.md
index 3f22497a27..e8bfba67c2 100644
--- a/doc/bin/cli.md
+++ b/doc/bin/cli.md
@@ -162,7 +162,7 @@ behind the `play` feature, since it pulls in windowing and audio-device
dependencies:
```bash
-cargo install moq-cli --no-default-features --features "iroh,noq,websocket,play"
+cargo install moq-cli --no-default-features --features "iroh,websocket,play"
```
## Capture
diff --git a/doc/bin/relay/config.md b/doc/bin/relay/config.md
index 209b8032e3..add00f679b 100644
--- a/doc/bin/relay/config.md
+++ b/doc/bin/relay/config.md
@@ -52,8 +52,8 @@ send_window = 33554432
qlog = "/var/log/moq/qlog" # Existing directory. Needs the `qlog` build feature.
```
-The native QUIC stack uses BBRv3 for delay-based congestion control. Iroh also
-uses noq and the same congestion controller.
+The native QUIC stack always uses Quinn, with BBRv1 for delay-based
+congestion control. Iroh and the experimental io\_uring runtime use noq and BBRv3.
Raise the receive windows when a fat, long path idles below the link rate: a
window under the bandwidth-delay product stalls the sender waiting for credit.
@@ -79,10 +79,7 @@ io_uring = false # Drive them with io_uring instead of tokio
Packets are steered by connection ID, so a client that migrates stays with its
worker. The group shares one port, including an ephemeral (zero) port, which is
resolved once and joined by every worker. Use an explicit port unless
-something reads the bound address at startup. `workers` needs the `noq`
-feature and real certificate files rather than `tls.generate`. A build without
-QUIC rejects `workers` instead of
-ignoring it. An embedding process leaves this group inside `Relay::run`;
+something reads the bound address at startup. `workers` needs real certificate files rather than `tls.generate`. An embedding process leaves this group inside `Relay::run`;
taking the sockets out and driving them yourself is how a later library
update can drop QUIC while still compiling. `io_uring` additionally needs Linux 6.12+, the `io-uring` cargo
feature, and exactly one certificate read at startup; it serves moq-lite only,
diff --git a/doc/lib/rs/index.md b/doc/lib/rs/index.md
index 48bf90f55d..4d55c0b741 100644
--- a/doc/lib/rs/index.md
+++ b/doc/lib/rs/index.md
@@ -14,7 +14,7 @@ The reference implementation. Every crate is on
| --- | --- |
| [moq-net](/lib/rs/moq-net) | The pub/sub layer: sessions, origins, broadcasts, tracks, groups, frames. Transport-agnostic. |
| [moq-pattern](https://docs.rs/moq-pattern) | Exact path patterns: grammar, matching, and set algebra. Re-exported by moq-net and moq-auth. |
-| [moq-tokio](https://docs.rs/moq-tokio) | Stands up QUIC with noq, TLS, WebSocket fallback, and iroh, from config or CLI flags. |
+| [moq-tokio](https://docs.rs/moq-tokio) | Stands up QUIC with Quinn, TLS, WebSocket fallback, and iroh, from config or CLI flags. |
| [moq-sock](https://docs.rs/moq-sock) | Dual-stack socket binding, `SO_REUSEPORT` groups steered by QUIC connection ID, and CPU pinning for thread-per-core listeners. |
| [moq-uring](https://docs.rs/moq-uring) | Experimental Linux io\_uring worker: one pinned thread per ring serving moq-lite over its own QUIC stack. |
| [hang](/lib/rs/hang) | The media layer: catalog, containers, ordered frame delivery. |
diff --git a/doc/lib/rs/moq-net.md b/doc/lib/rs/moq-net.md
index cb2975a206..44b90ef92b 100644
--- a/doc/lib/rs/moq-net.md
+++ b/doc/lib/rs/moq-net.md
@@ -25,7 +25,7 @@ above ([hang](/lib/rs/hang)); relays and CDNs implement only this.
- **Routes** record the relay hops and a cost, which is what the relay [cluster](/bin/relay/cluster) routes on. A hop of 0 marks the chain anonymous: `Route::is_anonymous()` is true, and that route ranks below every fully identified one. `Route::source()` says where a delivered route entered: `Source::Local`, or `Source::Peer(hop)` when a handle marked `origin::Producer::peer()` announced it. `origin::Consumer::local()` sees only the local ones.
- **Stats** counters per broadcast and session, drained by [`moq-stats`](https://docs.rs/moq-stats).
-It runs over anything implementing `web_transport_trait::poll::Session`: noq, the
+It runs over anything implementing `web_transport_trait::poll::Session`: Quinn, noq, the
browser, iroh, or qmux over TCP, Unix sockets, and
WebSockets. [`moq-tokio`](https://docs.rs/moq-tokio) wires those up.
diff --git a/doc/setup/upgrade.md b/doc/setup/upgrade.md
index 149cb99f86..f59cc11505 100644
--- a/doc/setup/upgrade.md
+++ b/doc/setup/upgrade.md
@@ -134,8 +134,10 @@ Other changes to a deployment:
frame. `moq auth serve --key` takes a file, not an https or JWKS URL.
`moq auth sign --root` is the token root and JS `verify --root` the dialed
path. `/.cluster*` roots are reserved. `--auth-public a,b` splits on commas.
-- **noq is the only QUIC stack** (#3811). The `quinn` and `quiche` cargo
- features and the backend setting are gone.
+- **Quinn is the native QUIC stack**. Remove `noq` and `quinn` from Cargo feature
+ lists; QUIC is always available. The backend setting and `quiche` feature
+ remain removed. Other moq-tokio features remain optional. Iroh and the
+ experimental io\_uring runtime still use noq.
- **Stats counters** are `*_started` / `*_ended` (`sessions_started`,
`announces_ended`, ...). This release still writes the old `announced` /
`*_closed` names beside them, so move consumers now.
diff --git a/rs/hang/Cargo.toml b/rs/hang/Cargo.toml
index 940ce9dea4..a8e6c115b2 100644
--- a/rs/hang/Cargo.toml
+++ b/rs/hang/Cargo.toml
@@ -33,7 +33,7 @@ features = ["from", "display", "debug"]
[dev-dependencies]
anyhow = { workspace = true }
moq-mux = { path = "../moq-mux" }
-moq-tokio = { path = "../moq-tokio", default-features = false, features = ["noq", "aws-lc-rs", "websocket"] }
+moq-tokio = { path = "../moq-tokio", default-features = false, features = ["aws-lc-rs", "websocket"] }
tokio = { workspace = true, features = ["macros", "fs"] }
tracing = { workspace = true }
url = { workspace = true }
diff --git a/rs/justfile b/rs/justfile
index c583c4e739..dfea3f45eb 100644
--- a/rs/justfile
+++ b/rs/justfile
@@ -153,10 +153,6 @@ wasm-fix *args:
audit:
cargo deny check --show-stats
-# Verify relay startup validation without a QUIC backend.
-relay-minimal:
- cargo nextest run --locked -p moq-relay --no-default-features --lib -E 'test(workers_require_a_quic_backend)'
-
# Outside nextest because the staticlib link alone outlasts its hang budget.
# Compile and run moq-c's C fixtures, and the C doc samples, against libmoq.a. Must run on Unix.
@@ -171,9 +167,8 @@ c-tests:
# Compile the workspace at the feature extremes.
features:
cargo check --locked --workspace {{ no_fuzz }} --all-targets --no-default-features
- cargo hack check --locked -p moq-tokio --each-feature --exclude-features _transport --features aws-lc-rs
+ cargo hack check --locked -p moq-tokio --each-feature --features aws-lc-rs
cargo check --locked -p moq-relay --no-default-features
- just rs relay-minimal
cargo check --locked -p moq-cli --no-default-features
cargo check --locked -p moq-bench --no-default-features
cargo check --locked -p moq-boy --no-default-features
diff --git a/rs/moq-bench/Cargo.toml b/rs/moq-bench/Cargo.toml
index 0de559a2e6..d5fd6fbed4 100644
--- a/rs/moq-bench/Cargo.toml
+++ b/rs/moq-bench/Cargo.toml
@@ -35,9 +35,8 @@ required-features = ["uring"]
doc = false
[features]
-default = ["noq", "websocket"]
+default = ["websocket"]
iroh = ["moq-tokio/iroh"]
-noq = ["moq-tokio/noq"]
uring = ["dep:moq-uring"]
websocket = ["moq-tokio/websocket"]
diff --git a/rs/moq-boy/Cargo.toml b/rs/moq-boy/Cargo.toml
index 53102aa218..3ad8d907c7 100644
--- a/rs/moq-boy/Cargo.toml
+++ b/rs/moq-boy/Cargo.toml
@@ -12,9 +12,8 @@ edition = "2024"
rust-version.workspace = true
[features]
-default = ["noq", "websocket"]
+default = ["websocket"]
jemalloc = ["moq-tokio/jemalloc"]
-noq = ["moq-tokio/noq"]
websocket = ["moq-tokio/websocket"]
[dependencies]
diff --git a/rs/moq-c/Cargo.toml b/rs/moq-c/Cargo.toml
index c7564df387..fd67711553 100644
--- a/rs/moq-c/Cargo.toml
+++ b/rs/moq-c/Cargo.toml
@@ -26,7 +26,7 @@ moq-audio = { workspace = true, features = ["aac"] }
moq-json = { workspace = true }
moq-mux = { workspace = true }
moq-net = { workspace = true }
-moq-tokio = { workspace = true, features = ["noq", "aws-lc-rs", "websocket", "tcp", "uds"] }
+moq-tokio = { workspace = true, features = ["aws-lc-rs", "websocket", "tcp", "uds"] }
# Select the OpenH264 fallback plus NVIDIA and VAAPI hardware codecs explicitly
# because workspace defaults are off. The hardware backends dlopen their driver;
# `vaapi` also runs bindgen, so Linux builds need libclang.
diff --git a/rs/moq-cli/Cargo.toml b/rs/moq-cli/Cargo.toml
index 1d93822e90..c664b72121 100644
--- a/rs/moq-cli/Cargo.toml
+++ b/rs/moq-cli/Cargo.toml
@@ -26,12 +26,11 @@ path = "src/main.rs"
doc = false
[features]
-default = ["iroh", "cluster-lan", "noq", "websocket", "nvidia"]
+default = ["iroh", "cluster-lan", "websocket", "nvidia"]
iroh = ["moq-tokio/iroh"]
# LAN discovery and meshing (`--cluster-lan`).
cluster-lan = ["moq-relay/cluster-lan"]
jemalloc = ["moq-tokio/jemalloc"]
-noq = ["moq-tokio/noq"]
# qlog trace capture (`--client-quic-qlog
`). Off by default: it compiles the
# qlog machinery into the enabled backends and is only wanted for debugging or
# congestion-control testing.
@@ -57,7 +56,7 @@ play = ["dep:moq-video", "moq-video/render", "dep:moq-audio", "moq-audio/playbac
# the matching moq-video feature, so it only bites when something else pulls in
# moq-video: `capture`, `transcode`, or `play`. For a build with no CUDA
# dependencies, drop it:
-# cargo build -p moq-cli --no-default-features --features "iroh,noq,websocket,capture"
+# cargo build -p moq-cli --no-default-features --features "iroh,websocket,capture"
nvidia = ["moq-video?/nvidia", "moq-transcode?/nvidia"]
# Compatibility aliases for the pre-consolidation names, kept so a manifest that
# already asks for the old split still resolves. Undocumented on purpose: the
@@ -99,7 +98,7 @@ moq-auth = { workspace = true, features = ["serve"] }
moq-hls = { workspace = true, features = ["server"] }
moq-mux = { workspace = true }
# Cluster, LAN mesh, and inbound `/.cluster` auth. default-features off so the
-# CLI's own feature set (iroh, noq, cluster-lan) is what selects them.
+# CLI's own feature set (iroh, cluster-lan) is what selects them.
moq-relay = { workspace = true }
moq-rtc = { workspace = true }
moq-rtmp = { workspace = true }
diff --git a/rs/moq-gst/src/lib.rs b/rs/moq-gst/src/lib.rs
index 57c518542e..d6989f3276 100644
--- a/rs/moq-gst/src/lib.rs
+++ b/rs/moq-gst/src/lib.rs
@@ -22,6 +22,7 @@ pub fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy() // Allow overriding with RUST_LOG
.add_directive("h2=warn".parse().unwrap())
+ .add_directive("quinn=info".parse().unwrap())
.add_directive("noq=info".parse().unwrap())
.add_directive("tracing::span=off".parse().unwrap())
.add_directive("tracing::span::active=off".parse().unwrap());
diff --git a/rs/moq-hls/Cargo.toml b/rs/moq-hls/Cargo.toml
index 122ad928b4..f63b6f4274 100644
--- a/rs/moq-hls/Cargo.toml
+++ b/rs/moq-hls/Cargo.toml
@@ -36,6 +36,6 @@ tracing = { workspace = true }
url = { workspace = true }
[dev-dependencies]
-moq-tokio = { path = "../moq-tokio", default-features = false, features = ["tcp", "noq", "aws-lc-rs"] }
+moq-tokio = { path = "../moq-tokio", default-features = false, features = ["tcp", "aws-lc-rs"] }
tokio = { workspace = true, features = ["test-util"] }
tower = { version = "0.5", features = ["util"] }
diff --git a/rs/moq-relay/Cargo.toml b/rs/moq-relay/Cargo.toml
index 2f77c34c96..cacecbd82c 100644
--- a/rs/moq-relay/Cargo.toml
+++ b/rs/moq-relay/Cargo.toml
@@ -25,16 +25,11 @@ doc = false
[features]
# A programmatic relay fixture with generated TLS and ephemeral ports.
test-support = []
-default = ["iroh", "cluster-lan", "noq", "websocket", "uds"]
+default = ["iroh", "cluster-lan", "websocket", "uds"]
iroh = ["moq-tokio/iroh"]
# Discover cluster peers on the LAN with mDNS (`--cluster-lan`).
cluster-lan = ["moq-tokio/mdns"]
jemalloc = ["moq-tokio/jemalloc"]
-noq = ["_quic", "moq-tokio/noq"]
-# The tokio QUIC listener, and the thread-per-core worker group behind
-# `runtime.workers`. Not one to enable on its own: it names no backend, so
-# moq-tokio would have nothing to bind a socket with.
-_quic = []
# qlog trace capture (`--quic-qlog `). Off by default: it compiles the
# qlog machinery into the enabled backends and is only wanted for debugging or
# congestion-control testing. Covers the io_uring workers too, so one feature
diff --git a/rs/moq-relay/src/relay.rs b/rs/moq-relay/src/relay.rs
index cdf037dac9..625419b441 100644
--- a/rs/moq-relay/src/relay.rs
+++ b/rs/moq-relay/src/relay.rs
@@ -93,7 +93,6 @@ pub struct Relay {
/// The thread-per-core QUIC workers, already bound and waiting to be split
/// and run. `None` unless `runtime.workers` is configured, in which case
/// `server` carries no QUIC listener of its own.
- #[cfg(feature = "_quic")]
workers: Option,
/// The io_uring QUIC workers, already bound and waiting for
/// [`serve`](crate::uring::Workers::serve). `None` unless both
@@ -158,15 +157,6 @@ impl Relay {
"runtime.io_uring needs moq-relay built with the `io-uring` feature"
);
- // Refused rather than ignored: a group is the whole point of the setting, and
- // silently serving from the shared runtime instead would look like it worked.
- #[cfg(not(feature = "_quic"))]
- anyhow::ensure!(
- io_uring || config.runtime.workers().is_none(),
- "runtime.workers needs moq-relay built with the `noq` feature"
- );
-
- #[cfg(feature = "_quic")]
let workers = match config.runtime.workers() {
Some(worker) if !io_uring => {
let mut server = moq_tokio::server::Config::default();
@@ -177,16 +167,10 @@ impl Relay {
_ => None,
};
- // What the rest of setup reads off the group, so it has one shape whether or
- // not a QUIC backend gave us one to read.
- #[cfg(feature = "_quic")]
+ // Read the worker endpoint before moving the group into the serve loop.
let workers_addr = workers.as_ref().map(moq_tokio::worker::Workers::local_addr);
- #[cfg(not(feature = "_quic"))]
- let workers_addr: Option = None;
- #[cfg(feature = "_quic")]
+
let workers_certificates = workers.as_ref().map(moq_tokio::worker::Workers::certificates);
- #[cfg(not(feature = "_quic"))]
- let workers_certificates: Option = None;
#[cfg(all(target_os = "linux", feature = "_uring"))]
let uring = match config.runtime.workers() {
@@ -326,7 +310,6 @@ impl Relay {
web_routes: None,
internal_routes: None,
sessions,
- #[cfg(feature = "_quic")]
workers,
#[cfg(all(target_os = "linux", feature = "_uring"))]
uring,
@@ -482,7 +465,6 @@ impl Relay {
web_routes,
internal_routes,
sessions,
- #[cfg(feature = "_quic")]
workers,
#[cfg(all(target_os = "linux", feature = "_uring"))]
uring,
@@ -548,13 +530,11 @@ impl Relay {
// reports the outcome. The group owns those threads and ends serving
// when the first member finishes, so it has to outlive the loop below
// and is torn down after it.
- #[cfg(feature = "_quic")]
let mut workers = workers.map(|workers| workers.split());
// Pends forever with no workers, so it composes into the `select!` either
// way. A worker only stops on error or on shutdown, so the first one to
// finish ends the relay the same way the shared accept loop does.
- #[cfg(feature = "_quic")]
let quic_workers = {
let mut running = futures::stream::FuturesUnordered::new();
if let Some(workers) = workers.as_mut() {
@@ -582,15 +562,7 @@ impl Relay {
}
}
};
- // No group to run, so this leg never resolves and the shared accept loop
- // below decides the `select!` on its own.
- #[cfg(not(feature = "_quic"))]
- let quic_workers = std::future::pending::>();
-
- #[cfg(feature = "_quic")]
let has_workers = workers.is_some();
- #[cfg(not(feature = "_quic"))]
- let has_workers = false;
// With the QUIC listener owned by a worker group, the shared server has
// only the `tcp`/`unix` stream listeners left, and a config with none
@@ -636,7 +608,6 @@ impl Relay {
// Explicitly, so the joins land on the blocking pool rather than on the
// executor thread this future happens to be running on.
- #[cfg(feature = "_quic")]
if let Some(workers) = workers {
workers.shutdown().await;
}
@@ -704,7 +675,6 @@ async fn shutdown_signal() -> anyhow::Result<()> {
///
/// The accept loop for a single [`moq_tokio::Server`]. [`Relay::run`] owns
/// worker selection and shutdown for embedders.
-#[cfg(feature = "_quic")]
async fn serve(
server: moq_tokio::Server,
cluster: cluster::Cluster,
@@ -740,20 +710,3 @@ async fn serve_listening(
anyhow::bail!("stopped accepting connections")
}
-
-#[cfg(all(test, not(feature = "_quic")))]
-mod tests {
- #[tokio::test]
- async fn workers_require_a_quic_backend() {
- let mut config = crate::Config::default();
- config.runtime.workers = Some(1);
- let error = match super::Relay::load(config).await {
- Ok(_) => panic!("workers accepted without a QUIC backend"),
- Err(error) => error,
- };
- assert_eq!(
- error.to_string(),
- "runtime.workers needs moq-relay built with the `noq` feature"
- );
- }
-}
diff --git a/rs/moq-relay/tests/drills.rs b/rs/moq-relay/tests/drills.rs
index 6ba7488364..11cc709cf5 100644
--- a/rs/moq-relay/tests/drills.rs
+++ b/rs/moq-relay/tests/drills.rs
@@ -21,7 +21,7 @@
//! See `test/drill/README.md` for the recipe, the sensitivity proof that each
//! drill fails when its recovery behavior is removed, and the loom/fuzz cases
//! covering the primitives underneath.
-#![cfg(all(feature = "noq", feature = "websocket"))]
+#![cfg(feature = "websocket")]
use std::time::Duration;
diff --git a/rs/moq-relay/tests/embed.rs b/rs/moq-relay/tests/embed.rs
index cc7055aae9..52407e006e 100644
--- a/rs/moq-relay/tests/embed.rs
+++ b/rs/moq-relay/tests/embed.rs
@@ -7,8 +7,6 @@
//! live QUIC subscriber, then stops the owner through its shutdown trigger
//! and proves `run` returned with its sockets closed.
-#![cfg(feature = "_quic")]
-
use std::net::{SocketAddr, TcpListener};
use std::time::Duration;
diff --git a/rs/moq-relay/tests/runtime_workers.rs b/rs/moq-relay/tests/runtime_workers.rs
index 97ce387de3..c9988dff7a 100644
--- a/rs/moq-relay/tests/runtime_workers.rs
+++ b/rs/moq-relay/tests/runtime_workers.rs
@@ -4,7 +4,7 @@
//! `SO_REUSEPORT`, and no other platform load-balances a unicast UDP port
//! across the group. There is also nothing to serve without a QUIC backend, so
//! a build without one refuses `runtime.workers` at load.
-#![cfg(all(target_os = "linux", feature = "_quic"))]
+#![cfg(target_os = "linux")]
use std::time::Duration;
diff --git a/rs/moq-rtmp/Cargo.toml b/rs/moq-rtmp/Cargo.toml
index a94613dbe3..d34340412e 100644
--- a/rs/moq-rtmp/Cargo.toml
+++ b/rs/moq-rtmp/Cargo.toml
@@ -56,6 +56,6 @@ tracing = { workspace = true }
# The RTMPS test borrows moq-tokio's self-signed cert generation to build a
# `rustls::ServerConfig`. No transport feature: a QUIC backend here would be
# unified into every other crate a `--workspace` build selects, which is exactly
-# what hid moq-tokio's backend-less build from the nightly feature matrix.
+# what would hide a missing provider from the isolated feature matrix.
moq-tokio = { path = "../moq-tokio", default-features = false, features = ["aws-lc-rs"] }
tokio = { workspace = true, features = ["test-util"] }
diff --git a/rs/moq-tokio/Cargo.toml b/rs/moq-tokio/Cargo.toml
index 4e1042c518..1bcc5ac68b 100644
--- a/rs/moq-tokio/Cargo.toml
+++ b/rs/moq-tokio/Cargo.toml
@@ -13,45 +13,25 @@ keywords = ["quic", "http3", "webtransport", "media", "live"]
categories = ["multimedia", "network-programming", "web-programming"]
[features]
-default = ["noq", "aws-lc-rs", "websocket", "tcp", "uds"]
-noq = ["_transport", "_certs", "dep:web-transport-moq", "dep:reqwest", "watch", "dep:dns-lookup"]
-# Server-side certificate handling: chains loaded from disk, self-signed generation,
-# in-memory identities, and per-SNI selection. Not one to enable on its own, since
-# nothing turns it into a listener: every QUIC backend needs it for its own TLS, and
-# so does the plain-TLS `tls::Listen::server_config` an RTMPS or HTTPS listener builds,
-# which is why the crypto-provider features below turn it on too.
-_certs = ["dep:rcgen", "dep:rustls-webpki"]
-# Private "any transport is compiled" marker. Not one to enable on its own: every
-# backend that can move bytes turns it on, and `uds` reaches it through `tcp`.
-_transport = []
-# Filesystem watcher for hot-reloading on-disk TLS certs, keys, and root CAs.
+default = ["aws-lc-rs", "watch", "websocket", "tcp", "uds"]
+# Filesystem watcher for hot-reloading TLS certificates, keys, and root CAs.
watch = ["dep:notify"]
-# Capture qlog traces (`--*-quic-qlog `). Off by default so production
-# builds do not pull in the qlog crate and its serde_json encoder.
-qlog = ["web-transport-moq?/qlog"]
-# The QUIC backend pulls in its crypto provider from these features (default-features
-# are off on the dependency), so a backend without aws-lc-rs/ring has no initial cipher.
-# A provider is also all it takes to sign and serve certificates, so each turns on
-# `_certs` even where there is no QUIC backend to serve them for.
-aws-lc-rs = ["_certs", "rustls/aws-lc-rs", "rcgen/aws_lc_rs", "web-transport-moq?/aws-lc-rs"]
-iroh = ["_transport", "dep:web-transport-iroh", "dep:web-transport-proto", "dep:noq-proto"]
-# LAN peer discovery over mDNS (the `mdns` module).
+qlog = ["web-transport-quinn/qlog"]
+aws-lc-rs = ["rustls/aws-lc-rs", "rcgen/aws_lc_rs", "web-transport-quinn/aws-lc-rs"]
+ring = ["rustls/ring", "rcgen/ring", "web-transport-quinn/ring"]
+iroh = ["dep:web-transport-iroh", "dep:web-transport-proto", "dep:noq-proto"]
mdns = ["dep:mdns-sd", "dep:hmac", "dep:sha2", "dep:subtle"]
jemalloc = ["dep:tikv-jemallocator", "dep:tikv-jemalloc-ctl"]
-websocket = ["_transport", "dep:qmux", "watch", "dep:dns-lookup"]
-# Plain-TCP qmux transport (`tcp://`), no TLS. Server-side and client-side.
-tcp = ["_transport", "dep:qmux", "dep:dns-lookup"]
-# Unix-domain-socket qmux transport (`unix://`), unix-only. Adds peer-credential
-# inspection on accept. Pulls in `tcp` (shares qmux's stream transport).
+websocket = ["dep:qmux", "watch"]
+tcp = ["dep:qmux"]
uds = ["tcp", "qmux/uds"]
-ring = ["_certs", "rustls/ring", "rcgen/ring", "web-transport-moq?/ring"]
android-logcat = ["dep:tracing-android"]
[dependencies]
bytes = "1"
# Family-restricted `getaddrinfo`, so the AAAA and A queries can be issued separately
# (RFC 8305 section 3) while still going through the system resolver.
-dns-lookup = { version = "4", optional = true }
+dns-lookup = { version = "4" }
futures = "0.3"
hex = { workspace = true }
hmac = { workspace = true, optional = true }
@@ -67,12 +47,12 @@ noq-proto = { workspace = true, optional = true }
notify = { version = "8", optional = true }
qmux = { workspace = true, features = ["wss", "tls", "tcp"], optional = true }
rand = "0.10.1"
-rcgen = { version = "0.14", default-features = false, optional = true }
-reqwest = { version = "0.13", default-features = false, optional = true }
+rcgen = { version = "0.14", default-features = false }
+reqwest = { version = "0.13", default-features = false }
rustls = "0.23"
# OS-native server-cert verifier for the rustls backend.
rustls-platform-verifier = "0.7"
-rustls-webpki = { version = "0.103", features = ["aws-lc-rs"], optional = true }
+rustls-webpki = { version = "0.103", features = ["aws-lc-rs"] }
serde = { version = "1", features = ["derive"] }
serde_with = { version = "3", features = ["hex"] }
sha2 = { workspace = true, optional = true }
@@ -88,8 +68,8 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
url = { version = "2", features = ["serde"] }
usage = { workspace = true, features = ["config"] }
web-transport-iroh = { workspace = true, optional = true }
-web-transport-moq = { workspace = true, optional = true }
web-transport-proto = { workspace = true, optional = true }
+web-transport-quinn = { workspace = true }
web-transport-trait = { workspace = true }
x509-parser = "0.18"
@@ -110,6 +90,7 @@ libc = { workspace = true }
anyhow = "1"
bytes = "1"
chrono = "0.4"
+criterion = { workspace = true }
# Use the channel type behind `mdns_sd::Receiver` so the mDNS readiness gate can
# be driven with synthetic daemon events.
flume = "0.12"
@@ -125,3 +106,7 @@ tracing-test = { workspace = true, features = ["no-env-filter"] }
# group closed its own without rebinding a port another test could take.
[target.'cfg(target_os = "linux")'.dev-dependencies]
socket2 = { workspace = true, features = ["all"] }
+
+[[bench]]
+name = "latency"
+harness = false
diff --git a/rs/moq-tokio/README.md b/rs/moq-tokio/README.md
index 4802a6760b..1f8d6b57fe 100644
--- a/rs/moq-tokio/README.md
+++ b/rs/moq-tokio/README.md
@@ -12,7 +12,7 @@ Tokio-based connection helpers for native [Media over QUIC](https://moq.dev) app
Establishes MoQ connections over a few different transports, selectable via cargo features:
-- **WebTransport** (HTTP/3) via [noq](https://crates.io/crates/noq)
+- **WebTransport** (HTTP/3) via [Quinn](https://crates.io/crates/quinn)
- **Raw QUIC** with ALPN negotiation
- **WebSocket** as a fallback when QUIC isn't available
- **Iroh** P2P (`iroh` feature)
@@ -41,3 +41,14 @@ including in one-shot mode. With mixed `Addrs`, the target that establishes the
session determines its redirect policy; unused pinned fallbacks do not restrict it.
To refresh DNS, resolve and apply your address policy again before creating a new
connection. Address filtering belongs to the caller.
+
+## Latency benchmark
+
+Measure loopback group delivery over raw QUIC and WebTransport across payload sizes:
+
+```bash
+cargo bench -p moq-tokio --bench latency
+```
+
+Quinn is always enabled. Iroh and the experimental io\_uring runtime still use noq.
+Other transports and diagnostics retain their optional Cargo features.
diff --git a/rs/moq-tokio/benches/latency.rs b/rs/moq-tokio/benches/latency.rs
new file mode 100644
index 0000000000..98aa8185eb
--- /dev/null
+++ b/rs/moq-tokio/benches/latency.rs
@@ -0,0 +1,111 @@
+//! Loopback group delivery latency through the selected native QUIC backend.
+
+use criterion::{BenchmarkId, Throughput};
+use criterion::{Criterion, criterion_group, criterion_main};
+use moq_tokio::{moq_net, origin};
+
+fn latency(c: &mut Criterion) {
+ let runtime = tokio::runtime::Builder::new_current_thread()
+ .enable_all()
+ .build()
+ .expect("runtime");
+ let mut bench = c.benchmark_group("quic_group_delivery");
+
+ for scheme in ["moqt", "https"] {
+ for size in [1200, 64 * 1024, 1024 * 1024] {
+ bench.throughput(Throughput::Bytes(size as u64));
+ bench.bench_with_input(BenchmarkId::new(scheme, size), &size, |b, &size| {
+ let (track, mut received, client, connection, serving) = runtime.block_on(async {
+ let publisher = origin::spawn();
+ let broadcast = publisher.create_broadcast("latency").expect("broadcast");
+ broadcast.announce(Default::default()).expect("announce");
+ let track = broadcast.create_track("data", None).expect("track");
+
+ let mut listen = moq_tokio::listen::Config::default();
+ listen.bind = Some("127.0.0.1:0".parse().unwrap());
+ listen.tls.generate = vec!["localhost".into()];
+ let mut server = listen
+ .init(Default::default())
+ .expect("server")
+ .listen()
+ .await
+ .expect("listen");
+ let addr = server.local_addr().expect("address");
+ let serving = tokio::spawn(async move {
+ let session = server
+ .accept()
+ .await
+ .expect("accept")
+ .with_publisher(&publisher)
+ .ok()
+ .await
+ .expect("handshake");
+ let _broadcast = broadcast;
+ let _ = session.closed().await;
+ });
+
+ let subscriber = origin::spawn();
+ let consumer = subscriber.consume();
+ let mut announcements = consumer.announced();
+ let mut connect = moq_tokio::connect::Config::default();
+ connect.bind = Some("127.0.0.1:0".parse().unwrap());
+ connect.tls.insecure = Some(true);
+ let client = connect
+ .init(Default::default())
+ .expect("client")
+ .with_subscriber(subscriber);
+ let url = url::Url::parse(&format!("{scheme}://localhost:{}", addr.port())).unwrap();
+ let connection = client
+ .clone()
+ .with_reconnect(false)
+ .connect(url)
+ .established()
+ .await
+ .expect("connect");
+ loop {
+ match announcements.next().await.expect("announcement") {
+ moq_net::announce::Event::Start(route) | moq_net::announce::Event::Update(route) => {
+ assert_eq!(route.prefix.as_str(), "latency");
+ break;
+ }
+ moq_net::announce::Event::Live => continue,
+ moq_net::announce::Event::End(_) => panic!("broadcast ended"),
+ }
+ }
+ let remote = consumer.request_broadcast("latency").await.expect("remote broadcast");
+ let received = remote.track("data").unwrap().subscribe(None).await.expect("subscribe");
+ (track, received, client, connection, serving)
+ });
+ let payload = vec![0x5a; size];
+ let mut sequence = 0;
+
+ b.iter_custom(|iterations| {
+ runtime.block_on(async {
+ let start = std::time::Instant::now();
+ for _ in 0..iterations {
+ let mut group = track.append_group().expect("group");
+ // Advance media time so the cache can evict previous groups.
+ let timestamp = moq_net::Timestamp::from_secs(sequence).expect("timestamp");
+ sequence += 1;
+ group.write_frame(timestamp, &payload).expect("write");
+ group.finish().expect("finish");
+ let mut group = received.recv_group().await.expect("receive").expect("group");
+ let frame = group.read_frame().await.expect("read").expect("frame");
+ assert_eq!(frame.payload.len(), size);
+ }
+ start.elapsed()
+ })
+ });
+ runtime.block_on(async {
+ drop(connection);
+ serving.await.expect("server task");
+ drop(client);
+ });
+ });
+ }
+ }
+ bench.finish();
+}
+
+criterion_group!(benches, latency);
+criterion_main!(benches);
diff --git a/rs/moq-tokio/src/client.rs b/rs/moq-tokio/src/client.rs
index 686fd6c6df..1477b116dc 100644
--- a/rs/moq-tokio/src/client.rs
+++ b/rs/moq-tokio/src/client.rs
@@ -6,7 +6,7 @@
use crate::connection::Goaway;
use crate::{Addrs, Backoff, Connection, Error};
-#[cfg(all(feature = "websocket", feature = "noq"))]
+#[cfg(feature = "websocket")]
use std::future::Future;
use url::Url;
@@ -46,18 +46,10 @@ pub struct Client {
/// every transport (passed into the QUIC backend's `connect` and used directly for
/// raw TCP/UDS qmux and WebSocket). Resolved once in [`Client::new`] so the ALPN list
/// can't diverge between transports.
- #[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- ))]
versions: moq_net::Versions,
/// The URL from [`connect.url`](crate::connect::Config::url), dialed by [`Client::publish`] / [`Client::consume`].
connect: Option,
/// Deadline for one [`Client::connect`], from [`crate::connect::Config::timeout`]. Zero waits forever.
- #[cfg(feature = "_transport")]
timeout: std::time::Duration,
pub(crate) reconnect: bool,
pub(crate) backoff: Backoff,
@@ -74,10 +66,8 @@ pub struct Client {
#[cfg(feature = "websocket")]
tls_host_name: Option,
/// Only the TLS-based dials read this; the plaintext qmux transports have none.
- #[cfg(any(feature = "noq", feature = "websocket"))]
tls: rustls::ClientConfig,
- #[cfg(feature = "noq")]
- noq: Option,
+ quic: crate::quinn::Client,
#[cfg(feature = "iroh")]
iroh: Option,
#[cfg(feature = "iroh")]
@@ -85,18 +75,7 @@ pub struct Client {
}
impl Client {
- /// Build a client from its config.
- ///
- /// Errors if no transport feature is compiled in.
- #[cfg(not(feature = "_transport"))]
- pub fn new(_config: Config) -> crate::Result {
- Err(Error::NoBackend(
- "no backend compiled; enable noq, iroh, websocket, tcp, or uds feature",
- ))
- }
-
/// Build a client from its config, binding the QUIC socket up front.
- #[cfg(feature = "_transport")]
pub fn new(config: Config) -> crate::Result {
let Config {
connect: config, quic, ..
@@ -112,13 +91,10 @@ impl Client {
quic.validate()?;
- // Only the rustls-backed transports use this. Iroh and the plaintext
- // qmux transports must not require a rustls crypto provider.
- #[cfg(any(feature = "noq", feature = "websocket"))]
+ // Quinn always uses the configured rustls provider.
let tls = config.tls.build()?;
- #[cfg(feature = "noq")]
- let noq = Some(crate::noq::NoqClient::new(&config, &quic)?);
+ let quic = crate::quinn::Client::new(&config, &quic)?;
let versions = config.versions();
// Read before the struct literal below moves fields out of `config`.
@@ -133,13 +109,6 @@ impl Client {
Ok(Self {
moq: moq_net::Client::new().with_versions(versions.clone()),
- #[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- ))]
versions,
connect: config.url,
timeout,
@@ -154,10 +123,8 @@ impl Client {
websocket: config.websocket,
#[cfg(feature = "websocket")]
tls_host_name,
- #[cfg(any(feature = "noq", feature = "websocket"))]
tls,
- #[cfg(feature = "noq")]
- noq,
+ quic,
#[cfg(feature = "iroh")]
iroh: None,
#[cfg(feature = "iroh")]
@@ -258,14 +225,11 @@ impl Client {
/// This closes at once, discarding stream data the peer has not acknowledged yet.
/// Call [`Connection::close`] on each connection first to deliver it.
///
- /// Only the noq endpoint is closed. WebSocket, TCP, and UDS sessions end when their
+ /// Only the QUIC endpoint is closed. WebSocket, TCP, and UDS sessions end when their
/// [`Connection`] is dropped (the kernel closes the socket on exit), and an iroh
/// endpoint passed to `with_iroh` is closed by its owner.
pub async fn close(self) {
- #[cfg(feature = "noq")]
- if let Some(noq) = self.noq {
- noq.close().await;
- }
+ self.quic.close().await;
}
/// Connect to the configured [`connect.url`](crate::connect::Config::url) URL, publishing
@@ -291,35 +255,12 @@ impl Client {
Some(self.with_subscriber(origin).connect(url))
}
- /// Dial the given URL and complete the MoQ handshake.
- ///
- /// Errors if no transport feature is compiled in.
- #[cfg(not(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- )))]
- pub(crate) async fn dial(&self, _addr: crate::connect::Addr) -> crate::Result {
- Err(Error::NoBackend(
- "no backend compiled; enable noq, iroh, websocket, tcp, or uds feature",
- ))
- }
-
/// Dial the given URL and complete the MoQ handshake.
///
/// The scheme picks the transport, and `https://` races QUIC against the
/// WebSocket fallback so a blocked UDP path still connects. The session's
/// protocol driver is spawned on the current tokio runtime; the session
/// closes once the last returned handle drops.
- #[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- ))]
pub(crate) async fn dial(&self, addr: crate::connect::Addr) -> crate::Result {
// Each compiled backend adds state to this dispatch future. Keep it off the
// caller's stack so all-feature builds remain safe on standard 2 MiB threads.
@@ -341,13 +282,6 @@ impl Client {
}
/// The moq client builder, advertising `path` in the SETUP when there is one.
- #[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- ))]
fn moq_with_path(&self, path: Option) -> moq_net::Client {
match path {
Some(path) => self.moq.clone().with_path(path),
@@ -355,13 +289,6 @@ impl Client {
}
}
- #[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
- ))]
async fn connect_inner(&self, addr: crate::connect::Addr) -> crate::Result {
let url = addr.url().clone();
// Transports with no request URI of their own advertise the request target in the
@@ -418,34 +345,24 @@ impl Client {
return Ok(connect_session(&moq, crate::transport::Session::new(session)).await?);
}
- #[cfg(feature = "noq")]
- if let Some(noq) = self.noq.as_ref() {
- let tls = self.tls.clone();
- let quic_addr = addr.clone();
- let quic_handle = async {
- noq.connect(&tls, quic_addr, &self.versions)
- .await
- .map(crate::transport::Session::new)
- .map_err(Error::from)
- };
-
- #[cfg(feature = "websocket")]
- {
- return self.race_moq_connect(&moq, addr, quic_handle).await;
- }
-
- #[cfg(not(feature = "websocket"))]
- {
- let session = quic_handle.await?;
- return Ok(connect_session(&moq, session).await?);
- }
- }
+ let tls = self.tls.clone();
+ let quic_addr = addr.clone();
+ let quic_handle = async {
+ self.quic
+ .connect(&tls, quic_addr, &self.versions)
+ .await
+ .map(crate::transport::Session::new)
+ .map_err(Error::from)
+ };
#[cfg(feature = "websocket")]
- return self.connect_websocket(addr).await;
+ return self.race_moq_connect(&moq, addr, quic_handle).await;
#[cfg(not(feature = "websocket"))]
- return Err(Error::NoBackend("no QUIC backend matched; this should not happen"));
+ {
+ let session = quic_handle.await?;
+ Ok(connect_session(&moq, session).await?)
+ }
}
/// Connect over WebSocket alone. qmux over WebSocket carries the path in its request
@@ -463,10 +380,7 @@ impl Client {
/// `moq` is the QUIC-side builder, which carries the SETUP path for a raw QUIC dial.
/// The WebSocket fallback uses the plain builder: qmux over WebSocket carries the
/// path in its request URI, so repeating it in the SETUP is a protocol violation.
- ///
- /// Only compiled when there is a QUIC dial to race: a WebSocket-only build connects
- /// over the fallback directly.
- #[cfg(all(feature = "websocket", feature = "noq"))]
+ #[cfg(feature = "websocket")]
async fn race_moq_connect(
&self,
moq: &moq_net::Client,
@@ -502,13 +416,6 @@ impl Client {
///
/// `None` when the result is empty, which means the same as omitting the parameter: the
/// server's default path. A peer on published lite-05 rejects an empty value outright.
-#[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
-))]
fn request_target(url: &Url) -> Option {
// A trailing `?` parses as an empty query, which is not a query: appending it would
// spell one target two ways, and `moqt://host?` would yield a bare "?" rather than
@@ -528,13 +435,6 @@ fn request_target(url: &Url) -> Option {
/// on top of it is a protocol violation. `iroh` is `None` here because its binding is
/// picked by ALPN negotiation rather than by the scheme; that dial reads the negotiated
/// [`crate::iroh::Binding`] and calls [`request_target`] itself.
-#[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
-))]
fn setup_path(url: &Url) -> Option {
match url.scheme() {
// A Unix socket URL's path is the socket file, so the request target rides in
@@ -557,13 +457,6 @@ fn setup_path(url: &Url) -> Option {
/// A raw-QUIC `moqt://` client MUST send it (draft-ietf-moq-transport-21, 9.1.1). Built
/// from the host and port so URL userinfo never goes on the wire. `None` for every other
/// scheme, and when the URL has no host, since an empty authority is meaningless.
-#[cfg(any(
- feature = "noq",
- feature = "iroh",
- feature = "websocket",
- feature = "tcp",
- feature = "uds"
-))]
fn setup_authority(url: &Url) -> Option {
if url.scheme() != "moqt" {
return None;
@@ -576,14 +469,14 @@ fn setup_authority(url: &Url) -> Option {
})
}
-#[cfg(all(feature = "websocket", feature = "noq"))]
+#[cfg(feature = "websocket")]
#[derive(Debug, PartialEq, Eq)]
enum TransportRace {
Quic(Q),
WebSocket(W),
}
-#[cfg(all(feature = "websocket", feature = "noq"))]
+#[cfg(feature = "websocket")]
async fn race_transport_connect(quic: Q, websocket: W) -> crate::Result>
where
Q: Future