1use std::net;
12use url::Url;
13
14const WIRE_VERSION: qmux::Version = qmux::Version::QMux01;
17
18#[derive(Debug, thiserror::Error)]
20#[non_exhaustive]
21pub enum Error {
22 #[error(transparent)]
24 Io(#[from] std::io::Error),
25
26 #[error("missing hostname")]
28 MissingHostname,
29
30 #[error("missing port")]
32 MissingPort,
33
34 #[error("qmux connect failed")]
36 Connect(#[source] qmux::Error),
37
38 #[error("qmux accept failed")]
40 Accept(#[source] qmux::Error),
41}
42
43type Result<T> = std::result::Result<T, Error>;
44
45pub(crate) async fn connect(url: Url, protocols: &[&str]) -> Result<qmux::Session> {
50 let host = url.host_str().ok_or(Error::MissingHostname)?;
51 let port = url.port().ok_or(Error::MissingPort)?;
52
53 tracing::debug!(%url, "connecting via TCP");
54 qmux::tcp::Config::new(WIRE_VERSION)
55 .protocols(protocols.iter().copied())
56 .connect((host, port))
57 .await
58 .map_err(Error::Connect)
59}
60
61pub struct Listener {
63 listener: tokio::net::TcpListener,
64 protocols: Vec<String>,
65}
66
67impl Listener {
68 pub async fn bind(addr: net::SocketAddr) -> Result<Self> {
70 let listener = tokio::net::TcpListener::bind(addr).await?;
71 Ok(Self {
72 listener,
73 protocols: Vec::new(),
74 })
75 }
76
77 pub fn with_protocols<I, S>(mut self, protocols: I) -> Self
80 where
81 I: IntoIterator<Item = S>,
82 S: Into<String>,
83 {
84 self.protocols = protocols.into_iter().map(Into::into).collect();
85 self
86 }
87
88 pub fn local_addr(&self) -> Result<net::SocketAddr> {
90 Ok(self.listener.local_addr()?)
91 }
92
93 pub async fn accept(&self) -> Option<Result<qmux::Session>> {
98 match self.listener.accept().await {
99 Ok((stream, addr)) => {
100 tracing::debug!(%addr, "accepted TCP connection");
101 let session = qmux::tcp::Config::new(WIRE_VERSION)
102 .protocols(self.protocols.iter().map(String::as_str))
103 .accept(stream)
104 .await
105 .map_err(Error::Accept);
106 Some(session)
107 }
108 Err(e) => Some(Err(e.into())),
109 }
110 }
111}