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