use anyhow::{bail, Result}; use tokio::io::{AsyncRead, AsyncReadExt as _}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RequestKind { Signal, Command, } #[derive(Debug)] pub struct Request<'a> { pub kind: RequestKind, body: &'a str, } impl<'a> Request<'a> { pub async fn read( reader: &mut R, buffer: &'a mut Vec, ) -> Result { buffer.clear(); let mut char = reader.read_u8().await?; while char == b'+' { // just ignore positive acks char = reader.read_u8().await?; } if char == b'-' { bail!("no support for negative acks"); } if char == 0x03 { // This is how the client "cancels an in-flight request" buffer.push(char); let body = std::str::from_utf8(buffer)?; return Ok(Self { kind: RequestKind::Signal, body, }); } if char != b'$' { // Messages are supposed to start with a dollar sign bail!("malformed message"); } // now read the body let mut checksum = 0u8; char = reader.read_u8().await?; while char != b'#' { if char == b'}' { // escape character checksum = checksum.wrapping_add(char); char = reader.read_u8().await?; checksum = checksum.wrapping_add(char); buffer.push(char ^ 0x20); } else { checksum = checksum.wrapping_add(char); buffer.push(char); } char = reader.read_u8().await?; } let mut checksum_bytes = [b'0'; 2]; reader.read_exact(&mut checksum_bytes).await?; let checksum_str = std::str::from_utf8(&checksum_bytes)?; let real_checksum = u8::from_str_radix(checksum_str, 16)?; if checksum != real_checksum { bail!("invalid checksum"); } let body = std::str::from_utf8(buffer)?; Ok(Self { kind: RequestKind::Command, body, }) } pub fn match_str(&mut self, prefix: &str) -> bool { if let Some(new_body) = self.body.strip_prefix(prefix) { self.body = new_body; return true; } false } }