use smallvec::SmallVec;
use crate::compression::DecompressStream;
use crate::constants::buffer::MAX_PENDING_BACKEND_BYTES;
use crate::tls::TlsStream;
use std::io::{self, IoSlice};
use std::ops::Range;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::TcpStream;
const PENDING_BACKEND_INLINE_BYTES: usize = 1024;
const PENDING_BACKEND_INLINE_SEGMENTS: usize = 2;
#[derive(Clone, Copy)]
enum PendingByteOrder {
Front,
Back,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum PendingBackendInput {
Copied {
bytes: SmallVec<[u8; PENDING_BACKEND_INLINE_BYTES]>,
pos: usize,
},
Pooled {
buffer: crate::pool::PooledBuffer,
range: Range<usize>,
pos: usize,
},
}
impl PendingBackendInput {
fn copied(bytes: &[u8]) -> Self {
let mut copied = SmallVec::new();
if bytes.len() > PENDING_BACKEND_INLINE_BYTES {
crate::pool::buffer::record_pending_backend_byte_heap_fallback();
}
copied.extend_from_slice(bytes);
Self::Copied {
bytes: copied,
pos: 0,
}
}
const fn pooled(buffer: crate::pool::PooledBuffer, range: Range<usize>) -> Self {
Self::Pooled {
buffer,
range,
pos: 0,
}
}
fn remaining(&self) -> usize {
match self {
Self::Copied { bytes, pos } => bytes.len().saturating_sub(*pos),
Self::Pooled { range, pos, .. } => range.len().saturating_sub(*pos),
}
}
fn is_empty(&self) -> bool {
self.remaining() == 0
}
fn read_into(&mut self, buf: &mut ReadBuf<'_>) -> usize {
match self {
Self::Copied { bytes, pos } => {
let available = &bytes[*pos..];
let n = available.len().min(buf.remaining());
buf.put_slice(&available[..n]);
*pos += n;
n
}
Self::Pooled { buffer, range, pos } => {
let available = &buffer.as_ref()[range.start + *pos..range.end];
let n = available.len().min(buf.remaining());
buf.put_slice(&available[..n]);
*pos += n;
n
}
}
}
}
pub trait AsyncStream: AsyncRead + AsyncWrite + Unpin + Send {}
impl<T> AsyncStream for T where T: AsyncRead + AsyncWrite + Unpin + Send {}
#[derive(Debug)]
enum ConnectionTransport {
Plain(TcpStream),
Tls(Box<TlsStream<TcpStream>>),
CompressedPlain(Box<DecompressStream<TcpStream>>),
CompressedTls(Box<DecompressStream<TlsStream<TcpStream>>>),
}
#[derive(Debug)]
pub struct ConnectionStream {
transport: ConnectionTransport,
pending_input: SmallVec<[PendingBackendInput; PENDING_BACKEND_INLINE_SEGMENTS]>,
}
fn ensure_pending_bytes_capacity(current: usize, additional: usize) -> anyhow::Result<()> {
anyhow::ensure!(
current + additional <= MAX_PENDING_BACKEND_BYTES,
"Pending bytes exceeds {} bytes ({} bytes): probable protocol desync",
MAX_PENDING_BACKEND_BYTES,
current + additional
);
Ok(())
}
impl ConnectionStream {
pub fn plain(stream: TcpStream) -> Self {
Self::new(ConnectionTransport::Plain(stream))
}
pub fn tls(stream: TlsStream<TcpStream>) -> Self {
Self::new(ConnectionTransport::Tls(Box::new(stream)))
}
pub fn compressed_plain(stream: TcpStream) -> Self {
Self::new(ConnectionTransport::CompressedPlain(Box::new(
DecompressStream::new(stream),
)))
}
pub fn compressed_tls(stream: TlsStream<TcpStream>) -> Self {
Self::new(ConnectionTransport::CompressedTls(Box::new(
DecompressStream::new(stream),
)))
}
fn new(transport: ConnectionTransport) -> Self {
Self {
transport,
pending_input: SmallVec::new(),
}
}
pub(crate) fn into_compressed(self, level: u32) -> io::Result<Self> {
let transport = match self.transport {
ConnectionTransport::Plain(tcp) => ConnectionTransport::CompressedPlain(Box::new(
DecompressStream::with_level(tcp, level),
)),
ConnectionTransport::Tls(tls) => ConnectionTransport::CompressedTls(Box::new(
DecompressStream::with_level(*tls, level),
)),
ConnectionTransport::CompressedPlain(_) | ConnectionTransport::CompressedTls(_) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"cannot enable compression on an already-compressed connection",
));
}
};
Ok(Self {
transport,
pending_input: self.pending_input,
})
}
#[must_use]
pub const fn connection_type(&self) -> &'static str {
match &self.transport {
ConnectionTransport::Plain(_) => "TCP",
ConnectionTransport::Tls(_) => "TLS",
ConnectionTransport::CompressedPlain(_) => "TCP+COMPRESS",
ConnectionTransport::CompressedTls(_) => "TLS+COMPRESS",
}
}
#[inline]
#[must_use]
pub const fn is_encrypted(&self) -> bool {
matches!(
&self.transport,
ConnectionTransport::Tls(_) | ConnectionTransport::CompressedTls(_)
)
}
#[inline]
#[must_use]
pub const fn is_unencrypted(&self) -> bool {
matches!(
&self.transport,
ConnectionTransport::Plain(_) | ConnectionTransport::CompressedPlain(_)
)
}
#[inline]
#[must_use]
pub const fn is_compressed(&self) -> bool {
matches!(
&self.transport,
ConnectionTransport::CompressedPlain(_) | ConnectionTransport::CompressedTls(_)
)
}
#[must_use]
pub const fn as_tcp_stream(&self) -> Option<&TcpStream> {
match &self.transport {
ConnectionTransport::Plain(tcp) => Some(tcp),
_ => None,
}
}
pub const fn as_tcp_stream_mut(&mut self) -> Option<&mut TcpStream> {
match &mut self.transport {
ConnectionTransport::Plain(tcp) => Some(tcp),
_ => None,
}
}
#[must_use]
pub fn as_tls_stream(&self) -> Option<&TlsStream<TcpStream>> {
match &self.transport {
ConnectionTransport::Tls(tls) => Some(tls.as_ref()),
_ => None,
}
}
pub fn as_tls_stream_mut(&mut self) -> Option<&mut TlsStream<TcpStream>> {
match &mut self.transport {
ConnectionTransport::Tls(tls) => Some(tls.as_mut()),
_ => None,
}
}
#[must_use]
pub fn underlying_tcp_stream(&self) -> &TcpStream {
match &self.transport {
ConnectionTransport::Plain(tcp) => tcp,
ConnectionTransport::Tls(tls) => tls.get_ref().0,
ConnectionTransport::CompressedPlain(cs) => cs.get_ref(),
ConnectionTransport::CompressedTls(cs) => cs.get_ref().get_ref().0,
}
}
pub fn queue_pending_bytes(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
self.queue_pending_bytes_ordered(bytes, PendingByteOrder::Back)
}
pub fn queue_pending_bytes_first(&mut self, bytes: &[u8]) -> anyhow::Result<()> {
self.queue_pending_bytes_ordered(bytes, PendingByteOrder::Front)
}
fn queue_pending_bytes_ordered(
&mut self,
bytes: &[u8],
order: PendingByteOrder,
) -> anyhow::Result<()> {
let Some(bytes) = (!bytes.is_empty()).then_some(bytes) else {
return Ok(());
};
ensure_pending_bytes_capacity(self.pending_bytes_len(), bytes.len())?;
if self.pending_input.spilled()
|| self.pending_input.len() >= PENDING_BACKEND_INLINE_SEGMENTS
{
crate::pool::buffer::record_pending_backend_byte_heap_fallback();
}
let segment = PendingBackendInput::copied(bytes);
match order {
PendingByteOrder::Back => self.pending_input.push(segment),
PendingByteOrder::Front => self.pending_input.insert(0, segment),
}
Ok(())
}
pub(crate) fn queue_pooled_pending_bytes_first(
&mut self,
buffer: crate::pool::PooledBuffer,
range: Range<usize>,
) -> anyhow::Result<()> {
anyhow::ensure!(
range.start <= range.end,
"pending pooled range start exceeds end"
);
let len = range.len();
if len == 0 {
return Ok(());
}
anyhow::ensure!(
range.end <= buffer.initialized(),
"pending pooled range exceeds initialized buffer"
);
ensure_pending_bytes_capacity(self.pending_bytes_len(), len)?;
if self.pending_input.spilled()
|| self.pending_input.len() >= PENDING_BACKEND_INLINE_SEGMENTS
{
crate::pool::buffer::record_pending_backend_byte_heap_fallback();
}
self.pending_input
.insert(0, PendingBackendInput::pooled(buffer, range));
Ok(())
}
#[must_use]
pub fn has_pending_bytes(&self) -> bool {
self.pending_input.iter().any(|segment| !segment.is_empty())
}
#[must_use]
pub fn pending_bytes_len(&self) -> usize {
self.pending_input
.iter()
.map(PendingBackendInput::remaining)
.sum()
}
pub fn clear_pending_bytes(&mut self) {
self.pending_input.clear();
}
}
impl AsyncRead for ConnectionStream {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
if let Some(front) = self.pending_input.first_mut()
&& buf.remaining() > 0
{
front.read_into(buf);
if front.is_empty() {
self.pending_input.remove(0);
}
return Poll::Ready(Ok(()));
}
match &mut self.transport {
ConnectionTransport::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
ConnectionTransport::Tls(stream) => Pin::new(stream.as_mut()).poll_read(cx, buf),
ConnectionTransport::CompressedPlain(stream) => {
Pin::new(stream.as_mut()).poll_read(cx, buf)
}
ConnectionTransport::CompressedTls(stream) => {
Pin::new(stream.as_mut()).poll_read(cx, buf)
}
}
}
}
impl AsyncWrite for ConnectionStream {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match &mut self.transport {
ConnectionTransport::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
ConnectionTransport::Tls(stream) => Pin::new(stream.as_mut()).poll_write(cx, buf),
ConnectionTransport::CompressedPlain(stream) => {
Pin::new(stream.as_mut()).poll_write(cx, buf)
}
ConnectionTransport::CompressedTls(stream) => {
Pin::new(stream.as_mut()).poll_write(cx, buf)
}
}
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
match &mut self.transport {
ConnectionTransport::Plain(stream) => Pin::new(stream).poll_write_vectored(cx, bufs),
ConnectionTransport::Tls(stream) => {
Pin::new(stream.as_mut()).poll_write_vectored(cx, bufs)
}
ConnectionTransport::CompressedPlain(stream) => {
Pin::new(stream.as_mut()).poll_write_vectored(cx, bufs)
}
ConnectionTransport::CompressedTls(stream) => {
Pin::new(stream.as_mut()).poll_write_vectored(cx, bufs)
}
}
}
fn is_write_vectored(&self) -> bool {
match &self.transport {
ConnectionTransport::Plain(stream) => stream.is_write_vectored(),
ConnectionTransport::Tls(stream) => stream.is_write_vectored(),
ConnectionTransport::CompressedPlain(stream) => stream.is_write_vectored(),
ConnectionTransport::CompressedTls(stream) => stream.is_write_vectored(),
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.transport {
ConnectionTransport::Plain(stream) => Pin::new(stream).poll_flush(cx),
ConnectionTransport::Tls(stream) => Pin::new(stream.as_mut()).poll_flush(cx),
ConnectionTransport::CompressedPlain(stream) => {
Pin::new(stream.as_mut()).poll_flush(cx)
}
ConnectionTransport::CompressedTls(stream) => Pin::new(stream.as_mut()).poll_flush(cx),
}
}
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match &mut self.transport {
ConnectionTransport::Plain(stream) => Pin::new(stream).poll_shutdown(cx),
ConnectionTransport::Tls(stream) => Pin::new(stream.as_mut()).poll_shutdown(cx),
ConnectionTransport::CompressedPlain(stream) => {
Pin::new(stream.as_mut()).poll_shutdown(cx)
}
ConnectionTransport::CompressedTls(stream) => {
Pin::new(stream.as_mut()).poll_shutdown(cx)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
#[tokio::test]
async fn test_connection_stream_plain_tcp() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let client_stream = client_handle.await.unwrap();
let mut server_conn = ConnectionStream::plain(server_stream);
let mut client_conn = ConnectionStream::plain(client_stream);
client_conn.write_all(b"Hello").await.unwrap();
let mut buf = [0u8; 5];
server_conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"Hello");
assert!(client_conn.is_unencrypted());
assert!(!client_conn.is_encrypted());
assert_eq!(client_conn.connection_type(), "TCP");
assert!(client_conn.as_tcp_stream().is_some());
}
#[test]
fn test_async_stream_trait() {
fn assert_async_stream<T: AsyncStream>() {}
assert_async_stream::<TcpStream>();
assert_async_stream::<ConnectionStream>();
}
#[tokio::test]
async fn test_plain_connection_stream_preserves_vectored_write_support() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (_server_stream, _) = listener.accept().await.unwrap();
let client_stream = client_handle.await.unwrap();
let conn = ConnectionStream::plain(client_stream);
assert!(AsyncWrite::is_write_vectored(&conn));
}
#[tokio::test]
async fn test_connection_stream_tcp_access() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client_stream = client_handle.await.unwrap();
let mut conn_stream = ConnectionStream::plain(server_stream);
assert!(conn_stream.is_unencrypted());
assert!(conn_stream.as_tcp_stream().is_some());
assert!(conn_stream.as_tls_stream().is_none());
let _underlying = conn_stream.underlying_tcp_stream();
let tcp_mut = conn_stream.as_tcp_stream_mut().unwrap();
tcp_mut.set_nodelay(true).unwrap();
}
#[tokio::test]
async fn test_plain_connection_type_checks() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let conn = ConnectionStream::plain(server_stream);
assert_eq!(conn.connection_type(), "TCP");
assert!(conn.is_unencrypted());
assert!(!conn.is_encrypted());
assert!(!conn.is_compressed());
}
#[tokio::test]
async fn test_tcp_access_methods_work() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let conn = ConnectionStream::plain(server_stream);
assert!(conn.as_tcp_stream().is_some());
assert!(conn.as_tls_stream().is_none());
}
#[tokio::test]
async fn test_mutable_tcp_access_methods_work() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let mut conn = ConnectionStream::plain(server_stream);
assert!(conn.as_tcp_stream_mut().is_some());
assert!(conn.as_tls_stream_mut().is_none());
assert!(conn.as_tcp_stream().is_some());
let _underlying = conn.underlying_tcp_stream();
}
#[tokio::test]
async fn test_constructor_creates_plain_variant() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let plain_conn = ConnectionStream::plain(server_stream);
assert_eq!(plain_conn.connection_type(), "TCP");
assert!(plain_conn.is_unencrypted());
}
#[tokio::test]
async fn test_pending_bytes_is_read_before_socket() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move {
let mut client = TcpStream::connect(addr).await.unwrap();
client.write_all(b"socket").await.unwrap();
client
});
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let mut conn = ConnectionStream::plain(server_stream);
conn.queue_pending_bytes(b"left").unwrap();
let mut buf = [0u8; 4];
conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"left");
let mut buf = [0u8; 6];
conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"socket");
}
#[tokio::test]
async fn test_pooled_pending_bytes_are_read_before_socket_without_copy_metric() {
crate::pool::buffer::reset_hot_path_allocation_metrics();
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move {
let mut client = TcpStream::connect(addr).await.unwrap();
client.write_all(b"socket").await.unwrap();
client
});
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let pool =
crate::pool::BufferPool::new(crate::types::BufferSize::try_new(1024).unwrap(), 1);
let mut pending = pool.acquire();
pending.copy_from_slice(b"xxpooledyy");
assert_eq!(pool.available_buffers(), 0);
let mut conn = ConnectionStream::plain(server_stream);
conn.queue_pooled_pending_bytes_first(pending, 2..8)
.unwrap();
assert_eq!(conn.pending_bytes_len(), 6);
assert_eq!(
pool.available_buffers(),
0,
"queued pending input should retain the pooled allocation"
);
let mut buf = [0u8; 6];
conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"pooled");
assert!(!conn.has_pending_bytes());
assert_eq!(
pool.available_buffers(),
1,
"fully consumed pending input should return the pooled allocation"
);
let metrics = crate::pool::buffer::hot_path_allocation_metrics_snapshot();
assert_eq!(metrics.pending_backend_byte_heap_fallbacks, 0);
let mut buf = [0u8; 6];
conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"socket");
}
#[tokio::test]
async fn test_queue_pending_bytes_rejects_oversized_buffers() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let mut conn = ConnectionStream::plain(server_stream);
let large = vec![b'x'; MAX_PENDING_BACKEND_BYTES + 1];
let err = conn.queue_pending_bytes(&large).unwrap_err();
assert!(
err.to_string()
.contains(&MAX_PENDING_BACKEND_BYTES.to_string())
);
assert_eq!(conn.pending_bytes_len(), 0);
}
#[allow(clippy::reversed_empty_ranges)]
#[tokio::test]
async fn test_queue_pooled_pending_bytes_rejects_backwards_range() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let pool =
crate::pool::BufferPool::new(crate::types::BufferSize::try_new(1024).unwrap(), 1);
let mut pending = pool.acquire();
pending.copy_from_slice(b"pooled");
let mut conn = ConnectionStream::plain(server_stream);
let err = conn
.queue_pooled_pending_bytes_first(pending, 5..2)
.unwrap_err();
assert!(err.to_string().contains("range start exceeds end"));
assert_eq!(conn.pending_bytes_len(), 0);
}
#[tokio::test]
async fn test_queue_pending_bytes_ignores_empty_buffers() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let mut conn = ConnectionStream::plain(server_stream);
conn.queue_pending_bytes(b"").unwrap();
assert!(!conn.has_pending_bytes());
assert_eq!(conn.pending_bytes_len(), 0);
}
#[tokio::test]
async fn test_into_compressed_preserves_pending_bytes() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move {
let mut client = TcpStream::connect(addr).await.unwrap();
client.write_all(b"socket").await.unwrap();
client
});
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let mut conn = ConnectionStream::plain(server_stream);
conn.queue_pending_bytes(b"left").unwrap();
let mut conn = conn.into_compressed(1).unwrap();
assert!(conn.is_compressed());
assert_eq!(conn.pending_bytes_len(), 4);
let mut buf = [0u8; 4];
conn.read_exact(&mut buf).await.unwrap();
assert_eq!(&buf, b"left");
}
#[tokio::test]
async fn test_into_compressed_rejects_already_compressed_stream() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let conn = ConnectionStream::compressed_plain(server_stream);
let err = conn.into_compressed(1).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
assert_eq!(
err.to_string(),
"cannot enable compression on an already-compressed connection"
);
}
#[tokio::test]
async fn test_plain_connection_debug_format() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let client_handle = tokio::spawn(async move { TcpStream::connect(addr).await.unwrap() });
let (server_stream, _) = listener.accept().await.unwrap();
let _client = client_handle.await.unwrap();
let conn = ConnectionStream::plain(server_stream);
let debug_str = format!("{conn:?}");
assert!(
debug_str.contains("Plain"),
"Debug output should indicate Plain TCP"
);
}
}