diff options
Diffstat (limited to 'src/protocol/response_writer.rs')
-rw-r--r-- | src/protocol/response_writer.rs | 137 |
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")?, |