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"
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",

View file

@ -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"

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::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 }
}

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]
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;

View file

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

View file

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

View file

@ -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<Cow<'a, str>>,
}
#[derive(Debug)]
pub enum Status {
Ok,
No,
@ -22,11 +25,13 @@ pub enum Status {
Bye,
}
#[derive(Debug)]
pub enum ResponseCode<'a> {
Alert,
Capabilities(Vec<Capability<'a>>),
}
#[derive(Debug)]
pub enum Capability<'a> {
Imap4rev1,
Auth(Cow<'a, [u8]>),

View file

@ -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};