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}