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

            
13
ns_use_this_variety! {
14
    use [crate::doc::netstatus::rs::build]::?::{RouterStatusBuilder};
15
    use [crate::doc::netstatus::rs]::?::{RouterStatus};
16
}
17
#[cfg(not(doc))]
18
ns_use_this_variety! {
19
    use [crate::doc::netstatus]::?::{Consensus, Preamble};
20
}
21
#[cfg(doc)]
22
ns_use_this_variety! {
23
    pub use [crate::doc::netstatus]::?::{Consensus, Preamble};
24
}
25

            
26
use 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")))]
35
pub struct ConsensusBuilder {
36
    /// See [`Consensus::flavor`]
37
    flavor: ConsensusFlavor,
38
    /// See [`Preamble::lifetime`]
39
    lifetime: Option<Lifetime>,
40
    /// See [`Preamble::client_versions`]
41
    client_versions: Vec<String>,
42
    /// See [`Preamble::server_versions`]
43
    server_versions: Vec<String>,
44
    /// See [`Preamble::proto_statuses`]
45
    client_protos: ProtoStatus,
46
    /// See [`Preamble::proto_statuses`]
47
    relay_protos: ProtoStatus,
48
    /// See [`Preamble::params`]
49
    params: NetParams<i32>,
50
    /// See [`Preamble::voting_delay`]
51
    voting_delay: Option<(u32, u32)>,
52
    /// See [`Preamble::consensus_method`]
53
    consensus_method: Option<u32>,
54
    /// See [`SharedRandStatuses::shared_rand_previous_value`]
55
    shared_rand_previous_value: Option<SharedRandStatus>,
56
    /// See [`SharedRandStatuses::shared_rand_current_value`]
57
    shared_rand_current_value: Option<SharedRandStatus>,
58
    /// See [`Consensus::voters`]
59
    voters: Vec<ConsensusAuthorityEntry>,
60
    /// See [`Consensus::relays`]
61
    relays: Vec<RouterStatus>,
62
    /// See [`ConsensusFooterFields::bandwidth_weights`]
63
    weights: NetParams<i32>,
64
}
65

            
66
impl ConsensusBuilder {
67
    /// Construct a new ConsensusBuilder object.
68
14895
    pub(crate) fn new(flavor: ConsensusFlavor) -> ConsensusBuilder {
69
14895
        ConsensusBuilder {
70
14895
            flavor,
71
14895
            lifetime: None,
72
14895
            client_versions: Vec::new(),
73
14895
            server_versions: Vec::new(),
74
14895
            client_protos: ProtoStatus::default(),
75
14895
            relay_protos: ProtoStatus::default(),
76
14895
            params: NetParams::new(),
77
14895
            voting_delay: None,
78
14895
            consensus_method: None,
79
14895
            shared_rand_previous_value: None,
80
14895
            shared_rand_current_value: None,
81
14895
            voters: Vec::new(),
82
14895
            relays: Vec::new(),
83
14895
            weights: NetParams::new(),
84
14895
        }
85
14895
    }
86

            
87
    /// Set the lifetime of this consensus.
88
    ///
89
    /// This value is required.
90
12775
    pub fn lifetime(&mut self, lifetime: Lifetime) -> &mut Self {
91
12775
        self.lifetime = Some(lifetime);
92
12775
        self
93
12775
    }
94

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

            
209
    /// Insert a single routerstatus into this builder.
210
452145
    pub(crate) fn add_rs(&mut self, rs: RouterStatus) -> &mut Self {
211
452145
        self.relays.push(rs);
212
452145
        self
213
452145
    }
214
}
215

            
216
impl 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
470960
    pub fn rs(&self) -> RouterStatusBuilder {
222
470960
        RouterStatusBuilder::new()
223
470960
    }
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
12457
    pub fn testing_consensus(&self) -> Result<Consensus> {
231
12457
        let lifetime = self
232
12457
            .lifetime
233
12457
            .as_ref()
234
12457
            .ok_or(Error::CannotBuild("Missing lifetime."))?
235
12457
            .clone();
236

            
237
12457
        let proto_statuses = Arc::new(ProtoStatuses {
238
12457
            client: self.client_protos.clone(),
239
12457
            relay: self.relay_protos.clone(),
240
12457
        });
241

            
242
12457
        let consensus_method = self
243
12457
            .consensus_method
244
12457
            .ok_or(Error::CannotBuild("Missing consensus method."))?;
245

            
246
12457
        let shared_rand = SharedRandStatuses {
247
12457
            shared_rand_previous_value: self.shared_rand_previous_value.clone(),
248
12457
            shared_rand_current_value: self.shared_rand_current_value.clone(),
249
12457
            __non_exhaustive: (),
250
12457
        };
251

            
252
24914
        let mk_versions = |v: &[String]| {
253
24914
            crate::doc::netstatus::RecommendedTorVersions::from_iter(v.iter())
254
24914
                .map_err(|_| crate::NetdocErrorKind::BadApiUsage.err())
255
24914
        };
256

            
257
12457
        let (vote_seconds, dist_seconds) = self
258
12457
            .voting_delay
259
12457
            .unwrap_or(
260
12457
                // This is wrong.  voting-delay is mandatory, and we shouldn't emit
261
12457
                // `voting-delay 0 0` in a network status document.
262
12457
                //
263
12457
                // But, this is the old, deprecated, way to build a consensus document.
264
12457
                // We retain compatibility with callers who don't specify a voting-delay
265
12457
                // to avoid breaking existing test cases, which are hopefully the only
266
12457
                // callers of this whole module.
267
12457
                (0, 0)
268
12457
            );
269
12457
        let voting_delay = VotingDelayConstructor {
270
12457
            vote_seconds,
271
12457
            dist_seconds,
272
12457
        }.construct();
273

            
274
12457
        let preamble = Preamble {
275
12457
            lifetime,
276
12457
            client_versions: mk_versions(&self.client_versions)?,
277
12457
            server_versions: mk_versions(&self.server_versions)?,
278
12457
            proto_statuses,
279
12457
            params: self.params.clone(),
280
12457
            voting_delay,
281
12457
            consensus_method: (consensus_method,),
282
12457
            consensus_methods: NotPresent,
283
12457
            published: NotPresent,
284
12457
            known_flags: DocRelayFlags::new_empty_unknown_discarded(),
285
12457
            shared_rand,
286
12457
            __non_exhaustive: (),
287
        };
288

            
289
12457
        let footer = ConsensusFooterFields {
290
12457
            bandwidth_weights: self.weights.clone(),
291
12457
            __non_exhaustive: (),
292
12457
        };
293

            
294
12457
        let mut relays = self.relays.clone();
295
902804
        relays.sort_by_key(|r| *r.rsa_identity());
296
        // TODO: check for duplicates?
297

            
298
12457
        Ok(Consensus {
299
12457
            flavor: self.flavor,
300
12457
            preamble,
301
12457
            voters: self.voters.clone(),
302
12457
            relays,
303
12457
            footer,
304
12457
        })
305
12457
    }
