blob: 09ea352ff419616c0a92cd137e67fa36959d1155 [file] [log] [blame]
#[cfg(feature = "trace-pkt")]
use alloc::string::String;
#[cfg(feature = "trace-pkt")]
use alloc::vec::Vec;
use num_traits::PrimInt;
use crate::conn::Connection;
use crate::internal::BeBytes;
use crate::protocol::{SpecificIdKind, SpecificThreadId};
/// Newtype around a Connection error. Having a newtype allows implementing a
/// `From<ResponseWriterError<C>> for crate::Error<T, C>`, which greatly
/// simplifies some of the error handling in the main gdbstub.
#[derive(Debug, Clone)]
pub struct Error<C>(pub C);
/// A wrapper around [`Connection`] that computes the single-byte checksum of
/// incoming / outgoing data.
pub struct ResponseWriter<'a, C: Connection> {
inner: &'a mut C,
started: bool,
checksum: u8,
rle_enabled: bool,
rle_char: u8,
rle_repeat: u8,
// buffer to log outgoing packets. only allocates if logging is enabled.
#[cfg(feature = "trace-pkt")]
msg: Vec<u8>,
}
impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
/// Creates a new ResponseWriter
pub fn new(inner: &'a mut C, rle_enabled: bool) -> Self {
Self {
inner,
started: false,
checksum: 0,
rle_enabled,
rle_char: 0,
rle_repeat: 0,
#[cfg(feature = "trace-pkt")]
msg: Vec::new(),
}
}
/// Consumes self, writing out the final '#' and checksum
pub fn flush(mut self) -> Result<(), Error<C::Error>> {
// don't include the '#' in checksum calculation
let checksum = if self.rle_enabled {
self.write(b'#')?;
// (note: even though `self.write` was called, the the '#' char hasn't been
// added to the checksum, and is just sitting in the RLE buffer)
self.checksum
} else {
let checksum = self.checksum;
self.write(b'#')?;
checksum
};
self.write_hex(checksum)?;
// HACK: "write" a dummy char to force an RLE flush
if self.rle_enabled {
self.write(0)?;
}
#[cfg(feature = "trace-pkt")]
trace!("--> ${}", String::from_utf8_lossy(&self.msg));
self.inner.flush().map_err(Error)?;
Ok(())
}
/// Get a mutable reference to the underlying connection.
pub fn as_conn(&mut self) -> &mut C {
self.inner
}
fn inner_write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
#[cfg(feature = "trace-pkt")]
if log_enabled!(log::Level::Trace) {
if self.rle_enabled {
match self.msg.as_slice() {
[.., c, b'*'] => {
let c = *c;
self.msg.pop();
for _ in 0..(byte - 29) {
self.msg.push(c);
}
}
_ => self.msg.push(byte),
}
} else {
self.msg.push(byte)
}
}
if !self.started {
self.started = true;
self.inner.write(b'$').map_err(Error)?;
}
self.checksum = self.checksum.wrapping_add(byte);
self.inner.write(byte).map_err(Error)
}
fn write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
if !self.rle_enabled {
return self.inner_write(byte);
}
const ASCII_FIRST_PRINT: u8 = b' ';
const ASCII_LAST_PRINT: u8 = b'~';
// handle RLE
let rle_printable = (ASCII_FIRST_PRINT - 4 + (self.rle_repeat + 1)) <= ASCII_LAST_PRINT;
if byte == self.rle_char && rle_printable {
self.rle_repeat += 1;
Ok(())
} else {
loop {
match self.rle_repeat {
0 => {} // happens once, after the first char is written
// RLE doesn't win, just output the byte
1 | 2 | 3 => {
for _ in 0..self.rle_repeat {
self.inner_write(self.rle_char)?
}
}
// RLE would output an invalid char ('#' or '$')
6 | 7 => {
self.inner_write(self.rle_char)?;
self.rle_repeat -= 1;
continue;
}
// RLE wins for repetitions >4
_ => {
self.inner_write(self.rle_char)?;
self.inner_write(b'*')?;
self.inner_write(ASCII_FIRST_PRINT - 4 + self.rle_repeat)?;
}
}
self.rle_char = byte;
self.rle_repeat = 1;
break Ok(());
}
}
}
/// Write an entire string over the connection.
pub fn write_str(&mut self, s: &'static str) -> Result<(), Error<C::Error>> {
for b in s.as_bytes().iter() {
self.write(*b)?;
}
Ok(())
}
/// Write a single byte as a hex string (two ascii chars)
fn write_hex(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
for &digit in [(byte & 0xf0) >> 4, byte & 0x0f].iter() {
let c = match digit {
0..=9 => b'0' + digit,
10..=15 => b'a' + digit - 10,
// This match arm is unreachable, but the compiler isn't smart enough to optimize
// out the branch. As such, using `unreachable!` here would introduce panicking
// code to `gdbstub`.
//
// In this case, it'd be totally reasonable to use
// `unsafe { core::hint::unreachable_unchecked() }`, but i'll be honest, using some
// spooky unsafe compiler hints just to eek out a smidge more performance here just
// isn't worth the cognitive overhead.
//
// Moreover, I've played around with this code in godbolt.org, and it turns out that
// leaving this match arm as `=> digit` ends up generating the _exact same code_ as
// using `unreachable_unchecked` (at least on x86_64 targets compiled using the
// latest Rust compiler). YMMV on other platforms.
_ => digit,
};
self.write(c)?;
}
Ok(())
}
/// Write a byte-buffer as a hex string (i.e: two ascii chars / byte).
pub fn write_hex_buf(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
for b in data.iter() {
self.write_hex(*b)?;
}
Ok(())
}
/// Write data using the binary protocol.
pub fn write_binary(&mut self, data: &[u8]) -> Result<(), Error<C::Error>> {
for &b in data.iter() {
match b {
b'#' | b'$' | b'}' | b'*' => {
self.write(b'}')?;
self.write(b ^ 0x20)?
}
_ => self.write(b)?,
}
}
Ok(())
}
/// Write a number as a big-endian hex string using the most compact
/// representation possible (i.e: trimming leading zeros).
pub fn write_num<D: BeBytes + PrimInt>(&mut self, digit: D) -> Result<(), Error<C::Error>> {
if digit.is_zero() {
return self.write_hex(0);
}
let mut buf = [0; 16];
// infallible (unless digit is a >128 bit number)
let len = digit.to_be_bytes(&mut buf).unwrap();
let buf = &buf[..len];
for b in buf.iter().copied().skip_while(|&b| b == 0) {
self.write_hex(b)?
}
Ok(())
}
fn write_specific_id_kind(&mut self, tid: SpecificIdKind) -> Result<(), Error<C::Error>> {
match tid {
SpecificIdKind::All => self.write_str("-1")?,
SpecificIdKind::WithId(id) => self.write_num(id.get())?,
};
Ok(())
}
pub fn write_specific_thread_id(
&mut self,
tid: SpecificThreadId,
) -> Result<(), Error<C::Error>> {
if let Some(pid) = tid.pid {
self.write_str("p")?;
self.write_specific_id_kind(pid)?;
self.write_str(".")?;
}
self.write_specific_id_kind(tid.tid)?;
Ok(())
}
}