1
//! Parsing implementation for networkstatus documents.
2
//!
3
//! In Tor, a networkstatus documents describes a complete view of the
4
//! relays in the network: how many there are, how to contact them,
5
//! and so forth.
6
//!
7
//! A networkstatus document can either be a "votes" -- an authority's
8
//! view of the network, used as input to the voting process -- or a
9
//! "consensus" -- a combined view of the network based on multiple
10
//! authorities' votes, and signed by multiple authorities.
11
//!
12
//! A consensus document can itself come in two different flavors: a
13
//! "ns"-flavored consensus has references to router descriptors, and
14
//! a "microdesc"-flavored consensus has references to
15
//! microdescriptors.
16
//!
17
//! To keep an up-to-date view of the network, clients download
18
//! microdescriptor-flavored consensuses periodically, and then
19
//! download whatever microdescriptors the consensus lists that the
20
//! client doesn't already have.
21
//!
22
//! For full information about the network status format, see
23
//! [dir-spec.txt](https://spec.torproject.org/dir-spec).
24
//!
25
//! # Limitations
26
//!
27
//! NOTE: The consensus format has changes time, using a
28
//! "consensus-method" mechanism.  This module is does not yet handle all
29
//! all historical consensus-methods.
30
//!
31
//! NOTE: This module _does_ parse some fields that are not in current
32
//! use, like relay nicknames, and the "published" times on
33
//! microdescriptors. We should probably decide whether we actually
34
//! want to do this.
35
//!
36
//! TODO: This module doesn't implement vote parsing at all yet.
37
//!
38
//! TODO: This module doesn't implement ns-flavored consensuses.
39
//!
40
//! TODO: More testing is needed!
41
//!
42
//! TODO: There should be accessor functions for most of the fields here.
43
//! As with the other tor-netdoc types, I'm deferring those till I know what
44
//! they should be.
45

            
46
mod rs;
47

            
48
#[cfg(feature = "build_docs")]
49
mod build;
50

            
51
use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
52
use crate::parse::keyword::Keyword;
53
use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
54
use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
55
use crate::types::misc::*;
56
use crate::util::private::Sealed;
57
use crate::util::PeekableIterator;
58
use crate::{Error, NetdocErrorKind as EK, Pos, Result};
59
use std::collections::{HashMap, HashSet};
60
use std::result::Result as StdResult;
61
use std::sync::Arc;
62
use std::{net, result, time};
63
use tor_error::{internal, HasKind};
64
use tor_protover::Protocols;
65

            
66
use bitflags::bitflags;
67
use digest::Digest;
68
use once_cell::sync::Lazy;
69
use tor_checkable::{timed::TimerangeBound, ExternallySigned};
70
use tor_llcrypto as ll;
71
use tor_llcrypto::pk::rsa::RsaIdentity;
72

            
73
use serde::{Deserialize, Deserializer};
74

            
75
#[cfg(feature = "build_docs")]
76
pub use build::ConsensusBuilder;
77
#[cfg(feature = "build_docs")]
78
pub use rs::build::RouterStatusBuilder;
79

            
80
pub use rs::MdConsensusRouterStatus;
81
#[cfg(feature = "ns_consensus")]
82
pub use rs::NsConsensusRouterStatus;
83
use void::ResultVoidExt as _;
84

            
85
/// The lifetime of a networkstatus document.
86
///
87
/// In a consensus, this type describes when the consensus may safely
88
/// be used.  In a vote, this type describes the proposed lifetime for a
89
/// consensus.
90
#[derive(Clone, Debug)]
91
pub struct Lifetime {
92
    /// Time at which the document becomes valid
93
    valid_after: time::SystemTime,
94
    /// Time after which there is expected to be a better version
95
    /// of this consensus
96
    fresh_until: time::SystemTime,
97
    /// Time after which this consensus is expired.
98
    ///
99
    /// (In practice, Tor clients will keep using documents for a while
100
    /// after this expiration time, if no better one can be found.)
101
    valid_until: time::SystemTime,
102
}
103

            
104
impl Lifetime {
105
    /// Construct a new Lifetime.
106
10309
    pub fn new(
107
10309
        valid_after: time::SystemTime,
108
10309
        fresh_until: time::SystemTime,
109
10309
        valid_until: time::SystemTime,
110
10309
    ) -> Result<Self> {
111
10309
        if valid_after < fresh_until && fresh_until < valid_until {
112
10309
            Ok(Lifetime {
113
10309
                valid_after,
114
10309
                fresh_until,
115
10309
                valid_until,
116
10309
            })
117
        } else {
118
            Err(EK::InvalidLifetime.err())
119
        }
120
10309
    }
121
    /// Return time when this consensus first becomes valid.
122
    ///
123
    /// (You might see a consensus a little while before this time,
124
    /// since voting tries to finish up before the.)
125
79618
    pub fn valid_after(&self) -> time::SystemTime {
126
79618
        self.valid_after
127
79618
    }
128
    /// Return time when this consensus is no longer fresh.
129
    ///
130
    /// You can use the consensus after this time, but there is (or is
131
    /// supposed to be) a better one by this point.
132
39856
    pub fn fresh_until(&self) -> time::SystemTime {
133
39856
        self.fresh_until
134
39856
    }
135
    /// Return the time when this consensus is no longer valid.
136
    ///
137
    /// You should try to get a better consensus after this time,
138
    /// though it's okay to keep using this one if no more recent one
139
    /// can be found.
140
5640
    pub fn valid_until(&self) -> time::SystemTime {
141
5640
        self.valid_until
142
5640
    }
143
    /// Return true if this consensus is officially valid at the provided time.
144
470
    pub fn valid_at(&self, when: time::SystemTime) -> bool {
145
470
        self.valid_after <= when && when <= self.valid_until
146
470
    }
147

            
148
    /// Return the voting period implied by this lifetime.
149
    ///
150
    /// (The "voting period" is the amount of time in between when a consensus first
151
    /// becomes valid, and when the next consensus is expected to become valid)
152
39292
    pub fn voting_period(&self) -> time::Duration {
153
39292
        let valid_after = self.valid_after();
154
39292
        let fresh_until = self.fresh_until();
155
39292
        fresh_until
156
39292
            .duration_since(valid_after)
157
39292
            .expect("Mis-formed lifetime")
158
39292
    }
159
}
160

            
161
/// A set of named network parameters.
162
///
163
/// These are used to describe current settings for the Tor network,
164
/// current weighting parameters for path selection, and so on.  They're
165
/// encoded with a space-separated K=V format.
166
///
167
/// A `NetParams<i32>` is part of the validated directory manager configuration,
168
/// where it is built (in the builder-pattern sense) from a transparent HashMap.
169
#[derive(Debug, Clone, Default, Eq, PartialEq)]
170
pub struct NetParams<T> {
171
    /// Map from keys to values.
172
    params: HashMap<String, T>,
173
}
174

            
175
impl<T> NetParams<T> {
176
    /// Create a new empty list of NetParams.
177
    #[allow(unused)]
178
14954
    pub fn new() -> Self {
179
14954
        NetParams {
180
14954
            params: HashMap::new(),
181
14954
        }
182
14954
    }
183
    /// Retrieve a given network parameter, if it is present.
184
142775
    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
185
142775
        self.params.get(v.as_ref())
186
142775
    }
187
    /// Return an iterator over all key value pairs in an arbitrary order.
188
15728
    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
189
15728
        self.params.iter()
190
15728
    }
191
    /// Set or replace the value of a network parameter.
192
7630
    pub fn set(&mut self, k: String, v: T) {
193
7630
        self.params.insert(k, v);
194
7630
    }
195
}
196

            
197
impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
198
5728
    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
199
5728
        NetParams {
200
5745
            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
201
5728
        }
202
5728
    }
203
}
204

            
205
impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
206
1386
    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
207
1386
        self.params.extend(iter);
208
1386
    }
209
}
210

            
211
impl<'de, T> Deserialize<'de> for NetParams<T>
212
where
213
    T: Deserialize<'de>,
