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