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

            
6
use super::rs::build::RouterStatusBuilder;
7
use super::{
8
    CommonHeader, Consensus, ConsensusFlavor, ConsensusHeader, ConsensusVoterInfo, DirSource,
9
    Footer, Lifetime, NetParams, ProtoStatus, ProtoStatuses, RouterStatus, SharedRandStatus,
10
    SharedRandVal,
11
};
12

            
13
use crate::{BuildError as Error, BuildResult as Result};
14
use tor_llcrypto::pk::rsa::RsaIdentity;
15
use tor_protover::Protocols;
16

            
17
use std::net::IpAddr;
18
use std::sync::Arc;
19
use 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")))]
28
pub 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

            
59
impl<RS> ConsensusBuilder<RS> {
60
    /// Construct a new ConsensusBuilder object.
61
7477
    pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder<RS> {
62
7477
        ConsensusBuilder {
63
7477
            flavor,
64
7477
            lifetime: None,
65
7477
            client_versions: Vec::new(),
66
7477
            relay_versions: Vec::new(),
67
7477
            client_protos: ProtoStatus::default(),
68
7477
            relay_protos: ProtoStatus::default(),
69
7477
            params: NetParams::new(),
70
7477
            voting_delay: None,
71
7477
            consensus_method: None,
72
7477
            shared_rand_prev: None,
73
7477
            shared_rand_cur: None,
74
7477
            voters: Vec::new(),
75
7477
            relays: Vec::new(),
76
7477
            weights: NetParams::new(),
77
7477
        }
78
7477
    }
79

            
80
    /// Set the lifetime of this consensus.
81
    ///
82
    /// This value is required.
83
7385
    pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
84
7385
        self.lifetime = Some(lifetime);
85
7385
        self
86
7385
    }
87

            
88
    /// Add a single recommended Tor client version to this consensus.
89
    ///
90
    /// These values are optional for testing.
91
2
    pub fn add_client_version(&mut self, ver: String) -> &mut Self {
92
2
        self.client_versions.push(ver);
93
2
        self
94
2
    }
95
    /// Add a single recommended Tor relay version to this consensus.
96
    ///
97
    /// These values are optional for testing.
98
2
    pub fn add_relay_version(&mut self, ver: String) -> &mut Self {
99
2
        self.relay_versions.push(ver);
100
2
        self
101
2
    }
102
    /// Set the required client protocol versions for this consensus.
103
    ///
104
    /// This value defaults to "no protocol versions required."
105
2
    pub fn required_client_protos(&mut self, protos: Protocols) -> &mut Self {
106
2
        self.client_protos.required = protos;
107
2
        self
108
2
    }
109
    /// Set the recommended client protocol versions for this consensus.
110
    ///
111
    /// This value defaults to "no protocol versions recommended."
112
2
    pub fn recommended_client_protos(&mut self, protos: Protocols) -> &mut Self {
113
2
        self.client_protos.recommended = protos;
114
2
        self
115
2
    }
116
    /// Set the required relay protocol versions for this consensus.
117
    ///
118
    /// This value defaults to "no protocol versions required."
119
2
    pub fn required_relay_protos(&mut self, protos: Protocols) -> &mut Self {
120
2
        self.relay_protos.required = protos;
121
2
        self
122
2
    }
123
    /// Set the recommended client protocol versions for this consensus.
124
    ///
125
    /// This value defaults to "no protocol versions recommended."
126
2
    pub fn recommended_relay_protos(&mut self, protos: Protocols) -> &mut Self {
127
2
        self.relay_protos.recommended = protos;
128
2
        self
129
2
    }
130
    /// Set the value for a given consensus parameter by name.
131
7401
    pub fn param<S>(&mut self, param: S, val: i32) -> &mut Self
132
7401
    where
133
7401
        S: Into<String>,
134
7401
    {
135
7401
        self.params.set(param.into(), val);
136
7401
        self
137
7401
    }
138
    /// Set the voting delays (in seconds) for this consensus.
139
2
    pub fn voting_delay(&mut self, vote_delay: u32, signature_delay: u32) -> &mut Self {
140
2
        self.voting_delay = Some((vote_delay, signature_delay));
141
2
        self
142
2
    }
