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#[enumset(repr = "u16")] // Must be the same as RelayFlagBits
115#[non_exhaustive]
116pub enum RelayFlag {
117    /// Is this a directory authority?
118    Authority,
119    /// Is this relay marked as a bad exit?
120    ///
121    /// Bad exits can be used as intermediate relays, but not to
122    /// deliver traffic.
123    BadExit,
124    /// Is this relay marked as an exit for weighting purposes?
125    Exit,
126    /// Is this relay considered "fast" above a certain threshold?
127    Fast,
128    /// Is this relay suitable for use as a guard relay?
129    ///
130    /// Clients choose their their initial relays from among the set
131    /// of Guard relays.
132    Guard,
133    /// Does this relay participate on the onion service directory
134    /// ring?
135    HSDir,
136    /// Set if this relay is considered "middle only", not suitable to run
137    /// as an exit or guard relay.
138    ///
139    /// Note that this flag is only used by authorities as part of
140    /// the voting process; clients do not and should not act
141    /// based on whether it is set.
142    MiddleOnly,
143    /// If set, there is no consensus for the ed25519 key for this relay.
144    NoEdConsensus,
145    /// Is this relay considered "stable" enough for long-lived circuits?
146    Stable,
147    /// Set if the authorities are requesting a fresh descriptor for
148    /// this relay.
149    StaleDesc,
150    /// Set if this relay is currently running.
151    ///
152    /// This flag can appear in votes, but in consensuses, every relay
153    /// is assumed to be running.
154    Running,
155    /// Set if this relay is considered "valid" -- allowed to be on
156    /// the network.
157    ///
158    /// This flag can appear in votes, but in consensuses, every relay
159    /// is assumed to be valid.
160    Valid,
161    /// Set if this relay supports a currently recognized version of the
162    /// directory protocol.
163    V2Dir,
164}
165
166/// Parsing helper for a relay flags line (eg `s` item in a routerdesc)
167///
168#[derive(Debug, Clone)]
169pub struct ParserEncoder<'s, M: ReprMode> {
170    /// Flags so far, including the implied ones
171    flags: DocRelayFlags,
172
173    /// The previous argument, if any
174    ///
175    /// Used only for checking that the arguments are sorted, as per the spec.
176    prev: Option<&'s str>,
177
178    /// The mode, which is just a type token
179    repr_mode: PhantomData<M>,
180}
181
182/// Problem parsing a relay flags line
183#[derive(Error, Debug, Clone)]
184#[non_exhaustive]
185pub enum RelayFlagsParseError {
186    /// Flags were not in lexical order by flag name
187    #[error("Flags out of order")]
188    OutOfOrder,
189}
190
191impl DocRelayFlags {
192    /// Create a new `DocRelayFlags` with no known flags and no information about unknown flags
193    pub fn new_empty_unknown_discarded() -> Self {
194        DocRelayFlags {
195            known: RelayFlags::default(),
196            unknown: Unknown::new_discard(),
197        }
198    }
199}
200
201impl<'s, M: ReprMode> ParserEncoder<'s, M> {
202    /// Start parsing relay flags
203    ///
204    /// If `PARSE_IMPLICIT` or `ENCODE_OMIT` contains unknown bits, compile will fail.
205    pub fn new(unknown: Unknown<()>) -> Self {
206        let known = M::PARSE_IMPLICIT | M::ENCODE_OMIT;
207        ParserEncoder {
208            flags: DocRelayFlags {
209                known,
210                unknown: unknown.map(|()| HashSet::new()),
211            },
212            prev: None,
213            repr_mode: PhantomData,
214        }
215    }
216    /// Parse the next relay flag argument
217    pub fn add(&mut self, arg: &'s str) -> Result<(), RelayFlagsParseError> {
218        if let Some(prev) = self.prev {
219            if prev >= arg {
220                // Arguments out of order.
221                return Err(RelayFlagsParseError::OutOfOrder);
222            }
223        }
224        match RelayFlag::from_str(arg) {
225            Ok(fl) => self.flags.known |= fl,
226            Err(_) => self.flags.unknown.with_mut_unknown(|u| {
227                u.insert(arg.to_string());
228            }),
229        }
230
231        self.prev = Some(arg);
232        Ok(())
233    }
234    /// Finish parsing relay flags
235    pub fn finish(self) -> DocRelayFlags {
236        self.flags
237    }
238}
239
240/// Old parser impl
241mod parse_impl {
242    use super::*;
243    use crate::doc::netstatus::NetstatusKwd;
244    use crate::parse::tokenize::Item;
245    use crate::{Error, NetdocErrorKind as EK, Result};
246
247    impl DocRelayFlags {
248        /// Parse a relay-flags entry from an "s" line.
249        pub(crate) fn from_item_consensus(item: &Item<'_, NetstatusKwd>) -> Result<DocRelayFlags> {
250            if item.kwd() != NetstatusKwd::RS_S {
251                return Err(
252                    Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
253                        .at_pos(item.pos()),
254                );
255            }
256            let mut flags = ParserEncoder::<ConsensusRepr>::new(Unknown::new_discard());
257
258            for s in item.args() {
259                flags
260                    .add(s)
261                    .map_err(|msg| EK::BadArgument.at_pos(item.pos()).with_msg(msg.to_string()))?;
262            }
263
264            Ok(flags.finish())
265        }
266    }
267}
268
269/// New parser impl
270mod parse2_impl {
271    use super::*;
272
273    use crate::parse2::{self, ErrorProblem as EP};
274    use {
275        crate::encode::ItemEncoder, itertools::chain, std::collections::BTreeSet, tor_error::Bug,
276    };
277
278    impl<'s, M: ReprMode> ParserEncoder<'s, M> {
279        /// Parse relay flags
280        #[allow(clippy::needless_pass_by_value)] // we must match trait signature
281        pub(crate) fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<DocRelayFlags, EP> {
282            item.check_no_object()?;
283            let mut flags = Self::new(item.parse_options().retain_unknown_values);
284            for arg in item.args_copy() {
285                flags
286                    .add(arg)
287                    .map_err(item.invalid_argument_handler("flags"))?;
288            }
289            Ok(flags.finish())
290        }
291
292        /// Encode relay flags
293        #[allow(clippy::unnecessary_wraps)] // we must match trait signature
294        #[allow(clippy::redundant_closure)] // rust-clippy/issues#14215 |f| <&'static str>::from(f)
295        pub(crate) fn write_item_value_onto(
296            flags: &DocRelayFlags,
297            mut out: ItemEncoder,
298        ) -> Result<(), Bug> {
299            let set = chain!(
300                flags.known.iter().map(|f| <&'static str>::from(f)),
301                flags
302                    .unknown
303                    .as_ref()
304                    .only_known()
305                    .map(|u| u.iter())
306                    .into_iter()
307                    .flatten()
308                    .map(|s: &String| &**s),
309            )
310            .collect::<BTreeSet<&'_ str>>();
311
312            for f in set {
313                out = out.arg(&f);
314            }
315            Ok(())
316        }
317    }
318}