Skip to main content

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