tor_netdoc/doc/netstatus/build/
each_flavor.rs

1//! consensus document builders - 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
13ns_use_this_variety! {
14    use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
15    use [crate::doc::netstatus::rs]::?::{RouterStatus};
16}
17#[cfg(not(doc))]
18ns_use_this_variety! {
19    use [crate::doc::netstatus]::?::{Consensus, Header};
20}
21#[cfg(doc)]
22ns_use_this_variety! {
23    pub use [crate::doc::netstatus]::?::{Consensus, Header};
24}
25
26use super::*;
27
28/// A builder object used to construct a consensus.
29///
30/// Create one of these with the [`Consensus::builder`] method.
31///
32/// This facility is only enabled when the crate is built with
33/// the `build_docs` feature.
34#[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))]
35pub struct ConsensusBuilder {
36    /// See [`Header::flavor`]
37    flavor: ConsensusFlavor,
38    /// See [`Header::lifetime`]
39    lifetime: Option<Lifetime>,
40    /// See [`Header::client_versions`]
41    client_versions: Vec<String>,
42    /// See [`Header::relay_versions`]
43    relay_versions: Vec<String>,
44    /// See [`Header::proto_statuses`]
45    client_protos: ProtoStatus,
46    /// See [`Header::proto_statuses`]
47    relay_protos: ProtoStatus,
48    /// See [`Header::params`]
49    params: NetParams<i32>,
50    /// See [`Header::voting_delay`]
51    voting_delay: Option<(u32, u32)>,
52    /// See [`Header::consensus_method`]
53    consensus_method: Option<u32>,
54    /// See [`Header::shared_rand_prev`]
55    shared_rand_prev: Option<SharedRandStatus>,
56    /// See [`Header::shared_rand_cur`]
57    shared_rand_cur: Option<SharedRandStatus>,
58    /// See [`Consensus::voters`]
59    voters: Vec<ConsensusVoterInfo>,
60    /// See [`Consensus::relays`]
61    relays: Vec<RouterStatus>,
62    /// See [`Footer::weights`]
63    weights: NetParams<i32>,
64}
65
66impl ConsensusBuilder {
67    /// Construct a new ConsensusBuilder object.
68    pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder {
69        ConsensusBuilder {
70            flavor,
71            lifetime: None,
72            client_versions: Vec::new(),
73            relay_versions: Vec::new(),
74            client_protos: ProtoStatus::default(),
75            relay_protos: ProtoStatus::default(),
76            params: NetParams::new(),
77            voting_delay: None,
78            consensus_method: None,
79            shared_rand_prev: None,
80            shared_rand_cur: None,
81            voters: Vec::new(),
82            relays: Vec::new(),
83            weights: NetParams::new(),
84        }
85    }
86
87    /// Set the lifetime of this consensus.
88    ///
89    /// This value is required.
90    pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
91        self.lifetime = Some(lifetime);
92        self
93    }
94
95    /// Add a single recommended Tor client version to this consensus.
96    ///
97    /// These values are optional for testing.
98    pub fn add_client_version(&mut self, ver: String) -> &mut Self {
99        self.client_versions.push(ver);
100        self
101    }
102    /// Add a single recommended Tor relay version to this consensus.
103    ///
104    /// These values are optional for testing.
105    pub fn add_relay_version(&mut self, ver: String) -> &mut Self {
106        self.relay_versions.push(ver);
107        self
108    }
109    /// Set the required client protocol versions for this consensus.
110    ///
111    /// This value defaults to "no protocol versions required."
112    pub fn required_client_protos(&mut self, protos: Protocols) -> &mut Self {
113        self.client_protos.required = protos;
114        self
115    }
116    /// Set the recommended client protocol versions for this consensus.
117    ///
118    /// This value defaults to "no protocol versions recommended."
119    pub fn recommended_client_protos(&mut self, protos: Protocols) -> &mut Self {
120        self.client_protos.recommended = protos;
121        self
122    }
123    /// Set the required relay protocol versions for this consensus.
124    ///
125    /// This value defaults to "no protocol versions required."
126    pub fn required_relay_protos(&mut self, protos: Protocols) -> &mut Self {
127        self.relay_protos.required = protos;
128        self
129    }
130    /// Set the recommended client protocol versions for this consensus.
131    ///
132    /// This value defaults to "no protocol versions recommended."
133    pub fn recommended_relay_protos(&mut self, protos: Protocols) -> &mut Self {
134        self.relay_protos.recommended = protos;
135        self
136    }
137    /// Set the value for a given consensus parameter by name.
138    pub fn param<S>(&mut self, param: S, val: i32) -> &mut Self
139    where
140        S: Into<String>,
141    {
142        self.params.set(param.into(), val);
143        self
144    }
145    /// Set the voting delays (in seconds) for this consensus.
146    pub fn voting_delay(&mut self, vote_delay: u32, signature_delay: u32) -> &mut Self {
147        self.voting_delay = Some((vote_delay, signature_delay));
148        self
149    }
150    /// Set the declared consensus method for this consensus.
151    ///
152    /// This value is required.
153    pub fn consensus_method(&mut self, consensus_method: u32) -> &mut Self {
154        self.consensus_method = Some(consensus_method);
155        self
156    }
157    /// Set the previous day's shared-random value for this consensus.
158    ///
159    /// This value is optional.
160    pub fn shared_rand_prev(
161        &mut self,
162        n_reveals: u8,
163        value: SharedRandVal,
164        timestamp: Option<SystemTime>,
165    ) -> &mut Self {
166        self.shared_rand_prev = Some(SharedRandStatus {
167            n_reveals,
168            value,
169            timestamp,
170        });
171        self
172    }
173    /// Set the current day's shared-random value for this consensus.
174    ///
175    /// This value is optional.
176    pub fn shared_rand_cur(
177        &mut self,
178        n_reveals: u8,
179        value: SharedRandVal,
180        timestamp: Option<SystemTime>,
181    ) -> &mut Self {
182        self.shared_rand_cur = Some(SharedRandStatus {
183            n_reveals,
184            value,
185            timestamp,
186        });
187        self
188    }
189    /// Set a named weight parameter for this consensus.
190    pub fn weight<S>(&mut self, param: S, val: i32) -> &mut Self
191    where
192        S: Into<String>,
193    {
194        self.weights.set(param.into(), val);
195        self
196    }
197    /// Replace all weight parameters for this consensus.
198    pub fn weights(&mut self, weights: NetParams<i32>) -> &mut Self {
199        self.weights = weights;
200        self
201    }
202    /// Create a VoterInfoBuilder to add a voter to this builder.
203    ///
204    /// In theory these are required, but nothing asks for them.
205    pub fn voter(&self) -> VoterInfoBuilder {
206        VoterInfoBuilder::new()
207    }
208
209    /// Insert a single routerstatus into this builder.
210    pub(crate) fn add_rs(&mut self, rs: RouterStatus) -> &mut Self {
211        self.relays.push(rs);
212        self
213    }
214}
215
216impl ConsensusBuilder {
217    /// Create a RouterStatusBuilder to add a RouterStatus to this builder.
218    ///
219    /// You can make a consensus with no RouterStatus entries, but it
220    /// won't actually be good for anything.
221    pub fn rs(&self) -> RouterStatusBuilder {
222        RouterStatusBuilder::new()
223    }
224
225    /// Try to create a consensus object from this builder.
226    ///
227    /// This object might not have all of the data that a valid
228    /// consensus would have. Therefore, it should only be used for
229    /// testing.
230    pub fn testing_consensus(&self) -> Result<Consensus> {
231        let lifetime = self
232            .lifetime
233            .as_ref()
234            .ok_or(Error::CannotBuild("Missing lifetime."))?
235            .clone();
236
237        let proto_statuses = Arc::new(ProtoStatuses {
238            client: self.client_protos.clone(),
239            relay: self.relay_protos.clone(),
240        });
241
242        let consensus_method = self
243            .consensus_method
244            .ok_or(Error::CannotBuild("Missing consensus method."))?;
245
246        let header = Header {
247            flavor: self.flavor,
248            lifetime,
249            client_versions: self.client_versions.clone(),
250            relay_versions: self.relay_versions.clone(),
251            proto_statuses,
252            params: self.params.clone(),
253            voting_delay: self.voting_delay,
254            consensus_method,
255            shared_rand_prev: self.shared_rand_prev.clone(),
256            shared_rand_cur: self.shared_rand_cur.clone(),
257        };
258
259        let footer = Footer {
260            weights: self.weights.clone(),
261        };
262
263        let mut relays = self.relays.clone();
264        relays.sort_by_key(|r| *r.rsa_identity());
265        // TODO: check for duplicates?
266
267        Ok(Consensus {
268            header,
269            voters: self.voters.clone(),
270            relays,
271            footer,
272        })
273    }
274}
275
276/// Builder object for constructing a [`ConsensusVoterInfo`]
277pub struct VoterInfoBuilder {
278    /// See [`DirSource::nickname`]
279    nickname: Option<String>,
280    /// See [`DirSource::identity`]
281    identity: Option<RsaIdentity>,
282    /// See [`DirSource::ip`]
283    ip: Option<IpAddr>,
284    /// See [`ConsensusVoterInfo::contact`]
285    contact: Option<String>,
286    /// See [`ConsensusVoterInfo::vote_digest`]
287    vote_digest: Vec<u8>,
288    /// See [`DirSource::or_port`]
289    or_port: u16,
290    /// See [`DirSource::dir_port`]
291    dir_port: u16,
292}
293
294impl VoterInfoBuilder {
295    /// Construct a new VoterInfoBuilder.
296    pub(crate) fn new() -> Self {
297        VoterInfoBuilder {
298            nickname: None,
299            identity: None,
300            ip: None,
301            contact: None,
302            vote_digest: Vec::new(),
303            or_port: 0,
304            dir_port: 0,
305        }
306    }
307
308    /// Set a nickname.
309    ///
310    /// This value is required.
311    pub fn nickname(&mut self, nickname: String) -> &mut Self {
312        self.nickname = Some(nickname);
313        self
314    }
315
316    /// Set an RSA identity.
317    ///
318    /// This value is required.
319    pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
320        self.identity = Some(identity);
321        self
322    }
323
324    /// Set a IP-valued address.
325    ///
326    /// This value is required.
327    pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
328        self.ip = Some(ip);
329        self
330    }
331
332    /// Set a contact line for this voter.
333    ///
334    /// This value is optional.
335    pub fn contact(&mut self, contact: String) -> &mut Self {
336        self.contact = Some(contact);
337        self
338    }
339
340    /// Set the declared vote digest for this voter within a consensus.
341    ///
342    /// This value is required.
343    pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
344        self.vote_digest = vote_digest;
345        self
346    }
347
348    /// Set the declared OrPort for this voter.
349    pub fn or_port(&mut self, or_port: u16) -> &mut Self {
350        self.or_port = or_port;
351        self
352    }
353
354    /// Set the declared DirPort for this voter.
355    pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
356        self.dir_port = dir_port;
357        self
358    }
359
360    /// Add the voter that we've been building into the in-progress
361    /// consensus of `builder`.
362    pub fn build(&self, builder: &mut ConsensusBuilder) -> Result<()> {
363        let nickname = self
364            .nickname
365            .as_ref()
366            .ok_or(Error::CannotBuild("Missing nickname"))?
367            .clone();
368        let identity = self
369            .identity
370            .ok_or(Error::CannotBuild("Missing identity"))?;
371        let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
372        let contact = self
373            .contact
374            .as_ref()
375            .ok_or(Error::CannotBuild("Missing contact"))?
376            .clone();
377        if self.vote_digest.is_empty() {
378            return Err(Error::CannotBuild("Missing vote digest"));
379        }
380        let dir_source = DirSource {
381            nickname,
382            identity,
383            ip,
384            dir_port: self.dir_port,
385            or_port: self.or_port,
386        };
387
388        let info = ConsensusVoterInfo {
389            dir_source,
390            contact,
391            vote_digest: self.vote_digest.clone(),
392        };
393        builder.voters.push(info);
394        Ok(())
395    }
396}
397
398#[cfg(test)]
399mod test {
400    // @@ begin test lint list maintained by maint/add_warning @@
401    #![allow(clippy::bool_assert_comparison)]
402    #![allow(clippy::clone_on_copy)]
403    #![allow(clippy::dbg_macro)]
404    #![allow(clippy::mixed_attributes_style)]
405    #![allow(clippy::print_stderr)]
406    #![allow(clippy::print_stdout)]
407    #![allow(clippy::single_char_pattern)]
408    #![allow(clippy::unwrap_used)]
409    #![allow(clippy::unchecked_duration_subtraction)]
410    #![allow(clippy::useless_vec)]
411    #![allow(clippy::needless_pass_by_value)]
412    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
413    use super::*;
414    use crate::doc::netstatus::RelayFlags;
415
416    use std::net::SocketAddr;
417    use std::time::{Duration, SystemTime};
418
419    #[test]
420    fn consensus() {
421        let now = SystemTime::now();
422        let one_hour = Duration::new(3600, 0);
423
424        let mut builder = crate::doc::netstatus::MdConsensus::builder();
425        builder
426            .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
427            .add_client_version("0.4.5.8".into())
428            .add_relay_version("0.4.5.9".into())
429            .required_client_protos("DirCache=2 LinkAuth=3".parse().unwrap())
430            .required_relay_protos("DirCache=1".parse().unwrap())
431            .recommended_client_protos("DirCache=6".parse().unwrap())
432            .recommended_relay_protos("DirCache=5".parse().unwrap())
433            .param("wombat", 7)
434            .param("knish", 1212)
435            .voting_delay(7, 8)
436            .consensus_method(32)
437            .shared_rand_prev(1, SharedRandVal([b'x'; 32]), None)
438            .shared_rand_cur(1, SharedRandVal([b'y'; 32]), None)
439            .weight("Wxy", 303)
440            .weight("Wow", 999);
441
442        builder
443            .voter()
444            .nickname("Fuzzy".into())
445            .identity([15; 20].into())
446            .ip("10.0.0.200".parse().unwrap())
447            .contact("admin@fuzzy.example.com".into())
448            .vote_digest((*b"1234").into())
449            .or_port(9001)
450            .dir_port(9101)
451            .build(&mut builder)
452            .unwrap();
453
454        builder
455            .rs()
456            .nickname("Fred".into())
457            .identity([155; 20].into())
458            .add_or_port(SocketAddr::from(([10, 0, 0, 60], 9100)))
459            .add_or_port("[f00f::1]:9200".parse().unwrap())
460            .doc_digest([99; 32])
461            .set_flags(RelayFlags::FAST)
462            .add_flags(RelayFlags::STABLE | RelayFlags::V2DIR)
463            .version("Arti 0.0.0".into())
464            .protos("DirCache=7".parse().unwrap())
465            .build_into(&mut builder)
466            .unwrap();
467
468        let _cons = builder.testing_consensus().unwrap();
469
470        // TODO: Check actual members of `cons` above.
471    }
472}