Skip to main content

tor_cell/
restrict.rs

1//! Declare a restricted variant of our message types.
2
3/// Re-export tor_bytes and paste here, so that the macro can use it.
4pub use {paste, tor_bytes};
5
6/// Declare a restricted version of
7/// [`AnyRelayMsg`](crate::relaycell::msg::AnyRelayMsg) or
8/// [`AnyChanMsg`](crate::chancell::msg::AnyChanMsg).
9///
10/// Frequently we only want to handle a subset of the possible channel or relay
11/// commands that we might see.  In those situations, it makes sense to define a
12/// a message types that will only try to parse the allowable commands.  That way,
13/// we can avoid exposing any unnecessary parsers to a possible attacker.
14///
15/// The restricted message type is an enum, and is declared with a syntax as follows:
16/// ```
17/// use tor_cell::{restrict::restricted_msg, relaycell::RelayMsgOuter};
18///
19/// restricted_msg! {
20///     enum OpenStreamMsg : RelayMsg {
21///         Data,
22///         Sendme,
23///         End,
24///         _ => Unrecognized,
25///    }
26/// }
27///
28/// type OpenStreamMsgOuter = RelayMsgOuter<OpenStreamMsg>;
29/// ```
30///
31/// Instead of `RelayMsg`, you can say `ChanMsg` to get a restricted channel
32/// message.
33///
34/// Only message variants exposed from the `tor_cell::{chan,relay}cell::msg` are
35/// supported.
36///
37/// You can omit the `_ => Unrecognized` clause at the end.  If you do, then any
38/// unexpected command types will be treated as a parse error.
39#[macro_export]
40macro_rules! restricted_msg {
41    {
42        $(#[$meta:meta])*
43        $(@omit_from $omit_from:literal)?
44        $v:vis enum $name:ident : RelayMsg {
45            $($tt:tt)*
46        }
47    } => {
48        $crate::restrict::restricted_msg!{
49            [
50            any_type: $crate::relaycell::msg::AnyRelayMsg,
51            msg_mod: $crate::relaycell::msg,
52            cmd_type: $crate::relaycell::RelayCmd,
53            unrecognized: $crate::relaycell::msg::Unrecognized,
54            body_trait: $crate::relaycell::msg::Body,
55            msg_trait: $crate::relaycell::RelayMsg,
56            omit_from: $($omit_from)?
57            ]
58            $(#[$meta])*
59            $v enum $name { $($tt)*}
60        }
61    };
62    {
63        $(#[$meta:meta])*
64        $(@omit_from $omit_from:literal)?
65        $v:vis enum $name:ident : ChanMsg {
66            $($tt:tt)*
67        }
68    } => {
69        $crate::restrict::restricted_msg!{
70            [
71            any_type: $crate::chancell::msg::AnyChanMsg,
72            msg_mod: $crate::chancell::msg,
73            cmd_type: $crate::chancell::ChanCmd,
74            unrecognized: $crate::chancell::msg::Unrecognized,
75            body_trait: $crate::chancell::msg::Body,
76            msg_trait: $crate::chancell::ChanMsg,
77            omit_from: $($omit_from)?
78            ]
79            $(#[$meta])*
80            $v enum $name { $($tt)*}
81        }
82    };
83    {
84        [
85          any_type: $any_msg:ty,
86          msg_mod: $msg_mod:path,
87          cmd_type: $cmd_type:ty,
88          unrecognized: $unrec_type:ty,
89          body_trait: $body_type:ty,
90          msg_trait: $msg_trait:ty,
91          omit_from: $($omit_from:literal)?
92        ]
93        $(#[$meta:meta])*
94        $v:vis enum $name:ident {
95            $(
96                $(#[$case_meta:meta])*
97                $([feature=$feat:literal])?
98                $case:ident
99            ),*
100            $(, _ =>
101                $(#[$unrec_meta:meta])*
102                $unrecognized:ident )?
103            $(,)?
104        }
105    } => {
106    $crate::restrict::paste::paste!{
107        $(#[$meta])*
108        $v enum $name {
109            $(
110                $(#[$case_meta])*
111                $( #[cfg(feature=$feat)] )?
112                $case($msg_mod :: $case),
113            )*
114            $(
115                $(#[$unrec_meta])*
116                $unrecognized($unrec_type)
117            )?
118        }
119
120        impl $msg_trait for $name {
121            fn cmd(&self) -> $cmd_type {
122                match self {
123                    $(
124                        $( #[cfg(feature=$feat)] )?
125                        Self::$case(_) => $cmd_type:: [<$case:snake:upper>] ,
126                    )*
127                    $(
128                        Self::$unrecognized(u) => u.cmd(),
129                    )?
130                }
131            }
132
133             fn encode_onto<W:>(self, w: &mut W) -> $crate::restrict::tor_bytes::EncodeResult<()>
134             where
135                W: $crate::restrict::tor_bytes::Writer + ?Sized
136             {
137                match self {
138                    $(
139                        $( #[cfg(feature=$feat)] )?
140                        Self::$case(m) => $body_type::encode_onto(m, w),
141                    )*
142                    $(
143                        Self::$unrecognized(u) => $body_type::encode_onto(u, w),
144                    )?
145                }
146            }
147
148            fn decode_from_reader(cmd: $cmd_type, r: &mut $crate::restrict::tor_bytes::Reader<'_>) -> $crate::restrict::tor_bytes::Result<Self> {
149                Ok(match cmd {
150                    $(
151                        $( #[cfg(feature=$feat)] )?
152                        $cmd_type:: [<$case:snake:upper>] => Self::$case( <$msg_mod :: $case as $body_type> :: decode_from_reader(r)? ),
153                    )*
154                    $(
155                        _ => Self::$unrecognized($unrec_type::decode_with_cmd(cmd, r)?),
156                    )?
157                    #[allow(unreachable_patterns)] // This is unreachable if we had an Unrecognized variant above.
158                    _ => return Err($crate::restrict::tor_bytes::Error::InvalidMessage(
159                        format!("Unexpected command {} in {}", cmd, stringify!($name)).into()
160                    )),
161                })
162            }
163        }
164
165        impl $crate::restrict::RestrictedMsg for $name {
166            type Cmd = $cmd_type;
167            fn cmds_for_logging() -> &'static [Self::Cmd] {
168                &[$(
169                    $( #[cfg(feature=$feat)] )?
170                    $cmd_type:: [<$case:snake:upper>],
171                )*]
172            }
173        }
174
175        #[allow(unexpected_cfgs)]
176        const _: () = {
177            $(
178                #[cfg(feature = $omit_from)]
179            )?
180            impl From<$name> for $any_msg {
181                fn from(msg: $name) -> $any_msg {
182                    match msg {
183                        $(
184                            $( #[cfg(feature=$feat)] )?
185                            $name::$case(b) => Self::$case(b),
186                        )*
187                        $(
188                            $name::$unrecognized(u) => $any_msg::Unrecognized(u),
189                        )?
190                    }
191                }
192            }
193        };
194
195        #[allow(unexpected_cfgs)]
196        const _: () = {
197            $(
198                #[cfg(feature = $omit_from)]
199            )?
200            impl TryFrom<$any_msg> for $name {
201                type Error = $any_msg;
202                fn try_from(msg: $any_msg) -> std::result::Result<$name, $any_msg> {
203                    Ok(match msg {
204                        $(
205                            $( #[cfg(feature=$feat)] )?
206                            $any_msg::$case(b) => $name::$case(b),
207                        )*
208                        $(
209                            $any_msg::Unrecognized(u) => Self::$unrecognized(u),
210                        )?
211                        #[allow(unreachable_patterns)]
212                        other => return Err(other),
213                    })
214                }
215            }
216        };
217
218        $(
219            $( #[cfg(feature=$feat)] )?
220            impl From<$msg_mod :: $case> for $name {
221                fn from(m: $msg_mod::$case) -> $name {
222                    $name :: $case(m)
223                }
224            }
225        )*
226        $(
227            impl From<$unrec_type> for $name {
228                fn from (u: $unrec_type) -> $name {
229                    $name::$unrecognized(u)
230                }
231            }
232        )?
233    }
234    }
235}
236
237pub use restricted_msg;
238
239/// Additional functionality for a restricted message set.
240///
241/// This is typically implemented using [`restricted_msg`].
242pub trait RestrictedMsg {
243    /// The type of cell. Typically [`ChanCmd`](crate::chancell::ChanCmd) or
244    /// [`RelayCmd`](crate::relaycell::RelayCmd).
245    type Cmd: Copy + Clone + std::fmt::Debug + std::fmt::Display + Eq + PartialEq + 'static;
246
247    /// The set of cell commands represented by this restricted message set.
248    ///
249    /// This isn't necessarily exhaustive
250    /// and doesn't always include all cells types that this message set can hold.
251    /// For example [`AnyChanMsg`](crate::chancell::msg::AnyChanMsg) also supports unrecognized cells,
252    /// which aren't represented in this list.
253    ///
254    /// This is intended for debugging purposes,
255    /// so that we can list what commands we are expecting in error messages.
256    ///
257    /// **NOTE:** This list is *not* intended to be used for filtering cells or performing any kind
258    /// of validation.
259    ///
260    /// Implementers should ensure that the returned list does not contain duplicate values.
261    fn cmds_for_logging() -> &'static [Self::Cmd];
262}
263
264#[cfg(test)]
265mod test {
266    // Here we do a couple of other variations of the example in the doctest, to
267    // make sure they work.
268
269    // As in the doctest, but no "unrecognized" variant.
270    restricted_msg! {
271        enum StrictOpenStreamMsg : RelayMsg {
272            Data,
273            Sendme,
274            End,
275       }
276    }
277
278    // Try it with chanmsg.
279    restricted_msg! {
280        enum CircuitBuildReply : ChanMsg {
281            Created,
282            Created2,
283            CreatedFast,
284            Destroy,
285            _ => Unrecognized,
286       }
287    }
288
289    // As above, but no "unrecognized" variant.
290    restricted_msg! {
291        enum StrictCircuitBuildReply : ChanMsg {
292            Created,
293            Created2,
294            CreatedFast,
295            Destroy,
296       }
297    }
298}