//! Declare a restricted variant of our message types. /// Re-export tor_bytes and paste here, so that the macro can use it. pub use {paste, tor_bytes}; /// Declare a restricted version of /// [`AnyRelayMsg`](crate::relaycell::msg::AnyRelayMsg) or /// [`AnyChanMsg`](crate::chancell::msg::AnyChanMsg). /// /// Frequently we only want to handle a subset of the possible channel or relay /// commands that we might see. In those situations, it makes sense to define a /// a message types that will only try to parse the allowable commands. That way, /// we can avoid exposing any unnecessary parsers to a possible attacker. /// /// The restricted message type is an enum, and is declared with a syntax as follows: /// ``` /// use tor_cell::{restrict::restricted_msg, relaycell::RelayMsgOuter}; /// /// restricted_msg! { /// enum OpenStreamMsg : RelayMsg { /// Data, /// Sendme, /// End, /// _ => Unrecognized, /// } /// } /// /// type OpenStreamMsgOuter = RelayMsgOuter; /// ``` /// /// Instead of `RelayMsg`, you can say `ChanMsg` to get a restricted channel /// message. /// /// Only message variants exposed from the `tor_cell::{chan,relay}cell::msg` are /// supported. /// /// You can omit the `_ => Unrecognized` clause at the end. If you do, then any /// unexpected command types will be treated as a parse error. #[macro_export] macro_rules! restricted_msg { { $(#[$meta:meta])* $(@omit_from $omit_from:literal)? $v:vis enum $name:ident : RelayMsg { $($tt:tt)* } } => { $crate::restrict::restricted_msg!{ [ any_type: $crate::relaycell::msg::AnyRelayMsg, msg_mod: $crate::relaycell::msg, cmd_type: $crate::relaycell::RelayCmd, unrecognized: $crate::relaycell::msg::Unrecognized, body_trait: $crate::relaycell::msg::Body, msg_trait: $crate::relaycell::RelayMsg, omit_from: $($omit_from)? ] $(#[$meta])* $v enum $name { $($tt)*} } }; { $(#[$meta:meta])* $(@omit_from $omit_from:literal)? $v:vis enum $name:ident : ChanMsg { $($tt:tt)* } } => { $crate::restrict::restricted_msg!{ [ any_type: $crate::chancell::msg::AnyChanMsg, msg_mod: $crate::chancell::msg, cmd_type: $crate::chancell::ChanCmd, unrecognized: $crate::chancell::msg::Unrecognized, body_trait: $crate::chancell::msg::Body, msg_trait: $crate::chancell::ChanMsg, omit_from: $($omit_from)? ] $(#[$meta])* $v enum $name { $($tt)*} } }; { [ any_type: $any_msg:ty, msg_mod: $msg_mod:path, cmd_type: $cmd_type:ty, unrecognized: $unrec_type:ty, body_trait: $body_type:ty, msg_trait: $msg_trait:ty, omit_from: $($omit_from:literal)? ] $(#[$meta:meta])* $v:vis enum $name:ident { $( $(#[$case_meta:meta])* $([feature=$feat:literal])? $case:ident ),* $(, _ => $(#[$unrec_meta:meta])* $unrecognized:ident )? $(,)? } } => { $crate::restrict::paste::paste!{ $(#[$meta])* $v enum $name { $( $(#[$case_meta])* $( #[cfg(feature=$feat)] )? $case($msg_mod :: $case), )* $( $(#[$unrec_meta])* $unrecognized($unrec_type) )? } impl $msg_trait for $name { fn cmd(&self) -> $cmd_type { match self { $( $( #[cfg(feature=$feat)] )? Self::$case(_) => $cmd_type:: [<$case:snake:upper>] , )* $( Self::$unrecognized(u) => u.cmd(), )? } } fn encode_onto(self, w: &mut W) -> $crate::restrict::tor_bytes::EncodeResult<()> where W: $crate::restrict::tor_bytes::Writer + ?Sized { match self { $( $( #[cfg(feature=$feat)] )? Self::$case(m) => $body_type::encode_onto(m, w), )* $( Self::$unrecognized(u) => $body_type::encode_onto(u, w), )? } } fn decode_from_reader(cmd: $cmd_type, r: &mut $crate::restrict::tor_bytes::Reader<'_>) -> $crate::restrict::tor_bytes::Result { Ok(match cmd { $( $( #[cfg(feature=$feat)] )? $cmd_type:: [<$case:snake:upper>] => Self::$case( <$msg_mod :: $case as $body_type> :: decode_from_reader(r)? ), )* $( _ => Self::$unrecognized($unrec_type::decode_with_cmd(cmd, r)?), )? #[allow(unreachable_patterns)] // This is unreachable if we had an Unrecognized variant above. _ => return Err($crate::restrict::tor_bytes::Error::InvalidMessage( format!("Unexpected command {} in {}", cmd, stringify!($name)).into() )), }) } } #[allow(unexpected_cfgs)] const _: () = { $( #[cfg(feature = $omit_from)] )? impl From<$name> for $any_msg { fn from(msg: $name) -> $any_msg { match msg { $( $( #[cfg(feature=$feat)] )? $name::$case(b) => Self::$case(b), )* $( $name::$unrecognized(u) => $any_msg::Unrecognized(u), )? } } } }; #[allow(unexpected_cfgs)] const _: () = { $( #[cfg(feature = $omit_from)] )? impl TryFrom<$any_msg> for $name { type Error = $any_msg; fn try_from(msg: $any_msg) -> std::result::Result<$name, $any_msg> { Ok(match msg { $( $( #[cfg(feature=$feat)] )? $any_msg::$case(b) => $name::$case(b), )* $( $any_msg::Unrecognized(u) => Self::$unrecognized(u), )? #[allow(unreachable_patterns)] other => return Err(other), }) } } }; $( $( #[cfg(feature=$feat)] )? impl From<$msg_mod :: $case> for $name { fn from(m: $msg_mod::$case) -> $name { $name :: $case(m) } } )* $( impl From<$unrec_type> for $name { fn from (u: $unrec_type) -> $name { $name::$unrecognized(u) } } )? } } } pub use restricted_msg; #[cfg(test)] mod test { use super::*; // Here we do a couple of other variations of the example in the doctest, to // make sure they work. // As in the doctest, but no "unrecognized" variant. restricted_msg! { enum StrictOpenStreamMsg : RelayMsg { Data, Sendme, End, } } // Try it with chanmsg. restricted_msg! { enum CircuitBuildReply : ChanMsg { Created, Created2, CreatedFast, Destroy, _ => Unrecognized, } } // As above, but no "unrecognized" variant. restricted_msg! { enum StrictCircuitBuildReply : ChanMsg { Created, Created2, CreatedFast, Destroy, } } }