Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,172 changes: 21 additions & 1,151 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,17 @@ usage = { package = "usage-rs", version = "6.3.0" }
web-async = { version = "0.1.5", features = ["tracing"] }
web-transport-iroh = "0.7"
# default-features off so the QUIC crypto provider is chosen by moq-tokio's
# aws-lc-rs / ring features (mirroring how the quinn dependency is wired).
# aws-lc-rs / ring features.
# 0.3 for the `qlog` passthrough feature moq-tokio's `qlog` forwards to.
web-transport-noq = { version = "0.3", default-features = false }
web-transport-proto = "0.6"
web-transport-quiche = "0.7"
web-transport-quinn = { version = "0.12", default-features = false }
web-transport-trait = "0.4"
web-transport-wasm = "0.6"
x11rb = { version = "0.14.0", features = ["randr", "xfixes"] }
zeroize = { version = "1", features = ["derive"] }

[patch.crates-io]
# web-transport-quinn and web-transport-iroh depend on kio from crates.io. Without
# 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).
kio = { path = "rs/kio" }
Expand Down
2 changes: 1 addition & 1 deletion bench/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ runtime $ROUNDS="3" $WORKERS="":
# Compile every binary and feature used by the repository-level harness.
check:
cargo check --locked --package moq-relay --package moq-bench \
--features moq-relay/io-uring-quinn
--features moq-relay/io-uring
4 changes: 2 additions & 2 deletions bench/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -394,10 +394,10 @@ run_runtime_comparison() {
return 1
fi

printf 'Building one relay binary with the Tokio and io_uring Quinn paths...\n'
printf 'Building one relay binary with the Tokio and io_uring paths...\n'
CARGO_TARGET_DIR=$CURRENT_TARGET cargo build --locked --release \
-p moq-relay -p moq-bench \
--features moq-relay/io-uring-quinn,moq-bench/uring
--features moq-relay/io-uring,moq-bench/uring
LOAD_BIN=$CURRENT_TARGET/release/moq-bench
HOST_BIN=$CURRENT_TARGET/release/moq-bench-host

Expand Down
18 changes: 2 additions & 16 deletions cpp/obs/src/moq-settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ namespace {
// Keys. These are stable: they land in scene collections and in the dock's settings
// file, so renaming one silently drops whatever the user had configured.
constexpr const char *VERSION = "version";
constexpr const char *BACKEND = "backend";
constexpr const char *BIND = "bind";
constexpr const char *CONNECT_TIMEOUT = "connect_timeout_ms";
constexpr const char *FAILOVER_DELAY = "failover_delay_ms";
Expand Down Expand Up @@ -110,15 +109,6 @@ std::vector<Option> VersionOptions()
return CapabilityOptions(moq_versions, "Automatic (offer all)", names);
}

// The QUIC backends this build of libmoq compiled in, so the menu can't offer one the
// setter rejects. The backends are feature-gated, so listing the three names would put
// dead options in front of the user on a release build.
std::vector<Option> BackendOptions()
{
static std::vector<std::string> names;
return CapabilityOptions(moq_backends, "Automatic", names);
}

// Shorthands so the table below reads as data rather than aggregate initializers.
Field Toggle(const char *key, const char *label, bool value, const char *tooltip = nullptr)
{
Expand Down Expand Up @@ -152,7 +142,7 @@ Field Directory(const char *key, const char *label, const char *tooltip = nullpt
return Field{key, label, tooltip, Kind::Directory, 0, 0, 0, false, 0, "", {}, false, nullptr};
}

// The tri-state used wherever the library default depends on the backend.
// The tri-state used wherever the library default depends on the transport.
std::vector<Option> AutoOnOff()
{
return {{"Automatic", AUTO}, {"Enabled", "on"}, {"Disabled", "off"}};
Expand Down Expand Up @@ -226,9 +216,6 @@ const std::vector<Field> &Fields()
// default and so never listed, can still be typed in.
f.push_back(Choose(VERSION, "Protocol version", VersionOptions(), true,
"Pin the handshake to one draft instead of offering every supported version."));
f.push_back(Choose(BACKEND, "QUIC backend", BackendOptions(), false,
"Which QUIC implementation this session uses. Automatic picks the "
"default compiled into this libmoq."));
f.push_back(Text(BIND, "Bind address",
"Local UDP address to send from, e.g. 192.0.2.7:0 to pin the outgoing "
"interface. Leave empty for any."));
Expand Down Expand Up @@ -400,7 +387,6 @@ bool BuildConfig(obs_data_t *settings, Config *out)
config.versions_len = 1;
}

borrow(OptionalString(settings, BACKEND), &out->backend, &config.backend, &config.backend_len);
borrow(OptionalString(settings, BIND), &out->bind, &config.bind, &config.bind_len);

config.connect_timeout_us = (uint64_t)Amount(settings, CONNECT_TIMEOUT) * 1000;
Expand Down Expand Up @@ -442,7 +428,7 @@ bool BuildConfig(obs_data_t *settings, Config *out)
config.has_quic_keep_alive = true;

// The tri-states stay unset when the user left them on Automatic, which is what
// keeps the backend free to pick. That is exactly what the has_* flag carries.
// keeps the transport free to pick. That is exactly what the has_* flag carries.
bool toggle = false;
if (TriState(settings, QUIC_GSO, &toggle)) {
config.quic_gso = toggle;
Expand Down
2 changes: 1 addition & 1 deletion cpp/obs/src/moq-settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ struct Config {

// Backing storage for the pointers in `value`. Held by value so a settings
// object released after BuildConfig can't dangle them.
std::string version, backend, bind, fingerprint, root, host_name, congestion, qlog;
std::string version, bind, fingerprint, root, host_name, congestion, qlog;
moq_string version_item{}, fingerprint_item{}, root_item{};
};

Expand Down
2 changes: 0 additions & 2 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ ignore = [
"RUSTSEC-2023-0071",

# foundations (Cloudflare) pins serde_yaml 0.8 which uses yaml-rust 0.4.
# Reachable via tokio-quiche -> web-transport-quiche -> moq-tokio
# (quiche feature). Awaits upstream migration to serde_yaml 0.9+.
"RUSTSEC-2024-0320",

# gstreamer 0.23 pulls paste (proc-macro). gstreamer 0.25 drops it but
Expand Down
23 changes: 8 additions & 15 deletions doc/bin/relay/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ description: TOML reference for moq-relay

# Configuration

`moq-relay relay.toml`. Every key is also a CLI flag and environment variable
(`--listen-backend`, `MOQ_LISTEN_BACKEND`), named by joining the section and key.
`moq-relay relay.toml`. Every key is also a CLI flag and environment variable.
Most names join the section and key: `listen.tls.cert` is
`--listen-tls-cert` / `MOQ_LISTEN_TLS_CERT`. The `listen.bind` key deliberately
uses the shorter `--listen` / `MOQ_LISTEN` spelling.
Precedence is CLI > env > file > defaults: a flag or environment variable that
was actually supplied overrides the file, and a file key that was actually
written (an empty list, a `false` boolean) overrides the built-in default.
Expand All @@ -23,7 +25,6 @@ cert = "cert.pem" # Certificate chain and key. Reloaded on ch
key = "key.pem"
generate = ["localhost"] # Or: a self-signed cert for development.
root = ["peer-ca.pem"] # Optional: CAs for client certs (mTLS), reported to the auth server.
# The quiche backend fixes these at startup; restart to rotate them.

[listen.tcp] # Plaintext qmux over TCP for trusted local workers.
bind = "127.0.0.1:4444"
Expand Down Expand Up @@ -51,10 +52,8 @@ send_window = 33554432
qlog = "/var/log/moq/qlog" # Existing directory. Needs the `qlog` build feature.
```

The `noq` (default), `quinn`, and `quiche` QUIC backends are compile-time
features selected with `listen.backend` / `connect.backend`. Each ships a
different BBR generation (BBRv1 on quinn, BBRv2 on quiche, BBRv3 on noq and
iroh), which is why the knob names a family rather than an algorithm.
The native QUIC stack uses BBRv3 for delay-based congestion control. Iroh also
uses noq and the same congestion controller.

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.
Expand All @@ -63,12 +62,6 @@ cannot starve the connection. `send_window` caps unacknowledged outgoing data
whatever the peer allows, bounding the transport send buffer. A zero window is refused, and the receive windows must fit a QUIC
varint since they ride on the wire as transport parameters.

quiche has no local send cap, so it refuses `send_window` rather than quietly
dropping it: use the `quinn` or `noq` backend, or leave it unset. It also
autotunes each receive window up to a ceiling, so the relay pins that ceiling to
the configured value and the window is exactly what was asked for, as on the
other backends.

## \[runtime]

By default one work-stealing runtime serves every connection off one UDP
Expand All @@ -87,8 +80,8 @@ 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: the
first worker binds it and the rest join that port. Use an explicit port unless
something reads the bound address at startup. `workers` needs the `noq`
(default) or `quinn` backend and real certificate files rather than
`tls.generate`. A build without a QUIC backend rejects `workers` instead of
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`;
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
Expand Down
7 changes: 2 additions & 5 deletions doc/bin/relay/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ by convention a publisher announces each broadcast's exact path, so the list
reads as broadcast names.

A relay configured with more than one certificate has no single fingerprint to
publish, and this endpoint answers for the first. On the quinn and noq backends
the others are reachable over `https://`, which selects by SNI at handshake. The
quiche backend serves the first pair to every handshake, so a name covered only
by a later certificate needs an explicit `--client-tls-fingerprint`, which checks
the fingerprint in place of the hostname.
publish, and this endpoint answers for the first. The others are reachable over
`https://`, which selects a certificate by SNI at the handshake.

Tokens sent over plain HTTP are visible on the wire, so use HTTPS in
production.
Expand Down
2 changes: 1 addition & 1 deletion doc/lib/c/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ and `target/include/moq.h`.
- **Raw playback.** Raw audio and video consumers start at the newest cached group when opened, so rebuilding a live decoder skips the retained backlog.
- **Raw decode output.** `moq_video_decoder_output` selects the decoded CPU pixel format (`MOQ_VIDEO_PIXEL_FORMAT_I420` or `_RGBA`) and target size (`width`/`height`, both zero for native; otherwise even and non-zero). Unknown formats and invalid sizes fail `moq_decode_video` before subscribing; accepted requests deliver exactly that layout or fail on the terminal callback.
- **Encoded video metadata.** `moq_video_init.hint` is a zero-initialized `moq_video_hint` with `has_*` flags for coded dimensions, bitrate (bits per second), frame rate, and latency preference. Hints seed a video codec track's catalog; detected dimensions take precedence.
- **Client config.** A zeroed `moq_client_config` means the defaults for every knob, which is what lets a new one be appended without disturbing callers. Fields cover protocol (`versions`), TLS (`tls_fingerprints`, `tls_roots`, `tls_cert`/`_key`, `tls_host_name`), transport (`backend`, `bind`, `connect_timeout_us`, the Happy Eyeballs delays, `websocket_enabled`), and tuning (reconnect backoff, `quic_*`). Every duration is in microseconds. A knob whose default isn't zero carries a `has_*` flag, so setting `backoff_timeout_us = 0` needs `has_backoff_timeout = true` to mean "retry forever" rather than "use the default". `moq_client_defaults()` reports what a NULL config dials with.
- **Client config.** A zeroed `moq_client_config` means the defaults for every knob, which is what lets a new one be appended without disturbing callers. Fields cover protocol (`versions`), TLS (`tls_fingerprints`, `tls_roots`, `tls_cert`/`_key`, `tls_host_name`), transport (`bind`, `connect_timeout_us`, the Happy Eyeballs delays, `websocket_enabled`), and tuning (reconnect backoff, `quic_*`). Every duration is in microseconds. A knob whose default isn't zero carries a `has_*` flag, so setting `backoff_timeout_us = 0` needs `has_backoff_timeout = true` to mean "retry forever" rather than "use the default". `moq_client_defaults()` reports what a NULL config dials with.
- **Demand.** A watcher on a published track (`moq_publish_track_demand`, `moq_publish_media_demand`, `moq_encode_video_demand`, `moq_encode_audio_demand`) calls `on_demand` with `MOQ_DEMAND_USED` or `MOQ_DEMAND_UNUSED` right away and again on every change, so an encoder on a battery-powered device runs only while someone is watching. The first call is the current state, so a track that went unused before the watcher existed still reports it. `moq_publish_demand_cancel` stops it; the terminal callback still fires. A container has no single demand and is refused. Demand is counted at the producer: a session that served the track keeps a warm copy for 30 seconds after its last subscriber leaves, so an unused edge behind a relay arrives after that linger.
- **Requests.** `moq_publish_dynamic` serves subscriptions to tracks the broadcast never declared: each arrives as a request handle, read its name with `moq_track_request_name`, then `moq_track_request_accept` (a raw track handle), `moq_track_request_video` / `_audio` (the media handle `moq_publish_video` / `_audio` return), or `moq_track_request_abort` with an application code the subscriber sees. Without a live handler an unknown name is refused. `moq_publish_track_dynamic` does the same for fetches of groups a track no longer has cached, delivered as `moq_group_request_*` (`sequence`, `priority`, `frame_start`); `moq_group_request_accept` starts the producer at `frame_start` so written frames keep their group indices. Register it with `moq_track_request_dynamic` before accepting a track that was itself requested by a fetch, so that pending group survives the transition. Both handlers stop with `moq_publish_dynamic_cancel`.
- **Everything the bindings can do** ([list](/lib/#what-every-binding-can-do)): media publish and consume with the catalog managed for you, raw pixels and PCM with the codec inside (`moq_encode_video`, `moq_encode_audio`, and the `moq_decode_*` mirrors), raw tracks with timestamps and datagrams, JSON snapshot and stream tracks, group fetch, catalog sections, shared video properties, and stalled hints. The three advertising operations are `moq_origin_create_broadcast` (unadvertised producer), `moq_publish_announce` / `moq_publish_unannounce` (exact-path advertisement), and `moq_origin_dynamic` (a claim over a path prefix and everything beneath it; `""` for everything). A route is a capability, not an inventory; `moq_announce_update.prefix` is the concrete covered prefix relative to the origin root.
Expand Down
8 changes: 4 additions & 4 deletions doc/lib/rs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (quinn, quiche, or noq), TLS, WebSocket fallback, and iroh, from config or CLI flags. |
| [moq-tokio](https://docs.rs/moq-tokio) | Stands up QUIC with noq, TLS, WebSocket fallback, and iroh, from config or CLI flags. |
| [hang](/lib/rs/hang) | The media layer: catalog, containers, ordered frame delivery. |
| [moq-mux](/lib/rs/moq-mux) | Import and export fMP4/CMAF, MPEG-TS, Matroska, FLV, and Annex-B. |
| [moq-video](/lib/rs/moq-video) | Native capture, hardware encode/decode (Apple, Windows, NVIDIA, VAAPI, V4L2, Android), and GPU rendering. |
Expand Down Expand Up @@ -94,9 +94,9 @@ quic.send_window = Some(32 << 20); // unacknowledged data we may hold
let client = moq_tokio::connect::Config::default().init(quic)?;
```

Unset flow-control windows keep the selected backend defaults, and `init` errors on
a knob that backend cannot honor rather than dropping it: quiche has no local
send cap, and iroh cannot disable GSO. `quic::Resolved::default()` is what an
Unset flow-control windows keep the transport defaults, and `init` errors on a
knob the transport cannot honor rather than dropping it: iroh cannot disable
GSO. `quic::Resolved::default()` is what an
untouched config resolves to, so read the defaults from there. The
[relay reference](/bin/relay/config#quic) documents each field.

Expand Down
4 changes: 2 additions & 2 deletions doc/lib/rs/moq-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ 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.
- **Stats** counters per broadcast and session, drained by [`moq-stats`](https://docs.rs/moq-stats).

It runs over anything implementing `web_transport_trait::Session`: quinn,
quiche, noq, the browser, iroh, or qmux over TCP, Unix sockets, and
It runs over anything implementing `web_transport_trait::Session`: noq, the
browser, iroh, or qmux over TCP, Unix sockets, and
WebSockets. [`moq-tokio`](https://docs.rs/moq-tokio) wires those up.

```bash
Expand Down
4 changes: 2 additions & 2 deletions js/net/src/connection/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import * as Time from "../time.ts";
*
* The field names match Rust's `moq_net::session::Stats` but the counters keep each
* stack's own semantics rather than being normalized. Notably W3C excludes
* retransmissions and QUIC overhead from the byte counts where quinn includes them,
* and counts packets where quinn counts datagrams.
* retransmissions and QUIC overhead from the byte counts where native QUIC includes
* them, and counts packets where native QUIC counts datagrams.
*
* @public
*/
Expand Down
2 changes: 1 addition & 1 deletion nix/overlay.nix
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let
};

# `[patch.crates-io] kio = { path = "rs/kio" }` in the root Cargo.toml points
# registry crates (web-transport-quinn, web-transport-iroh) at the workspace
# registry crates (web-transport-iroh) at the workspace
# member. crane's dependency-only stage stubs every workspace crate down to an
# empty lib.rs, so those registry crates no longer find kio's API and fail to
# compile. Put kio's real source back into the dummy tree.
Expand Down
5 changes: 1 addition & 4 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,10 @@ quest here and merged main into dev. The auth API line is here for its
request-side break (`mtls=<identity>` and the now-required fields) and ranks
first because moq.pro adopts the release only once that contract is settled;
it is priority, not a merge gate, and [Merge dev](/quest/m1/merge-dev.md)
does not require it. [One QUIC backend](/quest/m1/quic-one-backend.md) ranks
above it: it removes public features, so it cannot land after the merge, and
the transport line in m2 assumes a single stack.
does not require it. The transport line in m2 assumes the single noq stack.

## Quests

- [One QUIC backend](/quest/m1/quic-one-backend.md) - quinn and quiche are deleted; noq (and iroh on it) is the only QUIC stack, with the qmux fallbacks untouched
- [Announce event](/quest/m1/api-net-announce.md) - publishers announce prefixes on every wire, consumers scoped by a pattern read the covered path already trimmed, with no `as_prefix().expect()` at 89 call sites
- [Bindings announce match](/quest/m1/api-origin-scopes.md) - every binding takes a pattern scope and reports the announce match with its captures
- [PathPrefixes](/quest/m1/api-path-prefixes.md) - the unused moq_net::PathPrefixes type is deleted before the release
Expand Down
Loading
Loading