143
    /// Set the declared consensus method for this consensus.
144
    ///
145
    /// This value is required.
146
7385
    pub fn consensus_method(&mut self, consensus_method: u32) -> &mut Self {
147
7385
        self.consensus_method = Some(consensus_method);
148
7385
        self
149
7385
    }
150
    /// Set the previous day's shared-random value for this consensus.
151
    ///
152
    /// This value is optional.
153
664
    pub fn shared_rand_prev(
154
664
        &mut self,
155
664
        n_reveals: u8,
156
664
        value: SharedRandVal,
157
664
        timestamp: Option<SystemTime>,
158
664
    ) -> &mut Self {
159
664
        self.shared_rand_prev = Some(SharedRandStatus {
160
664
            n_reveals,
161
664
            value,
162
664
            timestamp,
163
664
        });
164
664
        self
165
664
    }
166
    /// Set the current day's shared-random value for this consensus.
167
    ///
168
    /// This value is optional.
169
24
    pub fn shared_rand_cur(
170
24
        &mut self,
171
24
        n_reveals: u8,
172
24
        value: SharedRandVal,
173
24
        timestamp: Option<SystemTime>,
174
24
    ) -> &mut Self {
175
24
        self.shared_rand_cur = Some(SharedRandStatus {
176
24
            n_reveals,
177
24
            value,
178
24
            timestamp,
179
24
        });
180
24
        self
181
24
    }
182
    /// Set a named weight parameter for this consensus.
183
4
    pub fn weight<S>(&mut self, param: S, val: i32) -> &mut Self
184
4
    where
185
4
        S: Into<String>,
186
4
    {
187
4
        self.weights.set(param.into(), val);
188
4
        self
189
4
    }
190
    /// Replace all weight parameters for this consensus.
191
7383
    pub fn weights(&mut self, weights: NetParams<i32>) -> &mut Self {
192
7383
        self.weights = weights;
193
7383
        self
194
7383
    }
195
    /// Create a VoterInfoBuilder to add a voter to this builder.
196
    ///
197
    /// In theory these are required, but nothing asks for them.
198
2
    pub fn voter(&self) -> VoterInfoBuilder {
199
2
        VoterInfoBuilder::new()
200
2
    }
201

            
202
    /// Insert a single routerstatus into this builder.
203
315148
    pub(crate) fn add_rs(&mut self, rs: RS) -> &mut Self {
204
315148
        self.relays.push(rs);
205
315148
        self
206
315148
    }
207
}
208

            
209
impl<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
294774
    pub fn rs(&self) -> RouterStatusBuilder<RS::DocumentDigest> {
215
294774
        RouterStatusBuilder::new()
216
294774
    }
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
7385
    pub fn testing_consensus(&self) -> Result<Consensus<RS>> {
224
7385
        let lifetime = self
225
7385
            .lifetime
226
7385
            .as_ref()
227
7385
            .ok_or(Error::CannotBuild("Missing lifetime."))?
228
7385
            .clone();
229
7385

            
230
7385
        let proto_statuses = Arc::new(ProtoStatuses {
231
7385
            client: self.client_protos.clone(),
232
7385
            relay: self.relay_protos.clone(),
233
7385
        });
234
7385

            
235
7385
        let hdr = CommonHeader {
236
7385
            flavor: self.flavor,
237
7385
            lifetime,
238
7385
            client_versions: self.client_versions.clone(),
239
7385
            relay_versions: self.relay_versions.clone(),
240
7385
            proto_statuses,
241
7385
            params: self.params.clone(),
242
7385
            voting_delay: self.voting_delay,
243
7385
        };
244

            
245
7385
        let consensus_method = self
246
7385
            .consensus_method
247
7385
            .ok_or(Error::CannotBuild("Missing consensus method."))?;
248

            
249
7385
        let header = ConsensusHeader {
250
7385
            hdr,
251
7385
            consensus_method,
252
7385
            shared_rand_prev: self.shared_rand_prev.clone(),
253
7385
            shared_rand_cur: self.shared_rand_cur.clone(),
254
7385
        };
255
7385

            
256
7385
        let footer = Footer {
257
7385
            weights: self.weights.clone(),
258
7385
        };
259
7385

            
260
7385
        let mut relays = self.relays.clone();
261
544114
        relays.sort_by_key(|r| *r.rsa_identity());
262
7385
        // TODO: check for duplicates?
263
7385

            
264
7385
        Ok(Consensus {
265
7385
            header,
266
7385
            voters: self.voters.clone(),
267
7385
            relays,
268
7385
            footer,
269
7385
        })
270
7385
    }