214
{
215
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
216
    where
217
        D: Deserializer<'de>,
218
    {
219
        let params = HashMap::deserialize(deserializer)?;
220
        Ok(NetParams { params })
221
    }
222
}
223

            
224
/// A list of subprotocol versions that implementors should/must provide.
225
///
226
/// Each consensus has two of these: one for relays, and one for clients.
227
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
228
pub struct ProtoStatus {
229
    /// Set of protocols that are recommended; if we're missing a protocol
230
    /// in this list we should warn the user.
231
    recommended: Protocols,
232
    /// Set of protocols that are required; if we're missing a protocol
233
    /// in this list we should refuse to start.
234
    required: Protocols,
235
}
236

            
237
impl ProtoStatus {
238
    /// Check whether the list of supported protocols
239
    /// is sufficient to satisfy this list of recommendations and requirements.
240
    ///
241
    /// If any required protocol is missing, returns [`ProtocolSupportError::MissingRequired`].
242
    ///
243
    /// Otherwise, if no required protocol is missing, but some recommended protocol is missing,
244
    /// returns [`ProtocolSupportError::MissingRecommended`].
245
    ///
246
    /// Otherwise, if no recommended or required protocol is missing, returns `Ok(())`.
247
194
    pub fn check_protocols(
248
194
        &self,
249
194
        supported_protocols: &Protocols,
250
194
    ) -> StdResult<(), ProtocolSupportError> {
251
194
        // Required protocols take precedence, so we check them first.
252
194
        let missing_required = self.required.difference(supported_protocols);
253
194
        if !missing_required.is_empty() {
254
96
            return Err(ProtocolSupportError::MissingRequired(missing_required));
255
98
        }
256
98
        let missing_recommended = self.recommended.difference(supported_protocols);
257
98
        if !missing_recommended.is_empty() {
258
49
            return Err(ProtocolSupportError::MissingRecommended(
259
49
                missing_recommended,
260
49
            ));
261
49
        }
262
49

            
263
49
        Ok(())
264
194
    }
265
}
266

            
267
/// A subprotocol that is recommended or required in the consensus was not present.
268
#[derive(Clone, Debug, thiserror::Error)]
269
#[cfg_attr(test, derive(PartialEq))]
270
#[non_exhaustive]
271
pub enum ProtocolSupportError {
272
    /// At least one required protocol was not in our list of supported protocols.
273
    #[error("Required protocols are not implemented: {0}")]
274
    MissingRequired(Protocols),
275

            
276
    /// At least one recommended protocol was not in our list of supported protocols.
277
    ///
278
    /// Also implies that no _required_ protocols were missing.
279
    #[error("Recommended protocols are not implemented: {0}")]
280
    MissingRecommended(Protocols),
281
}
282

            
283
impl ProtocolSupportError {
284
    /// Return true if the suggested behavior for this error is a shutdown.
285
    pub fn should_shutdown(&self) -> bool {
286
        matches!(self, Self::MissingRequired(_))
287
    }
288
}
289

            
290
impl HasKind for ProtocolSupportError {
291
    fn kind(&self) -> tor_error::ErrorKind {
292
        tor_error::ErrorKind::SoftwareDeprecated
293
    }
294
}
295

            
296
/// A set of recommended and required protocols when running
297
/// in various scenarios.
298
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
299
pub struct ProtoStatuses {
300
    /// Lists of recommended and required subprotocol versions for clients
301
    client: ProtoStatus,
302
    /// Lists of recommended and required subprotocol versions for relays
303
    relay: ProtoStatus,
304
}
305

            
306
impl ProtoStatuses {
307
    /// Return the list of recommended and required protocols for running as a client.
308
188
    pub fn client(&self) -> &ProtoStatus {
309
188
        &self.client
310
188
    }
311

            
312
    /// Return the list of recommended and required protocols for running as a relay.
313
    pub fn relay(&self) -> &ProtoStatus {
314
        &self.relay
315
    }
316
}
317

            
318
/// A recognized 'flavor' of consensus document.
319
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
320
#[non_exhaustive]
321
pub enum ConsensusFlavor {
322
    /// A "microdesc"-flavored consensus.  This is the one that
323
    /// clients and relays use today.
324
    Microdesc,
325
    /// A "networkstatus"-flavored consensus.  It's used for
326
    /// historical and network-health purposes.  Instead of listing
327
    /// microdescriptor digests, it lists digests of full relay
328
    /// descriptors.
329
    Ns,
330
}
331

            
332
impl ConsensusFlavor {
333
    /// Return the name of this consensus flavor.
334
1269
    pub fn name(&self) -> &'static str {
335
1269
        match self {
336
            ConsensusFlavor::Ns => "ns",
337
1269
            ConsensusFlavor::Microdesc => "microdesc",
338
        }
339
1269
    }
340
    /// Try to find the flavor whose name is `name`.
341
    ///
342
    /// For historical reasons, an unnamed flavor indicates an "Ns"
343
    /// document.
344
343
    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
345
343
        match name {
346
341
            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
347
2
            Some("ns") | None => Ok(ConsensusFlavor::Ns),
348
            Some(other) => {
349
                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
350
            }
351
        }
352
343
    }
353
}
354

            
355
/// The signature of a single directory authority on a networkstatus document.
356
#[allow(dead_code)]
357
#[cfg_attr(
358
    feature = "dangerous-expose-struct-fields",
359
    visible::StructFields(pub),
360
    non_exhaustive
361
)]
362
#[derive(Debug, Clone)]
363
pub struct Signature {
364
    /// The name of the digest algorithm used to make the signature.
365
    ///
366
    /// Currently sha1 and sh256 are recognized.  Here we only support
367
    /// sha256.
368
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
369
    digestname: String,
370
    /// Fingerprints of the keys for the authority that made
371
    /// this signature.
372
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
373
    key_ids: AuthCertKeyIds,
374
    /// The signature itself.
375
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
376
    signature: Vec<u8>,
377
}
378

            
379
/// A collection of signatures that can be checked on a networkstatus document
380
#[allow(dead_code)]
381
#[cfg_attr(
382
    feature = "dangerous-expose-struct-fields",
383
    visible::StructFields(pub),
384
    non_exhaustive
385
)]
386
#[derive(Debug, Clone)]
387
pub struct SignatureGroup {
388
    /// The sha256 of the document itself
389
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
390
    sha256: Option<[u8; 32]>,
391
    /// The sha1 of the document itself
392
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
393
    sha1: Option<[u8; 20]>,
394
    /// The signatures listed on the document.
395
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
396
    signatures: Vec<Signature>,
397
}
398

            
399
/// A shared random value produced by the directory authorities.
400
#[derive(
401
    Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
402
)]
403
// (This doesn't need to use CtByteArray; we don't really need to compare these.)
404
pub struct SharedRandVal([u8; 32]);
405

            
406
/// A shared-random value produced by the directory authorities,
407
/// along with meta-information about that value.
408
#[allow(dead_code)]
409
#[cfg_attr(
410
    feature = "dangerous-expose-struct-fields",
411
    visible::StructFields(pub),
412
    visibility::make(pub),
413
    non_exhaustive
414
)]
415
#[derive(Debug, Clone)]
416
pub struct SharedRandStatus {
417
    /// How many authorities revealed shares that contributed to this value.
418
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
419
    n_reveals: u8,
420
    /// The current random value.
421
    ///
422
    /// The properties of the secure shared-random system guarantee
423
    /// that this value isn't predictable before it first becomes
424
    /// live, and that a hostile party could not have forced it to
425
    /// have any more than a small number of possible random values.
426
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
427
    value: SharedRandVal,
428

            
429
    /// The time when this SharedRandVal becomes (or became) the latest.
430
    ///
431
    /// (This is added per proposal 342, assuming that gets accepted.)
432
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
433
    timestamp: Option<time::SystemTime>,
434
}
435

            
436
/// Parts of the networkstatus header that are present in every networkstatus.
437
///
438
/// NOTE: this type is separate from the header parts that are only in
439
/// votes or only in consensuses, even though we don't implement votes yet.
440
#[allow(dead_code)]
441
#[cfg_attr(
442
    feature = "dangerous-expose-struct-fields",
443
    visible::StructFields(pub),
444
    visibility::make(pub),
445
    non_exhaustive
446
)]
447
#[derive(Debug, Clone)]
448
struct CommonHeader {
449
    /// What kind of consensus document is this?  Absent in votes and
450
    /// in ns-flavored consensuses.
451
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
452
    flavor: ConsensusFlavor,
453
    /// Over what time is this consensus valid?  (For votes, this is
454
    /// the time over which the voted-upon consensus should be valid.)
455
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
456
    lifetime: Lifetime,
457
    /// List of recommended Tor client versions.
458
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
459
    client_versions: Vec<String>,
460
    /// List of recommended Tor relay versions.
461
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
462
    relay_versions: Vec<String>,
463
    /// Lists of recommended and required subprotocols.
464
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
465
    proto_statuses: Arc<ProtoStatuses>,
466
    /// Declared parameters for tunable settings about how to the
467
    /// network should operator. Some of these adjust timeouts and
468
    /// whatnot; some features things on and off.
469
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
470
    params: NetParams<i32>,
471
    /// How long in seconds should voters wait for votes and
472
    /// signatures (respectively) to propagate?
473
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
474
    voting_delay: Option<(u32, u32)>,
475
}
476

            
477
/// The header of a consensus networkstatus.
478
#[allow(dead_code)]
479
#[cfg_attr(
480
    feature = "dangerous-expose-struct-fields",
481
    visible::StructFields(pub),
