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}