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 #[cfg(any(
346 feature = "noq",
347 feature = "quinn",
348 feature = "quiche",
349 feature = "iroh",
350 feature = "tcp",
351 all(feature = "uds", unix)
352 ))]
353 pub async fn accept(&mut self) -> Option<Request> {
354 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
357 if let Err(err) = self.streams.ensure_started().await {
358 tracing::error!(%err, "failed to bind stream listener");
359 return None;
360 }
361
362 loop {
363 #[cfg(feature = "noq")]
365 let noq_accept = async {
366 #[cfg(feature = "noq")]
367 if let Some(noq) = self.noq.as_mut() {
368 return noq.accept().await;
369 }
370 None
371 };
372 #[cfg(not(feature = "noq"))]
373 let noq_accept = async { None::<()> };
374
375 #[cfg(feature = "iroh")]
376 let iroh_accept = async {
377 #[cfg(feature = "iroh")]
378 if let Some(endpoint) = self.iroh.as_mut() {
379 return endpoint.accept().await;
380 }
381 None
382 };
383 #[cfg(not(feature = "iroh"))]
384 let iroh_accept = async { None::<()> };
385
386 #[cfg(feature = "quinn")]
387 let quinn_accept = async {
388 #[cfg(feature = "quinn")]
389 if let Some(quinn) = self.quinn.as_mut() {
390 return quinn.accept().await;
391 }
392 None
393 };
394 #[cfg(not(feature = "quinn"))]
395 let quinn_accept = async { None::<()> };
396
397 #[cfg(feature = "quiche")]
398 let quiche_accept = async {
399 #[cfg(feature = "quiche")]
400 if let Some(quiche) = self.quiche.as_mut() {
401 return quiche.accept().await;
402 }
403 None
404 };
405 #[cfg(not(feature = "quiche"))]
406 let quiche_accept = async { None::<()> };
407
408 #[cfg(feature = "websocket")]
409 let ws_ref = self.websocket.as_ref();
410 #[cfg(feature = "websocket")]
411 let ws_accept = async {
412 match ws_ref {
413 Some(ws) => ws.accept().await,
414 None => std::future::pending().await,
415 }
416 };
417 #[cfg(not(feature = "websocket"))]
418 let ws_accept = std::future::pending::<Option<crate::Result<()>>>();
419
420 #[allow(unused_variables)]
421 let server = self.moq.clone();
422 #[allow(unused_variables)]
423 let versions = self.versions.clone();
424
425 #[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
427 let stream_accept = self.streams.recv();
428 #[cfg(not(any(feature = "tcp", all(feature = "uds", unix))))]
429 let stream_accept = std::future::pending::<Option<Request>>();
430
431 tokio::select! {
432 Some(request) = stream_accept => {
433 return Some(request);
434 }
435 Some(_conn) = noq_accept => {
436 #[cfg(feature = "noq")]
437 {
438 let alpns = versions.alpns();
439 self.accept.push(async move {
440 let (session, url, identity) = super::noq::accept(_conn, alpns).await?;
444 let request = server.accept_request(session).await?;
445 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Noq(Box::new(request)) })
446 }.boxed());
447 }
448 }
449 Some(_conn) = quinn_accept => {
450 #[cfg(feature = "quinn")]
451 {
452 let alpns = versions.alpns();
453 self.accept.push(async move {
454 let (session, url, identity) = super::quinn::accept(_conn, alpns).await?;
455 let request = server.accept_request(session).await?;
456 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quinn(Box::new(request)) })
457 }.boxed());
458 }
459 }
460 Some(_conn) = quiche_accept => {
461 #[cfg(feature = "quiche")]
462 {
463 let alpns = versions.alpns();
464 self.accept.push(async move {
465 let (session, url, identity) = super::quiche::accept(_conn, alpns).await?;
466 let request = server.accept_request(session).await?;
467 Ok(Request { transport: Transport::Quic, url, identity, kind: RequestKind::Quiche(Box::new(request)) })
468 }.boxed());
469 }
470 }
471 Some(_conn) = iroh_accept => {
472 #[cfg(feature = "iroh")]
473 self.accept.push(async move {
474 let (session, url, identity) = super::iroh::accept(_conn).await?;
475 let request = server.accept_request(session).await?;
476 Ok(Request { transport: Transport::Iroh, url, identity, kind: RequestKind::Iroh(Box::new(request)) })
477 }.boxed());
478 }
479 Some(_res) = ws_accept => {
480 #[cfg(feature = "websocket")]
481 match _res {
482 Ok(session) => {
483 self.accept.push(async move {
486 let request = server.accept_request(session).await?;
487 Ok(Request { transport: Transport::WebSocket, url: None, identity: None, kind: RequestKind::Qmux(Box::new(request)) })
488 }.boxed());
489 }
490 Err(err) => tracing::debug!(%err, "failed to accept WebSocket session"),
491 }
492 }
493 Some(res) = self.accept.next() => {
494 match res {
495 Ok(session) => return Some(session),
496 Err(err) => tracing::debug!(%err, "failed to accept session"),
497 }
498 }
499 _ = tokio::signal::ctrl_c() => {
500 self.close().await;
501 return None;
502 }
503 }
504 }
505 }
506
507 #[cfg(feature = "iroh")]
509 pub fn iroh_endpoint(&self) -> Option<&iroh::Endpoint> {
510 self.iroh.as_ref()
511 }
512
513 pub fn local_addr(&self) -> crate::Result<net::SocketAddr> {
519 #[cfg(feature = "noq")]
520 if let Some(noq) = self.noq.as_ref() {
521 return Ok(noq.local_addr()?);
522 }
523 #[cfg(feature = "quinn")]
524 if let Some(quinn) = self.quinn.as_ref() {
525 return Ok(quinn.local_addr()?);
526 }
527 #[cfg(feature = "quiche")]
528 if let Some(quiche) = self.quiche.as_ref() {
529 return Ok(quiche.local_addr()?);
530 }
531 Err(Error::NoBackend("no QUIC listener configured"))
533 }
534
535 #[cfg(feature = "websocket")]
538 pub fn websocket_local_addr(&self) -> Option<net::SocketAddr> {
539 self.websocket.as_ref().and_then(|ws| ws.local_addr().ok())
540 }
541
542 pub async fn close(&mut self) {
547 #[cfg(feature = "noq")]
548 if let Some(noq) = self.noq.as_mut() {
549 noq.close();
550 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
551 }
552 #[cfg(feature = "quinn")]
553 if let Some(quinn) = self.quinn.as_mut() {
554 quinn.close();
555 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
556 }
557 #[cfg(feature = "quiche")]
558 if let Some(quiche) = self.quiche.as_mut() {
559 quiche.close();
560 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
561 }
562 #[cfg(feature = "iroh")]
563 if let Some(iroh) = self.iroh.take() {
564 iroh.close().await;
565 }
566 #[cfg(feature = "websocket")]
567 {
568 let _ = self.websocket.take();
569 }
570 #[cfg(not(any(feature = "noq", feature = "quinn", feature = "quiche", feature = "iroh")))]
571 unreachable!("no QUIC backend compiled");
572 }
573}
574
575async fn serve_session(request: Request) -> crate::Result<()> {
577 let session = request.ok().await?;
578 Err(session.closed().await.into())
579}
580
581#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
587fn stream_versions(base: &moq_net::Versions) -> moq_net::Versions {
588 let mut versions: Vec<moq_net::Version> = base.iter().copied().collect();
589 if let Ok(lite05) = "moq-lite-05".parse::<moq_net::Version>()
590 && !versions.contains(&lite05)
591 {
592 versions.push(lite05);
593 }
594 moq_net::Versions::from(versions)
595}
596
597#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
599enum StreamBind {
600 #[cfg(feature = "tcp")]
601 Tcp(net::SocketAddr),
602 #[cfg(all(feature = "uds", unix))]
603 Unix(PathBuf),
604}
605
606#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
613struct StreamListeners {
614 binds: Vec<StreamBind>,
615 versions: moq_net::Versions,
616 #[cfg(all(feature = "uds", unix))]
617 unix_allow: Option<crate::unix::Allow>,
618 rx: Option<tokio::sync::mpsc::Receiver<Request>>,
619 tasks: Vec<tokio::task::JoinHandle<()>>,
620}
621
622#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
623impl StreamListeners {
624 fn new(
625 binds: Vec<StreamBind>,
626 versions: moq_net::Versions,
627 #[cfg(all(feature = "uds", unix))] unix_allow: Option<crate::unix::Allow>,
628 ) -> Self {
629 Self {
630 binds,
631 versions,
632 #[cfg(all(feature = "uds", unix))]
633 unix_allow,
634 rx: None,
635 tasks: Vec::new(),
636 }
637 }
638
639 async fn ensure_started(&mut self) -> crate::Result<()> {
641 if self.rx.is_some() || self.binds.is_empty() {
642 return Ok(());
643 }
644
645 let (tx, rx) = tokio::sync::mpsc::channel(16);
646 for bind in self.binds.drain(..) {
647 let versions = self.versions.clone();
648 match bind {
649 #[cfg(feature = "tcp")]
650 StreamBind::Tcp(addr) => {
651 if !addr.ip().is_loopback() {
652 tracing::warn!(%addr, "tcp listener bound to a non-loopback address; qmux is UNENCRYPTED, ensure the network is trusted");
653 }
654 let listener = crate::tcp::Listener::bind(addr).await?.with_protocols(versions.alpns());
655 tracing::info!(%addr, "listening (tcp)");
656 self.tasks.push(spawn_tcp_loop(listener, versions, tx.clone()));
657 }
658 #[cfg(all(feature = "uds", unix))]
659 StreamBind::Unix(path) => {
660 let listener = crate::unix::Listener::bind(&path)
661 .await?
662 .with_protocols(versions.alpns());
663 listener.set_mode(0o666)?;
666 tracing::info!(path = %path.display(), allow = ?self.unix_allow, "listening (unix)");
667 self.tasks
668 .push(spawn_unix_loop(listener, versions, self.unix_allow.clone(), tx.clone()));
669 }
670 }
671 }
672
673 self.rx = Some(rx);
674 Ok(())
675 }
676
677 async fn recv(&mut self) -> Option<Request> {
679 match self.rx.as_mut() {
680 Some(rx) => rx.recv().await,
681 None => std::future::pending().await,
682 }
683 }
684}
685
686#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
687impl Drop for StreamListeners {
688 fn drop(&mut self) {
689 for task in &self.tasks {
691 task.abort();
692 }
693 }
694}
695
696#[cfg(feature = "tcp")]
697fn spawn_tcp_loop(
698 listener: crate::tcp::Listener,
699 versions: moq_net::Versions,
700 tx: tokio::sync::mpsc::Sender<Request>,
701) -> tokio::task::JoinHandle<()> {
702 tokio::spawn(async move {
703 loop {
704 match listener.accept().await {
705 Some(Ok(session)) => spawn_stream_request(session, Transport::Tcp, versions.clone(), tx.clone()),
706 Some(Err(err)) => tracing::warn!(%err, "tcp listener accept failed"),
707 None => break,
708 }
709 }
710 })
711}
712
713#[cfg(all(feature = "uds", unix))]
714fn spawn_unix_loop(
715 listener: crate::unix::Listener,
716 versions: moq_net::Versions,
717 allow: Option<crate::unix::Allow>,
718 tx: tokio::sync::mpsc::Sender<Request>,
719) -> tokio::task::JoinHandle<()> {
720 tokio::spawn(async move {
721 loop {
722 match listener.accept().await {
723 Some(Ok((session, cred))) => {
724 if let Some(allow) = &allow
726 && !allow.permits(&cred)
727 {
728 tracing::warn!(uid = cred.uid, gid = cred.gid, pid = ?cred.pid, "unix connection rejected by allow list");
729 continue;
730 }
731 spawn_stream_request(session, Transport::Unix, versions.clone(), tx.clone());
732 }
733 Some(Err(err)) => tracing::warn!(%err, "unix listener accept failed"),
734 None => break,
735 }
736 }
737 })
738}
739
740#[cfg(any(feature = "tcp", all(feature = "uds", unix)))]
743fn spawn_stream_request(
744 session: qmux::Session,
745 transport: Transport,
746 versions: moq_net::Versions,
747 tx: tokio::sync::mpsc::Sender<Request>,
748) {
749 tokio::spawn(async move {
750 let server = moq_net::Server::new().with_versions(versions);
751 match server.accept_request(session).await {
752 Ok(request) => {
753 let request = Request {
754 transport,
755 url: None,
756 identity: None,
757 kind: RequestKind::Qmux(Box::new(request)),
758 };
759 let _ = tx.send(request).await;
760 }
761 Err(err) => tracing::debug!(%err, "stream SETUP handshake failed"),
762 }
763 });
764}
765
766pub(crate) enum RequestKind {
773 #[cfg(feature = "noq")]
774 Noq(Box<moq_net::Request<web_transport_noq::Session>>),
775 #[cfg(feature = "quinn")]
776 Quinn(Box<moq_net::Request<web_transport_quinn::Session>>),
777 #[cfg(feature = "quiche")]
778 Quiche(Box<moq_net::Request<web_transport_quiche::Connection>>),
779 #[cfg(feature = "iroh")]
780 Iroh(Box<moq_net::Request<web_transport_iroh::Session>>),
781 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
782 Qmux(Box<moq_net::Request<qmux::Session>>),
783}
784
785#[non_exhaustive]
787#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
788pub enum Transport {
789 Quic,
791 Iroh,
793 WebSocket,
795 Tcp,
797 Unix,
799}
800
801impl Transport {
802 pub const fn as_str(self) -> &'static str {
804 match self {
805 Self::Quic => "quic",
806 Self::Iroh => "iroh",
807 Self::WebSocket => "websocket",
808 Self::Tcp => "tcp",
809 Self::Unix => "unix",
810 }
811 }
812}
813
814impl std::fmt::Display for Transport {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 f.write_str(self.as_str())
817 }
818}
819
820pub struct Request {
829 transport: Transport,
830 url: Option<Url>,
833 identity: Option<crate::tls::PeerIdentity>,
836 kind: RequestKind,
837}
838
839macro_rules! request_ref {
841 ($self:expr, $r:ident => $body:expr) => {
842 match &$self.kind {
843 #[cfg(feature = "noq")]
844 RequestKind::Noq($r) => $body,
845 #[cfg(feature = "quinn")]
846 RequestKind::Quinn($r) => $body,
847 #[cfg(feature = "quiche")]
848 RequestKind::Quiche($r) => $body,
849 #[cfg(feature = "iroh")]
850 RequestKind::Iroh($r) => $body,
851 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
852 RequestKind::Qmux($r) => $body,
853 }
854 };
855}
856
857macro_rules! request_into {
859 ($kind:expr, $r:ident => $body:expr) => {
860 match $kind {
861 #[cfg(feature = "noq")]
862 RequestKind::Noq($r) => $body,
863 #[cfg(feature = "quinn")]
864 RequestKind::Quinn($r) => $body,
865 #[cfg(feature = "quiche")]
866 RequestKind::Quiche($r) => $body,
867 #[cfg(feature = "iroh")]
868 RequestKind::Iroh($r) => $body,
869 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
870 RequestKind::Qmux($r) => $body,
871 }
872 };
873}
874
875macro_rules! request_map {
877 ($kind:expr, $r:ident => $body:expr) => {
878 match $kind {
879 #[cfg(feature = "noq")]
880 RequestKind::Noq($r) => RequestKind::Noq(Box::new($body)),
881 #[cfg(feature = "quinn")]
882 RequestKind::Quinn($r) => RequestKind::Quinn(Box::new($body)),
883 #[cfg(feature = "quiche")]
884 RequestKind::Quiche($r) => RequestKind::Quiche(Box::new($body)),
885 #[cfg(feature = "iroh")]
886 RequestKind::Iroh($r) => RequestKind::Iroh(Box::new($body)),
887 #[cfg(any(feature = "tcp", all(feature = "uds", unix), feature = "websocket"))]
888 RequestKind::Qmux($r) => RequestKind::Qmux(Box::new($body)),
889 }
890 };
891}
892
893impl Request {
894 pub async fn close(self, code: u16) -> crate::Result<()> {
898 let err = match code {
899 401 | 403 => moq_net::Error::Unauthorized,
900 other => moq_net::Error::App(other),
901 };
902 request_into!(self.kind, request => request.close(err));
903 Ok(())
904 }
905
906 pub fn with_publisher(self, publish: impl moq_net::Consume<moq_net::origin::Consumer>) -> Self {
908 let Request {
909 transport,
910 url,
911 identity,
912 kind,
913 } = self;
914 let kind = request_map!(kind, request => request.with_publisher(publish));
915 Request {
916 transport,
917 url,
918 identity,
919 kind,
920 }
921 }
922
923 pub fn with_subscriber(self, subscribe: moq_net::origin::Producer) -> Self {
925 let Request {
926 transport,
927 url,
928 identity,
929 kind,
930 } = self;
931 let kind = request_map!(kind, request => request.with_subscriber(subscribe));
932 Request {
933 transport,
934 url,
935 identity,
936 kind,
937 }
938 }
939
940 pub fn with_stats(self, stats: moq_net::stats::Session) -> Self {
942 let Request {
943 transport,
944 url,
945 identity,
946 kind,
947 } = self;
948 let kind = request_map!(kind, request => request.with_stats(stats));
949 Request {
950 transport,
951 url,
952 identity,
953 kind,
954 }
955 }
956
957 pub async fn ok(self) -> crate::Result<Session> {
959 let pair = request_into!(self.kind, request => request.ok().await?);
960 Ok(crate::spawn_session(pair))
961 }
962
963 pub fn transport(&self) -> Transport {
965 self.transport
966 }
967
968 pub fn url(&self) -> Option<&Url> {
973 self.url.as_ref()
974 }
975
976 pub fn path(&self) -> &str {
982 let setup = request_ref!(self, r => r.path());
986 if setup.is_empty() {
987 self.url.as_ref().map(Url::path).unwrap_or("")
988 } else {
989 setup
990 }
991 }
992
993 pub fn role(&self) -> Option<moq_net::Role> {
998 request_ref!(self, r => r.role())
999 }
1000
1001 pub fn peer_identity(&self) -> Option<crate::tls::PeerIdentity> {
1009 self.identity.clone()
1010 }
1011
1012 #[doc(hidden)]
1013 #[deprecated(note = "use `peer_identity` instead")]
1014 pub fn has_peer_certificate(&self) -> bool {
1015 self.peer_identity().is_some()
1016 }
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022
1023 #[test]
1024 fn transport_names_are_stable() {
1025 assert_eq!(Transport::Quic.as_str(), "quic");
1026 assert_eq!(Transport::Iroh.as_str(), "iroh");
1027 assert_eq!(Transport::WebSocket.as_str(), "websocket");
1028 assert_eq!(Transport::Tcp.as_str(), "tcp");
1029 assert_eq!(Transport::Unix.as_str(), "unix");
1030 }
1031
1032 #[cfg(feature = "quinn")]
1035 #[tokio::test]
1036 async fn certificates_expose_generated_fingerprints() {
1037 let mut config = ServerConfig {
1038 bind: Some("[::]:0".to_string()),
1039 ..Default::default()
1040 };
1041 config.tls.generate = vec!["localhost".into()];
1042
1043 let certs = config.init().expect("server init").certificates();
1044 let fingerprints = certs.fingerprints();
1045 assert_eq!(fingerprints.len(), 1, "one generated certificate");
1046 assert_eq!(fingerprints[0].len(), 64);
1048 assert!(fingerprints[0].chars().all(|c| c.is_ascii_hexdigit()));
1049 }
1050
1051 #[cfg(all(feature = "uds", unix))]
1054 #[tokio::test]
1055 async fn certificates_are_empty_without_a_tls_backend() {
1056 let mut config = ServerConfig::default();
1057 config.unix.bind = Some(PathBuf::from("/tmp/moq-native-certificates-test.sock"));
1058
1059 let server = config.init().expect("server init");
1060 assert!(server.certificates().fingerprints().is_empty());
1061 }
1062
1063 #[test]
1064 fn test_tls_string_or_array() {
1065 let single = r#"
1067 cert = "cert.pem"
1068 key = "key.pem"
1069 "#;
1070 let config: crate::tls::Server = toml::from_str(single).unwrap();
1071 assert_eq!(config.cert, vec![PathBuf::from("cert.pem")]);
1072 assert_eq!(config.key, vec![PathBuf::from("key.pem")]);
1073
1074 let array = r#"
1076 cert = ["a.pem", "b.pem"]
1077 key = ["a.key", "b.key"]
1078 generate = ["localhost"]
1079 root = ["ca.pem"]
1080 "#;
1081 let config: crate::tls::Server = toml::from_str(array).unwrap();
1082 assert_eq!(config.cert, vec![PathBuf::from("a.pem"), PathBuf::from("b.pem")]);
1083 assert_eq!(config.key, vec![PathBuf::from("a.key"), PathBuf::from("b.key")]);
1084 assert_eq!(config.generate, vec!["localhost".to_string()]);
1085 assert_eq!(config.root, vec![PathBuf::from("ca.pem")]);
1086 }
1087
1088 #[test]
1089 fn bind_string_or_listen_alias() {
1090 let bind: ServerConfig = toml::from_str(r#"bind = "[::]:443""#).unwrap();
1092 assert_eq!(bind.bind.as_deref(), Some("[::]:443"));
1093
1094 let alias: ServerConfig = toml::from_str(r#"listen = "0.0.0.0:4443""#).unwrap();
1095 assert_eq!(alias.bind.as_deref(), Some("0.0.0.0:4443"));
1096 }
1097
1098 #[cfg(all(feature = "uds", unix))]
1099 #[test]
1100 fn stream_listener_config_parses() {
1101 let config: ServerConfig = toml::from_str(
1102 r#"
1103bind = "[::]:443"
1104
1105[unix]
1106bind = "/run/moq.sock"
1107
1108[unix.allow]
1109uid = [1001, 1002]
1110"#,
1111 )
1112 .unwrap();
1113 assert_eq!(config.bind.as_deref(), Some("[::]:443"));
1114 assert_eq!(config.unix.bind.as_deref(), Some(std::path::Path::new("/run/moq.sock")));
1115 assert_eq!(config.unix.allow.as_ref().expect("allow").uid, vec![1001, 1002]);
1116 assert!(config.has_stream_listener());
1117 }
1118
1119 #[cfg(all(feature = "uds", unix))]
1120 #[test]
1121 fn stream_only_config_has_no_quic() {
1122 let mut config = ServerConfig::default();
1124 config.unix.bind = Some(PathBuf::from("/run/moq.sock"));
1125 assert!(config.has_stream_listener());
1126 assert!(config.bind.is_none());
1127
1128 assert!(!ServerConfig::default().has_stream_listener());
1130 }
1131}