1
//! Parsing implementation for networkstatus documents.
2
//!
3
//! In Tor, a networkstatus documents describes a complete view of the
4
//! relays in the network: how many there are, how to contact them,
5
//! and so forth.
6
//!
7
//! A networkstatus document can either be a "votes" -- an authority's
8
//! view of the network, used as input to the voting process -- or a
9
//! "consensus" -- a combined view of the network based on multiple
10
//! authorities' votes, and signed by multiple authorities.
11
//!
12
//! A consensus document can itself come in two different flavors: a
13
//! plain (unflavoured) consensus has references to router descriptors, and
14
//! a "microdesc"-flavored consensus ("md") has references to
15
//! microdescriptors.
16
//!
17
//! To keep an up-to-date view of the network, clients download
18
//! microdescriptor-flavored consensuses periodically, and then
19
//! download whatever microdescriptors the consensus lists that the
20
//! client doesn't already have.
21
//!
22
//! For full information about the network status format, see
23
//! [dir-spec.txt](https://spec.torproject.org/dir-spec).
24
//!
25
//! # Limitations
26
//!
27
//! NOTE: The consensus format has changes time, using a
28
//! "consensus-method" mechanism.  This module is does not yet handle all
29
//! all historical consensus-methods.
30
//!
31
//! NOTE: This module _does_ parse some fields that are not in current
32
//! use, like relay nicknames, and the "published" times on
33
//! microdescriptors. We should probably decide whether we actually
34
//! want to do this.
35
//!
36
//! TODO: This module doesn't implement vote parsing at all yet.
37
//!
38
//! TODO: This module doesn't implement plain consensuses.
39
//!
40
//! TODO: More testing is needed!
41
//!
42
//! TODO: There should be accessor functions for most of the fields here.
43
//! As with the other tor-netdoc types, I'm deferring those till I know what
44
//! they should be.
45

            
46
mod rs;
47

            
48
pub mod md;
49
#[cfg(feature = "plain-consensus")]
50
pub mod plain;
51
#[cfg(feature = "ns-vote")]
52
pub mod vote;
53

            
54
#[cfg(feature = "build_docs")]
55
mod build;
56

            
57
#[cfg(feature = "parse2")]
58
use {
59
    crate::parse2::{self, ArgumentStream}, //
60
};
61

            
62
#[cfg(feature = "parse2")]
63
pub use {
64
    parse2_impls::ProtoStatusesNetdocParseAccumulator, //
65
};
66

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

            
83
use derive_deftly::{Deftly, define_derive_deftly};
84
use digest::Digest;
85
use std::sync::LazyLock;
86
use tor_checkable::{ExternallySigned, timed::TimerangeBound};
87
use tor_llcrypto as ll;
88
use tor_llcrypto::pk::rsa::RsaIdentity;
89

            
90
use serde::{Deserialize, Deserializer};
91

            
92
#[cfg(feature = "build_docs")]
93
pub use build::MdConsensusBuilder;
94
#[cfg(all(feature = "build_docs", feature = "plain-consensus"))]
95
pub use build::PlainConsensusBuilder;
96
#[cfg(feature = "build_docs")]
97
ns_export_each_flavor! {
98
    ty: RouterStatusBuilder;
99
}
100

            
101
ns_export_each_variety! {
102
    ty: RouterStatus, Preamble;
103
}
104

            
105
#[deprecated]
106
#[cfg(feature = "ns_consensus")]
107
pub use PlainConsensus as NsConsensus;
108
#[deprecated]
109
#[cfg(feature = "ns_consensus")]
110
pub use PlainRouterStatus as NsRouterStatus;
111
#[deprecated]
112
#[cfg(feature = "ns_consensus")]
113
pub use UncheckedPlainConsensus as UncheckedNsConsensus;
114
#[deprecated]
115
#[cfg(feature = "ns_consensus")]
116
pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
117

            
118
#[cfg(feature = "ns-vote")]
119
pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
120

            
121
/// `publiscation` field in routerstatus entry intro item other than in votes
122
///
123
/// Two arguments which are both ignored.
124
/// This used to be an ISO8601 timestamp in anomalous two-argument format.
125
///
126
/// Nowadays, according to the spec, it can be a dummy value.
127
/// So it can be a unit type.
128
///
129
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:r>,
130
/// except in votes which use [`Iso8601TimeSp`] instead.
131
///
132
/// **Not the same as** the `published` item:
133
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:published>
134
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
135
#[allow(clippy::exhaustive_structs)]
136
pub struct IgnoredPublicationTimeSp;
137

            
138
/// The lifetime of a networkstatus document.
139
///
140
/// In a consensus, this type describes when the consensus may safely
141
/// be used.  In a vote, this type describes the proposed lifetime for a
142
/// consensus.
143
///
144
/// Aggregate of three netdoc preamble fields.
145
#[derive(Clone, Debug, Deftly)]
146
#[derive_deftly(Lifetime)]
147
#[cfg_attr(feature = "parse2", derive_deftly(NetdocParseableFields))]
148
pub struct Lifetime {
149
    /// `valid-after` --- Time at which the document becomes valid
150
    ///
151
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:published>
152
    ///
153
    /// (You might see a consensus a little while before this time,
154
    /// since voting tries to finish up before the.)
155
    #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
156
    valid_after: Iso8601TimeSp,
157
    /// `fresh-until` --- Time after which there is expected to be a better version
158
    /// of this consensus
159
    ///
160
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:published>
161
    ///
162
    /// You can use the consensus after this time, but there is (or is
163
    /// supposed to be) a better one by this point.
164
    #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
165
    fresh_until: Iso8601TimeSp,
166
    /// `valid-until` --- Time after which this consensus is expired.
167
    ///
168
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:published>
169
    ///
170
    /// You should try to get a better consensus after this time,
171
    /// though it's okay to keep using this one if no more recent one
172
    /// can be found.
173
    #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
174
    valid_until: Iso8601TimeSp,
175
}
176

            
177
define_derive_deftly! {
178
    /// Bespoke derive for `Lifetime`, for `new` and accessors
179
    Lifetime:
180

            
181
    impl Lifetime {
182
        /// Construct a new Lifetime.
183
12807
        pub fn new(
184
12807
            $( $fname: time::SystemTime, )
185
12807
        ) -> Result<Self> {
186
            // Make this now because otherwise literal `valid_after` here in the body
187
            // has the wrong span - the compiler refuses to look at the argument.
188
            // But we can refer to the field names.
189
            let self_ = Lifetime {
190
                $( $fname: $fname.into(), )
191
            };
192
            if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
193
                Ok(self_)
194
            } else {
195
                Err(EK::InvalidLifetime.err())
196
            }
197
        }
198
      $(
199
        ${fattrs doc}
200
155134
        pub fn $fname(&self) -> time::SystemTime {
201
            *self.$fname
202
        }
203
      )
204
        /// Return true if this consensus is officially valid at the provided time.
205
490
        pub fn valid_at(&self, when: time::SystemTime) -> bool {
206
            *self.valid_after <= when && when <= *self.valid_until
207
        }
208

            
209
        /// Return the voting period implied by this lifetime.
210
        ///
211
        /// (The "voting period" is the amount of time in between when a consensus first
212
        /// becomes valid, and when the next consensus is expected to become valid)
213
49196
        pub fn voting_period(&self) -> time::Duration {
214
            let valid_after = self.valid_after();
215
            let fresh_until = self.fresh_until();
216
            fresh_until
217
                .duration_since(valid_after)
218
                .expect("Mis-formed lifetime")
219
        }
220
    }
