use crate::PortRedirectProtocol;
use anyhow::{Error, Result};
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader};
use tokio::time::{interval, timeout, Duration};
use tokio_util::sync::CancellationToken;
use tracing::{debug, info, warn};
const READ_TIMEOUT: Duration = PortRedirectProtocol::CONNECTION_KEEPALIVE_TIMEOUT;
const KEEP_ALIVE_INTERVAL: Duration = PortRedirectProtocol::CONNECTION_KEEPALIVE_INTERVAL;
const PING_MESSAGE: &[u8] = b"PING\n";
const PONG_MESSAGE: &[u8] = b"PONG\n";
const CONNECTION_END_MESSAGE: &[u8] = b"BYE\n";
pub async fn run_keepalive_client_loop<T>(mut auth_stream: T) -> Result<()>
where
T: AsyncRead + AsyncWrite + Unpin,
{
let mut tick_interval = interval(KEEP_ALIVE_INTERVAL);
let mut pong_count = 0usize;
loop {
tick_interval.tick().await;
if let Err(e) = auth_stream.write_all(PING_MESSAGE).await {
warn!("Failed to send PING: {}", e);
break;
}
if let Err(e) = auth_stream.flush().await {
warn!("Failed to flush PING: {}", e);
break;
}
info!("Sent PING");
let mut response_buf = Vec::with_capacity(16);
let mut reader = BufReader::new(&mut auth_stream);
match timeout(READ_TIMEOUT, reader.read_until(b'\n', &mut response_buf)).await {
Ok(Ok(0)) => {
warn!("Connection closed by remote during keepalive");
break;
}
Ok(Ok(_)) => {
if response_buf == PONG_MESSAGE {
pong_count += 1;
info!("Received PONG, count: {}", pong_count);
} else {
warn!("Unexpected response: {:?}", response_buf);
break;
}
}
Ok(Err(e)) => {
warn!("Failed to read PONG: {:?}", e);
break;
}
Err(_) => {
warn!("Timed out waiting for PONG response");
break;
}
}
}
Ok(())
}
pub async fn run_control_channel_loop<T>(mut auth_stream: T, cancel_token: CancellationToken) -> Result<()>
where
T: AsyncRead + AsyncWrite + Unpin,
{
loop {
let mut buf = Vec::with_capacity(16);
let read_result = tokio::select! {
_ = cancel_token.cancelled() => { info!("Cancellation token triggered in control channel loop");
return Ok(());
}
res = timeout(KEEP_ALIVE_INTERVAL + READ_TIMEOUT, async {
let mut byte = [0; 1];
loop {
let n = auth_stream.read(&mut byte).await?;
if n == 0 {
break;
}
buf.push(byte[0]);
if byte[0] == b'\n' {
break;
}
}
Ok::<(), Error>(())
}) => res,
};
match read_result {
Ok(Ok(())) => {
if buf.is_empty() {
warn!("Connection closed by remote during keepalive");
break;
}
if buf == PING_MESSAGE {
info!("Received PING, sending PONG");
if let Err(e) = auth_stream.write_all(PONG_MESSAGE).await {
warn!("Failed to send PONG: {}", e);
break;
}
if let Err(e) = auth_stream.flush().await {
warn!("Failed to flush PONG: {}", e);
break;
}
} else if buf == CONNECTION_END_MESSAGE {
info!("Received BYE, initiating client connection shutdown");
cancel_token.cancel();
break;
} else {
warn!("Unexpected message received: {:?}", buf);
break;
}
}
Ok(Err(e)) => {
warn!("Failed to read from stream: {:?}", e);
break;
}
Err(_) => {
warn!("Timed out waiting for PING message");
break;
}
}
}
debug!("Control channel loop ended, cancelling token");
cancel_token.cancel();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::error::Error;
use std::io;
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader};
use tokio::time::{self, timeout, Duration};
use tokio_test::io::Builder;
pub struct DummyProtocol;
impl DummyProtocol {
pub const CONNECTION_KEEPALIVE_INTERVAL_SECONDS: Duration = Duration::from_millis(100);
}
const TEST_KEEP_ALIVE_INTERVAL: Duration = DummyProtocol::CONNECTION_KEEPALIVE_INTERVAL_SECONDS;
const TEST_READ_TIMEOUT: Duration = Duration::from_millis(200);
async fn run_loop_with_test_interval<T>(mut stream: T) -> Result<(), Box<dyn Error>>
where
T: AsyncRead + AsyncWrite + Unpin,
{
let mut tick_interval = time::interval(TEST_KEEP_ALIVE_INTERVAL);
let mut pong_count = 0usize;
loop {
tick_interval.tick().await;
if stream.write_all(PING_MESSAGE).await.is_err() {
break;
}
if stream.flush().await.is_err() {
break;
}
let mut response_buf = Vec::with_capacity(16);
let mut reader = BufReader::new(&mut stream);
match timeout(
TEST_READ_TIMEOUT,
reader.read_until(b'\n', &mut response_buf),
)
.await
{
Ok(Ok(0)) => break, Ok(Ok(_)) => {
if response_buf == PONG_MESSAGE {
pong_count += 1;
} else {
break;
}
}
_ => break,
}
if pong_count >= 1 {
break;
}
}
Ok(())
}
struct NeverRead;
impl AsyncRead for NeverRead {
fn poll_read(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<io::Result<()>> {
std::task::Poll::Pending
}
}
impl AsyncWrite for NeverRead {
fn poll_write(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<io::Result<usize>> {
std::task::Poll::Ready(Ok(buf.len()))
}
fn poll_flush(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
fn poll_shutdown(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
fn build_good_keepalive() -> tokio_test::io::Mock {
let mut builder = Builder::new();
builder.write(PING_MESSAGE);
builder.read(b"PONG\n");
builder.build()
}
fn build_wrong_response() -> tokio_test::io::Mock {
let mut builder = Builder::new();
builder.write(PING_MESSAGE);
builder.read(b"WRONG\n");
builder.build()
}
fn build_write_error() -> tokio_test::io::Mock {
let mut builder = Builder::new();
builder.write_error(io::Error::new(io::ErrorKind::Other, "write error"));
builder.build()
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_client_good() {
let mock = build_good_keepalive();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_client_wrong_response() {
let mock = build_wrong_response();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_client_write_error() {
let mock = build_write_error();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_client_timeout() {
let stream = NeverRead;
let fut = run_loop_with_test_interval(stream);
time::advance(TEST_KEEP_ALIVE_INTERVAL).await;
time::advance(TEST_READ_TIMEOUT).await;
let result = fut.await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_server_good() {
let mock = build_good_keepalive();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_server_wrong_response() {
let mock = build_wrong_response();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_server_write_error() {
let mock = build_write_error();
let result = run_loop_with_test_interval(mock).await;
assert!(result.is_ok());
}
#[tokio::test(start_paused = true)]
async fn test_keepalive_server_timeout() {
let stream = NeverRead;
let fut = run_loop_with_test_interval(stream);
time::advance(TEST_KEEP_ALIVE_INTERVAL).await;
time::advance(TEST_READ_TIMEOUT).await;
let result = fut.await;
assert!(result.is_ok());
}
}