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

            
13
use super::*;
14
use void::ResultVoidExt as _;
15

            
16
impl RouterStatus {
17
    /// Return an iterator of ORPort addresses for this routerstatus
18
152643659
    pub fn addrs(&self) -> impl Iterator<Item = net::SocketAddr> {
19
152643659
        chain!(
20
152643659
            [std::net::SocketAddrV4::new(self.r.ip, self.r.or_port).into()],
21
152643659
            self.a.iter().copied(),
22
        )
23
152643659
    }
24
    /// Return the declared weight of this routerstatus in the directory.
25
33768952
    pub fn weight(&self) -> &RelayWeight {
26
33768952
        &self.weight.effective
27
33768952
    }
28
    /// Return the protovers that this routerstatus says it implements.
29
3258070
    pub fn protovers(&self) -> &Protocols {
30
3258070
        &self.protos
31
3258070
    }
32
    /// Return the nickname of this routerstatus.
33
    pub fn nickname(&self) -> &str {
34
        self.r.nickname.as_str()
35
    }
36
    /// Return the relay flags of this routerstatus.
37
    pub fn flags(&self) -> &RelayFlags {
38
        &self.flags.known
39
    }
40
    /// Return the version of this routerstatus.
41
    pub fn version(&self) -> Option<&crate::doc::netstatus::rs::SoftwareVersion> {
42
        self.version.as_ref()
43
    }
44
    /// Return true if the ed25519 identity on this relay reflects a
45
    /// true consensus among the authorities.
46
68254937
    pub fn ed25519_id_is_usable(&self) -> bool {
47
68254937
        !self.flags.contains(RelayFlag::NoEdConsensus)
48
68254937
    }
49
    /// Return true if this routerstatus is listed with the BadExit flag.
50
46040994
    pub fn is_flagged_bad_exit(&self) -> bool {
51
46040994
        self.flags.contains(RelayFlag::BadExit)
52
46040994
    }
53
    /// Return true if this routerstatus is listed with the v2dir flag.
54
34545029
    pub fn is_flagged_v2dir(&self) -> bool {
55
34545029
        self.flags.contains(RelayFlag::V2Dir)
56
34545029
    }
57
    /// Return true if this routerstatus is listed with the Exit flag.
58
33855022
    pub fn is_flagged_exit(&self) -> bool {
59
33855022
        self.flags.contains(RelayFlag::Exit)
60
33855022
    }
61
    /// Return true if this routerstatus is listed with the Guard flag.
62
35997229
    pub fn is_flagged_guard(&self) -> bool {
63
35997229
        self.flags.contains(RelayFlag::Guard)
64
35997229
    }
65
    /// Return true if this routerstatus is listed with the HSDir flag.
66
462955
    pub fn is_flagged_hsdir(&self) -> bool {
67
462955
        self.flags.contains(RelayFlag::HSDir)
68
462955
    }
69
    /// Return true if this routerstatus is listed with the Stable flag.
70
3857764
    pub fn is_flagged_stable(&self) -> bool {
71
3857764
        self.flags.contains(RelayFlag::Stable)
72
3857764
    }
73
    /// Return true if this routerstatus is listed with the Fast flag.
74
55456603
    pub fn is_flagged_fast(&self) -> bool {
75
55456603
        self.flags.contains(RelayFlag::Fast)
76
55456603
    }
77
    /// Return true if this routerstatus is listed with the MiddleOnly flag.
78
31164
    pub fn is_flagged_middle_only(&self) -> bool {
79
31164
        self.flags.contains(RelayFlag::MiddleOnly)
80
31164
    }
81
}
82

            
83
impl RouterStatus {
84
    /// Return RSA identity for the relay described by this RouterStatus
85
250791807
    pub fn rsa_identity(&self) -> &RsaIdentity {
86
250791807
        &self.r.identity
87
250791807
    }
88

            
89
    /// Return the networkstatus consensus flavor in which this
90
    /// routerstatus appears.
91
17692
    pub(crate) fn flavor() -> ConsensusFlavor {
92
17692
        FLAVOR
93
17692
    }
94

            
95
    /// Parse a generic routerstatus from a section.
96
    ///
97
    /// Requires that the section obeys the right SectionRules,
98
    /// matching `consensus_flavor`.
99
2046
    pub(crate) fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<RouterStatus> {
100
        use NetstatusKwd::*;
101
        // R line
102
2046
        let r_item = sec.required(RS_R)?;
103
2046
        let nickname = r_item
104
2046
            .required_arg(0)?
105
2046
            .parse()
106
2046
            .map_err(|e: InvalidNickname| {
107
                EK::BadArgument.with_msg(e.to_string()).at_pos(r_item.pos())
108
            })?;
109
2046
        let ident = r_item.required_arg(1)?;
110
2046
        let identity = ident.parse::<Base64Fingerprint>()?;
111
        // Fields to skip in the "r" line.
112
2046
        let n_skip = match FLAVOR {
113
2039
            ConsensusFlavor::Microdesc => 0,
114
7
            ConsensusFlavor::Plain => 1,
115
        };
116
        // We check that the published time is well-formed, but we never use it
117
        // for anything in a consensus document.
118
2046
        let _ignore_published: time::SystemTime = {
119
            // TODO: It's annoying to have to do this allocation, since we
120
            // already have a slice that contains both of these arguments.
121
            // Instead, we could get a slice of arguments: we'd have to add
122
            // a feature for that.
123
2046
            let mut p = r_item.required_arg(2 + n_skip)?.to_string();
124
2046
            p.push(' ');
125
2046
            p.push_str(r_item.required_arg(3 + n_skip)?);
126
2046
            p.parse::<Iso8601TimeSp>()?.into()
127
        };
128
2046
        let ip = r_item.required_arg(4 + n_skip)?.parse::<net::Ipv4Addr>()?;
129
2046
        let or_port = r_item.required_arg(5 + n_skip)?.parse::<u16>()?;
130
2046
        let _ = r_item.required_arg(6 + n_skip)?.parse::<u16>()?;
131

            
132
        // main address and A lines.
133
2046
        let a_items = sec.slice(RS_A);
134
2046
        let a = a_items
135
2046
            .iter()
136
2046
            .map(|a_item| Ok(a_item.required_arg(0)?.parse::<net::SocketAddr>()?))
137
2046
            .collect::<Result<Vec<_>>>()?;
138

            
139
        // S line
140
        //
141
        // Wrong for votes, but this code doesn't run for votes.
142
2046
        let flags = DocRelayFlags::from_item_consensus(sec.required(RS_S)?)?;
143

            
144
        // V line
145
2045
        let version = sec
146
2045
            .maybe(RS_V)
147
2045
            .args_as_str()
148
2045
            .map(str::parse)
149
2045
            .transpose()
150
2045
            .void_unwrap();
151

            
152
        // PR line
153
2045
        let protos = {
154
2045
            let tok = sec.required(RS_PR)?;
155
2045
            Protocols::from_str_c_compatible(tok.args_as_str())
156
2045
                .map_err(|e| EK::BadArgument.at_pos(tok.pos()).with_source(e))?
157
        };
158

            
159
        // W line
160
2045
        let weight = sec
161
2045
            .get(RS_W)
162
2045
            .map(RelayWeightsItem::from_item)
163
2045
            .transpose()?
164
2044
            .unwrap_or_else(RelayWeightsItem::new_no_info);
165

            
166
        // No p line
167
        // no ID line
168

            
169
        // Try to find the document digest.  This is in different
170
        // places depending on the kind of consensus we're in.
171
2044
        let doc_digest: DocDigest = match FLAVOR {
172
            ConsensusFlavor::Microdesc => {
173
                // M line
174
2037
                let m_item = sec.required(RS_M)?;
175
2037
                DocDigest::decode(m_item.required_arg(0)?)?
176
            }
177
7
            ConsensusFlavor::Plain => DocDigest::decode(r_item.required_arg(2)?)?,
178
        };
179

            
180
        ns_choose! { (
181
7
            let r_doc_digest = doc_digest;
182
7
            let m_doc_digest = NotPresent;
183
        ) (
184
2036
            let r_doc_digest = NotPresent;
185
2036
            let m_doc_digest = doc_digest;
186
        ) (
187
            let r_doc_digest = doc_digest;
188
            let m_doc_digest = NotPresent;
189
        ) };
190

            
191
        #[allow(clippy::useless_conversion)] // sometimes doc_digest needs into
192
2043
        Ok(RouterStatus {
193
2043
            r: RouterStatusIntroItem {
194
2043
                nickname,
195
2043
                identity,
196
2043
                or_port,
197
2043
                doc_digest: r_doc_digest.into(),
198
2043
                publication: IgnoredPublicationTimeSp,
199
2043
                ip,
200
2043
                dir_port: 0,
201
2043
                __non_exhaustive: (),
202
2043
            },
203
2043
            m: m_doc_digest.into(),
204
2043
            a,
205
2043
            flags,
206
2043
            version,
207
2043
            protos,
208
2043
            weight,
209
2043
            port_policy: ns_expr!(None, NotPresent, Void {}),
210
2043
            ed25519_id: NotPresent,
211
2043
            stats: NotPresent,
212
2043
            __non_exhaustive: (),
213
2043
        })
214
2046
    }
215
}
216

            
217
impl FromRsString for DocDigest {
218
2044
    fn decode(s: &str) -> Result<DocDigest> {
219
2044
        s.parse::<B64>()?
220
2043
            .check_len(DOC_DIGEST_LEN..=DOC_DIGEST_LEN)?
221
2043
            .as_bytes()
222
2043
            .try_into()
223
2043
            .map_err(|_| Error::from(internal!("correct length on digest, but unable to convert")))
224
2044
    }
225
}