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//! plain (unflavoured) consensus has references to router descriptors, and
14//! a "microdesc"-flavored consensus ("md") 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 plain 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
48pub mod md;
49#[cfg(feature = "plain-consensus")]
50pub mod plain;
51#[cfg(feature = "ns-vote")]
52pub mod vote;
53
54#[cfg(feature = "build_docs")]
55mod build;
56
57use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
58use crate::parse::keyword::Keyword;
59use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
60use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
61use crate::types::misc::*;
62use crate::util::PeekableIterator;
63use crate::{Error, NetdocErrorKind as EK, Pos, Result};
64use std::collections::{HashMap, HashSet};
65use std::result::Result as StdResult;
66use std::sync::Arc;
67use std::{net, result, time};
68use tor_error::{HasKind, internal};
69use tor_protover::Protocols;
70
71use bitflags::bitflags;
72use digest::Digest;
73use std::sync::LazyLock;
74use tor_checkable::{ExternallySigned, timed::TimerangeBound};
75use tor_llcrypto as ll;
76use tor_llcrypto::pk::rsa::RsaIdentity;
77
78use serde::{Deserialize, Deserializer};
79
80#[cfg(feature = "build_docs")]
81pub use build::MdConsensusBuilder;
82#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
83pub use build::PlainConsensusBuilder;
84#[cfg(feature = "build_docs")]
85ns_export_each_flavor! {
86    ty: RouterStatusBuilder;
87}
88
89ns_export_each_variety! {
90    ty: RouterStatus, Header;
91}
92
93use void::ResultVoidExt as _;
94
95#[deprecated]
96#[cfg(feature = "ns_consensus")]
97pub use PlainConsensus as NsConsensus;
98#[deprecated]
99#[cfg(feature = "ns_consensus")]
100pub use PlainRouterStatus as NsRouterStatus;
101#[deprecated]
102#[cfg(feature = "ns_consensus")]
103pub use UncheckedPlainConsensus as UncheckedNsConsensus;
104#[deprecated]
105#[cfg(feature = "ns_consensus")]
106pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
107
108/// The lifetime of a networkstatus document.
109///
110/// In a consensus, this type describes when the consensus may safely
111/// be used.  In a vote, this type describes the proposed lifetime for a
112/// consensus.
113#[derive(Clone, Debug)]
114pub struct Lifetime {
115    /// Time at which the document becomes valid
116    valid_after: time::SystemTime,
117    /// Time after which there is expected to be a better version
118    /// of this consensus
119    fresh_until: time::SystemTime,
120    /// Time after which this consensus is expired.
121    ///
122    /// (In practice, Tor clients will keep using documents for a while
123    /// after this expiration time, if no better one can be found.)
124    valid_until: time::SystemTime,
125}
126
127impl Lifetime {
128    /// Construct a new Lifetime.
129    pub fn new(
130        valid_after: time::SystemTime,
131        fresh_until: time::SystemTime,
132        valid_until: time::SystemTime,
133    ) -> Result<Self> {
134        if valid_after < fresh_until && fresh_until < valid_until {
135            Ok(Lifetime {
136                valid_after,
137                fresh_until,
138                valid_until,
139            })
140        } else {
141            Err(EK::InvalidLifetime.err())
142        }
143    }
144    /// Return time when this consensus first becomes valid.
145    ///
146    /// (You might see a consensus a little while before this time,
147    /// since voting tries to finish up before the.)
148    pub fn valid_after(&self) -> time::SystemTime {
149        self.valid_after
150    }
151    /// Return time when this consensus is no longer fresh.
152    ///
153    /// You can use the consensus after this time, but there is (or is
154    /// supposed to be) a better one by this point.
155    pub fn fresh_until(&self) -> time::SystemTime {
156        self.fresh_until
157    }
158    /// Return the time when this consensus is no longer valid.
159    ///
160    /// You should try to get a better consensus after this time,
161    /// though it's okay to keep using this one if no more recent one
162    /// can be found.
163    pub fn valid_until(&self) -> time::SystemTime {
164        self.valid_until
165    }
166    /// Return true if this consensus is officially valid at the provided time.
167    pub fn valid_at(&self, when: time::SystemTime) -> bool {
168        self.valid_after <= when && when <= self.valid_until
169    }
170
171    /// Return the voting period implied by this lifetime.
172    ///
173    /// (The "voting period" is the amount of time in between when a consensus first
174    /// becomes valid, and when the next consensus is expected to become valid)
175    pub fn voting_period(&self) -> time::Duration {
176        let valid_after = self.valid_after();
177        let fresh_until = self.fresh_until();
178        fresh_until
179            .duration_since(valid_after)
180            .expect("Mis-formed lifetime")
181    }
182}
183
184/// A set of named network parameters.
185///
186/// These are used to describe current settings for the Tor network,
187/// current weighting parameters for path selection, and so on.  They're
188/// encoded with a space-separated K=V format.
189///
190/// A `NetParams<i32>` is part of the validated directory manager configuration,
191/// where it is built (in the builder-pattern sense) from a transparent HashMap.
192#[derive(Debug, Clone, Default, Eq, PartialEq)]
193pub struct NetParams<T> {
194    /// Map from keys to values.
195    params: HashMap<String, T>,
196}
197
198impl<T> NetParams<T> {
199    /// Create a new empty list of NetParams.
200    #[allow(unused)]
201    pub fn new() -> Self {
202        NetParams {
203            params: HashMap::new(),
204        }
205    }
206    /// Retrieve a given network parameter, if it is present.
207    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
208        self.params.get(v.as_ref())
209    }
210    /// Return an iterator over all key value pairs in an arbitrary order.
211    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
212        self.params.iter()
213    }
214    /// Set or replace the value of a network parameter.
215    pub fn set(&mut self, k: String, v: T) {
216        self.params.insert(k, v);
217    }
218}
219
220impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
221    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
222        NetParams {
223            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
224        }
225    }
226}
227
228impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
229    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
230        self.params.extend(iter);
231    }
232}
233
234impl<'de, T> Deserialize<'de> for NetParams<T>
235where
236    T: Deserialize<'de>,
237{
238    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
239    where
240        D: Deserializer<'de>,
241    {
242        let params = HashMap::deserialize(deserializer)?;
243        Ok(NetParams { params })
244    }
245}
246
247/// A list of subprotocol versions that implementors should/must provide.
248///
249/// Each consensus has two of these: one for relays, and one for clients.
250#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
251pub struct ProtoStatus {
252    /// Set of protocols that are recommended; if we're missing a protocol
253    /// in this list we should warn the user.
254    recommended: Protocols,
255    /// Set of protocols that are required; if we're missing a protocol
256    /// in this list we should refuse to start.
257    required: Protocols,
258}
259
260impl ProtoStatus {
261    /// Check whether the list of supported protocols
262    /// is sufficient to satisfy this list of recommendations and requirements.
263    ///
264    /// If any required protocol is missing, returns [`ProtocolSupportError::MissingRequired`].
265    ///
266    /// Otherwise, if no required protocol is missing, but some recommended protocol is missing,
267    /// returns [`ProtocolSupportError::MissingRecommended`].
268    ///
269    /// Otherwise, if no recommended or required protocol is missing, returns `Ok(())`.
270    pub fn check_protocols(
271        &self,
272        supported_protocols: &Protocols,
273    ) -> StdResult<(), ProtocolSupportError> {
274        // Required protocols take precedence, so we check them first.
275        let missing_required = self.required.difference(supported_protocols);
276        if !missing_required.is_empty() {
277            return Err(ProtocolSupportError::MissingRequired(missing_required));
278        }
279        let missing_recommended = self.recommended.difference(supported_protocols);
280        if !missing_recommended.is_empty() {
281            return Err(ProtocolSupportError::MissingRecommended(
282                missing_recommended,
283            ));
284        }
285
286        Ok(())
287    }
288}
289
290/// A subprotocol that is recommended or required in the consensus was not present.
291#[derive(Clone, Debug, thiserror::Error)]
292#[cfg_attr(test, derive(PartialEq))]
293#[non_exhaustive]
294pub enum ProtocolSupportError {
295    /// At least one required protocol was not in our list of supported protocols.
296    #[error("Required protocols are not implemented: {0}")]
297    MissingRequired(Protocols),
298
299    /// At least one recommended protocol was not in our list of supported protocols.
300    ///
301    /// Also implies that no _required_ protocols were missing.
302    #[error("Recommended protocols are not implemented: {0}")]
303    MissingRecommended(Protocols),
304}
305
306impl ProtocolSupportError {
307    /// Return true if the suggested behavior for this error is a shutdown.
308    pub fn should_shutdown(&self) -> bool {
309        matches!(self, Self::MissingRequired(_))
310    }
311}
312
313impl HasKind for ProtocolSupportError {
314    fn kind(&self) -> tor_error::ErrorKind {
315        tor_error::ErrorKind::SoftwareDeprecated
316    }
317}
318
319/// A set of recommended and required protocols when running
320/// in various scenarios.
321#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
322pub struct ProtoStatuses {
323    /// Lists of recommended and required subprotocol versions for clients
324    client: ProtoStatus,
325    /// Lists of recommended and required subprotocol versions for relays
326    relay: ProtoStatus,
327}
328
329impl ProtoStatuses {
330    /// Return the list of recommended and required protocols for running as a client.
331    pub fn client(&self) -> &ProtoStatus {
332        &self.client
333    }
334
335    /// Return the list of recommended and required protocols for running as a relay.
336    pub fn relay(&self) -> &ProtoStatus {
337        &self.relay
338    }
339}
340
341/// A recognized 'flavor' of consensus document.
342#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
343#[non_exhaustive]
344pub enum ConsensusFlavor {
345    /// A "microdesc"-flavored consensus.  This is the one that
346    /// clients and relays use today.
347    Microdesc,
348    /// A "networkstatus"-flavored consensus.  It's used for
349    /// historical and network-health purposes.  Instead of listing
350    /// microdescriptor digests, it lists digests of full relay
351    /// descriptors.
352    Plain,
353}
354
355impl ConsensusFlavor {
356    /// Return the name of this consensus flavor.
357    pub fn name(&self) -> &'static str {
358        match self {
359            ConsensusFlavor::Plain => "ns", // spec bug, now baked in
360            ConsensusFlavor::Microdesc => "microdesc",
361        }
362    }
363    /// Try to find the flavor whose name is `name`.
364    ///
365    /// For historical reasons, an unnamed flavor indicates an "Plain"
366    /// document.
367    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
368        match name {
369            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
370            Some("ns") | None => Ok(ConsensusFlavor::Plain),
371            Some(other) => {
372                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
373            }
374        }
375    }
376}
377
378/// The signature of a single directory authority on a networkstatus document.
379#[derive(Debug, Clone)]
380#[non_exhaustive]
381pub struct Signature {
382    /// The name of the digest algorithm used to make the signature.
383    ///
384    /// Currently sha1 and sh256 are recognized.  Here we only support
385    /// sha256.
386    pub digestname: String,
387    /// Fingerprints of the keys for the authority that made
388    /// this signature.
389    pub key_ids: AuthCertKeyIds,
390    /// The signature itself.
391    pub signature: Vec<u8>,
392}
393
394/// A collection of signatures that can be checked on a networkstatus document
395#[derive(Debug, Clone)]
396#[non_exhaustive]
397pub struct SignatureGroup {
398    /// The sha256 of the document itself
399    pub sha256: Option<[u8; 32]>,
400    /// The sha1 of the document itself
401    pub sha1: Option<[u8; 20]>,
402    /// The signatures listed on the document.
403    pub signatures: Vec<Signature>,
404}
405
406/// A shared random value produced by the directory authorities.
407#[derive(
408    Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
409)]
410// (This doesn't need to use CtByteArray; we don't really need to compare these.)
411pub struct SharedRandVal([u8; 32]);
412
413/// A shared-random value produced by the directory authorities,
414/// along with meta-information about that value.
415#[derive(Debug, Clone)]
416#[non_exhaustive]
417pub struct SharedRandStatus {
418    /// How many authorities revealed shares that contributed to this value.
419    pub 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    pub value: SharedRandVal,
427
428    /// The time when this SharedRandVal becomes (or became) the latest.
429    ///
430    /// (This is added per proposal 342, assuming that gets accepted.)
431    pub timestamp: Option<time::SystemTime>,
432}
433
434/// Description of an authority's identity and address.
435///
436/// (Corresponds to a dir-source line.)
437#[derive(Debug, Clone)]
438#[non_exhaustive]
439pub struct DirSource {
440    /// human-readable nickname for this authority.
441    pub nickname: String,
442    /// Fingerprint for the _authority_ identity key of this
443    /// authority.
444    ///
445    /// This is the same key as the one that signs the authority's
446    /// certificates.
447    pub identity: RsaIdentity,
448    /// IP address for the authority
449    pub ip: net::IpAddr,
450    /// HTTP directory port for this authority
451    pub dir_port: u16,
452    /// OR port for this authority.
453    pub or_port: u16,
454}
455
456bitflags! {
457    /// A set of recognized directory flags on a single relay.
458    ///
459    /// These flags come from a consensus directory document, and are
460    /// used to describe what the authorities believe about the relay.
461    /// If the document contained any flags that we _didn't_ recognize,
462    /// they are not listed in this type.
463    ///
464    /// The bit values used to represent the flags have no meaning;
465    /// they may change between releases of this crate.  Relying on their
466    /// values may void your semver guarantees.
467    #[derive(Clone, Copy, Debug)]
468    pub struct RelayFlags: u16 {
469        /// Is this a directory authority?
470        const AUTHORITY = (1<<0);
471        /// Is this relay marked as a bad exit?
472        ///
473        /// Bad exits can be used as intermediate relays, but not to
474        /// deliver traffic.
475        const BAD_EXIT = (1<<1);
476        /// Is this relay marked as an exit for weighting purposes?
477        const EXIT = (1<<2);
478        /// Is this relay considered "fast" above a certain threshold?
479        const FAST = (1<<3);
480        /// Is this relay suitable for use as a guard relay?
481        ///
482        /// Clients choose their their initial relays from among the set
483        /// of Guard relays.
484        const GUARD = (1<<4);
485        /// Does this relay participate on the onion service directory
486        /// ring?
487        const HSDIR = (1<<5);
488        /// Set if this relay is considered "middle only", not suitable to run
489        /// as an exit or guard relay.
490        ///
491        /// Note that this flag is only used by authorities as part of
492        /// the voting process; clients do not and should not act
493        /// based on whether it is set.
494        const MIDDLE_ONLY = (1<<6);
495        /// If set, there is no consensus for the ed25519 key for this relay.
496        const NO_ED_CONSENSUS = (1<<7);
497        /// Is this relay considered "stable" enough for long-lived circuits?
498        const STABLE = (1<<8);
499        /// Set if the authorities are requesting a fresh descriptor for
500        /// this relay.
501        const STALE_DESC = (1<<9);
502        /// Set if this relay is currently running.
503        ///
504        /// This flag can appear in votes, but in consensuses, every relay
505        /// is assumed to be running.
506        const RUNNING = (1<<10);
507        /// Set if this relay is considered "valid" -- allowed to be on
508        /// the network.
509        ///
510        /// This flag can appear in votes, but in consensuses, every relay
511        /// is assumed to be valid.
512        const VALID = (1<<11);
513        /// Set if this relay supports a currently recognized version of the
514        /// directory protocol.
515        const V2DIR = (1<<12);
516    }
517}
518
519/// Recognized weight fields on a single relay in a consensus
520#[non_exhaustive]
521#[derive(Debug, Clone, Copy)]
522pub enum RelayWeight {
523    /// An unmeasured weight for a relay.
524    Unmeasured(u32),
525    /// An measured weight for a relay.
526    Measured(u32),
527}
528
529impl RelayWeight {
530    /// Return true if this weight is the result of a successful measurement
531    pub fn is_measured(&self) -> bool {
532        matches!(self, RelayWeight::Measured(_))
533    }
534    /// Return true if this weight is nonzero
535    pub fn is_nonzero(&self) -> bool {
536        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
537    }
538}
539
540/// All information about a single authority, as represented in a consensus
541#[derive(Debug, Clone)]
542#[non_exhaustive]
543pub struct ConsensusVoterInfo {
544    /// Contents of the dirsource line about an authority
545    pub dir_source: DirSource,
546    /// Human-readable contact information about the authority
547    pub contact: String,
548    /// Digest of the vote that the authority cast to contribute to
549    /// this consensus.
550    pub vote_digest: Vec<u8>,
551}
552
553/// The signed footer of a consensus netstatus.
554#[derive(Debug, Clone)]
555#[non_exhaustive]
556pub struct Footer {
557    /// Weights to be applied to certain classes of relays when choosing
558    /// for different roles.
559    ///
560    /// For example, we want to avoid choosing exits for non-exit
561    /// roles when overall the proportion of exits is small.
562    pub weights: NetParams<i32>,
563}
564
565/// A consensus document that lists relays along with their
566/// microdescriptor documents.
567pub type MdConsensus = md::Consensus;
568
569/// An MdConsensus that has been parsed and checked for timeliness,
570/// but not for signatures.
571pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
572
573/// An MdConsensus that has been parsed but not checked for signatures
574/// and timeliness.
575pub type UncheckedMdConsensus = md::UncheckedConsensus;
576
577#[cfg(feature = "plain-consensus")]
578/// A consensus document that lists relays along with their
579/// router descriptor documents.
580pub type PlainConsensus = plain::Consensus;
581
582#[cfg(feature = "plain-consensus")]
583/// An PlainConsensus that has been parsed and checked for timeliness,
584/// but not for signatures.
585pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
586
587#[cfg(feature = "plain-consensus")]
588/// An PlainConsensus that has been parsed but not checked for signatures
589/// and timeliness.
590pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
591
592decl_keyword! {
593    /// Keywords that can be used in votes and consensuses.
594    // TODO: This is public because otherwise we can't use it in the
595    // ParseRouterStatus crate.  But I'd rather find a way to make it
596    // private.
597    #[non_exhaustive]
598    #[allow(missing_docs)]
599    pub NetstatusKwd {
600        // Header
601        "network-status-version" => NETWORK_STATUS_VERSION,
602        "vote-status" => VOTE_STATUS,
603        "consensus-methods" => CONSENSUS_METHODS,
604        "consensus-method" => CONSENSUS_METHOD,
605        "published" => PUBLISHED,
606        "valid-after" => VALID_AFTER,
607        "fresh-until" => FRESH_UNTIL,
608        "valid-until" => VALID_UNTIL,
609        "voting-delay" => VOTING_DELAY,
610        "client-versions" => CLIENT_VERSIONS,
611        "server-versions" => SERVER_VERSIONS,
612        "known-flags" => KNOWN_FLAGS,
613        "flag-thresholds" => FLAG_THRESHOLDS,
614        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
615        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
616        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
617        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
618        "params" => PARAMS,
619        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
620        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
621        // "package" is now ignored.
622
623        // header in consensus, voter section in vote?
624        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
625        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
626
627        // Voter section (both)
628        "dir-source" => DIR_SOURCE,
629        "contact" => CONTACT,
630
631        // voter section (vote, but not consensus)
632        "legacy-dir-key" => LEGACY_DIR_KEY,
633        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
634        "shared-rand-commit" => SHARED_RAND_COMMIT,
635
636        // voter section (consensus, but not vote)
637        "vote-digest" => VOTE_DIGEST,
638
639        // voter cert beginning (but only the beginning)
640        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
641
642        // routerstatus
643        "r" => RS_R,
644        "a" => RS_A,
645        "s" => RS_S,
646        "v" => RS_V,
647        "pr" => RS_PR,
648        "w" => RS_W,
649        "p" => RS_P,
650        "m" => RS_M,
651        "id" => RS_ID,
652
653        // footer
654        "directory-footer" => DIRECTORY_FOOTER,
655        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
656        "directory-signature" => DIRECTORY_SIGNATURE,
657    }
658}
659
660/// Shared parts of rules for all kinds of netstatus headers
661static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
662    use NetstatusKwd::*;
663    let mut rules = SectionRules::builder();
664    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
665    rules.add(VOTE_STATUS.rule().required().args(1..));
666    rules.add(VALID_AFTER.rule().required());
667    rules.add(FRESH_UNTIL.rule().required());
668    rules.add(VALID_UNTIL.rule().required());
669    rules.add(VOTING_DELAY.rule().args(2..));
670    rules.add(CLIENT_VERSIONS.rule());
671    rules.add(SERVER_VERSIONS.rule());
672    rules.add(KNOWN_FLAGS.rule().required());
673    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
674    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
675    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
676    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
677    rules.add(PARAMS.rule());
678    rules
679});
680/// Rules for parsing the header of a consensus.
681static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
682    use NetstatusKwd::*;
683    let mut rules = NS_HEADER_RULES_COMMON_.clone();
684    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
685    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
686    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
687    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
688    rules.build()
689});
690/*
691/// Rules for parsing the header of a vote.
692static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
693    use NetstatusKwd::*;
694    let mut rules = NS_HEADER_RULES_COMMON_.clone();
695    rules.add(CONSENSUS_METHODS.rule().args(1..));
696    rules.add(FLAG_THRESHOLDS.rule());
697    rules.add(BANDWIDTH_FILE_HEADERS.rule());
698    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
699    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
700    rules
701};
702/// Rules for parsing a single voter's information in a vote.
703static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
704    use NetstatusKwd::*;
705    let mut rules = SectionRules::new();
706    rules.add(DIR_SOURCE.rule().required().args(6..));
707    rules.add(CONTACT.rule().required());
708    rules.add(LEGACY_DIR_KEY.rule().args(1..));
709    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
710    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
711    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
712    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
713    // then comes an entire cert: When we implement vote parsing,
714    // we should use the authcert code for handling that.
715    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
716    rules
717};
718 */
719/// Rules for parsing a single voter's information in a consensus
720static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
721    use NetstatusKwd::*;
722    let mut rules = SectionRules::builder();
723    rules.add(DIR_SOURCE.rule().required().args(6..));
724    rules.add(CONTACT.rule().required());
725    rules.add(VOTE_DIGEST.rule().required());
726    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
727    rules.build()
728});
729/// Shared rules for parsing a single routerstatus
730static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
731    LazyLock::new(|| {
732        use NetstatusKwd::*;
733        let mut rules = SectionRules::builder();
734        rules.add(RS_A.rule().may_repeat().args(1..));
735        rules.add(RS_S.rule().required());
736        rules.add(RS_V.rule());
737        rules.add(RS_PR.rule().required());
738        rules.add(RS_W.rule());
739        rules.add(RS_P.rule().args(2..));
740        rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
741        rules
742    });
743
744/// Rules for parsing a single routerstatus in an NS consensus
745static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
746    use NetstatusKwd::*;
747    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
748    rules.add(RS_R.rule().required().args(8..));
749    rules.build()
750});
751
752/*
753/// Rules for parsing a single routerstatus in a vote
754static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
755    use NetstatusKwd::*;
756        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
757        rules.add(RS_R.rule().required().args(8..));
758        rules.add(RS_M.rule().may_repeat().args(2..));
759        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
760        rules
761    };
762*/
763/// Rules for parsing a single routerstatus in a microdesc consensus
764static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
765    use NetstatusKwd::*;
766    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
767    rules.add(RS_R.rule().required().args(6..));
768    rules.add(RS_M.rule().required().args(1..));
769    rules.build()
770});
771/// Rules for parsing consensus fields from a footer.
772static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
773    use NetstatusKwd::*;
774    let mut rules = SectionRules::builder();
775    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
776    // consensus only
777    rules.add(BANDWIDTH_WEIGHTS.rule());
778    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
779    rules.build()
780});
781
782impl ProtoStatus {
783    /// Construct a ProtoStatus from two chosen keywords in a section.
784    fn from_section(
785        sec: &Section<'_, NetstatusKwd>,
786        recommend_token: NetstatusKwd,
787        required_token: NetstatusKwd,
788    ) -> Result<ProtoStatus> {
789        /// Helper: extract a Protocols entry from an item's arguments.
790        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
791            if let Some(item) = t {
792                item.args_as_str()
793                    .parse::<Protocols>()
794                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
795            } else {
796                Ok(Protocols::new())
797            }
798        }
799
800        let recommended = parse(sec.get(recommend_token))?;
801        let required = parse(sec.get(required_token))?;
802        Ok(ProtoStatus {
803            recommended,
804            required,
805        })
806    }
807
808    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
809    ///
810    /// Implementations may assume that relays on the network implement all the
811    /// protocols in the relays' required-protocols list.  Implementations should
812    /// refuse to start if they do not implement all the protocols on their own
813    /// (client or relay) required-protocols list.
814    pub fn required_protocols(&self) -> &Protocols {
815        &self.required
816    }
817
818    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
819    ///
820    /// Implementations should warn if they do not implement all the protocols
821    /// on their own (client or relay) recommended-protocols list.
822    pub fn recommended_protocols(&self) -> &Protocols {
823        &self.recommended
824    }
825}
826
827impl<T> std::str::FromStr for NetParams<T>
828where
829    T: std::str::FromStr,
830    T::Err: std::error::Error,
831{
832    type Err = Error;
833    fn from_str(s: &str) -> Result<Self> {
834        /// Helper: parse a single K=V pair.
835        fn parse_pair<U>(p: &str) -> Result<(String, U)>
836        where
837            U: std::str::FromStr,
838            U::Err: std::error::Error,
839        {
840            let parts: Vec<_> = p.splitn(2, '=').collect();
841            if parts.len() != 2 {
842                return Err(EK::BadArgument
843                    .at_pos(Pos::at(p))
844                    .with_msg("Missing = in key=value list"));
845            }
846            let num = parts[1].parse::<U>().map_err(|e| {
847                EK::BadArgument
848                    .at_pos(Pos::at(parts[1]))
849                    .with_msg(e.to_string())
850            })?;
851            Ok((parts[0].to_string(), num))
852        }
853
854        let params = s
855            .split(' ')
856            .filter(|p| !p.is_empty())
857            .map(parse_pair)
858            .collect::<Result<HashMap<_, _>>>()?;
859        Ok(NetParams { params })
860    }
861}
862
863impl SharedRandStatus {
864    /// Parse a current or previous shared rand value from a given
865    /// SharedRandPreviousValue or SharedRandCurrentValue.
866    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
867        match item.kwd() {
868            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
869            _ => {
870                return Err(Error::from(internal!(
871                    "wrong keyword {:?} on shared-random value",
872                    item.kwd()
873                ))
874                .at_pos(item.pos()));
875            }
876        }
877        let n_reveals: u8 = item.parse_arg(0)?;
878        let val: B64 = item.parse_arg(1)?;
879        let value = SharedRandVal(val.into_array()?);
880        // Added in proposal 342
881        let timestamp = item
882            .parse_optional_arg::<Iso8601TimeNoSp>(2)?
883            .map(Into::into);
884        Ok(SharedRandStatus {
885            n_reveals,
886            value,
887            timestamp,
888        })
889    }
890
891    /// Return the actual shared random value.
892    pub fn value(&self) -> &SharedRandVal {
893        &self.value
894    }
895
896    /// Return the timestamp (if any) associated with this `SharedRandValue`.
897    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
898        self.timestamp
899    }
900}
901
902impl DirSource {
903    /// Parse a "dir-source" item
904    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
905        if item.kwd() != NetstatusKwd::DIR_SOURCE {
906            return Err(
907                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
908                    .at_pos(item.pos()),
909            );
910        }
911        let nickname = item.required_arg(0)?.to_string();
912        let identity = item.parse_arg::<Fingerprint>(1)?.into();
913        let ip = item.parse_arg(3)?;
914        let dir_port = item.parse_arg(4)?;
915        let or_port = item.parse_arg(5)?;
916
917        Ok(DirSource {
918            nickname,
919            identity,
920            ip,
921            dir_port,
922            or_port,
923        })
924    }
925}
926
927impl ConsensusVoterInfo {
928    /// Parse a single ConsensusVoterInfo from a voter info section.
929    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
930        use NetstatusKwd::*;
931        // this unwrap should be safe because if there is not at least one
932        // token in the section, the section is unparsable.
933        #[allow(clippy::unwrap_used)]
934        let first = sec.first_item().unwrap();
935        if first.kwd() != DIR_SOURCE {
936            return Err(Error::from(internal!(
937                "Wrong keyword {:?} at start of voter info",
938                first.kwd()
939            ))
940            .at_pos(first.pos()));
941        }
942        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
943
944        let contact = sec.required(CONTACT)?.args_as_str().to_string();
945
946        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
947
948        Ok(ConsensusVoterInfo {
949            dir_source,
950            contact,
951            vote_digest,
952        })
953    }
954}
955
956impl std::str::FromStr for RelayFlags {
957    type Err = void::Void;
958    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
959        Ok(match s {
960            "Authority" => RelayFlags::AUTHORITY,
961            "BadExit" => RelayFlags::BAD_EXIT,
962            "Exit" => RelayFlags::EXIT,
963            "Fast" => RelayFlags::FAST,
964            "Guard" => RelayFlags::GUARD,
965            "HSDir" => RelayFlags::HSDIR,
966            "MiddleOnly" => RelayFlags::MIDDLE_ONLY,
967            "NoEdConsensus" => RelayFlags::NO_ED_CONSENSUS,
968            "Stable" => RelayFlags::STABLE,
969            "StaleDesc" => RelayFlags::STALE_DESC,
970            "Running" => RelayFlags::RUNNING,
971            "Valid" => RelayFlags::VALID,
972            "V2Dir" => RelayFlags::V2DIR,
973            _ => RelayFlags::empty(),
974        })
975    }
976}
977
978impl RelayFlags {
979    /// Parse a relay-flags entry from an "s" line.
980    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayFlags> {
981        if item.kwd() != NetstatusKwd::RS_S {
982            return Err(
983                Error::from(internal!("Wrong keyword {:?} for S line", item.kwd()))
984                    .at_pos(item.pos()),
985            );
986        }
987        // These flags are implicit.
988        let mut flags: RelayFlags = RelayFlags::RUNNING | RelayFlags::VALID;
989
990        let mut prev: Option<&str> = None;
991        for s in item.args() {
992            if let Some(p) = prev {
993                if p >= s {
994                    // Arguments out of order.
995                    return Err(EK::BadArgument
996                        .at_pos(item.pos())
997                        .with_msg("Flags out of order"));
998                }
999            }
1000            let fl = s.parse().void_unwrap();
1001            flags |= fl;
1002            prev = Some(s);
1003        }
1004
1005        Ok(flags)
1006    }
1007}
1008
1009impl Default for RelayWeight {
1010    fn default() -> RelayWeight {
1011        RelayWeight::Unmeasured(0)
1012    }
1013}
1014
1015impl RelayWeight {
1016    /// Parse a routerweight from a "w" line.
1017    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1018        if item.kwd() != NetstatusKwd::RS_W {
1019            return Err(
1020                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
1021                    .at_pos(item.pos()),
1022            );
1023        }
1024
1025        let params: NetParams<u32> = item.args_as_str().parse()?;
1026
1027        let bw = params.params.get("Bandwidth");
1028        let unmeas = params.params.get("Unmeasured");
1029
1030        let bw = match bw {
1031            None => return Ok(RelayWeight::Unmeasured(0)),
1032            Some(b) => *b,
1033        };
1034
1035        match unmeas {
1036            None | Some(0) => Ok(RelayWeight::Measured(bw)),
1037            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
1038            _ => Err(EK::BadArgument
1039                .at_pos(item.pos())
1040                .with_msg("unmeasured value")),
1041        }
1042    }
1043}
1044
1045impl Footer {
1046    /// Parse a directory footer from a footer section.
1047    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
1048        use NetstatusKwd::*;
1049        sec.required(DIRECTORY_FOOTER)?;
1050
1051        let weights = sec
1052            .maybe(BANDWIDTH_WEIGHTS)
1053            .args_as_str()
1054            .unwrap_or("")
1055            .parse()?;
1056
1057        Ok(Footer { weights })
1058    }
1059}
1060
1061/// Result of checking a single authority signature.
1062enum SigCheckResult {
1063    /// The signature checks out.  Great!
1064    Valid,
1065    /// The signature is invalid; no additional information could make it
1066    /// valid.
1067    Invalid,
1068    /// We can't check the signature because we don't have a
1069    /// certificate with the right signing key.
1070    MissingCert,
1071}
1072
1073impl Signature {
1074    /// Parse a Signature from a directory-signature section
1075    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1076        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
1077            return Err(Error::from(internal!(
1078                "Wrong keyword {:?} for directory signature",
1079                item.kwd()
1080            ))
1081            .at_pos(item.pos()));
1082        }
1083
1084        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
1085            (
1086                item.required_arg(0)?,
1087                item.required_arg(1)?,
1088                item.required_arg(2)?,
1089            )
1090        } else {
1091            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
1092        };
1093
1094        let digestname = alg.to_string();
1095        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1096        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1097        let key_ids = AuthCertKeyIds {
1098            id_fingerprint,
1099            sk_fingerprint,
1100        };
1101        let signature = item.obj("SIGNATURE")?;
1102
1103        Ok(Signature {
1104            digestname,
1105            key_ids,
1106            signature,
1107        })
1108    }
1109
1110    /// Return true if this signature has the identity key and signing key
1111    /// that match a given cert.
1112    fn matches_cert(&self, cert: &AuthCert) -> bool {
1113        cert.key_ids() == &self.key_ids
1114    }
1115
1116    /// If possible, find the right certificate for checking this signature
1117    /// from among a slice of certificates.
1118    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1119        certs.iter().find(|&c| self.matches_cert(c))
1120    }
1121
1122    /// Try to check whether this signature is a valid signature of a
1123    /// provided digest, given a slice of certificates that might contain
1124    /// its signing key.
1125    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
1126        match self.find_cert(certs) {
1127            None => SigCheckResult::MissingCert,
1128            Some(cert) => {
1129                let key = cert.signing_key();
1130                match key.verify(signed_digest, &self.signature[..]) {
1131                    Ok(()) => SigCheckResult::Valid,
1132                    Err(_) => SigCheckResult::Invalid,
1133                }
1134            }
1135        }
1136    }
1137}
1138
1139impl SignatureGroup {
1140    // TODO: these functions are pretty similar and could probably stand to be
1141    // refactored a lot.
1142
1143    /// Helper: Return a pair of the number of possible authorities'
1144    /// signatures in this object for which we _could_ find certs, and
1145    /// a list of the signatures we couldn't find certificates for.
1146    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
1147        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1148        let mut missing = Vec::new();
1149        for sig in &self.signatures {
1150            let id_fingerprint = &sig.key_ids.id_fingerprint;
1151            if ok.contains(id_fingerprint) {
1152                continue;
1153            }
1154            if sig.find_cert(certs).is_some() {
1155                ok.insert(*id_fingerprint);
1156                continue;
1157            }
1158
1159            missing.push(sig);
1160        }
1161        (ok.len(), missing)
1162    }
1163
1164    /// Given a list of authority identity key fingerprints, return true if
1165    /// this signature group is _potentially_ well-signed according to those
1166    /// authorities.
1167    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
1168        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1169        for sig in &self.signatures {
1170            let id_fp = &sig.key_ids.id_fingerprint;
1171            if signed_by.contains(id_fp) {
1172                // Already found this in the list.
1173                continue;
1174            }
1175            if authorities.contains(&id_fp) {
1176                signed_by.insert(*id_fp);
1177            }
1178        }
1179
1180        signed_by.len() > (authorities.len() / 2)
1181    }
1182
1183    /// Return true if the signature group defines a valid signature.
1184    ///
1185    /// A signature is valid if it signed by more than half of the
1186    /// authorities.  This API requires that `n_authorities` is the number of
1187    /// authorities we believe in, and that every cert in `certs` belongs
1188    /// to a real authority.
1189    fn validate(&self, n_authorities: u16, certs: &[AuthCert]) -> bool {
1190        // A set of the authorities (by identity) who have have signed
1191        // this document.  We use a set here in case `certs` has more
1192        // than one certificate for a single authority.
1193        let mut ok: HashSet<RsaIdentity> = HashSet::new();
1194
1195        for sig in &self.signatures {
1196            let id_fingerprint = &sig.key_ids.id_fingerprint;
1197            if ok.contains(id_fingerprint) {
1198                // We already checked at least one signature using this
1199                // authority's identity fingerprint.
1200                continue;
1201            }
1202
1203            let d: Option<&[u8]> = match sig.digestname.as_ref() {
1204                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
1205                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
1206                _ => None, // We don't know how to find this digest.
1207            };
1208            if d.is_none() {
1209                // We don't support this kind of digest for this kind
1210                // of document.
1211                continue;
1212            }
1213
1214            // Unwrap should be safe because of above `d.is_none()` check
1215            #[allow(clippy::unwrap_used)]
1216            match sig.check_signature(d.as_ref().unwrap(), certs) {
1217                SigCheckResult::Valid => {
1218                    ok.insert(*id_fingerprint);
1219                }
1220                _ => continue,
1221            }
1222        }
1223
1224        ok.len() > (n_authorities / 2) as usize
1225    }
1226}
1227
1228#[cfg(test)]
1229mod test {
1230    // @@ begin test lint list maintained by maint/add_warning @@
1231    #![allow(clippy::bool_assert_comparison)]
1232    #![allow(clippy::clone_on_copy)]
1233    #![allow(clippy::dbg_macro)]
1234    #![allow(clippy::mixed_attributes_style)]
1235    #![allow(clippy::print_stderr)]
1236    #![allow(clippy::print_stdout)]
1237    #![allow(clippy::single_char_pattern)]
1238    #![allow(clippy::unwrap_used)]
1239    #![allow(clippy::unchecked_duration_subtraction)]
1240    #![allow(clippy::useless_vec)]
1241    #![allow(clippy::needless_pass_by_value)]
1242    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
1243    use super::*;
1244    use hex_literal::hex;
1245
1246    const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
1247    const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
1248
1249    #[cfg(feature = "plain-consensus")]
1250    const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
1251    #[cfg(feature = "plain-consensus")]
1252    const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
1253
1254    fn read_bad(fname: &str) -> String {
1255        use std::fs;
1256        use std::path::PathBuf;
1257        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1258        path.push("testdata");
1259        path.push("bad-mdconsensus");
1260        path.push(fname);
1261
1262        fs::read_to_string(path).unwrap()
1263    }
1264
1265    #[test]
1266    fn parse_and_validate_md() -> Result<()> {
1267        use std::net::SocketAddr;
1268        use tor_checkable::{SelfSigned, Timebound};
1269        let mut certs = Vec::new();
1270        for cert in AuthCert::parse_multiple(CERTS)? {
1271            let cert = cert?.check_signature()?.dangerously_assume_timely();
1272            certs.push(cert);
1273        }
1274        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1275
1276        assert_eq!(certs.len(), 3);
1277
1278        let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
1279        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1280
1281        // The set of authorities we know _could_ validate this cert.
1282        assert!(consensus.authorities_are_correct(&auth_ids));
1283        // A subset would also work.
1284        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1285        {
1286            // If we only believe in an authority that isn't listed,
1287            // that won't work.
1288            let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
1289            assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
1290        }
1291
1292        let missing = consensus.key_is_correct(&[]).err().unwrap();
1293        assert_eq!(3, missing.len());
1294        assert!(consensus.key_is_correct(&certs).is_ok());
1295        let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
1296        assert_eq!(2, missing.len());
1297
1298        // here is a trick that had better not work.
1299        let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
1300        let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
1301
1302        assert_eq!(2, missing.len());
1303        assert!(consensus.is_well_signed(&same_three_times).is_err());
1304
1305        assert!(consensus.key_is_correct(&certs).is_ok());
1306        let consensus = consensus.check_signature(&certs)?;
1307
1308        assert_eq!(6, consensus.relays().len());
1309        let r0 = &consensus.relays()[0];
1310        assert_eq!(
1311            r0.md_digest(),
1312            &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
1313        );
1314        assert_eq!(
1315            r0.rsa_identity().as_bytes(),
1316            &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
1317        );
1318        assert!(!r0.weight().is_measured());
1319        assert!(!r0.weight().is_nonzero());
1320        let pv = &r0.protovers();
1321        assert!(pv.supports_subver("HSDir", 2));
1322        assert!(!pv.supports_subver("HSDir", 3));
1323        let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
1324        let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
1325        assert!(r0.orport_addrs().any(|a| a == &ip4));
1326        assert!(r0.orport_addrs().any(|a| a == &ip6));
1327
1328        Ok(())
1329    }
1330
1331    #[test]
1332    #[cfg(feature = "plain-consensus")]
1333    fn parse_and_validate_ns() -> Result<()> {
1334        use tor_checkable::{SelfSigned, Timebound};
1335        let mut certs = Vec::new();
1336        for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
1337            let cert = cert?.check_signature()?.dangerously_assume_timely();
1338            certs.push(cert);
1339        }
1340        let auth_ids: Vec<_> = certs.iter().map(|c| &c.key_ids().id_fingerprint).collect();
1341        assert_eq!(certs.len(), 4);
1342
1343        let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
1344        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
1345        // The set of authorities we know _could_ validate this cert.
1346        assert!(consensus.authorities_are_correct(&auth_ids));
1347        // A subset would also work.
1348        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
1349
1350        assert!(consensus.key_is_correct(&certs).is_ok());
1351
1352        let _consensus = consensus.check_signature(&certs)?;
1353
1354        Ok(())
1355    }
1356
1357    #[test]
1358    fn test_bad() {
1359        use crate::Pos;
1360        fn check(fname: &str, e: &Error) {
1361            let content = read_bad(fname);
1362            let res = MdConsensus::parse(&content);
1363            assert!(res.is_err());
1364            assert_eq!(&res.err().unwrap(), e);
1365        }
1366
1367        check(
1368            "bad-flags",
1369            &EK::BadArgument
1370                .at_pos(Pos::from_line(27, 1))
1371                .with_msg("Flags out of order"),
1372        );
1373        check(
1374            "bad-md-digest",
1375            &EK::BadArgument
1376                .at_pos(Pos::from_line(40, 3))
1377                .with_msg("Invalid base64"),
1378        );
1379        check(
1380            "bad-weight",
1381            &EK::BadArgument
1382                .at_pos(Pos::from_line(67, 141))
1383                .with_msg("invalid digit found in string"),
1384        );
1385        check(
1386            "bad-weights",
1387            &EK::BadArgument
1388                .at_pos(Pos::from_line(51, 13))
1389                .with_msg("invalid digit found in string"),
1390        );
1391        check(
1392            "wrong-order",
1393            &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
1394        );
1395        check(
1396            "wrong-start",
1397            &EK::UnexpectedToken
1398                .with_msg("vote-status")
1399                .at_pos(Pos::from_line(1, 1)),
1400        );
1401        check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
1402    }
1403
1404    fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
1405        let mut reader = NetDocReader::new(s)?;
1406        let tok = reader.next().unwrap();
1407        assert!(reader.next().is_none());
1408        tok
1409    }
1410
1411    #[test]
1412    fn test_weight() {
1413        let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
1414        let w = RelayWeight::from_item(&w).unwrap();
1415        assert!(!w.is_measured());
1416        assert!(w.is_nonzero());
1417
1418        let w = gettok("w Bandwidth=10\n").unwrap();
1419        let w = RelayWeight::from_item(&w).unwrap();
1420        assert!(w.is_measured());
1421        assert!(w.is_nonzero());
1422
1423        let w = RelayWeight::default();
1424        assert!(!w.is_measured());
1425        assert!(!w.is_nonzero());
1426
1427        let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
1428        let w = RelayWeight::from_item(&w).unwrap();
1429        assert!(!w.is_measured());
1430        assert!(!w.is_nonzero());
1431
1432        let w = gettok("r foo\n").unwrap();
1433        let w = RelayWeight::from_item(&w);
1434        assert!(w.is_err());
1435
1436        let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
1437        let w = RelayWeight::from_item(&w);
1438        assert!(w.is_err());
1439
1440        let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
1441        let w = RelayWeight::from_item(&w);
1442        assert!(w.is_err());
1443    }
1444
1445    #[test]
1446    fn test_netparam() {
1447        let p = "Hello=600 Goodbye=5 Fred=7"
1448            .parse::<NetParams<u32>>()
1449            .unwrap();
1450        assert_eq!(p.get("Hello"), Some(&600_u32));
1451
1452        let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
1453        assert!(p.is_err());
1454
1455        let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
1456        assert!(p.is_err());
1457    }
1458
1459    #[test]
1460    fn test_sharedrand() {
1461        let sr =
1462            gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
1463                .unwrap();
1464        let sr = SharedRandStatus::from_item(&sr).unwrap();
1465
1466        assert_eq!(sr.n_reveals, 9);
1467        assert_eq!(
1468            sr.value.0,
1469            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
1470        );
1471        assert!(sr.timestamp.is_none());
1472
1473        let sr2 = gettok(
1474            "shared-rand-current-value 9 \
1475                    5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
1476        )
1477        .unwrap();
1478        let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
1479        assert_eq!(sr2.n_reveals, sr.n_reveals);
1480        assert_eq!(sr2.value.0, sr.value.0);
1481        assert_eq!(
1482            sr2.timestamp.unwrap(),
1483            humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
1484        );
1485
1486        let sr = gettok("foo bar\n").unwrap();
1487        let sr = SharedRandStatus::from_item(&sr);
1488        assert!(sr.is_err());
1489    }
1490
1491    #[test]
1492    fn test_protostatus() {
1493        let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
1494
1495        let outcome = ProtoStatus {
1496            recommended: "Link=7".parse().unwrap(),
1497            required: "Desc=5".parse().unwrap(),
1498        }
1499        .check_protocols(&my_protocols);
1500        assert!(outcome.is_ok());
1501
1502        let outcome = ProtoStatus {
1503            recommended: "Microdesc=4 Link=7".parse().unwrap(),
1504            required: "Desc=5".parse().unwrap(),
1505        }
1506        .check_protocols(&my_protocols);
1507        assert_eq!(
1508            outcome,
1509            Err(ProtocolSupportError::MissingRecommended(
1510                "Microdesc=4".parse().unwrap()
1511            ))
1512        );
1513
1514        let outcome = ProtoStatus {
1515            recommended: "Microdesc=4 Link=7".parse().unwrap(),
1516            required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
1517        }
1518        .check_protocols(&my_protocols);
1519        assert_eq!(
1520            outcome,
1521            Err(ProtocolSupportError::MissingRequired(
1522                "Cons=6-12 Wombat=15".parse().unwrap()
1523            ))
1524        );
1525    }
1526
1527    #[test]
1528    fn serialize_protostatus() {
1529        let ps = ProtoStatuses {
1530            client: ProtoStatus {
1531                recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
1532                required: "Link=5 LinkAuth=3".parse().unwrap(),
1533            },
1534            relay: ProtoStatus {
1535                recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
1536                required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
1537            },
1538        };
1539        let json = serde_json::to_string(&ps).unwrap();
1540        let ps2 = serde_json::from_str(json.as_str()).unwrap();
1541        assert_eq!(ps, ps2);
1542
1543        let ps3: ProtoStatuses = serde_json::from_str(
1544            r#"{
1545            "client":{
1546                "required":"Link=5 LinkAuth=3",
1547                "recommended":"Link=1-5 LinkAuth=2-5"
1548            },
1549            "relay":{
1550                "required":"Wombat=20-22 Knish=25-27",
1551                "recommended":"Wombat=20-30 Knish=20-30"
1552            }
1553        }"#,
1554        )
1555        .unwrap();
1556        assert_eq!(ps, ps3);
1557    }
1558}