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};
54
use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
55
use crate::types::misc::*;
56
use crate::util::private::Sealed;
57
use crate::{Error, ParseErrorKind as EK, Pos, Result};
58
use std::collections::{HashMap, HashSet};
59
use std::{net, result, time};
60
use tor_error::internal;
61
use tor_protover::Protocols;
62

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

            
70
use serde::{Deserialize, Deserializer};
71

            
72
#[cfg(feature = "build_docs")]
73
pub use build::ConsensusBuilder;
74
#[cfg(feature = "build_docs")]
75
pub use rs::build::RouterStatusBuilder;
76

            
77
pub use rs::MdConsensusRouterStatus;
78
#[cfg(feature = "ns_consensus")]
79
pub use rs::NsConsensusRouterStatus;
80

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

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

            
141
/// A set of named network parameters.
142
///
143
/// These are used to describe current settings for the Tor network,
144
/// current weighting parameters for path selection, and so on.  They're
145
/// encoded with a space-separated K=V format.
146
1428
#[derive(Debug, Clone, Default, Eq, PartialEq)]
147
pub struct NetParams<T> {
148
    /// Map from keys to values.
149
    params: HashMap<String, T>,
150
}
151

            
152
impl<T> NetParams<T> {
153
    /// Create a new empty list of NetParams.
154
    #[allow(unused)]
155
1498
    pub(crate) fn new() -> Self {
156
1498
        NetParams {
157
1498
            params: HashMap::new(),
158
1498
        }
159
1498
    }
160
    /// Retrieve a given network parameter, if it is present.
161
14231
    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
162
14231
        self.params.get(v.as_ref())
163
14231
    }
164
    /// Return an iterator over all key value pares in an arbitrary order.
165
1075
    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
166
1075
        self.params.iter()
167
1075
    }
168
    /// Set or replace the value of a network parameter.
169
785
    pub fn set(&mut self, k: String, v: T) {
170
785
        self.params.insert(k, v);
171
785
    }
172
}
173

            
174
impl<'de, T> Deserialize<'de> for NetParams<T>
175
where
176
    T: Deserialize<'de>,
177
{
178
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
179
    where
180
        D: Deserializer<'de>,
181
    {
182
        let params = HashMap::deserialize(deserializer)?;
183
        Ok(NetParams { params })
184
    }
185
}
186

            
187
/// A list of subprotocol versions that implementors should/must provide.
188
#[allow(dead_code)]
189
1498
#[derive(Debug, Clone, Default)]
190
pub struct ProtoStatus {
191
    /// Set of protocols that are recommended; if we're missing a protocol
192
    /// in this list we should warn the user.
193
    recommended: Protocols,
194
    /// Set of protocols that are required; if we're missing a protocol
195
    /// in this list we should refuse to start.
196
    required: Protocols,
197
}
198

            
199
/// A recognized 'flavor' of consensus document.
200
21
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
201
#[non_exhaustive]
202
pub enum ConsensusFlavor {
203
    /// A "microdesc"-flavored consensus.  This is the one that
204
    /// clients and relays use today.
205
    Microdesc,
206
    /// A "networkstatus"-flavored consensus.  It's used for
207
    /// historical and network-health purposes.  Instead of listing
208
    /// microdescriptor digests, it lists digests of full relay
209
    /// descriptors.
210
    Ns,
211
}
212

            
213
impl ConsensusFlavor {
214
    /// Return the name of this consensus flavor.
215
442
    pub fn name(&self) -> &'static str {
216
442
        match self {
217
            ConsensusFlavor::Ns => "ns",
218
442
            ConsensusFlavor::Microdesc => "microdesc",
219
        }
220
442
    }
221
    /// Try to find the flavor whose name is `name`.
222
    ///
223
    /// For historical reasons, an unnamed flavor indicates an "Ns"
224
    /// document.
225
126
    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
226
126
        match name {
227
125
            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
228
1
            Some("ns") | None => Ok(ConsensusFlavor::Ns),
229
            Some(other) => {
230
                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
231
            }
232
        }
233
126
    }
