| 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])), |
| } |
| } |
| } |