221
}
222
use derive_deftly_template_Lifetime;
223

            
224
/// A single consensus method
225
///
226
/// These are integers, but we don't do arithmetic on them.
227
///
228
/// As defined here:
229
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-methods>
230
/// <https://spec.torproject.org/dir-spec/computing-consensus.html#flavor:microdesc>
231
///
232
/// As used in a `consensus-method` item:
233
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-method>
234
#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] //
235
#[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
236
pub struct ConsensusMethod(u32);
237
impl NormalItemArgument for ConsensusMethod {}
238

            
239
/// A set of consensus methods
240
///
241
/// Implements `ItemValueParseable` as required for `consensus-methods`,
242
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-methods>
243
///
244
/// There is also [`consensus_methods_comma_separated`] for `m` lines in votes.
245
#[derive(Debug, Clone, Default, Eq, PartialEq)]
246
#[cfg_attr(feature = "parse2", derive(Deftly), derive_deftly(ItemValueParseable))]
247
#[non_exhaustive]
248
pub struct ConsensusMethods {
249
    /// Consensus methods.
250
    pub methods: BTreeSet<ConsensusMethod>,
251
}
252

            
253
/// Module for use with parse2's `with`, to parse one argument of comma-separated consensus methods
254
///
255
/// As found in an `m` item in a vote:
256
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:m>
257
#[cfg(feature = "parse2")]
258
pub mod consensus_methods_comma_separated {
259
    use super::*;
260
    use parse2::ArgumentError as AE;
261
    use std::result::Result;
262

            
263
    /// Parse
264
24
    pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
265
24
        let mut methods = BTreeSet::new();
266
72
        for ent in args.next().ok_or(AE::Missing)?.split(',') {
267
72
            let ent = ent.parse().map_err(|_| AE::Invalid)?;
268
72
            if !methods.insert(ent) {
269
                return Err(AE::Invalid);
270
72
            }
271
        }
272
24
        Ok(ConsensusMethods { methods })
273
24
    }