234
}
235

            
236
/// The signature of a single directory authority on a networkstatus document.
237
#[allow(dead_code)]
238
#[derive(Debug, Clone)]
239
pub struct Signature {
240
    /// The name of the digest algorithm used to make the signature.
241
    ///
242
    /// Currently sha1 and sh256 are recognized.  Here we only support
243
    /// sha256.
244
    digestname: String,
245
    /// Fingerprints of the keys for the authority that made
246
    /// this signature.
247
    key_ids: AuthCertKeyIds,
248
    /// The signature itself.
249
    signature: Vec<u8>,
250
}
251

            
252
/// A collection of signatures that can be checked on a networkstatus document
253
#[allow(dead_code)]
254
#[derive(Debug, Clone)]
255
pub struct SignatureGroup {
256
    /// The sha256 of the document itself
257
    sha256: Option<[u8; 32]>,
258
    /// The sha1 of the document itself
259
    sha1: Option<[u8; 20]>,
260
    /// The signatures listed on the document.
261
    signatures: Vec<Signature>,
262
}
263

            
264
/// A shared-random value produced by the directory authorities.
265
#[allow(dead_code)]
266
2
#[derive(Debug, Clone)]
267
struct SharedRandVal {
268
    /// How many authorities revealed shares that contributed to this value.
269
    n_reveals: u8,
270
    /// The current random value.
271
    ///
272
    /// The properties of the secure shared-random system guarantee
273
    /// that this value isn't predictable before it first becomes
274
    /// live, and that a hostile party could not have forced it to
275
    /// have any more than a small number of possible random values.
276
    value: Vec<u8>,
277
}
278

            
279
/// Parts of the networkstatus header that are present in every networkstatus.
280
///
281
/// NOTE: this type is separate from the header parts that are only in
282
/// votes or only in consensuses, even though we don't implement votes yet.
283
#[allow(dead_code)]
284
3
#[derive(Debug, Clone)]
285
struct CommonHeader {
286
    /// What kind of consensus document is this?  Absent in votes and
287
    /// in ns-flavored consensuses.
288
    flavor: ConsensusFlavor,
289
    /// Over what time is this consensus valid?  (For votes, this is
290
    /// the time over which the voted-upon consensus should be valid.)
291
    lifetime: Lifetime,
292
    /// List of recommended Tor client versions.
293
    client_versions: Vec<String>,
294
    /// List of recommended Tor relay versions.
295
    relay_versions: Vec<String>,
296
    /// Lists of recommended and required subprotocol versions for clients
297
    client_protos: ProtoStatus,
298
    /// Lists of recommended and required subprotocol versions for relays
299
    relay_protos: ProtoStatus,
300
    /// Declared parameters for tunable settings about how to the
301
    /// network should operator. Some of these adjust timeouts and
302
    /// whatnot; some features things on and off.
303
    params: NetParams<i32>,
304
    /// How long in seconds should voters wait for votes and
305
    /// signatures (respectively) to propagate?
306
    voting_delay: Option<(u32, u32)>,
307
}
308

            
309
/// The header of a consensus networkstatus.
310
#[allow(dead_code)]
311
3
#[derive(Debug, Clone)]
312
struct ConsensusHeader {
313
    /// Header fields common to votes and consensuses
314
    hdr: CommonHeader,
315
    /// What "method" was used to produce this consensus?  (A
316
    /// consensus method is a version number used by authorities to
317
    /// upgrade the consensus algorithm.)
318
    consensus_method: u32,
319
    /// Global shared-random value for the previous shared-random period.
320
    shared_rand_prev: Option<SharedRandVal>,
321
    /// Global shared-random value for the current shared-random period.
322
    shared_rand_cur: Option<SharedRandVal>,
323
}
324

            
325
/// Description of an authority's identity and address.
326
///
327
/// (Corresponds to a dir-source line.)
328
#[allow(dead_code)]
329
1
#[derive(Debug, Clone)]
330
struct DirSource {
331
    /// human-readable nickname for this authority.
332
    nickname: String,
333
    /// Fingerprint for the _authority_ identity key of this
334
    /// authority.
335
    ///
336
    /// This is the same key as the one that signs the authority's
337
    /// certificates.
338
    identity: RsaIdentity,
339
    /// IP address for the authority
340
    ip: net::IpAddr,
341
    /// HTTP directory port for this authority
342
    dir_port: u16,
343
    /// OR port for this authority.
344
    or_port: u16,
345
}
346

            
347
bitflags! {
348
    /// A set of recognized directory flags on a single relay.
349
    ///
350
    /// These flags come from a consensus directory document, and are
351
    /// used to describe what the authorities believe about the relay.
352
    /// If the document contained any flags that we _didn't_ recognize,
353
    /// they are not listed in this type.
354
    ///
355
    /// The bit values used to represent the flags have no meaning.
356
    pub struct RelayFlags: u16 {
357
        /// Is this a directory authority?
358
        const AUTHORITY = (1<<0);
359
        /// Is this relay marked as a bad exit?
360
        ///
361
        /// Bad exits can be used as intermediate relays, but not to
362
        /// deliver traffic.
363
        const BAD_EXIT = (1<<1);
364
        /// Is this relay marked as an exit for weighting purposes?
365
        const EXIT = (1<<2);
366
        /// Is this relay considered "fast" above a certain threshold?
367
        const FAST = (1<<3);
368
        /// Is this relay suitable for use as a guard relay?
369
        ///
370
        /// Clients choose their their initial relays from among the set
371
        /// of Guard relays.
372
        const GUARD = (1<<4);
373
        /// Does this relay participate on the onion service directory
374
        /// ring?
375
        const HSDIR = (1<<5);
376
        /// If set, there is no consensus for the ed25519 key for this relay.
377
        const NO_ED_CONSENSUS = (1<<6);
378
        /// Is this relay considered "stable" enough for long-lived circuits?
379
        const STABLE = (1<<7);
380
        /// Set if the authorities are requesting a fresh descriptor for
381
        /// this relay.
382
        const STALE_DESC = (1<<8);
383
        /// Set if this relay is currently running.
384
        ///
385
        /// This flag can appear in votes, but in consensuses, every relay
386
        /// is assumed to be running.
387
        const RUNNING = (1<<9);
388
        /// Set if this relay is considered "valid" -- allowed to be on
389
        /// the network.
390
        ///
391
        /// This flag can appear in votes, but in consensuses, every relay
392
        /// is assumed to be valid.
393
        const VALID = (1<<10);
394
        /// Set if this relay supports a currently recognized version of the
395
        /// directory protocol.
396
        const V2DIR = (1<<11);
397
    }
398
}
399

            
400
/// Recognized weight fields on a single relay in a consensus
401
28164
#[derive(Debug, Clone, Copy)]
402
#[non_exhaustive]
403
pub enum RelayWeight {
404
    /// An unmeasured weight for a relay.
405
    Unmeasured(u32),
406
    /// An measured weight for a relay.
407
    Measured(u32),
408
}
409

            
410
impl RelayWeight {
411
    /// Return true if this weight is the result of a successful measurement
412
2793
    pub fn is_measured(&self) -> bool {
413
2793
        matches!(self, RelayWeight::Measured(_))
414
2793
    }
415
    /// Return true if this weight is nonzero
416
2317
    pub fn is_nonzero(&self) -> bool {
417
2317
        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
418
2334
    }
419
}
420

            
421
/// All information about a single authority, as represented in a consensus
422
#[allow(dead_code)]
423
1
#[derive(Debug, Clone)]
424
struct ConsensusVoterInfo {
425
    /// Contents of the dirsource line about an authority
426
    dir_source: DirSource,
427
    /// Human-readable contact information about the authority
428
    contact: String,
429
    /// Digest of the vote that the authority cast to contribute to
430
    /// this consensus.
431
    vote_digest: Vec<u8>,
432
}
433

            
434
/// The signed footer of a consensus netstatus.
435
#[allow(dead_code)]
436
3
#[derive(Debug, Clone)]
437
struct Footer {
438
    /// Weights to be applied to certain classes of relays when choosing
439
    /// for different roles.
440
    ///
441
    /// For example, we want to avoid choosing exits for non-exit
442
    /// roles when overall the proportion of exits is small.
443
    weights: NetParams<i32>,
444
}
445

            
446
/// Trait to parse a single relay as listed in a consensus document.
447
///
448
/// TODO(nickm): I'd rather not have this trait be public, but I haven't yet
449
/// figured out how to make it private.
450
pub trait ParseRouterStatus: Sized + Sealed {
451
    /// Parse this object from a `Section` object containing its
452
    /// elements.
453
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Self>;
454

            
455
    /// Return the networkstatus consensus flavor in which this
456
    /// routerstatus appears.
457
    fn flavor() -> ConsensusFlavor;
458
}
459

            
460
/// Represents a single relay as listed in a consensus document.
461
///
462
/// Not implementable outside of the `tor-netdoc` crate.
463
pub trait RouterStatus: Sealed {
464
    /// A digest of the document that's identified by this RouterStatus.
465
    type DocumentDigest: Clone;
466

            
467
    /// Return RSA identity for the relay described by this RouterStatus
468
    fn rsa_identity(&self) -> &RsaIdentity;
469

            
470
    /// Return the digest of the document identified by this
471
    /// routerstatus.
472
    fn doc_digest(&self) -> &Self::DocumentDigest;
473
}
474

            
475
/// A single microdescriptor consensus netstatus
476
///
477
/// TODO: This should possibly turn into a parameterized type, to represent
478
/// votes and ns consensuses.
479
#[allow(dead_code)]
480
3
#[derive(Debug, Clone)]
481
pub struct Consensus<RS> {
482
    /// Part of the header shared by all consensus types.
483
    header: ConsensusHeader,
484
    /// List of voters whose votes contributed to this consensus.
485
    voters: Vec<ConsensusVoterInfo>,
486
    /// A list of routerstatus entries for the relays on the network,
487
    /// with one entry per relay.
488
    relays: Vec<RS>,
489
    /// Footer for the consensus object.
490
    footer: Footer,
491
}
492

            
493
/// A consensus document that lists relays along with their
494
/// microdescriptor documents.
495
pub type MdConsensus = Consensus<MdConsensusRouterStatus>;
496

            
497
/// An MdConsensus that has been parsed and checked for timeliness,
498
/// but not for signatures.
499
pub type UnvalidatedMdConsensus = UnvalidatedConsensus<MdConsensusRouterStatus>;
500

            
501
/// An MdConsensus that has been parsed but not checked for signatures
502
/// and timeliness.
503
pub type UncheckedMdConsensus = UncheckedConsensus<MdConsensusRouterStatus>;
504

            
505
#[cfg(feature = "ns_consensus")]
506
/// A consensus document that lists relays along with their
507
/// router descriptor documents.
508
pub type NsConsensus = Consensus<NsConsensusRouterStatus>;
509

            
510
#[cfg(feature = "ns_consensus")]
511
/// An NsConsensus that has been parsed and checked for timeliness,
512
/// but not for signatures.
513
pub type UnvalidatedNsConsensus = UnvalidatedConsensus<NsConsensusRouterStatus>;
514

            
515
#[cfg(feature = "ns_consensus")]
516
/// An NsConsensus that has been parsed but not checked for signatures
517
/// and timeliness.
518
pub type UncheckedNsConsensus = UncheckedConsensus<NsConsensusRouterStatus>;
519

            
520
impl<RS> Consensus<RS> {
521
    /// Return the Lifetime for this consensus.
522
49745
    pub fn lifetime(&self) -> &Lifetime {
523
49745
        &self.header.hdr.lifetime
524
49745
    }
525

            
526
    /// Return a slice of all the routerstatus entries in this consensus.
527
335037
    pub fn relays(&self) -> &[RS] {
528
335037
        &self.relays[..]
529
335037
    }
530

            
531
    /// Return a mapping from keywords to integers representing how
532
    /// to weight different kinds of relays in different path positions.
533
753
    pub fn bandwidth_weights(&self) -> &NetParams<i32> {
534
753
        &self.footer.weights
535
753
    }
536

            
537
    /// Return the map of network parameters that this consensus advertises.
538
768
    pub fn params(&self) -> &NetParams<i32> {
539
768
        &self.header.hdr.params
540
768
    }
541
}
542

            
543
decl_keyword! {
544
    /// Keywords that can be used in votes and consensuses.
545
    // TODO: This is public because otherwise we can't use it in the
546
    // ParseRouterStatus crate.  But I'd rather find a way to make it
547
    // private.
548
    #[non_exhaustive]
549
    #[allow(missing_docs)]
550
    pub NetstatusKwd {
551
        // Header
552
        "network-status-version" => NETWORK_STATUS_VERSION,
553
        "vote-status" => VOTE_STATUS,
554
        "consensus-methods" => CONSENSUS_METHODS,
555
        "consensus-method" => CONSENSUS_METHOD,
556
        "published" => PUBLISHED,
557
        "valid-after" => VALID_AFTER,
558
        "fresh-until" => FRESH_UNTIL,
559
        "valid-until" => VALID_UNTIL,
560
        "voting-delay" => VOTING_DELAY,
561
        "client-versions" => CLIENT_VERSIONS,
562
        "server-versions" => SERVER_VERSIONS,
563
        "known-flags" => KNOWN_FLAGS,
564
        "flag-thresholds" => FLAG_THRESHOLDS,
565
        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
566
        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
567
        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
568
        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
569
        "params" => PARAMS,
570
        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
571
        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
572
        // "package" is now ignored.
573

            
574
        // header in consensus, voter section in vote?
575
        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
576
        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
577

            
578
        // Voter section (both)
579
        "dir-source" => DIR_SOURCE,
580
        "contact" => CONTACT,
581

            
582
        // voter section (vote, but not consensus)
583
        "legacy-dir-key" => LEGACY_DIR_KEY,
584
        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
585
        "shared-rand-commit" => SHARED_RAND_COMMIT,
586

            
587
        // voter section (consensus, but not vote)
588
        "vote-digest" => VOTE_DIGEST,
589

            
590
        // voter cert beginning (but only the beginning)
591
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
592

            
593
        // routerstatus
594
        "r" => RS_R,
595
        "a" => RS_A,
596
        "s" => RS_S,
597
        "v" => RS_V,
598
        "pr" => RS_PR,
599
        "w" => RS_W,
600
        "p" => RS_P,
601
        "m" => RS_M,
602
        "id" => RS_ID,
603

            
604
        // footer
605
        "directory-footer" => DIRECTORY_FOOTER,
606
        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
607
        "directory-signature" => DIRECTORY_SIGNATURE,
608
    }
609
}
610

            
611
/// Shared parts of rules for all kinds of netstatus headers
612
18
static NS_HEADER_RULES_COMMON_: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
613
18
    use NetstatusKwd::*;
