aboutsummaryrefslogtreecommitdiff
path: root/src/protocol/response_writer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol/response_writer.rs')
-rw-r--r--src/protocol/response_writer.rs137
1 files changed, 103 insertions, 34 deletions
diff --git a/src/protocol/response_writer.rs b/src/protocol/response_writer.rs
index 484bd60..3dea22a 100644
--- a/src/protocol/response_writer.rs
+++ b/src/protocol/response_writer.rs
@@ -1,8 +1,14 @@
-use num_traits::PrimInt;
+#[cfg(feature = "trace-pkt")]
+use alloc::string::String;
+#[cfg(feature = "trace-pkt")]
+use alloc::vec::Vec;
+use num_traits::identities::one;
+use num_traits::{CheckedRem, PrimInt};
+
+use crate::conn::Connection;
use crate::internal::BeBytes;
use crate::protocol::{SpecificIdKind, SpecificThreadId};
-use crate::Connection;
/// Newtype around a Connection error. Having a newtype allows implementing a
/// `From<ResponseWriterError<C>> for crate::Error<T, C>`, which greatly
@@ -12,55 +18,60 @@ 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 + 'a> {
- // TODO: add `write_all` method to Connection, and allow user to optionally pass outgoing
- // packet buffer? This could improve performance (instead of writing a single byte at a time)
+pub struct ResponseWriter<'a, C: Connection> {
inner: &'a mut C,
started: bool,
checksum: u8,
- // TODO?: Make using RLE configurable by the target?
- // if implemented correctly, targets that disable RLE entirely could have all RLE code
- // dead-code-eliminated.
+
+ rle_enabled: bool,
rle_char: u8,
rle_repeat: u8,
+
// buffer to log outgoing packets. only allocates if logging is enabled.
- #[cfg(feature = "std")]
+ #[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) -> Self {
+ 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 = "std")]
+
+ #[cfg(feature = "trace-pkt")]
msg: Vec::new(),
}
}
/// Consumes self, writing out the final '#' and checksum
pub fn flush(mut self) -> Result<(), Error<C::Error>> {
- self.write(b'#')?;
-
// don't include the '#' in checksum calculation
- // (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)
- let checksum = self.checksum;
-
- #[cfg(feature = "std")]
- trace!(
- "--> ${}#{:02x?}",
- core::str::from_utf8(&self.msg).unwrap(), // buffers are always ascii
+ 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
- self.write(0)?;
+ if self.rle_enabled {
+ self.write(0)?;
+ }
+
+ #[cfg(feature = "trace-pkt")]
+ trace!("--> ${}", String::from_utf8_lossy(&self.msg));
self.inner.flush().map_err(Error)?;
@@ -73,17 +84,21 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
}
fn inner_write(&mut self, byte: u8) -> Result<(), Error<C::Error>> {
- #[cfg(feature = "std")]
+ #[cfg(feature = "trace-pkt")]
if log_enabled!(log::Level::Trace) {
- match self.msg.as_slice() {
- [.., c, b'*'] => {
- let c = *c;
- self.msg.pop();
- for _ in 0..(byte - 29) {
- self.msg.push(c);
+ 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),
}
- _ => self.msg.push(byte),
+ } else {
+ self.msg.push(byte)
}
}
@@ -97,6 +112,10 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
}
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'~';
@@ -138,7 +157,7 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
}
/// Write an entire string over the connection.
- pub fn write_str(&mut self, s: &'static str) -> Result<(), Error<C::Error>> {
+ pub fn write_str(&mut self, s: &str) -> Result<(), Error<C::Error>> {
for b in s.as_bytes().iter() {
self.write(*b)?;
}
@@ -147,11 +166,24 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
/// 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() {
+ for &digit in [(byte & 0xf0) >> 4, byte & 0x0f].iter() {
let c = match digit {
0..=9 => b'0' + digit,
10..=15 => b'a' + digit - 10,
- _ => unreachable!(),
+ // 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)?;
}
@@ -197,6 +229,43 @@ impl<'a, C: Connection + 'a> ResponseWriter<'a, C> {
Ok(())
}
+ /// Write a number as a decimal string, converting every digit to an ascii
+ /// char.
+ pub fn write_dec<D: PrimInt + CheckedRem>(
+ &mut self,
+ mut digit: D,
+ ) -> Result<(), Error<C::Error>> {
+ if digit.is_zero() {
+ return self.write(b'0');
+ }
+
+ let one: D = one();
+ let ten = (one << 3) + (one << 1);
+ let mut d = digit;
+ let mut pow_10 = one;
+ // Get the number of digits in digit
+ while d >= ten {
+ d = d / ten;
+ pow_10 = pow_10 * ten;
+ }
+
+ // Write every digit from left to right as an ascii char
+ while !pow_10.is_zero() {
+ let mut byte = 0;
+ // We have a single digit here which uses up to 4 bit
+ for i in 0..4 {
+ if !((digit / pow_10) & (one << i)).is_zero() {
+ byte += 1 << i;
+ }
+ }
+ self.write(b'0' + byte)?;
+ digit = digit % pow_10;
+ pow_10 = pow_10 / ten;
+ }
+ Ok(())
+ }
+
+ #[inline]
fn write_specific_id_kind(&mut self, tid: SpecificIdKind) -> Result<(), Error<C::Error>> {
match tid {
SpecificIdKind::All => self.write_str("-1")?,