Skip to main content

imap_codec/
fragmentizer.rs

1//! Utilities to split IMAP bytes into line and literal fragments.
2//!
3//! These utilities can be used to fragment a stream of IMAP bytes into lines (with metadata) and
4//! literals (before actually doing detailed IMAP parsing).
5//!
6//! This approach has multiple advantages: It separates literal handling from IMAP parsing and sets
7//! clear message boundaries even in the presence of malformed messages. Consequently, malformed
8//! messages can be reliably discarded. (A naive implementation of byte discardment may lead to
9//! adventurous (security) issues, such as, literal data being interpreted as command or response.)
10//! Further, this two-layered approach allows to more easily guard against excessive memory
11//! allocation by malevolant actors.
12//!
13//! # Example
14//!
15//! ```rust,ignore
16//! # use std::io::{stdin, Read};
17//! #
18//! # use imap_codec::{
19//! #     fragmentizer::Fragmentizer,
20//! #     imap_types::utils::escape_byte_string,
21//! # };
22//! #
23//! # fn read_bytes() -> &'static [u8] { b"" }
24//! #
25//! # fn main() {
26//! let mut fragmentizer = Fragmentizer::new(1024);
27//!
28//! loop {
29//!     match fragmentizer.progress() {
30//!         Some(fragment_info) => {
31//!             let fragment_bytes = fragmentizer.fragment_bytes(fragment_info);
32//!
33//!             if fragmentizer.is_message_complete() {
34//!                 let message_bytes = fragmentizer.message_bytes();
35//!             }
36//!         }
37//!         None => {
38//!             let received = read_bytes();
39//!             fragmentizer.enqueue_bytes(received);
40//!         }
41//!     }
42//! }
43//! # }
44//! ```
45use std::{collections::VecDeque, ops::Range};
46
47use imap_types::{
48    core::{LiteralMode, Tag},
49    secret::Secret,
50};
51
52use crate::decode::Decoder;
53
54/// Splits IMAP bytes into line and literal fragments.
55///
56/// The `Fragmentizer` prevents excessive memory allocation through a configurable maximum message size.
57/// Correct fragmentation is ensured even for messages exceeding the allowed message size.
58///
59/// If the message size is exceeded,
60/// [`Fragmentizer::decode_message`] will fail and
61/// [`Fragmentizer::message_bytes`] will emit truncated message bytes.
62/// However, fragmentation will seamlessly continue with the following message.
63#[derive(Clone, Debug)]
64pub struct Fragmentizer {
65    /// Enqueued bytes that are not parsed by [`Fragmentizer::progress`] yet.
66    unparsed_buffer: VecDeque<u8>,
67    /// Upper limit for the size of parsed messages.
68    max_message_size: Option<u32>,
69    /// Whether the size limit is exceeded for the current message.
70    max_message_size_exceeded: bool,
71    /// The current message was poisoned. The message will still be parsed, but the decoding
72    /// will fail.
73    message_poisoned: bool,
74    /// Parsed bytes of the current messages. The length is limited by
75    /// [`Fragmentizer::max_message_size`].
76    message_buffer: Vec<u8>,
77    /// Parser for the next fragment of the current message. Is `None` if no fragment is expected
78    /// because the message is complete.
79    parser: Option<Parser>,
80}
81
82impl Fragmentizer {
83    /// Creates a `Fragmentizer` with a maximum message size.
84    ///
85    /// The maximum message size is bounded by `max_message_size` to prevent excessive memory allocation.
86    pub fn new(max_message_size: u32) -> Self {
87        Self {
88            unparsed_buffer: VecDeque::new(),
89            max_message_size: Some(max_message_size),
90            max_message_size_exceeded: false,
91            message_poisoned: false,
92            message_buffer: Vec::new(),
93            parser: Some(Parser::Line(LineParser::new(0))),
94        }
95    }
96
97    /// Creates a `Fragmentizer` without a maximum message size.
98    ///
99    /// <div class="warning">
100    /// This is dangerous because it allows an attacker to allocate an excessive amount of memory
101    /// by sending a huge message.
102    /// </div>
103    pub fn without_max_message_size() -> Self {
104        Self {
105            unparsed_buffer: VecDeque::new(),
106            max_message_size: None,
107            max_message_size_exceeded: false,
108            message_poisoned: false,
109            message_buffer: Vec::new(),
110            parser: Some(Parser::Line(LineParser::new(0))),
111        }
112    }
113
114    /// Continue parsing the current message until the next fragment is detected.
115    ///
116    /// Returns `None` if more bytes need to be enqueued via [`Fragmentizer::enqueue_bytes`].
117    /// If [`Fragmentizer::is_message_complete`] returns true after this function was called,
118    /// then the message was fully parsed. The following call of this function will then start
119    /// the next message.
120    pub fn progress(&mut self) -> Option<FragmentInfo> {
121        let parser = match &mut self.parser {
122            Some(parser) => {
123                // Continue current message
124                parser
125            }
126            None => {
127                // Start next message
128                self.max_message_size_exceeded = false;
129                self.message_poisoned = false;
130                self.message_buffer.clear();
131                self.parser.insert(Parser::Line(LineParser::new(0)))
132            }
133        };
134
135        // Progress fragment
136        let (parsed_byte_count, fragment) = match parser {
137            Parser::Line(parser) => parser.parse(&self.unparsed_buffer),
138            Parser::Literal(parser) => parser.parse(&self.unparsed_buffer),
139        };
140        self.dequeue_parsed_bytes(parsed_byte_count);
141
142        if let Some(fragment) = fragment {
143            self.parser = match fragment {
144                // Finish current message
145                FragmentInfo::Line {
146                    announcement: None, ..
147                } => None,
148                // Next fragment will be a literal
149                FragmentInfo::Line {
150                    end,
151                    announcement: Some(LiteralAnnouncement { length, .. }),
152                    ..
153                } => Some(Parser::Literal(LiteralParser::new(end, length))),
154                // Next fragment will be a line
155                FragmentInfo::Literal { end, .. } => Some(Parser::Line(LineParser::new(end))),
156            }
157        }
158
159        fragment
160    }
161
162    /// Enqueues more byte that can be parsed by [`Fragmentizer::progress`].
163    ///
164    /// Note that the message size limit is not enforced on the enqueued bytes. You can control
165    /// the size of the enqueued bytes by only calling this function if more bytes are necessary.
166    /// More bytes are necessary if [`Fragmentizer::progress`] returns `None`.
167    pub fn enqueue_bytes(&mut self, bytes: &[u8]) {
168        self.unparsed_buffer.extend(bytes);
169    }
170
171    /// Returns the bytes for a fragment of the current message.
172    pub fn fragment_bytes(&self, fragment_info: FragmentInfo) -> &[u8] {
173        let (start, end) = match fragment_info {
174            FragmentInfo::Line { start, end, .. } => (start, end),
175            FragmentInfo::Literal { start, end } => (start, end),
176        };
177        let start = start.min(self.message_buffer.len());
178        let end = end.min(self.message_buffer.len());
179        &self.message_buffer[start..end]
180    }
181
182    /// Returns whether the current message was fully parsed.
183    ///
184    /// If it returns true then it makes sense to call [`Fragmentizer::decode_message`]
185    /// to decode the message. Alternatively, you can access all bytes of the message via
186    /// [`Fragmentizer::message_bytes`].
187    pub fn is_message_complete(&self) -> bool {
188        self.parser.is_none()
189    }
190
191    /// Returns the bytes of the current message.
192    ///
193    /// Note that the bytes might be incomplete:
194    /// - The message might not be fully parsed yet and [`Fragmentizer::progress`] need to be
195    ///   called. You can check whether the message is complete via
196    ///   [`Fragmentizer::is_message_complete`].
197    /// - The size limit might be exceeded and bytes might be dropped. You can check this
198    ///   via [`Fragmentizer::is_max_message_size_exceeded`]
199    pub fn message_bytes(&self) -> &[u8] {
200        &self.message_buffer
201    }
202
203    /// Returns whether the size limit is exceeded for the current message.
204    pub fn is_max_message_size_exceeded(&self) -> bool {
205        self.max_message_size_exceeded
206    }
207
208    /// Returns whether the current message was explicitly poisoned to prevent decoding.
209    pub fn is_message_poisoned(&self) -> bool {
210        self.message_poisoned
211    }
212
213    /// Skips the current message and starts the next message immediately.
214    ///
215    /// Warning: Using this method might be dangerous. If client and server don't
216    /// agree at which point a message is skipped, then the client or server might
217    /// treat untrusted bytes (e.g. literal bytes) as IMAP messages. Currently the
218    /// only valid use-case is a server that rejects synchronizing literals from the
219    /// client. Otherwise consider using [`Fragmentizer::poison_message`].
220    pub fn skip_message(&mut self) {
221        self.max_message_size_exceeded = false;
222        self.message_poisoned = false;
223        self.message_buffer.clear();
224        self.parser = Some(Parser::Line(LineParser::new(0)));
225    }
226
227    /// Poisons the current message to prevent its decoding.
228    ///
229    /// When this function is called the fragments of the current message are parsed normally, but
230    /// [`Fragmentizer::decode_message`] is guaranteed to fail and return
231    /// [`DecodeMessageError::MessagePoisoned`]. This allows to skip malformed messages (e.g.
232    /// a message with an unexpected line ending) safely without the risk of treating untrusted
233    /// bytes (e.g. literal bytes) as IMAP messages
234    pub fn poison_message(&mut self) {
235        self.message_poisoned = true;
236    }
237
238    /// Tries to decode the [`Tag`] for the current message.
239    ///
240    /// Note that decoding the [`Tag`] is on best effort basis. Some message types don't have
241    /// a [`Tag`] and without context you can't know whether this function will succeed. However,
242    /// this function is useful if the message is incomplete or malformed and you want to decode
243    /// the [`Tag`] in order to send a response.
244    pub fn decode_tag(&self) -> Option<Tag> {
245        parse_tag(&self.message_buffer)
246    }
247
248    /// Tries to decode the current message with the given decoder.
249    ///
250    /// You usually want to call this method once [`Fragmentizer::is_message_complete`] returns
251    /// true. Which decoder should be used depends on the state of the IMAP conversation. The
252    /// caller is responsible for tracking this state and choosing the decoder.
253    pub fn decode_message<'a, C: Decoder>(
254        &'a self,
255        codec: &C,
256    ) -> Result<C::Message<'a>, DecodeMessageError<'a, C>> {
257        if self.max_message_size_exceeded {
258            return Err(DecodeMessageError::MessageTooLong {
259                initial: Secret::new(&self.message_buffer),
260            });
261        }
262
263        if self.message_poisoned {
264            return Err(DecodeMessageError::MessagePoisoned {
265                discarded: Secret::new(&self.message_buffer),
266            });
267        }
268
269        let (remainder, message) = match codec.decode(&self.message_buffer) {
270            Ok(res) => res,
271            Err(err) => return Err(DecodeMessageError::DecodingFailure(err)),
272        };
273
274        if !remainder.is_empty() {
275            return Err(DecodeMessageError::DecodingRemainder {
276                message,
277                remainder: Secret::new(remainder),
278            });
279        }
280
281        Ok(message)
282    }
283
284    fn dequeue_parsed_bytes(&mut self, parsed_byte_count: usize) {
285        // This will remove the parsed bytes even if we don't add them to the message buffer
286        let parsed_bytes = self.unparsed_buffer.drain(..parsed_byte_count);
287        // How many bytes can we add to the message buffer?
288        let remaining_size = self
289            .max_message_size
290            .map(|size| size as usize - self.message_buffer.len());
291
292        // Add bytes to the message buffer
293        match remaining_size {
294            Some(remaining_size) if remaining_size < parsed_byte_count => {
295                let remaining_bytes = parsed_bytes.take(remaining_size);
296                self.message_buffer.extend(remaining_bytes);
297                self.max_message_size_exceeded = true;
298            }
299            _ => {
300                self.message_buffer.extend(parsed_bytes);
301            }
302        }
303    }
304}
305
306/// Stateful parser for the next fragment.
307#[derive(Clone, Debug)]
308enum Parser {
309    Line(LineParser),
310    Literal(LiteralParser),
311}
312
313/// Stateful parser for the next line fragment.
314#[derive(Clone, Debug)]
315struct LineParser {
316    /// Where we started parsing the line.
317    start: usize,
318    /// Until where we parsed the line.
319    end: usize,
320    /// Accumulated state based on the parsed bytes.
321    latest_byte: LatestByte,
322}
323
324impl LineParser {
325    fn new(start: usize) -> Self {
326        Self {
327            start,
328            end: start,
329            latest_byte: LatestByte::Other,
330        }
331    }
332
333    fn parse(&mut self, unprocessed_bytes: &VecDeque<u8>) -> (usize, Option<FragmentInfo>) {
334        let mut parsed_byte_count = 0;
335        let mut parsed_line = None;
336
337        // Parse next byte
338        for &next_byte in unprocessed_bytes {
339            parsed_byte_count += 1;
340            self.end += 1;
341
342            self.latest_byte = match self.latest_byte {
343                LatestByte::Other => match next_byte {
344                    b'\r' => LatestByte::Cr { announcement: None },
345                    b'\n' => {
346                        parsed_line = Some(FragmentInfo::Line {
347                            start: self.start,
348                            end: self.end,
349                            announcement: None,
350                            ending: LineEnding::Lf,
351                        });
352                        LatestByte::Other
353                    }
354                    b'{' => LatestByte::OpeningBracket,
355                    _ => LatestByte::Other,
356                },
357                LatestByte::OpeningBracket => match next_byte {
358                    b'\r' => LatestByte::Cr { announcement: None },
359                    b'\n' => {
360                        parsed_line = Some(FragmentInfo::Line {
361                            start: self.start,
362                            end: self.end,
363                            announcement: None,
364                            ending: LineEnding::Lf,
365                        });
366                        LatestByte::Other
367                    }
368                    b'{' => LatestByte::OpeningBracket,
369                    b'0'..=b'9' => {
370                        let digit = (next_byte - b'0') as u32;
371                        LatestByte::Digit { length: digit }
372                    }
373                    _ => LatestByte::Other,
374                },
375                LatestByte::Plus { length } => match next_byte {
376                    b'\r' => LatestByte::Cr { announcement: None },
377                    b'\n' => {
378                        parsed_line = Some(FragmentInfo::Line {
379                            start: self.start,
380                            end: self.end,
381                            announcement: None,
382                            ending: LineEnding::Lf,
383                        });
384                        LatestByte::Other
385                    }
386                    b'{' => LatestByte::OpeningBracket,
387                    b'}' => LatestByte::ClosingBracket {
388                        announcement: LiteralAnnouncement {
389                            mode: LiteralMode::NonSync,
390                            length,
391                        },
392                    },
393                    _ => LatestByte::Other,
394                },
395                LatestByte::Digit { length } => match next_byte {
396                    b'\r' => LatestByte::Cr { announcement: None },
397                    b'\n' => {
398                        parsed_line = Some(FragmentInfo::Line {
399                            start: self.start,
400                            end: self.end,
401                            announcement: None,
402                            ending: LineEnding::Lf,
403                        });
404                        LatestByte::Other
405                    }
406                    b'{' => LatestByte::OpeningBracket,
407                    b'0'..=b'9' => {
408                        let digit = (next_byte - b'0') as u32;
409                        let new_length = length.checked_mul(10).and_then(|x| x.checked_add(digit));
410                        match new_length {
411                            None => LatestByte::Other,
412                            Some(length) => LatestByte::Digit { length },
413                        }
414                    }
415                    b'+' => LatestByte::Plus { length },
416                    b'}' => LatestByte::ClosingBracket {
417                        announcement: LiteralAnnouncement {
418                            mode: LiteralMode::Sync,
419                            length,
420                        },
421                    },
422                    _ => LatestByte::Other,
423                },
424                LatestByte::ClosingBracket { announcement } => match next_byte {
425                    b'\r' => LatestByte::Cr {
426                        announcement: Some(announcement),
427                    },
428                    b'\n' => {
429                        parsed_line = Some(FragmentInfo::Line {
430                            start: self.start,
431                            end: self.end,
432                            announcement: Some(announcement),
433                            ending: LineEnding::Lf,
434                        });
435                        LatestByte::Other
436                    }
437                    b'{' => LatestByte::OpeningBracket,
438                    _ => LatestByte::Other,
439                },
440                LatestByte::Cr { announcement } => match next_byte {
441                    b'\r' => LatestByte::Cr { announcement: None },
442                    b'\n' => {
443                        parsed_line = Some(FragmentInfo::Line {
444                            start: self.start,
445                            end: self.end,
446                            announcement,
447                            ending: LineEnding::CrLf,
448                        });
449                        LatestByte::Other
450                    }
451                    b'{' => LatestByte::OpeningBracket,
452                    _ => LatestByte::Other,
453                },
454            };
455
456            if parsed_line.is_some() {
457                // We parsed a complete line
458                break;
459            }
460        }
461
462        (parsed_byte_count, parsed_line)
463    }
464}
465
466/// The latest byte seen by the [`LineParser`] with additional accumulated state.
467#[derive(Clone, Debug)]
468enum LatestByte {
469    Other,
470    OpeningBracket,
471    Digit {
472        length: u32,
473    },
474    Plus {
475        length: u32,
476    },
477    ClosingBracket {
478        announcement: LiteralAnnouncement,
479    },
480    Cr {
481        announcement: Option<LiteralAnnouncement>,
482    },
483}
484
485/// Stateful parser for the next literal fragment.
486#[derive(Clone, Debug)]
487struct LiteralParser {
488    /// Where we started parsing the literal.
489    start: usize,
490    /// Until where we parsed the literal.
491    end: usize,
492    /// Remaining bytes we need to parse.
493    remaining: u32,
494}
495
496impl LiteralParser {
497    fn new(start: usize, length: u32) -> Self {
498        Self {
499            start,
500            end: start,
501            remaining: length,
502        }
503    }
504
505    fn parse(&mut self, unprocessed_bytes: &VecDeque<u8>) -> (usize, Option<FragmentInfo>) {
506        if unprocessed_bytes.len() < self.remaining as usize {
507            // Not enough bytes yet
508            let parsed_byte_count = unprocessed_bytes.len();
509            self.end += parsed_byte_count;
510            self.remaining -= parsed_byte_count as u32;
511            (parsed_byte_count, None)
512        } else {
513            // There are enough bytes
514            let parsed_byte_count = self.remaining as usize;
515            self.end += parsed_byte_count;
516            self.remaining = 0;
517            let parsed_literal = FragmentInfo::Literal {
518                start: self.start,
519                end: self.end,
520            };
521            (parsed_byte_count, Some(parsed_literal))
522        }
523    }
524}
525
526/// Describes a fragment of the current message found by [`Fragmentizer::progress`].
527///
528/// The corresponding bytes can be retrieved via [`Fragmentizer::fragment_bytes`]
529/// until [`Fragmentizer::is_message_complete`] returns true. After that the
530/// next call of [`Fragmentizer::progress`] will start the next message.
531#[derive(Clone, Copy, Debug, Eq, PartialEq)]
532pub enum FragmentInfo {
533    /// The fragment is a line.
534    Line {
535        /// Inclusive start index relative to the current message.
536        start: usize,
537        /// Exclusive end index relative to the current message.
538        end: usize,
539        /// Whether the next fragment will be a literal.
540        announcement: Option<LiteralAnnouncement>,
541        /// The detected ending sequence for this line.
542        ending: LineEnding,
543    },
544    /// The fragment is a literal.
545    Literal {
546        /// Inclusive start index relative to the current message.
547        start: usize,
548        /// Exclusive end index relative to the current message.
549        end: usize,
550    },
551}
552
553impl FragmentInfo {
554    /// The index range relative to the current message.
555    pub fn range(self) -> Range<usize> {
556        match self {
557            FragmentInfo::Line { start, end, .. } => start..end,
558            FragmentInfo::Literal { start, end } => start..end,
559        }
560    }
561}
562
563/// Used by a line to announce a literal following the line.
564#[derive(Clone, Copy, Debug, Eq, PartialEq)]
565pub struct LiteralAnnouncement {
566    /// The mode of the announced literal.
567    pub mode: LiteralMode,
568    /// The length of the announced literal in bytes.
569    pub length: u32,
570}
571
572/// The character sequence used for ending a line.
573#[derive(Clone, Copy, Debug, Eq, PartialEq)]
574pub enum LineEnding {
575    /// The line ends with the character `\n`.
576    Lf,
577    /// The line ends with the character sequence `\r\n`.
578    CrLf,
579}
580
581/// An error returned by [`Fragmentizer::decode_message`].
582#[derive(Clone, Debug, Eq, PartialEq)]
583pub enum DecodeMessageError<'a, C: Decoder> {
584    /// The decoder failed decoding the message.
585    DecodingFailure(C::Error<'a>),
586    /// Not all bytes of the message were used when decoding the message.
587    DecodingRemainder {
588        /// The decoded message.
589        message: C::Message<'a>,
590        /// The unused bytes.
591        remainder: Secret<&'a [u8]>,
592    },
593    /// Max message size was exceeded and bytes were dropped.
594    MessageTooLong { initial: Secret<&'a [u8]> },
595    /// The message was explicitly poisoned to prevent decoding.
596    MessagePoisoned { discarded: Secret<&'a [u8]> },
597}
598
599fn parse_tag(message_bytes: &[u8]) -> Option<Tag> {
600    let mut bytes = message_bytes.iter().enumerate();
601    let sp = loop {
602        let (i, byte) = bytes.next()?;
603        match byte {
604            // A tag is always delimited by SP
605            b' ' => break i,
606            // End of line reached
607            b'\n' => return None,
608            // Parse more bytes
609            _ => continue,
610        }
611    };
612
613    Tag::try_from(&message_bytes[..sp]).ok()
614}
615
616#[cfg(test)]
617mod tests {
618    use core::panic;
619    use std::collections::VecDeque;
620
621    use imap_types::{
622        command::{Command, CommandBody},
623        core::{LiteralMode, Tag},
624        secret::Secret,
625    };
626
627    use super::{
628        FragmentInfo, Fragmentizer, LineEnding, LineParser, LiteralAnnouncement, parse_tag,
629    };
630    use crate::{
631        CommandCodec, ResponseCodec, decode::ResponseDecodeError, fragmentizer::DecodeMessageError,
632    };
633
634    #[test]
635    fn fragmentizer_progress_nothing() {
636        let mut fragmentizer = Fragmentizer::without_max_message_size();
637
638        let fragment_info = fragmentizer.progress();
639
640        assert_eq!(fragment_info, None);
641        assert_eq!(fragmentizer.message_bytes(), b"");
642        assert!(!fragmentizer.is_message_complete());
643
644        fragmentizer.enqueue_bytes(&[]);
645        let fragment_info = fragmentizer.progress();
646
647        assert_eq!(fragment_info, None);
648        assert_eq!(fragmentizer.message_bytes(), b"");
649        assert!(!fragmentizer.is_message_complete());
650    }
651
652    #[test]
653    fn fragmentizer_progress_single_message() {
654        let mut fragmentizer = Fragmentizer::without_max_message_size();
655        fragmentizer.enqueue_bytes(b"* OK ...\r\n");
656
657        let fragment_info = fragmentizer.progress().unwrap();
658
659        assert_eq!(
660            fragment_info,
661            FragmentInfo::Line {
662                start: 0,
663                end: 10,
664                announcement: None,
665                ending: LineEnding::CrLf,
666            }
667        );
668        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"* OK ...\r\n");
669        assert!(fragmentizer.is_message_complete());
670
671        let fragment_info = fragmentizer.progress();
672
673        assert_eq!(fragment_info, None);
674        assert_eq!(fragmentizer.message_bytes(), b"");
675        assert!(!fragmentizer.is_message_complete());
676    }
677
678    #[test]
679    fn fragmentizer_progress_multiple_messages() {
680        let mut fragmentizer = Fragmentizer::without_max_message_size();
681        fragmentizer.enqueue_bytes(b"A1 OK ...\r\n");
682        fragmentizer.enqueue_bytes(b"A2 BAD ...\r\n");
683
684        let fragment_info = fragmentizer.progress().unwrap();
685
686        assert_eq!(
687            fragment_info,
688            FragmentInfo::Line {
689                start: 0,
690                end: 11,
691                announcement: None,
692                ending: LineEnding::CrLf,
693            }
694        );
695        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A1 OK ...\r\n");
696        assert!(fragmentizer.is_message_complete());
697
698        let fragment_info = fragmentizer.progress().unwrap();
699
700        assert_eq!(
701            fragment_info,
702            FragmentInfo::Line {
703                start: 0,
704                end: 12,
705                announcement: None,
706                ending: LineEnding::CrLf,
707            }
708        );
709        assert_eq!(
710            fragmentizer.fragment_bytes(fragment_info),
711            b"A2 BAD ...\r\n"
712        );
713        assert!(fragmentizer.is_message_complete());
714
715        let fragment_info = fragmentizer.progress();
716
717        assert_eq!(fragment_info, None);
718        assert_eq!(fragmentizer.message_bytes(), b"");
719        assert!(!fragmentizer.is_message_complete());
720    }
721
722    #[test]
723    fn fragmentizer_progress_multiple_messages_with_lf() {
724        let mut fragmentizer = Fragmentizer::without_max_message_size();
725        fragmentizer.enqueue_bytes(b"A1 NOOP\n");
726        fragmentizer.enqueue_bytes(b"A2 LOGIN {5}\n");
727        fragmentizer.enqueue_bytes(b"ABCDE");
728        fragmentizer.enqueue_bytes(b" EFGIJ\n");
729
730        let fragment_info = fragmentizer.progress().unwrap();
731
732        assert_eq!(
733            fragment_info,
734            FragmentInfo::Line {
735                start: 0,
736                end: 8,
737                announcement: None,
738                ending: LineEnding::Lf,
739            }
740        );
741        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A1 NOOP\n");
742        assert!(fragmentizer.is_message_complete());
743
744        let fragment_info = fragmentizer.progress().unwrap();
745
746        assert_eq!(
747            fragment_info,
748            FragmentInfo::Line {
749                start: 0,
750                end: 13,
751                announcement: Some(LiteralAnnouncement {
752                    mode: LiteralMode::Sync,
753                    length: 5
754                }),
755                ending: LineEnding::Lf,
756            }
757        );
758        assert_eq!(
759            fragmentizer.fragment_bytes(fragment_info),
760            b"A2 LOGIN {5}\n"
761        );
762        assert!(!fragmentizer.is_message_complete());
763
764        let fragment_info = fragmentizer.progress().unwrap();
765
766        assert_eq!(fragment_info, FragmentInfo::Literal { start: 13, end: 18 });
767        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"ABCDE");
768        assert!(!fragmentizer.is_message_complete());
769
770        let fragment_info = fragmentizer.progress().unwrap();
771
772        assert_eq!(
773            fragment_info,
774            FragmentInfo::Line {
775                start: 18,
776                end: 25,
777                announcement: None,
778                ending: LineEnding::Lf,
779            }
780        );
781        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b" EFGIJ\n");
782        assert!(fragmentizer.is_message_complete());
783
784        let fragment_info = fragmentizer.progress();
785
786        assert_eq!(fragment_info, None);
787        assert_eq!(fragmentizer.message_bytes(), b"");
788        assert!(!fragmentizer.is_message_complete());
789    }
790
791    #[test]
792    fn fragmentizer_progress_message_with_multiple_literals() {
793        let mut fragmentizer = Fragmentizer::without_max_message_size();
794        fragmentizer.enqueue_bytes(b"A1 LOGIN {5}\r\n");
795        fragmentizer.enqueue_bytes(b"ABCDE");
796        fragmentizer.enqueue_bytes(b" {5}\r\n");
797        fragmentizer.enqueue_bytes(b"FGHIJ");
798        fragmentizer.enqueue_bytes(b"\r\n");
799
800        let fragment_info = fragmentizer.progress().unwrap();
801
802        assert_eq!(
803            fragment_info,
804            FragmentInfo::Line {
805                start: 0,
806                end: 14,
807                announcement: Some(LiteralAnnouncement {
808                    mode: LiteralMode::Sync,
809                    length: 5,
810                }),
811                ending: LineEnding::CrLf,
812            }
813        );
814        assert_eq!(
815            fragmentizer.fragment_bytes(fragment_info),
816            b"A1 LOGIN {5}\r\n"
817        );
818        assert!(!fragmentizer.is_message_complete());
819
820        let fragment_info = fragmentizer.progress().unwrap();
821
822        assert_eq!(fragment_info, FragmentInfo::Literal { start: 14, end: 19 });
823        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"ABCDE");
824        assert!(!fragmentizer.is_message_complete());
825
826        let fragment_info = fragmentizer.progress().unwrap();
827
828        assert_eq!(
829            fragment_info,
830            FragmentInfo::Line {
831                start: 19,
832                end: 25,
833                announcement: Some(LiteralAnnouncement {
834                    mode: LiteralMode::Sync,
835                    length: 5,
836                }),
837                ending: LineEnding::CrLf,
838            }
839        );
840        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b" {5}\r\n");
841        assert!(!fragmentizer.is_message_complete());
842
843        let fragment_info = fragmentizer.progress().unwrap();
844
845        assert_eq!(fragment_info, FragmentInfo::Literal { start: 25, end: 30 });
846        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"FGHIJ");
847        assert!(!fragmentizer.is_message_complete());
848
849        let fragment_info = fragmentizer.progress().unwrap();
850
851        assert_eq!(
852            fragment_info,
853            FragmentInfo::Line {
854                start: 30,
855                end: 32,
856                announcement: None,
857                ending: LineEnding::CrLf,
858            }
859        );
860        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"\r\n");
861        assert!(fragmentizer.is_message_complete());
862
863        let fragment_info = fragmentizer.progress();
864
865        assert_eq!(fragment_info, None);
866        assert_eq!(fragmentizer.message_bytes(), b"");
867        assert!(!fragmentizer.is_message_complete());
868    }
869
870    #[test]
871    fn fragmentizer_progress_message_and_skip_after_literal_announcement() {
872        let mut fragmentizer = Fragmentizer::without_max_message_size();
873        fragmentizer.enqueue_bytes(b"A1 LOGIN {5}\r\n");
874        fragmentizer.enqueue_bytes(b"A2 NOOP\r\n");
875
876        let fragment_info = fragmentizer.progress().unwrap();
877
878        assert_eq!(
879            fragment_info,
880            FragmentInfo::Line {
881                start: 0,
882                end: 14,
883                announcement: Some(LiteralAnnouncement {
884                    mode: LiteralMode::Sync,
885                    length: 5,
886                }),
887                ending: LineEnding::CrLf,
888            }
889        );
890        assert_eq!(
891            fragmentizer.fragment_bytes(fragment_info),
892            b"A1 LOGIN {5}\r\n"
893        );
894        assert!(!fragmentizer.is_message_complete());
895
896        fragmentizer.skip_message();
897
898        let fragment_info = fragmentizer.progress().unwrap();
899
900        assert_eq!(
901            fragment_info,
902            FragmentInfo::Line {
903                start: 0,
904                end: 9,
905                announcement: None,
906                ending: LineEnding::CrLf,
907            }
908        );
909        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A2 NOOP\r\n");
910        assert!(fragmentizer.is_message_complete());
911
912        let fragment_info = fragmentizer.progress();
913
914        assert_eq!(fragment_info, None);
915        assert_eq!(fragmentizer.message_bytes(), b"");
916        assert!(!fragmentizer.is_message_complete());
917    }
918
919    #[test]
920    fn fragmentizer_progress_message_byte_by_byte() {
921        let mut fragmentizer = Fragmentizer::without_max_message_size();
922        let mut bytes = VecDeque::new();
923        bytes.extend(b"A1 LOGIN {5}\r\n");
924        bytes.extend(b"ABCDE");
925        bytes.extend(b" FGHIJ\r\n");
926
927        for _ in 0..14 {
928            let fragment_info = fragmentizer.progress();
929
930            assert_eq!(fragment_info, None);
931            assert!(!fragmentizer.is_message_complete());
932
933            fragmentizer.enqueue_bytes(&[bytes.pop_front().unwrap()]);
934        }
935
936        let fragment_info = fragmentizer.progress().unwrap();
937
938        assert_eq!(
939            fragment_info,
940            FragmentInfo::Line {
941                start: 0,
942                end: 14,
943                announcement: Some(LiteralAnnouncement {
944                    mode: LiteralMode::Sync,
945                    length: 5,
946                }),
947                ending: LineEnding::CrLf,
948            }
949        );
950        assert_eq!(
951            fragmentizer.fragment_bytes(fragment_info),
952            b"A1 LOGIN {5}\r\n"
953        );
954        assert!(!fragmentizer.is_message_complete());
955
956        for _ in 0..5 {
957            let fragment_info = fragmentizer.progress();
958
959            assert_eq!(fragment_info, None);
960            assert!(!fragmentizer.is_message_complete());
961
962            fragmentizer.enqueue_bytes(&[bytes.pop_front().unwrap()]);
963        }
964
965        let fragment_info = fragmentizer.progress().unwrap();
966
967        assert_eq!(fragment_info, FragmentInfo::Literal { start: 14, end: 19 });
968        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"ABCDE");
969        assert!(!fragmentizer.is_message_complete());
970
971        for _ in 0..8 {
972            let fragment_info = fragmentizer.progress();
973
974            assert_eq!(fragment_info, None);
975            assert!(!fragmentizer.is_message_complete());
976
977            fragmentizer.enqueue_bytes(&[bytes.pop_front().unwrap()]);
978        }
979
980        let fragment_info = fragmentizer.progress().unwrap();
981
982        assert_eq!(
983            fragment_info,
984            FragmentInfo::Line {
985                start: 19,
986                end: 27,
987                announcement: None,
988                ending: LineEnding::CrLf,
989            }
990        );
991        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b" FGHIJ\r\n");
992        assert!(fragmentizer.is_message_complete());
993
994        let fragment_info = fragmentizer.progress();
995
996        assert_eq!(fragment_info, None);
997        assert_eq!(fragmentizer.message_bytes(), b"");
998        assert!(!fragmentizer.is_message_complete());
999    }
1000
1001    #[track_caller]
1002    fn assert_is_line(
1003        unprocessed_bytes: &[u8],
1004        line_byte_count: usize,
1005        expected_announcement: Option<LiteralAnnouncement>,
1006        expected_ending: LineEnding,
1007    ) {
1008        let mut line_parser = LineParser::new(0);
1009        let unprocessed_bytes = unprocessed_bytes.iter().copied().collect();
1010
1011        let (parsed_byte_count, fragment_info) = line_parser.parse(&unprocessed_bytes);
1012
1013        assert_eq!(parsed_byte_count, line_byte_count);
1014
1015        let Some(FragmentInfo::Line {
1016            start,
1017            end,
1018            announcement,
1019            ending,
1020        }) = fragment_info
1021        else {
1022            panic!("Unexpected fragment: {fragment_info:?}");
1023        };
1024
1025        assert_eq!(start, 0);
1026        assert_eq!(end, line_byte_count);
1027        assert_eq!(announcement, expected_announcement);
1028        assert_eq!(ending, expected_ending);
1029    }
1030
1031    #[test]
1032    fn fragmentizer_progress_multiple_messages_longer_than_max_size() {
1033        let mut fragmentizer = Fragmentizer::new(17);
1034        fragmentizer.enqueue_bytes(b"A1 NOOP\r\n");
1035        fragmentizer.enqueue_bytes(b"A2 LOGIN ABCDE EFGIJ\r\n");
1036        fragmentizer.enqueue_bytes(b"A3 LOGIN {5}\r\n");
1037        fragmentizer.enqueue_bytes(b"ABCDE");
1038        fragmentizer.enqueue_bytes(b" EFGIJ\r\n");
1039        fragmentizer.enqueue_bytes(b"A4 LOGIN A B\r\n");
1040
1041        let fragment_info = fragmentizer.progress().unwrap();
1042
1043        assert_eq!(
1044            fragment_info,
1045            FragmentInfo::Line {
1046                start: 0,
1047                end: 9,
1048                announcement: None,
1049                ending: LineEnding::CrLf,
1050            }
1051        );
1052        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A1 NOOP\r\n");
1053        assert_eq!(fragmentizer.message_bytes(), b"A1 NOOP\r\n");
1054        assert!(fragmentizer.is_message_complete());
1055        assert!(!fragmentizer.is_max_message_size_exceeded());
1056
1057        let fragment_info = fragmentizer.progress().unwrap();
1058
1059        assert_eq!(
1060            fragment_info,
1061            FragmentInfo::Line {
1062                start: 0,
1063                end: 22,
1064                announcement: None,
1065                ending: LineEnding::CrLf,
1066            }
1067        );
1068        assert_eq!(
1069            fragmentizer.fragment_bytes(fragment_info),
1070            b"A2 LOGIN ABCDE EF"
1071        );
1072        assert_eq!(fragmentizer.message_bytes(), b"A2 LOGIN ABCDE EF");
1073        assert!(fragmentizer.is_message_complete());
1074        assert!(fragmentizer.is_max_message_size_exceeded());
1075
1076        let fragment_info = fragmentizer.progress().unwrap();
1077
1078        assert_eq!(
1079            fragment_info,
1080            FragmentInfo::Line {
1081                start: 0,
1082                end: 14,
1083                announcement: Some(LiteralAnnouncement {
1084                    mode: LiteralMode::Sync,
1085                    length: 5
1086                }),
1087                ending: LineEnding::CrLf,
1088            }
1089        );
1090        assert_eq!(
1091            fragmentizer.fragment_bytes(fragment_info),
1092            b"A3 LOGIN {5}\r\n"
1093        );
1094        assert_eq!(fragmentizer.message_bytes(), b"A3 LOGIN {5}\r\n");
1095        assert!(!fragmentizer.is_message_complete());
1096        assert!(!fragmentizer.is_max_message_size_exceeded());
1097
1098        let fragment_info = fragmentizer.progress().unwrap();
1099
1100        assert_eq!(fragment_info, FragmentInfo::Literal { start: 14, end: 19 });
1101        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"ABC");
1102        assert_eq!(fragmentizer.message_bytes(), b"A3 LOGIN {5}\r\nABC");
1103        assert!(!fragmentizer.is_message_complete());
1104        assert!(fragmentizer.is_max_message_size_exceeded());
1105
1106        let fragment_info = fragmentizer.progress().unwrap();
1107
1108        assert_eq!(
1109            fragment_info,
1110            FragmentInfo::Line {
1111                start: 19,
1112                end: 27,
1113                announcement: None,
1114                ending: LineEnding::CrLf,
1115            }
1116        );
1117        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1118        assert_eq!(fragmentizer.message_bytes(), b"A3 LOGIN {5}\r\nABC");
1119        assert!(fragmentizer.is_message_complete());
1120        assert!(fragmentizer.is_max_message_size_exceeded());
1121
1122        let fragment_info = fragmentizer.progress().unwrap();
1123
1124        assert_eq!(
1125            fragment_info,
1126            FragmentInfo::Line {
1127                start: 0,
1128                end: 14,
1129                announcement: None,
1130                ending: LineEnding::CrLf,
1131            }
1132        );
1133        assert_eq!(
1134            fragmentizer.fragment_bytes(fragment_info),
1135            b"A4 LOGIN A B\r\n"
1136        );
1137        assert_eq!(fragmentizer.message_bytes(), b"A4 LOGIN A B\r\n");
1138        assert!(fragmentizer.is_message_complete());
1139        assert!(!fragmentizer.is_max_message_size_exceeded());
1140
1141        let fragment_info = fragmentizer.progress();
1142
1143        assert_eq!(fragment_info, None);
1144        assert_eq!(fragmentizer.message_bytes(), b"");
1145        assert_eq!(fragmentizer.message_bytes(), b"");
1146        assert!(!fragmentizer.is_message_complete());
1147        assert!(!fragmentizer.is_max_message_size_exceeded());
1148    }
1149
1150    #[test]
1151    fn fragmentizer_progress_messages_with_zero_max_size() {
1152        let mut fragmentizer = Fragmentizer::new(0);
1153        fragmentizer.enqueue_bytes(b"A1 NOOP\r\n");
1154        fragmentizer.enqueue_bytes(b"A2 LOGIN ABCDE EFGIJ\r\n");
1155        fragmentizer.enqueue_bytes(b"A3 LOGIN {5}\r\n");
1156        fragmentizer.enqueue_bytes(b"ABCDE");
1157        fragmentizer.enqueue_bytes(b" EFGIJ\r\n");
1158
1159        let fragment_info = fragmentizer.progress().unwrap();
1160
1161        assert_eq!(
1162            fragment_info,
1163            FragmentInfo::Line {
1164                start: 0,
1165                end: 9,
1166                announcement: None,
1167                ending: LineEnding::CrLf,
1168            }
1169        );
1170        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1171        assert_eq!(fragmentizer.message_bytes(), b"");
1172        assert!(fragmentizer.is_message_complete());
1173        assert!(fragmentizer.is_max_message_size_exceeded());
1174
1175        let fragment_info = fragmentizer.progress().unwrap();
1176
1177        assert_eq!(
1178            fragment_info,
1179            FragmentInfo::Line {
1180                start: 0,
1181                end: 22,
1182                announcement: None,
1183                ending: LineEnding::CrLf,
1184            }
1185        );
1186        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1187        assert_eq!(fragmentizer.message_bytes(), b"");
1188        assert!(fragmentizer.is_message_complete());
1189        assert!(fragmentizer.is_max_message_size_exceeded());
1190
1191        let fragment_info = fragmentizer.progress().unwrap();
1192
1193        assert_eq!(
1194            fragment_info,
1195            FragmentInfo::Line {
1196                start: 0,
1197                end: 14,
1198                announcement: Some(LiteralAnnouncement {
1199                    mode: LiteralMode::Sync,
1200                    length: 5
1201                }),
1202                ending: LineEnding::CrLf,
1203            }
1204        );
1205        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1206        assert_eq!(fragmentizer.message_bytes(), b"");
1207        assert!(!fragmentizer.is_message_complete());
1208        assert!(fragmentizer.is_max_message_size_exceeded());
1209
1210        let fragment_info = fragmentizer.progress().unwrap();
1211
1212        assert_eq!(fragment_info, FragmentInfo::Literal { start: 14, end: 19 });
1213        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1214        assert_eq!(fragmentizer.message_bytes(), b"");
1215        assert!(!fragmentizer.is_message_complete());
1216        assert!(fragmentizer.is_max_message_size_exceeded());
1217
1218        let fragment_info = fragmentizer.progress().unwrap();
1219
1220        assert_eq!(
1221            fragment_info,
1222            FragmentInfo::Line {
1223                start: 19,
1224                end: 27,
1225                announcement: None,
1226                ending: LineEnding::CrLf,
1227            }
1228        );
1229        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"");
1230        assert_eq!(fragmentizer.message_bytes(), b"");
1231        assert!(fragmentizer.is_message_complete());
1232        assert!(fragmentizer.is_max_message_size_exceeded());
1233
1234        let fragment_info = fragmentizer.progress();
1235
1236        assert_eq!(fragment_info, None);
1237        assert_eq!(fragmentizer.message_bytes(), b"");
1238        assert_eq!(fragmentizer.message_bytes(), b"");
1239        assert!(!fragmentizer.is_message_complete());
1240        assert!(!fragmentizer.is_max_message_size_exceeded());
1241    }
1242
1243    #[test]
1244    fn fragmentizer_decode_message() {
1245        let command_codec = CommandCodec::new();
1246        let response_codec = ResponseCodec::new();
1247
1248        let mut fragmentizer = Fragmentizer::new(10);
1249        fragmentizer.enqueue_bytes(b"A1 NOOP\r\n");
1250        fragmentizer.enqueue_bytes(b"A2 LOGIN ABCDE EFGIJ\r\n");
1251
1252        fragmentizer.progress();
1253        assert_eq!(
1254            fragmentizer.decode_message(&command_codec),
1255            Ok(Command::new("A1", CommandBody::Noop).unwrap()),
1256        );
1257        assert_eq!(
1258            fragmentizer.decode_message(&response_codec),
1259            Err(DecodeMessageError::DecodingFailure(
1260                ResponseDecodeError::Failed
1261            )),
1262        );
1263
1264        fragmentizer.progress();
1265        assert_eq!(
1266            fragmentizer.decode_message(&response_codec),
1267            Err(DecodeMessageError::MessageTooLong {
1268                initial: Secret::new(b"A2 LOGIN A"),
1269            }),
1270        );
1271    }
1272
1273    #[test]
1274    fn fragmentizer_poison_message() {
1275        let command_codec = CommandCodec::new();
1276
1277        let mut fragmentizer = Fragmentizer::without_max_message_size();
1278        fragmentizer.enqueue_bytes(b"A1 NOOP\r\n");
1279        fragmentizer.enqueue_bytes(b"A2 LOGIN {5}\r\n");
1280        fragmentizer.enqueue_bytes(b"ABCDE");
1281        fragmentizer.enqueue_bytes(b" EFGIJ\r\n");
1282
1283        assert!(!fragmentizer.is_message_poisoned());
1284
1285        fragmentizer.poison_message();
1286
1287        assert!(fragmentizer.is_message_poisoned());
1288
1289        let fragment_info = fragmentizer.progress().unwrap();
1290
1291        assert_eq!(
1292            fragment_info,
1293            FragmentInfo::Line {
1294                start: 0,
1295                end: 9,
1296                announcement: None,
1297                ending: LineEnding::CrLf,
1298            }
1299        );
1300        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A1 NOOP\r\n");
1301        assert_eq!(fragmentizer.message_bytes(), b"A1 NOOP\r\n");
1302        assert!(fragmentizer.is_message_complete());
1303        assert!(fragmentizer.is_message_poisoned());
1304
1305        let decode_err = fragmentizer.decode_message(&command_codec).unwrap_err();
1306
1307        assert_eq!(
1308            decode_err,
1309            DecodeMessageError::MessagePoisoned {
1310                discarded: Secret::new(fragmentizer.message_bytes())
1311            }
1312        );
1313
1314        let fragment_info = fragmentizer.progress().unwrap();
1315
1316        assert_eq!(
1317            fragment_info,
1318            FragmentInfo::Line {
1319                start: 0,
1320                end: 14,
1321                announcement: Some(LiteralAnnouncement {
1322                    mode: LiteralMode::Sync,
1323                    length: 5
1324                }),
1325                ending: LineEnding::CrLf,
1326            }
1327        );
1328        assert_eq!(
1329            fragmentizer.fragment_bytes(fragment_info),
1330            b"A2 LOGIN {5}\r\n"
1331        );
1332        assert_eq!(fragmentizer.message_bytes(), b"A2 LOGIN {5}\r\n");
1333        assert!(!fragmentizer.is_message_complete());
1334        assert!(!fragmentizer.is_message_poisoned());
1335
1336        let fragment_info = fragmentizer.progress().unwrap();
1337
1338        assert_eq!(fragment_info, FragmentInfo::Literal { start: 14, end: 19 });
1339        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"ABCDE");
1340        assert_eq!(fragmentizer.message_bytes(), b"A2 LOGIN {5}\r\nABCDE");
1341        assert!(!fragmentizer.is_message_complete());
1342        assert!(!fragmentizer.is_message_poisoned());
1343
1344        fragmentizer.poison_message();
1345        assert!(fragmentizer.is_message_poisoned());
1346
1347        let fragment_info = fragmentizer.progress().unwrap();
1348
1349        assert_eq!(
1350            fragment_info,
1351            FragmentInfo::Line {
1352                start: 19,
1353                end: 27,
1354                announcement: None,
1355                ending: LineEnding::CrLf,
1356            }
1357        );
1358        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b" EFGIJ\r\n");
1359        assert_eq!(
1360            fragmentizer.message_bytes(),
1361            b"A2 LOGIN {5}\r\nABCDE EFGIJ\r\n"
1362        );
1363        assert!(fragmentizer.is_message_complete());
1364        assert!(fragmentizer.is_message_poisoned());
1365
1366        let decode_err = fragmentizer.decode_message(&command_codec).unwrap_err();
1367
1368        assert_eq!(
1369            decode_err,
1370            DecodeMessageError::MessagePoisoned {
1371                discarded: Secret::new(fragmentizer.message_bytes())
1372            }
1373        );
1374
1375        let fragment_info = fragmentizer.progress();
1376
1377        assert_eq!(fragment_info, None);
1378        assert_eq!(fragmentizer.message_bytes(), b"");
1379        assert_eq!(fragmentizer.message_bytes(), b"");
1380        assert!(!fragmentizer.is_message_complete());
1381        assert!(!fragmentizer.is_message_poisoned());
1382    }
1383
1384    #[test]
1385    fn fragmentizer_poison_too_long_message() {
1386        let command_codec = CommandCodec::new();
1387
1388        let mut fragmentizer = Fragmentizer::new(5);
1389        fragmentizer.enqueue_bytes(b"A1 NOOP\r\n");
1390
1391        assert!(!fragmentizer.is_message_poisoned());
1392
1393        fragmentizer.poison_message();
1394
1395        assert!(fragmentizer.is_message_poisoned());
1396
1397        let fragment_info = fragmentizer.progress().unwrap();
1398
1399        assert_eq!(
1400            fragment_info,
1401            FragmentInfo::Line {
1402                start: 0,
1403                end: 9,
1404                announcement: None,
1405                ending: LineEnding::CrLf,
1406            }
1407        );
1408        assert_eq!(fragmentizer.fragment_bytes(fragment_info), b"A1 NO");
1409        assert_eq!(fragmentizer.message_bytes(), b"A1 NO");
1410        assert!(fragmentizer.is_message_complete());
1411        assert!(fragmentizer.is_max_message_size_exceeded());
1412        assert!(fragmentizer.is_message_poisoned());
1413
1414        let decode_err = fragmentizer.decode_message(&command_codec).unwrap_err();
1415
1416        assert_eq!(
1417            decode_err,
1418            DecodeMessageError::MessageTooLong {
1419                initial: Secret::new(b"A1 NO")
1420            }
1421        );
1422
1423        let fragment_info = fragmentizer.progress();
1424
1425        assert_eq!(fragment_info, None);
1426        assert_eq!(fragmentizer.message_bytes(), b"");
1427        assert_eq!(fragmentizer.message_bytes(), b"");
1428        assert!(!fragmentizer.is_message_complete());
1429        assert!(!fragmentizer.is_max_message_size_exceeded());
1430        assert!(!fragmentizer.is_message_poisoned());
1431    }
1432
1433    #[track_caller]
1434    fn assert_not_line(not_a_line_bytes: &[u8]) {
1435        let mut line_parser = LineParser::new(0);
1436        let not_a_line_bytes = not_a_line_bytes.iter().copied().collect();
1437
1438        let (parsed_byte_count, fragment_info) = line_parser.parse(&not_a_line_bytes);
1439
1440        assert_eq!(parsed_byte_count, not_a_line_bytes.len());
1441        assert_eq!(fragment_info, None);
1442    }
1443
1444    #[test]
1445    fn parse_line_examples() {
1446        assert_not_line(b"");
1447        assert_not_line(b"foo");
1448
1449        assert_is_line(b"\n", 1, None, LineEnding::Lf);
1450        assert_is_line(b"\r\n", 2, None, LineEnding::CrLf);
1451        assert_is_line(b"\n\r", 1, None, LineEnding::Lf);
1452        assert_is_line(b"foo\n", 4, None, LineEnding::Lf);
1453        assert_is_line(b"foo\r\n", 5, None, LineEnding::CrLf);
1454        assert_is_line(b"foo\n\r", 4, None, LineEnding::Lf);
1455        assert_is_line(b"foo\nbar\n", 4, None, LineEnding::Lf);
1456        assert_is_line(b"foo\r\nbar\r\n", 5, None, LineEnding::CrLf);
1457        assert_is_line(b"\r\nfoo\r\n", 2, None, LineEnding::CrLf);
1458        assert_is_line(
1459            b"{1}\r\n",
1460            5,
1461            Some(LiteralAnnouncement {
1462                length: 1,
1463                mode: LiteralMode::Sync,
1464            }),
1465            LineEnding::CrLf,
1466        );
1467        assert_is_line(
1468            b"{1}\n",
1469            4,
1470            Some(LiteralAnnouncement {
1471                length: 1,
1472                mode: LiteralMode::Sync,
1473            }),
1474            LineEnding::Lf,
1475        );
1476        assert_is_line(
1477            b"foo {1}\r\n",
1478            9,
1479            Some(LiteralAnnouncement {
1480                length: 1,
1481                mode: LiteralMode::Sync,
1482            }),
1483            LineEnding::CrLf,
1484        );
1485        assert_is_line(
1486            b"foo {2} {1}\r\n",
1487            13,
1488            Some(LiteralAnnouncement {
1489                length: 1,
1490                mode: LiteralMode::Sync,
1491            }),
1492            LineEnding::CrLf,
1493        );
1494        assert_is_line(b"foo {1} \r\n", 10, None, LineEnding::CrLf);
1495        assert_is_line(b"foo \n {1}\r\n", 5, None, LineEnding::Lf);
1496        assert_is_line(b"foo {1} foo\r\n", 13, None, LineEnding::CrLf);
1497        assert_is_line(b"foo {1\r\n", 8, None, LineEnding::CrLf);
1498        assert_is_line(b"foo 1}\r\n", 8, None, LineEnding::CrLf);
1499        assert_is_line(b"foo { 1}\r\n", 10, None, LineEnding::CrLf);
1500        assert_is_line(
1501            b"foo {{1}\r\n",
1502            10,
1503            Some(LiteralAnnouncement {
1504                length: 1,
1505                mode: LiteralMode::Sync,
1506            }),
1507            LineEnding::CrLf,
1508        );
1509        assert_is_line(
1510            b"foo {42}\r\n",
1511            10,
1512            Some(LiteralAnnouncement {
1513                length: 42,
1514                mode: LiteralMode::Sync,
1515            }),
1516            LineEnding::CrLf,
1517        );
1518        assert_is_line(
1519            b"foo {42+}\r\n",
1520            11,
1521            Some(LiteralAnnouncement {
1522                length: 42,
1523                mode: LiteralMode::NonSync,
1524            }),
1525            LineEnding::CrLf,
1526        );
1527        assert_is_line(
1528            b"foo +{42}\r\n",
1529            11,
1530            Some(LiteralAnnouncement {
1531                length: 42,
1532                mode: LiteralMode::Sync,
1533            }),
1534            LineEnding::CrLf,
1535        );
1536        assert_is_line(b"foo {+}\r\n", 9, None, LineEnding::CrLf);
1537        assert_is_line(b"foo {42++}\r\n", 12, None, LineEnding::CrLf);
1538        assert_is_line(b"foo {+42+}\r\n", 12, None, LineEnding::CrLf);
1539        assert_is_line(b"foo {+42}\r\n", 11, None, LineEnding::CrLf);
1540        assert_is_line(b"foo {42}+\r\n", 11, None, LineEnding::CrLf);
1541        assert_is_line(b"foo {-42}\r\n", 11, None, LineEnding::CrLf);
1542        assert_is_line(b"foo {42-}\r\n", 11, None, LineEnding::CrLf);
1543        assert_is_line(
1544            b"foo {4294967295}\r\n",
1545            18,
1546            Some(LiteralAnnouncement {
1547                length: 4294967295,
1548                mode: LiteralMode::Sync,
1549            }),
1550            LineEnding::CrLf,
1551        );
1552        assert_is_line(b"foo {4294967296}\r\n", 18, None, LineEnding::CrLf);
1553    }
1554
1555    #[test]
1556    fn parse_line_corner_case() {
1557        // According to the IMAP RFC, this line does not announce a literal.
1558        // We thought intensively about this corner case and asked different people.
1559        // Our conclusion: This corner case is an oversight of the RFC authors and
1560        // doesn't appear in the wild. We ignore it for now. If this becomes an issue
1561        // in practice then we should implement a detection for "* OK", "* NO" and
1562        // "* BAD".
1563        // See https://github.com/duesee/imap-codec/issues/432#issuecomment-1962427538
1564        assert_is_line(
1565            b"* OK {1}\r\n",
1566            10,
1567            Some(LiteralAnnouncement {
1568                length: 1,
1569                mode: LiteralMode::Sync,
1570            }),
1571            LineEnding::CrLf,
1572        );
1573    }
1574
1575    #[test]
1576    fn parse_tag_examples() {
1577        assert_eq!(parse_tag(b"1 NOOP\r\n"), Tag::try_from("1").ok());
1578        assert_eq!(parse_tag(b"12 NOOP\r\n"), Tag::try_from("12").ok());
1579        assert_eq!(parse_tag(b"123 NOOP\r\n"), Tag::try_from("123").ok());
1580        assert_eq!(parse_tag(b"1234 NOOP\r\n"), Tag::try_from("1234").ok());
1581        assert_eq!(parse_tag(b"12345 NOOP\r\n"), Tag::try_from("12345").ok());
1582
1583        assert_eq!(parse_tag(b"A1 NOOP\r\n"), Tag::try_from("A1").ok());
1584        assert_eq!(parse_tag(b"A1 NOOP"), Tag::try_from("A1").ok());
1585        assert_eq!(parse_tag(b"A1 "), Tag::try_from("A1").ok());
1586        assert_eq!(parse_tag(b"A1  "), Tag::try_from("A1").ok());
1587        assert_eq!(parse_tag(b"A1 \r\n"), Tag::try_from("A1").ok());
1588        assert_eq!(parse_tag(b"A1 \n"), Tag::try_from("A1").ok());
1589        assert_eq!(parse_tag(b"A1"), None);
1590        assert_eq!(parse_tag(b"A1\r\n"), None);
1591        assert_eq!(parse_tag(b"A1\n"), None);
1592        assert_eq!(parse_tag(b" \r\n"), None);
1593        assert_eq!(parse_tag(b"\r\n"), None);
1594        assert_eq!(parse_tag(b""), None);
1595        assert_eq!(parse_tag(b" A1 NOOP\r\n"), None);
1596    }
1597}