274
}
275

            
276
/// A set of named network parameters.
277
///
278
/// These are used to describe current settings for the Tor network,
279
/// current weighting parameters for path selection, and so on.  They're
280
/// encoded with a space-separated K=V format.
281
///
282
/// A `NetParams<i32>` is part of the validated directory manager configuration,
283
/// where it is built (in the builder-pattern sense) from a transparent HashMap.
284
///
285
/// As found in `params` in a network status:
286
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:params>
287
///
288
/// The same syntax is also used, and this type used for parsing, in various other places,
289
/// for example routerstatus entry `w` items (bandwith weights):
290
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:w>
291
#[derive(Debug, Clone, Default, Eq, PartialEq)]
292
pub struct NetParams<T> {
293
    /// Map from keys to values.
294
    params: HashMap<String, T>,
295
}
296

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

            
319
impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
320
6538
    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
321
        NetParams {
322
6585
            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
323
        }
324
6538
    }
325
}
326

            
327
impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
328
3030
    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
329
3030
        self.params.extend(iter);
330
3030
    }
331
}
332

            
333
impl<'de, T> Deserialize<'de> for NetParams<T>
334
where
335
    T: Deserialize<'de>,
336
{
337
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
338
    where
339
        D: Deserializer<'de>,
340
    {
341
        let params = HashMap::deserialize(deserializer)?;
342
        Ok(NetParams { params })
343
    }
344
}
345

            
346
/// A list of subprotocol versions that implementors should/must provide.
347
///
348
/// This struct represents a pair of (optional) items:
349
/// `recommended-FOO-protocols` and `required-FOO-protocols`.
350
///
351
/// Each consensus has two of these: one for relays, and one for clients.
352
///
353
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:required-relay-protocols>
354
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
355
pub struct ProtoStatus {
356
    /// Set of protocols that are recommended; if we're missing a protocol
357
    /// in this list we should warn the user.
358
    ///
359
    /// `recommended-client-protocols` or `recommended-relay-protocols`
360
    recommended: Protocols,
361
    /// Set of protocols that are required; if we're missing a protocol
362
    /// in this list we should refuse to start.
363
    ///
364
    /// `required-client-protocols` or `required-relay-protocols`
365
    required: Protocols,
366
}
367

            
368
impl ProtoStatus {
369
    /// Check whether the list of supported protocols
370
    /// is sufficient to satisfy this list of recommendations and requirements.
371
    ///
372
    /// If any required protocol is missing, returns [`ProtocolSupportError::MissingRequired`].
373
    ///
374
    /// Otherwise, if no required protocol is missing, but some recommended protocol is missing,
375
    /// returns [`ProtocolSupportError::MissingRecommended`].
376
    ///
377
    /// Otherwise, if no recommended or required protocol is missing, returns `Ok(())`.
378
202
    pub fn check_protocols(
379
202
        &self,
380
202
        supported_protocols: &Protocols,
381
202
    ) -> StdResult<(), ProtocolSupportError> {
382
        // Required protocols take precedence, so we check them first.
383
202
        let missing_required = self.required.difference(supported_protocols);
384
202
        if !missing_required.is_empty() {
385
100
            return Err(ProtocolSupportError::MissingRequired(missing_required));
386
102
        }
387
102
        let missing_recommended = self.recommended.difference(supported_protocols);
388
102
        if !missing_recommended.is_empty() {
389
51
            return Err(ProtocolSupportError::MissingRecommended(
390
51
                missing_recommended,
391
51
            ));
392
51
        }
393

            
394
51
        Ok(())
395
202
    }
396
}
397

            
398
/// A subprotocol that is recommended or required in the consensus was not present.
399
#[derive(Clone, Debug, thiserror::Error)]
400
#[cfg_attr(test, derive(PartialEq))]
401
#[non_exhaustive]
402
pub enum ProtocolSupportError {
403
    /// At least one required protocol was not in our list of supported protocols.
404
    #[error("Required protocols are not implemented: {0}")]
405
    MissingRequired(Protocols),
406

            
407
    /// At least one recommended protocol was not in our list of supported protocols.
408
    ///
409
    /// Also implies that no _required_ protocols were missing.
410
    #[error("Recommended protocols are not implemented: {0}")]
411
    MissingRecommended(Protocols),
412
}
413

            
414
impl ProtocolSupportError {
415
    /// Return true if the suggested behavior for this error is a shutdown.
416
    pub fn should_shutdown(&self) -> bool {
417
        matches!(self, Self::MissingRequired(_))
418
    }
419
}
420

            
421
impl HasKind for ProtocolSupportError {
422
    fn kind(&self) -> tor_error::ErrorKind {
423
        tor_error::ErrorKind::SoftwareDeprecated
424
    }
425
}
426

            
427
/// A set of recommended and required protocols when running
428
/// in various scenarios.
429
///
430
/// Represents the collection of four items: `{recommended,required}-{client,relay}-protocols`.
431
///
432
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:required-relay-protocols>
433
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
434
pub struct ProtoStatuses {
435
    /// Lists of recommended and required subprotocol versions for clients
436
    client: ProtoStatus,
437
    /// Lists of recommended and required subprotocol versions for relays
438
    relay: ProtoStatus,
439
}
440

            
441
impl ProtoStatuses {
442
    /// Return the list of recommended and required protocols for running as a client.
443
196
    pub fn client(&self) -> &ProtoStatus {
444
196
        &self.client
445
196
    }
446

            
447
    /// Return the list of recommended and required protocols for running as a relay.
448
    pub fn relay(&self) -> &ProtoStatus {
449
        &self.relay
450
    }
451
}
452

            
453
/// A recognized 'flavor' of consensus document.
454
///
455
/// The enum is exhaustive because the addition/removal of a consensus flavor
456
/// should indeed be a breaking change, as it would inevitable require
457
/// interfacing code to think about the handling of it.
458
///
459
/// <https://spec.torproject.org/dir-spec/computing-consensus.html#flavors>
460
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
461
#[allow(clippy::exhaustive_enums)]
462
pub enum ConsensusFlavor {
463
    /// A "microdesc"-flavored consensus.  This is the one that
464
    /// clients and relays use today.
465
    Microdesc,
466
    /// A "networkstatus"-flavored consensus.  It's used for
467
    /// historical and network-health purposes.  Instead of listing
468
    /// microdescriptor digests, it lists digests of full relay
469
    /// descriptors.
470
    Plain,
471
}
472

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

            
496
/// The signature of a single directory authority on a networkstatus document.
497
#[derive(Debug, Clone)]
498
#[non_exhaustive]
499
pub struct Signature {
500
    /// The name of the digest algorithm used to make the signature.
501
    ///
502
    /// Currently sha1 and sh256 are recognized.  Here we only support
503
    /// sha256.
504
    pub digestname: String,
505
    /// Fingerprints of the keys for the authority that made
506
    /// this signature.
507
    pub key_ids: AuthCertKeyIds,
508
    /// The signature itself.
509
    pub signature: Vec<u8>,
510
}
511

            
512
/// A collection of signatures that can be checked on a networkstatus document
513
#[derive(Debug, Clone)]
514
#[non_exhaustive]
515
pub struct SignatureGroup {
516
    /// The sha256 of the document itself
517
    pub sha256: Option<[u8; 32]>,
518
    /// The sha1 of the document itself
519
    pub sha1: Option<[u8; 20]>,
520
    /// The signatures listed on the document.
521
    pub signatures: Vec<Signature>,
522
}
523

            
524
/// A shared random value produced by the directory authorities.
525
#[derive(
526
    Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
