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.
4
pub 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]
40
macro_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
1110489
            fn cmd(&self) -> $cmd_type {
122
1110489
                match self {
123
                    $(
124
                        $( #[cfg(feature=$feat)] )?
125
                        Self::$case(_) => $cmd_type:: [<$case:snake:upper>] ,
126
                    )*
127
                    $(
128
285
                        Self::$unrecognized(u) => u.cmd(),
129
                    )?
130
                }
131
1110489
            }
132

            
133
5568
             fn encode_onto<W:>(self, w: &mut W) -> $crate::restrict::tor_bytes::EncodeResult<()>
134
5568
             where
135
5568
                W: $crate::restrict::tor_bytes::Writer + ?Sized
136
             {
137
5568
                match self {
138
                    $(
139
                        $( #[cfg(feature=$feat)] )?
140
192
                        Self::$case(m) => $body_type::encode_onto(m, w),
141
                    )*
142
                    $(
143
16
                        Self::$unrecognized(u) => $body_type::encode_onto(u, w),
144
                    )?
145
                }
146
5568
            }
147

            
148
123531
            fn decode_from_reader(cmd: $cmd_type, r: &mut $crate::restrict::tor_bytes::Reader<'_>) -> $crate::restrict::tor_bytes::Result<Self> {
149
123531
                Ok(match cmd {
150
                    $(
151
                        $( #[cfg(feature=$feat)] )?
152
3052
                        $cmd_type:: [<$case:snake:upper>] => Self::$case( <$msg_mod :: $case as $body_type> :: decode_from_reader(r)? ),
153
                    )*
154
                    $(
155
285
                        _ => 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
2
                    _ => return Err($crate::restrict::tor_bytes::Error::InvalidMessage(
159
2
                        format!("Unexpected command {} in {}", cmd, stringify!($name)).into()
160
2
                    )),
161
                })
162
123531
            }
163
        }
164

            
165
        impl $crate::restrict::RestrictedMsg for $name {
166
            type Cmd = $cmd_type;
167
6
            fn cmds_for_logging() -> &'static [Self::Cmd] {
168
6
                &[$(
169
6
                    $( #[cfg(feature=$feat)] )?
170
6
                    $cmd_type:: [<$case:snake:upper>],
171
6
                )*]
172
6
            }
173
        }
174

            
175
        #[allow(unexpected_cfgs)]
176
        const _: () = {
177
            $(
178
                #[cfg(feature = $omit_from)]
179
            )?
180
            impl From<$name> for $any_msg {
181
208
                fn from(msg: $name) -> $any_msg {
182
208
                    match msg {
183
                        $(
184
                            $( #[cfg(feature=$feat)] )?
185
70
                            $name::$case(b) => Self::$case(b),
186
                        )*
187
                        $(
188
                            $name::$unrecognized(u) => $any_msg::Unrecognized(u),
189
                        )?
190
                    }
191
208
                }
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
223
                fn try_from(msg: $any_msg) -> std::result::Result<$name, $any_msg> {
203
223
                    Ok(match msg {
204
                        $(
205
                            $( #[cfg(feature=$feat)] )?
206
8
                            $any_msg::$case(b) => $name::$case(b),
207
                        )*
208
                        $(
209
                            $any_msg::Unrecognized(u) => Self::$unrecognized(u),
210
                        )?
211
                        #[allow(unreachable_patterns)]
212
6
                        other => return Err(other),
213
                    })
214
223
                }
215
            }
216
        };
217

            
218
        $(
219
            $( #[cfg(feature=$feat)] )?
220
            impl From<$msg_mod :: $case> for $name {
221
163932
                fn from(m: $msg_mod::$case) -> $name {
222
163932
                    $name :: $case(m)
223
163932
                }
224
            }
225
        )*
226
        $(
227
            impl From<$unrec_type> for $name {
228
228
                fn from (u: $unrec_type) -> $name {
229
228
                    $name::$unrecognized(u)
230
228
                }
231
            }
232
        )?
233
    }
234
    }
235
}
236

            
237
pub use restricted_msg;
238

            
239
/// Additional functionality for a restricted message set.
240
///
241
/// This is typically implemented using [`restricted_msg`].
242
pub 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)]
265
mod 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
}