tor_netdoc/doc/netstatus/
build.rs

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