527
)]
528
// (This doesn't need to use CtByteArray; we don't really need to compare these.)
529
pub struct SharedRandVal([u8; 32]);
530

            
531
/// A shared-random value produced by the directory authorities,
532
/// along with meta-information about that value.
533
#[derive(Debug, Clone, Deftly)]
534
#[non_exhaustive]
535
#[cfg_attr(feature = "parse2", derive_deftly(ItemValueParseable))]
536
#[cfg_attr(feature = "encode", derive_deftly(ItemValueEncodable))]
537
pub struct SharedRandStatus {
538
    /// How many authorities revealed shares that contributed to this value.
539
    pub n_reveals: u8,
540
    /// The current random value.
541
    ///
542
    /// The properties of the secure shared-random system guarantee
543
    /// that this value isn't predictable before it first becomes
544
    /// live, and that a hostile party could not have forced it to
545
    /// have any more than a small number of possible random values.
546
    pub value: SharedRandVal,
547

            
548
    /// The time when this SharedRandVal becomes (or became) the latest.
549
    ///
550
    /// (This is added per proposal 342, assuming that gets accepted.)
551
    pub timestamp: Option<Iso8601TimeNoSp>,
552
}
553

            
554
/// Description of an authority's identity and address.
555
///
556
/// (Corresponds to a dir-source line.)
557
#[derive(Debug, Clone)]
558
#[non_exhaustive]
559
pub struct DirSource {
560
    /// human-readable nickname for this authority.
561
    pub nickname: String,
562
    /// Fingerprint for the _authority_ identity key of this
563
    /// authority.
564
    ///
565
    /// This is the same key as the one that signs the authority's
566
    /// certificates.
567
    pub identity: RsaIdentity,
568
    /// IP address for the authority
569
    pub ip: net::IpAddr,
570
    /// HTTP directory port for this authority
571
    pub dir_port: u16,
572
    /// OR port for this authority.
573
    pub or_port: u16,
574
}
575

            
576
/// Recognized weight fields on a single relay in a consensus
577
#[non_exhaustive]
578
#[derive(Debug, Clone, Copy)]
579
pub enum RelayWeight {
580
    /// An unmeasured weight for a relay.
581
    Unmeasured(u32),
582
    /// An measured weight for a relay.
583
    Measured(u32),
584
}
585

            
586
impl RelayWeight {
587
    /// Return true if this weight is the result of a successful measurement
588
24265
    pub fn is_measured(&self) -> bool {
589
24265
        matches!(self, RelayWeight::Measured(_))
590
24265
    }
591
    /// Return true if this weight is nonzero
592
22893
    pub fn is_nonzero(&self) -> bool {
593
22893
        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
594
22893
    }
595
}
596

            
597
/// All information about a single authority, as represented in a consensus
598
#[derive(Debug, Clone)]
599
#[non_exhaustive]
600
pub struct ConsensusVoterInfo {
601
    /// Contents of the dirsource line about an authority
602
    pub dir_source: DirSource,
603
    /// Human-readable contact information about the authority
604
    pub contact: String,
605
    /// Digest of the vote that the authority cast to contribute to
606
    /// this consensus.
607
    pub vote_digest: Vec<u8>,
608
}
609

            
610
/// The signed footer of a consensus netstatus.
611
#[derive(Debug, Clone)]
612
#[non_exhaustive]
613
pub struct Footer {
614
    /// Weights to be applied to certain classes of relays when choosing
615
    /// for different roles.
616
    ///
617
    /// For example, we want to avoid choosing exits for non-exit
618
    /// roles when overall the proportion of exits is small.
619
    pub weights: NetParams<i32>,
620
}
621

            
622
/// A consensus document that lists relays along with their
623
/// microdescriptor documents.
624
pub type MdConsensus = md::Consensus;
625

            
626
/// An MdConsensus that has been parsed and checked for timeliness,
627
/// but not for signatures.
628
pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
629

            
630
/// An MdConsensus that has been parsed but not checked for signatures
631
/// and timeliness.
632
pub type UncheckedMdConsensus = md::UncheckedConsensus;
633

            
634
#[cfg(feature = "plain-consensus")]
635
/// A consensus document that lists relays along with their
636
/// router descriptor documents.
637
pub type PlainConsensus = plain::Consensus;
638

            
639
#[cfg(feature = "plain-consensus")]
640
/// An PlainConsensus that has been parsed and checked for timeliness,
641
/// but not for signatures.
642
pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
643

            
644
#[cfg(feature = "plain-consensus")]
645
/// An PlainConsensus that has been parsed but not checked for signatures
646
/// and timeliness.
647
pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
648

            
649
decl_keyword! {
650
    /// Keywords that can be used in votes and consensuses.
651
    // TODO: This is public because otherwise we can't use it in the
652
    // ParseRouterStatus crate.  But I'd rather find a way to make it
653
    // private.
654
    #[non_exhaustive]
655
    #[allow(missing_docs)]
656
    pub NetstatusKwd {
657
        // Header
658
        "network-status-version" => NETWORK_STATUS_VERSION,
659
        "vote-status" => VOTE_STATUS,
660
        "consensus-methods" => CONSENSUS_METHODS,
661
        "consensus-method" => CONSENSUS_METHOD,
662
        "published" => PUBLISHED,
663
        "valid-after" => VALID_AFTER,
664
        "fresh-until" => FRESH_UNTIL,
665
        "valid-until" => VALID_UNTIL,
666
        "voting-delay" => VOTING_DELAY,
667
        "client-versions" => CLIENT_VERSIONS,
668
        "server-versions" => SERVER_VERSIONS,
669
        "known-flags" => KNOWN_FLAGS,
670
        "flag-thresholds" => FLAG_THRESHOLDS,
671
        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
672
        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
673
        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
674
        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
675
        "params" => PARAMS,
676
        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
677
        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
678
        // "package" is now ignored.
679

            
680
        // header in consensus, voter section in vote?
681
        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
682
        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
683

            
684
        // Voter section (both)
685
        "dir-source" => DIR_SOURCE,
686
        "contact" => CONTACT,
687

            
688
        // voter section (vote, but not consensus)
689
        "legacy-dir-key" => LEGACY_DIR_KEY,
690
        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
691
        "shared-rand-commit" => SHARED_RAND_COMMIT,
692

            
693
        // voter section (consensus, but not vote)
694
        "vote-digest" => VOTE_DIGEST,
695

            
696
        // voter cert beginning (but only the beginning)
697
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
698

            
699
        // routerstatus
700
        "r" => RS_R,
701
        "a" => RS_A,
702
        "s" => RS_S,
703
        "v" => RS_V,
704
        "pr" => RS_PR,
705
        "w" => RS_W,
706
        "p" => RS_P,
707
        "m" => RS_M,
708
        "id" => RS_ID,
709

            
710
        // footer
711
        "directory-footer" => DIRECTORY_FOOTER,
712
        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
713
        "directory-signature" => DIRECTORY_SIGNATURE,
714
    }
715
}
716

            
717
/// Shared parts of rules for all kinds of netstatus headers
718
51
static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
719
    use NetstatusKwd::*;
