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

            
51
mod rs;
52

            
53
pub mod md;
54
#[cfg(feature = "plain-consensus")]
55
pub mod plain;
56
#[cfg(feature = "ns-vote")]
57
pub mod vote;
58

            
59
#[cfg(feature = "build_docs")]
60
mod build;
61

            
62
#[cfg(feature = "parse2")]
63
use {
64
    crate::parse2::{self, ArgumentStream}, //
65
};
66

            
67
#[cfg(feature = "parse2")]
68
pub use {
69
    parse2_impls::ProtoStatusesNetdocParseAccumulator, //
70
};
71

            
72
use crate::doc::authcert::{AuthCert, AuthCertKeyIds};
73
use crate::parse::keyword::Keyword;
74
use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
75
use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
76
use crate::types::misc::*;
77
use crate::util::PeekableIterator;
78
use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos, Result};
79
use std::collections::{BTreeSet, HashMap, HashSet};
80
use std::fmt::{self, Display};
81
use std::result::Result as StdResult;
82
use std::str::FromStr;
83
use std::sync::Arc;
84
use std::{net, result, time};
85
use tor_error::{HasKind, internal};
86
use tor_protover::Protocols;
87

            
88
use derive_deftly::{Deftly, define_derive_deftly};
89
use digest::Digest;
90
use std::sync::LazyLock;
91
use tor_checkable::{ExternallySigned, timed::TimerangeBound};
92
use tor_llcrypto as ll;
93
use tor_llcrypto::pk::rsa::RsaIdentity;
94

            
95
use serde::{Deserialize, Deserializer};
96

            
97
#[cfg(feature = "build_docs")]
98
pub use build::MdConsensusBuilder;
99
#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
100
pub use build::PlainConsensusBuilder;
101
#[cfg(feature = "build_docs")]
102
ns_export_each_flavor! {
103
    ty: RouterStatusBuilder;
104
}
105

            
106
ns_export_each_variety! {
107
    ty: RouterStatus, Preamble;
108
}
109

            
110
#[deprecated]
111
#[cfg(feature = "ns_consensus")]
112
pub use PlainConsensus as NsConsensus;
113
#[deprecated]
114
#[cfg(feature = "ns_consensus")]
115
pub use PlainRouterStatus as NsRouterStatus;
116
#[deprecated]
117
#[cfg(feature = "ns_consensus")]
118
pub use UncheckedPlainConsensus as UncheckedNsConsensus;
119
#[deprecated]
120
#[cfg(feature = "ns_consensus")]
121
pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
122

            
123
#[cfg(feature = "ns-vote")]
124
pub 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)]
141
pub 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))]
153
pub 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

            
182
define_derive_deftly! {
183
    /// Bespoke derive for `Lifetime`, for `new` and accessors
184
    Lifetime:
185

            
186
    impl Lifetime {
187
        /// Construct a new Lifetime.
188
13851
        pub fn new(
189
13851
            $( $fname: time::SystemTime, )
190
13851
        ) -> 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
167798
        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
530
        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
53212
        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
}
227
use 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)]
241
pub struct ConsensusMethod(u32);
242
impl 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]
253
pub 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")]
263
pub mod consensus_methods_comma_separated {
264
    use super::*;
265
    use parse2::ArgumentError as AE;
266
    use std::result::Result;
267

            
268
    /// Parse
269
24
    pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
270
24
        let mut methods = BTreeSet::new();
271
72
        for ent in args.next().ok_or(AE::Missing)?.split(',') {
272
72
            let ent = ent.parse().map_err(|_| AE::Invalid)?;
273
72
            if !methods.insert(ent) {
274
                return Err(AE::Invalid);
275
72
            }
276
        }
277
24
        Ok(ConsensusMethods { methods })
278
24
    }
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)]
297
pub struct NetParams<T> {
298
    /// Map from keys to values.
299
    params: HashMap<String, T>,
300
}
301

            
302
impl<T> NetParams<T> {
303
    /// Create a new empty list of NetParams.
304
    #[allow(unused)]
305
29158
    pub fn new() -> Self {
306
29158
        NetParams {
307
29158
            params: HashMap::new(),
308
29158
        }
309
29158
    }
310
    /// Retrieve a given network parameter, if it is present.
311
194497
    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
312
194497
        self.params.get(v.as_ref())
313
194497
    }
314
    /// Return an iterator over all key value pairs in an arbitrary order.
315
21922
    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
316
21922
        self.params.iter()
317
21922
    }
318
    /// Set or replace the value of a network parameter.
319
10368
    pub fn set(&mut self, k: String, v: T) {
320
10368
        self.params.insert(k, v);
321
10368
    }
322
}
323

            
324
impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
325
6958
    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
326
        NetParams {
327
7005
            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
328
        }
329
6958
    }
330
}
331

            
332
impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
333
4118
    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
334
4118
        self.params.extend(iter);
335
4118
    }
