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