614
18
    let mut rules = SectionRules::new();
615
18
    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
616
18
    rules.add(VOTE_STATUS.rule().required().args(1..));
617
18
    rules.add(VALID_AFTER.rule().required());
618
18
    rules.add(FRESH_UNTIL.rule().required());
619
18
    rules.add(VALID_UNTIL.rule().required());
620
18
    rules.add(VOTING_DELAY.rule().args(2..));
621
18
    rules.add(CLIENT_VERSIONS.rule());
622
18
    rules.add(SERVER_VERSIONS.rule());
623
18
    rules.add(KNOWN_FLAGS.rule().required());
624
18
    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
625
18
    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
626
18
    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
627
18
    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
628
18
    rules.add(PARAMS.rule());
629
18
    rules
630
18
});
631
/// Rules for parsing the header of a consensus.
632
18
static NS_HEADER_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
633
18
    use NetstatusKwd::*;
634
18
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
635
18
    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
636
18
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
637
18
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
638
18
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
639
18
    rules
640
18
});
641
/*
642
/// Rules for parsing the header of a vote.
643
static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
644
    use NetstatusKwd::*;
645
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
646
    rules.add(CONSENSUS_METHODS.rule().args(1..));
647
    rules.add(FLAG_THRESHOLDS.rule());
648
    rules.add(BANDWIDTH_FILE_HEADERS.rule());
649
    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
650
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
651
    rules
652
};
653
/// Rules for parsing a single voter's information in a vote.
654
static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
655
    use NetstatusKwd::*;
656
    let mut rules = SectionRules::new();
657
    rules.add(DIR_SOURCE.rule().required().args(6..));
658
    rules.add(CONTACT.rule().required());
659
    rules.add(LEGACY_DIR_KEY.rule().args(1..));
660
    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
661
    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
662
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
663
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
664
    // then comes an entire cert: When we implement vote parsing,
665
    // we should use the authcert code for handling that.
666
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
667
    rules
668
};
669
 */
