From 4ddcaf19c1006f9a61d6f3e1032ccaced218cd8a Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Sun, 8 Aug 2021 00:54:01 -0500 Subject: [PATCH] added some high level client stuff --- Cargo.lock | 13 ++++++ imap/Cargo.toml | 2 + imap/src/client.rs | 0 imap/src/client/auth.rs | 8 ++++ imap/src/client/inner.rs | 61 ++++++++++++++++++++++++++ imap/src/client/mod.rs | 3 ++ imap/src/client/upgrade.rs | 4 ++ imap/src/codec.rs | 88 +++++++++++++++++++------------------- imap/src/events.rs | 5 +++ imap/src/lib.rs | 7 ++- imap/src/proto/command.rs | 3 ++ imap/src/proto/mod.rs | 1 + imap/src/proto/response.rs | 7 ++- imap/src/proto/rfc3501.rs | 4 +- 14 files changed, 158 insertions(+), 48 deletions(-) delete mode 100644 imap/src/client.rs create mode 100644 imap/src/client/auth.rs create mode 100644 imap/src/client/inner.rs create mode 100644 imap/src/client/mod.rs create mode 100644 imap/src/client/upgrade.rs create mode 100644 imap/src/events.rs create mode 100644 imap/src/proto/command.rs diff --git a/Cargo.lock b/Cargo.lock index 4b4bb3f..aeb5bf9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-trait" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -260,6 +271,8 @@ name = "imap" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "bitflags", "bytes", "derive_builder", "futures", diff --git a/imap/Cargo.toml b/imap/Cargo.toml index 24520ff..20f39a0 100644 --- a/imap/Cargo.toml +++ b/imap/Cargo.toml @@ -8,6 +8,8 @@ rfc2177-idle = [] [dependencies] anyhow = "1.0.42" +async-trait = "0.1.51" +bitflags = "1.2.1" bytes = "1.0.1" derive_builder = "0.10.2" futures = "0.3.16" diff --git a/imap/src/client.rs b/imap/src/client.rs deleted file mode 100644 index e69de29..0000000 diff --git a/imap/src/client/auth.rs b/imap/src/client/auth.rs new file mode 100644 index 0000000..c45e15c --- /dev/null +++ b/imap/src/client/auth.rs @@ -0,0 +1,8 @@ +pub trait AuthMethod {} + +pub struct Login { + pub username: String, + pub password: String, +} + +impl AuthMethod for Login {} diff --git a/imap/src/client/inner.rs b/imap/src/client/inner.rs new file mode 100644 index 0000000..11096a6 --- /dev/null +++ b/imap/src/client/inner.rs @@ -0,0 +1,61 @@ +use anyhow::Result; +use futures::future::FutureExt; +use tokio::{ + io::{split, AsyncRead, AsyncWrite, ReadHalf}, + sync::oneshot, + task::JoinHandle, +}; +use tokio_util::codec::FramedRead; + +use crate::codec::ImapCodec; + +type ExitSender = oneshot::Sender<()>; +type ExitListener = oneshot::Receiver<()>; + +pub struct Inner { + read_exit: ExitSender, + read_handle: JoinHandle>, +} + +impl Inner +where + C: AsyncRead + AsyncWrite + Send + 'static, +{ + pub async fn open(c: C) -> Result { + // break the stream of bytes into a reader and a writer + // the read_half represents the server->client connection + // the write_half represents the client->server connection + let (read_half, write_half) = split(c); + + // spawn the server->client loop + let (read_exit, exit_rx) = oneshot::channel(); + let read_handle = tokio::spawn(read_loop(read_half, exit_rx)); + + Ok(Inner { + read_exit, + read_handle, + }) + } +} + +// exit is a channel that will notify this loop when some external +// even requires this loop to stop (for example, TLS upgrade). +// +// when the loop exits, the read half of the stream will be returned +async fn read_loop(stream: ReadHalf, exit: ExitListener) -> ReadHalf +where + C: AsyncRead, +{ + // set up framed communication + let codec = ImapCodec::default(); + let framed = FramedRead::new(stream, codec); + + let exit = exit.fuse(); + pin_mut!(exit); + loop { + select! { + _ = exit => break, + } + } + todo!() +} diff --git a/imap/src/client/mod.rs b/imap/src/client/mod.rs new file mode 100644 index 0000000..deccb45 --- /dev/null +++ b/imap/src/client/mod.rs @@ -0,0 +1,3 @@ +pub mod auth; +pub mod inner; +pub mod upgrade; diff --git a/imap/src/client/upgrade.rs b/imap/src/client/upgrade.rs new file mode 100644 index 0000000..790d6e5 --- /dev/null +++ b/imap/src/client/upgrade.rs @@ -0,0 +1,4 @@ +use anyhow::Result; +use tokio_rustls::TlsStream; + +pub fn upgrade(c: C) -> Result> { todo!() } diff --git a/imap/src/codec.rs b/imap/src/codec.rs index 85f87f9..388073f 100644 --- a/imap/src/codec.rs +++ b/imap/src/codec.rs @@ -1,11 +1,13 @@ use std::io; -use std::mem; use bytes::{BufMut, Bytes, BytesMut}; -use nom::{self, Needed}; + use tokio_util::codec::{Decoder, Encoder}; -use imap_proto::types::{Request, RequestId, Response}; +use crate::proto::{ + command::Command, + response::{Response, ResponseDone, Tag}, +}; #[derive(Default)] pub struct ImapCodec { @@ -19,45 +21,47 @@ impl<'a> Decoder for ImapCodec { if self.decode_need_message_bytes > buf.len() { return Ok(None); } - - let (response, rsp_len) = match imap_proto::Response::from_bytes(buf) { - Ok((remaining, response)) => { - // This SHOULD be acceptable/safe: BytesMut storage memory is - // allocated on the heap and should not move. It will not be - // freed as long as we keep a reference alive, which we do - // by retaining a reference to the split buffer, below. - let response = unsafe { mem::transmute(response) }; - (response, buf.len() - remaining.len()) - } - Err(nom::Err::Incomplete(Needed::Size(min))) => { - self.decode_need_message_bytes = min.get(); - return Ok(None); - } - Err(nom::Err::Incomplete(_)) => { - return Ok(None); - } - Err(nom::Err::Error(nom::error::Error { code, .. })) - | Err(nom::Err::Failure(nom::error::Error { code, .. })) => { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("{:?} during parsing of {:?}", code, buf), - )); - } - }; - let raw = buf.split_to(rsp_len).freeze(); - self.decode_need_message_bytes = 0; - Ok(Some(ResponseData { raw, response })) + + todo!() + // let (response, rsp_len) = match Response::from_bytes(buf) { + // Ok((remaining, response)) => { + // // This SHOULD be acceptable/safe: BytesMut storage memory is + // // allocated on the heap and should not move. It will not be + // // freed as long as we keep a reference alive, which we do + // // by retaining a reference to the split buffer, below. + // let response = unsafe { mem::transmute(response) }; + // (response, buf.len() - remaining.len()) + // } + // Err(nom::Err::Incomplete(Needed::Size(min))) => { + // self.decode_need_message_bytes = min.get(); + // return Ok(None); + // } + // Err(nom::Err::Incomplete(_)) => { + // return Ok(None); + // } + // Err(nom::Err::Error(nom::error::Error { code, .. })) + // | Err(nom::Err::Failure(nom::error::Error { code, .. })) => { + // return Err(io::Error::new( + // io::ErrorKind::Other, + // format!("{:?} during parsing of {:?}", code, buf), + // )); + // } + // }; + // let raw = buf.split_to(rsp_len).freeze(); + // self.decode_need_message_bytes = 0; + // Ok(Some(ResponseData { raw, response })) } } -impl<'a> Encoder<&'a Request<'a>> for ImapCodec { +impl<'a> Encoder<&'a Command<'a>> for ImapCodec { type Error = io::Error; - fn encode(&mut self, msg: &Request, dst: &mut BytesMut) -> Result<(), io::Error> { - dst.put(&*msg.0); - dst.put_u8(b' '); - dst.put_slice(&*msg.1); - dst.put_slice(b"\r\n"); - Ok(()) + fn encode(&mut self, msg: &Command, dst: &mut BytesMut) -> Result<(), io::Error> { + todo!() + // dst.put(&*msg.0); + // dst.put_u8(b' '); + // dst.put_slice(&*msg.1); + // dst.put_slice(b"\r\n"); + // Ok(()) } } @@ -76,15 +80,13 @@ pub struct ResponseData { } impl ResponseData { - pub fn request_id(&self) -> Option<&RequestId> { + pub fn request_id(&self) -> Option<&Tag> { match self.response { - Response::Done { ref tag, .. } => Some(tag), + Response::Done(ResponseDone { ref tag, .. }) => Some(tag), _ => None, } } #[allow(clippy::needless_lifetimes)] - pub fn parsed<'a>(&'a self) -> &'a Response<'a> { - &self.response - } + pub fn parsed<'a>(&'a self) -> &'a Response<'a> { &self.response } } diff --git a/imap/src/events.rs b/imap/src/events.rs new file mode 100644 index 0000000..37e86d9 --- /dev/null +++ b/imap/src/events.rs @@ -0,0 +1,5 @@ +bitflags! { + pub struct EventMask: u32 { + const NONE = 0; + } +} diff --git a/imap/src/lib.rs b/imap/src/lib.rs index 62b5adc..d88ae60 100644 --- a/imap/src/lib.rs +++ b/imap/src/lib.rs @@ -1,16 +1,19 @@ #[macro_use] extern crate anyhow; #[macro_use] +extern crate async_trait; +#[macro_use] extern crate log; #[macro_use] extern crate futures; #[macro_use] extern crate derive_builder; #[macro_use] -extern crate nom; +extern crate bitflags; // mod auth; pub mod client; -// mod codec; +mod codec; +mod events; // mod inner; pub mod proto; diff --git a/imap/src/proto/command.rs b/imap/src/proto/command.rs new file mode 100644 index 0000000..1edbfa1 --- /dev/null +++ b/imap/src/proto/command.rs @@ -0,0 +1,3 @@ +pub enum Command<'a> { + Todo(&'a ()), +} diff --git a/imap/src/proto/mod.rs b/imap/src/proto/mod.rs index ceabbbf..89ca9c5 100644 --- a/imap/src/proto/mod.rs +++ b/imap/src/proto/mod.rs @@ -4,6 +4,7 @@ mod macros; // data types +pub mod command; pub mod response; // parsers diff --git a/imap/src/proto/response.rs b/imap/src/proto/response.rs index 8d4fb62..7fb759b 100644 --- a/imap/src/proto/response.rs +++ b/imap/src/proto/response.rs @@ -1,12 +1,14 @@ use std::borrow::Cow; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Tag(pub String); +#[derive(Debug)] pub enum Response<'a> { Done(ResponseDone<'a>), } +#[derive(Debug)] pub struct ResponseDone<'a> { pub tag: Tag, pub status: Status, @@ -14,6 +16,7 @@ pub struct ResponseDone<'a> { pub info: Option>, } +#[derive(Debug)] pub enum Status { Ok, No, @@ -22,11 +25,13 @@ pub enum Status { Bye, } +#[derive(Debug)] pub enum ResponseCode<'a> { Alert, Capabilities(Vec>), } +#[derive(Debug)] pub enum Capability<'a> { Imap4rev1, Auth(Cow<'a, [u8]>), diff --git a/imap/src/proto/rfc3501.rs b/imap/src/proto/rfc3501.rs index de60e60..76e6f8f 100644 --- a/imap/src/proto/rfc3501.rs +++ b/imap/src/proto/rfc3501.rs @@ -4,7 +4,7 @@ use std::borrow::Cow; use nom::{ branch::alt, - bytes::streaming::{tag_no_case, take_while1, take}, + bytes::streaming::{tag_no_case, take, take_while1}, character::streaming::char, combinator::{map, map_res}, multi::{many0, many1}, @@ -12,7 +12,7 @@ use nom::{ IResult, }; -use super::parsers::{satisfy, byte}; +use super::parsers::{byte, satisfy}; use super::response::{Capability, ResponseCode}; use super::rfc2234::{is_char, is_cr, is_ctl, is_dquote, is_lf, is_sp, CRLF, DIGIT, DQUOTE, SP};