482
    visibility::make(pub),
483
    non_exhaustive
484
)]
485
#[derive(Debug, Clone)]
486
struct ConsensusHeader {
487
    /// Header fields common to votes and consensuses
488
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
489
    hdr: CommonHeader,
490
    /// What "method" was used to produce this consensus?  (A
491
    /// consensus method is a version number used by authorities to
492
    /// upgrade the consensus algorithm.)
493
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
494
    consensus_method: u32,
495
    /// Global shared-random value for the previous shared-random period.
496
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
497
    shared_rand_prev: Option<SharedRandStatus>,
498
    /// Global shared-random value for the current shared-random period.
499
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
500
    shared_rand_cur: Option<SharedRandStatus>,
501
}
502

            
503
/// Description of an authority's identity and address.
504
///
505
/// (Corresponds to a dir-source line.)
506
#[allow(dead_code)]
507
#[cfg_attr(
508
    feature = "dangerous-expose-struct-fields",
509
    visible::StructFields(pub),
510
    visibility::make(pub),
511
    non_exhaustive
512
)]
513
#[derive(Debug, Clone)]
514
struct DirSource {
515
    /// human-readable nickname for this authority.
516
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
517
    nickname: String,
518
    /// Fingerprint for the _authority_ identity key of this
519
    /// authority.
520
    ///
521
    /// This is the same key as the one that signs the authority's
522
    /// certificates.
523
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
524
    identity: RsaIdentity,
525
    /// IP address for the authority
526
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
527
    ip: net::IpAddr,
528
    /// HTTP directory port for this authority
529
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
530
    dir_port: u16,
531
    /// OR port for this authority.
532
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
533
    or_port: u16,
534
}
535

            
536
bitflags! {
537
    /// A set of recognized directory flags on a single relay.
538
    ///
539
    /// These flags come from a consensus directory document, and are
540
    /// used to describe what the authorities believe about the relay.
541
    /// If the document contained any flags that we _didn't_ recognize,
542
    /// they are not listed in this type.
543
    ///
544
    /// The bit values used to represent the flags have no meaning;
545
    /// they may change between releases of this crate.  Relying on their
546
    /// values may void your semver guarantees.
547
    #[derive(Clone, Copy, Debug)]
548
    pub struct RelayFlags: u16 {
549
        /// Is this a directory authority?
550
        const AUTHORITY = (1<<0);
551
        /// Is this relay marked as a bad exit?
552
        ///
553
        /// Bad exits can be used as intermediate relays, but not to
554
        /// deliver traffic.
555
        const BAD_EXIT = (1<<1);
556
        /// Is this relay marked as an exit for weighting purposes?
557
        const EXIT = (1<<2);
558
        /// Is this relay considered "fast" above a certain threshold?
559
        const FAST = (1<<3);
560
        /// Is this relay suitable for use as a guard relay?
561
        ///
562
        /// Clients choose their their initial relays from among the set
563
        /// of Guard relays.
564
        const GUARD = (1<<4);
565
        /// Does this relay participate on the onion service directory
566
        /// ring?
567
        const HSDIR = (1<<5);
568
        /// Set if this relay is considered "middle only", not suitable to run
569
        /// as an exit or guard relay.
570
        ///
571
        /// Note that this flag is only used by authorities as part of
572
        /// the voting process; clients do not and should not act
573
        /// based on whether it is set.
574
        const MIDDLE_ONLY = (1<<6);
575
        /// If set, there is no consensus for the ed25519 key for this relay.
576
        const NO_ED_CONSENSUS = (1<<7);
577
        /// Is this relay considered "stable" enough for long-lived circuits?
578
        const STABLE = (1<<8);
579
        /// Set if the authorities are requesting a fresh descriptor for
580
        /// this relay.
581
        const STALE_DESC = (1<<9);
582
        /// Set if this relay is currently running.
583
        ///
584
        /// This flag can appear in votes, but in consensuses, every relay
585
        /// is assumed to be running.
586
        const RUNNING = (1<<10);
587
        /// Set if this relay is considered "valid" -- allowed to be on
588
        /// the network.
589
        ///
590
        /// This flag can appear in votes, but in consensuses, every relay
591
        /// is assumed to be valid.
592
        const VALID = (1<<11);
593
        /// Set if this relay supports a currently recognized version of the
594
        /// directory protocol.
595
        const V2DIR = (1<<12);
596
    }
597
}
598

            
599
/// Recognized weight fields on a single relay in a consensus
600
#[non_exhaustive]
601
#[derive(Debug, Clone, Copy)]
602
pub enum RelayWeight {
603
    /// An unmeasured weight for a relay.
604
    Unmeasured(u32),
605
    /// An measured weight for a relay.
606
    Measured(u32),
607
}
608

            
609
impl RelayWeight {
610
    /// Return true if this weight is the result of a successful measurement
611
19421
    pub fn is_measured(&self) -> bool {
612
19421
        matches!(self, RelayWeight::Measured(_))
613
19421
    }
614
    /// Return true if this weight is nonzero
615
18011
    pub fn is_nonzero(&self) -> bool {
616
18011
        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
617
18011
    }
618
}
619

            
620
/// All information about a single authority, as represented in a consensus
621
#[allow(dead_code)]
622
#[cfg_attr(
623
    feature = "dangerous-expose-struct-fields",
624
    visible::StructFields(pub),
625
    visibility::make(pub),
626
    non_exhaustive
627
)]
628
#[derive(Debug, Clone)]
629
struct ConsensusVoterInfo {
630
    /// Contents of the dirsource line about an authority
631
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
632
    dir_source: DirSource,
633
    /// Human-readable contact information about the authority
634
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
635
    contact: String,
636
    /// Digest of the vote that the authority cast to contribute to
637
    /// this consensus.
638
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
639
    vote_digest: Vec<u8>,
640
}
641

            
642
/// The signed footer of a consensus netstatus.
643
#[allow(dead_code)]
644
#[cfg_attr(
645
    feature = "dangerous-expose-struct-fields",
646
    visible::StructFields(pub),
647
    visibility::make(pub),
648
    non_exhaustive
649
)]
650
#[derive(Debug, Clone)]
651
struct Footer {
652
    /// Weights to be applied to certain classes of relays when choosing
653
    /// for different roles.
654
    ///
655
    /// For example, we want to avoid choosing exits for non-exit
656
    /// roles when overall the proportion of exits is small.
657
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
658
    weights: NetParams<i32>,
659
}
660

            
661
/// Trait to parse a single relay as listed in a consensus document.
662
///
663
/// TODO(nickm): I'd rather not have this trait be public, but I haven't yet
664
/// figured out how to make it private.
665
pub trait ParseRouterStatus: Sized + Sealed {
666
    /// Parse this object from a `Section` object containing its
667
    /// elements.
668
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Self>;
669

            
670
    /// Return the networkstatus consensus flavor in which this
671
    /// routerstatus appears.
672
    fn flavor() -> ConsensusFlavor;
673
}
674

            
675
/// Represents a single relay as listed in a consensus document.
676
///
677
/// Not implementable outside of the `tor-netdoc` crate.
678
pub trait RouterStatus: Sealed {
679
    /// A digest of the document that's identified by this RouterStatus.
680
    type DocumentDigest: Clone;
681

            
682
    /// Return RSA identity for the relay described by this RouterStatus
683
    fn rsa_identity(&self) -> &RsaIdentity;
684

            
685
    /// Return the digest of the document identified by this
686
    /// routerstatus.
687
    fn doc_digest(&self) -> &Self::DocumentDigest;
688
}
689

            
690
/// A single microdescriptor consensus netstatus
691
///
692
/// TODO: This should possibly turn into a parameterized type, to represent
693
/// votes and ns consensuses.
694
#[allow(dead_code)]
695
#[cfg_attr(
696
    feature = "dangerous-expose-struct-fields",
697
    visible::StructFields(pub),
698
    non_exhaustive