670
/// Rules for parsing a single voter's information in a consensus
671
18
static NS_VOTERINFO_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
672
18
    use NetstatusKwd::*;
673
18
    let mut rules = SectionRules::new();
674
18
    rules.add(DIR_SOURCE.rule().required().args(6..));
675
18
    rules.add(CONTACT.rule().required());
676
18
    rules.add(VOTE_DIGEST.rule().required());
677
18
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
678
18
    rules
679
18
});
680
/// Shared rules for parsing a single routerstatus
681
18
static NS_ROUTERSTATUS_RULES_COMMON_: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
682
18
    use NetstatusKwd::*;
683
18
    let mut rules = SectionRules::new();
684
18
    rules.add(RS_A.rule().may_repeat().args(1..));
685
18
    rules.add(RS_S.rule().required());
686
18
    rules.add(RS_V.rule());
687
18
    rules.add(RS_PR.rule().required());
688
18
    rules.add(RS_W.rule());
689
18
    rules.add(RS_P.rule().args(2..));
690
18
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
691
18
    rules
692
18
});
693

            
694
/// Rules for parsing a single routerstatus in an NS consensus
695
1
static NS_ROUTERSTATUS_RULES_NSCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
696
1
    use NetstatusKwd::*;
697
1
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
698
1
    rules.add(RS_R.rule().required().args(8..));
699
1
    rules
700
1
});
701

            
702
/*
703
/// Rules for parsing a single routerstatus in a vote
704
static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
705
    use NetstatusKwd::*;
706
        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
707
        rules.add(RS_R.rule().required().args(8..));
708
        rules.add(RS_M.rule().may_repeat().args(2..));
709
        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
710
        rules
711
    };
712
*/
713
/// Rules for parsing a single routerstatus in a microdesc consensus
714
18
static NS_ROUTERSTATUS_RULES_MDCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
715
18
    use NetstatusKwd::*;
716
18
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
717
18
    rules.add(RS_R.rule().required().args(6..));
718
18
    rules.add(RS_M.rule().required().args(1..));
719
18
    rules
720
18
});
721
/// Rules for parsing consensus fields from a footer.
722
18
static NS_FOOTER_RULES: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
723
18
    use NetstatusKwd::*;
724
18
    let mut rules = SectionRules::new();
725
18
    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
726
18
    // consensus only
727
18
    rules.add(BANDWIDTH_WEIGHTS.rule());
728
18
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
729
18
    rules
