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 let build_quic = config.bind.is_some() || !config.has_stream_listener();
149
150 if build_quic && !config.tls.root.is_empty() {
151 let mtls_supported = match backend {
152 #[cfg(feature = "quinn")]
153 QuicBackend::Quinn => true,
154 #[cfg(feature = "noq")]
155 QuicBackend::Noq => true,
156 #[allow(unreachable_patterns)]
157 _ => false,
158 };
159 if !mtls_supported {
160 return Err(Error::MtlsUnsupported);
161 }
162 }
163
164 #[cfg(feature = "noq")]
165 #[allow(unreachable_patterns)]
166 let noq = match backend {
167 QuicBackend::Noq if build_quic => Some(crate::noq::NoqServer::new(config.clone())?),
168 _ => None,
169 };
170
171 #[cfg(feature = "quinn")]
172 #[allow(unreachable_patterns)]
173 let quinn = match backend {
174 QuicBackend::Quinn if build_quic => Some(crate::quinn::QuinnServer::new(config.clone())?),
175 _ => None,
176 };
177
178 #[cfg(feature = "quiche")]
179 let quiche = match backend {
180 QuicBackend::Quiche if build_quic => Some(crate::quiche::QuicheServer::new(config.clone())?),
181 _ => None,
182 };
183
184 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
186 let mut stream_binds = Vec::new();
187 #[cfg(feature = "tcp")]
188 if let Some(addr) = config.tcp.bind {
189 stream_binds.push(StreamBind::Tcp(addr));
190 }
191 #[cfg(all(feature = "uds", unix))]
192 if let Some(path) = config.unix.bind.clone() {
193 stream_binds.push(StreamBind::Unix(path));
194 }
195 #[cfg(all(feature = "uds", unix))]
197 let unix_allow = config.unix.allow.clone().filter(|allow| !allow.is_empty());
198 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
199 let streams = StreamListeners::new(
200 stream_binds,
201 stream_versions(&versions),
202 #[cfg(all(feature = "uds", unix))]
203 unix_allow,
204 );
205
206 Ok(Server {
207 accept: Default::default(),
208 moq: moq_net::Server::new().with_versions(versions.clone()),
209 versions,
210 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
211 streams,
212 #[cfg(feature = "iroh")]
213 iroh: None,
214 #[cfg(feature = "noq")]
215 noq,
216 #[cfg(feature = "quinn")]
217 quinn,
218 #[cfg(feature = "quiche")]
219 quiche,
220 #[cfg(feature = "websocket")]
221 websocket: None,
222 })
223 }
224
225 #[cfg(feature = "websocket")]
231 pub fn with_websocket(mut self, websocket: crate::websocket::Listener) -> Self {
232 self.websocket = Some(websocket);
233 self
234 }
235
236 #[cfg(feature = "iroh")]
238 pub fn with_iroh(mut self, iroh: iroh::Endpoint) -> Self {
239 self.iroh = Some(iroh);
240 self
241 }
242
243 pub fn with_publisher(mut self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
245 self.moq = self.moq.with_publisher(publish);
246 self
247 }
248
249 pub fn with_subscriber(mut self, subscribe: moq_net::origin::Producer) -> Self {
251 self.moq = self.moq.with_subscriber(subscribe);
252 self
253 }
254
255 pub fn with_stats(mut self, stats: moq_net::stats::Session) -> Self {
258 self.moq = self.moq.with_stats(stats);
259 self
260 }
261
262 pub async fn serve_publish(self, origin: moq_net::origin::Consumer) -> crate::Result<()> {
269 self.with_publisher(origin).serve().await
270 }
271
272 pub async fn serve_consume(self, origin: moq_net::origin::Producer) -> crate::Result<()> {
276 self.with_subscriber(origin).serve().await
277 }
278
279 async fn serve(mut self) -> crate::Result<()> {
282 if let Ok(addr) = self.local_addr() {
283 tracing::info!(%addr, "listening");
284 }
285 while let Some(request) = self.accept().await {
286 tokio::spawn(async move {
287 if let Err(err) = serve_session(request).await {
288 tracing::warn!(%err, "session ended with error");
289 }
290 });
291 }
292 Ok(())
293 }
294
295 pub fn certificates(&self) -> crate::tls::Certificates {
304 #[cfg(feature = "noq")]
305 if let Some(noq) = self.noq.as_ref() {
306 return noq.certificates();
307 }
308 #[cfg(feature = "quinn")]
309 if let Some(quinn) = self.quinn.as_ref() {
310 return quinn.certificates();
311 }
312 #[cfg(feature = "quiche")]
313 if let Some(quiche) = self.quiche.as_ref() {
314 return quiche.certificates();
315 }
316 crate::tls::Certificates::empty()
318 }
319
320 #[cfg(not(any(
321 feature = "noq",
322 feature = "quinn",
323 feature = "quiche",
324 feature = "iroh",
325 feature = "tcp",
326 all(feature = "uds", unix)
327 )))]
328 pub async fn accept(&mut self) -> Option<Request> {
332 unreachable!("no transport compiled; enable a QUIC backend, tcp, or uds feature");
333 }
334
335 #[cfg(any(
342 feature = "noq",
343 feature = "quinn",
344 feature = "quiche",
345 feature = "iroh",
346 feature = "tcp",
347 all(feature = "uds", unix)
348 ))]
349 pub async fn accept(&mut self) -> Option<Request> {
350 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
353 if let Err(err) = self.streams.ensure_started().await {
354 tracing::error!(%err, "failed to bind stream listener");
355 return None;
356 }
357
358 loop {
359 #[cfg(feature = "noq")]
361 let noq_accept = async {
362 #[cfg(feature = "noq")]
363 if let Some(noq) = self.noq.as_mut() {
364 return noq.accept().await;
365 }
366 None
367 };
368 #[cfg(not(feature = "noq"))]
369 let noq_accept = async { None::<()> };
370
371 #[cfg(feature = "iroh")]
372 let iroh_accept = async {
373 #[cfg(feature = "iroh")]
374 if let Some(endpoint) = self.iroh.as_mut() {
375 return endpoint.accept().await;
376 }
377 None
378 };
379 #[cfg(not(feature = "iroh"))]
380 let iroh_accept = async { None::<()> };
381
382 #[cfg(feature = "quinn")]
383 let quinn_accept = async {
384 #[cfg(feature = "quinn")]
385 if let Some(quinn) = self.quinn.as_mut() {
386 return quinn.accept().await;
387 }
388 None
389 };
390 #[cfg(not(feature = "quinn"))]
391 let quinn_accept = async { None::<()> };
392
393 #[cfg(feature = "quiche")]
394 let quiche_accept = async {
395 #[cfg(feature = "quiche")]
396 if let Some(quiche) = self.quiche.as_mut() {
397 return quiche.accept().await;
398 }
399 None
400 };
401 #[cfg(not(feature = "quiche"))]
402 let quiche_accept = async { None::<()> };
403
404 #[cfg(feature = "websocket")]
405 let ws_ref = self.websocket.as_ref();
406 #[cfg(feature = "websocket")]
407 let ws_accept = async {
408 match ws_ref {
409 Some(ws) => ws.accept().await,
410 None => std::future::pending().await,
411 }
412 };
413 #[cfg(not(feature = "websocket"))]
414 let ws_accept = std::future::pending::<Option<crate::Result<()>>>();
415
416 #[allow(unused_variables)]
417 let server = self.moq.clone();
418 #[allow(unused_variables)]
419 let versions = self.versions.clone();
420
421 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
423 let stream_accept = self.streams.recv();
424 #[cfg(not(any(feature = "tcp", all(feature = "uds", unix))))]
425 let stream_accept = std::future::pending::<Option<Request>>();
426
427 tokio::select! {
428 Some(request) = stream_accept => {
429 return Some(request);
430 }
431 Some(_conn) = noq_accept => {
432 #[cfg(feature = "noq")]
433 {
434 let alpns = versions.alpns();
435 self.accept.push(async move {
436 let (session, url, identity) = super::noq::accept(_conn, alpns).await?;
440 let request = server.accept_request(session).await?;
441 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Noq(Box::new(request)) })
442 }.boxed());
443 }
444 }
445 Some(_conn) = quinn_accept => {
446 #[cfg(feature = "quinn")]
447 {
448 let alpns = versions.alpns();
449 self.accept.push(async move {
450 let (session, url, identity) = super::quinn::accept(_conn, alpns).await?;
451 let request = server.accept_request(session).await?;
452 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quinn(Box::new(request)) })
453 }.boxed());
454 }
455 }
456 Some(_conn) = quiche_accept => {
457 #[cfg(feature = "quiche")]
458 {
459 let alpns = versions.alpns();
460 self.accept.push(async move {
461 let (session, url, identity) = super::quiche::accept(_conn, alpns).await?;
462 let request = server.accept_request(session).await?;
463 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quiche(Box::new(request)) })
464 }.boxed());
465 }
466 }
467 Some(_conn) = iroh_accept => {
468 #[cfg(feature = "iroh")]
469 self.accept.push(async move {
470 let (session, url, identity) = super::iroh::accept(_conn).await?;
471 let request = server.accept_request(session).await?;
472 Ok(Request { transport: Transport::Iroh, url, identity, kind: RequestKind::Iroh(Box::new(request)) })
473 }.boxed());
474 }
475 Some(_res) = ws_accept => {
476 #[cfg(feature = "websocket")]
477 match _res {
478 Ok(session) => {
479 self.accept.push(async move {
482 let request = server.accept_request(session).await?;
483 Ok(Request { transport: Transport::WebSocket, url: None, identity: None, kind: RequestKind::Qmux(Box::new(request)) })
484 }.boxed());
485 }
486 Err(err) => tracing::debug!(%err, "failed to accept WebSocket session"),
487 }
488 }
489 Some(res) = self.accept.next() => {
490 match res {
491 Ok(session) => return Some(session),
492 Err(err) => tracing::debug!(%err, "failed to accept session"),
493 }
494 }
495 _ = tokio::signal::ctrl_c() => {
496 self.close().await;
497 return None;
498 }
499 }
500 }
501 }
502
503 #[cfg(feature = "iroh")]
505 pub fn iroh_endpoint(&self) -> Option<&iroh::Endpoint> {
506 self.iroh.as_ref()
507 }
508
509 pub fn local_addr(&self) -> crate::Result<net::SocketAddr> {
515 #[cfg(feature = "noq")]
516 if let Some(noq) = self.noq.as_ref() {
517 return Ok(noq.local_addr()?);
518 }
519 #[cfg(feature = "quinn")]
520 if let Some(quinn) = self.quinn.as_ref() {
521 return Ok(quinn.local_addr()?);
522 }
523 #[cfg(feature = "quiche")]
524 if let Some(quiche) = self.quiche.as_ref() {
525 return Ok(quiche.local_addr()?);
526 }
527 Err(Error::NoBackend("no QUIC listener configured"))
529 }
530
531 #[cfg(feature = "websocket")]
534 pub fn websocket_local_addr(&self) -> Option<net::SocketAddr> {
535 self.websocket.as_ref().and_then(|ws| ws.local_addr().ok())
536 }
537
538 pub async fn close(&mut self) {
543 #[cfg(feature = "noq")]
544 if let Some(noq) = self.noq.as_mut() {
545 noq.close();
546 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
547 }
548 #[cfg(feature = "quinn")]
549 if let Some(quinn) = self.quinn.as_mut() {
550 quinn.close();
551 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
552 }
553 #[cfg(feature = "quiche")]
554 if let Some(quiche) = self.quiche.as_mut() {
555 quiche.close();
556 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
557 }
558 #[cfg(feature = "iroh")]
559 if let Some(iroh) = self.iroh.take() {
560 iroh.close().await;
561 }
562 #[cfg(feature = "websocket")]
563 {
564 let _ = self.websocket.take();
565 }
566 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche", feature = "iroh")))]
567 unreachable!("no QUIC backend compiled");
568 }
569}
570
571async fn serve_session(request: Request) -> crate::Result<()> {
573 let session = request.ok().await?;
574 Err(session.closed().await.into())
575}
576
577#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
583fn stream_versions(base: &moq_net::Versions) -> moq_net::Versions {
584 let mut versions: Vec<moq_net::Version> = base.iter().copied().collect();
585 if let Ok(lite05) = "moq-lite-05".parse::<moq_net::Version>() {
586 if !versions.contains(&lite05) {
587 versions.push(lite05);
588 }
589 }
590 moq_net::Versions::from(versions)
591}
592
593#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
595enum StreamBind {
596 #[cfg(feature = "tcp")]
597 Tcp(net::SocketAddr),
598 #[cfg(all(feature = "uds", unix))]
599 Unix(PathBuf),
600}
601
602#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
609struct StreamListeners {
610 binds: Vec<StreamBind>,
611 versions: moq_net::Versions,
612 #[cfg(all(feature = "uds", unix))]
613 unix_allow: Option<crate::unix::Allow>,
614 rx: Option<tokio::sync::mpsc::Receiver<Request>>,
615 tasks: Vec<tokio::task::JoinHandle<()>>,
616}
617
618#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
619impl StreamListeners {
620 fn new(
621 binds: Vec<StreamBind>,
622 versions: moq_net::Versions,
623 #[cfg(all(feature = "uds", unix))] unix_allow: Option<crate::unix::Allow>,
624 ) -> Self {
625 Self {
626 binds,
627 versions,
628 #[cfg(all(feature = "uds", unix))]
629 unix_allow,
630 rx: None,
631 tasks: Vec::new(),
632 }
633 }
634
635 async fn ensure_started(&mut self) -> crate::Result<()> {
637 if self.rx.is_some() || self.binds.is_empty() {
638 return Ok(());
639 }
640
641 let (tx, rx) = tokio::sync::mpsc::channel(16);
642 for bind in self.binds.drain(..) {
643 let versions = self.versions.clone();
644 match bind {
645 #[cfg(feature = "tcp")]
646 StreamBind::Tcp(addr) => {
647 if !addr.ip().is_loopback() {
648 tracing::warn!(%addr, "tcp listener bound to a non-loopback address; qmux is UNENCRYPTED, ensure the network is trusted");
649 }
650 let listener = crate::tcp::Listener::bind(addr).await?.with_protocols(versions.alpns());
651 tracing::info!(%addr, "listening (tcp)");
652 self.tasks.push(spawn_tcp_loop(listener, versions, tx.clone()));
653 }
654 #[cfg(all(feature = "uds", unix))]
655 StreamBind::Unix(path) => {
656 let listener = crate::unix::Listener::bind(&path)
657 .await?
658 .with_protocols(versions.alpns());
659 listener.set_mode(0o666)?;
662 tracing::info!(path = %path.display(), allow = ?self.unix_allow, "listening (unix)");
663 self.tasks
664 .push(spawn_unix_loop(listener, versions, self.unix_allow.clone(), tx.clone()));
665 }
666 }
667 }
668
669 self.rx = Some(rx);
670 Ok(())
671 }
672
673 async fn recv(&mut self) -> Option<Request> {
675 match self.rx.as_mut() {
676 Some(rx) => rx.recv().await,
677 None => std::future::pending().await,
678 }
679 }
680}
681
682#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
683impl Drop for StreamListeners {
684 fn drop(&mut self) {
685 for task in &self.tasks {
687 task.abort();
688 }
689 }
690}
691
692#[cfg(feature = "tcp")]
693fn spawn_tcp_loop(
694 listener: crate::tcp::Listener,
695 versions: moq_net::Versions,
696 tx: tokio::sync::mpsc::Sender<Request>,
697) -> tokio::task::JoinHandle<()> {
698 tokio::spawn(async move {
699 loop {
700 match listener.accept().await {
701 Some(Ok(session)) => spawn_stream_request(session, Transport::Tcp, versions.clone(), tx.clone()),
702 Some(Err(err)) => tracing::warn!(%err, "tcp listener accept failed"),
703 None => break,
704 }
705 }
706 })
707}
708
709#[cfg(all(feature = "uds", unix))]
710fn spawn_unix_loop(
711 listener: crate::unix::Listener,
712 versions: moq_net::Versions,
713 allow: Option<crate::unix::Allow>,
714 tx: tokio::sync::mpsc::Sender<Request>,
715) -> tokio::task::JoinHandle<()> {
716 tokio::spawn(async move {
717 loop {
718 match listener.accept().await {
719 Some(Ok((session, cred))) => {
720 if let Some(allow) = &allow
722 && !allow.permits(&cred)
723 {
724 tracing::warn!(uid = cred.uid, gid = cred.gid, pid = ?cred.pid, "unix connection rejected by allow list");
725 continue;
726 }
727 spawn_stream_request(session, Transport::Unix, versions.clone(), tx.clone());
728 }
729 Some(Err(err)) => tracing::warn!(%err, "unix listener accept failed"),
730 None => break,
731 }
732 }
733 })
734}
735
736#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
739fn spawn_stream_request(
740 session: qmux::Session,
741 transport: Transport,
742 versions: moq_net::Versions,
743 tx: tokio::sync::mpsc::Sender<Request>,
744) {
745 tokio::spawn(async move {
746 let server = moq_net::Server::new().with_versions(versions);
747 match server.accept_request(session).await {
748 Ok(request) => {
749 let request = Request {
750 transport,
751 url: None,
752 identity: None,
753 kind: RequestKind::Qmux(Box::new(request)),
754 };
755 let _ = tx.send(request).await;
756 }
757 Err(err) => tracing::debug!(%err, "stream SETUP handshake failed"),
758 }
759 });
760}
761
762pub(crate) enum RequestKind {
769 #[cfg(feature = "noq")]
770 Noq(Box<moq_net::Request<web_transport_noq::Session>>),
771 #[cfg(feature = "quinn")]
772 Quinn(Box<moq_net::Request<web_transport_quinn::Session>>),
773 #[cfg(feature = "quiche")]
774 Quiche(Box<moq_net::Request<web_transport_quiche::Connection>>),
775 #[cfg(feature = "iroh")]
776 Iroh(Box<moq_net::Request<web_transport_iroh::Session>>),
777 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
778 Qmux(Box<moq_net::Request<qmux::Session>>),
779}
780
781#[non_exhaustive]
783#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
784pub enum Transport {
785 Quic,
787 Iroh,
789 WebSocket,
791 Tcp,
793 Unix,
795}
796
797impl Transport {
798 pub const fn as_str(self) -> &'static str {
800 match self {
801 Self::Quic => "quic",
802 Self::Iroh => "iroh",
803 Self::WebSocket => "websocket",
804 Self::Tcp => "tcp",
805 Self::Unix => "unix",
806 }
807 }
808}
809
810impl std::fmt::Display for Transport {
811 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
812 f.write_str(self.as_str())
813 }
814}
815
816pub struct Request {
825 transport: Transport,
826 url: Option<Url>,
829 identity: Option<crate::tls::PeerIdentity>,
832 kind: RequestKind,
833}
834
835macro_rules! request_ref {
837 ($self:expr, $r:ident => $body:expr) => {
838 match &$self.kind {
839 #[cfg(feature = "noq")]
840 RequestKind::Noq($r) => $body,
841 #[cfg(feature = "quinn")]
842 RequestKind::Quinn($r) => $body,
843 #[cfg(feature = "quiche")]
844 RequestKind::Quiche($r) => $body,
845 #[cfg(feature = "iroh")]
846 RequestKind::Iroh($r) => $body,
847 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
848 RequestKind::Qmux($r) => $body,
849 }
850 };
851}
852
853macro_rules! request_into {
855 ($kind:expr, $r:ident => $body:expr) => {
856 match $kind {
857 #[cfg(feature = "noq")]
858 RequestKind::Noq($r) => $body,
859 #[cfg(feature = "quinn")]
860 RequestKind::Quinn($r) => $body,
861 #[cfg(feature = "quiche")]
862 RequestKind::Quiche($r) => $body,
863 #[cfg(feature = "iroh")]
864 RequestKind::Iroh($r) => $body,
865 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
866 RequestKind::Qmux($r) => $body,
867 }
868 };
869}
870
871macro_rules! request_map {
873 ($kind:expr, $r:ident => $body:expr) => {
874 match $kind {
875 #[cfg(feature = "noq")]
876 RequestKind::Noq($r) => RequestKind::Noq(Box::new($body)),
877 #[cfg(feature = "quinn")]
878 RequestKind::Quinn($r) => RequestKind::Quinn(Box::new($body)),
879 #[cfg(feature = "quiche")]
880 RequestKind::Quiche($r) => RequestKind::Quiche(Box::new($body)),
881 #[cfg(feature = "iroh")]
882 RequestKind::Iroh($r) => RequestKind::Iroh(Box::new($body)),
883 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
884 RequestKind::Qmux($r) => RequestKind::Qmux(Box::new($body)),
885 }
886 };
887}
888
889impl Request {
890 pub async fn close(self, code: u16) -> crate::Result<()> {
894 let err = match code {
895 401 | 403 => moq_net::Error::Unauthorized,
896 other => moq_net::Error::App(other),
897 };
898 request_into!(self.kind, request => request.close(err));
899 Ok(())
900 }
901
902 pub fn with_publisher(self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
904 let Request {
905 transport,
906 url,
907 identity,
908 kind,
909 } = self;
910 let kind = request_map!(kind, request => request.with_publisher(publish));
911 Request {
912 transport,
913 url,
914 identity,
915 kind,
916 }
917 }
918
919 pub fn with_subscriber(self, subscribe: moq_net::origin::Producer) -> Self {
921 let Request {
922 transport,
923 url,
924 identity,
925 kind,
926 } = self;
927 let kind = request_map!(kind, request => request.with_subscriber(subscribe));
928 Request {
929 transport,
930 url,
931 identity,
932 kind,
933 }
934 }
935
936 pub fn with_stats(self, stats: moq_net::stats::Session) -> Self {
938 let Request {
939 transport,
940 url,
941 identity,
942 kind,
943 } = self;
944 let kind = request_map!(kind, request => request.with_stats(stats));
945 Request {
946 transport,
947 url,
948 identity,
949 kind,
950 }
951 }
952
953 pub async fn ok(self) -> crate::Result<Session> {
955 let pair = request_into!(self.kind, request => request.ok().await?);
956 Ok(crate::spawn_session(pair))
957 }
958
959 pub fn transport(&self) -> Transport {
961 self.transport
962 }
963
964 pub fn url(&self) -> Option<&Url> {
969 self.url.as_ref()
970 }
971
972 pub fn path(&self) -> &str {
978 let setup = request_ref!(self, r => r.path());
982 if setup.is_empty() {
983 self.url.as_ref().map(Url::path).unwrap_or("")
984 } else {
985 setup
986 }
987 }
988
989 pub fn role(&self) -> Option<moq_net::Role> {
994 request_ref!(self, r => r.role())
995 }
996
997 pub fn peer_identity(&self) -> Option<crate::tls::PeerIdentity> {
1005 self.identity.clone()
1006 }
1007
1008 #[doc(hidden)]
1009 #[deprecated(note = "use `peer_identity` instead")]
1010 pub fn has_peer_certificate(&self) -> bool {
1011 self.peer_identity().is_some()
1012 }
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018
1019 #[test]
1020 fn transport_names_are_stable() {
1021 assert_eq!(Transport::Quic.as_str(), "quic");
1022 assert_eq!(Transport::Iroh.as_str(), "iroh");
1023 assert_eq!(Transport::WebSocket.as_str(), "websocket");
1024 assert_eq!(Transport::Tcp.as_str(), "tcp");
1025 assert_eq!(Transport::Unix.as_str(), "unix");
1026 }
1027
1028 #[cfg(feature = "quinn")]
1031 #[tokio::test]
1032 async fn certificates_expose_generated_fingerprints() {
1033 let mut config = ServerConfig {
1034 bind: Some("[::]:0".to_string()),
1035 ..Default::default()
1036 };
1037 config.tls.generate = vec!["localhost".into()];
1038
1039 let certs = config.init().expect("server init").certificates();
1040 let fingerprints = certs.fingerprints();
1041 assert_eq!(fingerprints.len(), 1, "one generated certificate");
1042 assert_eq!(fingerprints[0].len(), 64);
1044 assert!(fingerprints[0].chars().all(|c| c.is_ascii_hexdigit()));
1045 }
1046
1047 #[cfg(all(feature = "uds", unix))]
1050 #[tokio::test]
1051 async fn certificates_are_empty_without_a_tls_backend() {
1052 let mut config = ServerConfig::default();
1053 config.unix.bind = Some(PathBuf::from("/tmp/moq-native-certificates-test.sock"));
1054
1055 let server = config.init().expect("server init");
1056 assert!(server.certificates().fingerprints().is_empty());
1057 }
1058
1059 #[test]
1060 fn test_tls_string_or_array() {
1061 let single = r#"
1063 cert = "cert.pem"
1064 key = "key.pem"
1065 "#;
1066 let config: crate::tls::Server = toml::from_str(single).unwrap();
1067 assert_eq!(config.cert, vec![PathBuf::from("cert.pem")]);
1068 assert_eq!(config.key, vec![PathBuf::from("key.pem")]);
1069
1070 let array = r#"
1072 cert = ["a.pem", "b.pem"]
1073 key = ["a.key", "b.key"]
1074 generate = ["localhost"]
1075 root = ["ca.pem"]
1076 "#;
1077 let config: crate::tls::Server = toml::from_str(array).unwrap();
1078 assert_eq!(config.cert, vec![PathBuf::from("a.pem"), PathBuf::from("b.pem")]);
1079 assert_eq!(config.key, vec![PathBuf::from("a.key"), PathBuf::from("b.key")]);
1080 assert_eq!(config.generate, vec!["localhost".to_string()]);
1081 assert_eq!(config.root, vec![PathBuf::from("ca.pem")]);
1082 }
1083
1084 #[test]
1085 fn bind_string_or_listen_alias() {
1086 let bind: ServerConfig = toml::from_str(r#"bind = "[::]:443""#).unwrap();
1088 assert_eq!(bind.bind.as_deref(), Some("[::]:443"));
1089
1090 let alias: ServerConfig = toml::from_str(r#"listen = "0.0.0.0:4443""#).unwrap();
1091 assert_eq!(alias.bind.as_deref(), Some("0.0.0.0:4443"));
1092 }
1093
1094 #[cfg(all(feature = "uds", unix))]
1095 #[test]
1096 fn stream_listener_config_parses() {
1097 let config: ServerConfig = toml::from_str(
1098 r#"
1099bind = "[::]:443"
1100
1101[unix]
1102bind = "/run/moq.sock"
1103
1104[unix.allow]
1105uid = [1001, 1002]
1106"#,
1107 )
1108 .unwrap();
1109 assert_eq!(config.bind.as_deref(), Some("[::]:443"));
1110 assert_eq!(config.unix.bind.as_deref(), Some(std::path::Path::new("/run/moq.sock")));
1111 assert_eq!(config.unix.allow.as_ref().expect("allow").uid, vec![1001, 1002]);
1112 assert!(config.has_stream_listener());
1113 }
1114
1115 #[cfg(all(feature = "uds", unix))]
1116 #[test]
1117 fn stream_only_config_has_no_quic() {
1118 let mut config = ServerConfig::default();
1120 config.unix.bind = Some(PathBuf::from("/run/moq.sock"));
1121 assert!(config.has_stream_listener());
1122 assert!(config.bind.is_none());
1123
1124 assert!(!ServerConfig::default().has_stream_listener());
1126 }
1127}