tor_netdoc/doc/
netstatus.rs

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
46mod rs;
47
48#[cfg(feature = "build_docs")]
49mod build;
50
51use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
52use crate::parse::keyword::Keyword;
53use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
54use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
55use crate::types::misc::*;
56use crate::util::private::Sealed;
57use crate::util::PeekableIterator;
58use crate::{Error, NetdocErrorKind as EK, Pos, Result};
59use std::collections::{HashMap, HashSet};
60use std::{net, result, time};
61use tor_error::internal;
62use tor_protover::Protocols;
63
64use bitflags::bitflags;
65use digest::Digest;
66use once_cell::sync::Lazy;
67use tor_checkable::{timed::TimerangeBound, ExternallySigned};
68use tor_llcrypto as ll;
69use tor_llcrypto::pk::rsa::RsaIdentity;
70
71use serde::{Deserialize, Deserializer};
72
73#[cfg(feature = "build_docs")]
74pub use build::ConsensusBuilder;
75#[cfg(feature = "build_docs")]
76pub use rs::build::RouterStatusBuilder;
77
78pub use rs::MdConsensusRouterStatus;
79#[cfg(feature = "ns_consensus")]
80pub use rs::NsConsensusRouterStatus;
81use void::ResultVoidExt as _;
82
83/// The lifetime of a networkstatus document.
84///
85/// In a consensus, this type describes when the consensus may safely
86/// be used.  In a vote, this type describes the proposed lifetime for a
87/// consensus.
88#[derive(Clone, Debug)]
89pub struct Lifetime {
90    /// Time at which the document becomes valid
91    valid_after: time::SystemTime,
92    /// Time after which there is expected to be a better version
93    /// of this consensus
94    fresh_until: time::SystemTime,
95    /// Time after which this consensus is expired.
96    ///
97    /// (In practice, Tor clients will keep using documents for a while
98    /// after this expiration time, if no better one can be found.)
99    valid_until: time::SystemTime,
100}
101
102impl Lifetime {
103    /// Construct a new Lifetime.
104    pub fn new(
105        valid_after: time::SystemTime,
106        fresh_until: time::SystemTime,
107        valid_until: time::SystemTime,
108    ) -> Result<Self> {
109        if valid_after < fresh_until && fresh_until < valid_until {
110            Ok(Lifetime {
111                valid_after,
112                fresh_until,
113                valid_until,
114            })
115        } else {
116            Err(EK::InvalidLifetime.err())
117        }
118    }
119    /// Return time when this consensus first becomes valid.
120    ///
121    /// (You might see a consensus a little while before this time,
122    /// since voting tries to finish up before the.)
123    pub fn valid_after(&self) -> time::SystemTime {
124        self.valid_after
125    }
126    /// Return time when this consensus is no longer fresh.
127    ///
128    /// You can use the consensus after this time, but there is (or is
129    /// supposed to be) a better one by this point.
130    pub fn fresh_until(&self) -> time::SystemTime {
131        self.fresh_until
132    }
133    /// Return the time when this consensus is no longer valid.
134    ///
135    /// You should try to get a better consensus after this time,
136    /// though it's okay to keep using this one if no more recent one
137    /// can be found.
138    pub fn valid_until(&self) -> time::SystemTime {
139        self.valid_until
140    }
141    /// Return true if this consensus is officially valid at the provided time.
142    pub fn valid_at(&self, when: time::SystemTime) -> bool {
143        self.valid_after <= when && when <= self.valid_until
144    }
145
146    /// Return the voting period implied by this lifetime.
147    ///
148    /// (The "voting period" is the amount of time in between when a consensus first
149    /// becomes valid, and when the next consensus is expected to become valid)
150    pub fn voting_period(&self) -> time::Duration {
151        let valid_after = self.valid_after();
152        let fresh_until = self.fresh_until();
153        fresh_until
154            .duration_since(valid_after)
155            .expect("Mis-formed lifetime")
156    }
157}
158
159/// A set of named network parameters.
160///
161/// These are used to describe current settings for the Tor network,
162/// current weighting parameters for path selection, and so on.  They're
163/// encoded with a space-separated K=V format.
164///
165/// A `NetParams<i32>` is part of the validated directory manager configuration,
166/// where it is built (in the builder-pattern sense) from a transparent HashMap.
167#[derive(Debug, Clone, Default, Eq, PartialEq)]
168pub struct NetParams<T> {
169    /// Map from keys to values.
170    params: HashMap<String, T>,
171}
172
173impl<T> NetParams<T> {
174    /// Create a new empty list of NetParams.
175    #[allow(unused)]
176    pub fn new() -> Self {
177        NetParams {
178            params: HashMap::new(),
179        }
180    }
181    /// Retrieve a given network parameter, if it is present.
182    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
183        self.params.get(v.as_ref())
184    }
185    /// Return an iterator over all key value pairs in an arbitrary order.
186    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
187        self.params.iter()
188    }
189    /// Set or replace the value of a network parameter.
190    pub fn set(&mut self, k: String, v: T) {
191        self.params.insert(k, v);
192    }
193}
194
195impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
196    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
197        NetParams {
198            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
199        }
200    }
201}
202
203impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
204    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
205        self.params.extend(iter);
206    }
207}
208
209impl<'de, T> Deserialize<'de> for NetParams<T>
210where
211    T: Deserialize<'de>,
212{
213    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
214    where
215        D: Deserializer<'de>,
216    {
217        let params = HashMap::deserialize(deserializer)?;
218        Ok(NetParams { params })
219    }
220}
221
222/// A list of subprotocol versions that implementors should/must provide.
223///
224/// Each consensus has two of these: one for relays, and one for clients.
225#[allow(dead_code)]
226#[derive(Debug, Clone, Default)]
227pub struct ProtoStatus {
228    /// Set of protocols that are recommended; if we're missing a protocol
229    /// in this list we should warn the user.
230    recommended: Protocols,
231    /// Set of protocols that are required; if we're missing a protocol
232    /// in this list we should refuse to start.
233    required: Protocols,
234}
235
236/// A recognized 'flavor' of consensus document.
237#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
238#[non_exhaustive]
239pub enum ConsensusFlavor {
240    /// A "microdesc"-flavored consensus.  This is the one that
241    /// clients and relays use today.
242    Microdesc,
243    /// A "networkstatus"-flavored consensus.  It's used for
244    /// historical and network-health purposes.  Instead of listing
245    /// microdescriptor digests, it lists digests of full relay
246    /// descriptors.
247    Ns,
248}
249
250impl ConsensusFlavor {
251    /// Return the name of this consensus flavor.
252    pub fn name(&self) -> &'static str {
253        match self {
254            ConsensusFlavor::Ns => "ns",
255            ConsensusFlavor::Microdesc => "microdesc",
256        }
257    }
258    /// Try to find the flavor whose name is `name`.
259    ///
260    /// For historical reasons, an unnamed flavor indicates an "Ns"
261    /// document.
262    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
263        match name {
264            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
265            Some("ns") | None => Ok(ConsensusFlavor::Ns),
266            Some(other) => {
267                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
268            }
269        }
270    }
271}
272
273/// The signature of a single directory authority on a networkstatus document.
274#[allow(dead_code)]
275#[cfg_attr(
276    feature = "dangerous-expose-struct-fields",
277    visible::StructFields(pub),
278    non_exhaustive
279)]
280#[derive(Debug, Clone)]
281pub struct Signature {
282    /// The name of the digest algorithm used to make the signature.
283    ///
284    /// Currently sha1 and sh256 are recognized.  Here we only support
285    /// sha256.
286    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
287    digestname: String,
288    /// Fingerprints of the keys for the authority that made
289    /// this signature.
290    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
291    key_ids: AuthCertKeyIds,
292    /// The signature itself.
293    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
294    signature: Vec<u8>,
295}
296
297/// A collection of signatures that can be checked on a networkstatus document
298#[allow(dead_code)]
299#[cfg_attr(
300    feature = "dangerous-expose-struct-fields",
301    visible::StructFields(pub),
302    non_exhaustive
303)]
304#[derive(Debug, Clone)]
305pub struct SignatureGroup {
306    /// The sha256 of the document itself
307    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
308    sha256: Option<[u8; 32]>,
309    /// The sha1 of the document itself
310    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
311    sha1: Option<[u8; 20]>,
312    /// The signatures listed on the document.
313    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
314    signatures: Vec<Signature>,
315}
316
317/// A shared random value produced by the directory authorities.
318#[derive(
319    Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
320)]
321// (This doesn't need to use CtByteArray; we don't really need to compare these.)
322pub struct SharedRandVal([u8; 32]);
323
324/// A shared-random value produced by the directory authorities,
325/// along with meta-information about that value.
326#[allow(dead_code)]
327#[cfg_attr(
328    feature = "dangerous-expose-struct-fields",
329    visible::StructFields(pub),
330    visibility::make(pub),
331    non_exhaustive
332)]
333#[derive(Debug, Clone)]
334pub struct SharedRandStatus {
335    /// How many authorities revealed shares that contributed to this value.
336    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
337    n_reveals: u8,
338    /// The current random value.
339    ///
340    /// The properties of the secure shared-random system guarantee
341    /// that this value isn't predictable before it first becomes
342    /// live, and that a hostile party could not have forced it to
343    /// have any more than a small number of possible random values.
344    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
345    value: SharedRandVal,
346
347    /// The time when this SharedRandVal becomes (or became) the latest.
348    ///
349    /// (This is added per proposal 342, assuming that gets accepted.)
350    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
351    timestamp: Option<time::SystemTime>,
352}
353
354/// Parts of the networkstatus header that are present in every networkstatus.
355///
356/// NOTE: this type is separate from the header parts that are only in
357/// votes or only in consensuses, even though we don't implement votes yet.
358#[allow(dead_code)]
359#[cfg_attr(
360    feature = "dangerous-expose-struct-fields",
361    visible::StructFields(pub),
362    visibility::make(pub),
363    non_exhaustive
364)]
365#[derive(Debug, Clone)]
366struct CommonHeader {
367    /// What kind of consensus document is this?  Absent in votes and
368    /// in ns-flavored consensuses.
369    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
370    flavor: ConsensusFlavor,
371    /// Over what time is this consensus valid?  (For votes, this is
372    /// the time over which the voted-upon consensus should be valid.)
373    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
374    lifetime: Lifetime,
375    /// List of recommended Tor client versions.
376    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
377    client_versions: Vec<String>,
378    /// List of recommended Tor relay versions.
379    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
380    relay_versions: Vec<String>,
381    /// Lists of recommended and required subprotocol versions for clients
382    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
383    client_protos: ProtoStatus,
384    /// Lists of recommended and required subprotocol versions for relays
385    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
386    relay_protos: ProtoStatus,
387    /// Declared parameters for tunable settings about how to the
388    /// network should operator. Some of these adjust timeouts and
389    /// whatnot; some features things on and off.
390    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
391    params: NetParams<i32>,
392    /// How long in seconds should voters wait for votes and
393    /// signatures (respectively) to propagate?
394    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
395    voting_delay: Option<(u32, u32)>,
396}
397
398/// The header of a consensus networkstatus.
399#[allow(dead_code)]
400#[cfg_attr(
401    feature = "dangerous-expose-struct-fields",
402    visible::StructFields(pub),
403    visibility::make(pub),
404    non_exhaustive
405)]
406#[derive(Debug, Clone)]
407struct ConsensusHeader {
408    /// Header fields common to votes and consensuses
409    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
410    hdr: CommonHeader,
411    /// What "method" was used to produce this consensus?  (A
412    /// consensus method is a version number used by authorities to
413    /// upgrade the consensus algorithm.)
414    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
415    consensus_method: u32,
416    /// Global shared-random value for the previous shared-random period.
417    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
418    shared_rand_prev: Option<SharedRandStatus>,
419    /// Global shared-random value for the current shared-random period.
420    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
421    shared_rand_cur: Option<SharedRandStatus>,
422}
423
424/// Description of an authority's identity and address.
425///
426/// (Corresponds to a dir-source line.)
427#[allow(dead_code)]
428#[cfg_attr(
429    feature = "dangerous-expose-struct-fields",
430    visible::StructFields(pub),
431    visibility::make(pub),
432    non_exhaustive
433)]
434#[derive(Debug, Clone)]
435struct DirSource {
436    /// human-readable nickname for this authority.
437    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
438    nickname: String,
439    /// Fingerprint for the _authority_ identity key of this
440    /// authority.
441    ///
442    /// This is the same key as the one that signs the authority's
443    /// certificates.
444    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
445    identity: RsaIdentity,
446    /// IP address for the authority
447    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
448    ip: net::IpAddr,
449    /// HTTP directory port for this authority
450    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
451    dir_port: u16,
452    /// OR port for this authority.
453    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
454    or_port: u16,
455}
456
457bitflags! {
458    /// A set of recognized directory flags on a single relay.
459    ///
460    /// These flags come from a consensus directory document, and are
461    /// used to describe what the authorities believe about the relay.
462    /// If the document contained any flags that we _didn't_ recognize,
463    /// they are not listed in this type.
464    ///
465    /// The bit values used to represent the flags have no meaning;
466    /// they may change between releases of this crate.  Relying on their
467    /// values may void your semver guarantees.
468    #[derive(Clone, Copy, Debug)]
469    pub struct RelayFlags: u16 {
470        /// Is this a directory authority?
471        const AUTHORITY = (1<<0);
472        /// Is this relay marked as a bad exit?
473        ///
474        /// Bad exits can be used as intermediate relays, but not to
475        /// deliver traffic.
476        const BAD_EXIT = (1<<1);
477        /// Is this relay marked as an exit for weighting purposes?
478        const EXIT = (1<<2);
479        /// Is this relay considered "fast" above a certain threshold?
480        const FAST = (1<<3);
481        /// Is this relay suitable for use as a guard relay?
482        ///
483        /// Clients choose their their initial relays from among the set
484        /// of Guard relays.
485        const GUARD = (1<<4);
486        /// Does this relay participate on the onion service directory
487        /// ring?
488        const HSDIR = (1<<5);
489        /// Set if this relay is considered "middle only", not suitable to run
490        /// as an exit or guard relay.
491        ///
492        /// Note that this flag is only used by authorities as part of
493        /// the voting process; clients do not and should not act
494        /// based on whether it is set.
495        const MIDDLE_ONLY = (1<<6);
496        /// If set, there is no consensus for the ed25519 key for this relay.
497        const NO_ED_CONSENSUS = (1<<7);
498        /// Is this relay considered "stable" enough for long-lived circuits?
499        const STABLE = (1<<8);
500        /// Set if the authorities are requesting a fresh descriptor for
501        /// this relay.
502        const STALE_DESC = (1<<9);
503        /// Set if this relay is currently running.
504        ///
505        /// This flag can appear in votes, but in consensuses, every relay
506        /// is assumed to be running.
507        const RUNNING = (1<<10);
508        /// Set if this relay is considered "valid" -- allowed to be on
509        /// the network.
510        ///
511        /// This flag can appear in votes, but in consensuses, every relay
512        /// is assumed to be valid.
513        const VALID = (1<<11);
514        /// Set if this relay supports a currently recognized version of the
515        /// directory protocol.
516        const V2DIR = (1<<12);
517    }
518}
519
520/// Recognized weight fields on a single relay in a consensus
521#[non_exhaustive]
522#[derive(Debug, Clone, Copy)]
523pub enum RelayWeight {
524    /// An unmeasured weight for a relay.
525    Unmeasured(u32),
526    /// An measured weight for a relay.
527    Measured(u32),
528}
529
530impl RelayWeight {
531    /// Return true if this weight is the result of a successful measurement
532    pub fn is_measured(&self) -> bool {
533        matches!(self, RelayWeight::Measured(_))
534    }
535    /// Return true if this weight is nonzero
536    pub fn is_nonzero(&self) -> bool {
537        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
538    }
539}
540
541/// All information about a single authority, as represented in a consensus
542#[allow(dead_code)]
543#[cfg_attr(
544    feature = "dangerous-expose-struct-fields",
545    visible::StructFields(pub),
546    visibility::make(pub),
547    non_exhaustive
548)]
549#[derive(Debug, Clone)]
550struct ConsensusVoterInfo {
551    /// Contents of the dirsource line about an authority
552    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
553    dir_source: DirSource,
554    /// Human-readable contact information about the authority
555    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
556    contact: String,
557    /// Digest of the vote that the authority cast to contribute to
558    /// this consensus.
559    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
560    vote_digest: Vec<u8>,
561}
562
563/// The signed footer of a consensus netstatus.
564#[allow(dead_code)]
565#[cfg_attr(
566    feature = "dangerous-expose-struct-fields",
567    visible::StructFields(pub),
568    visibility::make(pub),
569    non_exhaustive
570)]
571#[derive(Debug, Clone)]
572struct Footer {
573    /// Weights to be applied to certain classes of relays when choosing
574    /// for different roles.
575    ///
576    /// For example, we want to avoid choosing exits for non-exit
577    /// roles when overall the proportion of exits is small.
578    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
579    weights: NetParams<i32>,
580}
581
582/// Trait to parse a single relay as listed in a consensus document.
583///
584/// TODO(nickm): I'd rather not have this trait be public, but I haven't yet
585/// figured out how to make it private.
586pub trait ParseRouterStatus: Sized + Sealed {
587    /// Parse this object from a `Section` object containing its
588    /// elements.
589    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Self>;
590
591    /// Return the networkstatus consensus flavor in which this
592    /// routerstatus appears.
593    fn flavor() -> ConsensusFlavor;
594}
595
596/// Represents a single relay as listed in a consensus document.
597///
598/// Not implementable outside of the `tor-netdoc` crate.
599pub trait RouterStatus: Sealed {
600    /// A digest of the document that's identified by this RouterStatus.
601    type DocumentDigest: Clone;
602
603    /// Return RSA identity for the relay described by this RouterStatus
604    fn rsa_identity(&self) -> &RsaIdentity;
605
606    /// Return the digest of the document identified by this
607    /// routerstatus.
608    fn doc_digest(&self) -> &Self::DocumentDigest;
609}
610
611/// A single microdescriptor consensus netstatus
612///
613/// TODO: This should possibly turn into a parameterized type, to represent
614/// votes and ns consensuses.
615#[allow(dead_code)]
616#[cfg_attr(
617    feature = "dangerous-expose-struct-fields",
618    visible::StructFields(pub),
619    non_exhaustive
620)]
621#[derive(Debug, Clone)]
622pub struct Consensus<RS> {
623    /// Part of the header shared by all consensus types.
624    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
625    header: ConsensusHeader,
626    /// List of voters whose votes contributed to this consensus.
627    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
628    voters: Vec<ConsensusVoterInfo>,
629    /// A list of routerstatus entries for the relays on the network,
630    /// with one entry per relay.
631    ///
632    /// These are currently ordered by the router's RSA identity, but this is not
633    /// to be relied on, since we may want to even abolish RSA at some point!
634    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
635    relays: Vec<RS>,
636    /// Footer for the consensus object.
637    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
638    footer: Footer,
639}
640
641/// A consensus document that lists relays along with their
642/// microdescriptor documents.
643pub type MdConsensus = Consensus<MdConsensusRouterStatus>;
644
645/// An MdConsensus that has been parsed and checked for timeliness,
646/// but not for signatures.
647pub type UnvalidatedMdConsensus = UnvalidatedConsensus<MdConsensusRouterStatus>;
648
649/// An MdConsensus that has been parsed but not checked for signatures
650/// and timeliness.
651pub type UncheckedMdConsensus = UncheckedConsensus<MdConsensusRouterStatus>;
652
653#[cfg(feature = "ns_consensus")]
654/// A consensus document that lists relays along with their
655/// router descriptor documents.
656pub type NsConsensus = Consensus<NsConsensusRouterStatus>;
657
658#[cfg(feature = "ns_consensus")]
659/// An NsConsensus that has been parsed and checked for timeliness,
660/// but not for signatures.
661pub type UnvalidatedNsConsensus = UnvalidatedConsensus<NsConsensusRouterStatus>;
662
663#[cfg(feature = "ns_consensus")]
664/// An NsConsensus that has been parsed but not checked for signatures
665/// and timeliness.
666pub type UncheckedNsConsensus = UncheckedConsensus<NsConsensusRouterStatus>;
667
668impl<RS> Consensus<RS> {
669    /// Return the Lifetime for this consensus.
670    pub fn lifetime(&self) -> &Lifetime {
671        &self.header.hdr.lifetime
672    }
673
674    /// Return a slice of all the routerstatus entries in this consensus.
675    pub fn relays(&self) -> &[RS] {
676        &self.relays[..]
677    }
678
679    /// Return a mapping from keywords to integers representing how
680    /// to weight different kinds of relays in different path positions.
681    pub fn bandwidth_weights(&self) -> &NetParams<i32> {
682        &self.footer.weights
683    }
684
685    /// Return the map of network parameters that this consensus advertises.
686    pub fn params(&self) -> &NetParams<i32> {
687        &self.header.hdr.params
688    }
689
690    /// Return the latest shared random value, if the consensus
691    /// contains one.
692    pub fn shared_rand_cur(&self) -> Option<&SharedRandStatus> {
693        self.header.shared_rand_cur.as_ref()
694    }
695
696    /// Return the previous shared random value, if the consensus
697    /// contains one.
698    pub fn shared_rand_prev(&self) -> Option<&SharedRandStatus> {
699        self.header.shared_rand_prev.as_ref()
700    }
701
702    /// Return a [`ProtoStatus`] that lists the network's current requirements and
703    /// recommendations for the list of protocols that every relay must implement.  
704    pub fn relay_protocol_status(&self) -> &ProtoStatus {
705        &self.header.hdr.relay_protos
706    }
707
708    /// Return a [`ProtoStatus`] that lists the network's current requirements and
709    /// recommendations for the list of protocols that every client must implement.
710    pub fn client_protocol_status(&self) -> &ProtoStatus {
711        &self.header.hdr.client_protos
712    }
713}
714
715decl_keyword! {
716    /// Keywords that can be used in votes and consensuses.
717    // TODO: This is public because otherwise we can't use it in the
718    // ParseRouterStatus crate.  But I'd rather find a way to make it
719    // private.
720    #[non_exhaustive]
721    #[allow(missing_docs)]
722    pub NetstatusKwd {
723        // Header
724        "network-status-version" => NETWORK_STATUS_VERSION,
725        "vote-status" => VOTE_STATUS,
726        "consensus-methods" => CONSENSUS_METHODS,
727        "consensus-method" => CONSENSUS_METHOD,
728        "published" => PUBLISHED,
729        "valid-after" => VALID_AFTER,
730        "fresh-until" => FRESH_UNTIL,
731        "valid-until" => VALID_UNTIL,
732        "voting-delay" => VOTING_DELAY,
733        "client-versions" => CLIENT_VERSIONS,
734        "server-versions" => SERVER_VERSIONS,
735        "known-flags" => KNOWN_FLAGS,
736        "flag-thresholds" => FLAG_THRESHOLDS,
737        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
738        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
739        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
740        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
741        "params" => PARAMS,
742        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
743        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
744        // "package" is now ignored.
745
746        // header in consensus, voter section in vote?
747        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
748        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
749
750        // Voter section (both)
751        "dir-source" => DIR_SOURCE,
752        "contact" => CONTACT,
753
754        // voter section (vote, but not consensus)
755        "legacy-dir-key" => LEGACY_DIR_KEY,
756        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
757        "shared-rand-commit" => SHARED_RAND_COMMIT,
758
759        // voter section (consensus, but not vote)
760        "vote-digest" => VOTE_DIGEST,
761
762        // voter cert beginning (but only the beginning)
763        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
764
765        // routerstatus
766        "r" => RS_R,
767        "a" => RS_A,
768        "s" => RS_S,
769        "v" => RS_V,
770        "pr" => RS_PR,
771        "w" => RS_W,
772        "p" => RS_P,
773        "m" => RS_M,
774        "id" => RS_ID,
775
776        // footer
777        "directory-footer" => DIRECTORY_FOOTER,
778        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
779        "directory-signature" => DIRECTORY_SIGNATURE,
780    }
781}
782
783/// Shared parts of rules for all kinds of netstatus headers
784static NS_HEADER_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
785    use NetstatusKwd::*;
786    let mut rules = SectionRules::builder();
787    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
788    rules.add(VOTE_STATUS.rule().required().args(1..));
789    rules.add(VALID_AFTER.rule().required());
790    rules.add(FRESH_UNTIL.rule().required());
791    rules.add(VALID_UNTIL.rule().required());
792    rules.add(VOTING_DELAY.rule().args(2..));
793    rules.add(CLIENT_VERSIONS.rule());
794    rules.add(SERVER_VERSIONS.rule());
795    rules.add(KNOWN_FLAGS.rule().required());
796    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
797    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
798    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
799    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
800    rules.add(PARAMS.rule());
801    rules
802});
803/// Rules for parsing the header of a consensus.
804static NS_HEADER_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
805    use NetstatusKwd::*;
806    let mut rules = NS_HEADER_RULES_COMMON_.clone();
807    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
808    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
809    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
810    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
811    rules.build()
812});
813/*
814/// Rules for parsing the header of a vote.
815static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
816    use NetstatusKwd::*;
817    let mut rules = NS_HEADER_RULES_COMMON_.clone();
818    rules.add(CONSENSUS_METHODS.rule().args(1..));
819    rules.add(FLAG_THRESHOLDS.rule());
820    rules.add(BANDWIDTH_FILE_HEADERS.rule());
821    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
822    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
823    rules
824};
825/// Rules for parsing a single voter's information in a vote.
826static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
827    use NetstatusKwd::*;
828    let mut rules = SectionRules::new();
829    rules.add(DIR_SOURCE.rule().required().args(6..));
830    rules.add(CONTACT.rule().required());
831    rules.add(LEGACY_DIR_KEY.rule().args(1..));
832    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
833    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
834    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
835    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
836    // then comes an entire cert: When we implement vote parsing,
837    // we should use the authcert code for handling that.
838    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
839    rules
840};
841 */
842/// Rules for parsing a single voter's information in a consensus
843static NS_VOTERINFO_RULES_CONSENSUS: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
844    use NetstatusKwd::*;
845    let mut rules = SectionRules::builder();
846    rules.add(DIR_SOURCE.rule().required().args(6..));
847    rules.add(CONTACT.rule().required());
848    rules.add(VOTE_DIGEST.rule().required());
849    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
850    rules.build()
851});
852/// Shared rules for parsing a single routerstatus
853static NS_ROUTERSTATUS_RULES_COMMON_: Lazy<SectionRulesBuilder<NetstatusKwd>> = Lazy::new(|| {
854    use NetstatusKwd::*;
855    let mut rules = SectionRules::builder();
856    rules.add(RS_A.rule().may_repeat().args(1..));
857    rules.add(RS_S.rule().required());
858    rules.add(RS_V.rule());
859    rules.add(RS_PR.rule().required());
860    rules.add(RS_W.rule());
861    rules.add(RS_P.rule().args(2..));
862    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
863    rules
864});
865
866/// Rules for parsing a single routerstatus in an NS consensus
867static NS_ROUTERSTATUS_RULES_NSCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
868    use NetstatusKwd::*;
869    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
870    rules.add(RS_R.rule().required().args(8..));
871    rules.build()
872});
873
874/*
875/// Rules for parsing a single routerstatus in a vote
876static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
877    use NetstatusKwd::*;
878        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
879        rules.add(RS_R.rule().required().args(8..));
880        rules.add(RS_M.rule().may_repeat().args(2..));
881        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
882        rules
883    };
884*/
885/// Rules for parsing a single routerstatus in a microdesc consensus
886static NS_ROUTERSTATUS_RULES_MDCON: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
887    use NetstatusKwd::*;
888    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
889    rules.add(RS_R.rule().required().args(6..));
890    rules.add(RS_M.rule().required().args(1..));
891    rules.build()
892});
893/// Rules for parsing consensus fields from a footer.
894static NS_FOOTER_RULES: Lazy<SectionRules<NetstatusKwd>> = Lazy::new(|| {
895    use NetstatusKwd::*;
896    let mut rules = SectionRules::builder();
897    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
898    // consensus only
899    rules.add(BANDWIDTH_WEIGHTS.rule());
900    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
901    rules.build()
902});
903
904impl ProtoStatus {
905    /// Construct a ProtoStatus from two chosen keywords in a section.
906    fn from_section(
907        sec: &Section<'_, NetstatusKwd>,
908        recommend_token: NetstatusKwd,
909        required_token: NetstatusKwd,
910    ) -> Result<ProtoStatus> {
911        /// Helper: extract a Protocols entry from an item's arguments.
912        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
913            if let Some(item) = t {
914                item.args_as_str()
915                    .parse::<Protocols>()
916                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
917            } else {
918                Ok(Protocols::new())
919            }
920        }
921
922        let recommended = parse(sec.get(recommend_token))?;
923        let required = parse(sec.get(required_token))?;
924        Ok(ProtoStatus {
925            recommended,
926            required,
927        })
928    }
929
930    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
931    ///
932    /// Implementations may assume that relays on the network implement all the
933    /// protocols in the relays' required-protocols list.  Implementations should
934    /// refuse to start if they do not implement all the protocols on their own
935    /// (client or relay) required-protocols list.
936    pub fn required_protocols(&self) -> &Protocols {
937        &self.required
938    }
939
940    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
941    ///
942    /// Implementations should warn if they do not implement all the protocols
943    /// on their own (client or relay) recommended-protocols list.
944    pub fn recommended_protocols(&self) -> &Protocols {
945        &self.recommended
946    }
947}
948
949impl<T> std::str::FromStr for NetParams<T>
950where
951    T: std::str::FromStr,
952    T::Err: std::error::Error,
953{
954    type Err = Error;
955    fn from_str(s: &str) -> Result<Self> {
956        /// Helper: parse a single K=V pair.
957        fn parse_pair<U>(p: &str) -> Result<(String, U)>
958        where
959            U: std::str::FromStr,
960            U::Err: std::error::Error,
961        {
962            let parts: Vec<_> = p.splitn(2, '=').collect();
963            if parts.len() != 2 {
964                return Err(EK::BadArgument
965                    .at_pos(Pos::at(p))
966                    .with_msg("Missing = in key=value list"));
967            }
968            let num = parts[1].parse::<U>().map_err(|e| {
969                EK::BadArgument
970                    .at_pos(Pos::at(parts[1]))
971                    .with_msg(e.to_string())
972            })?;
973            Ok((parts[0].to_string(), num))
974        }
975
976        let params = s
977            .split(' ')
978            .filter(|p| !p.is_empty())
979            .map(parse_pair)
980            .collect::<Result<HashMap<_, _>>>()?;
981        Ok(NetParams { params })
982    }
983}
984
985impl CommonHeader {
986    /// Extract the CommonHeader members from a single header section.
987    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
988        use NetstatusKwd::*;
989
990        {
991            // this unwrap is safe because if there is not at least one
992            // token in the section, the section is unparsable.
993            #[allow(clippy::unwrap_used)]
994            let first = sec.first_item().unwrap();
995            if first.kwd() != NETWORK_STATUS_VERSION {
996                return Err(EK::UnexpectedToken
997                    .with_msg(first.kwd().to_str())
998                    .at_pos(first.pos()));
999            }
1000        }
1001
1002        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
1003
1004        let version: u32 = ver_item.parse_arg(0)?;
1005        if version != 3 {
1006            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
1007        }
1008        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
1009
1010        let valid_after = sec
1011            .required(VALID_AFTER)?
1012            .args_as_str()
1013            .parse::<Iso8601TimeSp>()?
1014            .into();
1015        let fresh_until = sec
1016            .required(FRESH_UNTIL)?
1017            .args_as_str()
1018            .parse::<Iso8601TimeSp>()?
1019            .into();
1020        let valid_until = sec
1021            .required(VALID_UNTIL)?
1022            .args_as_str()
1023            .parse::<Iso8601TimeSp>()?
1024            .into();
1025        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
1026
1027        let client_versions = sec
1028            .maybe(CLIENT_VERSIONS)
1029            .args_as_str()
1030            .unwrap_or("")
1031            .split(',')
1032            .map(str::to_string)
1033            .collect();
1034        let relay_versions = sec
1035            .maybe(SERVER_VERSIONS)
1036            .args_as_str()
1037            .unwrap_or("")
1038            .split(',')
1039            .map(str::to_string)
1040            .collect();
1041
1042        let client_protos = ProtoStatus::from_section(
1043            sec,
1044            RECOMMENDED_CLIENT_PROTOCOLS,
1045            REQUIRED_CLIENT_PROTOCOLS,
1046        )?;
1047        let relay_protos =
1048            ProtoStatus::from_section(sec, RECOMMENDED_RELAY_PROTOCOLS, REQUIRED_RELAY_PROTOCOLS)?;
1049
1050        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
1051
1052        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
1053            let n1 = tok.parse_arg(0)?;
1054            let n2 = tok.parse_arg(1)?;
1055            Some((n1, n2))
1056        } else {
1057            None
1058        };
1059
1060        Ok(CommonHeader {
1061            flavor,
1062            lifetime,
1063            client_versions,
1064            relay_versions,
1065            client_protos,
1066            relay_protos,
1067            params,
1068            voting_delay,
1069        })
1070    }
1071}
1072
1073impl SharedRandStatus {
1074    /// Parse a current or previous shared rand value from a given
1075    /// SharedRandPreviousValue or SharedRandCurrentValue.
1076    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1077        match item.kwd() {
1078            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1079            _ => {
1080                return Err(Error::from(internal!(
1081                    "wrong keyword {:?} on shared-random value",
1082                    item.kwd()
1083                ))
1084                .at_pos(item.pos()))
1085            }
1086        }
1087        let n_reveals: u8 = item.parse_arg(0)?;
1088        let val: B64 = item.parse_arg(1)?;
1089        let value = SharedRandVal(val.into_array()?);
1090        // Added in proposal 342
1091        let timestamp = item
1092            .parse_optional_arg::<Iso8601TimeNoSp>(2)?
1093            .map(Into::into);
1094        Ok(SharedRandStatus {
1095            n_reveals,
1096            value,
1097            timestamp,
1098        })
1099    }
1100
1101    /// Return the actual shared random value.
1102    pub fn value(&self) -> &SharedRandVal {
1103        &self.value
1104    }
1105
1106    /// Return the timestamp (if any) associated with this `SharedRandValue`.
1107    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1108        self.timestamp
1109    }
1110}
1111
1112impl ConsensusHeader {
1113    /// Parse the ConsensusHeader members from a provided section.
1114    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
1115        use NetstatusKwd::*;
1116
1117        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
1118        if status != "consensus" {
1119            return Err(EK::BadDocumentType.err());
1120        }
1121
1122        // We're ignoring KNOWN_FLAGS in the consensus.
1123
1124        let hdr = CommonHeader::from_section(sec)?;
1125
1126        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
1127
1128        let shared_rand_prev = sec
1129            .get(SHARED_RAND_PREVIOUS_VALUE)
1130            .map(SharedRandStatus::from_item)
1131            .transpose()?;
1132
1133        let shared_rand_cur = sec
1134            .get(SHARED_RAND_CURRENT_VALUE)
1135            .map(SharedRandStatus::from_item)
1136            .transpose()?;
1137
1138        Ok(ConsensusHeader {
1139            hdr,
1140            consensus_method,
1141            shared_rand_prev,
1142            shared_rand_cur,
1143        })
1144    }
1145}
1146
1147impl DirSource {
1148    /// Parse a "dir-source" item
1149    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1150        if item.kwd() != NetstatusKwd::DIR_SOURCE {
1151            return Err(
1152                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1153                    .at_pos(item.pos()),
1154            );
1155        }
1156        let nickname = item.required_arg(0)?.to_string();
1157        let identity = item.parse_arg::<Fingerprint>(1)?.into();
1158        let ip = item.parse_arg(3)?;
1159        let dir_port = item.parse_arg(4)?;
1160        let or_port = item.parse_arg(5)?;
1161
1162        Ok(DirSource {
1163            nickname,
1164            identity,
1165            ip,
1166            dir_port,
1167            or_port,
1168        })
1169    }
1170}
1171
1172impl ConsensusVoterInfo {
1173    /// Parse a single ConsensusVoterInfo from a voter info section.
1174    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
1175        use NetstatusKwd::*;
1176        // this unwrap should be safe because if there is not at least one
1177        // token in the section, the section is unparsable.
1178        #[allow(clippy::unwrap_used)]
1179        let first = sec.first_item().unwrap();
1180        if first.kwd() != DIR_SOURCE {
1181            return Err(Error::from(internal!(
1182                "Wrong keyword {:?} at start of voter info",
1183                first.kwd()
1184            ))
1185            .at_pos(first.pos()));
1186        }
1187        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1188
1189        let contact = sec.required(CONTACT)?.args_as_str().to_string();
1190
1191        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1192
1193        Ok(ConsensusVoterInfo {
1194            dir_source,
1195            contact,
1196            vote_digest,
1197        })
1198    }
1199}
1200
1201impl std::str::FromStr for RelayFlags {
1202    type Err = void::Void;
1203    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1204        Ok(match s {
1205            "Authority" => RelayFlags::AUTHORITY,
1206            "BadExit" => RelayFlags::BAD_EXIT,
1207            "Exit" => RelayFlags::EXIT,
1208            "Fast" => RelayFlags::FAST,
1209            "Guard" => RelayFlags::GUARD,
1210            "HSDir" => RelayFlags::HSDIR,
1211            "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1212            "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1213            "Stable" => RelayFlags::STABLE,
1214            "StaleDesc" => RelayFlags::STALE_DESC,
1215            "Running" => RelayFlags::RUNNING,
1216            "Valid" => RelayFlags::VALID,
1217            "V2Dir" => RelayFlags::V2DIR,
1218            _ => RelayFlags::empty(),
1219        })
1220    }
1221}
1222
1223impl RelayFlags {
1224    /// Parse a relay-flags entry from an "s" line.
1225    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1226        if item.kwd() != NetstatusKwd::RS_S {
1227            return Err(
1228                Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1229                    .at_pos(item.pos()),
1230            );
1231        }
1232        // These flags are implicit.
1233        let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1234
1235        let mut prev: Option<&str> = None;
1236        for s in item.args() {
1237            if let Some(p) = prev {
1238                if p >= s {
1239                    // Arguments out of order.
1240                    return Err(EK::BadArgument
1241                        .at_pos(item.pos())
1242                        .with_msg("Flags out of order"));
1243                }
1244            }
1245            let fl = s.parse().void_unwrap();
1246            flags |= fl;
1247            prev = Some(s);
1248        }
1249
1250        Ok(flags)
1251    }
1252}
1253
1254impl Default for RelayWeight {
1255    fn default() -> RelayWeight {
1256        RelayWeight::Unmeasured(0)
1257    }
1258}
1259
1260impl RelayWeight {
1261    /// Parse a routerweight from a "w" line.
1262    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1263        if item.kwd() != NetstatusKwd::RS_W {
1264            return Err(
1265                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1266                    .at_pos(item.pos()),
1267            );
1268        }
1269
1270        let params: NetParams<u32> = item.args_as_str().parse()?;
1271
1272        let bw = params.params.get("Bandwidth");
1273        let unmeas = params.params.get("Unmeasured");
1274
1275        let bw = match bw {
1276            None => return Ok(RelayWeight::Unmeasured(0)),
1277            Some(b) => *b,
1278        };
1279
1280        match unmeas {
1281            None | Some(0) => Ok(RelayWeight::Measured(bw)),
1282            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1283            _ => Err(EK::BadArgument
1284                .at_pos(item.pos())
1285                .with_msg("unmeasured value")),
1286        }
1287    }
1288}
1289
1290impl Footer {
1291    /// Parse a directory footer from a footer section.
1292    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1293        use NetstatusKwd::*;
1294        sec.required(DIRECTORY_FOOTER)?;
1295
1296        let weights = sec
1297            .maybe(BANDWIDTH_WEIGHTS)
1298            .args_as_str()
1299            .unwrap_or("")
1300            .parse()?;
1301
1302        Ok(Footer { weights })
1303    }
1304}
1305
1306/// Result of checking a single authority signature.
1307enum SigCheckResult {
1308    /// The signature checks out.  Great!
1309    Valid,
1310    /// The signature is invalid; no additional information could make it
1311    /// valid.
1312    Invalid,
1313    /// We can't check the signature because we don't have a
1314    /// certificate with the right signing key.
1315    MissingCert,
1316}
1317
1318impl Signature {
1319    /// Parse a Signature from a directory-signature section
1320    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1321        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1322            return Err(Error::from(internal!(
1323                "Wrong keyword {:?} for directory signature",
1324                item.kwd()
1325            ))
1326            .at_pos(item.pos()));
1327        }
1328
1329        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1330            (
1331                item.required_arg(0)?,
1332                item.required_arg(1)?,
1333                item.required_arg(2)?,
1334            )
1335        } else {
1336            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1337        };
1338
1339        let digestname = alg.to_string();
1340        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1341        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1342        let key_ids = AuthCertKeyIds {
1343            id_fingerprint,
1344            sk_fingerprint,
1345        };
1346        let signature = item.obj("SIGNATURE")?;
1347
1348        Ok(Signature {
1349            digestname,
1350            key_ids,
1351            signature,
1352        })
1353    }
1354
1355    /// Return true if this signature has the identity key and signing key
1356    /// that match a given cert.
1357    fn matches_cert(&self, cert: &AuthCert) -> bool {
1358        cert.key_ids() == &self.key_ids
1359    }
1360
1361    /// If possible, find the right certificate for checking this signature
1362    /// from among a slice of certificates.
1363    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1364        certs.iter().find(|&c| self.matches_cert(c))
1365    }
1366
1367    /// Try to check whether this signature is a valid signature of a
1368    /// provided digest, given a slice of certificates that might contain
1369    /// its signing key.
1370    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1371        match self.find_cert(certs) {
1372            None => SigCheckResult::MissingCert,
1373            Some(cert) => {
1374                let key = cert.signing_key();
1375                match key.verify(signed_digest, &self.signature[..]) {
1376                    Ok(()) => SigCheckResult::Valid,
1377                    Err(_) => SigCheckResult::Invalid,
1378                }
1379            }
1380        }
1381    }
1382}
1383
1384/// A Consensus object that has been parsed, but not checked for
1385/// signatures and timeliness.
1386pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
1387
1388impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
1389    /// Return a new ConsensusBuilder for building test consensus objects.
1390    ///
1391    /// This function is only available when the `build_docs` feature has
1392    /// been enabled.
1393    #[cfg(feature = "build_docs")]
1394    pub fn builder() -> ConsensusBuilder<RS> {
1395        ConsensusBuilder::new(RS::flavor())
1396    }
1397
1398    /// Try to parse a single networkstatus document from a string.
1399    pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
1400        let mut reader = NetDocReader::new(s);
1401        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
1402    }
1403    /// Extract a voter-info section from the reader; return
1404    /// Ok(None) when we are out of voter-info sections.
1405    fn take_voterinfo(
1406        r: &mut NetDocReader<'_, NetstatusKwd>,
1407    ) -> Result<Option<ConsensusVoterInfo>> {
1408        use NetstatusKwd::*;
1409
1410        match r.peek() {
1411            None => return Ok(None),
1412            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
1413            _ => (),
1414        };
1415
1416        let mut first_dir_source = true;
1417        // TODO: Extract this pattern into a "pause at second"???
1418        // Pause at the first 'r', or the second 'dir-source'.
1419        let mut p = r.pause_at(|i| match i {
1420            Err(_) => false,
1421            Ok(item) => {
1422                item.kwd() == RS_R
1423                    || if item.kwd() == DIR_SOURCE {
1424                        let was_first = first_dir_source;
1425                        first_dir_source = false;
1426                        !was_first
1427                    } else {
1428                        false
1429                    }
1430            }
1431        });
1432
1433        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
1434        let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
1435
1436        Ok(Some(voter))
1437    }
1438
1439    /// Extract the footer (but not signatures) from the reader.
1440    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
1441        use NetstatusKwd::*;
1442        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
1443        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
1444        let footer = Footer::from_section(&footer_sec)?;
1445        Ok(footer)
1446    }
1447
1448    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
1449    /// out of routerstatus entries.
1450    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
1451        use NetstatusKwd::*;
1452        match r.peek() {
1453            None => return Ok(None),
1454            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
1455            _ => (),
1456        };
1457
1458        let pos = r.pos();
1459
1460        let mut first_r = true;
1461        let mut p = r.pause_at(|i| match i {
1462            Err(_) => false,
1463            Ok(item) => {
1464                item.kwd() == DIRECTORY_FOOTER
1465                    || if item.kwd() == RS_R {
1466                        let was_first = first_r;
1467                        first_r = false;
1468                        !was_first
1469                    } else {
1470                        false
1471                    }
1472            }
1473        });
1474
1475        let rules = match RS::flavor() {
1476            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
1477            ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
1478        };
1479
1480        let rs_sec = rules.parse(&mut p)?;
1481        let rs = RS::from_section(&rs_sec)?;
1482        Ok(Some((pos, rs)))
1483    }
1484
1485    /// Extract an entire UncheckedConsensus from a reader.
1486    ///
1487    /// Returns the signed portion of the string, the remainder of the
1488    /// string, and an UncheckedConsensus.
1489    fn parse_from_reader<'a>(
1490        r: &mut NetDocReader<'a, NetstatusKwd>,
1491    ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
1492        use NetstatusKwd::*;
1493        let (header, start_pos) = {
1494            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
1495            let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
1496            // Unwrapping should be safe because above `.parse` would have
1497            // returned an Error
1498            #[allow(clippy::unwrap_used)]
1499            let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
1500            (ConsensusHeader::from_section(&header_sec)?, pos)
1501        };
1502        if RS::flavor() != header.hdr.flavor {
1503            return Err(EK::BadDocumentType.with_msg(format!(
1504                "Expected {:?}, got {:?}",
1505                RS::flavor(),
1506                header.hdr.flavor
1507            )));
1508        }
1509
1510        let mut voters = Vec::new();
1511
1512        while let Some(voter) = Self::take_voterinfo(r)? {
1513            voters.push(voter);
1514        }
1515
1516        let mut relays: Vec<RS> = Vec::new();
1517        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
1518            if let Some(prev) = relays.last() {
1519                if prev.rsa_identity() >= routerstatus.rsa_identity() {
1520                    return Err(EK::WrongSortOrder.at_pos(pos));
1521                }
1522            }
1523            relays.push(routerstatus);
1524        }
1525        relays.shrink_to_fit();
1526
1527        let footer = Self::take_footer(r)?;
1528
1529        let consensus = Consensus {
1530            header,
1531            voters,
1532            relays,
1533            footer,
1534        };
1535
1536        // Find the signatures.
1537        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
1538        let mut signatures = Vec::new();
1539        for item in &mut *r {
1540            let item = item?;
1541            if item.kwd() != DIRECTORY_SIGNATURE {
1542                return Err(EK::UnexpectedToken
1543                    .with_msg(item.kwd().to_str())
1544                    .at_pos(item.pos()));
1545            }
1546
1547            let sig = Signature::from_item(&item)?;
1548            if first_sig.is_none() {
1549                first_sig = Some(item);
1550            }
1551            signatures.push(sig);
1552        }
1553
1554        let end_pos = match first_sig {
1555            None => return Err(EK::MissingToken.with_msg("directory-signature")),
1556            // Unwrap should be safe because `first_sig` was parsed from `r`
1557            #[allow(clippy::unwrap_used)]
1558            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
1559        };
1560
1561        // Find the appropriate digest.
1562        let signed_str = &r.str()[start_pos..end_pos];
1563        let remainder = &r.str()[end_pos..];
1564        let (sha256, sha1) = match RS::flavor() {
1565            ConsensusFlavor::Ns => (
1566                None,
1567                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
1568            ),
1569            ConsensusFlavor::Microdesc => (
1570                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
1571                None,
1572            ),
1573        };
1574        let siggroup = SignatureGroup {
1575            sha256,
1576            sha1,
1577            signatures,
1578        };
1579
1580        let unval = UnvalidatedConsensus {
1581            consensus,
1582            siggroup,
1583            n_authorities: None,
1584        };
1585        let lifetime = unval.consensus.header.hdr.lifetime.clone();
1586        let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
1587        let dist_interval = time::Duration::from_secs(delay.1.into());
1588        let starting_time = lifetime.valid_after - dist_interval;
1589        let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
1590        Ok((signed_str, remainder, timebound))
1591    }
1592}
1593
1594/// A Microdesc consensus whose signatures have not yet been checked.
1595///
1596/// To validate this object, call set_n_authorities() on it, then call
1597/// check_signature() on that result with the set of certs that you
1598/// have.  Make sure only to provide authority certificates representing
1599/// real authorities!
1600#[cfg_attr(
1601    feature = "dangerous-expose-struct-fields",
1602    visible::StructFields(pub),
1603    non_exhaustive
1604)]
1605#[derive(Debug, Clone)]
1606pub struct UnvalidatedConsensus<RS> {
1607    /// The consensus object. We don't want to expose this until it's
1608    /// validated.
1609    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1610    consensus: Consensus<RS>,
1611    /// The signatures that need to be validated before we can call
1612    /// this consensus valid.
1613    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1614    siggroup: SignatureGroup,
1615    /// The total number of authorities that we believe in.  We need
1616    /// this information in order to validate the signatures, since it
1617    /// determines how many signatures we need to find valid in `siggroup`.
1618    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1619    n_authorities: Option<u16>,
1620}
1621
1622impl<RS> UnvalidatedConsensus<RS> {
1623    /// Tell the unvalidated consensus how many authorities we believe in.
1624    ///
1625    /// Without knowing this number, we can't validate the signature.
1626    #[must_use]
1627    pub fn set_n_authorities(self, n_authorities: u16) -> Self {
1628        UnvalidatedConsensus {
1629            n_authorities: Some(n_authorities),
1630            ..self
1631        }
1632    }
1633
1634    /// Return an iterator of all the certificate IDs that we might use
1635    /// to validate this consensus.
1636    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
1637        match self.key_is_correct(&[]) {
1638            Ok(()) => Vec::new(),
1639            Err(missing) => missing,
1640        }
1641        .into_iter()
1642    }
1643
1644    /// Return the lifetime of this unvalidated consensus
1645    pub fn peek_lifetime(&self) -> &Lifetime {
1646        self.consensus.lifetime()
1647    }
1648
1649    /// Return true if a client who believes in exactly the provided
1650    /// set of authority IDs might might consider this consensus to be
1651    /// well-signed.
1652    ///
1653    /// (This is the case if the consensus claims to be signed by more than
1654    /// half of the authorities in the list.)
1655    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
1656        self.siggroup.could_validate(authorities)
1657    }
1658
1659    /// Return the number of relays in this unvalidated consensus.
1660    ///
1661    /// This function is unstable. It is only enabled if the crate was
1662    /// built with the `experimental-api` feature.
1663    #[cfg(feature = "experimental-api")]
1664    pub fn n_relays(&self) -> usize {
1665        self.consensus.relays.len()
1666    }
1667
1668    /// Modify the list of relays in this unvalidated consensus.
1669    ///
1670    /// A use case for this is long-lasting custom directories. To ensure Arti can still quickly
1671    /// build circuits when the directory gets old, a tiny churn file can be regularly obtained,
1672    /// listing no longer available Tor nodes, which can then be removed from the consensus.
1673    ///
1674    /// This function is unstable. It is only enabled if the crate was
1675    /// built with the `experimental-api` feature.
1676    #[cfg(feature = "experimental-api")]
1677    pub fn modify_relays<F>(&mut self, func: F)
1678    where
1679        F: FnOnce(&mut Vec<RS>),
1680    {
1681        func(&mut self.consensus.relays);
1682    }
1683}
1684
1685impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
1686    type Key = [AuthCert];
1687    type KeyHint = Vec<AuthCertKeyIds>;
1688    type Error = Error;
1689
1690    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
1691        let (n_ok, missing) = self.siggroup.list_missing(k);
1692        match self.n_authorities {
1693            Some(n) if n_ok > (n / 2) as usize => Ok(()),
1694            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
1695        }
1696    }
1697    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
1698        match self.n_authorities {
1699            None => Err(Error::from(internal!(
1700                "Didn't set authorities on consensus"
1701            ))),
1702            Some(authority) => {
1703                if self.siggroup.validate(authority, k) {
1704                    Ok(())
1705                } else {
1706                    Err(EK::BadSignature.err())
1707                }
1708            }
1709        }
1710    }
1711    fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
1712        self.consensus
1713    }
1714}
1715
1716impl SignatureGroup {
1717    // TODO: these functions are pretty similar and could probably stand to be
1718    // refactored a lot.
1719
1720    /// Helper: Return a pair of the number of possible authorities'
1721    /// signatures in this object for which we _could_ find certs, and
1722    /// a list of the signatures we couldn't find certificates for.
1723    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1724        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1725        let mut missing = Vec::new();
1726        for sig in &self.signatures {
1727            let id_fingerprint = &sig.key_ids.id_fingerprint;
1728            if ok.contains(id_fingerprint) {
1729                continue;
1730            }
1731            if sig.find_cert(certs).is_some() {
1732                ok.insert(*id_fingerprint);
1733                continue;
1734            }
1735
1736            missing.push(sig);
1737        }
1738        (ok.len(), missing)
1739    }
1740
1741    /// Given a list of authority identity key fingerprints, return true if
1742    /// this signature group is _potentially_ well-signed according to those
1743    /// authorities.
1744    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1745        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1746        for sig in &self.signatures {
1747            let id_fp = &sig.key_ids.id_fingerprint;
1748            if signed_by.contains(id_fp) {
1749                // Already found this in the list.
1750                continue;
1751            }
1752            if authorities.contains(&id_fp) {
1753                signed_by.insert(*id_fp);
1754            }
1755        }
1756
1757        signed_by.len() > (authorities.len() / 2)
1758    }
1759
1760    /// Return true if the signature group defines a valid signature.
1761    ///
1762    /// A signature is valid if it signed by more than half of the
1763    /// authorities.  This API requires that `n_authorities` is the number of
1764    /// authorities we believe in, and that every cert in `certs` belongs
1765    /// to a real authority.
1766    fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1767        // A set of the authorities (by identity) who have have signed
1768        // this document.  We use a set here in case `certs` has more
1769        // than one certificate for a single authority.
1770        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1771
1772        for sig in &self.signatures {
1773            let id_fingerprint = &sig.key_ids.id_fingerprint;
1774            if ok.contains(id_fingerprint) {
1775                // We already checked at least one signature using this
1776                // authority's identity fingerprint.
1777                continue;
1778            }
1779
1780            let d: Option<&[u8]> = match sig.digestname.as_ref() {
1781                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1782                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1783                _ => None, // We don't know how to find this digest.
1784            };
1785            if d.is_none() {
1786                // We don't support this kind of digest for this kind
1787                // of document.
1788                continue;
1789            }
1790
1791            // Unwrap should be safe because of above `d.is_none()` check
1792            #[allow(clippy::unwrap_used)]
1793            match sig.check_signature(d.as_ref().unwrap(), certs) {
1794                SigCheckResult::Valid => {
1795                    ok.insert(*id_fingerprint);
1796                }
1797                _ => continue,
1798            }
1799        }
1800
1801        ok.len() > (n_authorities / 2) as usize
1802    }
1803}
1804
1805#[cfg(test)]
1806mod test {
1807    // @@ begin test lint list maintained by maint/add_warning @@
1808    #![allow(clippy::bool_assert_comparison)]
1809    #![allow(clippy::clone_on_copy)]
1810    #![allow(clippy::dbg_macro)]
1811    #![allow(clippy::mixed_attributes_style)]
1812    #![allow(clippy::print_stderr)]
1813    #![allow(clippy::print_stdout)]
1814    #![allow(clippy::single_char_pattern)]
1815    #![allow(clippy::unwrap_used)]
1816    #![allow(clippy::unchecked_duration_subtraction)]
1817    #![allow(clippy::useless_vec)]
1818    #![allow(clippy::needless_pass_by_value)]
1819    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1820    use super::*;
1821    use hex_literal::hex;
1822
1823    const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1824    const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1825
1826    #[cfg(feature = "ns_consensus")]
1827    const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
1828    #[cfg(feature = "ns_consensus")]
1829    const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
1830
1831    fn read_bad(fname: &str) -> String {
1832        use std::fs;
1833        use std::path::PathBuf;
1834        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1835        path.push("testdata");
1836        path.push("bad-mdconsensus");
1837        path.push(fname);
1838
1839        fs::read_to_string(path).unwrap()
1840    }
1841
1842    #[test]
1843    fn parse_and_validate_md() -> Result<()> {
1844        use std::net::SocketAddr;
1845        use tor_checkable::{SelfSigned, Timebound};
1846        let mut certs = Vec::new();
1847        for cert in AuthCert::parse_multiple(CERTS) {
1848            let cert = cert?.check_signature()?.dangerously_assume_timely();
1849            certs.push(cert);
1850        }
1851        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1852
1853        assert_eq!(certs.len(), 3);
1854
1855        let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1856        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1857
1858        // The set of authorities we know _could_ validate this cert.
1859        assert!(consensus.authorities_are_correct(&auth_ids));
1860        // A subset would also work.
1861        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1862        {
1863            // If we only believe in an authority that isn't listed,
1864            // that won't work.
1865            let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1866            assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1867        }
1868
1869        let missing = consensus.key_is_correct(&[]).err().unwrap();
1870        assert_eq!(3, missing.len());
1871        assert!(consensus.key_is_correct(&certs).is_ok());
1872        let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1873        assert_eq!(2, missing.len());
1874
1875        // here is a trick that had better not work.
1876        let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1877        let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1878
1879        assert_eq!(2, missing.len());
1880        assert!(consensus.is_well_signed(&same_three_times).is_err());
1881
1882        assert!(consensus.key_is_correct(&certs).is_ok());
1883        let consensus = consensus.check_signature(&certs)?;
1884
1885        assert_eq!(6, consensus.relays().len());
1886        let r0 = &consensus.relays()[0];
1887        assert_eq!(
1888            r0.md_digest(),
1889            &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1890        );
1891        assert_eq!(
1892            r0.rsa_identity().as_bytes(),
1893            &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1894        );
1895        assert!(!r0.weight().is_measured());
1896        assert!(!r0.weight().is_nonzero());
1897        let pv = &r0.protovers();
1898        assert!(pv.supports_subver("HSDir", 2));
1899        assert!(!pv.supports_subver("HSDir", 3));
1900        let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1901        let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1902        assert!(r0.orport_addrs().any(|a| a == &ip4));
1903        assert!(r0.orport_addrs().any(|a| a == &ip6));
1904
1905        Ok(())
1906    }
1907
1908    #[test]
1909    #[cfg(feature = "ns_consensus")]
1910    fn parse_and_validate_ns() -> Result<()> {
1911        use tor_checkable::{SelfSigned, Timebound};
1912        let mut certs = Vec::new();
1913        for cert in AuthCert::parse_multiple(NS_CERTS) {
1914            let cert = cert?.check_signature()?.dangerously_assume_timely();
1915            certs.push(cert);
1916        }
1917        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1918        assert_eq!(certs.len(), 3);
1919
1920        let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
1921        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1922        // The set of authorities we know _could_ validate this cert.
1923        assert!(consensus.authorities_are_correct(&auth_ids));
1924        // A subset would also work.
1925        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1926
1927        assert!(consensus.key_is_correct(&certs).is_ok());
1928
1929        let _consensus = consensus.check_signature(&certs)?;
1930
1931        Ok(())
1932    }
1933
1934    #[test]
1935    fn test_bad() {
1936        use crate::Pos;
1937        fn check(fname: &str, e: &Error) {
1938            let content = read_bad(fname);
1939            let res = MdConsensus::parse(&content);
1940            assert!(res.is_err());
1941            assert_eq!(&res.err().unwrap(), e);
1942        }
1943
1944        check(
1945            "bad-flags",
1946            &EK::BadArgument
1947                .at_pos(Pos::from_line(27, 1))
1948                .with_msg("Flags out of order"),
1949        );
1950        check(
1951            "bad-md-digest",
1952            &EK::BadArgument
1953                .at_pos(Pos::from_line(40, 3))
1954                .with_msg("Invalid base64"),
1955        );
1956        check(
1957            "bad-weight",
1958            &EK::BadArgument
1959                .at_pos(Pos::from_line(67, 141))
1960                .with_msg("invalid digit found in string"),
1961        );
1962        check(
1963            "bad-weights",
1964            &EK::BadArgument
1965                .at_pos(Pos::from_line(51, 13))
1966                .with_msg("invalid digit found in string"),
1967        );
1968        check(
1969            "wrong-order",
1970            &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1971        );
1972        check(
1973            "wrong-start",
1974            &EK::UnexpectedToken
1975                .with_msg("vote-status")
1976                .at_pos(Pos::from_line(1, 1)),
1977        );
1978        check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1979    }
1980
1981    fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1982        let mut reader = NetDocReader::new(s);
1983        let tok = reader.next().unwrap();
1984        assert!(reader.next().is_none());
1985        tok
1986    }
1987
1988    #[test]
1989    fn test_weight() {
1990        let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1991        let w = RelayWeight::from_item(&w).unwrap();
1992        assert!(!w.is_measured());
1993        assert!(w.is_nonzero());
1994
1995        let w = gettok("w Bandwidth=10\n").unwrap();
1996        let w = RelayWeight::from_item(&w).unwrap();
1997        assert!(w.is_measured());
1998        assert!(w.is_nonzero());
1999
2000        let w = RelayWeight::default();
2001        assert!(!w.is_measured());
2002        assert!(!w.is_nonzero());
2003
2004        let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2005        let w = RelayWeight::from_item(&w).unwrap();
2006        assert!(!w.is_measured());
2007        assert!(!w.is_nonzero());
2008
2009        let w = gettok("r foo\n").unwrap();
2010        let w = RelayWeight::from_item(&w);
2011        assert!(w.is_err());
2012
2013        let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2014        let w = RelayWeight::from_item(&w);
2015        assert!(w.is_err());
2016
2017        let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2018        let w = RelayWeight::from_item(&w);
2019        assert!(w.is_err());
2020    }
2021
2022    #[test]
2023    fn test_netparam() {
2024        let p = "Hello=600 Goodbye=5 Fred=7"
2025            .parse::<NetParams<u32>>()
2026            .unwrap();
2027        assert_eq!(p.get("Hello"), Some(&600_u32));
2028
2029        let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2030        assert!(p.is_err());
2031
2032        let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2033        assert!(p.is_err());
2034    }
2035
2036    #[test]
2037    fn test_sharedrand() {
2038        let sr =
2039            gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2040                .unwrap();
2041        let sr = SharedRandStatus::from_item(&sr).unwrap();
2042
2043        assert_eq!(sr.n_reveals, 9);
2044        assert_eq!(
2045            sr.value.0,
2046            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2047        );
2048        assert!(sr.timestamp.is_none());
2049
2050        let sr2 = gettok(
2051            "shared-rand-current-value 9 \
2052                    5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2053        )
2054        .unwrap();
2055        let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2056        assert_eq!(sr2.n_reveals, sr.n_reveals);
2057        assert_eq!(sr2.value.0, sr.value.0);
2058        assert_eq!(
2059            sr2.timestamp.unwrap(),
2060            humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2061        );
2062
2063        let sr = gettok("foo bar\n").unwrap();
2064        let sr = SharedRandStatus::from_item(&sr);
2065        assert!(sr.is_err());
2066    }
2067}