use std::future::Future;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
pub const ASYNC_EVENT_TIMEOUT: Duration = Duration::from_secs(5);
const MAX_SHORT_PAYLOAD: usize = 125;
const CLIENT_MASK: [u8; 4] = [0x12, 0x34, 0x56, 0x78];
async fn within<F: Future>(context: &str, bound: Duration, future: F) -> F::Output {
tokio::time::timeout(bound, future)
.await
.unwrap_or_else(|_| panic!("timed out waiting for {context}"))
}
pub async fn lifecycle_event<F: Future>(context: &str, future: F) -> F::Output {
within(context, ASYNC_EVENT_TIMEOUT, future).await
}
pub async fn read_async_head<S>(stream: &mut S, context: &str, bound: Duration) -> Box<str>
where
S: AsyncRead + Unpin,
{
within(context, bound, frame_async_head(stream, context)).await
}
pub async fn read_async_head_unbounded<S>(stream: &mut S, context: &str) -> Box<str>
where
S: AsyncRead + Unpin,
{
frame_async_head(stream, context).await
}
async fn frame_async_head<S>(stream: &mut S, context: &str) -> Box<str>
where
S: AsyncRead + Unpin,
{
let mut head = Vec::new();
let mut byte = [0_u8; 1];
while !head.ends_with(b"\r\n\r\n") {
let count = stream
.read(&mut byte)
.await
.unwrap_or_else(|error| panic!("{context}: failed reading the HTTP head: {error}"));
assert_ne!(count, 0, "{context}: the peer closed mid-HTTP head");
head.push(byte[0]);
}
String::from_utf8(head)
.unwrap_or_else(|error| panic!("{context}: the HTTP head was not UTF-8: {error}"))
.into_boxed_str()
}
pub async fn read_async_http_head(stream: &mut TcpStream, context: &str) -> Box<str> {
read_async_head(stream, context, ASYNC_EVENT_TIMEOUT).await
}
pub async fn read_async_ws_frame_or_eof(
stream: &mut TcpStream,
context: &str,
) -> Option<(u8, Box<[u8]>)> {
lifecycle_event(context, async {
let mut header = [0_u8; 2];
let first = stream
.read(&mut header[..1])
.await
.unwrap_or_else(|error| panic!("{context}: failed reading a frame header: {error}"));
match first {
0 => None,
_ => Some(read_async_frame_body(stream, context, header).await),
}
})
.await
}
async fn read_async_frame_body(
stream: &mut TcpStream,
context: &str,
mut header: [u8; 2],
) -> (u8, Box<[u8]>) {
stream
.read_exact(&mut header[1..])
.await
.unwrap_or_else(|error| panic!("{context}: failed reading a frame header: {error}"));
assert_eq!(header[1] & 0x80, 0, "{context}: a server frame was masked");
let length = read_async_frame_length(stream, context, header[1] & 0x7f).await;
let mut payload = vec![0_u8; length];
stream
.read_exact(&mut payload)
.await
.unwrap_or_else(|error| panic!("{context}: failed reading a frame payload: {error}"));
(header[0] & 0x0f, payload.into_boxed_slice())
}
async fn read_async_frame_length(stream: &mut TcpStream, context: &str, short: u8) -> usize {
match short {
126 => {
let mut extended = [0_u8; 2];
stream
.read_exact(&mut extended)
.await
.unwrap_or_else(|error| panic!("{context}: failed reading a length: {error}"));
usize::from(u16::from_be_bytes(extended))
}
127 => {
let mut extended = [0_u8; 8];
stream
.read_exact(&mut extended)
.await
.unwrap_or_else(|error| panic!("{context}: failed reading a length: {error}"));
usize::try_from(u64::from_be_bytes(extended))
.unwrap_or_else(|_| panic!("{context}: the frame length did not fit a usize"))
}
length => usize::from(length),
}
}
pub async fn write_async_ws_frame(
stream: &mut TcpStream,
opcode: u8,
payload: &[u8],
context: &str,
) {
assert!(
payload.len() <= MAX_SHORT_PAYLOAD,
"{context}: a test frame payload must fit one length byte"
);
let length = u8::try_from(payload.len()).expect("a short payload fits one length byte");
let mut frame = Vec::with_capacity(payload.len() + 6);
frame.extend_from_slice(&[0x80 | opcode, 0x80 | length]);
frame.extend_from_slice(&CLIENT_MASK);
frame.extend(
payload
.iter()
.enumerate()
.map(|(index, byte)| byte ^ CLIENT_MASK[index % CLIENT_MASK.len()]),
);
lifecycle_event(context, stream.write_all(&frame))
.await
.unwrap_or_else(|error| panic!("{context}: failed writing a frame: {error}"));
}
pub async fn assert_transport_eof(stream: &mut TcpStream, context: &str) {
lifecycle_event(context, async {
let mut byte = [0_u8; 1];
match stream.read(&mut byte).await {
Ok(0) => {}
Err(error) if super::http::is_closed_connection_error(&error) => {}
Ok(count) => panic!("{context}: expected a closed transport, read {count} byte(s)"),
Err(error) => panic!("{context}: failed observing the closed transport: {error}"),
}
})
.await;
}
const MAX_REFUSAL_BODY: u64 = 64 * 1024;
pub async fn assert_refusal_body_then_eof(stream: &mut TcpStream, expected: &str, context: &str) {
lifecycle_event(context, async {
let mut body = Vec::new();
match stream.take(MAX_REFUSAL_BODY).read_to_end(&mut body).await {
Ok(_) => {}
Err(error) if super::http::is_closed_connection_error(&error) => {}
Err(error) => panic!("{context}: failed reading the refusal body: {error}"),
}
assert_refusal_body(&body, expected, context);
})
.await;
}
pub fn assert_refusal_body(body: &[u8], expected: &str, context: &str) {
assert_eq!(
String::from_utf8_lossy(body),
expected,
"{context}: the peer is told only what the refusal declared safe"
);
}
pub async fn assert_graceful_close_then_eof(stream: &mut TcpStream, subject: &str) {
let (opcode, _) = read_async_ws_frame_or_eof(stream, &format!("the {subject} close"))
.await
.unwrap_or_else(|| panic!("{subject}: the transport ended without a close frame"));
assert_eq!(
opcode, 0x8,
"{subject}: expected a close frame, got opcode {opcode:#x}"
);
write_async_ws_frame(stream, 0x8, &[], &format!("the {subject} close reply")).await;
assert_transport_eof(stream, &format!("the {subject} transport")).await;
}
pub async fn assert_optional_close_then_eof(stream: &mut TcpStream, subject: &str) {
match read_async_ws_frame_or_eof(stream, &format!("the {subject} close")).await {
None => {}
Some((0x8, _)) => {
write_async_ws_frame(stream, 0x8, &[], &format!("the {subject} close reply")).await;
assert_transport_eof(stream, &format!("the {subject} transport")).await;
}
Some((opcode, payload)) => {
panic!("{subject} emitted opcode {opcode:#x} with payload {payload:?}")
}
}
}
pub async fn assert_http_ok(addr: SocketAddr, path: &str, context: &str) {
let mut stream = lifecycle_event(context, TcpStream::connect(addr))
.await
.unwrap_or_else(|error| panic!("{context}: failed connecting the HTTP probe: {error}"));
let request = format!("GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n");
lifecycle_event(context, stream.write_all(request.as_bytes()))
.await
.unwrap_or_else(|error| panic!("{context}: failed writing the HTTP probe: {error}"));
let response = read_async_http_head(&mut stream, context).await;
assert_eq!(
super::http::status_from_raw(&response),
200,
"{context}: the HTTP probe did not succeed: {response}"
);
}