699
)]
700
#[derive(Debug, Clone)]
701
pub struct Consensus<RS> {
702
    /// Part of the header shared by all consensus types.
703
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
704
    header: ConsensusHeader,
705
    /// List of voters whose votes contributed to this consensus.
706
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
707
    voters: Vec<ConsensusVoterInfo>,
708
    /// A list of routerstatus entries for the relays on the network,
709
    /// with one entry per relay.
710
    ///
711
    /// These are currently ordered by the router's RSA identity, but this is not
712
    /// to be relied on, since we may want to even abolish RSA at some point!
713
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
714
    relays: Vec<RS>,
715
    /// Footer for the consensus object.
716
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
717
    footer: Footer,
718
}
719

            
720
/// A consensus document that lists relays along with their
721
/// microdescriptor documents.
722
pub type MdConsensus = Consensus<MdConsensusRouterStatus>;
723

            
724
/// An MdConsensus that has been parsed and checked for timeliness,
725
/// but not for signatures.
726
pub type UnvalidatedMdConsensus = UnvalidatedConsensus<MdConsensusRouterStatus>;
727

            
728
/// An MdConsensus that has been parsed but not checked for signatures
729
/// and timeliness.
730
pub type UncheckedMdConsensus = UncheckedConsensus<MdConsensusRouterStatus>;
731

            
732
#[cfg(feature = "ns_consensus")]
733
/// A consensus document that lists relays along with their
734
/// router descriptor documents.
735
pub type NsConsensus = Consensus<NsConsensusRouterStatus>;
736

            
737
#[cfg(feature = "ns_consensus")]
738
/// An NsConsensus that has been parsed and checked for timeliness,
739
/// but not for signatures.
740
pub type UnvalidatedNsConsensus = UnvalidatedConsensus<NsConsensusRouterStatus>;
741

            
742
#[cfg(feature = "ns_consensus")]
743
/// An NsConsensus that has been parsed but not checked for signatures
744
/// and timeliness.
745
pub type UncheckedNsConsensus = UncheckedConsensus<NsConsensusRouterStatus>;
746

            
747
impl<RS> Consensus<RS> {
748
    /// Return the Lifetime for this consensus.
749
60455
    pub fn lifetime(&self) -> &Lifetime {
750
60455
        &self.header.hdr.lifetime
751
60455
    }
752

            
753
    /// Return a slice of all the routerstatus entries in this consensus.
754
35298211
    pub fn relays(&self) -> &[RS] {
755
35298211
        &self.relays[..]
756
35298211
    }
757

            
758
    /// Return a mapping from keywords to integers representing how
759
    /// to weight different kinds of relays in different path positions.
760
7510
    pub fn bandwidth_weights(&self) -> &NetParams<i32> {
761
7510
        &self.footer.weights
762
7510
    }
763

            
764
    /// Return the map of network parameters that this consensus advertises.
765
7512
    pub fn params(&self) -> &NetParams<i32> {
766
7512
        &self.header.hdr.params
767
7512
    }
768

            
769
    /// Return the latest shared random value, if the consensus
770
    /// contains one.
771
29854
    pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
772
29854
        self.header.shared_rand_cur.as_ref()
773
29854
    }
774

            
775
    /// Return the previous shared random value, if the consensus
776
    /// contains one.
777
29854
    pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
778
29854
        self.header.shared_rand_prev.as_ref()
779
29854
    }
780

            
781
    /// Return a [`ProtoStatus`] that lists the network's current requirements and
782
    /// recommendations for the list of protocols that every relay must implement.  
783
135
    pub fn relay_protocol_status(&self) -> &ProtoStatus {
784
135
        &self.header.hdr.proto_statuses.relay
785
135
    }
786

            
787
    /// Return a [`ProtoStatus`] that lists the network's current requirements and
788
    /// recommendations for the list of protocols that every client must implement.
789
    pub fn client_protocol_status(&self) -> &ProtoStatus {
790
        &self.header.hdr.proto_statuses.client
791
    }
792

            
793
    /// Return a set of all known [`ProtoStatus`] values.
794
2
    pub fn protocol_statuses(&self) -> &Arc<ProtoStatuses> {
795
2
        &self.header.hdr.proto_statuses
796
2
    }
797
}
798

            
799
decl_keyword! {
800
    /// Keywords that can be used in votes and consensuses.
801
    // TODO: This is public because otherwise we can't use it in the
802
    // ParseRouterStatus crate.  But I'd rather find a way to make it
803
    // private.
804
    #[non_exhaustive]
805
    #[allow(missing_docs)]
806
    pub NetstatusKwd {
807
        // Header
808
        "network-status-version" => NETWORK_STATUS_VERSION,
809
        "vote-status" => VOTE_STATUS,
810
        "consensus-methods" => CONSENSUS_METHODS,
811
        "consensus-method" => CONSENSUS_METHOD,
812
        "published" => PUBLISHED,
813
        "valid-after" => VALID_AFTER,
814
        "fresh-until" => FRESH_UNTIL,
815
        "valid-until" => VALID_UNTIL,
816
        "voting-delay" => VOTING_DELAY,
817
        "client-versions" => CLIENT_VERSIONS,
818
        "server-versions" => SERVER_VERSIONS,
819
        "known-flags" => KNOWN_FLAGS,
820
        "flag-thresholds" => FLAG_THRESHOLDS,
821
        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
822
        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
823
        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
824
        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
825
        "params" => PARAMS,
826
        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
827
        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
828
        // "package" is now ignored.
829

            
830
        // header in consensus, voter section in vote?
831
        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
832
        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
833

            
834
        // Voter section (both)
835
        "dir-source" => DIR_SOURCE,
836
        "contact" => CONTACT,
837

            
838
        // voter section (vote, but not consensus)
839
        "legacy-dir-key" => LEGACY_DIR_KEY,
840
        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
841
        "shared-rand-commit" => SHARED_RAND_COMMIT,
842

            
843
        // voter section (consensus, but not vote)
844
        "vote-digest" => VOTE_DIGEST,
845

            
846
        // voter cert beginning (but only the beginning)
847
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
848

            
849
        // routerstatus
850
        "r" => RS_R,
851
        "a" => RS_A,
852
        "s" => RS_S,
853
        "v" => RS_V,
854
        "pr" => RS_PR,
855
        "w" => RS_W,
856
        "p" => RS_P,
857
        "m" => RS_M,
858
        "id" => RS_ID,
859

            
860
        // footer
861
        "directory-footer" => DIRECTORY_FOOTER,
862
        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
863
        "directory-signature" => DIRECTORY_SIGNATURE,
864
    }
865
}
866

            
867
/// Shared parts of rules for all kinds of netstatus headers
868
49
static NS_HEADER_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
869
    use NetstatusKwd::*;
870
49
    let mut rules = SectionRules::builder();
871
49
    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
872
49
    rules.add(VOTE_STATUS.rule().required().args(1..));
873
49
    rules.add(VALID_AFTER.rule().required());
874
49
    rules.add(FRESH_UNTIL.rule().required());
875
49
    rules.add(VALID_UNTIL.rule().required());
876
49
    rules.add(VOTING_DELAY.rule().args(2..));
877
49
    rules.add(CLIENT_VERSIONS.rule());
878
49
    rules.add(SERVER_VERSIONS.rule());
879
49
    rules.add(KNOWN_FLAGS.rule().required());
880
49
    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
881
49
    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
882
49
    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
883
49
    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
884
49
    rules.add(PARAMS.rule());
885
49
    rules
886
49
});
887
/// Rules for parsing the header of a consensus.
888
49
static NS_HEADER_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
889
    use NetstatusKwd::*;
890
49
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
891
49
    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
892
49
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
893
49
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
894
49
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
895
49
    rules.build()
896
49
});
897
/*
898
/// Rules for parsing the header of a vote.
899
static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
900
    use NetstatusKwd::*;
901
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
902
    rules.add(CONSENSUS_METHODS.rule().args(1..));
903
    rules.add(FLAG_THRESHOLDS.rule());
904
    rules.add(BANDWIDTH_FILE_HEADERS.rule());
905
    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
906
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
907
    rules
908
};
909
/// Rules for parsing a single voter's information in a vote.
910
static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
911
    use NetstatusKwd::*;
912
    let mut rules = SectionRules::new();
913
    rules.add(DIR_SOURCE.rule().required().args(6..));
914
    rules.add(CONTACT.rule().required());
915
    rules.add(LEGACY_DIR_KEY.rule().args(1..));
916
    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
917
    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
918
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
919
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
920
    // then comes an entire cert: When we implement vote parsing,
921
    // we should use the authcert code for handling that.
922
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
923
    rules
924
};
925
 */
926
/// Rules for parsing a single voter's information in a consensus
927
49
static NS_VOTERINFO_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
928
    use NetstatusKwd::*;
929
49
    let mut rules = SectionRules::builder();
930
49
    rules.add(DIR_SOURCE.rule().required().args(6..));
931
49
    rules.add(CONTACT.rule().required());
932
49
    rules.add(VOTE_DIGEST.rule().required());
933
49
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
934
49
    rules.build()
935
49
});
936
/// Shared rules for parsing a single routerstatus
937
49
static NS_ROUTERSTATUS_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
938
    use NetstatusKwd::*;
939
49
    let mut rules = SectionRules::builder();
940
49
    rules.add(RS_A.rule().may_repeat().args(1..));
941
49
    rules.add(RS_S.rule().required());
942
49
    rules.add(RS_V.rule());
943
49
    rules.add(RS_PR.rule().required());
944
49
    rules.add(RS_W.rule());
945
49
    rules.add(RS_P.rule().args(2..));
946
49
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
947
49
    rules
