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, RouterStatus, SharedRandVal,
10
};
11

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

            
16
use std::net::IpAddr;
17

            
18
/// A builder object used to construct a consensus.
19
///
20
/// Create one of these with the [`Consensus::builder`] method.
21
///
22
/// This facility is only enabled when the crate is built with
23
/// the `build_docs` feature.
24
pub struct ConsensusBuilder<RS> {
25
    /// See [`CommonHeader::flavor`]
26
    flavor: ConsensusFlavor,
27
    /// See [`CommonHeader::lifetime`]
28
    lifetime: Option<Lifetime>,
29
    /// See [`CommonHeader::client_versions`]
30
    client_versions: Vec<String>,
31
    /// See [`CommonHeader::relay_versions`]
32
    relay_versions: Vec<String>,
33
    /// See [`CommonHeader::client_protos`]
34
    client_protos: ProtoStatus,
35
    /// See [`CommonHeader::relay_protos`]
36
    relay_protos: ProtoStatus,
37
    /// See [`CommonHeader::params`]
38
    params: NetParams<i32>,
39
    /// See [`CommonHeader::voting_delay`]
40
    voting_delay: Option<(u32, u32)>,
41
    /// See [`ConsensusHeader::consensus_method`]
42
    consensus_method: Option<u32>,
43
    /// See [`ConsensusHeader::shared_rand_prev`]
44
    shared_rand_prev: Option<SharedRandVal>,
45
    /// See [`ConsensusHeader::shared_rand_cur`]
46
    shared_rand_cur: Option<SharedRandVal>,
47
    /// See [`Consensus::voters`]
48
    voters: Vec<ConsensusVoterInfo>,
49
    /// See [`Consensus::relays`]
50
    relays: Vec<RS>,
51
    /// See [`Footer::weights`]
52
    weights: NetParams<i32>,
53
}
54

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

            
76
    /// Set the lifetime of this consensus.
77
    ///
78
    /// This value is required.
79
703
    pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
80
703
        self.lifetime = Some(lifetime);
81
703
        self
82
703
    }
83

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

            
180
    /// Insert a single routerstatus into this builder.
181
38642
    pub(crate) fn add_rs(&mut self, rs: RS) -> &mut Self {
182
38642
        self.relays.push(rs);
183
38642
        self
184
38642
    }
185
}
186

            
187
impl<RS: RouterStatus + Clone> ConsensusBuilder<RS> {
188
    /// Create a RouterStatusBuilder to add a RouterStatus to this builder.
189
    ///
190
    /// You can make a consensus with no RouterStatus entries, but it
191
    /// won't actually be good for anything.
192
27747
    pub fn rs(&self) -> RouterStatusBuilder<RS::DocumentDigest> {
193
27747
        RouterStatusBuilder::new()
194
27747
    }
195

            
196
    /// Try to create a consensus object from this builder.
197
    ///
198
    /// This object might not have all of the data that a valid
199
    /// consensus would have. Therefore, it should only be used for
200
    /// testing.
201
703
    pub fn testing_consensus(&self) -> Result<Consensus<RS>> {
202
703
        let lifetime = self
203
703
            .lifetime
204
703
            .as_ref()
205
703
            .ok_or(Error::CannotBuild("Missing lifetime."))?
206
703
            .clone();
207
703

            
208
703
        let hdr = CommonHeader {
209
703
            flavor: self.flavor,
210
703
            lifetime,
211
703
            client_versions: self.client_versions.clone(),
212
703
            relay_versions: self.relay_versions.clone(),
213
703
            client_protos: self.client_protos.clone(),
214
703
            relay_protos: self.relay_protos.clone(),
215
703
            params: self.params.clone(),
216
703
            voting_delay: self.voting_delay,
217
703
        };
218

            
219
703
        let consensus_method = self
220
703
            .consensus_method
221
703
            .ok_or(Error::CannotBuild("Missing consensus method."))?;
222

            
223
703
        let header = ConsensusHeader {
224
703
            hdr,
225
703
            consensus_method,
226
703
            shared_rand_prev: self.shared_rand_prev.clone(),
227
703
            shared_rand_cur: self.shared_rand_cur.clone(),
228
703
        };
229
703

            
230
703
        let footer = Footer {
231
703
            weights: self.weights.clone(),
232
703
        };
233
703

            
234
703
        let mut relays = self.relays.clone();
235
55001
        relays.sort_by_key(|r| *r.rsa_identity());
236
703
        // TODO: check for duplicates?
237
703

            
238
703
        Ok(Consensus {
239
703
            header,
240
703
            voters: self.voters.clone(),
241
703
            relays,
242
703
            footer,
243
703
        })
244
703
    }
245
}
246

            
247
/// Builder object for constructing a [`ConsensusVoterInfo`]
248
pub struct VoterInfoBuilder {
249
    /// See [`DirSource::nickname`]
250
    nickname: Option<String>,
251
    /// See [`DirSource::identity`]
252
    identity: Option<RsaIdentity>,
253
    /// See [`DirSource::ip`]
254
    ip: Option<IpAddr>,
255
    /// See [`ConsensusVoterInfo::contact`]
256
    contact: Option<String>,
257
    /// See [`ConsensusVoterInfo::vote_digest`]
258
    vote_digest: Vec<u8>,
259
    /// See [`DirSource::or_port`]
260
    or_port: u16,
261
    /// See [`DirSource::dir_port`]
262
    dir_port: u16,
263
}
264

            
265
impl VoterInfoBuilder {
266
    /// Construct a new VoterInfoBuilder.
267
1
    pub(crate) fn new() -> Self {
268
1
        VoterInfoBuilder {
269
1
            nickname: None,
270
1
            identity: None,
271
1
            ip: None,
272
1
            contact: None,
273
1
            vote_digest: Vec::new(),
274
1
            or_port: 0,
275
1
            dir_port: 0,
276
1
        }
277
1
    }
278

            
279
    /// Set a nickname.
280
    ///
281
    /// This value is required.
282
1
    pub fn nickname(&mut self, nickname: String) -> &mut Self {
283
1
        self.nickname = Some(nickname);
284
1
        self
285
1
    }
286

            
287
    /// Set an RSA identity.
288
    ///
289
    /// This value is required.
290
1
    pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
291
1
        self.identity = Some(identity);
292
1
        self
293
1
    }
