added some high level client stuff

This commit is contained in:
Michael Zhang 2021-08-08 00:54:01 -05:00
parent bd2e670a70
commit 4ddcaf19c1
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
14 changed files with 158 additions and 48 deletions

13
Cargo.lock generated
View file

@ -14,6 +14,17 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.1" version = "1.0.1"
@ -260,6 +271,8 @@ name = "imap"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"bitflags",
"bytes", "bytes",
"derive_builder", "derive_builder",
"futures", "futures",

View file

@ -8,6 +8,8 @@ rfc2177-idle = []
[dependencies] [dependencies]
anyhow = "1.0.42" anyhow = "1.0.42"
async-trait = "0.1.51"
bitflags = "1.2.1"
bytes = "1.0.1" bytes = "1.0.1"
derive_builder = "0.10.2" derive_builder = "0.10.2"
futures = "0.3.16" futures = "0.3.16"

View file

8
imap/src/client/auth.rs Normal file
View file

@ -0,0 +1,8 @@
pub trait AuthMethod {}
pub struct Login {
pub username: String,
pub password: String,
}
impl AuthMethod for Login {}

61
imap/src/client/inner.rs Normal file
View file

@ -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<C> {
read_exit: ExitSender,
read_handle: JoinHandle<ReadHalf<C>>,
}
impl<C> Inner<C>
where
C: AsyncRead + AsyncWrite + Send + 'static,
{
pub async fn open(c: C) -> Result<Self> {
// 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<C>(stream: ReadHalf<C>, exit: ExitListener) -> ReadHalf<C>
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!()
}

3
imap/src/client/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod auth;
pub mod inner;
pub mod upgrade;

View file

@ -0,0 +1,4 @@
use anyhow::Result;
use tokio_rustls::TlsStream;
pub fn upgrade<C>(c: C) -> Result<TlsStream<C>> { todo!() }

View file

@ -1,11 +1,13 @@
use std::io; use std::io;
use std::mem;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use nom::{self, Needed};
use tokio_util::codec::{Decoder, Encoder}; use tokio_util::codec::{Decoder, Encoder};
use imap_proto::types::{Request, RequestId, Response}; use crate::proto::{
command::Command,
response::{Response, ResponseDone, Tag},
};
#[derive(Default)] #[derive(Default)]
pub struct ImapCodec { pub struct ImapCodec {
@ -19,45 +21,47 @@ impl<'a> Decoder for ImapCodec {
if self.decode_need_message_bytes > buf.len() { if self.decode_need_message_bytes > buf.len() {
return Ok(None); return Ok(None);
} }
let (response, rsp_len) = match imap_proto::Response::from_bytes(buf) { todo!()
Ok((remaining, response)) => { // let (response, rsp_len) = match Response::from_bytes(buf) {
// This SHOULD be acceptable/safe: BytesMut storage memory is // Ok((remaining, response)) => {
// allocated on the heap and should not move. It will not be // // This SHOULD be acceptable/safe: BytesMut storage memory is
// freed as long as we keep a reference alive, which we do // // allocated on the heap and should not move. It will not be
// by retaining a reference to the split buffer, below. // // freed as long as we keep a reference alive, which we do
let response = unsafe { mem::transmute(response) }; // // by retaining a reference to the split buffer, below.
(response, buf.len() - remaining.len()) // 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(); // Err(nom::Err::Incomplete(Needed::Size(min))) => {
return Ok(None); // self.decode_need_message_bytes = min.get();
} // return Ok(None);
Err(nom::Err::Incomplete(_)) => { // }
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, .. })) => { // Err(nom::Err::Error(nom::error::Error { code, .. }))
return Err(io::Error::new( // | Err(nom::Err::Failure(nom::error::Error { code, .. })) => {
io::ErrorKind::Other, // return Err(io::Error::new(
format!("{:?} during parsing of {:?}", code, buf), // io::ErrorKind::Other,
)); // format!("{:?} during parsing of {:?}", code, buf),
} // ));
}; // }
let raw = buf.split_to(rsp_len).freeze(); // };
self.decode_need_message_bytes = 0; // let raw = buf.split_to(rsp_len).freeze();
Ok(Some(ResponseData { raw, response })) // 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; type Error = io::Error;
fn encode(&mut self, msg: &Request, dst: &mut BytesMut) -> Result<(), io::Error> { fn encode(&mut self, msg: &Command, dst: &mut BytesMut) -> Result<(), io::Error> {
dst.put(&*msg.0); todo!()
dst.put_u8(b' '); // dst.put(&*msg.0);
dst.put_slice(&*msg.1); // dst.put_u8(b' ');
dst.put_slice(b"\r\n"); // dst.put_slice(&*msg.1);
Ok(()) // dst.put_slice(b"\r\n");
// Ok(())
} }
} }
@ -76,15 +80,13 @@ pub struct ResponseData {
} }
impl ResponseData { impl ResponseData {
pub fn request_id(&self) -> Option<&RequestId> { pub fn request_id(&self) -> Option<&Tag> {
match self.response { match self.response {
Response::Done { ref tag, .. } => Some(tag), Response::Done(ResponseDone { ref tag, .. }) => Some(tag),
_ => None, _ => None,
} }
} }
#[allow(clippy::needless_lifetimes)] #[allow(clippy::needless_lifetimes)]
pub fn parsed<'a>(&'a self) -> &'a Response<'a> { pub fn parsed<'a>(&'a self) -> &'a Response<'a> { &self.response }
&self.response
}
} }