948
49
});
949

            
950
/// Rules for parsing a single routerstatus in an NS consensus
951
2
static NS_ROUTERSTATUS_RULES_NSCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
952
    use NetstatusKwd::*;
953
2
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
954
2
    rules.add(RS_R.rule().required().args(8..));
955
2
    rules.build()
956
2
});
957

            
958
/*
959
/// Rules for parsing a single routerstatus in a vote
960
static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
961
    use NetstatusKwd::*;
962
        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
963
        rules.add(RS_R.rule().required().args(8..));
964
        rules.add(RS_M.rule().may_repeat().args(2..));
965
        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
966
        rules
967
    };
968
*/
969
/// Rules for parsing a single routerstatus in a microdesc consensus
970
49
static NS_ROUTERSTATUS_RULES_MDCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
971
    use NetstatusKwd::*;
972
49
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
973
49
    rules.add(RS_R.rule().required().args(6..));
974
49
    rules.add(RS_M.rule().required().args(1..));
975
49
    rules.build()
976
49
});
977
/// Rules for parsing consensus fields from a footer.
978
49
static NS_FOOTER_RULES: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
979
    use NetstatusKwd::*;
980
49
    let mut rules = SectionRules::builder();
981
49
    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
982
49
    // consensus only
983
49
    rules.add(BANDWIDTH_WEIGHTS.rule());
984
49
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
985
49
    rules.build()
986
49
});
987

            
988
impl ProtoStatus {
989
    /// Construct a ProtoStatus from two chosen keywords in a section.
990
686
    fn from_section(
991
686
        sec: &Section<'_, NetstatusKwd>,
992
686
        recommend_token: NetstatusKwd,
993
686
        required_token: NetstatusKwd,
994
686
    ) -> Result<ProtoStatus> {
995
        /// Helper: extract a Protocols entry from an item's arguments.
996
1372
        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
997
1372
            if let Some(item) = t {
998
1372
                item.args_as_str()
999
1372
                    .parse::<Protocols>()
1372
                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
            } else {
                Ok(Protocols::new())
            }
1372
        }
686
        let recommended = parse(sec.get(recommend_token))?;
686
        let required = parse(sec.get(required_token))?;
686
        Ok(ProtoStatus {
686
            recommended,
686
            required,
686
        })
686
    }
    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
    ///
    /// Implementations may assume that relays on the network implement all the
    /// protocols in the relays' required-protocols list.  Implementations should
    /// refuse to start if they do not implement all the protocols on their own
    /// (client or relay) required-protocols list.
141
    pub fn required_protocols(&self) -> &Protocols {
141
        &self.required
141
    }
    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
    ///
    /// Implementations should warn if they do not implement all the protocols
    /// on their own (client or relay) recommended-protocols list.
    pub fn recommended_protocols(&self) -> &Protocols {
        &self.recommended
    }
}
impl<T> std::str::FromStr for NetParams<T>
where
    T: std::str::FromStr,
    T::Err: std::error::Error,
{
    type Err = Error;
12316
    fn from_str(s: &str) -> Result<Self> {
        /// Helper: parse a single K=V pair.
14446
        fn parse_pair<U>(p: &str) -> Result<(String, U)>
14446
        where
14446
            U: std::str::FromStr,
14446
            U::Err: std::error::Error,
14446
        {
14446
            let parts: Vec<_> = p.splitn(2, '=').collect();
14446
            if parts.len() != 2 {
                return Err(EK::BadArgument
                    .at_pos(Pos::at(p))
                    .with_msg("Missing = in key=value list"));
14446
            }
14446
            let num = parts[1].parse::<U>().map_err(|e| {
8
                EK::BadArgument
8
                    .at_pos(Pos::at(parts[1]))
8
                    .with_msg(e.to_string())
14446
            })?;
14438
            Ok((parts[0].to_string(), num))
14446
        }
12316
        let params = s
12316
            .split(' ')
23531
            .filter(|p| !p.is_empty())
12316
            .map(parse_pair)
12316
            .collect::<Result<HashMap<_, _>>>()?;
12308
        Ok(NetParams { params })
12316
    }
}
impl CommonHeader {
    /// Extract the CommonHeader members from a single header section.
347
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
        use NetstatusKwd::*;
        {
            // this unwrap is safe because if there is not at least one
            // token in the section, the section is unparsable.
            #[allow(clippy::unwrap_used)]
347
            let first = sec.first_item().unwrap();
347
            if first.kwd() != NETWORK_STATUS_VERSION {
2
                return Err(EK::UnexpectedToken
2
                    .with_msg(first.kwd().to_str())
2
                    .at_pos(first.pos()));
345
            }
        }
345
        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
345
        let version: u32 = ver_item.parse_arg(0)?;
345
        if version != 3 {
2
            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
343
        }
343
        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
343
        let valid_after = sec
343
            .required(VALID_AFTER)?
343
            .args_as_str()
343
            .parse::<Iso8601TimeSp>()?
343
            .into();
343
        let fresh_until = sec
343
            .required(FRESH_UNTIL)?
343
            .args_as_str()
343
            .parse::<Iso8601TimeSp>()?
343
            .into();
343
        let valid_until = sec
343
            .required(VALID_UNTIL)?
343
            .args_as_str()
343
            .parse::<Iso8601TimeSp>()?
343
            .into();
343
        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
343
        let client_versions = sec
343
            .maybe(CLIENT_VERSIONS)
343
            .args_as_str()
343
            .unwrap_or("")
343
            .split(',')
343
            .map(str::to_string)
343
            .collect();
343
        let relay_versions = sec
343
            .maybe(SERVER_VERSIONS)
343
            .args_as_str()
343
            .unwrap_or("")
343
            .split(',')
343
            .map(str::to_string)
343
            .collect();
343
        let proto_statuses = {
343
            let client = ProtoStatus::from_section(
343
                sec,
343
                RECOMMENDED_CLIENT_PROTOCOLS,
343
                REQUIRED_CLIENT_PROTOCOLS,
343
            )?;
343
            let relay = ProtoStatus::from_section(
343
                sec,
343
                RECOMMENDED_RELAY_PROTOCOLS,
343
                REQUIRED_RELAY_PROTOCOLS,
343
            )?;
343
            Arc::new(ProtoStatuses { client, relay })
        };
343
        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
343
        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
343
            let n1 = tok.parse_arg(0)?;
343
            let n2 = tok.parse_arg(1)?;
343
            Some((n1, n2))
        } else {
            None
        };
343
        Ok(CommonHeader {
343
            flavor,
343
            lifetime,
343
            client_versions,
343
            relay_versions,
343
            proto_statuses,
343
            params,
343
            voting_delay,
343
        })
347
    }
}
impl SharedRandStatus {
    /// Parse a current or previous shared rand value from a given
    /// SharedRandPreviousValue or SharedRandCurrentValue.
10
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
10
        match item.kwd() {
8
            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
            _ => {
2
                return Err(Error::from(internal!(
2
                    "wrong keyword {:?} on shared-random value",
2
                    item.kwd()
2
                ))
2
                .at_pos(item.pos()))
            }
        }
8
        let n_reveals: u8 = item.parse_arg(0)?;
8
        let val: B64 = item.parse_arg(1)?;
8
        let value = SharedRandVal(val.into_array()?);
        // Added in proposal 342
8
        let timestamp = item
8
            .parse_optional_arg::<Iso8601TimeNoSp>(2)?
8
            .map(Into::into);
8
        Ok(SharedRandStatus {
8
            n_reveals,
8
            value,
8
            timestamp,
8
        })
10
    }
    /// Return the actual shared random value.
752
    pub fn value(&self) -> &SharedRandVal {
752
        &self.value
752
    }
    /// Return the timestamp (if any) associated with this `SharedRandValue`.
1410
    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1410
        self.timestamp