720
51
    let mut rules = SectionRules::builder();
721
51
    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
722
51
    rules.add(VOTE_STATUS.rule().required().args(1..));
723
51
    rules.add(VALID_AFTER.rule().required());
724
51
    rules.add(FRESH_UNTIL.rule().required());
725
51
    rules.add(VALID_UNTIL.rule().required());
726
51
    rules.add(VOTING_DELAY.rule().args(2..));
727
51
    rules.add(CLIENT_VERSIONS.rule());
728
51
    rules.add(SERVER_VERSIONS.rule());
729
51
    rules.add(KNOWN_FLAGS.rule().required());
730
51
    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
731
51
    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
732
51
    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
733
51
    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
734
51
    rules.add(PARAMS.rule());
735
51
    rules
736
51
});
737
/// Rules for parsing the header of a consensus.
738
51
static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
739
    use NetstatusKwd::*;
740
51
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
741
51
    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
742
51
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
743
51
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
744
51
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
745
51
    rules.build()
746
51
});
747
/*
748
/// Rules for parsing the header of a vote.
749
static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
750
    use NetstatusKwd::*;
751
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
752
    rules.add(CONSENSUS_METHODS.rule().args(1..));
753
    rules.add(FLAG_THRESHOLDS.rule());
754
    rules.add(BANDWIDTH_FILE_HEADERS.rule());
755
    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
756
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
757
    rules
758
};
759
/// Rules for parsing a single voter's information in a vote.
760
static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
761
    use NetstatusKwd::*;
762
    let mut rules = SectionRules::new();
763
    rules.add(DIR_SOURCE.rule().required().args(6..));
764
    rules.add(CONTACT.rule().required());
765
    rules.add(LEGACY_DIR_KEY.rule().args(1..));
766
    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
767
    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
768
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
769
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
770
    // then comes an entire cert: When we implement vote parsing,
771
    // we should use the authcert code for handling that.
772
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
773
    rules
774
};
775
 */
