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