use paste::paste; use crate::protocol::packet::PacketBuf; use crate::target::Target; pub(self) mod prelude { pub use super::ParseCommand; pub use crate::common::*; pub use crate::protocol::common::*; pub use crate::protocol::packet::PacketBuf; pub use core::convert::{TryFrom, TryInto}; } pub trait ParseCommand<'a>: Sized { /// Try to parse a packet from the packet buffer. fn from_packet(buf: PacketBuf<'a>) -> Option; } macro_rules! commands { ( $( $ext:ident $(use $lt:lifetime)? { $($name:literal => $mod:ident::$command:ident$(<$lifetime:lifetime>)?,)* } )* ) => {paste! { $($( #[allow(non_snake_case, non_camel_case_types)] pub mod $mod; )*)* pub mod ext { $( #[allow(non_camel_case_types)] pub enum [<$ext:camel>] $(<$lt>)? { $($command(super::$mod::$command<$($lifetime)?>),)* } )* } /// GDB commands pub enum Command<'a> { $( [<$ext:camel>](ext::[<$ext:camel>]$(<$lt>)?), )* Unknown(&'a str), } impl<'a> Command<'a> { pub fn from_packet( target: &mut impl Target, buf: PacketBuf<'a> ) -> Result, CommandParseError<'a>> { if buf.as_body().is_empty() { return Err(CommandParseError::Empty); } let body = buf.as_body(); // This scoped extension trait enables using `base` as an // `$ext`, even through the `base` method on `Target` doesn't // return an Option. trait Hack { fn base(&mut self) -> Option<()> { Some(()) } } impl Hack for T {} $( if target.$ext().is_some() { // TODO?: use tries for more efficient longest prefix matching #[allow(clippy::string_lit_as_bytes)] match body { $(_ if body.starts_with($name.as_bytes()) => { crate::__dead_code_marker!($name, "prefix_match"); let buf = buf.trim_start_body_bytes($name.len()); let cmd = $mod::$command::from_packet(buf) .ok_or(CommandParseError::MalformedCommand($name))?; return Ok( Command::[<$ext:camel>]( ext::[<$ext:camel>]::$command(cmd) ) ) })* _ => {}, } } )* Ok(Command::Unknown(buf.into_body_str())) } } }}; } /// Command parse error // TODO?: add more granular errors to command parsing code pub enum CommandParseError<'a> { Empty, /// catch-all MalformedCommand(&'a str), } commands! { base use 'a { "?" => question_mark::QuestionMark, "c" => _c::c<'a>, "D" => _d_upcase::D, "g" => _g::g, "G" => _g_upcase::G<'a>, "H" => _h_upcase::H, "k" => _k::k, "m" => _m::m<'a>, "M" => _m_upcase::M<'a>, "p" => _p::p, "P" => _p_upcase::P<'a>, "qAttached" => _qAttached::qAttached, "qfThreadInfo" => _qfThreadInfo::qfThreadInfo, "QStartNoAckMode" => _QStartNoAckMode::QStartNoAckMode, "qsThreadInfo" => _qsThreadInfo::qsThreadInfo, "qSupported" => _qSupported::qSupported<'a>, "qXfer:features:read" => _qXfer_features_read::qXferFeaturesRead, "s" => _s::s<'a>, "T" => _t_upcase::T, "vCont" => _vCont::vCont<'a>, "vKill" => _vKill::vKill, "z" => _z::z<'a>, "Z" => _z_upcase::Z<'a>, } extended_mode use 'a { "!" => exclamation_mark::ExclamationMark, "QDisableRandomization" => _QDisableRandomization::QDisableRandomization, "QEnvironmentHexEncoded" => _QEnvironmentHexEncoded::QEnvironmentHexEncoded<'a>, "QEnvironmentReset" => _QEnvironmentReset::QEnvironmentReset, "QEnvironmentUnset" => _QEnvironmentUnset::QEnvironmentUnset<'a>, "QSetWorkingDir" => _QSetWorkingDir::QSetWorkingDir<'a>, "QStartupWithShell" => _QStartupWithShell::QStartupWithShell, "R" => _r_upcase::R, "vAttach" => _vAttach::vAttach, "vRun" => _vRun::vRun<'a>, } monitor_cmd use 'a { "qRcmd" => _qRcmd::qRcmd<'a>, } section_offsets { "qOffsets" => _qOffsets::qOffsets, } }