Skip to main content

imap_codec/
codec.rs

1pub mod decode;
2pub mod encode;
3
4/// Codec for greetings.
5#[derive(Clone, Debug, Default, PartialEq)]
6// We use `#[non_exhaustive]` to prevent users from using struct literal syntax.
7//
8// This allows to add configuration options later. For example, the
9// codec could transparently replace all literals with non-sync literals.
10#[non_exhaustive]
11pub struct GreetingCodec;
12
13/// Codec for commands.
14#[derive(Clone, Debug, Default, PartialEq)]
15#[non_exhaustive]
16pub struct CommandCodec;
17
18/// Codec for authenticate data lines.
19#[derive(Clone, Debug, Default, PartialEq)]
20#[non_exhaustive]
21pub struct AuthenticateDataCodec;
22
23/// Codec for responses.
24#[derive(Clone, Debug, Default, PartialEq)]
25#[non_exhaustive]
26pub struct ResponseCodec;
27
28/// Codec for idle dones.
29#[derive(Clone, Debug, Default, PartialEq)]
30#[non_exhaustive]
31pub struct IdleDoneCodec;
32
33macro_rules! impl_codec_new {
34    ($codec:ty) => {
35        impl $codec {
36            /// Create codec with default configuration.
37            pub fn new() -> Self {
38                Self::default()
39            }
40        }
41    };
42}
43
44impl_codec_new!(GreetingCodec);
45impl_codec_new!(CommandCodec);
46impl_codec_new!(AuthenticateDataCodec);
47impl_codec_new!(ResponseCodec);
48impl_codec_new!(IdleDoneCodec);
49
50#[cfg(test)]
51mod tests {
52    use std::num::NonZeroU32;
53
54    use imap_types::{
55        auth::AuthenticateData,
56        command::{Command, CommandBody},
57        core::{IString, Literal, LiteralMode, NString, Tag, Vec1},
58        extensions::idle::IdleDone,
59        fetch::MessageDataItem,
60        mailbox::Mailbox,
61        response::{Data, Greeting, GreetingKind, Response},
62    };
63
64    use super::*;
65    use crate::{
66        decode::{CommandDecodeError, Decoder, GreetingDecodeError, ResponseDecodeError},
67        testing::{
68            kat_inverse_authenticate_data, kat_inverse_command, kat_inverse_done,
69            kat_inverse_greeting, kat_inverse_response,
70        },
71    };
72
73    #[test]
74    fn test_kat_inverse_greeting() {
75        kat_inverse_greeting(&[
76            (
77                b"* OK ...\r\n".as_ref(),
78                b"".as_ref(),
79                Greeting::new(GreetingKind::Ok, None, "...").unwrap(),
80            ),
81            (
82                b"* ByE .\r\n???",
83                b"???",
84                Greeting::new(GreetingKind::Bye, None, ".").unwrap(),
85            ),
86            (
87                b"* preaUth x\r\n?",
88                b"?",
89                Greeting::new(GreetingKind::PreAuth, None, "x").unwrap(),
90            ),
91        ]);
92    }
93
94    #[test]
95    fn test_kat_inverse_command() {
96        kat_inverse_command(&[
97            (
98                b"a nOOP\r\n".as_ref(),
99                b"".as_ref(),
100                Command::new("a", CommandBody::Noop).unwrap(),
101            ),
102            (
103                b"a NooP\r\n???",
104                b"???",
105                Command::new("a", CommandBody::Noop).unwrap(),
106            ),
107            (
108                b"a SeLECT {5}\r\ninbox\r\n",
109                b"",
110                Command::new(
111                    "a",
112                    CommandBody::Select {
113                        mailbox: Mailbox::Inbox,
114                        #[cfg(feature = "ext_condstore_qresync")]
115                        parameters: Vec::default(),
116                    },
117                )
118                .unwrap(),
119            ),
120            (
121                b"a SElECT {5}\r\ninbox\r\nxxx",
122                b"xxx",
123                Command::new(
124                    "a",
125                    CommandBody::Select {
126                        mailbox: Mailbox::Inbox,
127                        #[cfg(feature = "ext_condstore_qresync")]
128                        parameters: Vec::default(),
129                    },
130                )
131                .unwrap(),
132            ),
133        ]);
134    }
135
136    #[test]
137    fn test_kat_inverse_response() {
138        kat_inverse_response(&[
139            (
140                b"* SEARCH 1\r\n".as_ref(),
141                b"".as_ref(),
142                Response::Data(Data::Search(
143                    vec![NonZeroU32::new(1).unwrap()],
144                    #[cfg(feature = "ext_condstore_qresync")]
145                    None,
146                )),
147            ),
148            (
149                b"* SEARCH 1\r\n???",
150                b"???",
151                Response::Data(Data::Search(
152                    vec![NonZeroU32::new(1).unwrap()],
153                    #[cfg(feature = "ext_condstore_qresync")]
154                    None,
155                )),
156            ),
157            (
158                b"* 1 FETCH (RFC822 {5}\r\nhello)\r\n",
159                b"",
160                Response::Data(Data::Fetch {
161                    seq: NonZeroU32::new(1).unwrap(),
162                    items: Vec1::from(MessageDataItem::Rfc822(NString(Some(IString::Literal(
163                        Literal::try_from(b"hello".as_ref()).unwrap(),
164                    ))))),
165                }),
166            ),
167        ]);
168    }
169
170    #[test]
171    fn test_kat_inverse_authenticate_data() {
172        kat_inverse_authenticate_data(&[
173            (
174                b"VGVzdA==\r\n".as_ref(),
175                b"".as_ref(),
176                AuthenticateData::r#continue(b"Test".to_vec()),
177            ),
178            (
179                b"AA==\r\n".as_ref(),
180                b"".as_ref(),
181                AuthenticateData::r#continue(b"\x00".to_vec()),
182            ),
183            (
184                b"aQ==\r\n".as_ref(),
185                b"".as_ref(),
186                AuthenticateData::r#continue(b"\x69".to_vec()),
187            ),
188            (b"*\r\n".as_ref(), b"".as_ref(), AuthenticateData::Cancel),
189        ]);
190    }
191
192    #[test]
193    fn test_kat_inverse_done() {
194        kat_inverse_done(&[
195            (b"done\r\n".as_ref(), b"".as_ref(), IdleDone),
196            (b"DONE\r\n".as_ref(), b"".as_ref(), IdleDone),
197        ]);
198    }
199
200    #[test]
201    fn test_greeting_incomplete_failed() {
202        let tests = [
203            // Incomplete
204            (b"*".as_ref(), Err(GreetingDecodeError::Incomplete)),
205            (b"* ".as_ref(), Err(GreetingDecodeError::Incomplete)),
206            (b"* O".as_ref(), Err(GreetingDecodeError::Incomplete)),
207            (b"* OK".as_ref(), Err(GreetingDecodeError::Incomplete)),
208            (b"* OK ".as_ref(), Err(GreetingDecodeError::Incomplete)),
209            (b"* OK .".as_ref(), Err(GreetingDecodeError::Incomplete)),
210            (b"* OK .\r".as_ref(), Err(GreetingDecodeError::Incomplete)),
211            // Failed
212            (b"**".as_ref(), Err(GreetingDecodeError::Failed)),
213            (b"* NO x\r\n".as_ref(), Err(GreetingDecodeError::Failed)),
214        ];
215
216        for (test, expected) in tests {
217            let got = GreetingCodec::default().decode(test);
218            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
219            assert_eq!(expected, got);
220
221            {
222                let got = GreetingCodec::default().decode_static(test);
223                assert_eq!(expected, got);
224            }
225        }
226    }
227
228    #[test]
229    fn test_command_incomplete_failed() {
230        let tests = [
231            // Incomplete
232            (b"a".as_ref(), Err(CommandDecodeError::Incomplete)),
233            (b"a ".as_ref(), Err(CommandDecodeError::Incomplete)),
234            (b"a n".as_ref(), Err(CommandDecodeError::Incomplete)),
235            (b"a no".as_ref(), Err(CommandDecodeError::Incomplete)),
236            (b"a noo".as_ref(), Err(CommandDecodeError::Incomplete)),
237            (b"a noop".as_ref(), Err(CommandDecodeError::Incomplete)),
238            (b"a noop\r".as_ref(), Err(CommandDecodeError::Incomplete)),
239            // LiteralAckRequired
240            (
241                b"a select {5}\r\n".as_ref(),
242                Err(CommandDecodeError::LiteralFound {
243                    tag: Tag::try_from("a").unwrap(),
244                    length: 5,
245                    mode: LiteralMode::Sync,
246                }),
247            ),
248            (
249                b"a select {5+}\r\n".as_ref(),
250                Err(CommandDecodeError::LiteralFound {
251                    tag: Tag::try_from("a").unwrap(),
252                    length: 5,
253                    mode: LiteralMode::NonSync,
254                }),
255            ),
256            // Incomplete (after literal)
257            (
258                b"a select {5}\r\nxxx".as_ref(),
259                Err(CommandDecodeError::Incomplete),
260            ),
261            // Failed
262            (b"* noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
263            (b"A  noop\r\n".as_ref(), Err(CommandDecodeError::Failed)),
264        ];
265
266        for (test, expected) in tests {
267            let got = CommandCodec::default().decode(test);
268            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
269            assert_eq!(expected, got);
270
271            {
272                let got = CommandCodec::default().decode_static(test);
273                assert_eq!(expected, got);
274            }
275        }
276    }
277
278    #[test]
279    fn test_response_incomplete_failed() {
280        let tests = [
281            // Incomplete
282            (b"".as_ref(), Err(ResponseDecodeError::Incomplete)),
283            (b"*".as_ref(), Err(ResponseDecodeError::Incomplete)),
284            (b"* ".as_ref(), Err(ResponseDecodeError::Incomplete)),
285            (b"* S".as_ref(), Err(ResponseDecodeError::Incomplete)),
286            (b"* SE".as_ref(), Err(ResponseDecodeError::Incomplete)),
287            (b"* SEA".as_ref(), Err(ResponseDecodeError::Incomplete)),
288            (b"* SEAR".as_ref(), Err(ResponseDecodeError::Incomplete)),
289            (b"* SEARC".as_ref(), Err(ResponseDecodeError::Incomplete)),
290            (b"* SEARCH".as_ref(), Err(ResponseDecodeError::Incomplete)),
291            (b"* SEARCH ".as_ref(), Err(ResponseDecodeError::Incomplete)),
292            (b"* SEARCH 1".as_ref(), Err(ResponseDecodeError::Incomplete)),
293            (
294                b"* SEARCH 1\r".as_ref(),
295                Err(ResponseDecodeError::Incomplete),
296            ),
297            // LiteralAck treated as Incomplete
298            (
299                b"* 1 FETCH (RFC822 {5}\r\n".as_ref(),
300                Err(ResponseDecodeError::LiteralFound { length: 5 }),
301            ),
302            // Failed
303            (
304                b"*  search 1 2 3\r\n".as_ref(),
305                Err(ResponseDecodeError::Failed),
306            ),
307            (b"A search\r\n".as_ref(), Err(ResponseDecodeError::Failed)),
308        ];
309
310        for (test, expected) in tests {
311            let got = ResponseCodec::default().decode(test);
312            dbg!((std::str::from_utf8(test).unwrap(), &expected, &got));
313            assert_eq!(expected, got);
314
315            {
316                let got = ResponseCodec::default().decode_static(test);
317                assert_eq!(expected, got);
318            }
319        }
320    }
321}