5
imap/src/events.rs Normal file
View file

@ -0,0 +1,5 @@
bitflags! {
pub struct EventMask: u32 {
const NONE = 0;
}
}

View file

@ -1,16 +1,19 @@
#[macro_use] #[macro_use]
extern crate anyhow; extern crate anyhow;
#[macro_use] #[macro_use]
extern crate async_trait;
#[macro_use]
extern crate log; extern crate log;
#[macro_use] #[macro_use]
extern crate futures; extern crate futures;
#[macro_use] #[macro_use]
extern crate derive_builder; extern crate derive_builder;
#[macro_use] #[macro_use]
extern crate nom; extern crate bitflags;
// mod auth; // mod auth;
pub mod client; pub mod client;
// mod codec; mod codec;
mod events;
// mod inner; // mod inner;
pub mod proto; pub mod proto;

View file

@ -0,0 +1,3 @@
pub enum Command<'a> {
Todo(&'a ()),
}

View file

@ -4,6 +4,7 @@
mod macros; mod macros;
// data types // data types
pub mod command;
pub mod response; pub mod response;
// parsers // parsers

View file

@ -1,12 +1,14 @@
use std::borrow::Cow; use std::borrow::Cow;
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Tag(pub String); pub struct Tag(pub String);
#[derive(Debug)]
pub enum Response<'a> { pub enum Response<'a> {
Done(ResponseDone<'a>), Done(ResponseDone<'a>),
} }
#[derive(Debug)]
pub struct ResponseDone<'a> { pub struct ResponseDone<'a> {
pub tag: Tag, pub tag: Tag,
pub status: Status, pub status: Status,
@ -14,6 +16,7 @@ pub struct ResponseDone<'a> {
pub info: Option<Cow<'a, str>>, pub info: Option<Cow<'a, str>>,
} }
#[derive(Debug)]
pub enum Status { pub enum Status {
Ok, Ok,
No, No,
@ -22,11 +25,13 @@ pub enum Status {
Bye, Bye,
} }
#[derive(Debug)]
pub enum ResponseCode<'a> { pub enum ResponseCode<'a> {
Alert, Alert,
Capabilities(Vec<Capability<'a>>), Capabilities(Vec<Capability<'a>>),
} }
#[derive(Debug)]
pub enum Capability<'a> { pub enum Capability<'a> {
Imap4rev1, Imap4rev1,
Auth(Cow<'a, [u8]>), Auth(Cow<'a, [u8]>),

View file

@ -4,7 +4,7 @@ use std::borrow::Cow;
use nom::{ use nom::{
branch::alt, branch::alt,
bytes::streaming::{tag_no_case, take_while1, take}, bytes::streaming::{tag_no_case, take, take_while1},
character::streaming::char, character::streaming::char,
combinator::{map, map_res}, combinator::{map, map_res},
multi::{many0, many1}, multi::{many0, many1},
@ -12,7 +12,7 @@ use nom::{
IResult, IResult,
}; };
use super::parsers::{satisfy, byte}; use super::parsers::{byte, satisfy};
use super::response::{Capability, ResponseCode}; use super::response::{Capability, ResponseCode};
use super::rfc2234::{is_char, is_cr, is_ctl, is_dquote, is_lf, is_sp, CRLF, DIGIT, DQUOTE, SP}; use super::rfc2234::{is_char, is_cr, is_ctl, is_dquote, is_lf, is_sp, CRLF, DIGIT, DQUOTE, SP};