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