730
18
});
731

            
732
impl ProtoStatus {
733
    /// Construct a ProtoStatus from two chosen keywords in a section.
734
252
    fn from_section(
735
252
        sec: &Section<'_, NetstatusKwd>,
736
252
        recommend_token: NetstatusKwd,
737
252
        required_token: NetstatusKwd,
738
252
    ) -> Result<ProtoStatus> {
739
        /// Helper: extract a Protocols entry from an item's arguments.
740
        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
741
504
            if let Some(item) = t {
742
504
                item.args_as_str()
743
504
                    .parse::<Protocols>()
744
504
                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
745
            } else {
746
                Ok(Protocols::new())
747
            }
748
504
        }
749

            
750
252
        let recommended = parse(sec.get(recommend_token))?;
751
252
        let required = parse(sec.get(required_token))?;
752
252
        Ok(ProtoStatus {
753
252
            recommended,
754
252
            required,
755
252
        })
756
252
    }
757
}
758

            
759
impl<T> std::str::FromStr for NetParams<T>
760
where
761
    T: std::str::FromStr,
762
    T::Err: std::error::Error,
763
{
764
    type Err = Error;
765
2190
    fn from_str(s: &str) -> Result<Self> {
766
        /// Helper: parse a single K=V pair.
767
4852
        fn parse_pair<U>(p: &str) -> Result<(String, U)>
768
4852
        where
769
4852
            U: std::str::FromStr,
770
4852
            U::Err: std::error::Error,
771
4852
        {
772
4852
            let parts: Vec<_> = p.splitn(2, '=').collect();
773
4852
            if parts.len() != 2 {
774
                return Err(EK::BadArgument
775
                    .at_pos(Pos::at(p))
776
                    .with_msg("Missing = in key=value list"));
777
4852
            }
778
4852
            let num = parts[1].parse::<U>().map_err(|e| {
779
4
                EK::BadArgument
780
4
                    .at_pos(Pos::at(parts[1]))
781
4
                    .with_msg(e.to_string())
782
4852
            })?;
783
4848
            Ok((parts[0].to_string(), num))
784
4852
        }
785

            
786
2190
        let params = s
787
2190
            .split(' ')
788
5930
            .filter(|p| !p.is_empty())
789
2190
            .map(parse_pair)
790
2190
            .collect::<Result<HashMap<_, _>>>()?;
791
2186
        Ok(NetParams { params })
792
2190
    }
793
}
794

            
795
impl CommonHeader {
796
    /// Extract the CommonHeader members from a single header section.
797
128
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
798
128
        use NetstatusKwd::*;
799
128

            
800
128
        {
801
128
            // this unwrap is safe because if there is not at least one
802
128
            // token in the section, the section is unparsable.
803
128
            #[allow(clippy::unwrap_used)]
804
128
            let first = sec.first_item().unwrap();
805
128
            if first.kwd() != NETWORK_STATUS_VERSION {
806
1
                return Err(EK::UnexpectedToken
807
1
                    .with_msg(first.kwd().to_str())
808
1
                    .at_pos(first.pos()));
809
127
            }
810
        }
811

            
812
127
        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
813

            
814
127
        let version: u32 = ver_item.parse_arg(0)?;
815
127
        if version != 3 {
816
1
            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
817
126
        }
818
126
        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
819

            
820
126
        let valid_after = sec
821
126
            .required(VALID_AFTER)?
822
126
            .args_as_str()
823
126
            .parse::<Iso8601TimeSp>()?
824
126
            .into();
825
126
        let fresh_until = sec
826
126
            .required(FRESH_UNTIL)?
827
126
            .args_as_str()
828
126
            .parse::<Iso8601TimeSp>()?
829
126
            .into();
830
126
        let valid_until = sec
831
126
            .required(VALID_UNTIL)?
832
126
            .args_as_str()
833
126
            .parse::<Iso8601TimeSp>()?
834
126
            .into();
835
126
        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
836

            
837
126
        let client_versions = sec
838
126
            .maybe(CLIENT_VERSIONS)
839
126
            .args_as_str()
840
126
            .unwrap_or("")
841
126
            .split(',')
842
126
            .map(str::to_string)
843
126
            .collect();
844
126
        let relay_versions = sec
845
126
            .maybe(SERVER_VERSIONS)
846
126
            .args_as_str()
847
126
            .unwrap_or("")
848
126
            .split(',')
849
126
            .map(str::to_string)
850
126
            .collect();
851

            
852
126
        let client_protos = ProtoStatus::from_section(
853
126
            sec,
854
126
            RECOMMENDED_CLIENT_PROTOCOLS,
855
126
            REQUIRED_CLIENT_PROTOCOLS,
856
126
        )?;
857
126
        let relay_protos =
858
126
            ProtoStatus::from_section(sec, RECOMMENDED_RELAY_PROTOCOLS, REQUIRED_RELAY_PROTOCOLS)?;
859

            
860
126
        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
861

            
862
126
        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
863
126
            let n1 = tok.parse_arg(0)?;
864
126
            let n2 = tok.parse_arg(1)?;
865
126
            Some((n1, n2))
866
        } else {
867
            None
868
        };
869

            
870
126
        Ok(CommonHeader {
871
126
            flavor,
872
126
            lifetime,
873
126
            client_versions,
874
126
            relay_versions,
875
126
            client_protos,
876
126
            relay_protos,
877
126
            params,
878
126
            voting_delay,
879
126
        })
880
128
    }
881
}
882

            
883
impl SharedRandVal {
884
    /// Parse a current or previous shared rand value from a given
885
    /// SharedRandPreviousValue or SharedRandCurrentValue.
886
4
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
887
4
        match item.kwd() {
888
3
            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
889
            _ => {
890
1
                return Err(Error::from(internal!(
891
1
                    "wrong keyword {:?} on shared-random value",
892
1
                    item.kwd()
893
1
                ))
894
1
                .at_pos(item.pos()))
895
            }
896
        }
897
3
        let n_reveals: u8 = item.parse_arg(0)?;
898
3
        let val: B64 = item.parse_arg(1)?;
899
3
        let value = val.into();
900
3
        Ok(SharedRandVal { n_reveals, value })
901
4
    }
