85 lines
2.3 KiB
Rust
85 lines
2.3 KiB
Rust
|
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<R: AsyncRead + Unpin>(
|
||
|
reader: &mut R,
|
||
|
buffer: &'a mut Vec<u8>,
|
||
|
) -> Result<Self> {
|
||
|
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
|
||
|
}
|
||
|
}
|