1use core::fmt;
40
41use alloc::{
42 borrow::Cow,
43 string::{String, ToString},
44 vec::Vec,
45};
46
47use log::trace;
48use thiserror::Error;
49
50use crate::{coroutine::*, rfc5321::types::reply_code::ReplyCode, send::*, smtp_try};
51
52pub struct SmtpNoopCommand<'a> {
54 pub string: Option<Cow<'a, str>>,
56}
57
58impl<'a> From<SmtpNoopCommand<'a>> for Vec<u8> {
59 fn from(cmd: SmtpNoopCommand<'a>) -> Vec<u8> {
60 let mut buf = String::from("NOOP");
61
62 if let Some(s) = cmd.string {
63 buf.push(' ');
64 buf.push_str(&s);
65 }
66
67 buf.push_str("\r\n");
68 buf.into_bytes()
69 }
70}
71
72#[derive(Clone, Debug, Error)]
74pub enum SmtpNoopError {
75 #[error("SMTP NOOP failed: rejected {code} {message}")]
76 Rejected { code: u16, message: String },
77 #[error("SMTP NOOP failed: {0}")]
78 Send(#[from] SendSmtpCommandError),
79}
80
81pub struct SmtpNoop {
83 state: State,
84}
85
86impl SmtpNoop {
87 pub fn new() -> Self {
88 Self {
89 state: State::Send(SendSmtpCommand::new(SmtpNoopCommand { string: None })),
90 }
91 }
92}
93
94impl Default for SmtpNoop {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100impl SmtpCoroutine for SmtpNoop {
101 type Yield = SmtpYield;
102 type Return = Result<(), SmtpNoopError>;
103
104 fn resume(&mut self, arg: Option<&[u8]>) -> SmtpCoroutineState<Self::Yield, Self::Return> {
105 loop {
106 trace!("noop: {}", self.state);
107
108 match &mut self.state {
109 State::Send(send) => {
110 let out = smtp_try!(send, arg);
111
112 if out.response.code == ReplyCode::OK {
113 return SmtpCoroutineState::Complete(Ok(()));
114 }
115
116 let code = out.response.code.code();
117 let message = out.response.text().to_string();
118 return SmtpCoroutineState::Complete(Err(SmtpNoopError::Rejected {
119 code,
120 message,
121 }));
122 }
123 }
124 }
125 }
126}
127
128enum State {
129 Send(SendSmtpCommand<SmtpNoopCommand<'static>>),
130}
131
132impl fmt::Display for State {
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 match self {
135 Self::Send(_) => f.write_str("send noop"),
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
145 fn success_returns_ok() {
146 let mut noop = SmtpNoop::new();
147
148 let bytes = expect_wants_write(&mut noop, None);
149 assert_eq!(bytes, b"NOOP\r\n");
150
151 expect_wants_read(&mut noop);
152 expect_complete_ok(&mut noop, b"250 OK\r\n");
153 }
154
155 #[test]
156 fn rejected_returns_rejected_error() {
157 let mut noop = SmtpNoop::new();
158 let _ = expect_wants_write(&mut noop, None);
159 expect_wants_read(&mut noop);
160
161 let err = expect_complete_err(&mut noop, b"500 syntax error\r\n");
162 let SmtpNoopError::Rejected { code, message } = err else {
163 panic!("expected SmtpNoopError::Rejected, got {err:?}");
164 };
165 assert_eq!(code, 500);
166 assert_eq!(message, "syntax error");
167 }
168
169 #[test]
170 fn eof_returns_eof_error() {
171 let mut noop = SmtpNoop::new();
172 let _ = expect_wants_write(&mut noop, None);
173 expect_wants_read(&mut noop);
174
175 let err = expect_complete_err(&mut noop, b"");
176 assert!(matches!(
177 err,
178 SmtpNoopError::Send(SendSmtpCommandError::Eof)
179 ));
180 }
181
182 fn expect_wants_write(cor: &mut SmtpNoop, arg: Option<&[u8]>) -> Vec<u8> {
185 match cor.resume(arg) {
186 SmtpCoroutineState::Yielded(SmtpYield::WantsWrite(bytes)) => bytes,
187 state => panic!("expected WantsWrite, got {state:?}"),
188 }
189 }
190
191 fn expect_wants_read(cor: &mut SmtpNoop) {
192 match cor.resume(None) {
193 SmtpCoroutineState::Yielded(SmtpYield::WantsRead) => {}
194 state => panic!("expected WantsRead, got {state:?}"),
195 }
196 }
197
198 fn expect_complete_ok(cor: &mut SmtpNoop, reply: &[u8]) {
199 match cor.resume(Some(reply)) {
200 SmtpCoroutineState::Complete(Ok(())) => {}
201 state => panic!("expected Complete(Ok), got {state:?}"),
202 }
203 }
204
205 fn expect_complete_err(cor: &mut SmtpNoop, reply: &[u8]) -> SmtpNoopError {
206 match cor.resume(Some(reply)) {
207 SmtpCoroutineState::Complete(Err(err)) => err,
208 state => panic!("expected Complete(Err), got {state:?}"),
209 }
210 }
211}