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