902
}
903

            
904
impl ConsensusHeader {
905
    /// Parse the ConsensusHeader members from a provided section.
906
128
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
907
        use NetstatusKwd::*;
908

            
909
128
        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
910
128
        if status != "consensus" {
911
            return Err(EK::BadDocumentType.err());
912
128
        }
913

            
914
        // We're ignoring KNOWN_FLAGS in the consensus.
915

            
916
128
        let hdr = CommonHeader::from_section(sec)?;
917

            
918
126
        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
919

            
920
126
        let shared_rand_prev = sec
921
126
            .get(SHARED_RAND_PREVIOUS_VALUE)
922
126
            .map(SharedRandVal::from_item)
923
126
            .transpose()?;
924

            
925
126
        let shared_rand_cur = sec
926
126
            .get(SHARED_RAND_CURRENT_VALUE)
927
126
            .map(SharedRandVal::from_item)
928
126
            .transpose()?;
929

            
930
126
        Ok(ConsensusHeader {
931
126
            hdr,
932
126
            consensus_method,
933
126
            shared_rand_prev,
934
126
            shared_rand_cur,
935
126
        })
936
128
    }
937
}
938

            
939
impl DirSource {
940
    /// Parse a "dir-source" item
941
378
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
942
378
        if item.kwd() != NetstatusKwd::DIR_SOURCE {
943
            return Err(
944
                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
945
                    .at_pos(item.pos()),
946
            );
947
378
        }
948
378
        let nickname = item.required_arg(0)?.to_string();
949
378
        let identity = item.parse_arg::<Fingerprint>(1)?.into();
950
378
        let ip = item.parse_arg(3)?;
951
378
        let dir_port = item.parse_arg(4)?;
952
378
        let or_port = item.parse_arg(5)?;
953

            
954
378
        Ok(DirSource {
955
378
            nickname,
956
378
            identity,
957
378
            ip,
958
378
            dir_port,
959
378
            or_port,
960
378
        })
961
378
    }
962
}
963

            
964
impl ConsensusVoterInfo {
965
    /// Parse a single ConsensusVoterInfo from a voter info section.
966
378
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
967
378
        use NetstatusKwd::*;
968
378
        // this unwrap should be safe because if there is not at least one
969
378
        // token in the section, the section is unparsable.
970
378
        #[allow(clippy::unwrap_used)]
971
378
        let first = sec.first_item().unwrap();
972
378
        if first.kwd() != DIR_SOURCE {
973
            return Err(Error::from(internal!(
974
                "Wrong keyword {:?} at start of voter info",
975
                first.kwd()
976
            ))
977
            .at_pos(first.pos()));
978
378
        }
979
378
        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
980

            
981
378
        let contact = sec.required(CONTACT)?.args_as_str().to_string();
982

            
983
378
        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
984
378

            
985
378
        Ok(ConsensusVoterInfo {
986
378
            dir_source,
987
378
            contact,
988
378
            vote_digest,
989
378
        })
990
378
    }
991
}
992

            
993
impl std::str::FromStr for RelayFlags {
994
    type Err = std::convert::Infallible;
995
5765
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
996
5765
        Ok(match s {
997
5765
            "Authority" => RelayFlags::AUTHORITY,
998
5392
            "BadExit" => RelayFlags::BAD_EXIT,
999
5392
            "Exit" => RelayFlags::EXIT,
4713
            "Fast" => RelayFlags::FAST,
4035
            "Guard" => RelayFlags::GUARD,
3357
            "HSDir" => RelayFlags::HSDIR,
2679
            "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
2679
            "Stable" => RelayFlags::STABLE,
2035
            "StaleDesc" => RelayFlags::STALE_DESC,
2035
            "Running" => RelayFlags::RUNNING,
1357
            "Valid" => RelayFlags::VALID,
679
            "V2Dir" => RelayFlags::V2DIR,
1
            _ => RelayFlags::empty(),
        })
5765
    }
}

            
impl RelayFlags {
    /// Parse a relay-flags entry from an "s" line.
679
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
679
        if item.kwd() != NetstatusKwd::RS_S {
            return Err(
                Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
                    .at_pos(item.pos()),
            );
679
        }
679
        // These flags are implicit.
679
        let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
679

            
679
        let mut prev: Option<&str> = None;
5766
        for s in item.args() {
5766
            if let Some(p) = prev {
5087
                if p >= s {
                    // Arguments out of order.
1
                    return Err(EK::BadArgument
1
                        .at_pos(item.pos())
1
                        .with_msg("Flags out of order"));
5086
                }
679
            }
5765
            match s.parse() {
5765
                Ok(fl) => {
5765
                    flags |= fl;
5765
                    prev = Some(s);
5765
                }
                Err(_e) => {
                    return Err(EK::BadArgument
                        .at_pos(item.pos())
                        .with_msg("failed to parse flag"))
                }
            };
        }

            
678
        Ok(flags)
679
    }
}

            
impl Default for RelayWeight {
1
    fn default() -> RelayWeight {
1
        RelayWeight::Unmeasured(0)
1
    }
}

            
impl RelayWeight {
    /// Parse a routerweight from a "w" line.
684
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
684
        if item.kwd() != NetstatusKwd::RS_W {
3
            return Err(
3
                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
3
                    .at_pos(item.pos()),
3
            );
681
        }

            
681
        let params: NetParams<u32> = item.args_as_str().parse()?;

            
680
        let bw = params.params.get("Bandwidth");
680
        let unmeas = params.params.get("Unmeasured");

            
680
        let bw = match bw {
1
            None => return Ok(RelayWeight::Unmeasured(0)),
679
            Some(b) => *b,
679
        };
679

            
679
        match unmeas {
1
            None | Some(0) => Ok(RelayWeight::Measured(bw)),
678
            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
            _ => Err(EK::BadArgument
                .at_pos(item.pos())
                .with_msg("unmeasured value")),
        }
684
    }
}

            
impl Footer {
    /// Parse a directory footer from a footer section.
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
122
        sec.required(DIRECTORY_FOOTER)?;

            
122
        let weights = sec
122
            .maybe(BANDWIDTH_WEIGHTS)
122
            .args_as_str()
122
            .unwrap_or("")
122
            .parse()?;

            
121
        Ok(Footer { weights })
122
    }
}

            
/// 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
363
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
363
        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} for directory signature",
                item.kwd()
            ))
            .at_pos(item.pos()));
