use std::io::{self, Result};
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use rustls::pki_types::ServerName;
use rustls::ClientConfig;
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
use crate::pinger::Pinger;
use crate::tls::default_client_config;
use crate::uri::get_uri;
use crate::util::with_timeout;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
const DEFAULT_PORT: u16 = 443;
pub struct TlsPinger {
pub target: String,
pub timeout: Duration,
tls_config: Option<Arc<ClientConfig>>,
}
impl TlsPinger {
pub fn new(target: impl Into<String>) -> Self {
Self {
target: target.into(),
timeout: DEFAULT_TIMEOUT,
tls_config: None,
}
}
pub fn with_timeout(mut self, t: Duration) -> Self {
self.timeout = t;
self
}
pub fn with_tls_config(mut self, config: Arc<ClientConfig>) -> Self {
self.tls_config = Some(config);
self
}
}
#[async_trait]
impl Pinger for TlsPinger {
async fn ping(&self) -> Result<()> {
let uri = get_uri(&self.target);
if uri.domain.is_empty() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"TLS target is missing a host",
));
}
let port = if uri.port > 0 {
uri.port as u16
} else {
DEFAULT_PORT
};
let endpoint = format!("{}:{port}", uri.domain);
let server_name = ServerName::try_from(uri.domain.clone())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
let config = self
.tls_config
.clone()
.unwrap_or_else(default_client_config);
with_timeout(self.timeout, async move {
let tcp = TcpStream::connect(&endpoint).await?;
let connector = TlsConnector::from(config);
let _stream = connector.connect(server_name, tcp).await?;
Ok(())
})
.await
}
}