use std::net;
use url::Url;
const WIRE_VERSION: qmux::Version = qmux::Version::QMux01;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("missing hostname")]
MissingHostname,
#[error("missing port")]
MissingPort,
#[error("qmux connect failed")]
Connect(#[source] qmux::Error),
#[error("qmux accept failed")]
Accept(#[source] qmux::Error),
}
type Result<T> = std::result::Result<T, Error>;
pub(crate) async fn connect(url: Url, protocols: &[&str]) -> Result<qmux::Session> {
let host = url.host_str().ok_or(Error::MissingHostname)?;
let port = url.port().ok_or(Error::MissingPort)?;
tracing::debug!(%url, "connecting via TCP");
qmux::tcp::Config::new(WIRE_VERSION)
.protocols(protocols.iter().copied())
.connect((host, port))
.await
.map_err(Error::Connect)
}
pub struct Listener {
listener: tokio::net::TcpListener,
protocols: Vec<String>,
}
impl Listener {
pub async fn bind(addr: net::SocketAddr) -> Result<Self> {
let listener = tokio::net::TcpListener::bind(addr).await?;
Ok(Self {
listener,
protocols: Vec::new(),
})
}
pub fn with_protocols<I, S>(mut self, protocols: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.protocols = protocols.into_iter().map(Into::into).collect();
self
}
pub fn local_addr(&self) -> Result<net::SocketAddr> {
Ok(self.listener.local_addr()?)
}
pub async fn accept(&self) -> Option<Result<qmux::Session>> {
match self.listener.accept().await {
Ok((stream, addr)) => {
tracing::debug!(%addr, "accepted TCP connection");
let session = qmux::tcp::Config::new(WIRE_VERSION)
.protocols(self.protocols.iter().map(String::as_str))
.accept(stream)
.await
.map_err(Error::Accept);
Some(session)
}
Err(e) => Some(Err(e.into())),
}
}
}