futures-loco-protocol 0.4.0

Asynchronous Loco protocol stream for futures
Documentation
use std::{
    io::{self, Cursor, ErrorKind, Read},
    mem,
    pin::Pin,
    task::{ready, Context, Poll},
};

use futures_io::{AsyncRead, AsyncWrite};
use loco_protocol::secure::{
    client::{LocoClientSecureLayer, ReadState as LayerReadState, rsa::RsaPublicKey},
    SecurePacket,
};
use rand::RngCore;

pub use loco_protocol::secure::client::rsa;

pin_project_lite::pin_project! {
    #[derive(Debug)]
    pub struct LocoSecureStream<T> {
        read_state: ReadState,
        write_state: WriteState,

        layer: LocoClientSecureLayer,

        #[pin]
        inner: T,
    }
}

impl<T> LocoSecureStream<T> {
    pub const MAX_IO_SIZE: u64 = 16 * 1024 * 1024;

    pub fn new(rsa_key: RsaPublicKey, inner: T) -> Self {
        let mut key = [0_u8; 16];
        rand::thread_rng().fill_bytes(&mut key);

        Self {
            read_state: ReadState::Pending,
            write_state: WriteState::Initial(rsa_key),

            layer: LocoClientSecureLayer::new(key),

            inner,
        }
    }

    pub fn inner(&self) -> &T {
        &self.inner
    }

    pub fn inner_mut(&mut self) -> &mut T {
        &mut self.inner
    }

    pub fn into_inner(self) -> T {
        self.inner
    }
}

impl<T: AsyncRead> AsyncRead for LocoSecureStream<T> {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<io::Result<usize>> {
        let mut this = self.project();

        loop {
            match mem::replace(this.read_state, ReadState::Corrupted) {
                ReadState::Pending => {
                    if let Some(packet) = this.layer.read() {
                        *this.read_state = ReadState::Reading(Cursor::new(packet.data));
                    } else {
                        if let LayerReadState::Header(header) = this.layer.read_state() {
                            if header.size as u64 - 16 > Self::MAX_IO_SIZE {
                                *this.read_state = ReadState::PacketTooLarge;
                                continue;
                            }
                        }

                        let mut read_buf = [0_u8; 1024];

                        *this.read_state = ReadState::Pending;

                        let read = ready!(this.inner.as_mut().poll_read(cx, &mut read_buf))?;
                        if read == 0 {
                            *this.read_state = ReadState::Done;
                            continue;
                        }

                        this.layer.read_buffer.extend(&read_buf[..read]);
                    }
                }

                ReadState::Reading(mut cursor) => {
                    let read = cursor.read(buf)?;

                    *this.read_state = if cursor.position() as usize == cursor.get_ref().len() {
                        ReadState::Pending
                    } else {
                        ReadState::Reading(cursor)
                    };

                    break Poll::Ready(Ok(read));
                }

                ReadState::PacketTooLarge => {
                    *this.read_state = ReadState::PacketTooLarge;

                    break Poll::Ready(Err(io::Error::new(
                        ErrorKind::InvalidData,
                        "packet is too large",
                    )));
                }

                ReadState::Done => break Poll::Ready(Err(ErrorKind::UnexpectedEof.into())),

                ReadState::Corrupted => unreachable!(),
            }
        }
    }
}

impl<T: AsyncWrite> AsyncWrite for LocoSecureStream<T> {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<io::Result<usize>> {
        let mut this = self.project();

        loop {
            match mem::replace(this.write_state, WriteState::Corrupted) {
                WriteState::Initial(key) => {
                    this.layer.handshake(&key);

                    *this.write_state = WriteState::Pending;
                }

                WriteState::Pending => {
                    let data = if buf.len() as u64 > Self::MAX_IO_SIZE {
                        &buf[..Self::MAX_IO_SIZE as usize]
                    } else {
                        buf
                    };

                    let mut iv = [0_u8; 16];
                    rand::thread_rng().fill_bytes(&mut iv);

                    *this.write_state = WriteState::Writing(data.len());
                    this.layer.send(SecurePacket { iv, data });
                }

                WriteState::Writing(size) => {
                    let write_buffer = &mut this.layer.write_buffer;

                    loop {
                        let slice = {
                            let slices = write_buffer.as_slices();

                            if !slices.0.is_empty() {
                                slices.0
                            } else {
                                slices.1
                            }
                        };

                        match this.inner.as_mut().poll_write(cx, slice)? {
                            Poll::Ready(written) => {
                                write_buffer.drain(..written);
                            }

                            Poll::Pending => {
                                *this.write_state = WriteState::Writing(size);
                                return Poll::Pending;
                            }
                        }

                        if write_buffer.is_empty() {
                            *this.write_state = WriteState::Pending;
                            return Poll::Ready(Ok(size));
                        }
                    }
                }

                WriteState::Corrupted => unreachable!(),
            }
        }
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
        self.project().inner.poll_flush(cx)
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
        self.project().inner.poll_close(cx)
    }
}

#[derive(Debug)]
enum ReadState {
    Pending,
    Reading(Cursor<Box<[u8]>>),
    PacketTooLarge,
    Done,
    Corrupted,
}

#[derive(Debug)]
enum WriteState {
    Initial(RsaPublicKey),
    Pending,
    Writing(usize),
    Corrupted,
}