306
}
307

            
308
/// Builder object for constructing a [`ConsensusAuthorityEntry`]
309
pub struct VoterInfoBuilder {
310
    /// See [`DirSource::nickname`]
311
    nickname: Option<String>,
312
    /// See [`DirSource::identity`]
313
    identity: Option<RsaIdentity>,
314
    /// See [`DirSource::ip`]
315
    ip: Option<IpAddr>,
316
    /// See [`ConsensusAuthorityEntry::contact`]
317
    contact: Option<String>,
318
    /// See [`ConsensusAuthorityEntry::vote_digest`]
319
    vote_digest: Vec<u8>,
320
    /// See [`DirSource::or_port`]
321
    or_port: u16,
322
    /// See [`DirSource::dir_port`]
323
    dir_port: u16,
324
}
325

            
326
impl VoterInfoBuilder {
327
    /// Construct a new VoterInfoBuilder.
328
2
    pub(crate) fn new() -> Self {
329
2
        VoterInfoBuilder {
330
2
            nickname: None,
331
2
            identity: None,
332
2
            ip: None,
333
2
            contact: None,
334
2
            vote_digest: Vec::new(),
335
2
            or_port: 0,
336
2
            dir_port: 0,
337
2
        }
338
2
    }
339

            
340
    /// Set a nickname.
341
    ///
342
    /// This value is required.
343
2
    pub fn nickname(&mut self, nickname: String) -> &mut Self {
344
2
        self.nickname = Some(nickname);
345
2
        self
346
2
    }
347

            
348
    /// Set an RSA identity.
349
    ///
350
    /// This value is required.
351
2
    pub fn identity(&mut self, identity: RsaIdentity) -> &mut Self {
352
2
        self.identity = Some(identity);
353
2
        self
354
2
    }
355

            
356
    /// Set a IP-valued address.
357
    ///
358
    /// This value is required.
359
2
    pub fn ip(&mut self, ip: IpAddr) -> &mut Self {
360
2
        self.ip = Some(ip);
361
2
        self
362
2
    }
363

            
364
    /// Set a contact line for this voter.
365
    ///
366
    /// This value is optional.
367
2
    pub fn contact(&mut self, contact: String) -> &mut Self {
368
2
        self.contact = Some(contact);
369
2
        self
370
2
    }
371

            
372
    /// Set the declared vote digest for this voter within a consensus.
373
    ///
374
    /// This value is required.
375
2
    pub fn vote_digest(&mut self, vote_digest: Vec<u8>) -> &mut Self {
376
2
        self.vote_digest = vote_digest;
377
2
        self
378
2
    }
379

            
380
    /// Set the declared OrPort for this voter.
381
2
    pub fn or_port(&mut self, or_port: u16) -> &mut Self {
382
2
        self.or_port = or_port;
383
2
        self
384
2
    }
385

            
386
    /// Set the declared DirPort for this voter.
387
2
    pub fn dir_port(&mut self, dir_port: u16) -> &mut Self {
388
2
        self.dir_port = dir_port;
389
2
        self
390
2
    }
391

            
392
    /// Add the voter that we've been building into the in-progress
393
    /// consensus of `builder`.
394
2
    pub fn build(&self, builder: &mut ConsensusBuilder) -> Result<()> {
395
2
        let nickname = self
396
2
            .nickname
397
2
            .as_ref()
398
2
            .ok_or(Error::CannotBuild("Missing nickname"))?
399
2
            .parse()
400
2
            .map_err(|_| Error::CannotBuild("Invalid nickname"))?;
401
2
        let identity = self
402
2
            .identity
403
2
            .ok_or(Error::CannotBuild("Missing identity"))?
404
2
            .into();
405
2
        let ip = self.ip.ok_or(Error::CannotBuild("Missing IP"))?;
406
2
        let contact = self
407
2
            .contact
408
2
            .as_ref()
409
2
            .ok_or(Error::CannotBuild("Missing contact"))?
410
2
            .parse()
411
2
            .map_err(|_| Error::CannotBuild("Invalid contact"))?;
412
2
        if self.vote_digest.is_empty() {
413
            return Err(Error::CannotBuild("Missing vote digest"));
414
2
        }
415
2
        let dir_source = DirSource {
416
2
            nickname,
417
2
            identity,
418
2
            hostname: "deprecated-builder.invalid".parse().expect("statically valid"),
419
2
            ip,
420
2
            dir_port: self.dir_port,
421
2
            or_port: self.or_port,
422
2
            __non_exhaustive: (),
423
2
        };
424

            
425
2
        let info = ConsensusAuthorityEntry {
426
2
            dir_source,
427
2
            contact,
428
2
            vote_digest: self.vote_digest.clone().into(),
429
2
            __non_exhaustive: (),
430
2
        };
431
2
        builder.voters.push(info);
432
2
        Ok(())
433
2
    }
434
}
435

            
436
#[cfg(test)]
437
mod test {
438
    // @@ begin test lint list maintained by maint/add_warning @@
439
    #![allow(clippy::bool_assert_comparison)]
440
    #![allow(clippy::clone_on_copy)]
441
    #![allow(clippy::dbg_macro)]
442
    #![allow(clippy::mixed_attributes_style)]
443
    #![allow(clippy::print_stderr)]
444
    #![allow(clippy::print_stdout)]
445
    #![allow(clippy::single_char_pattern)]
446
    #![allow(clippy::unwrap_used)]
447
    #![allow(clippy::unchecked_time_subtraction)]
448
    #![allow(clippy::useless_vec)]
449
    #![allow(clippy::needless_pass_by_value)]
450
    #![allow(clippy::string_slice)] // See arti#2571
451
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
452
    use super::*;
453
    use crate::types::relay_flags::RelayFlag;
454

            
455
    use std::net::SocketAddr;
456
    use web_time_compat::{Duration, SystemTime, SystemTimeExt};
457

            
458
    #[test]
459
    fn consensus() {
460
        let now = SystemTime::get();
461
        let one_hour = Duration::new(3600, 0);
462

            
463
        let mut builder = crate::doc::netstatus::MdConsensus::builder();
464
        builder
465
            .lifetime(Lifetime::new(now, now + one_hour, now + 2 * one_hour).unwrap())
466
            .add_client_version("0.4.5.8".into())
467
            .add_relay_version("0.4.5.9".into())
468
            .required_client_protos("DirCache=2 LinkAuth=3".parse().unwrap())
469
            .required_relay_protos("DirCache=1".parse().unwrap())
470
            .recommended_client_protos("DirCache=6".parse().unwrap())
471
            .recommended_relay_protos("DirCache=5".parse().unwrap())
472
            .param("wombat", 7)
473
            .param("knish", 1212)
474
            .voting_delay(7, 8)
475
            .consensus_method(32)
476
            .shared_rand_prev(1, SharedRandVal([b'x'; 32]), None)
477
            .shared_rand_cur(1, SharedRandVal([b'y'; 32]), None)
478
            .weight("Wxy", 303)
479
            .weight("Wow", 999);
480

            
481
        builder
482
            .voter()
483
            .nickname("Fuzzy".into())
484
            .identity([15; 20].into())
485
            .ip("10.0.0.200".parse().unwrap())
486
            .contact("admin@fuzzy.example.com".into())
487
            .vote_digest((*b"1234").into())
488
            .or_port(9001)
489
            .dir_port(9101)
490
            .build(&mut builder)
491
            .unwrap();
492

            
493
        builder
494
            .rs()
495
            .nickname("Fred".into())
496
            .identity([155; 20].into())
497
            .add_or_port(SocketAddr::from(([10, 0, 0, 60], 9100)))
498
            .add_or_port("[f00f::1]:9200".parse().unwrap())
499
            .doc_digest([99; 32])
500
            .set_flags(RelayFlag::Fast)
501
            .add_flags(RelayFlag::Stable | RelayFlag::V2Dir)
502
            .version("Arti 0.0.0".into())
503
            .protos("DirCache=7".parse().unwrap())
504
            .build_into(&mut builder)
505
            .unwrap();
506

            
507
        let _cons = builder.testing_consensus().unwrap();
508

            
509
        // TODO: Check actual members of `cons` above.
510
    }
511
}