1410
    }
}
impl ConsensusHeader {
    /// Parse the ConsensusHeader members from a provided section.
347
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
        use NetstatusKwd::*;
347
        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
347
        if status != "consensus" {
            return Err(EK::BadDocumentType.err());
347
        }
        // We're ignoring KNOWN_FLAGS in the consensus.
347
        let hdr = CommonHeader::from_section(sec)?;
343
        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
343
        let shared_rand_prev = sec
343
            .get(SHARED_RAND_PREVIOUS_VALUE)
343
            .map(SharedRandStatus::from_item)
343
            .transpose()?;
343
        let shared_rand_cur = sec
343
            .get(SHARED_RAND_CURRENT_VALUE)
343
            .map(SharedRandStatus::from_item)
343
            .transpose()?;
343
        Ok(ConsensusHeader {
343
            hdr,
343
            consensus_method,
343
            shared_rand_prev,
343
            shared_rand_cur,
343
        })
347
    }
}
impl DirSource {
    /// Parse a "dir-source" item
1029
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1029
        if item.kwd() != NetstatusKwd::DIR_SOURCE {
            return Err(
                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
                    .at_pos(item.pos()),
            );
1029
        }
1029
        let nickname = item.required_arg(0)?.to_string();
1029
        let identity = item.parse_arg::<Fingerprint>(1)?.into();
1029
        let ip = item.parse_arg(3)?;
1029
        let dir_port = item.parse_arg(4)?;
1029
        let or_port = item.parse_arg(5)?;
1029
        Ok(DirSource {
1029
            nickname,
1029
            identity,
1029
            ip,
1029
            dir_port,
1029
            or_port,
1029
        })
1029
    }
}
impl ConsensusVoterInfo {
    /// Parse a single ConsensusVoterInfo from a voter info section.
1029
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
        use NetstatusKwd::*;
        // this unwrap should be safe because if there is not at least one
        // token in the section, the section is unparsable.
        #[allow(clippy::unwrap_used)]
1029
        let first = sec.first_item().unwrap();
1029
        if first.kwd() != DIR_SOURCE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} at start of voter info",
                first.kwd()
            ))
            .at_pos(first.pos()));
1029
        }
1029
        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1029
        let contact = sec.required(CONTACT)?.args_as_str().to_string();
1029
        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1029

            
1029
        Ok(ConsensusVoterInfo {
1029
            dir_source,
1029
            contact,
1029
            vote_digest,
1029
        })
1029
    }
}
impl std::str::FromStr for RelayFlags {
    type Err = void::Void;
15729
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
15729
        Ok(match s {
15729
            "Authority" => RelayFlags::AUTHORITY,
14710
            "BadExit" => RelayFlags::BAD_EXIT,
14710
            "Exit" => RelayFlags::EXIT,
12858
            "Fast" => RelayFlags::FAST,
11008
            "Guard" => RelayFlags::GUARD,
9158
            "HSDir" => RelayFlags::HSDIR,
7308
            "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
7308
            "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
7308
            "Stable" => RelayFlags::STABLE,
5552
            "StaleDesc" => RelayFlags::STALE_DESC,
5552
            "Running" => RelayFlags::RUNNING,
3702
            "Valid" => RelayFlags::VALID,
1852
            "V2Dir" => RelayFlags::V2DIR,
2
            _ => RelayFlags::empty(),
        })
15729
    }
}
impl RelayFlags {
    /// Parse a relay-flags entry from an "s" line.
1852
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1852
        if item.kwd() != NetstatusKwd::RS_S {
            return Err(
                Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
                    .at_pos(item.pos()),
            );
1852
        }
1852
        // These flags are implicit.
1852
        let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1852

            
1852
        let mut prev: Option<&str> = None;
15731
        for s in item.args() {
15731
            if let Some(p) = prev {
13879
                if p >= s {
                    // Arguments out of order.
2
                    return Err(EK::BadArgument
2
                        .at_pos(item.pos())
2
                        .with_msg("Flags out of order"));
13877
                }
1852
            }
15729
            let fl = s.parse().void_unwrap();
15729
            flags |= fl;
15729
            prev = Some(s);
        }
1850
        Ok(flags)
1852
    }
}
impl Default for RelayWeight {
2
    fn default() -> RelayWeight {
2
        RelayWeight::Unmeasured(0)
2
    }
}
impl RelayWeight {
    /// Parse a routerweight from a "w" line.
1862
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1862
        if item.kwd() != NetstatusKwd::RS_W {
6
            return Err(
6
                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
6
                    .at_pos(item.pos()),
6
            );
1856
        }
1856
        let params: NetParams<u32> = item.args_as_str().parse()?;
1854
        let bw = params.params.get("Bandwidth");
1854
        let unmeas = params.params.get("Unmeasured");
1854
        let bw = match bw {
2
            None => return Ok(RelayWeight::Unmeasured(0)),
1852
            Some(b) => *b,
1852
        };
1852

            
1852
        match unmeas {
2
            None | Some(0) => Ok(RelayWeight::Measured(bw)),
1850
            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
            _ => Err(EK::BadArgument
                .at_pos(item.pos())
                .with_msg("unmeasured value")),
        }
1862
    }
}
impl Footer {
    /// Parse a directory footer from a footer section.
335
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
335
        sec.required(DIRECTORY_FOOTER)?;
335
        let weights = sec
335
            .maybe(BANDWIDTH_WEIGHTS)
335
            .args_as_str()
335
            .unwrap_or("")
335
            .parse()?;
333
        Ok(Footer { weights })
335
    }
}
/// Result of checking a single authority signature.
enum SigCheckResult {
    /// The signature checks out.  Great!
    Valid,
    /// The signature is invalid; no additional information could make it
    /// valid.
    Invalid,
    /// We can't check the signature because we don't have a
    /// certificate with the right signing key.
    MissingCert,
}
impl Signature {
    /// Parse a Signature from a directory-signature section
999
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
999
        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} for directory signature",
                item.kwd()
            ))
            .at_pos(item.pos()));
999
        }
999
        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
            (
993
                item.required_arg(0)?,
993
                item.required_arg(1)?,
993
                item.required_arg(2)?,
            )
        } else {
6
            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
        };
999
        let digestname = alg.to_string();
999
        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
999
        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
999
        let key_ids = AuthCertKeyIds {
999
            id_fingerprint,
999
            sk_fingerprint,
999
        };
999
        let signature = item.obj("SIGNATURE")?;
999
        Ok(Signature {
999
            digestname,
999
            key_ids,
999
            signature,
999
        })
999
    }
    /// Return true if this signature has the identity key and signing key
    /// that match a given cert.
705
    fn matches_cert(&self, cert: &AuthCert) -> bool {
705
        cert.key_ids() == &self.key_ids
705
    }
    /// If possible, find the right certificate for checking this signature
    /// from among a slice of certificates.
1041
    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1121
        certs.iter().find(|&c| self.matches_cert(c))
1041
    }
    /// Try to check whether this signature is a valid signature of a
    /// provided digest, given a slice of certificates that might contain
    /// its signing key.
159
    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
159
        match self.find_cert(certs) {
51
            None => SigCheckResult::MissingCert,
108
            Some(cert) => {
108
                let key = cert.signing_key();
108
                match key.verify(signed_digest, &self.signature[..]) {
108
                    Ok(()) => SigCheckResult::Valid,
                    Err(_) => SigCheckResult::Invalid,
                }
            }
        }
159
    }
}
/// A Consensus object that has been parsed, but not checked for
/// signatures and timeliness.
pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
    /// Return a new ConsensusBuilder for building test consensus objects.
    ///
    /// This function is only available when the `build_docs` feature has
    /// been enabled.
    #[cfg(feature = "build_docs")]
7477
    pub fn builder() -> ConsensusBuilder<RS> {
7477
        ConsensusBuilder::new(RS::flavor())
7477
    }
    /// Try to parse a single networkstatus document from a string.
34
    pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
34
        let mut reader = NetDocReader::new(s)?;
34
        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
34
    }
    /// Extract a voter-info section from the reader; return
    /// Ok(None) when we are out of voter-info sections.
112
    fn take_voterinfo(
112
        r: &mut NetDocReader<'_, NetstatusKwd>,
112
    ) -> Result<Option<ConsensusVoterInfo>> {
        use NetstatusKwd::*;
112
        match r.peek() {
            None => return Ok(None),
112
            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
84
            _ => (),
84
        };
84

            
84
        let mut first_dir_source = true;
84
        // TODO: Extract this pattern into a "pause at second"???
84
        // Pause at the first 'r', or the second 'dir-source'.
336
        let mut p = r.pause_at(|i| match i {
            Err(_) => false,
336
            Ok(item) => {
336
                item.kwd() == RS_R
308
                    || if item.kwd() == DIR_SOURCE {
140
                        let was_first = first_dir_source;
140
                        first_dir_source = false;
140
                        !was_first
                    } else {
168
                        false
                    }
            }
336
        });
84
        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
84
        let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
84
        Ok(Some(voter))
112
    }
    /// Extract the footer (but not signatures) from the reader.
20
    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
60
        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
20
        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
20
        let footer = Footer::from_section(&footer_sec)?;
18
        Ok(footer)
20
    }
    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
    /// out of routerstatus entries.
162
    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
        use NetstatusKwd::*;
162
        match r.peek() {
            None => return Ok(None),
162
            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
142
            _ => (),
142
        };
142

            
142
        let pos = r.pos();
142

            
142
        let mut first_r = true;
1104
        let mut p = r.pause_at(|i| match i {
            Err(_) => false,
1104
            Ok(item) => {
1104
                item.kwd() == DIRECTORY_FOOTER
1082
                    || if item.kwd() == RS_R {
262
                        let was_first = first_r;
262
                        first_r = false;
262
                        !was_first
                    } else {
820
                        false
                    }
            }
1104
        });