336
}
337

            
338
impl<'de, T> Deserialize<'de> for NetParams<T>
339
where
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)]
360
pub 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

            
373
impl 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
218
    pub fn check_protocols(
384
218
        &self,
385
218
        supported_protocols: &Protocols,
386
218
    ) -> StdResult<(), ProtocolSupportError> {
387
        // Required protocols take precedence, so we check them first.
388
218
        let missing_required = self.required.difference(supported_protocols);
389
218
        if !missing_required.is_empty() {
390
108
            return Err(ProtocolSupportError::MissingRequired(missing_required));
391
110
        }
392
110
        let missing_recommended = self.recommended.difference(supported_protocols);
393
110
        if !missing_recommended.is_empty() {
394
55
            return Err(ProtocolSupportError::MissingRecommended(
395
55
                missing_recommended,
396
55
            ));
397
55
        }
398

            
399
55
        Ok(())
400
218
    }
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]
407
pub 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

            
419
impl 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

            
426
impl 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)]
439
pub 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

            
446
impl ProtoStatuses {
447
    /// Return the list of recommended and required protocols for running as a client.
448
212
    pub fn client(&self) -> &ProtoStatus {
449
212
        &self.client
450
212
    }
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)]
467
pub 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

            
478
impl ConsensusFlavor {
479
    /// Return the name of this consensus flavor.
480
2703
    pub fn name(&self) -> &'static str {
481
2703
        match self {
482
954
            ConsensusFlavor::Plain => "ns", // spec bug, now baked in
483
1749
            ConsensusFlavor::Microdesc => "microdesc",
484
        }
485
2703
    }
486
    /// Try to find the flavor whose name is `name`.
487
    ///
488
    /// For historical reasons, an unnamed flavor indicates an "Plain"
489
    /// document.
490
385
    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
491
385
        match name {
492
383
            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
493
2
            Some("ns") | None => Ok(ConsensusFlavor::Plain),
494
            Some(other) => {
495
                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
496
            }
497
        }
498
385
    }
499
}
500

            
501
/// The signature of a single directory authority on a networkstatus document.
502
#[derive(Debug, Clone)]
503
#[non_exhaustive]
504
pub 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]
520
pub 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.)
534
pub 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))]
542
pub 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]
564
pub 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)]
584
pub enum RelayWeight {
585
    /// An unmeasured weight for a relay.
586
    Unmeasured(u32),
587
    /// An measured weight for a relay.
588
    Measured(u32),
589
}
590

            
591
impl RelayWeight {
592
    /// Return true if this weight is the result of a successful measurement
593
26245
    pub fn is_measured(&self) -> bool {
594
26245
        matches!(self, RelayWeight::Measured(_))
595
26245
    }
596
    /// Return true if this weight is nonzero
597
24761
    pub fn is_nonzero(&self) -> bool {
598
24761
        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
599
24761
    }
600
}
601

            
602
/// All information about a single authority, as represented in a consensus
603
#[derive(Debug, Clone)]
604
#[non_exhaustive]
605
pub 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]
618
pub 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.
629
pub type MdConsensus = md::Consensus;
630

            
631
/// An MdConsensus that has been parsed and checked for timeliness,
632
/// but not for signatures.
633
pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
634

            
635
/// An MdConsensus that has been parsed but not checked for signatures
636
/// and timeliness.
637
pub type UncheckedMdConsensus = md::UncheckedConsensus;
638

            
639
#[cfg(feature = "plain-consensus")]
640
/// A consensus document that lists relays along with their
641
/// router descriptor documents.
642
pub 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.
647
pub 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.
652
pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
653

            
654
decl_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
723
55
static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
724
    use NetstatusKwd::*;
725
55
    let mut rules = SectionRules::builder();
726
55
    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
727
55
    rules.add(VOTE_STATUS.rule().required().args(1..));
728
55
    rules.add(VALID_AFTER.rule().required());
729
55
    rules.add(FRESH_UNTIL.rule().required());
730
55
    rules.add(VALID_UNTIL.rule().required());
731
55
    rules.add(VOTING_DELAY.rule().args(2..));
732
55
    rules.add(CLIENT_VERSIONS.rule());
733
55
    rules.add(SERVER_VERSIONS.rule());
734
55
    rules.add(KNOWN_FLAGS.rule().required());
735
55
    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
736
55
    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
737
55
    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
738
55
    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
739
55
    rules.add(PARAMS.rule());
740
55
    rules
741
55
});
742
/// Rules for parsing the header of a consensus.
743
55
static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
744
    use NetstatusKwd::*;
745
55
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
746
55
    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
747
55
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
748
55
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
749
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
750
55
    rules.build()