363
        }

            
363
        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
            (
360
                item.required_arg(0)?,
360
                item.required_arg(1)?,
360
                item.required_arg(2)?,
            )
        } else {
3
            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
        };

            
363
        let digestname = alg.to_string();
363
        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
363
        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
363
        let key_ids = AuthCertKeyIds {
363
            id_fingerprint,
363
            sk_fingerprint,
363
        };
363
        let signature = item.obj("SIGNATURE")?;

            
363
        Ok(Signature {
363
            digestname,
363
            key_ids,
363
            signature,
363
        })
363
    }

            
    /// Return true if this signature has the identity key and signing key
    /// that match a given cert.
353
    fn matches_cert(&self, cert: &AuthCert) -> bool {
353
        cert.key_ids() == &self.key_ids
353
    }

            
    /// If possible, find the right certificate for checking this signature
    /// from among a slice of certificates.
486
    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
702
        for c in certs {
353
            if self.matches_cert(c) {
137
                return Some(c);
216
            }
        }
349
        None
486
    }

            
    /// 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.
60
    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
60
        match self.find_cert(certs) {
19
            None => SigCheckResult::MissingCert,
41
            Some(cert) => {
41
                let key = cert.signing_key();
41
                match key.verify(signed_digest, &self.signature[..]) {
41
                    Ok(()) => SigCheckResult::Valid,
                    Err(_) => SigCheckResult::Invalid,
                }
            }
        }
60
    }
}

            
/// 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")]
749
    pub fn builder() -> ConsensusBuilder<RS> {
749
        ConsensusBuilder::new(RS::flavor())
749
    }

            
    /// Try to parse a single networkstatus document from a string.
17
    pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
17
        let mut reader = NetDocReader::new(s);
17
        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
17
    }
    /// Extract a voter-info section from the reader; return
    /// Ok(None) when we are out of voter-info sections.
    fn take_voterinfo(
        r: &mut NetDocReader<'_, NetstatusKwd>,
    ) -> Result<Option<ConsensusVoterInfo>> {
        use NetstatusKwd::*;

            
56
        match r.iter().peek() {
            None => return Ok(None),
56
            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
42
            _ => (),
42
        };
42

            
42
        let mut first_dir_source = true;
42
        // TODO: Extract this pattern into a "pause at second"???
42
        // Pause at the first 'r', or the second 'dir-source'.
168
        let mut p = r.pause_at(|i| match i {
            Err(_) => false,
168
            Ok(item) => {
168
                item.kwd() == RS_R
154
                    || if item.kwd() == DIR_SOURCE {
70
                        let was_first = first_dir_source;
70
                        first_dir_source = false;
70
                        !was_first
                    } else {
84
                        false
                    }
            }
168
        });

            
42
        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
42
        let voter = ConsensusVoterInfo::from_section(&voter_sec)?;

            
42
        Ok(Some(voter))
56
    }

            
    /// Extract the footer (but not signatures) from the reader.
10
    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
10
        use NetstatusKwd::*;
30
        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
10
        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
10
        let footer = Footer::from_section(&footer_sec)?;
9
        Ok(footer)
10
    }

            
    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
    /// out of routerstatus entries.
    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
        use NetstatusKwd::*;
81
        match r.iter().peek() {
            None => return Ok(None),
81
            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
71
            _ => (),
71
        };
71

            
71
        let pos = r.pos();
71

            
71
        let mut first_r = true;
552
        let mut p = r.pause_at(|i| match i {
            Err(_) => false,
552
            Ok(item) => {
552
                item.kwd() == DIRECTORY_FOOTER
541
                    || if item.kwd() == RS_R {
131
                        let was_first = first_r;
131
                        first_r = false;
131
                        !was_first
                    } else {
410
                        false
                    }
            }
552
        });

            
71
        let rules = match RS::flavor() {
63
            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
8
            ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
        };

            
71
        let rs_sec = rules.parse(&mut p)?;
71
        let rs = RS::from_section(&rs_sec)?;
68
        Ok(Some((pos, rs)))
81
    }

            
    /// Extract an entire UncheckedConsensus from a reader.
    ///
    /// Returns the signed portion of the string, the remainder of the
    /// string, and an UncheckedConsensus.
17
    fn parse_from_reader<'a>(
17
        r: &mut NetDocReader<'a, NetstatusKwd>,
17
    ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
        use NetstatusKwd::*;
14
        let (header, start_pos) = {
242
            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
17
            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)]
16
            let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
16
            (ConsensusHeader::from_section(&header_sec)?, pos)
14
        };
14
        if RS::flavor() != header.hdr.flavor {
            return Err(EK::BadDocumentType.with_msg(format!(
                "Expected {:?}, got {:?}",
                RS::flavor(),
                header.hdr.flavor
            )));
14
        }
14

            
14
        let mut voters = Vec::new();

            
56
        while let Some(voter) = Self::take_voterinfo(r)? {
42
            voters.push(voter);
42
        }

            
14
        let mut relays: Vec<RS> = Vec::new();
81
        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
68
            if let Some(prev) = relays.last() {
55
                if prev.rsa_identity() >= routerstatus.rsa_identity() {
1
                    return Err(EK::WrongSortOrder.at_pos(pos));
54
                }
13
            }
