use std::{fmt, pin::Pin};
use futures::{
io::{self, AsyncWrite},
ready,
task::{Context, Poll},
};
use pin_project::pin_project;
use salsa20::{XSalsa20, cipher::StreamCipher};
#[pin_project]
pub(crate) struct CryptWriter<W> {
#[pin]
inner: W,
buf: Vec<u8>,
cipher: XSalsa20,
}
impl<W: AsyncWrite> CryptWriter<W> {
pub(crate) fn with_capacity(capacity: usize, inner: W, cipher: XSalsa20) -> CryptWriter<W> {
CryptWriter {
inner,
buf: Vec::with_capacity(capacity),
cipher,
}
}
pub(crate) fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut W> {
self.project().inner
}
}
fn poll_flush_buf<W: AsyncWrite>(
inner: &mut Pin<&mut W>,
buf: &mut Vec<u8>,
cx: &mut Context<'_>,
) -> Poll<io::Result<()>> {
let mut ret = Poll::Ready(Ok(()));
let mut written = 0;
let len = buf.len();
while written < len {
match inner.as_mut().poll_write(cx, &buf[written..]) {
Poll::Ready(Ok(n)) => {
if n > 0 {
written += n;
} else {
ret = Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"Failed to write buffered data",
)));
break;
}
}
Poll::Ready(Err(e)) => {
if e.kind() != io::ErrorKind::Interrupted {
ret = Poll::Ready(Err(e));
break;
}
}
Poll::Pending => {
ret = Poll::Pending;
break;
}
}
}
if written > 0 {
buf.drain(..written);
}
if let Poll::Ready(Ok(())) = ret {
debug_assert!(buf.is_empty());
}
ret
}
impl<W: AsyncWrite> AsyncWrite for CryptWriter<W> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let mut this = self.project();
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
debug_assert!(this.buf.is_empty());
let res = Pin::new(&mut *this.buf).poll_write(cx, buf);
if let Poll::Ready(Ok(count)) = res {
this.cipher.apply_keystream(&mut this.buf[0..count]);
tracing::trace!(bytes=%count, "encrypted bytes");
} else {
debug_assert!(false);
};
if let Poll::Ready(Err(e)) = poll_flush_buf(&mut this.inner, this.buf, cx) {
Poll::Ready(Err(e))
} else {
res
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
this.inner.poll_flush(cx)
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
let mut this = self.project();
ready!(poll_flush_buf(&mut this.inner, this.buf, cx))?;
this.inner.poll_close(cx)
}
}
impl<W: AsyncWrite + fmt::Debug> fmt::Debug for CryptWriter<W> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CryptWriter")
.field("writer", &self.inner)
.field("buf", &self.buf)
.finish()
}
}