use super::Context;
use core::time::Duration;
use std::panic::{catch_unwind, AssertUnwindSafe};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::test]
async fn zero_read_test() {
let context = Context::new().await;
let (mut client, _server) = context.pair().await;
tokio::time::timeout(core::time::Duration::from_millis(5), client.read(&mut []))
.await
.expect_err("the read operation should time out");
}
#[tokio::test]
async fn zero_read_reset_test() {
let context = Context::new().await;
let (mut client, server) = context.pair().await;
drop(server);
tokio::time::sleep(Duration::from_millis(1)).await;
let res =
tokio::time::timeout(core::time::Duration::from_millis(5), client.read(&mut [])).await;
if context.protocol().is_udp() {
return;
}
let len = res.expect("operation should not time out").unwrap();
assert_eq!(len, 0);
}
#[tokio::test]
async fn read_immediately_test() {
let context = Context::new().await;
let (mut client, mut server) = context.pair().await;
let client = async move {
let mut buffer = vec![];
client.read_to_end(&mut buffer).await.unwrap();
buffer
};
let server = async move {
server.write_all(b"hello!").await.unwrap();
};
let (response, _) = tokio::join!(client, server);
assert_eq!(response, b"hello!");
}
#[tokio::test]
async fn multiple_empty_read_test() {
let context = Context::new().await;
let (mut client, server) = context.pair().await;
drop(server);
tokio::time::sleep(Duration::from_millis(1)).await;
for buffer_len in [0, 1] {
for _ in 0..5 {
let buffer = &mut [42][..buffer_len];
let res =
tokio::time::timeout(core::time::Duration::from_millis(5), client.read(buffer))
.await;
if context.protocol().is_udp() {
continue;
}
let len = res.expect("operation should not time out").unwrap();
assert_eq!(len, 0);
}
}
}
#[tokio::test]
async fn stream_closed_without_authentication() {
let context = Context::new().await;
let (mut client, server) = context.pair().await;
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
let _s = server;
panic!("expected panic to test unclean shutdown");
}));
tokio::time::sleep(Duration::from_millis(1)).await;
let res =
tokio::time::timeout(core::time::Duration::from_millis(5), client.read(&mut [])).await;
let res = res.expect("operation should not time out");
if context.is_plaintext() {
assert_eq!(res.unwrap(), 0);
return;
}
let err = res.unwrap_err();
if context.protocol().is_tcp() {
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof, "{:?}", err);
assert!(
matches!(
err.get_ref()
.expect("has inner")
.downcast_ref::<crate::stream::recv::Error>()
.unwrap()
.kind,
crate::stream::recv::ErrorKind::TruncatedTransport
),
"{:?}",
err
);
} else {
assert_eq!(err.kind(), std::io::ErrorKind::ConnectionReset, "{:?}", err);
let crate::stream::recv::ErrorKind::ApplicationError { error } = err
.get_ref()
.expect("has inner")
.downcast_ref::<crate::stream::recv::Error>()
.unwrap()
.kind
else {
panic!("unexpected error: {:?}", err);
};
assert_eq!(
*error,
crate::stream::shared::ShutdownKind::Panicking
.error_code()
.unwrap() as u64,
"{:?}",
err
);
}
}
#[tokio::test]
async fn read_exact_missing_fin_no_rst() {
let context = Context::new().await;
if !context.protocol().is_tcp() {
return;
}
let payload_size = 16;
let (mut client, mut server) = context.pair().await;
let (read_done_tx, read_done_rx) = tokio::sync::oneshot::channel::<()>();
let client_handle = tokio::spawn(async move {
let payload = vec![42u8; payload_size];
client.write_all(&payload).await.unwrap();
let _ = read_done_rx.await;
client.shutdown().await.unwrap();
tokio::time::sleep(Duration::from_millis(10)).await;
let mut buf = vec![0u8; 1];
let err = client.read_exact(&mut buf).await.unwrap_err();
assert_eq!(
err.kind(),
std::io::ErrorKind::UnexpectedEof,
"Expected UnexpectedEof (TruncatedTransport from drained shutdown), \
got {err:?} — if ConnectionReset, the drain-on-shutdown fix is broken"
);
});
let server_handle = tokio::spawn(async move {
let mut buf = vec![0u8; payload_size];
server.read_exact(&mut buf).await.unwrap();
let _ = read_done_tx.send(());
tokio::time::sleep(Duration::from_millis(10)).await;
let _ = catch_unwind(AssertUnwindSafe(move || {
let _server = server;
panic!("intentional panic to skip authenticated closure");
}));
});
server_handle.await.unwrap();
client_handle.await.unwrap();
}