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.
64pub(crate) fn spawn_session((session, driver): (moq_net::Session, moq_net::Driver)) -> moq_net::Session {
65	tokio::spawn(driver);
66	session
67}
68
69// Re-export these crates.
70pub use moq_net;
71pub use rustls;
72
73fn version_parser() -> impl clap::builder::TypedValueParser<Value = moq_net::Version> {
74	use clap::builder::TypedValueParser;
75
76	clap::builder::PossibleValuesParser::new(moq_net::Version::names())
77		.map(|name| name.parse().expect("possible version names must parse"))
78}
79
80/// Re-exported because [`watch::FileWatcher`] surfaces `notify::Result`/`notify::Error`
81/// in its API; a major `notify` bump is therefore a breaking change for this crate.
82#[cfg(feature = "watch")]
83pub use notify;
84
85/// Re-exported because [`tls::init_android`] takes a `jni::Env` handle; a major
86/// `jni` bump is therefore a breaking change for this crate.
87#[cfg(target_os = "android")]
88pub use jni;
89
90#[cfg(feature = "quiche")]
91pub mod quiche;
92
93#[cfg(feature = "iroh")]
94pub mod iroh;
95
96/// The QUIC backend to use for connections.
97#[derive(Clone, Debug, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
98#[serde(rename_all = "lowercase")]
99#[non_exhaustive]
100pub enum QuicBackend {
101	/// [web-transport-quinn](https://crates.io/crates/web-transport-quinn)
102	#[cfg(feature = "quinn")]
103	Quinn,
104
105	/// [web-transport-quiche](https://crates.io/crates/web-transport-quiche)
106	#[cfg(feature = "quiche")]
107	Quiche,
108
109	/// [web-transport-noq](https://crates.io/crates/web-transport-noq)
110	#[cfg(feature = "noq")]
111	Noq,
112}
113
114/// Parses the same spellings the CLI and TOML accept (`quinn`, `quiche`, `noq`),
115/// case-insensitively. A backend this build was compiled without is an error, since
116/// its variant doesn't exist.
117impl std::str::FromStr for QuicBackend {
118	type Err = String;
119
120	fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
121		<Self as clap::ValueEnum>::from_str(s, true)
122	}
123}
124
125impl QuicBackend {
126	/// Every backend this build was compiled with, spelled the way [`FromStr`] accepts.
127	///
128	/// The variants are feature-gated, so this is the only honest answer to "what can I
129	/// pass here". A caller building a menu should read it rather than listing the three
130	/// names, which would offer options that cannot parse.
131	///
132	/// [`FromStr`]: std::str::FromStr
133	pub fn compiled() -> &'static [Self] {
134		&[
135			#[cfg(feature = "quinn")]
136			Self::Quinn,
137			#[cfg(feature = "quiche")]
138			Self::Quiche,
139			#[cfg(feature = "noq")]
140			Self::Noq,
141		]
142	}
143
144	/// The name [`FromStr`] accepts for this backend.
145	///
146	/// [`FromStr`]: std::str::FromStr
147	pub fn as_str(&self) -> &'static str {
148		match *self {
149			#[cfg(feature = "quinn")]
150			Self::Quinn => "quinn",
151			#[cfg(feature = "quiche")]
152			Self::Quiche => "quiche",
153			#[cfg(feature = "noq")]
154			Self::Noq => "noq",
155		}
156	}
157}
158
159/// Whether this build can capture qlog traces, which the `qlog` feature gates.
160///
161/// Setting a qlog directory without it is an error at dial time, so a caller offering
162/// the knob should check here rather than surfacing an option that cannot work.
163pub fn qlog_supported() -> bool {
164	cfg!(feature = "qlog")
165}
166
167/// The backend a config without an explicit `--*-backend` gets.
168///
169/// Only compiled when there is one to pick: a build with no QUIC backend never
170/// reaches for a default, since `QuicBackend` has no variants there.
171#[cfg(any(feature = "noq", feature = "quinn", feature = "quiche"))]
172fn default_quic_backend() -> QuicBackend {
173	#[cfg(feature = "quinn")]
174	{
175		QuicBackend::Quinn
176	}
177	#[cfg(all(feature = "noq", not(feature = "quinn")))]
178	{
179		QuicBackend::Noq
180	}
181	#[cfg(all(feature = "quiche", not(feature = "quinn"), not(feature = "noq")))]
182	{
183		QuicBackend::Quiche
184	}
185}
186
187#[cfg(test)]
188mod tests {
189	#[cfg(feature = "quinn")]
190	#[test]
191	fn quinn_is_the_default_backend() {
192		assert!(matches!(super::default_quic_backend(), super::QuicBackend::Quinn));
193	}
194}