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