271
}
272

            
273
/// Builder object for constructing a [`ConsensusVoterInfo`]
274
pub 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

            
291
impl VoterInfoBuilder {
292
    /// Construct a new VoterInfoBuilder.
293
2
    pub(crate) fn new() -> Self {
294
2
        VoterInfoBuilder {
295
2
            nickname: None,
296
2
            identity: None,
297
2
            ip: None,
298
2
            contact: None,
299
2
            vote_digest: Vec::new(),
300
2
            or_port: 0,
301
2
            dir_port: 0,
302
2
        }
303
2
    }
304

            
305
    /// Set a nickname.
306
    ///
307
    /// This value is required.
308
2
    pub fn nickname(&mut self, nickname: String) -> &mut Self {
309
2
        self.nickname = Some(nickname);
310
2
        self
311
2
    }
312

            
313
    /// Set an RSA identity.
314
    ///
315
    /// This value is required.
316
2
    pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
317
2
        self.identity = Some(identity);
318
2
        self
319
2
    }
320

            
321
    /// Set a IP-valued address.
322
    ///
323
    /// This value is required.
324
2
    pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
325
2
        self.ip = Some(ip);
326
2
        self
327
2
    }
328

            
329
    /// Set a contact line for this voter.
330
    ///
331
    /// This value is optional.
332
2
    pub fn contact(&mut self, contact: String) -> &mut Self {
333
2
        self.contact = Some(contact);
334
2
        self
335
2
    }
336

            
337
    /// Set the declared vote digest for this voter within a consensus.
338
    ///
339
    /// This value is required.
340
2
    pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
341
2
        self.vote_digest = vote_digest;
342
2
        self
343
2
    }
344

            
345
    /// Set the declared OrPort for this voter.
346
2
    pub fn or_port(&mut self, or_port: u16) -> &mut Self {
347
2
        self.or_port = or_port;
348
2
        self
349
2
    }
350

            
351
    /// Set the declared DirPort for this voter.
352
2
    pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
353
2
        self.dir_port = dir_port;
354
2
        self
355
2
    }
356

            
357
    /// Add the voter that we've been building into the in-progress
358
    /// consensus of `builder`.
359
2
    pub fn build<RS>(&self, builder: &mut ConsensusBuilder<RS>) -> Result<()> {
360
2
        let nickname = self
361
2
            .nickname
362
2
            .as_ref()
363
2
            .ok_or(Error::CannotBuild("Missing nickname"))?
364
2
            .clone();
365
2
        let identity = self
366
2
            .identity
367
2
            .ok_or(Error::CannotBuild("Missing identity"))?;
368
2
        let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
369
2
        let contact = self
370
2
            .contact
371
2
            .as_ref()
372
2
            .ok_or(Error::CannotBuild("Missing contact"))?
373
2
            .clone();
374
2
        if self.vote_digest.is_empty() {
375
            return Err(Error::CannotBuild("Missing vote digest"));
376
2
        }
377
2
        let dir_source = DirSource {
378
2
            nickname,
379
2
            identity,
380
2
            ip,
381
2
            dir_port: self.dir_port,
382
2
            or_port: self.or_port,
383
2
        };
384
2

            
385
2
        let info = ConsensusVoterInfo {
386
2
            dir_source,
387
2
            contact,
388
2
            vote_digest: self.vote_digest.clone(),
389
2
        };
390
2
        builder.voters.push(info);
391
2
        Ok(())
392
2
    }
393
}
394

            
395
#[cfg(test)]
396
mod 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
}