blob: 8b1d4e4a764eb77d6fde7379f2d04779702b1a53 [file] [log] [blame]
use crate::protocol::{commands::Command, common::decode_hex};
use crate::target::Target;
/// Packet parse error.
#[derive(Debug)]
pub enum PacketParseError {
ChecksumMismatched { checksum: u8, calculated: u8 },
EmptyBuf,
MissingChecksum,
MalformedChecksum,
MalformedCommand,
NotASCII,
UnexpectedHeader(u8),
}
/// Top-Level GDB packet
pub enum Packet<'a> {
Ack,
Nack,
Interrupt,
Command(Command<'a>),
}
pub struct PacketBuf<'a> {
buf: &'a mut [u8],
body_range: core::ops::Range<usize>,
}
impl<'a> PacketBuf<'a> {
/// Validate the contents of the raw packet buffer, checking for checksum
/// consistency, structural correctness, and ASCII validation.
pub fn new(pkt_buf: &'a mut [u8]) -> Result<PacketBuf<'a>, PacketParseError> {
// validate the packet is valid ASCII
if !pkt_buf.is_ascii() {
return Err(PacketParseError::NotASCII);
}
let end_of_body = pkt_buf
.iter()
.position(|b| *b == b'#')
.ok_or(PacketParseError::MissingChecksum)?;
// split buffer into body and checksum components
let (body, checksum) = pkt_buf.split_at_mut(end_of_body);
let body = &mut body[1..]; // skip the '$'
let checksum = &mut checksum[1..][..2]; // skip the '#'
// validate the checksum
let checksum = decode_hex(checksum).map_err(|_| PacketParseError::MalformedChecksum)?;
let calculated = body.iter().fold(0u8, |a, x| a.wrapping_add(*x));
if calculated != checksum {
return Err(PacketParseError::ChecksumMismatched {
checksum,
calculated,
});
}
if log_enabled!(log::Level::Trace) {
// SAFETY: body confirmed to be `is_ascii()`
let body = unsafe { core::str::from_utf8_unchecked(body) };
trace!("<-- ${}#{:02x?}", body, checksum);
}
Ok(PacketBuf {
buf: pkt_buf,
body_range: 1..end_of_body,
})
}
/// (used for tests) Create a packet buffer from a raw body buffer, skipping
/// the header/checksum trimming stage. ASCII validation is still performed.
#[cfg(test)]
pub fn new_with_raw_body(body: &'a mut [u8]) -> Result<PacketBuf<'a>, PacketParseError> {
// validate the packet is valid ASCII
if !body.is_ascii() {
return Err(PacketParseError::NotASCII);
}
let len = body.len();
Ok(PacketBuf {
buf: body,
body_range: 0..len,
})
}
pub fn trim_start_body_bytes(self, n: usize) -> Self {
PacketBuf {
buf: self.buf,
body_range: (self.body_range.start + n)..self.body_range.end,
}
}
pub fn as_body(&'a self) -> &'a [u8] {
&self.buf[self.body_range.clone()]
}
/// Return a mut reference to slice of the packet buffer corresponding to
/// the current body.
pub fn into_body(self) -> &'a mut [u8] {
&mut self.buf[self.body_range]
}
pub fn into_body_str(self) -> &'a str {
// SAFETY: buffer confirmed to be `is_ascii()` in `new`, and no other PacketBuf
// member allow arbitrary modification of `self.buf`.
unsafe { core::str::from_utf8_unchecked(&self.buf[self.body_range]) }
}
/// Return a mut reference to the _entire_ underlying packet buffer, and the
/// current body's range.
#[allow(dead_code)]
pub fn into_raw_buf(self) -> (&'a mut [u8], core::ops::Range<usize>) {
(self.buf, self.body_range)
}
}
impl<'a> Packet<'a> {
pub fn from_buf(
target: &mut impl Target,
buf: &'a mut [u8],
) -> Result<Packet<'a>, PacketParseError> {
// cannot have empty packet
if buf.is_empty() {
return Err(PacketParseError::EmptyBuf);
}
match buf[0] {
b'$' => Ok(Packet::Command(
Command::from_packet(target, PacketBuf::new(buf)?)
// TODO?: preserve command parse error context
.map_err(|_| PacketParseError::MalformedCommand)?,
)),
b'+' => Ok(Packet::Ack),
b'-' => Ok(Packet::Nack),
0x03 => Ok(Packet::Interrupt),
_ => Err(PacketParseError::UnexpectedHeader(buf[0])),
}
}
}