1use std::net;
12use url::Url;
13
14const WIRE_VERSION: qmux::Version = qmux::Version::QMux01;
17
18#[derive(clap::Args, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
26#[group(id = "server-tcp")]
27#[serde(deny_unknown_fields, default)]
28#[non_exhaustive]
29pub struct Config {
30 #[arg(long = "server-tcp-bind", id = "server-tcp-bind", env = "MOQ_SERVER_TCP_BIND")]
32 #[serde(default, skip_serializing_if = "Option::is_none")]
33 pub bind: Option<net::SocketAddr>,
34}
35
36#[derive(Debug, thiserror::Error)]
38#[non_exhaustive]
39pub enum Error {
40 #[error(transparent)]
42 Io(#[from] std::io::Error),
43
44 #[error("missing hostname")]
46 MissingHostname,
47
48 #[error("missing port")]
50 MissingPort,
51
52 #[error("qmux connect failed")]
54 Connect(#[source] qmux::Error),
55
56 #[error("qmux accept failed")]
58 Accept(#[source] qmux::Error),
59}
60
61type Result<T> = std::result::Result<T, Error>;
62
63pub(crate) async fn connect(url: Url, protocols: &[&str]) -> Result<qmux::Session> {
68 let host = url.host_str().ok_or(Error::MissingHostname)?;
69 let port = url.port().ok_or(Error::MissingPort)?;
70
71 tracing::debug!(%url, "connecting via TCP");
72 qmux::tcp::Config::new(WIRE_VERSION)
73 .protocols(protocols.iter().copied())
74 .connect((host, port))
75 .await
76 .map_err(Error::Connect)
77}
78
79pub struct Listener {
81 listener: tokio::net::TcpListener,
82 protocols: Vec<String>,
83}
84
85impl Listener {
86 pub async fn bind(addr: net::SocketAddr) -> Result<Self> {
88 let listener = tokio::net::TcpListener::bind(addr).await?;
89 Ok(Self {
90 listener,
91 protocols: Vec::new(),
92 })
93 }
94
95 pub fn with_protocols<I, S>(mut self, protocols: I) -> Self
98 where
99 I: IntoIterator<Item = S>,
100 S: Into<String>,
101 {
102 self.protocols = protocols.into_iter().map(Into::into).collect();
103 self
104 }
105
106 pub fn local_addr(&self) -> Result<net::SocketAddr> {
108 Ok(self.listener.local_addr()?)
109 }
110
111 pub async fn accept(&self) -> Option<Result<qmux::Session>> {
116 match self.listener.accept().await {
117 Ok((stream, addr)) => {
118 tracing::debug!(%addr, "accepted TCP connection");
119 let session = qmux::tcp::Config::new(WIRE_VERSION)
120 .protocols(self.protocols.iter().map(String::as_str))
121 .accept(stream)
122 .await
123 .map_err(Error::Accept);
124 Some(session)
125 }
126 Err(e) => Some(Err(e.into())),
127 }
128 }
129}