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