1use std::net;
2#[cfg(any(test, all(feature = "uds", unix)))]
3use std::path::PathBuf;
4
5#[cfg(feature = "iroh")]
6use crate::iroh;
7use crate::{Error, QuicBackend};
8use moq_net::Session;
9use url::Url;
10
11use futures::FutureExt;
12use futures::future::BoxFuture;
13use futures::stream::FuturesUnordered;
14use futures::stream::StreamExt;
15
16#[derive(clap::Args, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
18#[serde(deny_unknown_fields, default)]
19#[non_exhaustive]
20pub struct ServerConfig {
21 #[serde(alias = "listen")]
29 #[arg(id = "server-bind", long = "server-bind", alias = "listen", env = "MOQ_SERVER_BIND")]
30 pub bind: Option<String>,
31
32 #[cfg(feature = "tcp")]
35 #[command(flatten)]
36 #[serde(default)]
37 pub tcp: crate::tcp::Config,
38
39 #[cfg(all(feature = "uds", unix))]
42 #[command(flatten)]
43 #[serde(default)]
44 pub unix: crate::unix::Config,
45
46 #[arg(id = "server-backend", long = "server-backend", env = "MOQ_SERVER_BACKEND")]
49 pub backend: Option<QuicBackend>,
50
51 #[command(flatten)]
54 #[serde(default)]
55 pub quic: crate::quic::Server,
56
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
65 #[arg(id = "server-version", long = "server-version", env = "MOQ_SERVER_VERSION")]
66 pub version: Vec<moq_net::Version>,
67
68 #[command(flatten)]
71 #[serde(default)]
72 pub tls: crate::tls::Server,
73}
74
75impl ServerConfig {
76 pub fn init(self) -> crate::Result<Server> {
78 Server::new(self)
79 }
80
81 pub fn versions(&self) -> moq_net::Versions {
83 if self.version.is_empty() {
84 moq_net::Versions::all()
85 } else {
86 moq_net::Versions::from(self.version.clone())
87 }
88 }
89
90 #[allow(unused_mut)]
95 fn has_stream_listener(&self) -> bool {
96 let mut has = false;
97 #[cfg(feature = "tcp")]
98 {
99 has |= self.tcp.bind.is_some();
100 }
101 #[cfg(all(feature = "uds", unix))]
102 {
103 has |= self.unix.bind.is_some();
104 }
105 has
106 }
107}
108
109pub(crate) const DEFAULT_BIND: &str = "[::]:443";
111
112pub struct Server {
118 moq: moq_net::Server,
119 versions: moq_net::Versions,
120 accept: FuturesUnordered<BoxFuture<'static, crate::Result<Request>>>,
121 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
122 streams: StreamListeners,
123 #[cfg(feature = "iroh")]
124 iroh: Option<iroh::Endpoint>,
125 #[cfg(feature = "noq")]
126 noq: Option<crate::noq::NoqServer>,
127 #[cfg(feature = "quinn")]
128 quinn: Option<crate::quinn::QuinnServer>,
129 #[cfg(feature = "quiche")]
130 quiche: Option<crate::quiche::QuicheServer>,
131 #[cfg(feature = "websocket")]
132 websocket: Option<crate::websocket::Listener>,
133}
134
135impl Server {
136 pub fn new(config: ServerConfig) -> crate::Result<Self> {
141 let backend = config.backend.clone().unwrap_or_else(crate::default_quic_backend);
142
143 let versions = config.versions();
144
145 config.quic.validate()?;
149
150 let build_quic = config.bind.is_some() || !config.has_stream_listener();
151
152 if build_quic && !config.tls.root.is_empty() {
153 let mtls_supported = match backend {
154 #[cfg(feature = "quinn")]
155 QuicBackend::Quinn => true,
156 #[cfg(feature = "noq")]
157 QuicBackend::Noq => true,
158 #[cfg(feature = "quiche")]
159 QuicBackend::Quiche => true,
160 #[allow(unreachable_patterns)]
161 _ => false,
162 };
163 if !mtls_supported {
164 return Err(Error::MtlsUnsupported);
165 }
166 }
167
168 #[cfg(feature = "noq")]
169 #[allow(unreachable_patterns)]
170 let noq = match backend {
171 QuicBackend::Noq if build_quic => Some(crate::noq::NoqServer::new(config.clone())?),
172 _ => None,
173 };
174
175 #[cfg(feature = "quinn")]
176 #[allow(unreachable_patterns)]
177 let quinn = match backend {
178 QuicBackend::Quinn if build_quic => Some(crate::quinn::QuinnServer::new(config.clone())?),
179 _ => None,
180 };
181
182 #[cfg(feature = "quiche")]
183 let quiche = match backend {
184 QuicBackend::Quiche if build_quic => Some(crate::quiche::QuicheServer::new(config.clone())?),
185 _ => None,
186 };
187
188 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
190 let mut stream_binds = Vec::new();
191 #[cfg(feature = "tcp")]
192 if let Some(addr) = config.tcp.bind {
193 stream_binds.push(StreamBind::Tcp(addr));
194 }
195 #[cfg(all(feature = "uds", unix))]
196 if let Some(path) = config.unix.bind.clone() {
197 stream_binds.push(StreamBind::Unix(path));
198 }
199 #[cfg(all(feature = "uds", unix))]
201 let unix_allow = config.unix.allow.clone().filter(|allow| !allow.is_empty());
202 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
203 let streams = StreamListeners::new(
204 stream_binds,
205 stream_versions(&versions),
206 #[cfg(all(feature = "uds", unix))]
207 unix_allow,
208 );
209
210 Ok(Server {
211 accept: Default::default(),
212 moq: moq_net::Server::new().with_versions(versions.clone()),
213 versions,
214 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
215 streams,
216 #[cfg(feature = "iroh")]
217 iroh: None,
218 #[cfg(feature = "noq")]
219 noq,
220 #[cfg(feature = "quinn")]
221 quinn,
222 #[cfg(feature = "quiche")]
223 quiche,
224 #[cfg(feature = "websocket")]
225 websocket: None,
226 })
227 }
228
229 #[cfg(feature = "websocket")]
235 pub fn with_websocket(mut self, websocket: crate::websocket::Listener) -> Self {
236 self.websocket = Some(websocket);
237 self
238 }
239
240 #[cfg(feature = "iroh")]
242 pub fn with_iroh(mut self, iroh: iroh::Endpoint) -> Self {
243 self.iroh = Some(iroh);
244 self
245 }
246
247 pub fn with_publisher(mut self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
249 self.moq = self.moq.with_publisher(publish);
250 self
251 }
252
253 pub fn with_subscriber(mut self, subscribe: moq_net::origin::Producer) -> Self {
255 self.moq = self.moq.with_subscriber(subscribe);
256 self
257 }
258
259 pub fn with_stats(mut self, stats: moq_net::stats::Session) -> Self {
262 self.moq = self.moq.with_stats(stats);
263 self
264 }
265
266 pub async fn serve_publish(self, origin: moq_net::origin::Consumer) -> crate::Result<()> {
273 self.with_publisher(origin).serve().await
274 }
275
276 pub async fn serve_consume(self, origin: moq_net::origin::Producer) -> crate::Result<()> {
280 self.with_subscriber(origin).serve().await
281 }
282
283 async fn serve(mut self) -> crate::Result<()> {
286 if let Ok(addr) = self.local_addr() {
287 tracing::info!(%addr, "listening");
288 }
289 while let Some(request) = self.accept().await {
290 tokio::spawn(async move {
291 if let Err(err) = serve_session(request).await {
292 tracing::warn!(%err, "session ended with error");
293 }
294 });
295 }
296 Ok(())
297 }
298
299 pub fn certificates(&self) -> crate::tls::Certificates {
308 #[cfg(feature = "noq")]
309 if let Some(noq) = self.noq.as_ref() {
310 return noq.certificates();
311 }
312 #[cfg(feature = "quinn")]
313 if let Some(quinn) = self.quinn.as_ref() {
314 return quinn.certificates();
315 }
316 #[cfg(feature = "quiche")]
317 if let Some(quiche) = self.quiche.as_ref() {
318 return quiche.certificates();
319 }
320 crate::tls::Certificates::empty()
322 }
323
324 #[cfg(not(any(
325 feature = "noq",
326 feature = "quinn",
327 feature = "quiche",
328 feature = "iroh",
329 feature = "tcp",
330 all(feature = "uds", unix)
331 )))]
332 pub async fn accept(&mut self) -> Option<Request> {
336 unreachable!("no transport compiled; enable a QUIC backend, tcp, or uds feature");
337 }
338
339 pub fn accept_health(&self) -> Vec<crate::accept::Health> {
351 #[allow(unused_mut)]
352 let mut health = Vec::new();
353 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
354 health.extend(self.streams.health.iter().cloned());
355 #[cfg(feature = "websocket")]
356 health.extend(self.websocket.as_ref().map(|ws| ws.accept_health()));
357 health
358 }
359
360 pub async fn listen(&mut self) -> crate::Result<()> {
374 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
375 self.streams.ensure_started().await?;
376 Ok(())
377 }
378
379 #[cfg(any(
390 feature = "noq",
391 feature = "quinn",
392 feature = "quiche",
393 feature = "iroh",
394 feature = "tcp",
395 all(feature = "uds", unix)
396 ))]
397 pub async fn accept(&mut self) -> Option<Request> {
398 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
401 if let Err(err) = self.streams.ensure_started().await {
402 tracing::error!(%err, "failed to bind stream listener");
403 return None;
404 }
405
406 loop {
407 #[cfg(feature = "noq")]
409 let noq_accept = async {
410 #[cfg(feature = "noq")]
411 if let Some(noq) = self.noq.as_mut() {
412 return noq.accept().await;
413 }
414 None
415 };
416 #[cfg(not(feature = "noq"))]
417 let noq_accept = async { None::<()> };
418
419 #[cfg(feature = "iroh")]
420 let iroh_accept = async {
421 #[cfg(feature = "iroh")]
422 if let Some(endpoint) = self.iroh.as_mut() {
423 return endpoint.accept().await;
424 }
425 None
426 };
427 #[cfg(not(feature = "iroh"))]
428 let iroh_accept = async { None::<()> };
429
430 #[cfg(feature = "quinn")]
431 let quinn_accept = async {
432 #[cfg(feature = "quinn")]
433 if let Some(quinn) = self.quinn.as_mut() {
434 return quinn.accept().await;
435 }
436 None
437 };
438 #[cfg(not(feature = "quinn"))]
439 let quinn_accept = async { None::<()> };
440
441 #[cfg(feature = "quiche")]
442 let quiche_accept = async {
443 #[cfg(feature = "quiche")]
444 if let Some(quiche) = self.quiche.as_mut() {
445 return quiche.accept().await;
446 }
447 None
448 };
449 #[cfg(not(feature = "quiche"))]
450 let quiche_accept = async { None::<()> };
451
452 #[cfg(feature = "websocket")]
453 let ws_ref = self.websocket.as_ref();
454 #[cfg(feature = "websocket")]
455 let ws_accept = async {
456 match ws_ref {
457 Some(ws) => ws.accept().await,
458 None => std::future::pending().await,
459 }
460 };
461 #[cfg(not(feature = "websocket"))]
462 let ws_accept = std::future::pending::<Option<crate::Result<()>>>();
463
464 #[allow(unused_variables)]
465 let server = self.moq.clone();
466 #[allow(unused_variables)]
467 let versions = self.versions.clone();
468
469 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
471 let stream_accept = self.streams.recv();
472 #[cfg(not(any(feature = "tcp", all(feature = "uds", unix))))]
473 let stream_accept = std::future::pending::<Option<Request>>();
474
475 tokio::select! {
476 Some(request) = stream_accept => {
477 return Some(request);
478 }
479 Some(_conn) = noq_accept => {
480 #[cfg(feature = "noq")]
481 {
482 let alpns = versions.alpns();
483 self.accept.push(async move {
484 let (session, url, identity) = super::noq::accept(_conn, alpns).await?;
488 let request = server.accept_request(session).await?;
489 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Noq(Box::new(request)) })
490 }.boxed());
491 }
492 }
493 Some(_conn) = quinn_accept => {
494 #[cfg(feature = "quinn")]
495 {
496 let alpns = versions.alpns();
497 self.accept.push(async move {
498 let (session, url, identity) = super::quinn::accept(_conn, alpns).await?;
499 let request = server.accept_request(session).await?;
500 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quinn(Box::new(request)) })
501 }.boxed());
502 }
503 }
504 Some(_conn) = quiche_accept => {
505 #[cfg(feature = "quiche")]
506 {
507 let alpns = versions.alpns();
508 self.accept.push(async move {
509 let (session, url, identity) = super::quiche::accept(_conn, alpns).await?;
510 let request = server.accept_request(session).await?;
511 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quiche(Box::new(request)) })
512 }.boxed());
513 }
514 }
515 Some(_conn) = iroh_accept => {
516 #[cfg(feature = "iroh")]
517 self.accept.push(async move {
518 let (session, url, identity) = super::iroh::accept(_conn).await?;
519 let request = server.accept_request(session).await?;
520 Ok(Request { transport: Transport::Iroh, url, identity, kind: RequestKind::Iroh(Box::new(request)) })
521 }.boxed());
522 }
523 Some(_res) = ws_accept => {
524 #[cfg(feature = "websocket")]
525 match _res {
526 Ok(session) => {
527 self.accept.push(async move {
530 let request = server.accept_request(session).await?;
531 Ok(Request { transport: Transport::WebSocket, url: None, identity: None, kind: RequestKind::Qmux(Box::new(request)) })
532 }.boxed());
533 }
534 Err(err) => tracing::debug!(%err, "WebSocket upgrade failed"),
538 }
539 }
540 Some(res) = self.accept.next() => {
541 match res {
542 Ok(session) => return Some(session),
543 Err(err) => tracing::debug!(%err, "failed to accept session"),
544 }
545 }
546 _ = tokio::signal::ctrl_c() => {
547 self.close().await;
548 return None;
549 }
550 }
551 }
552 }
553
554 #[cfg(feature = "iroh")]
556 pub fn iroh_endpoint(&self) -> Option<&iroh::Endpoint> {
557 self.iroh.as_ref()
558 }
559
560 pub fn local_addr(&self) -> crate::Result<net::SocketAddr> {
566 #[cfg(feature = "noq")]
567 if let Some(noq) = self.noq.as_ref() {
568 return Ok(noq.local_addr()?);
569 }
570 #[cfg(feature = "quinn")]
571 if let Some(quinn) = self.quinn.as_ref() {
572 return Ok(quinn.local_addr()?);
573 }
574 #[cfg(feature = "quiche")]
575 if let Some(quiche) = self.quiche.as_ref() {
576 return Ok(quiche.local_addr()?);
577 }
578 Err(Error::NoBackend("no QUIC listener configured"))
580 }
581
582 #[cfg(feature = "websocket")]
585 pub fn websocket_local_addr(&self) -> Option<net::SocketAddr> {
586 self.websocket.as_ref().and_then(|ws| ws.local_addr().ok())
587 }
588
589 pub async fn close(&mut self) {
594 #[cfg(feature = "noq")]
595 if let Some(noq) = self.noq.as_mut() {
596 noq.close();
597 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
598 }
599 #[cfg(feature = "quinn")]
600 if let Some(quinn) = self.quinn.as_mut() {
601 quinn.close();
602 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
603 }
604 #[cfg(feature = "quiche")]
605 if let Some(quiche) = self.quiche.as_mut() {
606 quiche.close();
607 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
608 }
609 #[cfg(feature = "iroh")]
610 if let Some(iroh) = self.iroh.take() {
611 iroh.close().await;
612 }
613 #[cfg(feature = "websocket")]
614 {
615 let _ = self.websocket.take();
616 }
617 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche", feature = "iroh")))]
618 unreachable!("no QUIC backend compiled");
619 }
620}
621
622async fn serve_session(request: Request) -> crate::Result<()> {
624 let session = request.ok().await?;
625 Err(session.closed().await.into())
626}
627
628#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
634fn stream_versions(base: &moq_net::Versions) -> moq_net::Versions {
635 let mut versions: Vec<moq_net::Version> = base.iter().copied().collect();
636 if let Ok(lite05) = "moq-lite-05".parse::<moq_net::Version>()
637 && !versions.contains(&lite05)
638 {
639 versions.push(lite05);
640 }
641 moq_net::Versions::from(versions)
642}
643
644#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
646#[derive(Clone)]
647enum StreamBind {
648 #[cfg(feature = "tcp")]
649 Tcp(net::SocketAddr),
650 #[cfg(all(feature = "uds", unix))]
651 Unix(PathBuf),
652}
653
654#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
655impl StreamBind {
656 fn name(&self) -> &'static str {
658 match self {
659 #[cfg(feature = "tcp")]
660 Self::Tcp(_) => "tcp",
661 #[cfg(all(feature = "uds", unix))]
662 Self::Unix(_) => "unix",
663 }
664 }
665}
666
667#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
674struct StreamListeners {
675 binds: Vec<StreamBind>,
676 health: Vec<crate::accept::Health>,
680 versions: moq_net::Versions,
681 #[cfg(all(feature = "uds", unix))]
682 unix_allow: Option<crate::unix::Allow>,
683 rx: Option<tokio::sync::mpsc::Receiver<Request>>,
684 tasks: Vec<tokio::task::JoinHandle<()>>,
685}
686
687#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
688impl StreamListeners {
689 fn new(
690 binds: Vec<StreamBind>,
691 versions: moq_net::Versions,
692 #[cfg(all(feature = "uds", unix))] unix_allow: Option<crate::unix::Allow>,
693 ) -> Self {
694 let health = binds
695 .iter()
696 .map(|bind| crate::accept::Health::new(bind.name()))
697 .collect();
698 Self {
699 binds,
700 health,
701 versions,
702 #[cfg(all(feature = "uds", unix))]
703 unix_allow,
704 rx: None,
705 tasks: Vec::new(),
706 }
707 }
708
709 async fn ensure_started(&mut self) -> crate::Result<()> {
711 if self.rx.is_some() || self.binds.is_empty() {
712 return Ok(());
713 }
714
715 let (tx, rx) = tokio::sync::mpsc::channel(16);
716 if let Err(err) = self.start(&tx).await {
717 for task in self.tasks.drain(..) {
723 task.abort();
724 }
725 return Err(err);
726 }
727
728 self.rx = Some(rx);
729 Ok(())
730 }
731
732 async fn start(&mut self, tx: &tokio::sync::mpsc::Sender<Request>) -> crate::Result<()> {
734 let binds = self.binds.clone();
737 let health = self.health.clone();
738 for (bind, health) in binds.into_iter().zip(health) {
739 let versions = self.versions.clone();
740 match bind {
741 #[cfg(feature = "tcp")]
742 StreamBind::Tcp(addr) => {
743 if !addr.ip().is_loopback() {
744 tracing::warn!(%addr, "tcp listener bound to a non-loopback address; qmux is UNENCRYPTED, ensure the network is trusted");
745 }
746 let listener = crate::tcp::Listener::bind(addr)
747 .await?
748 .with_protocols(versions.alpns())
749 .with_accept_health(health);
750 tracing::info!(%addr, "listening (tcp)");
751 self.tasks.push(spawn_tcp_loop(listener, versions, tx.clone()));
752 }
753 #[cfg(all(feature = "uds", unix))]
754 StreamBind::Unix(path) => {
755 let listener = crate::unix::Listener::bind(&path)
756 .await?
757 .with_protocols(versions.alpns())
758 .with_accept_health(health);
759 listener.set_mode(0o666)?;
762 tracing::info!(path = %path.display(), allow = ?self.unix_allow, "listening (unix)");
763 self.tasks
764 .push(spawn_unix_loop(listener, versions, self.unix_allow.clone(), tx.clone()));
765 }
766 }
767 }
768
769 Ok(())
770 }
771
772 async fn recv(&mut self) -> Option<Request> {
774 match self.rx.as_mut() {
775 Some(rx) => rx.recv().await,
776 None => std::future::pending().await,
777 }
778 }
779}
780
781#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
782impl Drop for StreamListeners {
783 fn drop(&mut self) {
784 for task in &self.tasks {
786 task.abort();
787 }
788 }
789}
790
791#[cfg(feature = "tcp")]
792fn spawn_tcp_loop(
793 listener: crate::tcp::Listener,
794 versions: moq_net::Versions,
795 tx: tokio::sync::mpsc::Sender<Request>,
796) -> tokio::task::JoinHandle<()> {
797 tokio::spawn(async move {
798 loop {
799 match listener.accept().await {
800 Some(Ok(session)) => spawn_stream_request(session, Transport::Tcp, versions.clone(), tx.clone()),
801 Some(Err(err)) => tracing::warn!(%err, "tcp qmux handshake failed"),
804 None => break,
805 }
806 }
807 })
808}
809
810#[cfg(all(feature = "uds", unix))]
811fn spawn_unix_loop(
812 listener: crate::unix::Listener,
813 versions: moq_net::Versions,
814 allow: Option<crate::unix::Allow>,
815 tx: tokio::sync::mpsc::Sender<Request>,
816) -> tokio::task::JoinHandle<()> {
817 tokio::spawn(async move {
818 loop {
819 match listener.accept().await {
820 Some(Ok((session, cred))) => {
821 if let Some(allow) = &allow
823 && !allow.permits(&cred)
824 {
825 tracing::warn!(uid = cred.uid, gid = cred.gid, pid = ?cred.pid, "unix connection rejected by allow list");
826 continue;
827 }
828 spawn_stream_request(session, Transport::Unix, versions.clone(), tx.clone());
829 }
830 Some(Err(err)) => tracing::warn!(%err, "unix qmux handshake failed"),
832 None => break,
833 }
834 }
835 })
836}
837
838#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
841fn spawn_stream_request(
842 session: qmux::Session,
843 transport: Transport,
844 versions: moq_net::Versions,
845 tx: tokio::sync::mpsc::Sender<Request>,
846) {
847 tokio::spawn(async move {
848 let server = moq_net::Server::new().with_versions(versions);
849 match server.accept_request(session).await {
850 Ok(request) => {
851 let request = Request {
852 transport,
853 url: None,
854 identity: None,
855 kind: RequestKind::Qmux(Box::new(request)),
856 };
857 let _ = tx.send(request).await;
858 }
859 Err(err) => tracing::debug!(%err, "stream SETUP handshake failed"),
860 }
861 });
862}
863
864pub(crate) enum RequestKind {
871 #[cfg(feature = "noq")]
872 Noq(Box<moq_net::Request<web_transport_noq::Session>>),
873 #[cfg(feature = "quinn")]
874 Quinn(Box<moq_net::Request<web_transport_quinn::Session>>),
875 #[cfg(feature = "quiche")]
876 Quiche(Box<moq_net::Request<web_transport_quiche::Connection>>),
877 #[cfg(feature = "iroh")]
878 Iroh(Box<moq_net::Request<web_transport_iroh::Session>>),
879 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
880 Qmux(Box<moq_net::Request<qmux::Session>>),
881}
882
883#[non_exhaustive]
885#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
886pub enum Transport {
887 Quic,
889 Iroh,
891 WebSocket,
893 Tcp,
895 Unix,
897}
898
899impl Transport {
900 pub const fn as_str(self) -> &'static str {
902 match self {
903 Self::Quic => "quic",
904 Self::Iroh => "iroh",
905 Self::WebSocket => "websocket",
906 Self::Tcp => "tcp",
907 Self::Unix => "unix",
908 }
909 }
910}
911
912impl std::fmt::Display for Transport {
913 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
914 f.write_str(self.as_str())
915 }
916}
917
918pub struct Request {
927 transport: Transport,
928 url: Option<Url>,
931 identity: Option<crate::tls::PeerIdentity>,
934 kind: RequestKind,
935}
936
937macro_rules! request_ref {
939 ($self:expr, $r:ident => $body:expr) => {
940 match &$self.kind {
941 #[cfg(feature = "noq")]
942 RequestKind::Noq($r) => $body,
943 #[cfg(feature = "quinn")]
944 RequestKind::Quinn($r) => $body,
945 #[cfg(feature = "quiche")]
946 RequestKind::Quiche($r) => $body,
947 #[cfg(feature = "iroh")]
948 RequestKind::Iroh($r) => $body,
949 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
950 RequestKind::Qmux($r) => $body,
951 }
952 };
953}
954
955macro_rules! request_into {
957 ($kind:expr, $r:ident => $body:expr) => {
958 match $kind {
959 #[cfg(feature = "noq")]
960 RequestKind::Noq($r) => $body,
961 #[cfg(feature = "quinn")]
962 RequestKind::Quinn($r) => $body,
963 #[cfg(feature = "quiche")]
964 RequestKind::Quiche($r) => $body,
965 #[cfg(feature = "iroh")]
966 RequestKind::Iroh($r) => $body,
967 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
968 RequestKind::Qmux($r) => $body,
969 }
970 };
971}
972
973macro_rules! request_map {
975 ($kind:expr, $r:ident => $body:expr) => {
976 match $kind {
977 #[cfg(feature = "noq")]
978 RequestKind::Noq($r) => RequestKind::Noq(Box::new($body)),
979 #[cfg(feature = "quinn")]
980 RequestKind::Quinn($r) => RequestKind::Quinn(Box::new($body)),
981 #[cfg(feature = "quiche")]
982 RequestKind::Quiche($r) => RequestKind::Quiche(Box::new($body)),
983 #[cfg(feature = "iroh")]
984 RequestKind::Iroh($r) => RequestKind::Iroh(Box::new($body)),
985 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
986 RequestKind::Qmux($r) => RequestKind::Qmux(Box::new($body)),
987 }
988 };
989}
990
991impl Request {
992 pub async fn close(self, code: u16) -> crate::Result<()> {
996 let err = match code {
997 401 | 403 => moq_net::Error::Unauthorized,
998 other => moq_net::Error::App(other),
999 };
1000 request_into!(self.kind, request => request.close(err));
1001 Ok(())
1002 }
1003
1004 pub fn with_publisher(self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
1006 let Request {
1007 transport,
1008 url,
1009 identity,
1010 kind,
1011 } = self;
1012 let kind = request_map!(kind, request => request.with_publisher(publish));
1013 Request {
1014 transport,
1015 url,
1016 identity,
1017 kind,
1018 }
1019 }
1020
1021 pub fn with_subscriber(self, subscribe: moq_net::origin::Producer) -> Self {
1023 let Request {
1024 transport,
1025 url,
1026 identity,
1027 kind,
1028 } = self;
1029 let kind = request_map!(kind, request => request.with_subscriber(subscribe));
1030 Request {
1031 transport,
1032 url,
1033 identity,
1034 kind,
1035 }
1036 }
1037
1038 pub fn with_stats(self, stats: moq_net::stats::Session) -> Self {
1040 let Request {
1041 transport,
1042 url,
1043 identity,
1044 kind,
1045 } = self;
1046 let kind = request_map!(kind, request => request.with_stats(stats));
1047 Request {
1048 transport,
1049 url,
1050 identity,
1051 kind,
1052 }
1053 }
1054
1055 pub async fn ok(self) -> crate::Result<Session> {
1057 let pair = request_into!(self.kind, request => request.ok().await?);
1058 Ok(crate::spawn_session(pair))
1059 }
1060
1061 pub fn transport(&self) -> Transport {
1063 self.transport
1064 }
1065
1066 pub fn url(&self) -> Option<&Url> {
1071 self.url.as_ref()
1072 }
1073
1074 pub fn path(&self) -> &str {
1080 let setup = request_ref!(self, r => r.path());
1084 if setup.is_empty() {
1085 self.url.as_ref().map(Url::path).unwrap_or("")
1086 } else {
1087 setup
1088 }
1089 }
1090
1091 pub fn role(&self) -> Option<moq_net::Role> {
1096 request_ref!(self, r => r.role())
1097 }
1098
1099 pub fn peer_origin(&self) -> Option<moq_net::Origin> {
1107 request_ref!(self, r => r.peer_origin())
1108 }
1109
1110 pub fn peer_identity(&self) -> Option<crate::tls::PeerIdentity> {
1118 self.identity.clone()
1119 }
1120
1121 #[doc(hidden)]
1122 #[deprecated(note = "use `peer_identity` instead")]
1123 pub fn has_peer_certificate(&self) -> bool {
1124 self.peer_identity().is_some()
1125 }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130 use super::*;
1131
1132 #[cfg(feature = "tcp")]
1140 #[test]
1141 fn accept_health_covers_stream_listeners_before_they_bind() {
1142 let mut config = ServerConfig::default();
1143 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1144 let server = Server::new(config).expect("stream-only server");
1145
1146 let names: Vec<_> = server.accept_health().iter().map(|h| h.listener()).collect();
1147 assert_eq!(names, vec!["tcp"], "the tcp listener must report before it binds");
1148 }
1149
1150 #[cfg(all(feature = "tcp", feature = "uds", unix))]
1156 #[tokio::test]
1157 async fn a_failed_listen_binds_nothing_and_can_be_retried() {
1158 let dir = tempfile::TempDir::new().unwrap();
1161 let occupied = dir.path().join("not-a-socket");
1162 std::fs::write(&occupied, b"in the way").unwrap();
1163
1164 let mut config = ServerConfig::default();
1165 config.tcp.bind = Some("127.0.0.1:0".parse().unwrap());
1166 config.unix.bind = Some(occupied);
1167 let mut server = Server::new(config).expect("stream-only server");
1168
1169 assert!(server.listen().await.is_err(), "the unix bind must fail");
1170 assert!(server.listen().await.is_err(), "a retry must not report success");
1173 }
1174
1175 #[cfg(all(feature = "quinn", not(feature = "tcp")))]
1178 #[test]
1179 fn accept_health_is_empty_without_a_stream_listener() {
1180 let server = ServerConfig::default().init().expect("quic server");
1181 assert!(server.accept_health().is_empty());
1182 }
1183
1184 #[test]
1185 fn transport_names_are_stable() {
1186 assert_eq!(Transport::Quic.as_str(), "quic");
1187 assert_eq!(Transport::Iroh.as_str(), "iroh");
1188 assert_eq!(Transport::WebSocket.as_str(), "websocket");
1189 assert_eq!(Transport::Tcp.as_str(), "tcp");
1190 assert_eq!(Transport::Unix.as_str(), "unix");
1191 }
1192
1193 #[cfg(feature = "quinn")]
1196 #[tokio::test]
1197 async fn certificates_expose_generated_fingerprints() {
1198 let mut config = ServerConfig {
1199 bind: Some("[::]:0".to_string()),
1200 ..Default::default()
1201 };
1202 config.tls.generate = vec!["localhost".into()];
1203
1204 let certs = config.init().expect("server init").certificates();
1205 let fingerprints = certs.fingerprints();
1206 assert_eq!(fingerprints.len(), 1, "one generated certificate");
1207 assert_eq!(fingerprints[0].len(), 64);
1209 assert!(fingerprints[0].chars().all(|c| c.is_ascii_hexdigit()));
1210 }
1211
1212 #[cfg(all(feature = "uds", unix))]
1215 #[tokio::test]
1216 async fn certificates_are_empty_without_a_tls_backend() {
1217 let mut config = ServerConfig::default();
1218 config.unix.bind = Some(PathBuf::from("/tmp/moq-native-certificates-test.sock"));
1219
1220 let server = config.init().expect("server init");
1221 assert!(server.certificates().fingerprints().is_empty());
1222 }
1223
1224 #[test]
1225 fn test_tls_string_or_array() {
1226 let single = r#"
1228 cert = "cert.pem"
1229 key = "key.pem"
1230 "#;
1231 let config: crate::tls::Server = toml::from_str(single).unwrap();
1232 assert_eq!(config.cert, vec![PathBuf::from("cert.pem")]);
1233 assert_eq!(config.key, vec![PathBuf::from("key.pem")]);
1234
1235 let array = r#"
1237 cert = ["a.pem", "b.pem"]
1238 key = ["a.key", "b.key"]
1239 generate = ["localhost"]
1240 root = ["ca.pem"]
1241 "#;
1242 let config: crate::tls::Server = toml::from_str(array).unwrap();
1243 assert_eq!(config.cert, vec![PathBuf::from("a.pem"), PathBuf::from("b.pem")]);
1244 assert_eq!(config.key, vec![PathBuf::from("a.key"), PathBuf::from("b.key")]);
1245 assert_eq!(config.generate, vec!["localhost".to_string()]);
1246 assert_eq!(config.root, vec![PathBuf::from("ca.pem")]);
1247 }
1248
1249 #[test]
1250 fn bind_string_or_listen_alias() {
1251 let bind: ServerConfig = toml::from_str(r#"bind = "[::]:443""#).unwrap();
1253 assert_eq!(bind.bind.as_deref(), Some("[::]:443"));
1254
1255 let alias: ServerConfig = toml::from_str(r#"listen = "0.0.0.0:4443""#).unwrap();
1256 assert_eq!(alias.bind.as_deref(), Some("0.0.0.0:4443"));
1257 }
1258
1259 #[cfg(all(feature = "uds", unix))]
1260 #[test]
1261 fn stream_listener_config_parses() {
1262 let config: ServerConfig = toml::from_str(
1263 r#"
1264bind = "[::]:443"
1265
1266[unix]
1267bind = "/run/moq.sock"
1268
1269[unix.allow]
1270uid = [1001, 1002]
1271"#,
1272 )
1273 .unwrap();
1274 assert_eq!(config.bind.as_deref(), Some("[::]:443"));
1275 assert_eq!(config.unix.bind.as_deref(), Some(std::path::Path::new("/run/moq.sock")));
1276 assert_eq!(config.unix.allow.as_ref().expect("allow").uid, vec![1001, 1002]);
1277 assert!(config.has_stream_listener());
1278 }
1279
1280 #[cfg(all(feature = "uds", unix))]
1281 #[test]
1282 fn stream_only_config_has_no_quic() {
1283 let mut config = ServerConfig::default();
1285 config.unix.bind = Some(PathBuf::from("/run/moq.sock"));
1286 assert!(config.has_stream_listener());
1287 assert!(config.bind.is_none());
1288
1289 assert!(!ServerConfig::default().has_stream_listener());
1291 }
1292}