294

            
295
    /// Set a IP-valued address.
296
    ///
297
    /// This value is required.
298
1
    pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
299
1
        self.ip = Some(ip);
300
1
        self
301
1
    }
302

            
303
    /// Set a contact line for this voter.
304
    ///
305
    /// This value is optional.
306
1
    pub fn contact(&mut self, contact: String) -> &mut Self {
307
1
        self.contact = Some(contact);
308
1
        self
309
1
    }
310

            
311
    /// Set the declared vote digest for this voter within a consensus.
312
    ///
313
    /// This value is required.
314
1
    pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
315
1
        self.vote_digest = vote_digest;
316
1
        self
317
1
    }
318

            
319
    /// Set the declared OrPort for this voter.
320
1
    pub fn or_port(&mut self, or_port: u16) -> &mut Self {
321
1
        self.or_port = or_port;
322
1
        self
323
1
    }
324

            
325
    /// Set the declared DirPort for this voter.
326
1
    pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
327
1
        self.dir_port = dir_port;
328
1
        self
329
1
    }
330

            
331
    /// Add the voter that we've been building into the in-progress
332
    /// consensus of `builder`.
333
1
    pub fn build<RS>(&self, builder: &mut ConsensusBuilder<RS>) -> Result<()> {
334
1
        let nickname = self
335
1
            .nickname
336
1
            .as_ref()
337
1
            .ok_or(Error::CannotBuild("Missing nickname"))?
338
1
            .clone();
339
1
        let identity = self
340
1
            .identity
341
1
            .ok_or(Error::CannotBuild("Missing identity"))?;
342
1
        let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
343
1
        let contact = self
344
1
            .contact
345
1
            .as_ref()
346
1
            .ok_or(Error::CannotBuild("Missing contact"))?
347
1
            .clone();
348
1
        if self.vote_digest.is_empty() {
349
            return Err(Error::CannotBuild("Missing vote digest"));
350
1
        }
351
1
        let dir_source = DirSource {
352
1
            nickname,
353
1
            identity,
354
1
            ip,
355
1
            dir_port: self.dir_port,
356
1
            or_port: self.or_port,
357
1
        };
358
1

            
359
1
        let info = ConsensusVoterInfo {
360
1
            dir_source,
361
1
            contact,
362
1
            vote_digest: self.vote_digest.clone(),
363
1
        };
364
1
        builder.voters.push(info);
365
1
        Ok(())
366
1
    }
367
}
368

            
369
#[cfg(test)]
370
mod test {
371
    #![allow(clippy::unwrap_used)]
372
    use super::*;
373
    use crate::doc::netstatus::RelayFlags;
374

            
375
    use std::net::SocketAddr;
376
    use std::time::{Duration, SystemTime};
377

            
378
    #[test]
379
    fn consensus() {
380
        let now = SystemTime::now();
381
        let one_hour = Duration::new(3600, 0);
382

            
383
        let mut builder = crate::doc::netstatus::MdConsensus::builder();
384
        builder
385
            .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
386
            .add_client_version("0.4.5.8".into())
387
            .add_relay_version("0.4.5.9".into())
388
            .required_client_protos("DirCache=2 LinkAuth=3".parse().unwrap())
389
            .required_relay_protos("DirCache=1".parse().unwrap())
390
            .recommended_client_protos("DirCache=6".parse().unwrap())
391
            .recommended_relay_protos("DirCache=5".parse().unwrap())
392
            .param("wombat", 7)
393
            .param("knish", 1212)
394
            .voting_delay(7, 8)
395
            .consensus_method(32)
396
            .shared_rand_prev(1, (*b"").into())
397
            .shared_rand_cur(1, (*b"hi there").into())
398
            .weight("Wxy", 303)
399
            .weight("Wow", 999);
400

            
401
        builder
402
            .voter()
403
            .nickname("Fuzzy".into())
404
            .identity([15; 20].into())
405
            .ip("10.0.0.200".parse().unwrap())
406
            .contact("admin@fuzzy.example.com".into())
407
            .vote_digest((*b"1234").into())
408
            .or_port(9001)
409
            .dir_port(9101)
410
            .build(&mut builder)
411
            .unwrap();
412

            
413
        builder
414
            .rs()
415
            .nickname("Fred".into())
416
            .identity([155; 20].into())
417
            .add_or_port(SocketAddr::from(([10, 0, 0, 60], 9100)))
418
            .add_or_port("[f00f::1]:9200".parse().unwrap())
419
            .doc_digest([99; 32])
420
            .set_flags(RelayFlags::FAST)
421
            .add_flags(RelayFlags::STABLE | RelayFlags::V2DIR)
422
            .version("Arti 0.0.0".into())
423
            .protos("DirCache=7".parse().unwrap())
424
            .build_into(&mut builder)
425
            .unwrap();
426

            
427
        let _cons = builder.testing_consensus().unwrap();
428

            
429
        // TODO: Check actual members of `cons` above.
430
    }
431
}