tor_netdoc/doc/netstatus/rs/
each_flavor.rs

1//! router status entries - items that vary by consensus flavor
2//!
3//! **This file is reincluded multiple times**,
4//! by the macros in [`crate::doc::ns_variety_definition_macros`],
5//! once for votes, and once for each consensus flavour.
6//! It is *not* a module `crate::doc::netstatus::rs::each_flavor`.
7//!
8//! Each time this file is included by one of the macros mentioned above,
9//! the `ns_***` macros (such as `ns_const_name!`) may expand to different values.
10//!
11//! See [`crate::doc::ns_variety_definition_macros`].
12
13use super::*;
14
15impl RouterStatus {
16    /// Return an iterator of ORPort addresses for this routerstatus
17    pub fn addrs(&self) -> impl Iterator<Item = net::SocketAddr> {
18        chain!(
19            [std::net::SocketAddrV4::new(self.r.ip, self.r.or_port).into()],
20            self.a.iter().copied(),
21        )
22    }
23    /// Return the declared weight of this routerstatus in the directory.
24    pub fn weight(&self) -> &RelayWeight {
25        &self.weight
26    }
27    /// Return the protovers that this routerstatus says it implements.
28    pub fn protovers(&self) -> &Protocols {
29        &self.protos
30    }
31    /// Return the nickname of this routerstatus.
32    pub fn nickname(&self) -> &str {
33        self.r.nickname.as_str()
34    }
35    /// Return the relay flags of this routerstatus.
36    pub fn flags(&self) -> &RelayFlags {
37        &self.flags.known
38    }
39    /// Return the version of this routerstatus.
40    pub fn version(&self) -> Option<&crate::doc::netstatus::rs::Version> {
41        self.version.as_ref()
42    }
43    /// Return true if the ed25519 identity on this relay reflects a
44    /// true consensus among the authorities.
45    pub fn ed25519_id_is_usable(&self) -> bool {
46        !self.flags.contains(RelayFlag::NoEdConsensus)
47    }
48    /// Return true if this routerstatus is listed with the BadExit flag.
49    pub fn is_flagged_bad_exit(&self) -> bool {
50        self.flags.contains(RelayFlag::BadExit)
51    }
52    /// Return true if this routerstatus is listed with the v2dir flag.
53    pub fn is_flagged_v2dir(&self) -> bool {
54        self.flags.contains(RelayFlag::V2Dir)
55    }
56    /// Return true if this routerstatus is listed with the Exit flag.
57    pub fn is_flagged_exit(&self) -> bool {
58        self.flags.contains(RelayFlag::Exit)
59    }
60    /// Return true if this routerstatus is listed with the Guard flag.
61    pub fn is_flagged_guard(&self) -> bool {
62        self.flags.contains(RelayFlag::Guard)
63    }
64    /// Return true if this routerstatus is listed with the HSDir flag.
65    pub fn is_flagged_hsdir(&self) -> bool {
66        self.flags.contains(RelayFlag::HSDir)
67    }
68    /// Return true if this routerstatus is listed with the Stable flag.
69    pub fn is_flagged_stable(&self) -> bool {
70        self.flags.contains(RelayFlag::Stable)
71    }
72    /// Return true if this routerstatus is listed with the Fast flag.
73    pub fn is_flagged_fast(&self) -> bool {
74        self.flags.contains(RelayFlag::Fast)
75    }
76    /// Return true if this routerstatus is listed with the MiddleOnly flag.
77    pub fn is_flagged_middle_only(&self) -> bool {
78        self.flags.contains(RelayFlag::MiddleOnly)
79    }
80}
81
82impl RouterStatus {
83    /// Return RSA identity for the relay described by this RouterStatus
84    pub fn rsa_identity(&self) -> &RsaIdentity {
85        &self.r.identity
86    }
87
88    /// Return the networkstatus consensus flavor in which this
89    /// routerstatus appears.
90    pub(crate) fn flavor() -> ConsensusFlavor {
91        FLAVOR
92    }
93
94    /// Parse a generic routerstatus from a section.
95    ///
96    /// Requires that the section obeys the right SectionRules,
97    /// matching `consensus_flavor`.
98    pub(crate) fn from_section(
99        sec: &Section<'_, NetstatusKwd>,
100    ) -> Result<RouterStatus> {
101        use NetstatusKwd::*;
102        // R line
103        let r_item = sec.required(RS_R)?;
104        let nickname = r_item.required_arg(0)?.parse()?;
105        let ident = r_item.required_arg(1)?;
106        let identity = ident.parse::<Base64Fingerprint>()?;
107        // Fields to skip in the "r" line.
108        let n_skip = match FLAVOR {
109            ConsensusFlavor::Microdesc => 0,
110            ConsensusFlavor::Plain => 1,
111        };
112        // We check that the published time is well-formed, but we never use it
113        // for anything in a consensus document.
114        let _ignore_published: time::SystemTime = {
115            // TODO: It's annoying to have to do this allocation, since we
116            // already have a slice that contains both of these arguments.
117            // Instead, we could get a slice of arguments: we'd have to add
118            // a feature for that.
119            let mut p = r_item.required_arg(2 + n_skip)?.to_string();
120            p.push(' ');
121            p.push_str(r_item.required_arg(3 + n_skip)?);
122            p.parse::<Iso8601TimeSp>()?.into()
123        };
124        let ip = r_item.required_arg(4 + n_skip)?.parse::<net::Ipv4Addr>()?;
125        let or_port = r_item.required_arg(5 + n_skip)?.parse::<u16>()?;
126        let _ = r_item.required_arg(6 + n_skip)?.parse::<u16>()?;
127
128        // main address and A lines.
129        let a_items = sec.slice(RS_A);
130        let a = a_items.iter().map(|a_item| {
131            Ok(a_item.required_arg(0)?.parse::<net::SocketAddr>()?)
132        }).collect::<Result<Vec<_>>>()?;
133
134        // S line
135        //
136        // Wrong for votes, but this code doesn't run for votes.
137        let flags = DocRelayFlags::from_item_consensus(sec.required(RS_S)?)?;
138
139        // V line
140        let version = sec.maybe(RS_V).args_as_str().map(str::parse).transpose()?;
141
142        // PR line
143        let protos = {
144            let tok = sec.required(RS_PR)?;
145            doc::PROTOVERS_CACHE.intern(
146                tok.args_as_str()
147                    .parse::<Protocols>()
148                    .map_err(|e| EK::BadArgument.at_pos(tok.pos()).with_source(e))?,
149            )
150        };
151
152        // W line
153        let weight = sec
154            .get(RS_W)
155            .map(RelayWeight::from_item)
156            .transpose()?
157            .unwrap_or_default();
158
159        // No p line
160        // no ID line
161
162        // Try to find the document digest.  This is in different
163        // places depending on the kind of consensus we're in.
164        let doc_digest: DocDigest = match FLAVOR {
165            ConsensusFlavor::Microdesc => {
166                // M line
167                let m_item = sec.required(RS_M)?;
168                DocDigest::decode(m_item.required_arg(0)?)?
169            }
170            ConsensusFlavor::Plain => DocDigest::decode(r_item.required_arg(2)?)?,
171        };
172
173        ns_choose! { (
174            let r_doc_digest = doc_digest;
175            let m_doc_digest = NotPresent;
176        ) (
177            let r_doc_digest = NotPresent;
178            let m_doc_digest = doc_digest;
179        ) (
180            let r_doc_digest = doc_digest;
181            let m_doc_digest = NotPresent;
182        ) };
183
184        Ok(RouterStatus {
185            r: RouterStatusIntroItem {
186                nickname,
187                identity,
188                or_port,
189                doc_digest: r_doc_digest,
190                publication: IgnoredPublicationTimeSp,
191                ip,
192            },
193            m: m_doc_digest,
194            a,
195            flags,
196            version,
197            protos,
198            weight,
199        })
200    }
201}
202
203impl FromRsString for DocDigest {
204    fn decode(s: &str) -> Result<DocDigest> {
205        s.parse::<B64>()?
206            .check_len(DOC_DIGEST_LEN..=DOC_DIGEST_LEN)?
207            .as_bytes()
208            .try_into()
209            .map_err(|_| Error::from(internal!("correct length on digest, but unable to convert")))
210    }
211}