751
55
});
752
/*
753
/// Rules for parsing the header of a vote.
754
static 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.
765
static 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
782
55
static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
783
    use NetstatusKwd::*;
784
55
    let mut rules = SectionRules::builder();
785
55
    rules.add(DIR_SOURCE.rule().required().args(6..));
786
55
    rules.add(CONTACT.rule().required());
787
55
    rules.add(VOTE_DIGEST.rule().required());
788
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
789
55
    rules.build()
790
55
});
791
/// Shared rules for parsing a single routerstatus
792
static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
793
55
    LazyLock::new(|| {
794
        use NetstatusKwd::*;
795
55
        let mut rules = SectionRules::builder();
796
55
        rules.add(RS_A.rule().may_repeat().args(1..));
797
55
        rules.add(RS_S.rule().required());
798
55
        rules.add(RS_V.rule());
799
55
        rules.add(RS_PR.rule().required());
800
55
        rules.add(RS_W.rule());
801
55
        rules.add(RS_P.rule().args(2..));
802
55
        rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
803
55
        rules
804
55
    });
805

            
806
/// Rules for parsing a single routerstatus in an NS consensus
807
2
static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
808
    use NetstatusKwd::*;
809
2
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
810
2
    rules.add(RS_R.rule().required().args(8..));
811
2
    rules.build()
812
2
});
813

            
814
/*
815
/// Rules for parsing a single routerstatus in a vote
816
static 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
826
55
static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
827
    use NetstatusKwd::*;
828
55
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
829
55
    rules.add(RS_R.rule().required().args(6..));
830
55
    rules.add(RS_M.rule().required().args(1..));
831
55
    rules.build()
832
55
});
833
/// Rules for parsing consensus fields from a footer.
834
55
static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
835
    use NetstatusKwd::*;
836
55
    let mut rules = SectionRules::builder();
837
55
    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
838
    // consensus only
839
55
    rules.add(BANDWIDTH_WEIGHTS.rule());
840
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
841
55
    rules.build()
842
55
});
843

            
844
impl ProtoStatus {
845
    /// Construct a ProtoStatus from two chosen keywords in a section.
846
770
    fn from_section(
847
770
        sec: &Section<'_, NetstatusKwd>,
848
770
        recommend_token: NetstatusKwd,
849
770
        required_token: NetstatusKwd,
850
770
    ) -> Result<ProtoStatus> {
851
        /// Helper: extract a Protocols entry from an item's arguments.
852
1540
        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
853
1540
            if let Some(item) = t {
854
1540
                item.args_as_str()
855
1540
                    .parse::<Protocols>()
856
1540
                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
857
            } else {
858
                Ok(Protocols::new())
859
            }
860
1540
        }
861

            
862
770
        let recommended = parse(sec.get(recommend_token))?;
863
770
        let required = parse(sec.get(required_token))?;
864
770
        Ok(ProtoStatus {
865
770
            recommended,
866
770
            required,
867
770
        })
868
770
    }
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
159
    pub fn required_protocols(&self) -> &Protocols {
877
159
        &self.required
878
159
    }
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

            
889
impl<T> std::str::FromStr for NetParams<T>
890
where
891
    T: std::str::FromStr,
892
    T::Err: std::error::Error,
893
{
894
    type Err = Error;
895
16982
    fn from_str(s: &str) -> Result<Self> {
896
        /// Helper: parse a single K=V pair.
897
18010
        fn parse_pair<U>(p: &str) -> Result<(String, U)>
898
18010
        where
899
18010
            U: std::str::FromStr,
900
18010
            U::Err: std::error::Error,
901
        {
902
18010
            let parts: Vec<_> = p.splitn(2, '=').collect();
903
18010
            if parts.len() != 2 {
904
                return Err(EK::BadArgument
905
                    .at_pos(Pos::at(p))
906
                    .with_msg("Missing = in key=value list"));
907
18010
            }
908
18010
            let num = parts[1].parse::<U>().map_err(|e| {
909
8
                EK::BadArgument
910
8
                    .at_pos(Pos::at(parts[1]))
911
8
                    .with_msg(e.to_string())
912
8
            })?;
913
18002
            Ok((parts[0].to_string(), num))
914
18010
        }
915

            
916
16982
        let params = s
917
16982
            .split(' ')
918
30477
            .filter(|p| !p.is_empty())
919
16982
            .map(parse_pair)
920
16982
            .collect::<Result<HashMap<_, _>>>()?;
921
16974
        Ok(NetParams { params })
922
16982
    }
923
}
924

            
925
impl FromStr for SharedRandVal {
926
    type Err = Error;
927
4
    fn from_str(s: &str) -> Result<Self> {
928
4
        let val: B64 = s.parse()?;
929
4
        let val = SharedRandVal(val.into_array()?);
930
4
        Ok(val)
931
4
    }
932
}
933
impl 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
}
938
impl NormalItemArgument for SharedRandVal {}
939

            
940
impl SharedRandStatus {
941
    /// Parse a current or previous shared rand value from a given
942
    /// SharedRandPreviousValue or SharedRandCurrentValue.
943
6
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
944
6
        match item.kwd() {
945
4
            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
946
            _ => {
947
2
                return Err(Error::from(internal!(
948
2
                    "wrong keyword {:?} on shared-random value",
949
2
                    item.kwd()
950
2
                ))
951
2
                .at_pos(item.pos()));
952
            }
953
        }
954
4
        let n_reveals: u8 = item.parse_arg(0)?;
955
4
        let value: SharedRandVal = item.parse_arg(1)?;
956
        // Added in proposal 342
957
4
        let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
958
4
        Ok(SharedRandStatus {
959
4
            n_reveals,
960
4
            value,
961
4
            timestamp,
962
4
        })
963
6
    }
964

            
965
    /// Return the actual shared random value.
966
848
    pub fn value(&self) -> &SharedRandVal {
967
848
        &self.value
968
848
    }
969

            
970
    /// Return the timestamp (if any) associated with this `SharedRandValue`.
971
1590
    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
972
1590
        self.timestamp.map(|t| t.0)
973
1590
    }
974
}
975

            
976
impl DirSource {
977
    /// Parse a "dir-source" item
978
1157
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
979
1157
        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
1157
        }
985
1157
        let nickname = item.required_arg(0)?.to_string();
986
1157
        let identity = item.parse_arg::<Fingerprint>(1)?.into();
987
1157
        let ip = item.parse_arg(3)?;
988
1157
        let dir_port = item.parse_arg(4)?;
989
1157
        let or_port = item.parse_arg(5)?;
990

            
991
1157
        Ok(DirSource {
992
1157
            nickname,
993
1157
            identity,
994
1157
            ip,
995
1157
            dir_port,
996
1157
            or_port,
997
1157
        })
998
1157
    }
999
}
impl ConsensusVoterInfo {
    /// Parse a single ConsensusVoterInfo from a voter info section.
1157
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
        use NetstatusKwd::*;
        // this unwrap should be safe because if there is not at least one
        // token in the section, the section is unparsable.
        #[allow(clippy::unwrap_used)]
1157
        let first = sec.first_item().unwrap();
1157
        if first.kwd() != DIR_SOURCE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} at start of voter info",
                first.kwd()
            ))
            .at_pos(first.pos()));
1157
        }
1157
        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1157
        let contact = sec.required(CONTACT)?.args_as_str().to_string();
1157
        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1157
        Ok(ConsensusVoterInfo {
1157
            dir_source,
1157
            contact,
1157
            vote_digest,
1157
        })
1157
    }
}
impl Default for RelayWeight {
2
    fn default() -> RelayWeight {
2
        RelayWeight::Unmeasured(0)
2
    }
}
impl RelayWeight {
    /// Parse a routerweight from a "w" line.
2086
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
2086
        if item.kwd() != NetstatusKwd::RS_W {
6
            return Err(
6
                Error::from(internal!("Wrong keyword {:?} on W line", item.kwd()))
6
                    .at_pos(item.pos()),
6
            );
2080
        }
2080
        let params = item.args_as_str().parse()?;
2078
        Self::from_net_params(&params).map_err(|e| e.at_pos(item.pos()))
2086
    }
    /// Parse a routerweight from partially-parsed `w` line in the form of a `NetParams`
    ///
    /// This function is the common part shared between `parse2` and `parse`.
2538
    fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
2538
        let bw = params.params.get("Bandwidth");
2538
        let unmeas = params.params.get("Unmeasured");
2538
        let bw = match bw {
2
            None => return Ok(RelayWeight::Unmeasured(0)),
2536
            Some(b) => *b,
        };
2536
        match unmeas {
438
            None | Some(0) => Ok(RelayWeight::Measured(bw)),
2098
            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
            _ => Err(EK::BadArgument.with_msg("unmeasured value")),
        }
2538
    }
}
/// `parse2` impls for types in this module
///
/// Separate module to save on repeated `cfg` and for a separate namespace.
#[cfg(feature = "parse2")]
mod parse2_impls {
    use super::*;
    use parse2::ArgumentError as AE;
    use parse2::ErrorProblem as EP;
    use parse2::{ArgumentStream, ItemArgumentParseable, ItemValueParseable};
    use parse2::{KeywordRef, NetdocParseableFields, UnparsedItem};
    use paste::paste;
    use std::result::Result;
    /// Implements `NetdocParseableFields` for `ProtoStatuses`
    ///
    /// We have this macro so that it's impossible to write things like
    /// ```text
    ///      ProtoStatuses {
    ///          client: ProtoStatus {
    ///              recommended: something something recommended_relay_versions something,
    /// ```
    ///
    /// (The structure of `ProtoStatuses` means the normal parse2 derive won't work for it.
    /// Note the bug above: the recommended *relay* version info is put in the *client* field.
    /// Preventing this bug must involve: avoiding writing twice the field name elements,
    /// such as `relay` and `client`, during this kind of construction/conversion.)
    macro_rules! impl_proto_statuses { { $( $rr:ident $cr:ident; )* } => { paste! {
        #[derive(Deftly)]
        #[derive_deftly(NetdocParseableFields)]
        // Only ProtoStatusesParseNetdocParseAccumulator is exposed.
        #[allow(unreachable_pub)]
        pub struct ProtoStatusesParseHelper {
            $(
                #[deftly(netdoc(default))]
                [<$rr _ $cr _protocols>]: Protocols,
            )*
        }
        /// Partially parsed `ProtoStatuses`
        pub use ProtoStatusesParseHelperNetdocParseAccumulator
            as ProtoStatusesNetdocParseAccumulator;
        impl NetdocParseableFields for ProtoStatuses {
            type Accumulator = ProtoStatusesNetdocParseAccumulator;
            fn is_item_keyword(kw: KeywordRef<'_>) -> bool {
                ProtoStatusesParseHelper::is_item_keyword(kw)
            }
            fn accumulate_item(
                acc: &mut Self::Accumulator,
                item: UnparsedItem<'_>,
            ) -> Result<(), EP> {
                ProtoStatusesParseHelper::accumulate_item(acc, item)
            }
            fn finish(acc: Self::Accumulator) -> Result<Self, EP> {
                let parse = ProtoStatusesParseHelper::finish(acc)?;
                let mut out = ProtoStatuses::default();
                $(
                    out.$cr.$rr = parse.[< $rr _ $cr _protocols >];
                )*
                Ok(out)
            }
        }
    } } }
    impl_proto_statuses! {
        required client;
        required relay;
        recommended client;
        recommended relay;
    }
    impl ItemValueParseable for NetParams<i32> {
        fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
            item.check_no_object()?;
            item.args_copy()
                .into_remaining()
                .parse()
                .map_err(item.invalid_argument_handler("parameters"))
        }
    }
    impl ItemValueParseable for RelayWeight {
460
        fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
460
            item.check_no_object()?;
460
            (|| {
460
                let params = item.args_copy().into_remaining().parse()?;
460
                Self::from_net_params(&params)
            })()
460
            .map_err(item.invalid_argument_handler("weights"))
460
        }
    }
    impl ItemValueParseable for rs::SoftwareVersion {
460
        fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
460
            item.check_no_object()?;
460
            item.args_mut()
460
                .into_remaining()
460
                .parse()
460
                .map_err(item.invalid_argument_handler("version"))
460
        }
    }
    impl ItemArgumentParseable for IgnoredPublicationTimeSp {
448
        fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
916
            let mut next_arg = || a.next().ok_or(AE::Missing);
448
            let _: &str = next_arg()?;
448
            let _: &str = next_arg()?;
448
            Ok(IgnoredPublicationTimeSp)
448
        }
    }
}
impl Footer {
    /// Parse a directory footer from a footer section.
377
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
377
        sec.required(DIRECTORY_FOOTER)?;
377
        let weights = sec
377
            .maybe(BANDWIDTH_WEIGHTS)
377
            .args_as_str()
377
            .unwrap_or("")
377
            .parse()?;
375
        Ok(Footer { weights })
377
    }
}
/// Result of checking a single authority signature.
enum SigCheckResult {
    /// The signature checks out.  Great!
    Valid,
    /// The signature is invalid; no additional information could make it
    /// valid.
    Invalid,
    /// We can't check the signature because we don't have a
    /// certificate with the right signing key.
    MissingCert,
}
impl Signature {
    /// Parse a Signature from a directory-signature section
1127
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1127
        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} for directory signature",
                item.kwd()
            ))
            .at_pos(item.pos()));
1127
        }
1127
        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
            (
1119
                item.required_arg(0)?,
1119
                item.required_arg(1)?,
1119
                item.required_arg(2)?,
            )
        } else {
8
            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
        };
1127
        let digestname = alg.to_string();
1127
        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1127
        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1127
        let key_ids = AuthCertKeyIds {
1127
            id_fingerprint,
1127
            sk_fingerprint,
1127
        };
1127
        let signature = item.obj("SIGNATURE")?;
1127
        Ok(Signature {
1127
            digestname,
1127
            key_ids,
1127
            signature,
1127
        })
1127
    }
    /// Return true if this signature has the identity key and signing key
    /// that match a given cert.
799
    fn matches_cert(&self, cert: &AuthCert) -> bool {
799
        cert.key_ids() == self.key_ids
799
    }
    /// If possible, find the right certificate for checking this signature
    /// from among a slice of certificates.
1171
    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1265
        certs.iter().find(|&c| self.matches_cert(c))
1171
    }
    /// Try to check whether this signature is a valid signature of a
    /// provided digest, given a slice of certificates that might contain
    /// its signing key.
179
    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
179
        match self.find_cert(certs) {
57
            None => SigCheckResult::MissingCert,
122
            Some(cert) => {
122
                let key = cert.signing_key();
122
                match key.verify(signed_digest, &self.signature[..]) {
122
                    Ok(()) => SigCheckResult::Valid,
                    Err(_) => SigCheckResult::Invalid,
                }
            }
        }
179
    }
}
impl SignatureGroup {
    // TODO: these functions are pretty similar and could probably stand to be
    // refactored a lot.
    /// Helper: Return a pair of the number of possible authorities'
    /// signatures in this object for which we _could_ find certs, and
    /// a list of the signatures we couldn't find certificates for.
330
    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
330
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
330
        let mut missing = Vec::new();
1322
        for sig in &self.signatures {
992
            let id_fingerprint = &sig.key_ids.id_fingerprint;
992
            if ok.contains(id_fingerprint) {
                continue;
992
            }
992
            if sig.find_cert(certs).is_some() {
183
                ok.insert(*id_fingerprint);
183
                continue;
809
            }
809
            missing.push(sig);
        }
330
        (ok.len(), missing)
330
    }
    /// Given a list of authority identity key fingerprints, return true if
    /// this signature group is _potentially_ well-signed according to those
    /// authorities.
275
    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
275
        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1104
        for sig in &self.signatures {
829
            let id_fp = &sig.key_ids.id_fingerprint;
829
            if signed_by.contains(id_fp) {
                // Already found this in the list.
                continue;
829
            }
829
            if authorities.contains(&id_fp) {
442
                signed_by.insert(*id_fp);
442
            }
        }
275
        signed_by.len() > (authorities.len() / 2)
275
    }
    /// Return true if the signature group defines a valid signature.
    ///
    /// A signature is valid if it signed by more than half of the
    /// authorities.  This API requires that `n_authorities` is the number of
    /// authorities we believe in, and that every cert in `certs` belongs
    /// to a real authority.
59
    fn validate(&self, n_authorities: usize, certs: &[AuthCert]) -> bool {
        // A set of the authorities (by identity) who have have signed
        // this document.  We use a set here in case `certs` has more
        // than one certificate for a single authority.
59
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
238
        for sig in &self.signatures {
179
            let id_fingerprint = &sig.key_ids.id_fingerprint;
179
            if ok.contains(id_fingerprint) {
                // We already checked at least one signature using this
                // authority's identity fingerprint.
                continue;
179
            }
179
            let d: Option<&[u8]> = match sig.digestname.as_ref() {
188
                "sha256" => self.sha256.as_ref().map(|a| &a[..]),
12
                "sha1" => self.sha1.as_ref().map(|a| &a[..]),
                _ => None, // We don't know how to find this digest.
            };
179
            if d.is_none() {
                // We don't support this kind of digest for this kind
                // of document.
                continue;
179
            }
            // Unwrap should be safe because of above `d.is_none()` check
            #[allow(clippy::unwrap_used)]
179
            match sig.check_signature(d.as_ref().unwrap(), certs) {
122
                SigCheckResult::Valid => {
122
                    ok.insert(*id_fingerprint);
122
                }
57
                _ => continue,
            }
        }
59
        ok.len() > (n_authorities / 2)
59
    }
}
#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use super::*;
    use hex_literal::hex;
    #[cfg(all(feature = "ns-vote", feature = "parse2"))]
    use {
        crate::parse2::{NetdocUnverified as _, ParseInput, parse_netdoc},
        std::fs,
    };
    const CERTS: &str = include_str!("../../testdata/authcerts2.txt");
    const CONSENSUS: &str = include_str!("../../testdata/mdconsensus1.txt");
    #[cfg(feature = "plain-consensus")]
    const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
    #[cfg(feature = "plain-consensus")]
    const PLAIN_CONSENSUS: &str = include_str!("../../testdata2/cached-consensus");
    fn read_bad(fname: &str) -> String {
        use std::fs;
        use std::path::PathBuf;
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("testdata");
        path.push("bad-mdconsensus");
        path.push(fname);
        fs::read_to_string(path).unwrap()
    }
    #[test]
    fn parse_and_validate_md() -> Result<()> {
        use std::net::SocketAddr;
        use tor_checkable::{SelfSigned, Timebound};
        let mut certs = Vec::new();
        for cert in AuthCert::parse_multiple(CERTS)? {
            let cert = cert?.check_signature()?.dangerously_assume_timely();
            certs.push(cert);
        }
        let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
        assert_eq!(certs.len(), 3);
        let (_, _, consensus) = MdConsensus::parse(CONSENSUS)?;
        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
        // The set of authorities we know _could_ validate this cert.
        assert!(consensus.authorities_are_correct(&auth_ids));
        // A subset would also work.
        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
        {
            // If we only believe in an authority that isn't listed,
            // that won't work.
            let bad_auth_id = (*b"xxxxxxxxxxxxxxxxxxxx").into();
            assert!(!consensus.authorities_are_correct(&[&bad_auth_id]));
        }
        let missing = consensus.key_is_correct(&[]).err().unwrap();
        assert_eq!(3, missing.len());
        assert!(consensus.key_is_correct(&certs).is_ok());
        let missing = consensus.key_is_correct(&certs[0..1]).err().unwrap();
        assert_eq!(2, missing.len());
        // here is a trick that had better not work.
        let same_three_times = vec![certs[0].clone(), certs[0].clone(), certs[0].clone()];
        let missing = consensus.key_is_correct(&same_three_times).err().unwrap();
        assert_eq!(2, missing.len());
        assert!(consensus.is_well_signed(&same_three_times).is_err());
        assert!(consensus.key_is_correct(&certs).is_ok());
        let consensus = consensus.check_signature(&certs)?;
        assert_eq!(6, consensus.relays().len());
        let r0 = &consensus.relays()[0];
        assert_eq!(
            r0.md_digest(),
            &hex!("73dabe0a0468f4f7a67810a18d11e36731bb1d2ec3634db459100609f3b3f535")
        );
        assert_eq!(
            r0.rsa_identity().as_bytes(),
            &hex!("0a3057af2910415794d8ea430309d9ac5f5d524b")
        );
        assert!(!r0.weight().is_measured());
        assert!(!r0.weight().is_nonzero());
        let pv = &r0.protovers();
        assert!(pv.supports_subver("HSDir", 2));
        assert!(!pv.supports_subver("HSDir", 3));
        let ip4 = "127.0.0.1:5002".parse::<SocketAddr>().unwrap();
        let ip6 = "[::1]:5002".parse::<SocketAddr>().unwrap();
        assert!(r0.addrs().any(|a| a == ip4));
        assert!(r0.addrs().any(|a| a == ip6));
        Ok(())
    }
    #[test]
    #[cfg(feature = "plain-consensus")]
    fn parse_and_validate_ns() -> Result<()> {
        use tor_checkable::{SelfSigned, Timebound};
        let mut certs = Vec::new();
        for cert in AuthCert::parse_multiple(PLAIN_CERTS)? {
            let cert = cert?.check_signature()?.dangerously_assume_timely();
            certs.push(cert);
        }
        let auth_ids: Vec<_> = certs.iter().map(|c| c.id_fingerprint()).collect();
        assert_eq!(certs.len(), 4);
        let (_, _, consensus) = PlainConsensus::parse(PLAIN_CONSENSUS)?;
        let consensus = consensus.dangerously_assume_timely().set_n_authorities(3);
        // The set of authorities we know _could_ validate this cert.
        assert!(consensus.authorities_are_correct(&auth_ids));
        // A subset would also work.
        assert!(consensus.authorities_are_correct(&auth_ids[0..1]));
        assert!(consensus.key_is_correct(&certs).is_ok());
        let _consensus = consensus.check_signature(&certs)?;
        Ok(())
    }
    #[test]
    #[cfg(all(feature = "ns-vote", feature = "parse2"))]
    fn parse2_vote() -> anyhow::Result<()> {
        let file = "testdata2/v3-status-votes--1";
        let text = fs::read_to_string(file)?;
        // TODO replace the poc struct here when we have parsing of proper whole votes
        use crate::parse2::poc::netstatus::NetworkStatusUnverifiedVote;
        let input = ParseInput::new(&text, file);
        let doc: NetworkStatusUnverifiedVote = parse_netdoc(&input)?;
        println!("{doc:?}");
        println!("{:#?}", doc.inspect_unverified().0.r[0]);
        Ok(())
    }
    #[test]
    fn test_bad() {
        use crate::Pos;
        fn check(fname: &str, e: &Error) {
            let content = read_bad(fname);
            let res = MdConsensus::parse(&content);
            assert!(res.is_err());
            assert_eq!(&res.err().unwrap(), e);
        }
        check(
            "bad-flags",
            &EK::BadArgument
                .at_pos(Pos::from_line(27, 1))
                .with_msg("Flags out of order"),
        );
        check(
            "bad-md-digest",
            &EK::BadArgument
                .at_pos(Pos::from_line(40, 3))
                .with_msg("Invalid base64"),
        );
        check(
            "bad-weight",
            &EK::BadArgument
                .at_pos(Pos::from_line(67, 141))
                .with_msg("invalid digit found in string"),
        );
        check(
            "bad-weights",
            &EK::BadArgument
                .at_pos(Pos::from_line(51, 13))
                .with_msg("invalid digit found in string"),
        );
        check(
            "wrong-order",
            &EK::WrongSortOrder.at_pos(Pos::from_line(52, 1)),
        );
        check(
            "wrong-start",
            &EK::UnexpectedToken
                .with_msg("vote-status")
                .at_pos(Pos::from_line(1, 1)),
        );
        check("wrong-version", &EK::BadDocumentVersion.with_msg("10"));
    }
    fn gettok(s: &str) -> Result<Item<'_, NetstatusKwd>> {
        let mut reader = NetDocReader::new(s)?;
        let tok = reader.next().unwrap();
        assert!(reader.next().is_none());
        tok
    }
    #[test]
    fn test_weight() {
        let w = gettok("w Unmeasured=1 Bandwidth=6\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(!w.is_measured());
        assert!(w.is_nonzero());
        let w = gettok("w Bandwidth=10\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(w.is_measured());
        assert!(w.is_nonzero());
        let w = RelayWeight::default();
        assert!(!w.is_measured());
        assert!(!w.is_nonzero());
        let w = gettok("w Mustelid=66 Cheato=7 Unmeasured=1\n").unwrap();
        let w = RelayWeight::from_item(&w).unwrap();
        assert!(!w.is_measured());
        assert!(!w.is_nonzero());
        let w = gettok("r foo\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
        let w = gettok("r Bandwidth=6 Unmeasured=Frog\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
        let w = gettok("r Bandwidth=6 Unmeasured=3\n").unwrap();
        let w = RelayWeight::from_item(&w);
        assert!(w.is_err());
    }
    #[test]
    fn test_netparam() {
        let p = "Hello=600 Goodbye=5 Fred=7"
            .parse::<NetParams<u32>>()
            .unwrap();
        assert_eq!(p.get("Hello"), Some(&600_u32));
        let p = "Hello=Goodbye=5 Fred=7".parse::<NetParams<u32>>();
        assert!(p.is_err());
        let p = "Hello=Goodbye Fred=7".parse::<NetParams<u32>>();
        assert!(p.is_err());
    }
    #[test]
    fn test_sharedrand() {
        let sr =
            gettok("shared-rand-previous-value 9 5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4\n")
                .unwrap();
        let sr = SharedRandStatus::from_item(&sr).unwrap();
        assert_eq!(sr.n_reveals, 9);
        assert_eq!(
            sr.value.0,
            hex!("e4ba1d638c96c458532adc6957dc0080d03d37c7e5854087d0da90bf5ff4e72e")
        );
        assert!(sr.timestamp.is_none());
        let sr2 = gettok(
            "shared-rand-current-value 9 \
                    5LodY4yWxFhTKtxpV9wAgNA9N8flhUCH0NqQv1/05y4 2022-01-20T12:34:56\n",
        )
        .unwrap();
        let sr2 = SharedRandStatus::from_item(&sr2).unwrap();
        assert_eq!(sr2.n_reveals, sr.n_reveals);
        assert_eq!(sr2.value.0, sr.value.0);
        assert_eq!(
            sr2.timestamp.unwrap().0,
            humantime::parse_rfc3339("2022-01-20T12:34:56Z").unwrap()
        );
        let sr = gettok("foo bar\n").unwrap();
        let sr = SharedRandStatus::from_item(&sr);
        assert!(sr.is_err());
    }
    #[test]
    fn test_protostatus() {
        let my_protocols: Protocols = "Link=7 Cons=1-5 Desc=3-10".parse().unwrap();
        let outcome = ProtoStatus {
            recommended: "Link=7".parse().unwrap(),
            required: "Desc=5".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert!(outcome.is_ok());
        let outcome = ProtoStatus {
            recommended: "Microdesc=4 Link=7".parse().unwrap(),
            required: "Desc=5".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert_eq!(
            outcome,
            Err(ProtocolSupportError::MissingRecommended(
                "Microdesc=4".parse().unwrap()
            ))
        );
        let outcome = ProtoStatus {
            recommended: "Microdesc=4 Link=7".parse().unwrap(),
            required: "Desc=5 Cons=5-12 Wombat=15".parse().unwrap(),
        }
        .check_protocols(&my_protocols);
        assert_eq!(
            outcome,
            Err(ProtocolSupportError::MissingRequired(
                "Cons=6-12 Wombat=15".parse().unwrap()
            ))
        );
    }
    #[test]
    fn serialize_protostatus() {
        let ps = ProtoStatuses {
            client: ProtoStatus {
                recommended: "Link=1-5 LinkAuth=2-5".parse().unwrap(),
                required: "Link=5 LinkAuth=3".parse().unwrap(),
            },
            relay: ProtoStatus {
                recommended: "Wombat=20-30 Knish=20-30".parse().unwrap(),
                required: "Wombat=20-22 Knish=25-27".parse().unwrap(),
            },
        };
        let json = serde_json::to_string(&ps).unwrap();
        let ps2 = serde_json::from_str(json.as_str()).unwrap();
        assert_eq!(ps, ps2);
        let ps3: ProtoStatuses = serde_json::from_str(
            r#"{
            "client":{
                "required":"Link=5 LinkAuth=3",
                "recommended":"Link=1-5 LinkAuth=2-5"
            },
            "relay":{
                "required":"Wombat=20-22 Knish=25-27",
                "recommended":"Wombat=20-30 Knish=20-30"
            }
        }"#,
        )
        .unwrap();
        assert_eq!(ps, ps3);
    }
}