use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::async_stream::AsyncStream;
use crate::crypto::{CryptoConnection, feed_crypto_connection};
use crate::util::{allocate_vec, write_all};
pub async fn perform_crypto_handshake(
connection: &mut CryptoConnection,
stream: &mut Box<dyn AsyncStream>,
buffer_size: usize,
) -> std::io::Result<()> {
log::debug!("TLS handshake starting");
let mut iteration = 0;
let mut eof = false;
let mut read_buf = allocate_vec(buffer_size).into_boxed_slice();
loop {
iteration += 1;
let mut write_would_block = false;
let mut read_would_block = false;
let until_handshaked = connection.is_handshaking();
log::trace!(
"TLS handshake iteration {}: is_handshaking={}, wants_read={}, wants_write={}",
iteration,
connection.is_handshaking(),
connection.wants_read(),
connection.wants_write()
);
if !connection.wants_read() && !connection.wants_write() {
if connection.is_handshaking() {
log::error!("TLS handshake stalled: neither wants_read nor wants_write");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"TLS handshake stalled: neither wants_read nor wants_write",
));
}
log::debug!(
"TLS handshake complete (early exit) after {} iterations",
iteration
);
break;
}
let mut wrote_data = false;
while connection.wants_write() {
match write_pending_data_no_flush(connection, stream).await {
Ok(did_write) => {
wrote_data |= did_write;
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
write_would_block = true;
break;
}
Err(e) => return Err(e),
}
}
if wrote_data {
match stream.flush().await {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
write_would_block = true;
}
Err(e) => return Err(e),
}
}
if !eof && connection.is_handshaking() && connection.wants_read() {
match read_and_process_data(connection, stream, &mut read_buf).await {
Ok(0) => {
log::debug!("TLS handshake: EOF from peer");
eof = true;
}
Ok(n) => {
log::debug!("TLS handshake: read and processed {} bytes", n);
}
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
read_would_block = true;
}
Err(e) => {
try_last_gasp_write(connection, stream).await;
return Err(e);
}
}
}
log::debug!(
"TLS handshake iteration {} done: eof={}, is_handshaking={}, wants_write={}",
iteration,
eof,
connection.is_handshaking(),
connection.wants_write()
);
if until_handshaked && !connection.is_handshaking() && connection.wants_write() {
log::debug!("TLS handshake: complete but has pending writes, continuing");
continue;
}
match (eof, connection.is_handshaking()) {
(true, true) => {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"EOF during TLS handshake",
));
}
(_, false) => {
log::debug!("TLS handshake complete after {} iterations", iteration);
break; }
(_, true) if write_would_block || read_would_block => {
log::error!("TLS handshake would block at iteration {}", iteration);
return Err(std::io::Error::new(
std::io::ErrorKind::WouldBlock,
"TLS handshake would block (should not happen in blocking mode)",
));
}
(..) => {
if iteration > 100 {
log::error!("TLS handshake exceeded 100 iterations");
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"TLS handshake exceeded maximum iterations",
));
}
log::debug!("TLS handshake continuing to iteration {}", iteration + 1);
continue;
}
}
}
stream.flush().await?;
Ok(())
}
async fn write_pending_data_no_flush(
connection: &mut CryptoConnection,
stream: &mut Box<dyn AsyncStream>,
) -> std::io::Result<bool> {
let mut write_buf = Vec::new();
while connection.wants_write() {
connection.write_tls(&mut write_buf)?;
}
if !write_buf.is_empty() {
write_all(stream, &write_buf).await?;
Ok(true)
} else {
Ok(false)
}
}
async fn read_and_process_data(
connection: &mut CryptoConnection,
stream: &mut Box<dyn AsyncStream>,
read_buf: &mut [u8],
) -> std::io::Result<usize> {
let n = stream.read(read_buf).await?;
log::debug!("TLS: read {} bytes from peer", n);
if n == 0 {
return Ok(0);
}
feed_crypto_connection(connection, &read_buf[..n])?;
connection.process_new_packets().map_err(|e| {
log::error!("TLS error processing packets: {}", e);
std::io::Error::new(std::io::ErrorKind::InvalidData, format!("TLS error: {}", e))
})?;
log::debug!("TLS: processed packets successfully");
Ok(n)
}
async fn try_last_gasp_write(connection: &mut CryptoConnection, stream: &mut Box<dyn AsyncStream>) {
if !connection.wants_write() {
return;
}
let mut alert_buf = Vec::new();
while connection.wants_write() {
if connection.write_tls(&mut alert_buf).is_err() {
return;
}
}
if !alert_buf.is_empty() {
log::debug!(
"TLS: sending last-gasp alert ({} bytes) before closing",
alert_buf.len()
);
let _ = write_all(stream, &alert_buf).await;
let _ = stream.flush().await;
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_rustls_connection_enum_exists() {
fn _assert_connection_type(_: &rustls::Connection) {}
}
#[test]
fn test_connection_has_required_methods() {
use std::sync::Arc;
let config = Arc::new(
rustls::ClientConfig::builder()
.with_root_certificates(rustls::RootCertStore::empty())
.with_no_client_auth(),
);
let server_name = rustls::pki_types::ServerName::try_from("example.com")
.unwrap()
.to_owned();
let client_conn = rustls::ClientConnection::new(config, server_name).unwrap();
let connection = rustls::Connection::Client(client_conn);
let _handshaking = connection.is_handshaking();
let _wants_read = connection.wants_read();
let _wants_write = connection.wants_write();
}
}