use core::{fmt, marker::PhantomData, mem};
use alloc::{string::String, vec::Vec};
use bounded_static::IntoBoundedStatic;
use log::trace;
use thiserror::Error;
use crate::{
coroutine::*,
rfc5321::types::response::Response,
utils::{escape_byte_string, parsers::format_rich_errors},
};
#[derive(Clone, Debug, Error)]
pub enum SendSmtpCommandError {
#[error("Reached unexpected EOF on SMTP stream")]
Eof,
#[error("Parse SMTP response error: {0}")]
ParseResponse(String),
}
pub struct SendSmtpCommandOk {
pub response: Response<'static>,
}
enum State {
Write,
Read,
Parse,
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Write => f.write_str("write command"),
Self::Read => f.write_str("read response"),
Self::Parse => f.write_str("parse response"),
}
}
}
pub struct SendSmtpCommand<Cmd> {
bytes: Option<Vec<u8>>,
state: State,
wants_read: bool,
buf: Vec<u8>,
_cmd: PhantomData<Cmd>,
}
impl<Cmd: Into<Vec<u8>>> SendSmtpCommand<Cmd> {
pub fn new(cmd: Cmd) -> Self {
Self {
bytes: Some(cmd.into()),
state: State::Write,
wants_read: false,
buf: Vec::new(),
_cmd: PhantomData,
}
}
}
impl<Cmd> SmtpCoroutine for SendSmtpCommand<Cmd> {
type Yield = SmtpYield;
type Return = Result<SendSmtpCommandOk, SendSmtpCommandError>;
fn resume(&mut self, mut arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
loop {
trace!("send: {}", self.state);
if mem::take(&mut self.wants_read) {
return SmtpCoroutineState::Yielded(SmtpYield::WantsRead);
}
match &mut self.state {
State::Write => {
let bytes = self.bytes.take().expect("command bytes taken twice");
self.state = State::Read;
return SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes));
}
State::Read => match arg.take() {
Some(&[]) => {
return SmtpCoroutineState::Complete(Err(SendSmtpCommandError::Eof));
}
Some(data) => {
trace!("read SMTP bytes: {}", escape_byte_string(data));
self.buf.extend_from_slice(data);
if !Response::is_complete(&self.buf) {
self.wants_read = true;
continue;
}
self.state = State::Parse;
}
None => {
self.wants_read = true;
}
},
State::Parse => {
return match Response::parse(&self.buf) {
Ok(response) => {
let response = response.into_static();
let _ = mem::take(&mut self.buf);
SmtpCoroutineState::Complete(Ok(SendSmtpCommandOk { response }))
}
Err(errors) => {
let reason = format_rich_errors(errors);
let err = SendSmtpCommandError::ParseResponse(reason);
SmtpCoroutineState::Complete(Err(err))
}
};
}
}
}
}
}