//! Newline-delimited protocol helpers shared by the client and server. //! //! TLS reads can return more than one line at a time. Keeping the leftover //! bytes is essential: dropping them silently loses the next message. use std::io::{self, Read}; #[derive(Debug, Default)] pub struct LineBuffer { buffer: Vec, } impl LineBuffer { pub fn new() -> Self { Self::default() } pub fn push(&mut self, bytes: &[u8]) { self.buffer.extend_from_slice(bytes); } /// Return the next complete line, including a line that ended at EOF. pub fn next_line(&mut self) -> Option { let end = self.buffer.iter().position(|byte| *byte == b'\n')?; let mut line: Vec = self.buffer.drain(..=end).collect(); line.pop(); // '\n' if line.last() == Some(&b'\r') { line.pop(); } Some(String::from_utf8_lossy(&line).into_owned()) } /// Return the unterminated remainder at EOF, if any. pub fn take_rest(&mut self) -> Option { if self.buffer.is_empty() { return None; } let mut rest = std::mem::take(&mut self.buffer); if rest.last() == Some(&b'\r') { rest.pop(); } Some(String::from_utf8_lossy(&rest).into_owned()) } /// Read one line from a blocking reader, preserving any extra lines for /// the next call. Returns `None` at EOF with nothing buffered. pub fn read_line(&mut self, reader: &mut R) -> io::Result> { loop { if let Some(line) = self.next_line() { return Ok(Some(line)); } let mut chunk = [0u8; 4096]; match reader.read(&mut chunk) { Ok(0) => return Ok(self.take_rest()), Ok(n) => self.push(&chunk[..n]), Err(error) if error.kind() == io::ErrorKind::Interrupted => continue, Err(error) => return Err(error), } } } } /// A stable, user-facing message for a failed request. pub fn io_failure_message(error: &io::Error) -> &'static str { match error.kind() { io::ErrorKind::TimedOut | io::ErrorKind::WouldBlock => "ERROR Request timed out", _ => "ERROR Connection lost", } }