Skip to main content

tor_netdoc/types/
relay_flags.rs

1//! Relay flags (aka Router Status Flags), eg in network status documents
2
3use std::collections::HashSet;
4use std::fmt::Debug;
5use std::marker::PhantomData;
6use std::str::FromStr;
7
8use enumset::{EnumSet, EnumSetType, enum_set};
9use thiserror::Error;
10
11use tor_error::internal;
12
13use super::Unknown;
14
15/// Raw bits value for [`RelayFlags`]
16pub type RelayFlagsBits = u16;
17
18/// Router flags (aka relay flags), including, maybe, unknown ones
19///
20/// ### PartialEq implementation
21///
22/// `DocRelayFlags` implements `PartialEq`.
23///
24/// Two `DocRelayFlags` which both omit unknown flags (ie, contain `Unknown::Discarded`)
25/// are treated as equal if they contain the same set of *known* flags.
26/// This makes sense, because applications (like clients) that discard flags during netdoc
27/// parsing *want* to completely ignore unknown flags, and want to have a working comparison
28/// function for relay flags (eg to tell if two relays are similar enough).
29///
30/// Two `RelayFlags` only *one* of which retained unknown flags are treated as unequal.
31/// Such a comparison is probably a bug, but panicking would be worse.
32#[derive(Debug, Clone, derive_more::Deref, PartialEq)]
33#[non_exhaustive]
34pub struct DocRelayFlags {
35    /// Known flags
36    ///
37    /// Invariant: contains no unknown set bits.
38    #[deref]
39    pub known: RelayFlags,
40
41    /// Unknown flags, if they were parsed
42    ///
43    /// Not sorted.
44    pub unknown: Unknown<HashSet<String>>,
45}
46
47/// Additional options for the representation of relay flags in network documents
48///
49/// This is a generic argument to `ParserEncoder`
50/// (and will be used for the encoder too).
51pub trait ReprMode: Debug + Copy {
52    /// Flags that should be treated as being present when parsing
53    ///
54    /// Ie, they should be inferred even if they aren't actually listed in the document.
55    ///
56    /// But, when encoding, they should still be emitted.
57    const PARSE_IMPLICIT: RelayFlags;
58
59    /// Flags that should be treated as being present, and won't even be encoded.
60    ///
61    /// These are inferred when parsing, and omitted when encoding.
62    ///
63    /// (During parsing `ENCODE_OMIT` and `PARSE_IMPLICIT` flags are treated the same.)
64    const ENCODE_OMIT: RelayFlags;
65}
66
67/// How relay flags are represented in `s` in a consensus
68#[derive(Debug, Copy, Clone)]
69#[allow(clippy::exhaustive_structs)]
70pub struct ConsensusRepr;
71
72impl ReprMode for ConsensusRepr {
73    const PARSE_IMPLICIT: RelayFlags = enum_set!(RelayFlag::Running | RelayFlag::Valid);
74    const ENCODE_OMIT: RelayFlags = RelayFlags::empty();
75}
76
77/// How relay flags are represented in `s` in a vote and a `known-flags` line
78#[derive(Debug, Copy, Clone)]
79#[allow(clippy::exhaustive_structs)]
80pub struct NoImplicitRepr;
81
82impl ReprMode for NoImplicitRepr {
83    const PARSE_IMPLICIT: RelayFlags = RelayFlags::empty();
84    const ENCODE_OMIT: RelayFlags = RelayFlags::empty();
85}
86
87/// Set of (known) router status flags
88///
89/// Set of [`RelayFlag`], in a cheap and compact representation.
90///
91/// Can contain only flags known to this implementation.
92/// This is a newtype around a machine integer.
93///
94/// Does not implement `ItemValueParseable`.  Parsing (and encoding) is different in
95/// different documents.  Use an appropriate parameterised [`ParserEncoder`],
96/// in `#[deftly(netdoc(with))]`.
97///
98/// To also maybe handle unknown flags, use [`DocRelayFlags`].
99///
100/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:s>
101pub type RelayFlags = EnumSet<RelayFlag>;
102
103/// Router status flags - one recognized directory flag on a single relay.
104///
105/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:s>
106///
107/// These flags come from a consensus directory document, and are
108/// used to describe what the authorities believe about the relay.
109/// If the document contained any flags that we _didn't_ recognize,
110/// they are not listed in this type.
111///
112/// TODO SPEC: Make the terminology the same everywhere.
113#[derive(Debug, strum::Display, strum::EnumString, strum::IntoStaticStr, EnumSetType)] //
114#[derive(Hash, Ord, PartialOrd)]
115#[enumset(repr = "u16")] // Must be the same as RelayFlagBits
116#[non_exhaustive]
117pub enum RelayFlag {
118    /// Is this a directory authority?
119    Authority,
120    /// Is this relay marked as a bad exit?
121    ///
122    /// Bad exits can be used as intermediate relays, but not to
123    /// deliver traffic.
124    BadExit,
125    /// Is this relay marked as an exit for weighting purposes?
126    Exit,
127    /// Is this relay considered "fast" above a certain threshold?
128    Fast,
129    /// Is this relay suitable for use as a guard relay?
130    ///
131    /// Clients choose their their initial relays from among the set
132    /// of Guard relays.
133    Guard,
134    /// Does this relay participate on the onion service directory
135    /// ring?
136    HSDir,
137    /// Set if this relay is considered "middle only", not suitable to run
138    /// as an exit or guard relay.
139    ///
140    /// Note that this flag is only used by authorities as part of
141    /// the voting process; clients do not and should not act
142    /// based on whether it is set.
143    MiddleOnly,
144    /// If set, there is no consensus for the ed25519 key for this relay.
145    NoEdConsensus,
146    /// Is this relay considered "stable" enough for long-lived circuits?
147    Stable,
148    /// Set if the authorities are requesting a fresh descriptor for
149    /// this relay.
150    StaleDesc,
151    /// Set if this relay is currently running.
152    ///
153    /// This flag can appear in votes, but in consensuses, every relay
154    /// is assumed to be running.
155    Running,
156    /// Set if this relay is considered "valid" -- allowed to be on
157    /// the network.
158    ///
159    /// This flag can appear in votes, but in consensuses, every relay
160    /// is assumed to be valid.
161    Valid,
162    /// Set if this relay supports a currently recognized version of the
163    /// directory protocol.
164    V2Dir,
165}
166
167/// Parsing helper for a relay flags line (eg `s` item in a routerdesc)
168///
169#[derive(Debug, Clone)]
170pub struct ParserEncoder<'s, M: ReprMode> {
171    /// Flags so far, including the implied ones
172    flags: DocRelayFlags,
173
174    /// The previous argument, if any
175    ///
176    /// Used only for checking that the arguments are sorted, as per the spec.
177    prev: Option<&'s str>,
178
179    /// The mode, which is just a type token
180    repr_mode: PhantomData<M>,
181}
182
183/// Problem parsing a relay flags line
184#[derive(Error, Debug, Clone)]
185#[non_exhaustive]
186pub enum RelayFlagsParseError {
187    /// Flags were not in lexical order by flag name
188    #[error("Flags out of order")]
189    OutOfOrder,
190}
191
192impl DocRelayFlags {
193    /// Create a new `DocRelayFlags` with no known flags and no information about unknown flags
194    pub fn new_empty_unknown_discarded() -> Self {
195        DocRelayFlags {
196            known: RelayFlags::default(),
197            unknown: Unknown::new_discard(),
198        }
199    }
200}
201
202impl<'s, M: ReprMode> ParserEncoder<'s, M> {
203    /// Start parsing relay flags
204    ///
205    /// If `PARSE_IMPLICIT` or `ENCODE_OMIT` contains unknown bits, compile will fail.
206    pub fn new(unknown: Unknown<()>) -> Self {
207        let known = M::PARSE_IMPLICIT | M::ENCODE_OMIT;
208        ParserEncoder {
209            flags: DocRelayFlags {
210                known,
211                unknown: unknown.map(|()| HashSet::new()),
212            },
213            prev: None,
214            repr_mode: PhantomData,
215        }
216    }
217    /// Parse the next relay flag argument
218    pub fn add(&mut self, arg: &'s str) -> Result<(), RelayFlagsParseError> {
219        if let Some(prev) = self.prev {
220            if prev >= arg {
221                // Arguments out of order.
222                return Err(RelayFlagsParseError::OutOfOrder);
223            }
224        }
225        match RelayFlag::from_str(arg) {
226            Ok(fl) => self.flags.known |= fl,
227            Err(_) => self.flags.unknown.with_mut_unknown(|u| {
228                u.insert(arg.to_string());
229            }),
230        }
231
232        self.prev = Some(arg);
233        Ok(())
234    }
235    /// Finish parsing relay flags
236    pub fn finish(self) -> DocRelayFlags {
237        self.flags
238    }
239}
240
241/// Old parser impl
242mod parse_impl {
243    use super::*;
244    use crate::doc::netstatus::NetstatusKwd;
245    use crate::parse::tokenize::Item;
246    use crate::{Error, NetdocErrorKind as EK, Result};
247
248    impl DocRelayFlags {
249        /// Parse a relay-flags entry from an "s" line.
250        pub(crate) fn from_item_consensus(item: &Item<'_, NetstatusKwd>) -> Result<DocRelayFlags> {
251            if item.kwd() != NetstatusKwd::RS_S {
252                return Err(
253                    Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
254                        .at_pos(item.pos()),
255                );
256            }
257            let mut flags = ParserEncoder::<ConsensusRepr>::new(Unknown::new_discard());
258
259            for s in item.args() {
260                flags
261                    .add(s)
262                    .map_err(|msg| EK::BadArgument.at_pos(item.pos()).with_msg(msg.to_string()))?;
263            }
264
265            Ok(flags.finish())
266        }
267    }
268}
269
270/// New parser impl
271mod parse2_impl {
272    use super::*;
273
274    use crate::parse2::{self, ErrorProblem as EP};
275    use {
276        crate::encode::ItemEncoder, itertools::chain, std::collections::BTreeSet, tor_error::Bug,
277    };
278
279    impl<'s, M: ReprMode> ParserEncoder<'s, M> {
280        /// Parse relay flags
281        #[allow(clippy::needless_pass_by_value)] // we must match trait signature
282        pub(crate) fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<DocRelayFlags, EP> {
283            item.check_no_object()?;
284            let mut flags = Self::new(item.parse_options().retain_unknown_values);
285            for arg in item.args_copy() {
286                flags
287                    .add(arg)
288                    .map_err(item.invalid_argument_handler("flags"))?;
289            }
290            Ok(flags.finish())
291        }
292
293        /// Encode relay flags
294        #[allow(clippy::unnecessary_wraps)] // we must match trait signature
295        #[allow(clippy::redundant_closure)] // rust-clippy/issues#14215 |f| <&'static str>::from(f)
296        pub(crate) fn write_item_value_onto(
297            flags: &DocRelayFlags,
298            mut out: ItemEncoder,
299        ) -> Result<(), Bug> {
300            let set = chain!(
301                flags.known.iter().map(|f| <&'static str>::from(f)),
302                flags
303                    .unknown
304                    .as_ref()
305                    .only_known()
306                    .map(|u| u.iter())
307                    .into_iter()
308                    .flatten()
309                    .map(|s: &String| &**s),
310            )
311            .collect::<BTreeSet<&'_ str>>();
312
313            for f in set {
314                out = out.arg(&f);
315            }
316            Ok(())
317        }
318    }
319}