lemur/src/gdbserver/request.rs

116 lines
3.2 KiB
Rust

use anyhow::{bail, Result};
use atoi::FromRadix16;
use tokio::io::{AsyncRead, AsyncReadExt as _};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestKind {
Signal,
Command,
}
impl RequestKind {
fn name(self) -> &'static str {
match self {
Self::Signal => "Signal",
Self::Command => "Command",
}
}
}
pub struct Request<'a> {
pub kind: RequestKind,
buffer: &'a [u8],
}
impl std::fmt::Debug for Request<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut ds = f.debug_tuple(self.kind.name());
match self.kind {
RequestKind::Signal => ds.field(&self.buffer),
RequestKind::Command => match std::str::from_utf8(self.buffer) {
Ok(str) => ds.field(&str),
Err(_) => ds.field(&self.buffer),
},
};
ds.finish()
}
}
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);
return Ok(Self {
kind: RequestKind::Signal,
buffer,
});
}
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 (real_checksum, 2) = u8::from_radix_16(&checksum_bytes) else {
bail!("invalid checksum");
};
if checksum != real_checksum {
bail!("mismatched checksum");
}
Ok(Self {
kind: RequestKind::Command,
buffer,
})
}
pub fn match_str(&mut self, prefix: &str) -> bool {
if let Some(new_buffer) = self.buffer.strip_prefix(prefix.as_bytes()) {
self.buffer = new_buffer;
return true;
}
false
}
pub fn match_hex<I: FromRadix16>(&mut self) -> Option<I> {
match I::from_radix_16(self.buffer) {
(_, 0) => None,
(val, used) => {
self.buffer = self.buffer.split_at(used).1;
Some(val)
}
}
}
}