776
/// Rules for parsing a single voter's information in a consensus
777
51
static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
778
    use NetstatusKwd::*;
779
51
    let mut rules = SectionRules::builder();
780
51
    rules.add(DIR_SOURCE.rule().required().args(6..));
781
51
    rules.add(CONTACT.rule().required());
782
51
    rules.add(VOTE_DIGEST.rule().required());
783
51
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
784
51
    rules.build()
785
51
});
786
/// Shared rules for parsing a single routerstatus
787
static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
788
51
    LazyLock::new(|| {
789
        use NetstatusKwd::*;
790
51
        let mut rules = SectionRules::builder();
791
51
        rules.add(RS_A.rule().may_repeat().args(1..));
792
51
        rules.add(RS_S.rule().required());
793
51
        rules.add(RS_V.rule());
794
51
        rules.add(RS_PR.rule().required());
795
51
        rules.add(RS_W.rule());
796
51
        rules.add(RS_P.rule().args(2..));
797
51
        rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
798
51
        rules
799
51
    });
800

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

            
809
/*
810
/// Rules for parsing a single routerstatus in a vote
811
static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
812
    use NetstatusKwd::*;
813
        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
814
        rules.add(RS_R.rule().required().args(8..));
815
        rules.add(RS_M.rule().may_repeat().args(2..));
816
        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
817
        rules
818
    };
819
*/
820
/// Rules for parsing a single routerstatus in a microdesc consensus
821
51
static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
822
    use NetstatusKwd::*;
823
51
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
824
51
    rules.add(RS_R.rule().required().args(6..));
825
51
    rules.add(RS_M.rule().required().args(1..));
826
51
    rules.build()
827
51
});
828
/// Rules for parsing consensus fields from a footer.
829
51
static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
830
    use NetstatusKwd::*;
831
51
    let mut rules = SectionRules::builder();
832
51
    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
833
    // consensus only
834
51
    rules.add(BANDWIDTH_WEIGHTS.rule());
835
51
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
836
51
    rules.build()
837
51
});
838

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

            
857
714
        let recommended = parse(sec.get(recommend_token))?;