142
        let rules = match RS::flavor() {
126
            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
16
            ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
        };
142
        let rs_sec = rules.parse(&mut p)?;
142
        let rs = RS::from_section(&rs_sec)?;
136
        Ok(Some((pos, rs)))
162
    }
    /// Extract an entire UncheckedConsensus from a reader.
    ///
    /// Returns the signed portion of the string, the remainder of the
    /// string, and an UncheckedConsensus.
34
    fn parse_from_reader<'a>(
34
        r: &mut NetDocReader<'a, NetstatusKwd>,
34
    ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
        use NetstatusKwd::*;
28
        let (header, start_pos) = {
486
            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
34
            let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
            // Unwrapping should be safe because above `.parse` would have
            // returned an Error
            #[allow(clippy::unwrap_used)]
32
            let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
32
            (ConsensusHeader::from_section(&header_sec)?, pos)
28
        };
28
        if RS::flavor() != header.hdr.flavor {
            return Err(EK::BadDocumentType.with_msg(format!(
                "Expected {:?}, got {:?}",
                RS::flavor(),
                header.hdr.flavor
            )));
28
        }
28

            
28
        let mut voters = Vec::new();
112
        while let Some(voter) = Self::take_voterinfo(r)? {
84
            voters.push(voter);
84
        }
28
        let mut relays: Vec<RS> = Vec::new();
162
        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
136
            if let Some(prev) = relays.last() {
110
                if prev.rsa_identity() >= routerstatus.rsa_identity() {
2
                    return Err(EK::WrongSortOrder.at_pos(pos));
108
                }
26
            }
134
            relays.push(routerstatus);
        }
20
        relays.shrink_to_fit();
20
        let footer = Self::take_footer(r)?;
18
        let consensus = Consensus {
18
            header,
18
            voters,
18
            relays,
18
            footer,
18
        };
18

            
18
        // Find the signatures.
18
        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
18
        let mut signatures = Vec::new();
72
        for item in &mut *r {
54
            let item = item?;
54
            if item.kwd() != DIRECTORY_SIGNATURE {
                return Err(EK::UnexpectedToken
                    .with_msg(item.kwd().to_str())
                    .at_pos(item.pos()));
54
            }
54
            let sig = Signature::from_item(&item)?;
54
            if first_sig.is_none() {
18
                first_sig = Some(item);
36
            }
54
            signatures.push(sig);
        }
18
        let end_pos = match first_sig {
            None => return Err(EK::MissingToken.with_msg("directory-signature")),
            // Unwrap should be safe because `first_sig` was parsed from `r`
            #[allow(clippy::unwrap_used)]
18
            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
18
        };
18

            
18
        // Find the appropriate digest.
18
        let signed_str = &r.str()[start_pos..end_pos];
18
        let remainder = &r.str()[end_pos..];
18
        let (sha256, sha1) = match RS::flavor() {
2
            ConsensusFlavor::Ns => (
2
                None,
2
                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
2
            ),
16
            ConsensusFlavor::Microdesc => (
16
                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
16
                None,
16
            ),
        };
18
        let siggroup = SignatureGroup {
18
            sha256,
18
            sha1,
18
            signatures,
18
        };
18

            
18
        let unval = UnvalidatedConsensus {
18
            consensus,
18
            siggroup,
18
            n_authorities: None,
18
        };
18
        let lifetime = unval.consensus.header.hdr.lifetime.clone();
18
        let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
18
        let dist_interval = time::Duration::from_secs(delay.1.into());
18
        let starting_time = lifetime.valid_after - dist_interval;
18
        let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
18
        Ok((signed_str, remainder, timebound))
34
    }
}
/// A Microdesc consensus whose signatures have not yet been checked.
///
/// To validate this object, call set_n_authorities() on it, then call
/// check_signature() on that result with the set of certs that you
/// have.  Make sure only to provide authority certificates representing
/// real authorities!
#[cfg_attr(
    feature = "dangerous-expose-struct-fields",
    visible::StructFields(pub),
    non_exhaustive
)]
#[derive(Debug, Clone)]
pub struct UnvalidatedConsensus<RS> {
    /// The consensus object. We don't want to expose this until it's
    /// validated.
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
    consensus: Consensus<RS>,
    /// The signatures that need to be validated before we can call
    /// this consensus valid.
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
    siggroup: SignatureGroup,
    /// The total number of authorities that we believe in.  We need
    /// this information in order to validate the signatures, since it
    /// determines how many signatures we need to find valid in `siggroup`.
    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
    n_authorities: Option<u16>,
}
impl<RS> UnvalidatedConsensus<RS> {
    /// Tell the unvalidated consensus how many authorities we believe in.
    ///
    /// Without knowing this number, we can't validate the signature.
    #[must_use]
14
    pub fn set_n_authorities(self, n_authorities: u16) -> Self {
14
        UnvalidatedConsensus {
14
            n_authorities: Some(n_authorities),
14
            ..self
14
        }
14
    }
    /// Return an iterator of all the certificate IDs that we might use
    /// to validate this consensus.
8
    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
8
        match self.key_is_correct(&[]) {
            Ok(()) => Vec::new(),
8
            Err(missing) => missing,
        }
8
        .into_iter()
8
    }
    /// Return the lifetime of this unvalidated consensus
10
    pub fn peek_lifetime(&self) -> &Lifetime {
10
        self.consensus.lifetime()
10
    }
    /// Return true if a client who believes in exactly the provided
    /// set of authority IDs might might consider this consensus to be
    /// well-signed.
    ///
    /// (This is the case if the consensus claims to be signed by more than
    /// half of the authorities in the list.)
20
    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
20
        self.siggroup.could_validate(authorities)
20
    }
    /// Return the number of relays in this unvalidated consensus.
    ///
    /// This function is unstable. It is only enabled if the crate was
    /// built with the `experimental-api` feature.
    #[cfg(feature = "experimental-api")]
    pub fn n_relays(&self) -> usize {
        self.consensus.relays.len()
    }
    /// Modify the list of relays in this unvalidated consensus.
    ///
    /// A use case for this is long-lasting custom directories. To ensure Arti can still quickly
    /// build circuits when the directory gets old, a tiny churn file can be regularly obtained,
    /// listing no longer available Tor nodes, which can then be removed from the consensus.
    ///
    /// This function is unstable. It is only enabled if the crate was
    /// built with the `experimental-api` feature.
    #[cfg(feature = "experimental-api")]
    pub fn modify_relays<F>(&mut self, func: F)
    where
        F: FnOnce(&mut Vec<RS>),
    {
        func(&mut self.consensus.relays);
    }
}
impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
    type Key = [AuthCert];
    type KeyHint = Vec<AuthCertKeyIds>;
    type Error = Error;
24
    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
24
        let (n_ok, missing) = self.siggroup.list_missing(k);
24
        match self.n_authorities {
24
            Some(n) if n_ok > (n / 2) as usize => Ok(()),
42
            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
        }
24
    }
8
    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
8
        match self.n_authorities {
            None => Err(Error::from(internal!(
                "Didn't set authorities on consensus"
            ))),
8
            Some(authority) => {
8
                if self.siggroup.validate(authority, k) {
6
                    Ok(())
                } else {
2
                    Err(EK::BadSignature.err())
                }
            }
        }
8
    }
10
    fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
10
        self.consensus
10
    }
}
impl SignatureGroup {
    // TODO: these functions are pretty similar and could probably stand to be
    // refactored a lot.
    /// Helper: Return a pair of the number of possible authorities'
    /// signatures in this object for which we _could_ find certs, and
    /// a list of the signatures we couldn't find certificates for.
294
    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
294
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
294
        let mut missing = Vec::new();
1176
        for sig in &self.signatures {
882
            let id_fingerprint = &sig.key_ids.id_fingerprint;
882
            if ok.contains(id_fingerprint) {
                continue;
882
            }
882
            if sig.find_cert(certs).is_some() {
163
                ok.insert(*id_fingerprint);
163
                continue;
719
            }
719

            
719
            missing.push(sig);
        }
294
        (ok.len(), missing)
294
    }
    /// Given a list of authority identity key fingerprints, return true if
    /// this signature group is _potentially_ well-signed according to those
    /// authorities.
245
    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
245
        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
980
        for sig in &self.signatures {
735
            let id_fp = &sig.key_ids.id_fingerprint;
735
            if signed_by.contains(id_fp) {
                // Already found this in the list.
                continue;
735
            }
735
            if authorities.contains(&id_fp) {
392
                signed_by.insert(*id_fp);
392
            }
        }
245
        signed_by.len() > (authorities.len() / 2)
245
    }
    /// Return true if the signature group defines a valid signature.
    ///
    /// A signature is valid if it signed by more than half of the
    /// authorities.  This API requires that `n_authorities` is the number of
    /// authorities we believe in, and that every cert in `certs` belongs
    /// to a real authority.
