Skip to main content

async_encrypted_stream/
write_half.rs

1use aead_stream::{Encryptor, NonceSize, StreamPrimitive};
2use bytes::{Buf, BufMut, BytesMut};
3use chacha20poly1305::aead::{array::ArraySize, AeadInOut};
4
5use std::{
6    ops::Sub,
7    pin::Pin,
8    task::{Poll, ready},
9};
10
11use tokio::io::AsyncWrite;
12
13use crate::{DEFAULT_BUFFER_SIZE, DEFAULT_CHUNK_SIZE};
14
15pin_project_lite::pin_project! {
16    /// Async Encryption Write Half.
17    ///
18    /// This struct has an internal buffer to hold encrypted bytes that were not written to the
19    /// inner writter. Under "normal" circunstances, the internal buffer will be seldom used.
20    pub struct WriteHalf<T, U> {
21        #[pin]
22        inner: T,
23        encryptor: U,
24        buffer: bytes::BytesMut,
25        chunk_size: usize
26    }
27}
28
29impl<T, A, S> WriteHalf<T, Encryptor<A, S>>
30where
31    T: AsyncWrite,
32    S: StreamPrimitive<A>,
33    A: AeadInOut,
34    A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
35    NonceSize<A, S>: ArraySize,
36{
37    pub fn new(inner: T, encryptor: Encryptor<A, S>) -> Self {
38        Self::with_capacity(inner, encryptor, DEFAULT_BUFFER_SIZE, DEFAULT_CHUNK_SIZE)
39    }
40
41    pub fn with_capacity(
42        inner: T,
43        encryptor: Encryptor<A, S>,
44        size: usize,
45        chunk_size: usize,
46    ) -> Self {
47        Self {
48            inner,
49            encryptor,
50            buffer: BytesMut::with_capacity(size),
51            chunk_size,
52        }
53    }
54
55    /// Encrypts `buf` contents and return a [`Vec<u8>`] with 4 bytes in LE representing the encrypted content
56    /// length and the encrypted contents.
57    ///
58    /// [0, 0, 0, 0, ...]
59    ///
60    /// If the encryption fails, it returns [std::error::ErrorKind::InvalidInput]
61    fn get_encrypted(&mut self, buf: &[u8]) -> std::io::Result<Vec<u8>> {
62        let mut encrypted = self
63            .encryptor
64            .encrypt_next(buf)
65            .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?;
66
67        let len = (encrypted.len() as u32).to_le_bytes();
68        let mut buf = Vec::with_capacity(encrypted.len() + std::mem::size_of::<u32>());
69        buf.extend_from_slice(&len);
70        buf.append(&mut encrypted);
71
72        Ok(buf)
73    }
74
75    /// Flush the internal buffer into the inner writer. This functions does nothing if the
76    /// internal buffer is empty.   
77    ///
78    /// If the inner writter writes 0 bytes, this function will return an
79    /// [std::io::ErrorKind::WriteZero] error.
80    fn flush_buf(
81        self: Pin<&mut Self>,
82        cx: &mut std::task::Context<'_>,
83    ) -> Poll<std::io::Result<()>> {
84        let mut me = self.project();
85        while me.buffer.has_remaining() {
86            match ready!(me.inner.as_mut().poll_write(cx, &me.buffer[..])) {
87                Ok(0) => {
88                    return Poll::Ready(Err(std::io::Error::new(
89                        std::io::ErrorKind::WriteZero,
90                        "failed to write the buffered data",
91                    )));
92                }
93                Ok(n) => me.buffer.advance(n),
94                Err(e) => return Poll::Ready(Err(e)),
95            }
96        }
97
98        Poll::Ready(Ok(()))
99    }
100}
101
102impl<T, A, S> AsyncWrite for WriteHalf<T, Encryptor<A, S>>
103where
104    T: AsyncWrite + Unpin,
105    S: StreamPrimitive<A>,
106    A: AeadInOut,
107    A::NonceSize: Sub<<S as StreamPrimitive<A>>::NonceOverhead>,
108    NonceSize<A, S>: ArraySize,
109{
110    /// Encrypt `buf` content, write into `self.inner` and returns the number of bytes
111    /// encrypted.
112    ///
113    /// Since tokio runtime will call this function repeatedly with the same contents when
114    /// [Poll::Pending] is returned, this function may return [Poll::Pending] only when
115    /// trying to flush the internal buffer, otherwise it will always return `Poll::Ready(Ok(n))`,
116    /// even if the inner writer fails.
117    ///
118    /// This behavior was adopted to guarantee parity with the reading counterpart,
119    /// the contents of `buf` must be encrypted only once, if the internal writing operation fails,
120    /// the already encrypted contents will be written into the internal buffer instead.
121    ///
122    /// It is guaranteed that `0 <= n <= buf.len()`
123    ///
124    /// Internally, the contents of `buf` will be splitted into chunks of `self.chunk_size` size,
125    /// default to 1024 bytes, to avoid allocating a huge `Vec<u8>` when encrypting larger messages.
126    fn poll_write(
127        mut self: Pin<&mut Self>,
128        cx: &mut std::task::Context<'_>,
129        buf: &[u8],
130    ) -> std::task::Poll<Result<usize, std::io::Error>> {
131        if !self.buffer.is_empty() {
132            ready!(self.as_mut().flush_buf(cx))?
133        }
134
135        let mut total_written = 0;
136        for chunk in buf.chunks(self.chunk_size) {
137            let encrypted = self.get_encrypted(chunk)?;
138            total_written += chunk.len();
139
140            let me = self.as_mut().project();
141            match me.inner.poll_write(cx, &encrypted[..]) {
142                Poll::Ready(Ok(written)) => {
143                    if written < encrypted.len() {
144                        self.buffer.put(&encrypted[written..]);
145                        return Poll::Ready(Ok(total_written));
146                    }
147                }
148                Poll::Pending | Poll::Ready(Err(..)) => {
149                    self.buffer.put(&encrypted[..]);
150                    return Poll::Ready(Ok(total_written));
151                }
152            }
153        }
154        Poll::Ready(Ok(buf.len()))
155    }
156
157    fn poll_flush(
158        mut self: Pin<&mut Self>,
159        cx: &mut std::task::Context<'_>,
160    ) -> std::task::Poll<Result<(), std::io::Error>> {
161        ready!(self.as_mut().flush_buf(cx))?;
162        self.project().inner.poll_flush(cx)
163    }
164
165    fn poll_shutdown(
166        self: Pin<&mut Self>,
167        cx: &mut std::task::Context<'_>,
168    ) -> std::task::Poll<Result<(), std::io::Error>> {
169        self.project().inner.poll_shutdown(cx)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use std::assert_eq;
176
177    use aead_stream::EncryptorLE31;
178    use chacha20poly1305::{KeyInit, XChaCha20Poly1305};
179    use tokio::io::AsyncWriteExt;
180
181    use crate::get_key;
182
183    use super::*;
184
185    #[tokio::test]
186    pub async fn test_crypto_stream_write_half() {
187        let key: [u8; 32] = get_key("key", "group");
188        let start_nonce = [0u8; 20];
189
190        let mut encryptor: EncryptorLE31<XChaCha20Poly1305> = EncryptorLE31::from_aead(
191            XChaCha20Poly1305::new((&key).into()),
192            (&start_nonce).into(),
193        );
194
195        let expected = {
196            let mut encrypted = encryptor.encrypt_next("some content".as_bytes()).unwrap();
197            let mut expected = Vec::new();
198            expected.extend((encrypted.len() as u32).to_le_bytes());
199            expected.append(&mut encrypted);
200
201            expected
202        };
203
204        let mut writer = WriteHalf::new(
205            tokio::io::BufWriter::new(Vec::new()),
206            EncryptorLE31::from_aead(
207                XChaCha20Poly1305::new((&key).into()),
208                (&start_nonce).into(),
209            ),
210        );
211
212        assert_eq!(
213            writer.write(b"some content").await.unwrap(),
214            "some content".len()
215        );
216
217        assert_eq!(expected, writer.inner.buffer())
218    }
219}