858
714
        let required = parse(sec.get(required_token))?;
859
714
        Ok(ProtoStatus {
860
714
            recommended,
861
714
            required,
862
714
        })
863
714
    }
864

            
865
    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
866
    ///
867
    /// Implementations may assume that relays on the network implement all the
868
    /// protocols in the relays' required-protocols list.  Implementations should
869
    /// refuse to start if they do not implement all the protocols on their own
870
    /// (client or relay) required-protocols list.
871
147
    pub fn required_protocols(&self) -> &Protocols {
872
147
        &self.required
873
147
    }
874

            
875
    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
876
    ///
877
    /// Implementations should warn if they do not implement all the protocols
878
    /// on their own (client or relay) recommended-protocols list.
879
    pub fn recommended_protocols(&self) -> &Protocols {
880
        &self.recommended
881
    }
882
}
883

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

            
911
15318
        let params = s
912
15318
            .split(' ')
913
27809
            .filter(|p| !p.is_empty())
914
15318
            .map(parse_pair)
915
15318
            .collect::<Result<HashMap<_, _>>>()?;
916
15310
        Ok(NetParams { params })
917
15318
    }
918
}
919

            
920
impl FromStr for SharedRandVal {
921
    type Err = Error;
922
4
    fn from_str(s: &str) -> Result<Self> {
923
4
        let val: B64 = s.parse()?;
924
4
        let val = SharedRandVal(val.into_array()?);
925
4
        Ok(val)
926
4
    }
927
}
928
impl Display for SharedRandVal {
929
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
930
        Display::fmt(&B64::from(Vec::from(self.0)), f)
931
    }
932
}
933
impl NormalItemArgument for SharedRandVal {}
934

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

            
960
    /// Return the actual shared random value.
961
784
    pub fn value(&self) -> &SharedRandVal {
962
784
        &self.value
963
784
    }
964

            
965
    /// Return the timestamp (if any) associated with this `SharedRandValue`.
966
1470
    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
967
1470
        self.timestamp.map(|t| t.0)
968
1470
    }
969
}
970

            
971
impl DirSource {
972
    /// Parse a "dir-source" item
973
1073
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
974
1073
        if item.kwd() != NetstatusKwd::DIR_SOURCE {
975
            return Err(
976
                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
977
                    .at_pos(item.pos()),
978
            );
979
1073
        }
980
1073
        let nickname = item.required_arg(0)?.to_string();
981
1073
        let identity = item.parse_arg::<Fingerprint>(1)?.into();
982
1073
        let ip = item.parse_arg(3)?;
983
1073
        let dir_port = item.parse_arg(4)?;
984
1073
        let or_port = item.parse_arg(5)?;
985

            
986
1073
        Ok(DirSource {
987
1073
            nickname,
988
1073
            identity,
989
1073
            ip,
990
1073
            dir_port,
991
1073
            or_port,
992
1073
        })
993
1073
    }
994
}
995

            
996
impl ConsensusVoterInfo {
997
    /// Parse a single ConsensusVoterInfo from a voter info section.
998
1073
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusVoterInfo> {
999
        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)]
1073
        let first = sec.first_item().unwrap();
1073
        if first.kwd() != DIR_SOURCE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} at start of voter info",
                first.kwd()
            ))
            .at_pos(first.pos()));
1073
        }
1073
        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1073
        let contact = sec.required(CONTACT)?.args_as_str().to_string();
1073
        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16>(0)?.into();
1073
        Ok(ConsensusVoterInfo {
1073
            dir_source,
1073
            contact,
1073
            vote_digest,
1073
        })
1073
    }
}
impl Default for RelayWeight {
2
    fn default() -> RelayWeight {
2
        RelayWeight::Unmeasured(0)
2
    }
}
impl RelayWeight {
    /// Parse a routerweight from a "w" line.
1934
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
1934
        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
            );
1928
        }
1928
        let params = item.args_as_str().parse()?;
1926
        Self::from_net_params(&params).map_err(|e| e.at_pos(item.pos()))
1934
    }
    /// 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`.
1962
    fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
1962
        let bw = params.params.get("Bandwidth");
1962
        let unmeas = params.params.get("Unmeasured");
1962
        let bw = match bw {
2
            None => return Ok(RelayWeight::Unmeasured(0)),
1960
            Some(b) => *b,
        };
1960
        match unmeas {
14
            None | Some(0) => Ok(RelayWeight::Measured(bw)),
1946
            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
            _ => Err(EK::BadArgument.with_msg("unmeasured value")),
        }
1962
    }
}
/// `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 {
36
        fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
36
            item.check_no_object()?;
36
            (|| {
36
                let params = item.args_copy().into_remaining().parse()?;
36
                Self::from_net_params(&params)
            })()
