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