67
            relays.push(routerstatus);
        }
10
        relays.shrink_to_fit();

            
10
        let footer = Self::take_footer(r)?;

            
9
        let consensus = Consensus {
9
            header,
9
            voters,
9
            relays,
9
            footer,
9
        };
9

            
9
        // Find the signatures.
9
        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
9
        let mut signatures = Vec::new();
27
        for item in r.iter() {
27
            let item = item?;
27
            if item.kwd() != DIRECTORY_SIGNATURE {
                return Err(EK::UnexpectedToken
                    .with_msg(item.kwd().to_str())
                    .at_pos(item.pos()));
27
            }

            
27
            let sig = Signature::from_item(&item)?;
27
            if first_sig.is_none() {
9
                first_sig = Some(item);
18
            }
27
            signatures.push(sig);
        }

            
9
        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)]
9
            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
9
        };
9

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

            
9
        let unval = UnvalidatedConsensus {
9
            consensus,
9
            siggroup,
9
            n_authorities: None,
9
        };
9
        let lifetime = unval.consensus.header.hdr.lifetime.clone();
9
        let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
9
        let dist_interval = time::Duration::from_secs(delay.1.into());
9
        let starting_time = lifetime.valid_after - dist_interval;
9
        let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
9
        Ok((signed_str, remainder, timebound))
17
    }
}

            
/// 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!
#[derive(Debug, Clone)]
pub struct UnvalidatedConsensus<RS> {
    /// The consensus object. We don't want to expose this until it's
    /// validated.
    consensus: Consensus<RS>,
    /// The signatures that need to be validated before we can call
    /// this consensus valid.
    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`.
    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]
7
    pub fn set_n_authorities(self, n_authorities: u16) -> Self {
7
        UnvalidatedConsensus {
7
            n_authorities: Some(n_authorities),
7
            ..self
7
        }
7
    }

            
    /// Return an iterator of all the certificate IDs that we might use
    /// to validate this consensus.
4
    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
4
        match self.key_is_correct(&[]) {
            Ok(()) => Vec::new(),
4
            Err(missing) => missing,
        }
4
        .into_iter()
4
    }

            
    /// Return the lifetime of this unvalidated consensus
5
    pub fn peek_lifetime(&self) -> &Lifetime {
5
        self.consensus.lifetime()
5
    }

            
    /// 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.)
10
    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
10
        self.siggroup.could_validate(authorities)
10
    }

            
    /// 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;

            
14
    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
14
        let (n_ok, missing) = self.siggroup.list_missing(k);
14
        match self.n_authorities {
14
            Some(n) if n_ok > (n / 2) as usize => Ok(()),
24
            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
        }
14
    }
4
    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
4
        match self.n_authorities {
            None => Err(Error::from(internal!(
                "Didn't set authorities on consensus"
            ))),
4
            Some(authority) => {
4
                if self.siggroup.validate(authority, k) {
3
                    Ok(())
                } else {
1
                    Err(EK::BadSignature.err())
                }
            }
        }
4
    }
5
    fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
5
        self.consensus
5
    }
}

            
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.
142
    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
142
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
142
        let mut missing = Vec::new();
568
        for sig in &self.signatures {
426
            let id_fingerprint = &sig.key_ids.id_fingerprint;
426
            if ok.contains(id_fingerprint) {
                continue;
426
            }
426
            if sig.find_cert(certs).is_some() {
96
                ok.insert(*id_fingerprint);
96
                continue;
330
            }
330

            
330
            missing.push(sig);
        }
142
        (ok.len(), missing)
142
    }

            
    /// Given a list of authority identity key fingerprints, return true if
    /// this signature group is _potentially_ well-signed according to those
    /// authorities.
90
    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
90
        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
360
        for sig in &self.signatures {
270
            let id_fp = &sig.key_ids.id_fingerprint;
270
            if signed_by.contains(id_fp) {
                // Already found this in the list.
                continue;
270
            }
270
            if authorities.contains(&id_fp) {
144
                signed_by.insert(*id_fp);
144
            }
        }

            
90
        signed_by.len() > (authorities.len() / 2)
90
    }

            
    /// 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.
20
    fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
20
        // A set of the authorities (by identity) who have have signed
20
        // this document.  We use a set here in case `certs` has more
20
        // than one certificate for a single authority.
20
        let mut ok: HashSet<RsaIdentity> = HashSet::new();

            
80
        for sig in &self.signatures {
60
            let id_fingerprint = &sig.key_ids.id_fingerprint;
60
            if ok.contains(id_fingerprint) {
                // We already checked at least one signature using this
                // authority's identity fingerprint.
                continue;
60
            }

            
60
            let d: Option<&[u8]> = match sig.digestname.as_ref() {
60
                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
3
                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
                _ => None, // We don't know how to find this digest.
            };
60
            if d.is_none() {
                // We don't support this kind of digest for this kind
                // of document.
                continue;
60
            }
60

            
60
            // Unwrap should be safe because of above `d.is_none()` check
60
            #[allow(clippy::unwrap_used)]
60
            match sig.check_signature(d.as_ref().unwrap(), certs) {
41
                SigCheckResult::Valid => {
41
                    ok.insert(*id_fingerprint);
41
                }
19
                _ => continue,
            }
        }

            
20
        ok.len() > (n_authorities / 2) as usize
20
    }
}

            
#[cfg(test)]
mod test {
    #![allow(clippy::unwrap_used)]
    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 it = reader.iter();
        let tok = it.next().unwrap();
        assert!(it.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 = SharedRandVal::from_item(&sr).unwrap();

            
        assert_eq!(sr.n_reveals, 9);
        assert_eq!(
            sr.value,
            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
        );

            
        let sr = gettok("foo bar\n").unwrap();
        let sr = SharedRandVal::from_item(&sr);
        assert!(sr.is_err());
    }
}