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}