use std::io; use std::pin::Pin; use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; // ============= CombinedStream ============= /// Combines separate read and write halves into a single bidirectional stream. /// /// `copy_bidirectional` requires `AsyncRead + AsyncWrite` on each side, /// but the handshake layer produces split reader/writer pairs /// (e.g. `CryptoReader>` + `CryptoWriter<...>`). /// /// This wrapper reunifies them with zero overhead — each trait method /// delegates directly to the corresponding half. No buffering, no copies. /// /// Safety: `poll_read` only touches `reader`, `poll_write` only touches `writer`, /// so there's no aliasing even though both are called on the same `&mut self`. pub(in crate::proxy::relay) struct CombinedStream { reader: R, writer: W, } impl CombinedStream { pub(in crate::proxy::relay) fn new(reader: R, writer: W) -> Self { Self { reader, writer } } } impl AsyncRead for CombinedStream { #[inline] fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut self.get_mut().reader).poll_read(cx, buf) } } impl AsyncWrite for CombinedStream { #[inline] fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.get_mut().writer).poll_write(cx, buf) } #[inline] fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.get_mut().writer).poll_flush(cx) } #[inline] fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.get_mut().writer).poll_shutdown(cx) } }