36
            .map_err(item.invalid_argument_handler("weights"))
36
        }
    }
    impl ItemValueParseable for rs::SoftwareVersion {
36
        fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
36
            item.check_no_object()?;
36
            item.args_mut()
36
                .into_remaining()
36
                .parse()
36
                .map_err(item.invalid_argument_handler("version"))
36
        }
    }
    impl ItemArgumentParseable for IgnoredPublicationTimeSp {
24
        fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
60
            let mut next_arg = || a.next().ok_or(AE::Missing);
24
            let _: &str = next_arg()?;
24
            let _: &str = next_arg()?;
24
            Ok(IgnoredPublicationTimeSp)
24
        }
    }
}
impl Footer {
    /// Parse a directory footer from a footer section.
349
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
349
        sec.required(DIRECTORY_FOOTER)?;
349
        let weights = sec
349
            .maybe(BANDWIDTH_WEIGHTS)
349
            .args_as_str()
349
            .unwrap_or("")
349
            .parse()?;
347
        Ok(Footer { weights })
349
    }
}
/// 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
1043
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1043
        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} for directory signature",
                item.kwd()
            ))
            .at_pos(item.pos()));
1043
        }
1043
        let (alg, id_fp, sk_fp) = if item.n_args() > 2 {
            (
1035
                item.required_arg(0)?,
1035
                item.required_arg(1)?,
1035
                item.required_arg(2)?,
            )
        } else {
8
            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
        };
1043
        let digestname = alg.to_string();
1043
        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1043
        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1043
        let key_ids = AuthCertKeyIds {
1043
            id_fingerprint,
1043
            sk_fingerprint,
1043
        };
1043
        let signature = item.obj("SIGNATURE")?;
1043
        Ok(Signature {
1043
            digestname,
1043
            key_ids,
1043
            signature,
1043
        })
1043
    }
    /// Return true if this signature has the identity key and signing key
    /// that match a given cert.
747
    fn matches_cert(&self, cert: &AuthCert) -> bool {
747
        cert.key_ids() == self.key_ids
747
    }
    /// If possible, find the right certificate for checking this signature
    /// from among a slice of certificates.
1087
    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1181
        certs.iter().find(|&c| self.matches_cert(c))
1087
    }
    /// 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.
167
    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
167
        match self.find_cert(certs) {
53
            None => SigCheckResult::MissingCert,
114
            Some(cert) => {
114
                let key = cert.signing_key();
114
                match key.verify(signed_digest, &self.signature[..]) {
114
                    Ok(()) => SigCheckResult::Valid,
                    Err(_) => SigCheckResult::Invalid,
                }
            }
        }
167
    }
}
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.
306
    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
306
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
306
        let mut missing = Vec::new();
1226
        for sig in &self.signatures {
920
            let id_fingerprint = &sig.key_ids.id_fingerprint;
920
            if ok.contains(id_fingerprint) {
                continue;
920
            }
920
            if sig.find_cert(certs).is_some() {
171
                ok.insert(*id_fingerprint);
171
                continue;
749
            }
749
            missing.push(sig);
        }
306
        (ok.len(), missing)
306
    }
    /// Given a list of authority identity key fingerprints, return true if
    /// this signature group is _potentially_ well-signed according to those
    /// authorities.
255
    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
255
        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
1024
        for sig in &self.signatures {
769
            let id_fp = &sig.key_ids.id_fingerprint;
769
            if signed_by.contains(id_fp) {
                // Already found this in the list.
                continue;
769
            }
769
            if authorities.contains(&id_fp) {
410
                signed_by.insert(*id_fp);
410
            }
        }
255
        signed_by.len() > (authorities.len() / 2)
255
    }
    /// 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.
55
    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.
55
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
222
        for sig in &self.signatures {
167
            let id_fingerprint = &sig.key_ids.id_fingerprint;
167
            if ok.contains(id_fingerprint) {
                // We already checked at least one signature using this
                // authority's identity fingerprint.
                continue;
167
            }
167
            let d: Option<&[u8]> = match sig.digestname.as_ref() {
176
                "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.
            };
167
            if d.is_none() {
                // We don't support this kind of digest for this kind
                // of document.
                continue;
167
            }
            // Unwrap should be safe because of above `d.is_none()` check
            #[allow(clippy::unwrap_used)]
167
            match sig.check_signature(d.as_ref().unwrap(), certs) {
114
                SigCheckResult::Valid => {
114
                    ok.insert(*id_fingerprint);
114
                }
53
                _ => continue,
            }
        }
55
        ok.len() > (n_authorities / 2)
55
    }
}
#[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::{NetdocSigned 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::NetworkStatusSignedVote;
        let input = ParseInput::new(&text, file);
        let doc: NetworkStatusSignedVote = 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);
    }
}