Skip to main content

moq_native/
lib.rs

1//! Helper library for native MoQ applications.
2//!
3//! Establishes MoQ connections over:
4//! - WebTransport (HTTP/3)
5//! - Raw QUIC (with ALPN negotiation)
6//! - WebSocket (fallback via [web-transport-ws](https://crates.io/crates/web-transport-ws))
7//! - Plain TCP via the `tcp://` scheme (qmux, no TLS; requires `tcp` feature)
8//! - Unix domain socket via the `unix://` scheme (qmux, peer-credential aware; requires `uds` feature, unix-only)
9//! - Iroh P2P (requires `iroh` feature)
10//!
11//! See [`Client`] for connecting to relays and [`Server`] for accepting connections.
12
13#![warn(missing_docs)]
14
15pub mod accept;
16pub mod bind;
17mod client;
18mod connect;
19mod crypto;
20mod error;
21#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche", feature = "tcp"))]
22pub mod failover;
23#[cfg(feature = "jemalloc")]
24pub mod jemalloc;
25mod log;
26#[cfg(feature = "noq")]
27pub mod noq;
28pub mod quic;
29#[cfg(feature = "quinn")]
30pub mod quinn;
31mod reconnect;
32#[cfg(any(feature = "quinn", feature = "noq", feature = "quiche", feature = "tcp"))]
33mod resolve;
34mod server;
35#[cfg(feature = "tcp")]
36pub mod tcp;
37pub mod tls;
38#[cfg(all(feature = "uds", unix))]
39pub mod unix;
40// Resolving a `host:port` bind string is a QUIC-listener concern; the stream
41// listeners take a `SocketAddr`/path straight from their config.
42#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
43mod util;
44#[cfg(feature = "watch")]
45pub mod watch;
46#[cfg(feature = "websocket")]
47pub mod websocket;
48
49// Enumerated rather than globbed, so the root surface is a deliberate list and a
50// new `pub` item in these modules doesn't silently join it.
51pub use client::{Client, ClientConfig};
52pub use connect::ConnectError;
53pub use error::{Error, Result};
54pub use log::Log;
55pub use reconnect::{Backoff, ConnectionStatsReader, Reconnect, Status};
56pub use server::{Request, Server, ServerConfig, Transport};
57
58/// Spawn the session's protocol driver on the current tokio runtime, handing back
59/// the session it drives.
60///
61/// The driver holds no session clone, so the session still closes when the caller
62/// drops their last [`moq_net::Session`] handle, which in turn lets the driver
63/// task finish.
64///
65/// Instruments the spawned task with the span active at the call site (e.g. the
66/// caller's per-connection span), since `tokio::spawn` otherwise starts the driver
67/// with no span at all: it doesn't inherit one from the spawning task the way a
68/// plain nested call would.
69pub(crate) fn spawn_session((session, driver): (moq_net::Session, moq_net::Driver)) -> moq_net::Session {
70	use tracing::Instrument;
71	tokio::spawn(driver.instrument(tracing::Span::current()));
72	session
73}
74
75// Re-export these crates.
76pub use moq_net;
77pub use rustls;
78
79fn version_parser() -> impl clap::builder::TypedValueParser<Value = moq_net::Version> {
80	use clap::builder::TypedValueParser;
81
82	clap::builder::PossibleValuesParser::new(moq_net::Version::names())
83		.map(|name| name.parse().expect("possible version names must parse"))
84}
85
86/// Re-exported because [`watch::FileWatcher`] surfaces `notify::Result`/`notify::Error`
87/// in its API; a major `notify` bump is therefore a breaking change for this crate.
88#[cfg(feature = "watch")]
89pub use notify;
90
91/// Re-exported because [`tls::init_android`] takes a `jni::Env` handle; a major
92/// `jni` bump is therefore a breaking change for this crate.
93#[cfg(target_os = "android")]
94pub use jni;
95
96#[cfg(feature = "quiche")]
97pub mod quiche;
98
99#[cfg(feature = "iroh")]
100pub mod iroh;
101
102/// The QUIC backend to use for connections.
103#[derive(Clone, Debug, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
104#[serde(rename_all = "lowercase")]
105#[non_exhaustive]
106pub enum QuicBackend {
107	/// [web-transport-quinn](https://crates.io/crates/web-transport-quinn)
108	#[cfg(feature = "quinn")]
109	Quinn,
110
111	/// [web-transport-quiche](https://crates.io/crates/web-transport-quiche)
112	#[cfg(feature = "quiche")]
113	Quiche,
114
115	/// [web-transport-noq](https://crates.io/crates/web-transport-noq)
116	#[cfg(feature = "noq")]
117	Noq,
118}
119
120/// Parses the same spellings the CLI and TOML accept (`quinn`, `quiche`, `noq`),
121/// case-insensitively. A backend this build was compiled without is an error, since
122/// its variant doesn't exist.
123impl std::str::FromStr for QuicBackend {
124	type Err = String;
125
126	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
127		<Self as clap::ValueEnum>::from_str(s, true)
128	}
129}
130
131impl QuicBackend {
132	/// Every backend this build was compiled with, spelled the way [`FromStr`] accepts.
133	///
134	/// The variants are feature-gated, so this is the only honest answer to "what can I
135	/// pass here". A caller building a menu should read it rather than listing the three
136	/// names, which would offer options that cannot parse.
137	///
138	/// [`FromStr`]: std::str::FromStr
139	pub fn compiled() -> &'static [Self] {
140		&[
141			#[cfg(feature = "quinn")]
142			Self::Quinn,
143			#[cfg(feature = "quiche")]
144			Self::Quiche,
145			#[cfg(feature = "noq")]
146			Self::Noq,
147		]
148	}
149
150	/// The name [`FromStr`] accepts for this backend.
151	///
152	/// [`FromStr`]: std::str::FromStr
153	pub fn as_str(&self) -> &'static str {
154		match *self {
155			#[cfg(feature = "quinn")]
156			Self::Quinn => "quinn",
157			#[cfg(feature = "quiche")]
158			Self::Quiche => "quiche",
159			#[cfg(feature = "noq")]
160			Self::Noq => "noq",
161		}
162	}
163}
164
165/// Whether this build can capture qlog traces, which the `qlog` feature gates.
166///
167/// Setting a qlog directory without it is an error at dial time, so a caller offering
168/// the knob should check here rather than surfacing an option that cannot work.
169pub fn qlog_supported() -> bool {
170	cfg!(feature = "qlog")
171}
172
173/// The backend a config without an explicit `--*-backend` gets.
174///
175/// Only compiled when there is one to pick: a build with no QUIC backend never
176/// reaches for a default, since `QuicBackend` has no variants there.
177#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
178fn default_quic_backend() -> QuicBackend {
179	#[cfg(feature = "quinn")]
180	{
181		QuicBackend::Quinn
182	}
183	#[cfg(all(feature = "noq", not(feature = "quinn")))]
184	{
185		QuicBackend::Noq
186	}
187	#[cfg(all(feature = "quiche", not(feature = "quinn"), not(feature = "noq")))]
188	{
189		QuicBackend::Quiche
190	}
191}
192
193#[cfg(test)]
194mod tests {
195	#[cfg(feature = "quinn")]
196	#[test]
197	fn quinn_is_the_default_backend() {
198		assert!(matches!(super::default_quic_backend(), super::QuicBackend::Quinn));
199	}
200}