use std::sync::Arc;
use tokio::net::{TcpStream, ToSocketAddrs};
use tokio_rustls::TlsAcceptor;
use tokio_rustls::TlsConnector;
use crate::transport::Stream;
use crate::{alpn, Config, Error, Session, Version};
#[derive(Clone)]
pub struct Client {
config: Arc<rustls::ClientConfig>,
protocols: Vec<(String, Vec<Version>)>,
require_protocol: bool,
}
impl Client {
pub fn new(config: Arc<rustls::ClientConfig>) -> Self {
Self {
config,
protocols: Vec::new(),
require_protocol: false,
}
}
pub fn with_protocol(mut self, alpn: &str, versions: &[Version]) -> Self {
self.protocols.push((alpn.to_string(), versions.to_vec()));
self
}
pub fn with_protocols<'a>(
mut self,
entries: impl IntoIterator<Item = (&'a str, &'a [Version])>,
) -> Self {
self.protocols.extend(
entries
.into_iter()
.map(|(a, vs)| (a.to_string(), vs.to_vec())),
);
self
}
pub fn require_protocol(mut self) -> Self {
self.require_protocol = true;
self
}
pub async fn connect(
&self,
addr: impl ToSocketAddrs,
server_name: &str,
) -> Result<Session, Error> {
let stream = TcpStream::connect(&addr).await?;
let server_name = rustls::pki_types::ServerName::try_from(server_name)
.map_err(|_| Error::InvalidServerName)?
.to_owned();
let entries = self
.protocols
.iter()
.map(|(a, vs)| (a.as_str(), vs.as_slice()));
let prefixed = alpn::build(entries, self.require_protocol);
let mut config = (*self.config).clone();
config.alpn_protocols = prefixed.iter().map(|s| s.as_bytes().to_vec()).collect();
tracing::debug!(?prefixed, "TLS connecting");
let connector = TlsConnector::from(Arc::new(config));
let tls_stream = connector.connect(server_name, stream).await?;
let negotiated = tls_stream.get_ref().1.alpn_protocol();
let negotiated_str = negotiated.and_then(|a| std::str::from_utf8(a).ok());
tracing::debug!(?negotiated_str, "TLS negotiated ALPN");
let (version, protocol) = alpn::parse(negotiated_str);
tracing::debug!(?version, ?protocol, "parsed ALPN");
if self.require_protocol && protocol.is_none() {
return Err(Error::InvalidProtocol(
negotiated_str.unwrap_or("<none>").to_string(),
));
}
let session_config = Config::negotiated(version, protocol);
let transport = Stream::new(tls_stream, version, session_config.max_record_size);
Session::connect(transport, session_config).await
}
}
#[derive(Clone)]
pub struct Server {
config: Arc<rustls::ServerConfig>,
}
impl Server {
pub fn new(config: Arc<rustls::ServerConfig>) -> Self {
Self { config }
}
pub async fn accept(&self, stream: TcpStream) -> Result<Session, Error> {
let acceptor = TlsAcceptor::from(self.config.clone());
let tls_stream = acceptor.accept(stream).await?;
let negotiated = tls_stream.get_ref().1.alpn_protocol();
let negotiated_str = negotiated.and_then(|a| std::str::from_utf8(a).ok());
tracing::debug!(?negotiated_str, "TLS accepted, negotiated ALPN");
let (version, protocol) = alpn::parse(negotiated_str);
tracing::debug!(?version, ?protocol, "parsed ALPN");
let session_config = Config::negotiated(version, protocol);
let transport = Stream::new(tls_stream, version, session_config.max_record_size);
Session::accept(transport, session_config).await
}
}