use futures::ready;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use std::future::Future;
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use crate::util::allocate_vec;
const DEFAULT_BUF_SIZE: usize = 16384;
#[derive(Debug)]
struct CopyBuffer {
read_done: bool,
need_flush: bool,
start_index: usize,
cache_length: usize,
size: usize,
buf: Box<[u8]>,
}
impl CopyBuffer {
pub fn new(size: usize, need_initial_flush: bool) -> Self {
let buf = allocate_vec(size);
Self {
read_done: false,
need_flush: need_initial_flush,
start_index: 0,
cache_length: 0,
size,
buf: buf.into_boxed_slice(),
}
}
pub fn poll_copy<R, W>(
&mut self,
cx: &mut Context<'_>,
mut reader: Pin<&mut R>,
mut writer: Pin<&mut W>,
) -> Poll<io::Result<()>>
where
R: AsyncRead + ?Sized,
W: AsyncWrite + ?Sized,
{
loop {
let mut read_pending = false;
let mut write_pending = false;
while !self.read_done && self.cache_length < self.size {
let unused_start_index = (self.start_index + self.cache_length) % self.size;
let unused_end_index_exclusive = if unused_start_index < self.start_index {
self.start_index
} else {
self.size
};
let me = &mut *self;
let mut buf =
ReadBuf::new(&mut me.buf[unused_start_index..unused_end_index_exclusive]);
match reader.as_mut().poll_read(cx, &mut buf) {
Poll::Ready(val) => {
val?;
let n = buf.filled().len();
if n == 0 {
self.read_done = true;
} else {
self.cache_length += n;
}
}
Poll::Pending => {
read_pending = true;
break;
}
}
}
while self.cache_length > 0 {
let used_start_index = self.start_index;
let used_end_index_exclusive =
std::cmp::min(self.start_index + self.cache_length, self.size);
let me = &mut *self;
match writer
.as_mut()
.poll_write(cx, &me.buf[used_start_index..used_end_index_exclusive])
{
Poll::Ready(val) => {
let written = val?;
if written == 0 {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"write zero byte into writer",
)));
} else {
self.cache_length -= written;
if self.cache_length == 0 {
self.start_index = 0;
} else {
self.start_index = (self.start_index + written) % self.size;
}
self.need_flush = true;
}
}
Poll::Pending => {
write_pending = true;
break;
}
}
}
if self.need_flush {
ready!(writer.as_mut().poll_flush(cx))?;
self.need_flush = false;
}
if self.read_done && self.cache_length == 0 {
return Poll::Ready(Ok(()));
}
if read_pending || write_pending {
return Poll::Pending;
}
}
}
}
enum TransferState {
Running,
ShuttingDown,
Done,
}
struct CopyBidirectional<'a, A: ?Sized, B: ?Sized> {
a: &'a mut A,
b: &'a mut B,
a_buf: CopyBuffer,
b_buf: CopyBuffer,
a_to_b: TransferState,
b_to_a: TransferState,
}
fn transfer_one_direction<A, B>(
cx: &mut Context<'_>,
state: &mut TransferState,
buf: &mut CopyBuffer,
r: &mut A,
w: &mut B,
) -> Poll<io::Result<()>>
where
A: AsyncRead + Unpin + ?Sized,
B: AsyncWrite + Unpin + ?Sized,
{
let mut r = Pin::new(r);
let mut w = Pin::new(w);
loop {
match state {
TransferState::Running => {
ready!(buf.poll_copy(cx, r.as_mut(), w.as_mut()))?;
*state = TransferState::ShuttingDown;
}
TransferState::ShuttingDown => {
ready!(w.as_mut().poll_shutdown(cx))?;
*state = TransferState::Done;
}
TransferState::Done => return Poll::Ready(Ok(())),
}
}
}
impl<A, B> Future for CopyBidirectional<'_, A, B>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let CopyBidirectional {
a,
b,
a_buf,
b_buf,
a_to_b,
b_to_a,
} = &mut *self;
let a_to_b = transfer_one_direction(cx, a_to_b, &mut *a_buf, &mut *a, &mut *b);
let b_to_a = transfer_one_direction(cx, b_to_a, &mut *b_buf, &mut *b, &mut *a);
match (a_to_b, b_to_a) {
(Poll::Ready(Err(e)), _) | (_, Poll::Ready(Err(e))) => Poll::Ready(Err(e)),
(Poll::Ready(Ok(())), Poll::Ready(Ok(()))) => Poll::Ready(Ok(())),
_ => Poll::Pending,
}
}
}
pub async fn copy_bidirectional<A, B>(
a: &mut A,
b: &mut B,
a_need_initial_flush: bool,
b_need_initial_flush: bool,
) -> io::Result<()>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
copy_bidirectional_with_sizes(
a,
b,
a_need_initial_flush,
b_need_initial_flush,
DEFAULT_BUF_SIZE,
DEFAULT_BUF_SIZE,
)
.await
}
pub async fn copy_bidirectional_with_sizes<A, B>(
a: &mut A,
b: &mut B,
a_need_initial_flush: bool,
b_need_initial_flush: bool,
a_to_b_buf_size: usize,
b_to_a_buf_size: usize,
) -> io::Result<()>
where
A: AsyncRead + AsyncWrite + Unpin + ?Sized,
B: AsyncRead + AsyncWrite + Unpin + ?Sized,
{
CopyBidirectional {
a,
b,
a_buf: CopyBuffer::new(a_to_b_buf_size, b_need_initial_flush),
b_buf: CopyBuffer::new(b_to_a_buf_size, a_need_initial_flush),
a_to_b: TransferState::Running,
b_to_a: TransferState::Running,
}
.await
}