53
    fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
53
        // A set of the authorities (by identity) who have have signed
53
        // this document.  We use a set here in case `certs` has more
53
        // than one certificate for a single authority.
53
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
212
        for sig in &self.signatures {
159
            let id_fingerprint = &sig.key_ids.id_fingerprint;
159
            if ok.contains(id_fingerprint) {
                // We already checked at least one signature using this
                // authority's identity fingerprint.
                continue;
159
            }
159
            let d: Option<&[u8]> = match sig.digestname.as_ref() {
168
                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
9
                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
                _ => None, // We don't know how to find this digest.
            };
159
            if d.is_none() {
                // We don't support this kind of digest for this kind
                // of document.
                continue;
159
            }
159

            
159
            // Unwrap should be safe because of above `d.is_none()` check
159
            #[allow(clippy::unwrap_used)]
159
            match sig.check_signature(d.as_ref().unwrap(), certs) {
108
                SigCheckResult::Valid => {
108
                    ok.insert(*id_fingerprint);
108
                }
51
                _ => continue,
            }
        }
53
        ok.len() > (n_authorities / 2) as usize
53
    }
}
#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_duration_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use super::*;
    use hex_literal::hex;
    const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
    const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
    #[cfg(feature = "ns_consensus")]
    const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
    #[cfg(feature = "ns_consensus")]
    const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
    fn read_bad(fname: &str) -> String {
        use std::fs;
        use std::path::PathBuf;
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("testdata");
        path.push("bad-mdconsensus");
        path.push(fname);
        fs::read_to_string(path).unwrap()
    }
    #[test]
    fn parse_and_validate_md() -> Result<()> {
        use std::net::SocketAddr;
        use tor_checkable::{SelfSigned, Timebound};
        let mut certs = Vec::new();
        for cert in AuthCert::parse_multiple(CERTS)? {
            let cert = cert?.check_signature()?.dangerously_assume_timely();
            certs.push(cert);
        }
        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
        assert_eq!(certs.len(), 3);
        let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
        // The set of authorities we know _could_ validate this cert.
        assert!(consensus.authorities_are_correct(&auth_ids));
        // A subset would also work.
        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
        {
            // If we only believe in an authority that isn't listed,
            // that won't work.
            let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
            assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
        }
        let missing = consensus.key_is_correct(&[]).err().unwrap();
        assert_eq!(3, missing.len());
        assert!(consensus.key_is_correct(&certs).is_ok());
        let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
        assert_eq!(2, missing.len());
        // here is a trick that had better not work.
        let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
        let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
        assert_eq!(2, missing.len());
        assert!(consensus.is_well_signed(&same_three_times).is_err());
        assert!(consensus.key_is_correct(&certs).is_ok());
        let consensus = consensus.check_signature(&certs)?;
        assert_eq!(6, consensus.relays().len());
        let r0 = &consensus.relays()[0];
        assert_eq!(
            r0.md_digest(),
            &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
        );
        assert_eq!(
            r0.rsa_identity().as_bytes(),
            &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
        );
        assert!(!r0.weight().is_measured());
        assert!(!r0.weight().is_nonzero());
        let pv = &r0.protovers();
        assert!(pv.supports_subver("HSDir", 2));
        assert!(!pv.supports_subver("HSDir", 3));
        let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
        let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
        assert!(r0.orport_addrs().any(|a| a == &ip4));
        assert!(r0.orport_addrs().any(|a| a == &ip6));
        Ok(())
    }
    #[test]
    #[cfg(feature = "ns_consensus")]
    fn parse_and_validate_ns() -> Result<()> {
        use tor_checkable::{SelfSigned, Timebound};
        let mut certs = Vec::new();
        for cert in AuthCert::parse_multiple(NS_CERTS)? {
            let cert = cert?.check_signature()?.dangerously_assume_timely();
            certs.push(cert);
        }
        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
        assert_eq!(certs.len(), 3);
        let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
        // The set of authorities we know _could_ validate this cert.
        assert!(consensus.authorities_are_correct(&auth_ids));
        // A subset would also work.
        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
        assert!(consensus.key_is_correct(&certs).is_ok());
        let _consensus = consensus.check_signature(&certs)?;
        Ok(())
    }
    #[test]
    fn test_bad() {
        use crate::Pos;
        fn check(fname: &str, e: &Error) {
            let content = read_bad(fname);
            let res = MdConsensus::parse(&content);
            assert!(res.is_err());
            assert_eq!(&res.err().unwrap(), e);
        }
        check(
            "bad-flags",
            &EK::BadArgument
                .at_pos(Pos::from_line(27, 1))
                .with_msg("Flags out of order"),
        );
        check(
            "bad-md-digest",
            &EK::BadArgument
                .at_pos(Pos::from_line(40, 3))
                .with_msg("Invalid base64"),
        );
        check(
            "bad-weight",
            &EK::BadArgument
                .at_pos(Pos::from_line(67, 141))
                .with_msg("invalid digit found in string"),
        );
        check(
            "bad-weights",
            &EK::BadArgument
                .at_pos(Pos::from_line(51, 13))
                .with_msg("invalid digit found in string"),
        );
        check(
            "wrong-order",
            &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
        );
        check(
            "wrong-start",
            &EK::UnexpectedToken
                .with_msg("vote-status")
                .at_pos(Pos::from_line(1, 1)),
        );
        check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
    }
    fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
        let mut reader = NetDocReader::new(s)?;
        let tok = reader.next().unwrap();
        assert!(reader.next().is_none());
        tok
    }
    #[test]
    fn test_weight() {
        let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(!w.is_measured());
        assert!(w.is_nonzero());
        let w = gettok("w Bandwidth=10\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(w.is_measured());
        assert!(w.is_nonzero());
        let w = RelayWeight::default();
        assert!(!w.is_measured());
        assert!(!w.is_nonzero());
        let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(!w.is_measured());
        assert!(!w.is_nonzero());
        let w = gettok("r foo\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
        let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
        let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
    }
    #[test]
    fn test_netparam() {
        let p = "Hello=600 Goodbye=5 Fred=7"
            .parse::<NetParams<u32>>()
            .unwrap();
        assert_eq!(p.get("Hello"), Some(&600_u32));
        let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
        assert!(p.is_err());
        let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
        assert!(p.is_err());
    }
    #[test]
    fn test_sharedrand() {
        let sr =
            gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
                .unwrap();
        let sr = SharedRandStatus::from_item(&sr).unwrap();
        assert_eq!(sr.n_reveals, 9);
        assert_eq!(
            sr.value.0,
            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
        );
        assert!(sr.timestamp.is_none());
        let sr2 = gettok(
            "shared-rand-current-value 9 \
                    5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
        )
        .unwrap();
        let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
        assert_eq!(sr2.n_reveals, sr.n_reveals);
        assert_eq!(sr2.value.0, sr.value.0);
        assert_eq!(
            sr2.timestamp.unwrap(),
            humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
        );
        let sr = gettok("foo bar\n").unwrap();
        let sr = SharedRandStatus::from_item(&sr);
        assert!(sr.is_err());
    }
    #[test]
    fn test_protostatus() {
        let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
        let outcome = ProtoStatus {
            recommended: "Link=7".parse().unwrap(),
            required: "Desc=5".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert!(outcome.is_ok());
        let outcome = ProtoStatus {
            recommended: "Microdesc=4 Link=7".parse().unwrap(),
            required: "Desc=5".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert_eq!(
            outcome,
            Err(ProtocolSupportError::MissingRecommended(
                "Microdesc=4".parse().unwrap()
            ))
        );
        let outcome = ProtoStatus {
            recommended: "Microdesc=4 Link=7".parse().unwrap(),
            required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert_eq!(
            outcome,
            Err(ProtocolSupportError::MissingRequired(
                "Cons=6-12 Wombat=15".parse().unwrap()
            ))
        );
    }
    #[test]
    fn serialize_protostatus() {
        let ps = ProtoStatuses {
            client: ProtoStatus {
                recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
                required: "Link=5 LinkAuth=3".parse().unwrap(),
            },
            relay: ProtoStatus {
                recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
                required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
            },
        };
        let json = serde_json::to_string(&ps).unwrap();
        let ps2 = serde_json::from_str(json.as_str()).unwrap();
        assert_eq!(ps, ps2);
        let ps3: ProtoStatuses = serde_json::from_str(
            r#"{
            "client":{
                "required":"Link=5 LinkAuth=3",
                "recommended":"Link=1-5 LinkAuth=2-5"
            },
            "relay":{
                "required":"Wombat=20-22 Knish=25-27",
                "recommended":"Wombat=20-30 Knish=20-30"
            }
        }"#,
        )
        .unwrap();
        assert_eq!(ps, ps3);
    }
}