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 std::sync::LazyLock;
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_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::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: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::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: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::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_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
938    LazyLock::new(|| {
939        use NetstatusKwd::*;
940        let mut rules = SectionRules::builder();
941        rules.add(RS_A.rule().may_repeat().args(1..));
942        rules.add(RS_S.rule().required());
943        rules.add(RS_V.rule());
944        rules.add(RS_PR.rule().required());
945        rules.add(RS_W.rule());
946        rules.add(RS_P.rule().args(2..));
947        rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
948        rules
949    });
950
951/// Rules for parsing a single routerstatus in an NS consensus
952static NS_ROUTERSTATUS_RULES_NSCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
953    use NetstatusKwd::*;
954    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
955    rules.add(RS_R.rule().required().args(8..));
956    rules.build()
957});
958
959/*
960/// Rules for parsing a single routerstatus in a vote
961static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
962    use NetstatusKwd::*;
963        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
964        rules.add(RS_R.rule().required().args(8..));
965        rules.add(RS_M.rule().may_repeat().args(2..));
966        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
967        rules
968    };
969*/
970/// Rules for parsing a single routerstatus in a microdesc consensus
971static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
972    use NetstatusKwd::*;
973    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
974    rules.add(RS_R.rule().required().args(6..));
975    rules.add(RS_M.rule().required().args(1..));
976    rules.build()
977});
978/// Rules for parsing consensus fields from a footer.
979static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
980    use NetstatusKwd::*;
981    let mut rules = SectionRules::builder();
982    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
983    // consensus only
984    rules.add(BANDWIDTH_WEIGHTS.rule());
985    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
986    rules.build()
987});
988
989impl ProtoStatus {
990    /// Construct a ProtoStatus from two chosen keywords in a section.
991    fn from_section(
992        sec: &Section<'_, NetstatusKwd>,
993        recommend_token: NetstatusKwd,
994        required_token: NetstatusKwd,
995    ) -> Result<ProtoStatus> {
996        /// Helper: extract a Protocols entry from an item's arguments.
997        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
998            if let Some(item) = t {
999                item.args_as_str()
1000                    .parse::<Protocols>()
1001                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
1002            } else {
1003                Ok(Protocols::new())
1004            }
1005        }
1006
1007        let recommended = parse(sec.get(recommend_token))?;
1008        let required = parse(sec.get(required_token))?;
1009        Ok(ProtoStatus {
1010            recommended,
1011            required,
1012        })
1013    }
1014
1015    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
1016    ///
1017    /// Implementations may assume that relays on the network implement all the
1018    /// protocols in the relays' required-protocols list.  Implementations should
1019    /// refuse to start if they do not implement all the protocols on their own
1020    /// (client or relay) required-protocols list.
1021    pub fn required_protocols(&self) -> &Protocols {
1022        &self.required
1023    }
1024
1025    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
1026    ///
1027    /// Implementations should warn if they do not implement all the protocols
1028    /// on their own (client or relay) recommended-protocols list.
1029    pub fn recommended_protocols(&self) -> &Protocols {
1030        &self.recommended
1031    }
1032}
1033
1034impl<T> std::str::FromStr for NetParams<T>
1035where
1036    T: std::str::FromStr,
1037    T::Err: std::error::Error,
1038{
1039    type Err = Error;
1040    fn from_str(s: &str) -> Result<Self> {
1041        /// Helper: parse a single K=V pair.
1042        fn parse_pair<U>(p: &str) -> Result<(String, U)>
1043        where
1044            U: std::str::FromStr,
1045            U::Err: std::error::Error,
1046        {
1047            let parts: Vec<_> = p.splitn(2, '=').collect();
1048            if parts.len() != 2 {
1049                return Err(EK::BadArgument
1050                    .at_pos(Pos::at(p))
1051                    .with_msg("Missing = in key=value list"));
1052            }
1053            let num = parts[1].parse::<U>().map_err(|e| {
1054                EK::BadArgument
1055                    .at_pos(Pos::at(parts[1]))
1056                    .with_msg(e.to_string())
1057            })?;
1058            Ok((parts[0].to_string(), num))
1059        }
1060
1061        let params = s
1062            .split(' ')
1063            .filter(|p| !p.is_empty())
1064            .map(parse_pair)
1065            .collect::<Result<HashMap<_, _>>>()?;
1066        Ok(NetParams { params })
1067    }
1068}
1069
1070impl CommonHeader {
1071    /// Extract the CommonHeader members from a single header section.
1072    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<CommonHeader> {
1073        use NetstatusKwd::*;
1074
1075        {
1076            // this unwrap is safe because if there is not at least one
1077            // token in the section, the section is unparsable.
1078            #[allow(clippy::unwrap_used)]
1079            let first = sec.first_item().unwrap();
1080            if first.kwd() != NETWORK_STATUS_VERSION {
1081                return Err(EK::UnexpectedToken
1082                    .with_msg(first.kwd().to_str())
1083                    .at_pos(first.pos()));
1084            }
1085        }
1086
1087        let ver_item = sec.required(NETWORK_STATUS_VERSION)?;
1088
1089        let version: u32 = ver_item.parse_arg(0)?;
1090        if version != 3 {
1091            return Err(EK::BadDocumentVersion.with_msg(version.to_string()));
1092        }
1093        let flavor = ConsensusFlavor::from_opt_name(ver_item.arg(1))?;
1094
1095        let valid_after = sec
1096            .required(VALID_AFTER)?
1097            .args_as_str()
1098            .parse::<Iso8601TimeSp>()?
1099            .into();
1100        let fresh_until = sec
1101            .required(FRESH_UNTIL)?
1102            .args_as_str()
1103            .parse::<Iso8601TimeSp>()?
1104            .into();
1105        let valid_until = sec
1106            .required(VALID_UNTIL)?
1107            .args_as_str()
1108            .parse::<Iso8601TimeSp>()?
1109            .into();
1110        let lifetime = Lifetime::new(valid_after, fresh_until, valid_until)?;
1111
1112        let client_versions = sec
1113            .maybe(CLIENT_VERSIONS)
1114            .args_as_str()
1115            .unwrap_or("")
1116            .split(',')
1117            .map(str::to_string)
1118            .collect();
1119        let relay_versions = sec
1120            .maybe(SERVER_VERSIONS)
1121            .args_as_str()
1122            .unwrap_or("")
1123            .split(',')
1124            .map(str::to_string)
1125            .collect();
1126
1127        let proto_statuses = {
1128            let client = ProtoStatus::from_section(
1129                sec,
1130                RECOMMENDED_CLIENT_PROTOCOLS,
1131                REQUIRED_CLIENT_PROTOCOLS,
1132            )?;
1133            let relay = ProtoStatus::from_section(
1134                sec,
1135                RECOMMENDED_RELAY_PROTOCOLS,
1136                REQUIRED_RELAY_PROTOCOLS,
1137            )?;
1138            Arc::new(ProtoStatuses { client, relay })
1139        };
1140
1141        let params = sec.maybe(PARAMS).args_as_str().unwrap_or("").parse()?;
1142
1143        let voting_delay = if let Some(tok) = sec.get(VOTING_DELAY) {
1144            let n1 = tok.parse_arg(0)?;
1145            let n2 = tok.parse_arg(1)?;
1146            Some((n1, n2))
1147        } else {
1148            None
1149        };
1150
1151        Ok(CommonHeader {
1152            flavor,
1153            lifetime,
1154            client_versions,
1155            relay_versions,
1156            proto_statuses,
1157            params,
1158            voting_delay,
1159        })
1160    }
1161}
1162
1163impl SharedRandStatus {
1164    /// Parse a current or previous shared rand value from a given
1165    /// SharedRandPreviousValue or SharedRandCurrentValue.
1166    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1167        match item.kwd() {
1168            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
1169            _ => {
1170                return Err(Error::from(internal!(
1171                    "wrong keyword {:?} on shared-random value",
1172                    item.kwd()
1173                ))
1174                .at_pos(item.pos()))
1175            }
1176        }
1177        let n_reveals: u8 = item.parse_arg(0)?;
1178        let val: B64 = item.parse_arg(1)?;
1179        let value = SharedRandVal(val.into_array()?);
1180        // Added in proposal 342
1181        let timestamp = item
1182            .parse_optional_arg::<Iso8601TimeNoSp>(2)?
1183            .map(Into::into);
1184        Ok(SharedRandStatus {
1185            n_reveals,
1186            value,
1187            timestamp,
1188        })
1189    }
1190
1191    /// Return the actual shared random value.
1192    pub fn value(&self) -> &SharedRandVal {
1193        &self.value
1194    }
1195
1196    /// Return the timestamp (if any) associated with this `SharedRandValue`.
1197    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1198        self.timestamp
1199    }
1200}
1201
1202impl ConsensusHeader {
1203    /// Parse the ConsensusHeader members from a provided section.
1204    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusHeader> {
1205        use NetstatusKwd::*;
1206
1207        let status: &str = sec.required(VOTE_STATUS)?.arg(0).unwrap_or("");
1208        if status != "consensus" {
1209            return Err(EK::BadDocumentType.err());
1210        }
1211
1212        // We're ignoring KNOWN_FLAGS in the consensus.
1213
1214        let hdr = CommonHeader::from_section(sec)?;
1215
1216        let consensus_method: u32 = sec.required(CONSENSUS_METHOD)?.parse_arg(0)?;
1217
1218        let shared_rand_prev = sec
1219            .get(SHARED_RAND_PREVIOUS_VALUE)
1220            .map(SharedRandStatus::from_item)
1221            .transpose()?;
1222
1223        let shared_rand_cur = sec
1224            .get(SHARED_RAND_CURRENT_VALUE)
1225            .map(SharedRandStatus::from_item)
1226            .transpose()?;
1227
1228        Ok(ConsensusHeader {
1229            hdr,
1230            consensus_method,
1231            shared_rand_prev,
1232            shared_rand_cur,
1233        })
1234    }
1235}
1236
1237impl DirSource {
1238    /// Parse a "dir-source" item
1239    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1240        if item.kwd() != NetstatusKwd::DIR_SOURCE {
1241            return Err(
1242                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
1243                    .at_pos(item.pos()),
1244            );
1245        }
1246        let nickname = item.required_arg(0)?.to_string();
1247        let identity = item.parse_arg::<Fingerprint>(1)?.into();
1248        let ip = item.parse_arg(3)?;
1249        let dir_port = item.parse_arg(4)?;
1250        let or_port = item.parse_arg(5)?;
1251
1252        Ok(DirSource {
1253            nickname,
1254            identity,
1255            ip,
1256            dir_port,
1257            or_port,
1258        })
1259    }
1260}
1261
1262impl ConsensusVoterInfo {
1263    /// Parse a single ConsensusVoterInfo from a voter info section.
1264    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
1265        use NetstatusKwd::*;
1266        // this unwrap should be safe because if there is not at least one
1267        // token in the section, the section is unparsable.
1268        #[allow(clippy::unwrap_used)]
1269        let first = sec.first_item().unwrap();
1270        if first.kwd() != DIR_SOURCE {
1271            return Err(Error::from(internal!(
1272                "Wrong keyword {:?} at start of voter info",
1273                first.kwd()
1274            ))
1275            .at_pos(first.pos()));
1276        }
1277        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1278
1279        let contact = sec.required(CONTACT)?.args_as_str().to_string();
1280
1281        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1282
1283        Ok(ConsensusVoterInfo {
1284            dir_source,
1285            contact,
1286            vote_digest,
1287        })
1288    }
1289}
1290
1291impl std::str::FromStr for RelayFlags {
1292    type Err = void::Void;
1293    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
1294        Ok(match s {
1295            "Authority" => RelayFlags::AUTHORITY,
1296            "BadExit" => RelayFlags::BAD_EXIT,
1297            "Exit" => RelayFlags::EXIT,
1298            "Fast" => RelayFlags::FAST,
1299            "Guard" => RelayFlags::GUARD,
1300            "HSDir" => RelayFlags::HSDIR,
1301            "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
1302            "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
1303            "Stable" => RelayFlags::STABLE,
1304            "StaleDesc" => RelayFlags::STALE_DESC,
1305            "Running" => RelayFlags::RUNNING,
1306            "Valid" => RelayFlags::VALID,
1307            "V2Dir" => RelayFlags::V2DIR,
1308            _ => RelayFlags::empty(),
1309        })
1310    }
1311}
1312
1313impl RelayFlags {
1314    /// Parse a relay-flags entry from an "s" line.
1315    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
1316        if item.kwd() != NetstatusKwd::RS_S {
1317            return Err(
1318                Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
1319                    .at_pos(item.pos()),
1320            );
1321        }
1322        // These flags are implicit.
1323        let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
1324
1325        let mut prev: Option<&str> = None;
1326        for s in item.args() {
1327            if let Some(p) = prev {
1328                if p >= s {
1329                    // Arguments out of order.
1330                    return Err(EK::BadArgument
1331                        .at_pos(item.pos())
1332                        .with_msg("Flags out of order"));
1333                }
1334            }
1335            let fl = s.parse().void_unwrap();
1336            flags |= fl;
1337            prev = Some(s);
1338        }
1339
1340        Ok(flags)
1341    }
1342}
1343
1344impl Default for RelayWeight {
1345    fn default() -> RelayWeight {
1346        RelayWeight::Unmeasured(0)
1347    }
1348}
1349
1350impl RelayWeight {
1351    /// Parse a routerweight from a "w" line.
1352    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1353        if item.kwd() != NetstatusKwd::RS_W {
1354            return Err(
1355                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1356                    .at_pos(item.pos()),
1357            );
1358        }
1359
1360        let params: NetParams<u32> = item.args_as_str().parse()?;
1361
1362        let bw = params.params.get("Bandwidth");
1363        let unmeas = params.params.get("Unmeasured");
1364
1365        let bw = match bw {
1366            None => return Ok(RelayWeight::Unmeasured(0)),
1367            Some(b) => *b,
1368        };
1369
1370        match unmeas {
1371            None | Some(0) => Ok(RelayWeight::Measured(bw)),
1372            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1373            _ => Err(EK::BadArgument
1374                .at_pos(item.pos())
1375                .with_msg("unmeasured value")),
1376        }
1377    }
1378}
1379
1380impl Footer {
1381    /// Parse a directory footer from a footer section.
1382    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1383        use NetstatusKwd::*;
1384        sec.required(DIRECTORY_FOOTER)?;
1385
1386        let weights = sec
1387            .maybe(BANDWIDTH_WEIGHTS)
1388            .args_as_str()
1389            .unwrap_or("")
1390            .parse()?;
1391
1392        Ok(Footer { weights })
1393    }
1394}
1395
1396/// Result of checking a single authority signature.
1397enum SigCheckResult {
1398    /// The signature checks out.  Great!
1399    Valid,
1400    /// The signature is invalid; no additional information could make it
1401    /// valid.
1402    Invalid,
1403    /// We can't check the signature because we don't have a
1404    /// certificate with the right signing key.
1405    MissingCert,
1406}
1407
1408impl Signature {
1409    /// Parse a Signature from a directory-signature section
1410    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1411        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1412            return Err(Error::from(internal!(
1413                "Wrong keyword {:?} for directory signature",
1414                item.kwd()
1415            ))
1416            .at_pos(item.pos()));
1417        }
1418
1419        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1420            (
1421                item.required_arg(0)?,
1422                item.required_arg(1)?,
1423                item.required_arg(2)?,
1424            )
1425        } else {
1426            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1427        };
1428
1429        let digestname = alg.to_string();
1430        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1431        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1432        let key_ids = AuthCertKeyIds {
1433            id_fingerprint,
1434            sk_fingerprint,
1435        };
1436        let signature = item.obj("SIGNATURE")?;
1437
1438        Ok(Signature {
1439            digestname,
1440            key_ids,
1441            signature,
1442        })
1443    }
1444
1445    /// Return true if this signature has the identity key and signing key
1446    /// that match a given cert.
1447    fn matches_cert(&self, cert: &AuthCert) -> bool {
1448        cert.key_ids() == &self.key_ids
1449    }
1450
1451    /// If possible, find the right certificate for checking this signature
1452    /// from among a slice of certificates.
1453    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1454        certs.iter().find(|&c| self.matches_cert(c))
1455    }
1456
1457    /// Try to check whether this signature is a valid signature of a
1458    /// provided digest, given a slice of certificates that might contain
1459    /// its signing key.
1460    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1461        match self.find_cert(certs) {
1462            None => SigCheckResult::MissingCert,
1463            Some(cert) => {
1464                let key = cert.signing_key();
1465                match key.verify(signed_digest, &self.signature[..]) {
1466                    Ok(()) => SigCheckResult::Valid,
1467                    Err(_) => SigCheckResult::Invalid,
1468                }
1469            }
1470        }
1471    }
1472}
1473
1474/// A Consensus object that has been parsed, but not checked for
1475/// signatures and timeliness.
1476pub type UncheckedConsensus<RS> = TimerangeBound<UnvalidatedConsensus<RS>>;
1477
1478impl<RS: RouterStatus + ParseRouterStatus> Consensus<RS> {
1479    /// Return a new ConsensusBuilder for building test consensus objects.
1480    ///
1481    /// This function is only available when the `build_docs` feature has
1482    /// been enabled.
1483    #[cfg(feature = "build_docs")]
1484    pub fn builder() -> ConsensusBuilder<RS> {
1485        ConsensusBuilder::new(RS::flavor())
1486    }
1487
1488    /// Try to parse a single networkstatus document from a string.
1489    pub fn parse(s: &str) -> Result<(&str, &str, UncheckedConsensus<RS>)> {
1490        let mut reader = NetDocReader::new(s)?;
1491        Self::parse_from_reader(&mut reader).map_err(|e| e.within(s))
1492    }
1493    /// Extract a voter-info section from the reader; return
1494    /// Ok(None) when we are out of voter-info sections.
1495    fn take_voterinfo(
1496        r: &mut NetDocReader<'_, NetstatusKwd>,
1497    ) -> Result<Option<ConsensusVoterInfo>> {
1498        use NetstatusKwd::*;
1499
1500        match r.peek() {
1501            None => return Ok(None),
1502            Some(e) if e.is_ok_with_kwd_in(&[RS_R, DIRECTORY_FOOTER]) => return Ok(None),
1503            _ => (),
1504        };
1505
1506        let mut first_dir_source = true;
1507        // TODO: Extract this pattern into a "pause at second"???
1508        // Pause at the first 'r', or the second 'dir-source'.
1509        let mut p = r.pause_at(|i| match i {
1510            Err(_) => false,
1511            Ok(item) => {
1512                item.kwd() == RS_R
1513                    || if item.kwd() == DIR_SOURCE {
1514                        let was_first = first_dir_source;
1515                        first_dir_source = false;
1516                        !was_first
1517                    } else {
1518                        false
1519                    }
1520            }
1521        });
1522
1523        let voter_sec = NS_VOTERINFO_RULES_CONSENSUS.parse(&mut p)?;
1524        let voter = ConsensusVoterInfo::from_section(&voter_sec)?;
1525
1526        Ok(Some(voter))
1527    }
1528
1529    /// Extract the footer (but not signatures) from the reader.
1530    fn take_footer(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Footer> {
1531        use NetstatusKwd::*;
1532        let mut p = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIRECTORY_SIGNATURE]));
1533        let footer_sec = NS_FOOTER_RULES.parse(&mut p)?;
1534        let footer = Footer::from_section(&footer_sec)?;
1535        Ok(footer)
1536    }
1537
1538    /// Extract a routerstatus from the reader.  Return Ok(None) if we're
1539    /// out of routerstatus entries.
1540    fn take_routerstatus(r: &mut NetDocReader<'_, NetstatusKwd>) -> Result<Option<(Pos, RS)>> {
1541        use NetstatusKwd::*;
1542        match r.peek() {
1543            None => return Ok(None),
1544            Some(e) if e.is_ok_with_kwd_in(&[DIRECTORY_FOOTER]) => return Ok(None),
1545            _ => (),
1546        };
1547
1548        let pos = r.pos();
1549
1550        let mut first_r = true;
1551        let mut p = r.pause_at(|i| match i {
1552            Err(_) => false,
1553            Ok(item) => {
1554                item.kwd() == DIRECTORY_FOOTER
1555                    || if item.kwd() == RS_R {
1556                        let was_first = first_r;
1557                        first_r = false;
1558                        !was_first
1559                    } else {
1560                        false
1561                    }
1562            }
1563        });
1564
1565        let rules = match RS::flavor() {
1566            ConsensusFlavor::Microdesc => &NS_ROUTERSTATUS_RULES_MDCON,
1567            ConsensusFlavor::Ns => &NS_ROUTERSTATUS_RULES_NSCON,
1568        };
1569
1570        let rs_sec = rules.parse(&mut p)?;
1571        let rs = RS::from_section(&rs_sec)?;
1572        Ok(Some((pos, rs)))
1573    }
1574
1575    /// Extract an entire UncheckedConsensus from a reader.
1576    ///
1577    /// Returns the signed portion of the string, the remainder of the
1578    /// string, and an UncheckedConsensus.
1579    fn parse_from_reader<'a>(
1580        r: &mut NetDocReader<'a, NetstatusKwd>,
1581    ) -> Result<(&'a str, &'a str, UncheckedConsensus<RS>)> {
1582        use NetstatusKwd::*;
1583        let (header, start_pos) = {
1584            let mut h = r.pause_at(|i| i.is_ok_with_kwd_in(&[DIR_SOURCE]));
1585            let header_sec = NS_HEADER_RULES_CONSENSUS.parse(&mut h)?;
1586            // Unwrapping should be safe because above `.parse` would have
1587            // returned an Error
1588            #[allow(clippy::unwrap_used)]
1589            let pos = header_sec.first_item().unwrap().offset_in(r.str()).unwrap();
1590            (ConsensusHeader::from_section(&header_sec)?, pos)
1591        };
1592        if RS::flavor() != header.hdr.flavor {
1593            return Err(EK::BadDocumentType.with_msg(format!(
1594                "Expected {:?}, got {:?}",
1595                RS::flavor(),
1596                header.hdr.flavor
1597            )));
1598        }
1599
1600        let mut voters = Vec::new();
1601
1602        while let Some(voter) = Self::take_voterinfo(r)? {
1603            voters.push(voter);
1604        }
1605
1606        let mut relays: Vec<RS> = Vec::new();
1607        while let Some((pos, routerstatus)) = Self::take_routerstatus(r)? {
1608            if let Some(prev) = relays.last() {
1609                if prev.rsa_identity() >= routerstatus.rsa_identity() {
1610                    return Err(EK::WrongSortOrder.at_pos(pos));
1611                }
1612            }
1613            relays.push(routerstatus);
1614        }
1615        relays.shrink_to_fit();
1616
1617        let footer = Self::take_footer(r)?;
1618
1619        let consensus = Consensus {
1620            header,
1621            voters,
1622            relays,
1623            footer,
1624        };
1625
1626        // Find the signatures.
1627        let mut first_sig: Option<Item<'_, NetstatusKwd>> = None;
1628        let mut signatures = Vec::new();
1629        for item in &mut *r {
1630            let item = item?;
1631            if item.kwd() != DIRECTORY_SIGNATURE {
1632                return Err(EK::UnexpectedToken
1633                    .with_msg(item.kwd().to_str())
1634                    .at_pos(item.pos()));
1635            }
1636
1637            let sig = Signature::from_item(&item)?;
1638            if first_sig.is_none() {
1639                first_sig = Some(item);
1640            }
1641            signatures.push(sig);
1642        }
1643
1644        let end_pos = match first_sig {
1645            None => return Err(EK::MissingToken.with_msg("directory-signature")),
1646            // Unwrap should be safe because `first_sig` was parsed from `r`
1647            #[allow(clippy::unwrap_used)]
1648            Some(sig) => sig.offset_in(r.str()).unwrap() + "directory-signature ".len(),
1649        };
1650
1651        // Find the appropriate digest.
1652        let signed_str = &r.str()[start_pos..end_pos];
1653        let remainder = &r.str()[end_pos..];
1654        let (sha256, sha1) = match RS::flavor() {
1655            ConsensusFlavor::Ns => (
1656                None,
1657                Some(ll::d::Sha1::digest(signed_str.as_bytes()).into()),
1658            ),
1659            ConsensusFlavor::Microdesc => (
1660                Some(ll::d::Sha256::digest(signed_str.as_bytes()).into()),
1661                None,
1662            ),
1663        };
1664        let siggroup = SignatureGroup {
1665            sha256,
1666            sha1,
1667            signatures,
1668        };
1669
1670        let unval = UnvalidatedConsensus {
1671            consensus,
1672            siggroup,
1673            n_authorities: None,
1674        };
1675        let lifetime = unval.consensus.header.hdr.lifetime.clone();
1676        let delay = unval.consensus.header.hdr.voting_delay.unwrap_or((0, 0));
1677        let dist_interval = time::Duration::from_secs(delay.1.into());
1678        let starting_time = lifetime.valid_after - dist_interval;
1679        let timebound = TimerangeBound::new(unval, starting_time..lifetime.valid_until);
1680        Ok((signed_str, remainder, timebound))
1681    }
1682}
1683
1684/// A Microdesc consensus whose signatures have not yet been checked.
1685///
1686/// To validate this object, call set_n_authorities() on it, then call
1687/// check_signature() on that result with the set of certs that you
1688/// have.  Make sure only to provide authority certificates representing
1689/// real authorities!
1690#[cfg_attr(
1691    feature = "dangerous-expose-struct-fields",
1692    visible::StructFields(pub),
1693    non_exhaustive
1694)]
1695#[derive(Debug, Clone)]
1696pub struct UnvalidatedConsensus<RS> {
1697    /// The consensus object. We don't want to expose this until it's
1698    /// validated.
1699    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1700    consensus: Consensus<RS>,
1701    /// The signatures that need to be validated before we can call
1702    /// this consensus valid.
1703    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1704    siggroup: SignatureGroup,
1705    /// The total number of authorities that we believe in.  We need
1706    /// this information in order to validate the signatures, since it
1707    /// determines how many signatures we need to find valid in `siggroup`.
1708    #[cfg_attr(docsrs, doc(cfg(feature = "dangerous-expose-struct-fields")))]
1709    n_authorities: Option<u16>,
1710}
1711
1712impl<RS> UnvalidatedConsensus<RS> {
1713    /// Tell the unvalidated consensus how many authorities we believe in.
1714    ///
1715    /// Without knowing this number, we can't validate the signature.
1716    #[must_use]
1717    pub fn set_n_authorities(self, n_authorities: u16) -> Self {
1718        UnvalidatedConsensus {
1719            n_authorities: Some(n_authorities),
1720            ..self
1721        }
1722    }
1723
1724    /// Return an iterator of all the certificate IDs that we might use
1725    /// to validate this consensus.
1726    pub fn signing_cert_ids(&self) -> impl Iterator<Item = AuthCertKeyIds> {
1727        match self.key_is_correct(&[]) {
1728            Ok(()) => Vec::new(),
1729            Err(missing) => missing,
1730        }
1731        .into_iter()
1732    }
1733
1734    /// Return the lifetime of this unvalidated consensus
1735    pub fn peek_lifetime(&self) -> &Lifetime {
1736        self.consensus.lifetime()
1737    }
1738
1739    /// Return true if a client who believes in exactly the provided
1740    /// set of authority IDs might might consider this consensus to be
1741    /// well-signed.
1742    ///
1743    /// (This is the case if the consensus claims to be signed by more than
1744    /// half of the authorities in the list.)
1745    pub fn authorities_are_correct(&self, authorities: &[&RsaIdentity]) -> bool {
1746        self.siggroup.could_validate(authorities)
1747    }
1748
1749    /// Return the number of relays in this unvalidated consensus.
1750    ///
1751    /// This function is unstable. It is only enabled if the crate was
1752    /// built with the `experimental-api` feature.
1753    #[cfg(feature = "experimental-api")]
1754    pub fn n_relays(&self) -> usize {
1755        self.consensus.relays.len()
1756    }
1757
1758    /// Modify the list of relays in this unvalidated consensus.
1759    ///
1760    /// A use case for this is long-lasting custom directories. To ensure Arti can still quickly
1761    /// build circuits when the directory gets old, a tiny churn file can be regularly obtained,
1762    /// listing no longer available Tor nodes, which can then be removed from the consensus.
1763    ///
1764    /// This function is unstable. It is only enabled if the crate was
1765    /// built with the `experimental-api` feature.
1766    #[cfg(feature = "experimental-api")]
1767    pub fn modify_relays<F>(&mut self, func: F)
1768    where
1769        F: FnOnce(&mut Vec<RS>),
1770    {
1771        func(&mut self.consensus.relays);
1772    }
1773}
1774
1775impl<RS> ExternallySigned<Consensus<RS>> for UnvalidatedConsensus<RS> {
1776    type Key = [AuthCert];
1777    type KeyHint = Vec<AuthCertKeyIds>;
1778    type Error = Error;
1779
1780    fn key_is_correct(&self, k: &Self::Key) -> result::Result<(), Self::KeyHint> {
1781        let (n_ok, missing) = self.siggroup.list_missing(k);
1782        match self.n_authorities {
1783            Some(n) if n_ok > (n / 2) as usize => Ok(()),
1784            _ => Err(missing.iter().map(|cert| cert.key_ids).collect()),
1785        }
1786    }
1787    fn is_well_signed(&self, k: &Self::Key) -> result::Result<(), Self::Error> {
1788        match self.n_authorities {
1789            None => Err(Error::from(internal!(
1790                "Didn't set authorities on consensus"
1791            ))),
1792            Some(authority) => {
1793                if self.siggroup.validate(authority, k) {
1794                    Ok(())
1795                } else {
1796                    Err(EK::BadSignature.err())
1797                }
1798            }
1799        }
1800    }
1801    fn dangerously_assume_wellsigned(self) -> Consensus<RS> {
1802        self.consensus
1803    }
1804}
1805
1806impl SignatureGroup {
1807    // TODO: these functions are pretty similar and could probably stand to be
1808    // refactored a lot.
1809
1810    /// Helper: Return a pair of the number of possible authorities'
1811    /// signatures in this object for which we _could_ find certs, and
1812    /// a list of the signatures we couldn't find certificates for.
1813    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1814        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1815        let mut missing = Vec::new();
1816        for sig in &self.signatures {
1817            let id_fingerprint = &sig.key_ids.id_fingerprint;
1818            if ok.contains(id_fingerprint) {
1819                continue;
1820            }
1821            if sig.find_cert(certs).is_some() {
1822                ok.insert(*id_fingerprint);
1823                continue;
1824            }
1825
1826            missing.push(sig);
1827        }
1828        (ok.len(), missing)
1829    }
1830
1831    /// Given a list of authority identity key fingerprints, return true if
1832    /// this signature group is _potentially_ well-signed according to those
1833    /// authorities.
1834    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1835        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1836        for sig in &self.signatures {
1837            let id_fp = &sig.key_ids.id_fingerprint;
1838            if signed_by.contains(id_fp) {
1839                // Already found this in the list.
1840                continue;
1841            }
1842            if authorities.contains(&id_fp) {
1843                signed_by.insert(*id_fp);
1844            }
1845        }
1846
1847        signed_by.len() > (authorities.len() / 2)
1848    }
1849
1850    /// Return true if the signature group defines a valid signature.
1851    ///
1852    /// A signature is valid if it signed by more than half of the
1853    /// authorities.  This API requires that `n_authorities` is the number of
1854    /// authorities we believe in, and that every cert in `certs` belongs
1855    /// to a real authority.
1856    fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1857        // A set of the authorities (by identity) who have have signed
1858        // this document.  We use a set here in case `certs` has more
1859        // than one certificate for a single authority.
1860        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1861
1862        for sig in &self.signatures {
1863            let id_fingerprint = &sig.key_ids.id_fingerprint;
1864            if ok.contains(id_fingerprint) {
1865                // We already checked at least one signature using this
1866                // authority's identity fingerprint.
1867                continue;
1868            }
1869
1870            let d: Option<&[u8]> = match sig.digestname.as_ref() {
1871                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1872                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1873                _ => None, // We don't know how to find this digest.
1874            };
1875            if d.is_none() {
1876                // We don't support this kind of digest for this kind
1877                // of document.
1878                continue;
1879            }
1880
1881            // Unwrap should be safe because of above `d.is_none()` check
1882            #[allow(clippy::unwrap_used)]
1883            match sig.check_signature(d.as_ref().unwrap(), certs) {
1884                SigCheckResult::Valid => {
1885                    ok.insert(*id_fingerprint);
1886                }
1887                _ => continue,
1888            }
1889        }
1890
1891        ok.len() > (n_authorities / 2) as usize
1892    }
1893}
1894
1895#[cfg(test)]
1896mod test {
1897    // @@ begin test lint list maintained by maint/add_warning @@
1898    #![allow(clippy::bool_assert_comparison)]
1899    #![allow(clippy::clone_on_copy)]
1900    #![allow(clippy::dbg_macro)]
1901    #![allow(clippy::mixed_attributes_style)]
1902    #![allow(clippy::print_stderr)]
1903    #![allow(clippy::print_stdout)]
1904    #![allow(clippy::single_char_pattern)]
1905    #![allow(clippy::unwrap_used)]
1906    #![allow(clippy::unchecked_duration_subtraction)]
1907    #![allow(clippy::useless_vec)]
1908    #![allow(clippy::needless_pass_by_value)]
1909    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1910    use super::*;
1911    use hex_literal::hex;
1912
1913    const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1914    const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1915
1916    #[cfg(feature = "ns_consensus")]
1917    const NS_CERTS: &str = include_str!("../../testdata/authcerts3.txt");
1918    #[cfg(feature = "ns_consensus")]
1919    const NS_CONSENSUS: &str = include_str!("../../testdata/nsconsensus1.txt");
1920
1921    fn read_bad(fname: &str) -> String {
1922        use std::fs;
1923        use std::path::PathBuf;
1924        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1925        path.push("testdata");
1926        path.push("bad-mdconsensus");
1927        path.push(fname);
1928
1929        fs::read_to_string(path).unwrap()
1930    }
1931
1932    #[test]
1933    fn parse_and_validate_md() -> Result<()> {
1934        use std::net::SocketAddr;
1935        use tor_checkable::{SelfSigned, Timebound};
1936        let mut certs = Vec::new();
1937        for cert in AuthCert::parse_multiple(CERTS)? {
1938            let cert = cert?.check_signature()?.dangerously_assume_timely();
1939            certs.push(cert);
1940        }
1941        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1942
1943        assert_eq!(certs.len(), 3);
1944
1945        let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1946        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1947
1948        // The set of authorities we know _could_ validate this cert.
1949        assert!(consensus.authorities_are_correct(&auth_ids));
1950        // A subset would also work.
1951        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1952        {
1953            // If we only believe in an authority that isn't listed,
1954            // that won't work.
1955            let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1956            assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1957        }
1958
1959        let missing = consensus.key_is_correct(&[]).err().unwrap();
1960        assert_eq!(3, missing.len());
1961        assert!(consensus.key_is_correct(&certs).is_ok());
1962        let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1963        assert_eq!(2, missing.len());
1964
1965        // here is a trick that had better not work.
1966        let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1967        let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1968
1969        assert_eq!(2, missing.len());
1970        assert!(consensus.is_well_signed(&same_three_times).is_err());
1971
1972        assert!(consensus.key_is_correct(&certs).is_ok());
1973        let consensus = consensus.check_signature(&certs)?;
1974
1975        assert_eq!(6, consensus.relays().len());
1976        let r0 = &consensus.relays()[0];
1977        assert_eq!(
1978            r0.md_digest(),
1979            &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1980        );
1981        assert_eq!(
1982            r0.rsa_identity().as_bytes(),
1983            &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1984        );
1985        assert!(!r0.weight().is_measured());
1986        assert!(!r0.weight().is_nonzero());
1987        let pv = &r0.protovers();
1988        assert!(pv.supports_subver("HSDir", 2));
1989        assert!(!pv.supports_subver("HSDir", 3));
1990        let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1991        let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1992        assert!(r0.orport_addrs().any(|a| a == &ip4));
1993        assert!(r0.orport_addrs().any(|a| a == &ip6));
1994
1995        Ok(())
1996    }
1997
1998    #[test]
1999    #[cfg(feature = "ns_consensus")]
2000    fn parse_and_validate_ns() -> Result<()> {
2001        use tor_checkable::{SelfSigned, Timebound};
2002        let mut certs = Vec::new();
2003        for cert in AuthCert::parse_multiple(NS_CERTS)? {
2004            let cert = cert?.check_signature()?.dangerously_assume_timely();
2005            certs.push(cert);
2006        }
2007        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
2008        assert_eq!(certs.len(), 3);
2009
2010        let (_, _, consensus) = NsConsensus::parse(NS_CONSENSUS)?;
2011        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
2012        // The set of authorities we know _could_ validate this cert.
2013        assert!(consensus.authorities_are_correct(&auth_ids));
2014        // A subset would also work.
2015        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
2016
2017        assert!(consensus.key_is_correct(&certs).is_ok());
2018
2019        let _consensus = consensus.check_signature(&certs)?;
2020
2021        Ok(())
2022    }
2023
2024    #[test]
2025    fn test_bad() {
2026        use crate::Pos;
2027        fn check(fname: &str, e: &Error) {
2028            let content = read_bad(fname);
2029            let res = MdConsensus::parse(&content);
2030            assert!(res.is_err());
2031            assert_eq!(&res.err().unwrap(), e);
2032        }
2033
2034        check(
2035            "bad-flags",
2036            &EK::BadArgument
2037                .at_pos(Pos::from_line(27, 1))
2038                .with_msg("Flags out of order"),
2039        );
2040        check(
2041            "bad-md-digest",
2042            &EK::BadArgument
2043                .at_pos(Pos::from_line(40, 3))
2044                .with_msg("Invalid base64"),
2045        );
2046        check(
2047            "bad-weight",
2048            &EK::BadArgument
2049                .at_pos(Pos::from_line(67, 141))
2050                .with_msg("invalid digit found in string"),
2051        );
2052        check(
2053            "bad-weights",
2054            &EK::BadArgument
2055                .at_pos(Pos::from_line(51, 13))
2056                .with_msg("invalid digit found in string"),
2057        );
2058        check(
2059            "wrong-order",
2060            &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
2061        );
2062        check(
2063            "wrong-start",
2064            &EK::UnexpectedToken
2065                .with_msg("vote-status")
2066                .at_pos(Pos::from_line(1, 1)),
2067        );
2068        check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
2069    }
2070
2071    fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
2072        let mut reader = NetDocReader::new(s)?;
2073        let tok = reader.next().unwrap();
2074        assert!(reader.next().is_none());
2075        tok
2076    }
2077
2078    #[test]
2079    fn test_weight() {
2080        let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
2081        let w = RelayWeight::from_item(&w).unwrap();
2082        assert!(!w.is_measured());
2083        assert!(w.is_nonzero());
2084
2085        let w = gettok("w Bandwidth=10\n").unwrap();
2086        let w = RelayWeight::from_item(&w).unwrap();
2087        assert!(w.is_measured());
2088        assert!(w.is_nonzero());
2089
2090        let w = RelayWeight::default();
2091        assert!(!w.is_measured());
2092        assert!(!w.is_nonzero());
2093
2094        let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
2095        let w = RelayWeight::from_item(&w).unwrap();
2096        assert!(!w.is_measured());
2097        assert!(!w.is_nonzero());
2098
2099        let w = gettok("r foo\n").unwrap();
2100        let w = RelayWeight::from_item(&w);
2101        assert!(w.is_err());
2102
2103        let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
2104        let w = RelayWeight::from_item(&w);
2105        assert!(w.is_err());
2106
2107        let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
2108        let w = RelayWeight::from_item(&w);
2109        assert!(w.is_err());
2110    }
2111
2112    #[test]
2113    fn test_netparam() {
2114        let p = "Hello=600 Goodbye=5 Fred=7"
2115            .parse::<NetParams<u32>>()
2116            .unwrap();
2117        assert_eq!(p.get("Hello"), Some(&600_u32));
2118
2119        let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
2120        assert!(p.is_err());
2121
2122        let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
2123        assert!(p.is_err());
2124    }
2125
2126    #[test]
2127    fn test_sharedrand() {
2128        let sr =
2129            gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
2130                .unwrap();
2131        let sr = SharedRandStatus::from_item(&sr).unwrap();
2132
2133        assert_eq!(sr.n_reveals, 9);
2134        assert_eq!(
2135            sr.value.0,
2136            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
2137        );
2138        assert!(sr.timestamp.is_none());
2139
2140        let sr2 = gettok(
2141            "shared-rand-current-value 9 \
2142                    5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
2143        )
2144        .unwrap();
2145        let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
2146        assert_eq!(sr2.n_reveals, sr.n_reveals);
2147        assert_eq!(sr2.value.0, sr.value.0);
2148        assert_eq!(
2149            sr2.timestamp.unwrap(),
2150            humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
2151        );
2152
2153        let sr = gettok("foo bar\n").unwrap();
2154        let sr = SharedRandStatus::from_item(&sr);
2155        assert!(sr.is_err());
2156    }
2157
2158    #[test]
2159    fn test_protostatus() {
2160        let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
2161
2162        let outcome = ProtoStatus {
2163            recommended: "Link=7".parse().unwrap(),
2164            required: "Desc=5".parse().unwrap(),
2165        }
2166        .check_protocols(&my_protocols);
2167        assert!(outcome.is_ok());
2168
2169        let outcome = ProtoStatus {
2170            recommended: "Microdesc=4 Link=7".parse().unwrap(),
2171            required: "Desc=5".parse().unwrap(),
2172        }
2173        .check_protocols(&my_protocols);
2174        assert_eq!(
2175            outcome,
2176            Err(ProtocolSupportError::MissingRecommended(
2177                "Microdesc=4".parse().unwrap()
2178            ))
2179        );
2180
2181        let outcome = ProtoStatus {
2182            recommended: "Microdesc=4 Link=7".parse().unwrap(),
2183            required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
2184        }
2185        .check_protocols(&my_protocols);
2186        assert_eq!(
2187            outcome,
2188            Err(ProtocolSupportError::MissingRequired(
2189                "Cons=6-12 Wombat=15".parse().unwrap()
2190            ))
2191        );
2192    }
2193
2194    #[test]
2195    fn serialize_protostatus() {
2196        let ps = ProtoStatuses {
2197            client: ProtoStatus {
2198                recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
2199                required: "Link=5 LinkAuth=3".parse().unwrap(),
2200            },
2201            relay: ProtoStatus {
2202                recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
2203                required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
2204            },
2205        };
2206        let json = serde_json::to_string(&ps).unwrap();
2207        let ps2 = serde_json::from_str(json.as_str()).unwrap();
2208        assert_eq!(ps, ps2);
2209
2210        let ps3: ProtoStatuses = serde_json::from_str(
2211            r#"{
2212            "client":{
2213                "required":"Link=5 LinkAuth=3",
2214                "recommended":"Link=1-5 LinkAuth=2-5"
2215            },
2216            "relay":{
2217                "required":"Wombat=20-22 Knish=25-27",
2218                "recommended":"Wombat=20-30 Knish=20-30"
2219            }
2220        }"#,
2221        )
2222        .unwrap();
2223        assert_eq!(ps, ps3);
2224    }
2225}