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 dir_source;
52
mod rs;
53

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

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

            
62
pub use proto_statuses_parse2_encode::ProtoStatusesNetdocParseAccumulator;
63

            
64
#[cfg(feature = "incomplete")]
65
use crate::doc::authcert::EncodedAuthCert;
66

            
67
use crate::doc::authcert::{self, AuthCert, AuthCertKeyIds};
68
use crate::encode::{
69
    ItemArgument, ItemEncoder, ItemValueEncodable, NetdocEncodable, NetdocEncoder,
70
};
71
use crate::parse::keyword::Keyword;
72
use crate::parse::parser::{Section, SectionRules, SectionRulesBuilder};
73
use crate::parse::tokenize::{Item, ItemResult, NetDocReader};
74
use crate::parse2::{
75
    self, ArgumentError, ArgumentStream, ErrorProblem, IsStructural, ItemArgumentParseable,
76
    ItemStream, ItemValueParseable, KeywordRef, NetdocParseable, SignatureHashInputs,
77
    SignatureItemParseable, StopAt, UnparsedItem,
78
};
79
use crate::types::relay_flags::{self, DocRelayFlags};
80
use crate::types::{self, *};
81
use crate::util::PeekableIterator;
82
use crate::{Error, KeywordEncodable, NetdocErrorKind as EK, NormalItemArgument, Pos, Result};
83
use std::collections::{BTreeSet, HashMap, HashSet};
84
use std::fmt::{self, Display};
85
use std::result::Result as StdResult;
86
use std::str::FromStr;
87
use std::sync::Arc;
88
use std::{net, result, time};
89
use tor_error::{Bug, HasKind, bad_api_usage, internal};
90
use tor_protover::Protocols;
91
use void::ResultVoidExt as _;
92

            
93
use derive_deftly::{Deftly, define_derive_deftly};
94
use digest::Digest;
95
use std::sync::LazyLock;
96
use tor_checkable::{ExternallySigned, timed::TimerangeBound};
97
use tor_llcrypto as ll;
98
use tor_llcrypto::pk::rsa::RsaIdentity;
99

            
100
use serde::{Deserialize, Deserializer};
101

            
102
#[cfg(feature = "build_docs")]
103
pub use build::MdConsensusBuilder;
104
#[cfg(feature = "build_docs")]
105
pub use build::PlainConsensusBuilder;
106
#[cfg(feature = "build_docs")]
107
ns_export_each_flavor! {
108
    ty: RouterStatusBuilder;
109
}
110

            
111
ns_export_each_variety! {
112
    ty: RouterStatus, Preamble;
113
}
114

            
115
#[deprecated]
116
pub use PlainConsensus as NsConsensus;
117
#[deprecated]
118
pub use PlainRouterStatus as NsRouterStatus;
119
#[deprecated]
120
pub use UncheckedPlainConsensus as UncheckedNsConsensus;
121
#[deprecated]
122
pub use UnvalidatedPlainConsensus as UnvalidatedNsConsensus;
123

            
124
pub use rs::{RouterStatusMdDigestsVote, SoftwareVersion};
125

            
126
pub use dir_source::{ConsensusAuthoritySection, DirSource, SupersededAuthorityKey};
127

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

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

            
187
    #[doc(hidden)]
188
    #[deftly(netdoc(skip))]
189
    pub __non_exhaustive: (),
190
}
191

            
192
define_derive_deftly! {
193
    /// Bespoke derive for `Lifetime`, for `new` and accessors
194
    Lifetime:
195

            
196
    ${defcond FIELD not(approx_equal($fname, __non_exhaustive))}
197

            
198
    impl Lifetime {
199
        /// Construct a new Lifetime.
200
14112
        pub fn new(
201
14112
            $( ${when FIELD} $fname: time::SystemTime, )
202
14112
        ) -> Result<Self> {
203
            // Make this now because otherwise literal `valid_after` here in the body
204
            // has the wrong span - the compiler refuses to look at the argument.
205
            // But we can refer to the field names.
206
            let self_ = Lifetime {
207
                $( ${when FIELD} $fname: $fname.into(), )
208
                __non_exhaustive: (),
209
            };
210
            if self_.valid_after < self_.fresh_until && self_.fresh_until < self_.valid_until {
211
                Ok(self_)
212
            } else {
213
                Err(EK::InvalidLifetime.err())
214
            }
215
        }
216
      $(
217
        ${when FIELD}
218

            
219
        ${fattrs doc}
220
170964
        pub fn $fname(&self) -> time::SystemTime {
221
            *self.$fname
222
        }
223
      )
224
        /// Return true if this consensus is officially valid at the provided time.
225
540
        pub fn valid_at(&self, when: time::SystemTime) -> bool {
226
            *self.valid_after <= when && when <= *self.valid_until
227
        }
228

            
229
        /// Return the voting period implied by this lifetime.
230
        ///
231
        /// (The "voting period" is the amount of time in between when a consensus first
232
        /// becomes valid, and when the next consensus is expected to become valid)
233
54216
        pub fn voting_period(&self) -> time::Duration {
234
            let valid_after = self.valid_after();
235
            let fresh_until = self.fresh_until();
236
            fresh_until
237
                .duration_since(valid_after)
238
                .expect("Mis-formed lifetime")
239
        }
240
    }
241
}
242
use derive_deftly_template_Lifetime;
243

            
244
/// A single consensus method
245
///
246
/// These are integers, but we don't do arithmetic on them.
247
///
248
/// As defined here:
249
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-methods>
250
/// <https://spec.torproject.org/dir-spec/computing-consensus.html#flavor:microdesc>
251
///
252
/// As used in a `consensus-method` item:
253
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-method>
254
#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy)] //
255
#[derive(derive_more::From, derive_more::Into, derive_more::Display, derive_more::FromStr)]
256
pub struct ConsensusMethod(u32);
257
impl NormalItemArgument for ConsensusMethod {}
258

            
259
/// A set of consensus methods
260
///
261
/// Implements `ItemValueParseable` as required for `consensus-methods`,
262
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:consensus-methods>
263
///
264
/// There is also [`consensus_methods_comma_separated`] for `m` lines in votes.
265
#[derive(Debug, Clone, Default, Eq, PartialEq, Deftly)]
266
#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
267
#[non_exhaustive]
268
pub struct ConsensusMethods {
269
    /// Consensus methods.
270
    pub methods: BTreeSet<ConsensusMethod>,
271
}
272

            
273
/// Module for use with parse2's `with`, to parse one argument of comma-separated consensus methods
274
///
275
/// As found in an `m` item in a vote:
276
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:m>
277
pub mod consensus_methods_comma_separated {
278
    use super::*;
279
    use parse2::ArgumentError as AE;
280
    use std::result::Result;
281

            
282
    /// Parse
283
14
    pub fn from_args<'s>(args: &mut ArgumentStream<'s>) -> Result<ConsensusMethods, AE> {
284
14
        let mut methods = BTreeSet::new();
285
56
        for ent in args.next().ok_or(AE::Missing)?.split(',') {
286
56
            let ent = ent.parse().map_err(|_| AE::Invalid)?;
287
56
            if !methods.insert(ent) {
288
                return Err(AE::Invalid);
289
56
            }
290
        }
291
14
        Ok(ConsensusMethods { methods })
292
14
    }
293
}
294

            
295
/// A set of named network parameters.
296
///
297
/// These are used to describe current settings for the Tor network,
298
/// current weighting parameters for path selection, and so on.  They're
299
/// encoded with a space-separated K=V format.
300
///
301
/// A `NetParams<i32>` is part of the validated directory manager configuration,
302
/// where it is built (in the builder-pattern sense) from a transparent HashMap.
303
///
304
/// As found in `params` in a network status:
305
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:params>
306
///
307
/// The same syntax is also used, and this type used for parsing, in various other places,
308
/// for example routerstatus entry `w` items (bandwidth weights):
309
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:w>
310
//
311
// TODO DIRAUTH torspec#401 Replace `String` with a suitable newtype
312
// Currently:
313
//  - Our parser allows any keyword that makes it into a netdoc argument,
314
//    but it splits on the *first* `=` so a `NetParams<i32>` cannot parse a keyword with a `=`.
315
//  - We provide constructors that allow any `String`, even ones containing space, `=`,
316
//    newline, etc.
317
//  - Encoding throws `Bug` if the resulting document will be clearly garbage,
318
//    forbidding `=`, whitespace, and controls.  If the supplied keywords are bizarre,
319
//    it may generate surprising documents (eg, containing exciting Unicode).
320
#[derive(Debug, Clone, Default, Eq, PartialEq)]
321
pub struct NetParams<T> {
322
    /// Map from keys to values.
323
    params: HashMap<String, T>,
324
}
325

            
326
impl<T> NetParams<T> {
327
    /// Create a new empty list of NetParams.
328
    #[allow(unused)]
329
29708
    pub fn new() -> Self {
330
29708
        NetParams {
331
29708
            params: HashMap::new(),
332
29708
        }
333
29708
    }
334
    /// Retrieve a given network parameter, if it is present.
335
198450
    pub fn get<A: AsRef<str>>(&self, v: A) -> Option<&T> {
336
198450
        self.params.get(v.as_ref())
337
198450
    }
338
    /// Return an iterator over all key value pairs in an arbitrary order.
339
25928
    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
340
25928
        self.params.iter()
341
25928
    }
342
    /// Set or replace the value of a network parameter.
343
10578
    pub fn set(&mut self, k: String, v: T) {
344
10578
        self.params.insert(k, v);
345
10578
    }
346
}
347

            
348
impl<K: Into<String>, T> FromIterator<(K, T)> for NetParams<T> {
349
7106
    fn from_iter<I: IntoIterator<Item = (K, T)>>(i: I) -> Self {
350
        NetParams {
351
7153
            params: i.into_iter().map(|(k, v)| (k.into(), v)).collect(),
352
        }
353
7106
    }
354
}
355

            
356
impl<T> std::iter::Extend<(String, T)> for NetParams<T> {
357
4118
    fn extend<I: IntoIterator<Item = (String, T)>>(&mut self, iter: I) {
358
4118
        self.params.extend(iter);
359
4118
    }
360
}
361

            
362
impl<'de, T> Deserialize<'de> for NetParams<T>
363
where
364
    T: Deserialize<'de>,
365
{
366
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
367
    where
368
        D: Deserializer<'de>,
369
    {
370
        let params = HashMap::deserialize(deserializer)?;
371
        Ok(NetParams { params })
372
    }
373
}
374

            
375
/// A list of subprotocol versions that implementors should/must provide.
376
///
377
/// This struct represents a pair of (optional) items:
378
/// `recommended-FOO-protocols` and `required-FOO-protocols`.
379
///
380
/// Each consensus has two of these: one for relays, and one for clients.
381
///
382
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:required-relay-protocols>
383
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
384
pub struct ProtoStatus {
385
    /// Set of protocols that are recommended; if we're missing a protocol
386
    /// in this list we should warn the user.
387
    ///
388
    /// `recommended-client-protocols` or `recommended-relay-protocols`
389
    recommended: Protocols,
390
    /// Set of protocols that are required; if we're missing a protocol
391
    /// in this list we should refuse to start.
392
    ///
393
    /// `required-client-protocols` or `required-relay-protocols`
394
    required: Protocols,
395
}
396

            
397
impl ProtoStatus {
398
    /// Check whether the list of supported protocols
399
    /// is sufficient to satisfy this list of recommendations and requirements.
400
    ///
401
    /// If any required protocol is missing, returns [`ProtocolSupportError::MissingRequired`].
402
    ///
403
    /// Otherwise, if no required protocol is missing, but some recommended protocol is missing,
404
    /// returns [`ProtocolSupportError::MissingRecommended`].
405
    ///
406
    /// Otherwise, if no recommended or required protocol is missing, returns `Ok(())`.
407
222
    pub fn check_protocols(
408
222
        &self,
409
222
        supported_protocols: &Protocols,
410
222
    ) -> StdResult<(), ProtocolSupportError> {
411
        // Required protocols take precedence, so we check them first.
412
222
        let missing_required = self.required.difference(supported_protocols);
413
222
        if !missing_required.is_empty() {
414
110
            return Err(ProtocolSupportError::MissingRequired(missing_required));
415
112
        }
416
112
        let missing_recommended = self.recommended.difference(supported_protocols);
417
112
        if !missing_recommended.is_empty() {
418
56
            return Err(ProtocolSupportError::MissingRecommended(
419
56
                missing_recommended,
420
56
            ));
421
56
        }
422

            
423
56
        Ok(())
424
222
    }
425
}
426

            
427
/// A subprotocol that is recommended or required in the consensus was not present.
428
#[derive(Clone, Debug, thiserror::Error)]
429
#[cfg_attr(test, derive(PartialEq))]
430
#[non_exhaustive]
431
pub enum ProtocolSupportError {
432
    /// At least one required protocol was not in our list of supported protocols.
433
    #[error("Required protocols are not implemented: {0}")]
434
    MissingRequired(Protocols),
435

            
436
    /// At least one recommended protocol was not in our list of supported protocols.
437
    ///
438
    /// Also implies that no _required_ protocols were missing.
439
    #[error("Recommended protocols are not implemented: {0}")]
440
    MissingRecommended(Protocols),
441
}
442

            
443
impl ProtocolSupportError {
444
    /// Return true if the suggested behavior for this error is a shutdown.
445
    pub fn should_shutdown(&self) -> bool {
446
        matches!(self, Self::MissingRequired(_))
447
    }
448
}
449

            
450
impl HasKind for ProtocolSupportError {
451
    fn kind(&self) -> tor_error::ErrorKind {
452
        tor_error::ErrorKind::SoftwareDeprecated
453
    }
454
}
455

            
456
/// A set of recommended and required protocols when running
457
/// in various scenarios.
458
///
459
/// Represents the collection of four items: `{recommended,required}-{client,relay}-protocols`.
460
///
461
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:required-relay-protocols>
462
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
463
pub struct ProtoStatuses {
464
    /// Lists of recommended and required subprotocol versions for clients
465
    client: ProtoStatus,
466
    /// Lists of recommended and required subprotocol versions for relays
467
    relay: ProtoStatus,
468
}
469

            
470
impl ProtoStatuses {
471
    /// Return the list of recommended and required protocols for running as a client.
472
216
    pub fn client(&self) -> &ProtoStatus {
473
216
        &self.client
474
216
    }
475

            
476
    /// Return the list of recommended and required protocols for running as a relay.
477
    pub fn relay(&self) -> &ProtoStatus {
478
        &self.relay
479
    }
480
}
481

            
482
/// A recognized 'flavor' of consensus document.
483
///
484
/// The enum is exhaustive because the addition/removal of a consensus flavor
485
/// should indeed be a breaking change, as it would inevitable require
486
/// interfacing code to think about the handling of it.
487
///
488
/// <https://spec.torproject.org/dir-spec/computing-consensus.html#flavors>
489
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
490
#[allow(clippy::exhaustive_enums)]
491
pub enum ConsensusFlavor {
492
    /// A "microdesc"-flavored consensus.  This is the one that
493
    /// clients and relays use today.
494
    Microdesc,
495
    /// A "networkstatus"-flavored consensus.  It's used for
496
    /// historical and network-health purposes.  Instead of listing
497
    /// microdescriptor digests, it lists digests of full relay
498
    /// descriptors.
499
    Plain,
500
}
501

            
502
impl ConsensusFlavor {
503
    /// Return the name of this consensus flavor.
504
2754
    pub fn name(&self) -> &'static str {
505
2754
        match self {
506
972
            ConsensusFlavor::Plain => "ns", // spec bug, now baked in
507
1782
            ConsensusFlavor::Microdesc => "microdesc",
508
        }
509
2754
    }
510
    /// Try to find the flavor whose name is `name`.
511
    ///
512
    /// For historical reasons, an unnamed flavor indicates an "Plain"
513
    /// document.
514
392
    pub fn from_opt_name(name: Option<&str>) -> Result<Self> {
515
392
        match name {
516
390
            Some("microdesc") => Ok(ConsensusFlavor::Microdesc),
517
2
            Some("ns") | None => Ok(ConsensusFlavor::Plain),
518
            Some(other) => {
519
                Err(EK::BadDocumentType.with_msg(format!("unrecognized flavor {:?}", other)))
520
            }
521
        }
522
392
    }
523
}
524

            
525
define_derive_deftly! {
526
    /// Bespoke derives applied to [`DirectorySignatureHashAlgo`]
527
    ///
528
    /// Generates:
529
    ///
530
    ///  * [`DirectorySignaturesHashesAccu`]
531
    ///  * [`DirectorySignaturesHashesAccu::update_from`]
532
    ///  * [`DirectorySignaturesHashesAccu::hash_slice_for_verification`]
533
    DirectorySignaturesHashesAccu:
534

            
535
    ${define FNAME ${paste ${snake_case $vname}} }
536

            
537
    /// `directory-signature`a hash algorithm argument
538
    #[derive(Clone, Copy, Default, Debug, Eq, PartialEq, Deftly)]
539
    #[derive_deftly(AsMutSelf)]
540
    #[non_exhaustive]
541
    pub struct DirectorySignaturesHashesAccu {
542
      $(
543
        ${vattrs doc}
544
        $FNAME: Option<[u8; ${vmeta(hash_len) as expr}]>,
545
      )
546

            
547
      /// `sha1` but without the algorithm name
548
      ///
549
      /// This is needed because the hash includes the whole signature item keyword line,
550
      /// and therefore a signature with the `sha1` explicitly stated,
551
      /// and one without, have different hashes!
552
      ///
553
      /// So we mustn't use the `sha1` field for both implicit and explicit use of SHA-1,
554
      /// or multiple signatures with different syntax would overwrite each others'
555
      /// different hashes.
556
      sha1_unnamed: Option<[u8; 20]>,
557
    }
558

            
559
    impl DirectorySignaturesHashesAccu {
560
        /// Calculate the hash for a signature item and update this accumulator
561
1962
        fn update_from(
562
            &mut self,
563
            algo: &DigestAlgoInSignature,
564
            body: &SignatureHashInputs,
565
        ) {
566
            // Update the hash in self.$UPDATE according to algorithm $AGLO
567
            // (uses dynamic bindings of those parameters)
568
            ${define HASH {
569
                // Avoid recalculating if we don't need to
570
222
                self.$UPDATE.get_or_insert_with(|| {
571
                    let mut h = tor_llcrypto::d::$ALGO::new();
572
                    h.update(body.body().body());
573
                    h.update(body.signature_item_kw_spc);
574
                    h.finalize().into()
575
                });
576
            }}
577

            
578
            match &**algo {
579
              $(
580
                Some(KeywordOrString::Known($vtype)) => {
581
                    ${define UPDATE $FNAME}
582
                    ${define ALGO $vname}
583
                    $HASH
584
                }
585
              )
586
                None => {
587
                    ${define UPDATE sha1_unnamed}
588
                    ${define ALGO Sha1}
589
                    $HASH
590
                }
591
                Some(KeywordOrString::Unknown(..)) => {}
592
            }
593
        }
594

            
595
        /// Return the hash value for a specific algorithm, as a slice
596
        ///
597
        /// `None` if the value wasn't computed.
598
        /// That shouldn't happen.
599
        // TODO DIRAUTH make private when poc's verification is abolished
600
8
        pub(crate) fn hash_slice_for_verification(
601
8
            &self,
602
8
            algo: &DigestAlgoInSignature,
603
8
        ) -> Option<&[u8]> {
604
            match &**algo {
605
              $(
606
                Some(KeywordOrString::Known($vtype)) => Some(self.$FNAME.as_ref()?),
607
              )
608
                None => Some(self.sha1_unnamed.as_ref()?),
609
                Some(KeywordOrString::Unknown(..)) => None,
610
            }
611
        }
612
    }
613
}
614

            
615
/// `directory-signature` hash algorithm argument
616
#[derive(Clone, Copy, Debug, Eq, PartialEq, strum::Display, strum::EnumString, Deftly)]
617
#[derive_deftly(DirectorySignaturesHashesAccu)]
618
#[non_exhaustive]
619
#[strum(serialize_all = "snake_case")]
620
pub enum DirectorySignatureHashAlgo {
621
    /// SHA-1
622
    #[deftly(hash_len = "20")]
623
    Sha1,
624
    /// SHA-256
625
    #[deftly(hash_len = "32")]
626
    Sha256,
627
}
628

            
629
/// `algorithm` field in a `directory-signature` item
630
///
631
/// This is extremely bizarre: it's an *optional item at the start of the arguments*!
632
// TODO SPEC #350
633
///
634
/// So we parse it with some kind of nightmarish lookahead.
635
///
636
/// Additionally, to be able to convey the signatures accurately, without breaking them,
637
/// we must remember whether the argument was present.
638
///
639
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:directory-signature>
640
#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
641
#[allow(clippy::exhaustive_structs)]
642
pub struct DigestAlgoInSignature(pub Option<KeywordOrString<DirectorySignatureHashAlgo>>);
643

            
644
impl ItemArgumentParseable for DigestAlgoInSignature {
645
1962
    fn from_args<'s>(args: &mut ArgumentStream<'s>) -> StdResult<Self, ArgumentError> {
646
1962
        let v = if args
647
1962
            .clone()
648
1962
            .next()
649
            // Treat it as a fingerprint if it doesn't have any non-hex characters
650
            // (including lowercase ones).  If we reuse this item for new algorithms
651
            // they should have at least one letter g-z in their name.
652
78213
            .and_then(|s| s.chars().all(|c| c.is_ascii_hexdigit()).then_some(()))
653
1962
            .is_some()
654
        {
655
            // next argument looks enough like a fingerprint that we don't treat as an algo name
656
1954
            None
657
        } else {
658
8
            Some(KeywordOrString::from_args(args)?)
659
        };
660
1962
        Ok(DigestAlgoInSignature(v))
661
1962
    }
662
}
663
impl ItemArgument for DigestAlgoInSignature {
664
    fn write_arg_onto(&self, out: &mut ItemEncoder<'_>) -> StdResult<(), Bug> {
665
        if let Some(y) = &self.0 {
666
            y.write_arg_onto(out)?;
667
        }
668
        Ok(())
669
    }
670
}
671
impl DigestAlgoInSignature {
672
    /// Return the actual algorithm
673
    ///
674
    /// This handles the defaulting, where an absent argument means `sha1`.
675
182
    pub fn algorithm(&self) -> &KeywordOrString<DirectorySignatureHashAlgo> {
676
182
        self.as_ref()
677
182
            .unwrap_or(&KeywordOrString::Known(DirectorySignatureHashAlgo::Sha1))
678
182
    }
679
}
680

            
681
impl NormalItemArgument for DirectorySignatureHashAlgo {}
682

            
683
/// The signature of a single directory authority on a networkstatus document.
684
///
685
/// Implements `ItemValueParseable` which parses without hashing anything;
686
/// this is mostly useful for use by the `SignatureItemParseable` implementation.
687
#[derive(Debug, Clone, Deftly)]
688
#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
689
#[non_exhaustive]
690
pub struct Signature {
691
    /// The name of the digest algorithm used to make the signature.
692
    ///
693
    /// Currently sha1 and sh256 are recognized.  Here we only support
694
    /// sha256.
695
    pub digest_algo: DigestAlgoInSignature,
696
    /// Fingerprints of the keys for the authority that made
697
    /// this signature.
698
    #[deftly(netdoc(with = authcert::keyids_directory_signature_args))]
699
    pub key_ids: AuthCertKeyIds,
700
    /// The signature itself.
701
    #[deftly(netdoc(object(label = "SIGNATURE"), with = types::raw_data_object))]
702
    pub signature: Vec<u8>,
703
}
704

            
705
impl SignatureItemParseable for Signature {
706
    type HashAccu = DirectorySignaturesHashesAccu;
707

            
708
1962
    fn from_unparsed_and_body(
709
1962
        item: UnparsedItem,
710
1962
        body: &SignatureHashInputs<'_>,
711
1962
        hash: &mut Self::HashAccu,
712
1962
    ) -> StdResult<Self, ErrorProblem> {
713
1962
        let signature = Signature::from_unparsed(item)?;
714
1962
        hash.update_from(&signature.digest_algo, body);
715
1962
        Ok(signature)
716
1962
    }
717
}
718

            
719
/// A collection of signatures that can be checked on a networkstatus document
720
#[derive(Debug, Clone)]
721
#[non_exhaustive]
722
pub struct SignatureGroup {
723
    /// The sha256 of the document itself
724
    pub sha256: Option<[u8; 32]>,
725
    /// The sha1 of the document itself
726
    pub sha1: Option<[u8; 20]>,
727
    /// The signatures listed on the document.
728
    pub signatures: Vec<Signature>,
729
}
730

            
731
/// A shared random value produced by the directory authorities.
732
#[derive(
733
    Debug, Clone, Copy, Eq, PartialEq, derive_more::From, derive_more::Into, derive_more::AsRef,
734
)]
735
// (This doesn't need to use CtByteArray; we don't really need to compare these.)
736
pub struct SharedRandVal([u8; 32]);
737

            
738
/// A shared-random value produced by the directory authorities,
739
/// along with meta-information about that value.
740
#[derive(Debug, Clone, Deftly)]
741
#[non_exhaustive]
742
#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
743
pub struct SharedRandStatus {
744
    /// How many authorities revealed shares that contributed to this value.
745
    pub n_reveals: u8,
746
    /// The current random value.
747
    ///
748
    /// The properties of the secure shared-random system guarantee
749
    /// that this value isn't predictable before it first becomes
750
    /// live, and that a hostile party could not have forced it to
751
    /// have any more than a small number of possible random values.
752
    pub value: SharedRandVal,
753

            
754
    /// The time when this SharedRandVal becomes (or became) the latest.
755
    ///
756
    /// (This is added per proposal 342, assuming that gets accepted.)
757
    pub timestamp: Option<Iso8601TimeNoSp>,
758
}
759

            
760
/// The two shared random values, `shared-rand-*-value`
761
///
762
/// As found in the consensus preamble
763
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-current-value>
764
/// and a vote's authority section
765
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#authority-item-shared-rand-value>
766
#[derive(Debug, Clone, Default, Deftly)]
767
#[derive_deftly(Constructor, NetdocEncodableFields, NetdocParseableFields)]
768
#[allow(clippy::exhaustive_structs)]
769
pub struct SharedRandStatuses {
770
    /// Global shared-random value for the previous shared-random period.
771
    pub shared_rand_previous_value: Option<SharedRandStatus>,
772

            
773
    /// Global shared-random value for the current shared-random period.
774
    pub shared_rand_current_value: Option<SharedRandStatus>,
775

            
776
    #[doc(hidden)]
777
    #[deftly(netdoc(skip))]
778
    pub __non_exhaustive: (),
779
}
780

            
781
/// Recognized weight fields on a single relay in a consensus
782
#[non_exhaustive]
783
#[derive(Debug, Clone, Copy)]
784
pub enum RelayWeight {
785
    /// An unmeasured weight for a relay.
786
    Unmeasured(u32),
787
    /// An measured weight for a relay.
788
    Measured(u32),
789
}
790

            
791
impl RelayWeight {
792
    /// Return true if this weight is the result of a successful measurement
793
26740
    pub fn is_measured(&self) -> bool {
794
26740
        matches!(self, RelayWeight::Measured(_))
795
26740
    }
796
    /// Return true if this weight is nonzero
797
25228
    pub fn is_nonzero(&self) -> bool {
798
25228
        !matches!(self, RelayWeight::Unmeasured(0) | RelayWeight::Measured(0))
799
25228
    }
800
}
801

            
802
/// Authority entry in a consensus - deprecated compatibility type alias
803
#[deprecated = "renamed to ConsensusAuthorityEntry"]
804
pub type ConsensusVoterInfo = ConsensusAuthorityEntry;
805

            
806
/// Authority entry in a plain consensus - type alias provided for consistency
807
pub type PlainAuthorityEntry = ConsensusAuthorityEntry;
808
/// Authority entry in an md consensus - type alias provided for consistency
809
pub type MdAuthorityEntry = ConsensusAuthorityEntry;
810

            
811
/// An authority entry as found in a consensus
812
///
813
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#section:authority-entry>
814
///
815
/// See also [`VoteAuthorityEntry`]
816
//
817
// We don't use the `each_variety` system for this because:
818
//  1. That avoids separating the two consensus authority entry types, which are identical
819
//  2. The only common fields are `dir-source` and `contact`, so there is little duplication
820
#[derive(Debug, Clone, Deftly)]
821
#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
822
#[allow(clippy::exhaustive_structs)]
823
pub struct ConsensusAuthorityEntry {
824
    /// Contents of the `dir-source` line about an authority
825
    #[deftly(constructor)]
826
    pub dir_source: DirSource,
827

            
828
    /// Human-readable contact information about the authority
829
    //
830
    // If more non-intro fields get added that are the same in votes and cosensuses,
831
    // consider using each_variety.rs or breaking those fields out into
832
    // `AuthorityEntryCommon` implementing `NetdocParseableFields`, or something.
833
    #[deftly(constructor)]
834
    pub contact: ContactInfo,
835

            
836
    /// Digest of the vote that the authority cast to contribute to
837
    /// this consensus.
838
    ///
839
    /// This is not a fixed-length, fixed-algorithm field.
840
    /// Bizarrely, the algorithm is supposed to be inferred from the length!
841
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:vote-digest>
842
    #[deftly(netdoc(single_arg))]
843
    #[deftly(constructor)]
844
    pub vote_digest: B16U,
845

            
846
    #[doc(hidden)]
847
    #[deftly(netdoc(skip))]
848
    pub __non_exhaustive: (),
849
}
850

            
851
/// An authority entry as found in a vote
852
///
853
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#section:authority-entry>
854
///
855
/// See also [`ConsensusAuthorityEntry`]
856
#[derive(Debug, Clone, Deftly)]
857
#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
858
#[allow(clippy::exhaustive_structs)]
859
pub struct VoteAuthorityEntry {
860
    /// Contents of the `dir-source` line about an authority
861
    #[deftly(constructor)]
862
    pub dir_source: DirSource,
863

            
864
    /// Human-readable contact information about the authority
865
    #[deftly(constructor)]
866
    pub contact: ContactInfo,
867

            
868
    /// `legacy-dir-key` - superseded authority identity key
869
    ///
870
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:legacy-dir-key>
871
    #[deftly(netdoc(single_arg))]
872
    pub legacy_dir_key: Option<Fingerprint>,
873

            
874
    /// `shared-rand-participate` - Indicate shared random participation
875
    ///
876
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-participate>
877
    pub shared_rand_participate: Option<SharedRandParticipate>,
878

            
879
    /// `shared-rand-commit` - Shared random commitment
880
    ///
881
    /// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-commit>
882
    pub shared_rand_commit: Vec<SharedRandCommit>,
883

            
884
    /// Global shared-random values
885
    #[deftly(netdoc(flatten))]
886
    pub shared_rand: SharedRandStatuses,
887

            
888
    #[doc(hidden)]
889
    #[deftly(netdoc(skip))]
890
    pub __non_exhaustive: (),
891
}
892

            
893
/// `shared-rand-participate` in a vote authority entry
894
///
895
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-participate>
896
//
897
// We could have done `shared_rand_participate: Option<()>` in VoteAuthorityEntry,
898
// but then we might end up with variables of type `&Option<()>` etc.
899
// whose meaning has been detached from its type.
900
//
901
// TODO DIRAUTH rework this according to the API design conclusion from !3977 when there is one
902
#[derive(Debug, Clone, Deftly)]
903
#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
904
#[allow(clippy::exhaustive_structs)]
905
pub struct SharedRandParticipate {
906
    #[doc(hidden)]
907
    #[deftly(netdoc(skip))]
908
    pub __non_exhaustive: (),
909
}
910

            
911
/// `shared-rand-commit` in a vote authority entry
912
///
913
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-commit>
914
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
915
// If new protocols use this item with a different version, we'll call it an API break.
916
#[allow(clippy::exhaustive_enums)]
917
pub enum SharedRandCommit {
918
    /// Version 1, the only one supported
919
    V1(SharedRandCommitV1),
920

            
921
    /// Other versions.  Cannot be encoded.
922
    // It's not clear that future versions will use this version mechanism.  torspec#408.
923
    Unknown {},
924
}
925

            
926
/// `shared-rand-commit` in a vote authority entry
927
///
928
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:shared-rand-commit>
929
///
930
/// Version and hash are not explicitly represented.  See torspec#407.
931
///
932
/// `ItemValueEncodable` and `ItemValueParseable` impls do not include the fixed arguments;
933
/// in a netdoc, this type should be used within `SharedRandCommit::V1`.
934
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deftly)]
935
#[derive_deftly(Constructor, ItemValueEncodable, ItemValueParseable)]
936
#[allow(clippy::exhaustive_structs)]
937
pub struct SharedRandCommitV1 {
938
    /// Authority id key, recapitulated.
939
    // TODO this field shouldn't here at all torspec#407
940
    #[deftly(constructor)]
941
    h_kp_auth_id_rsa: Fingerprint,
942

            
943
    /// Commitment
944
    ///
945
    /// `TIMESTAMP || SHA3_256(REVEAL)`, as per
946
    /// <https://spec.torproject.org/srv-spec/specification.html#COMMITREVEAL>
947
    //
948
    // TOOD we would like to replace this with a type that separates out the pieces!
949
    // But that would need a FixedB64 generic over some tor-bytes trait, or something.
950
    #[deftly(constructor)]
951
    commit: FixedB64<40>,
952

            
953
    /// Reveal
954
    ///
955
    /// `TIMESTAMP || random number`, as per
956
    /// <https://spec.torproject.org/srv-spec/specification.html#COMMITREVEAL>
957
    reveal: Option<FixedB64<40>>,
958

            
959
    #[doc(hidden)]
960
    #[deftly(netdoc(skip))]
961
    pub __non_exhaustive: (),
962
}
963

            
964
impl SharedRandCommitV1 {
965
    /// The fixed arguments that precede the actual value in `shared-rand-commit 1 ...`
966
    const FIXED_ARGUMENTS: &[&str] = &["1", "sha3-256"];
967
}
968
impl ItemValueEncodable for SharedRandCommit {
969
    fn write_item_value_onto(&self, mut out: ItemEncoder) -> StdResult<(), Bug> {
970
        match self {
971
            SharedRandCommit::V1(values) => {
972
                for fixed in SharedRandCommitV1::FIXED_ARGUMENTS {
973
                    out.args_raw_string(fixed);
974
                }
975
                values.write_item_value_onto(out)
976
            }
977
            SharedRandCommit::Unknown {} => Err(internal!("encoding SharedRandCommit::Unknown")),
978
        }
979
    }
980
}
981
impl ItemValueParseable for SharedRandCommit {
982
8
    fn from_unparsed(mut item: UnparsedItem<'_>) -> StdResult<Self, ErrorProblem> {
983
8
        let mut fixed = SharedRandCommitV1::FIXED_ARGUMENTS.iter().copied();
984
8
        let args = item.args_mut();
985
8
        let version = args
986
8
            .next()
987
8
            .ok_or_else(|| args.handle_error("version", ArgumentError::Missing))?;
988
8
        if version != fixed.next().expect("nonempty") {
989
            return Ok(SharedRandCommit::Unknown {});
990
8
        }
991
8
        for exp in fixed {
992
8
            let got = args
993
8
                .next()
994
8
                .ok_or_else(|| args.handle_error(exp, ArgumentError::Missing))?;
995
8
            if got != exp {
996
                return Err(args.handle_error(exp, ArgumentError::Invalid))?;
997
8
            }
998
        }
999
8
        let values = SharedRandCommitV1::from_unparsed(item)?;
8
        Ok(SharedRandCommit::V1(values))
8
    }
}
// For `ConsensusAuthoritySection`, see `dir_source.rs`.
define_derive_deftly! {
    /// Ad-hoc derive, `impl NetdocParseable for VoteAuthoritySection`
    ///
    /// We can't derive from `VoteAuthoritySection` with the normal macros, because
    /// it's not a document, with its own intro item.  It's just a collection of sub-documents.
    /// The netdoc derive macros don't have support for that - and it would be a fairly
    /// confusing thing to support because you'd end up with nested multiplicities and a whole
    /// variety of "intro item keywords" that were keywords for arbitrary sub-documents.
    ///
    /// Instead, we do that ad-hoc here.  It's less confusing because we don't need to
    /// worry about multiplicity, and because we know what only the outer document is
    /// that will contain this.
    VoteAuthoritySection:
    ${defcond F_NORMAL not(fmeta(netdoc(skip)))}
    #[cfg(feature = "incomplete")] // needs EncodedAuthCert, otherwise complete
    impl NetdocParseable for VoteAuthoritySection {
2
        fn doctype_for_error() -> &'static str {
            "vote.authority.section"
        }
314
        fn is_intro_item_keyword(kw: KeywordRef<'_>) -> bool {
            VoteAuthorityEntry::is_intro_item_keyword(kw)
        }
82
        fn is_structural_keyword(kw: KeywordRef<'_>) -> Option<IsStructural> {
          $(
            ${when F_NORMAL}
            if let y @ Some(_) = $ftype::is_structural_keyword(kw) {
                return y;
            }
          )
            None
        }
2
        fn from_items<'s>(
2
            input: &mut ItemStream<'s>,
2
            stop_outer: stop_at!(),
2
        ) -> StdResult<Self, ErrorProblem> {
            let stop_inner = stop_outer
              $(
                ${when F_NORMAL}
                | StopAt($ftype::is_intro_item_keyword)
              )
            ;
            Ok(VoteAuthoritySection { $(
                ${when F_NORMAL}
                $fname: NetdocParseable::from_items(input, stop_inner)?,
            )
                __non_exhaustive: (),
            })
        }
    }
    #[cfg(feature = "incomplete")]
    impl NetdocEncodable for VoteAuthoritySection {
        fn encode_unsigned(&self, out: &mut NetdocEncoder) -> StdResult<(), Bug> {
          $(
            ${when F_NORMAL}
            self.$fname.encode_unsigned(out)?;
          )
          Ok(())
        }
    }
}
/// An authority section in a vote
///
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#section:authority>
//
// We have split this out to help encapsulate vote/consensus-specific
// information in a forthcoming overall network status document type.
#[derive(Deftly, Clone, Debug)]
#[derive_deftly(VoteAuthoritySection, Constructor)]
#[allow(clippy::exhaustive_structs)]
#[cfg(feature = "incomplete")] // needs EncodedAuthCert, otherwise complete
pub struct VoteAuthoritySection {
    /// Authority entry
    #[deftly(constructor)]
    pub authority: VoteAuthorityEntry,
    /// Authority key certificate
    #[deftly(constructor)]
    pub cert: EncodedAuthCert,
    #[doc(hidden)]
    #[deftly(netdoc(skip))]
    pub __non_exhaustive: (),
}
/// The signed footer of a consensus netstatus.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Footer {
    /// Weights to be applied to certain classes of relays when choosing
    /// for different roles.
    ///
    /// For example, we want to avoid choosing exits for non-exit
    /// roles when overall the proportion of exits is small.
    pub weights: NetParams<i32>,
}
/// A consensus document that lists relays along with their
/// microdescriptor documents.
pub type MdConsensus = md::Consensus;
/// An MdConsensus that has been parsed and checked for timeliness,
/// but not for signatures.
pub type UnvalidatedMdConsensus = md::UnvalidatedConsensus;
/// An MdConsensus that has been parsed but not checked for signatures
/// and timeliness.
pub type UncheckedMdConsensus = md::UncheckedConsensus;
/// A consensus document that lists relays along with their
/// router descriptor documents.
pub type PlainConsensus = plain::Consensus;
/// An PlainConsensus that has been parsed and checked for timeliness,
/// but not for signatures.
pub type UnvalidatedPlainConsensus = plain::UnvalidatedConsensus;
/// An PlainConsensus that has been parsed but not checked for signatures
/// and timeliness.
pub type UncheckedPlainConsensus = plain::UncheckedConsensus;
decl_keyword! {
    /// Keywords that can be used in votes and consensuses.
    // TODO: This is public because otherwise we can't use it in the
    // ParseRouterStatus crate.  But I'd rather find a way to make it
    // private.
    #[non_exhaustive]
    #[allow(missing_docs)]
    pub NetstatusKwd {
        // Header
        "network-status-version" => NETWORK_STATUS_VERSION,
        "vote-status" => VOTE_STATUS,
        "consensus-methods" => CONSENSUS_METHODS,
        "consensus-method" => CONSENSUS_METHOD,
        "published" => PUBLISHED,
        "valid-after" => VALID_AFTER,
        "fresh-until" => FRESH_UNTIL,
        "valid-until" => VALID_UNTIL,
        "voting-delay" => VOTING_DELAY,
        "client-versions" => CLIENT_VERSIONS,
        "server-versions" => SERVER_VERSIONS,
        "known-flags" => KNOWN_FLAGS,
        "flag-thresholds" => FLAG_THRESHOLDS,
        "recommended-client-protocols" => RECOMMENDED_CLIENT_PROTOCOLS,
        "required-client-protocols" => REQUIRED_CLIENT_PROTOCOLS,
        "recommended-relay-protocols" => RECOMMENDED_RELAY_PROTOCOLS,
        "required-relay-protocols" => REQUIRED_RELAY_PROTOCOLS,
        "params" => PARAMS,
        "bandwidth-file-headers" => BANDWIDTH_FILE_HEADERS,
        "bandwidth-file-digest" => BANDWIDTH_FILE_DIGEST,
        // "package" is now ignored.
        // header in consensus, voter section in vote?
        "shared-rand-previous-value" => SHARED_RAND_PREVIOUS_VALUE,
        "shared-rand-current-value" => SHARED_RAND_CURRENT_VALUE,
        // Voter section (both)
        "dir-source" => DIR_SOURCE,
        "contact" => CONTACT,
        // voter section (vote, but not consensus)
        "legacy-dir-key" => LEGACY_DIR_KEY,
        "shared-rand-participate" => SHARED_RAND_PARTICIPATE,
        "shared-rand-commit" => SHARED_RAND_COMMIT,
        // voter section (consensus, but not vote)
        "vote-digest" => VOTE_DIGEST,
        // voter cert beginning (but only the beginning)
        "dir-key-certificate-version" => DIR_KEY_CERTIFICATE_VERSION,
        // routerstatus
        "r" => RS_R,
        "a" => RS_A,
        "s" => RS_S,
        "v" => RS_V,
        "pr" => RS_PR,
        "w" => RS_W,
        "p" => RS_P,
        "m" => RS_M,
        "id" => RS_ID,
        // footer
        "directory-footer" => DIRECTORY_FOOTER,
        "bandwidth-weights" => BANDWIDTH_WEIGHTS,
        "directory-signature" => DIRECTORY_SIGNATURE,
    }
}
/// Shared parts of rules for all kinds of netstatus headers
56
static NS_HEADER_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
56
    let mut rules = SectionRules::builder();
56
    rules.add(NETWORK_STATUS_VERSION.rule().required().args(1..=2));
56
    rules.add(VOTE_STATUS.rule().required().args(1..));
56
    rules.add(VALID_AFTER.rule().required());
56
    rules.add(FRESH_UNTIL.rule().required());
56
    rules.add(VALID_UNTIL.rule().required());
56
    rules.add(VOTING_DELAY.rule().args(2..));
56
    rules.add(CLIENT_VERSIONS.rule());
56
    rules.add(SERVER_VERSIONS.rule());
56
    rules.add(KNOWN_FLAGS.rule().required());
56
    rules.add(RECOMMENDED_CLIENT_PROTOCOLS.rule().args(1..));
56
    rules.add(RECOMMENDED_RELAY_PROTOCOLS.rule().args(1..));
56
    rules.add(REQUIRED_CLIENT_PROTOCOLS.rule().args(1..));
56
    rules.add(REQUIRED_RELAY_PROTOCOLS.rule().args(1..));
56
    rules.add(PARAMS.rule());
56
    rules
56
});
/// Rules for parsing the header of a consensus.
56
static NS_HEADER_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
56
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
56
    rules.add(CONSENSUS_METHOD.rule().args(1..=1));
56
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
56
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
56
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
56
    rules.build()
56
});
/*
/// Rules for parsing the header of a vote.
static NS_HEADER_RULES_VOTE: SectionRules<NetstatusKwd> = {
    use NetstatusKwd::*;
    let mut rules = NS_HEADER_RULES_COMMON_.clone();
    rules.add(CONSENSUS_METHODS.rule().args(1..));
    rules.add(FLAG_THRESHOLDS.rule());
    rules.add(BANDWIDTH_FILE_HEADERS.rule());
    rules.add(BANDWIDTH_FILE_DIGEST.rule().args(1..));
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
    rules
};
/// Rules for parsing a single voter's information in a vote.
static NS_VOTERINFO_RULES_VOTE: SectionRules<NetstatusKwd> = {
    use NetstatusKwd::*;
    let mut rules = SectionRules::new();
    rules.add(DIR_SOURCE.rule().required().args(6..));
    rules.add(CONTACT.rule().required());
    rules.add(LEGACY_DIR_KEY.rule().args(1..));
    rules.add(SHARED_RAND_PARTICIPATE.rule().no_args());
    rules.add(SHARED_RAND_COMMIT.rule().may_repeat().args(4..));
    rules.add(SHARED_RAND_PREVIOUS_VALUE.rule().args(2..));
    rules.add(SHARED_RAND_CURRENT_VALUE.rule().args(2..));
    // then comes an entire cert: When we implement vote parsing,
    // we should use the authcert code for handling that.
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
    rules
};
 */
/// Rules for parsing a single voter's information in a consensus
56
static NS_VOTERINFO_RULES_CONSENSUS: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
56
    let mut rules = SectionRules::builder();
56
    rules.add(DIR_SOURCE.rule().required().args(6..));
56
    rules.add(CONTACT.rule().required());
56
    rules.add(VOTE_DIGEST.rule().required());
56
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
56
    rules.build()
56
});
/// Shared rules for parsing a single routerstatus
static NS_ROUTERSTATUS_RULES_COMMON_: LazyLock<SectionRulesBuilder<NetstatusKwd>> =
56
    LazyLock::new(|| {
        use NetstatusKwd::*;
56
        let mut rules = SectionRules::builder();
56
        rules.add(RS_A.rule().may_repeat().args(1..));
56
        rules.add(RS_S.rule().required());
56
        rules.add(RS_V.rule());
56
        rules.add(RS_PR.rule().required());
56
        rules.add(RS_W.rule());
56
        rules.add(RS_P.rule().args(2..));
56
        rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
56
        rules
56
    });
/// Rules for parsing a single routerstatus in an NS consensus
2
static NS_ROUTERSTATUS_RULES_PLAIN: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
2
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
2
    rules.add(RS_R.rule().required().args(8..));
2
    rules.build()
2
});
/*
/// Rules for parsing a single routerstatus in a vote
static NS_ROUTERSTATUS_RULES_VOTE: SectionRules<NetstatusKwd> = {
    use NetstatusKwd::*;
        let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
        rules.add(RS_R.rule().required().args(8..));
        rules.add(RS_M.rule().may_repeat().args(2..));
        rules.add(RS_ID.rule().may_repeat().args(2..)); // may-repeat?
        rules
    };
*/
/// Rules for parsing a single routerstatus in a microdesc consensus
56
static NS_ROUTERSTATUS_RULES_MDCON: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
56
    let mut rules = NS_ROUTERSTATUS_RULES_COMMON_.clone();
56
    rules.add(RS_R.rule().required().args(6..));
56
    rules.add(RS_M.rule().required().args(1..));
56
    rules.build()
56
});
/// Rules for parsing consensus fields from a footer.
56
static NS_FOOTER_RULES: LazyLock<SectionRules<NetstatusKwd>> = LazyLock::new(|| {
    use NetstatusKwd::*;
56
    let mut rules = SectionRules::builder();
56
    rules.add(DIRECTORY_FOOTER.rule().required().no_args());
    // consensus only
56
    rules.add(BANDWIDTH_WEIGHTS.rule());
56
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
56
    rules.build()
56
});
impl ProtoStatus {
    /// Construct a ProtoStatus from two chosen keywords in a section.
784
    fn from_section(
784
        sec: &Section<'_, NetstatusKwd>,
784
        recommend_token: NetstatusKwd,
784
        required_token: NetstatusKwd,
784
    ) -> Result<ProtoStatus> {
        /// Helper: extract a Protocols entry from an item's arguments.
1568
        fn parse(t: Option<&Item<'_, NetstatusKwd>>) -> Result<Protocols> {
1568
            if let Some(item) = t {
1568
                item.args_as_str()
1568
                    .parse::<Protocols>()
1568
                    .map_err(|e| EK::BadArgument.at_pos(item.pos()).with_source(e))
            } else {
                Ok(Protocols::new())
            }
1568
        }
784
        let recommended = parse(sec.get(recommend_token))?;
784
        let required = parse(sec.get(required_token))?;
784
        Ok(ProtoStatus {
784
            recommended,
784
            required,
784
        })
784
    }
    /// Return the protocols that are listed as "required" in this `ProtoStatus`.
    ///
    /// Implementations may assume that relays on the network implement all the
    /// protocols in the relays' required-protocols list.  Implementations should
    /// refuse to start if they do not implement all the protocols on their own
    /// (client or relay) required-protocols list.
2538
    pub fn required_protocols(&self) -> &Protocols {
2538
        &self.required
2538
    }
    /// Return the protocols that are listed as "recommended" in this `ProtoStatus`.
    ///
    /// Implementations should warn if they do not implement all the protocols
    /// on their own (client or relay) recommended-protocols list.
    pub fn recommended_protocols(&self) -> &Protocols {
        &self.recommended
    }
}
impl<T> std::str::FromStr for NetParams<T>
where
    T: std::str::FromStr,
    T::Err: std::error::Error,
{
    type Err = Error;
17308
    fn from_str(s: &str) -> Result<Self> {
        /// Helper: parse a single K=V pair.
18358
        fn parse_pair<U>(p: &str) -> Result<(String, U)>
18358
        where
18358
            U: std::str::FromStr,
18358
            U::Err: std::error::Error,
        {
18358
            let parts: Vec<_> = p.splitn(2, '=').collect();
18358
            if parts.len() != 2 {
                return Err(EK::BadArgument
                    .at_pos(Pos::at(p))
                    .with_msg("Missing = in key=value list"));
18358
            }
18358
            let num = parts[1].parse::<U>().map_err(|e| {
8
                EK::BadArgument
8
                    .at_pos(Pos::at(parts[1]))
8
                    .with_msg(e.to_string())
8
            })?;
18350
            Ok((parts[0].to_string(), num))
18358
        }
17308
        let params = s
17308
            .split(' ')
31060
            .filter(|p| !p.is_empty())
17308
            .map(parse_pair)
17308
            .collect::<Result<HashMap<_, _>>>()?;
17300
        Ok(NetParams { params })
17308
    }
}
impl FromStr for SharedRandVal {
    type Err = Error;
4
    fn from_str(s: &str) -> Result<Self> {
4
        let val: B64 = s.parse()?;
4
        let val = SharedRandVal(val.into_array()?);
4
        Ok(val)
4
    }
}
impl Display for SharedRandVal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Display::fmt(&B64::from(Vec::from(self.0)), f)
    }
}
impl NormalItemArgument for SharedRandVal {}
impl SharedRandStatus {
    /// Parse a current or previous shared rand value from a given
    /// SharedRandPreviousValue or SharedRandCurrentValue.
6
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
6
        match item.kwd() {
4
            NetstatusKwd::SHARED_RAND_PREVIOUS_VALUE | NetstatusKwd::SHARED_RAND_CURRENT_VALUE => {}
            _ => {
2
                return Err(Error::from(internal!(
2
                    "wrong keyword {:?} on shared-random value",
2
                    item.kwd()
2
                ))
2
                .at_pos(item.pos()));
            }
        }
4
        let n_reveals: u8 = item.parse_arg(0)?;
4
        let value: SharedRandVal = item.parse_arg(1)?;
        // Added in proposal 342
4
        let timestamp = item.parse_optional_arg::<Iso8601TimeNoSp>(2)?;
4
        Ok(SharedRandStatus {
4
            n_reveals,
4
            value,
4
            timestamp,
4
        })
6
    }
    /// Return the actual shared random value.
864
    pub fn value(&self) -> &SharedRandVal {
864
        &self.value
864
    }
    /// Return the timestamp (if any) associated with this `SharedRandValue`.
1620
    pub fn timestamp(&self) -> Option<std::time::SystemTime> {
1620
        self.timestamp.map(|t| t.0)
1620
    }
}
impl DirSource {
    /// Parse a "dir-source" item
1178
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Self> {
1178
        if item.kwd() != NetstatusKwd::DIR_SOURCE {
            return Err(
                Error::from(internal!("Bad keyword {:?} on dir-source", item.kwd()))
                    .at_pos(item.pos()),
            );
1178
        }
1178
        let nickname = item
1178
            .required_arg(0)?
1178
            .parse()
1178
            .map_err(|e: InvalidNickname| {
                EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
            })?;
1178
        let identity = item.parse_arg(1)?;
1178
        let hostname = item
1178
            .required_arg(2)?
1178
            .parse()
1178
            .map_err(|e: InvalidInternetHost| {
                EK::BadArgument.at_pos(item.pos()).with_msg(e.to_string())
            })?;
1178
        let ip = item.parse_arg(3)?;
1178
        let dir_port = item.parse_arg(4)?;
1178
        let or_port = item.parse_arg(5)?;
1178
        Ok(DirSource {
1178
            nickname,
1178
            identity,
1178
            hostname,
1178
            ip,
1178
            dir_port,
1178
            or_port,
1178
            __non_exhaustive: (),
1178
        })
1178
    }
}
impl ConsensusAuthorityEntry {
    /// Parse a single ConsensusAuthorityEntry from a voter info section.
1178
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<ConsensusAuthorityEntry> {
        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)]
1178
        let first = sec.first_item().unwrap();
1178
        if first.kwd() != DIR_SOURCE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} at start of voter info",
                first.kwd()
            ))
            .at_pos(first.pos()));
1178
        }
1178
        let dir_source = DirSource::from_item(sec.required(DIR_SOURCE)?)?;
1178
        let contact = sec.required(CONTACT)?;
        // Ideally we would parse_args_as_str but that requires us to
        // impl From<InvalidContactInfo> for crate::Error which is wrong
        // because many it's a footgun which lets you just write ? here
        // resulting in lack of position information.
        // (This is a general problem with the error handling in crate::parse.)
1178
        let contact = contact
1178
            .args_as_str()
1178
            .parse()
1178
            .map_err(|err: InvalidContactInfo| {
                EK::BadArgument
                    .with_msg(err.to_string())
                    .at_pos(contact.pos())
            })?;
1178
        let vote_digest = sec.required(VOTE_DIGEST)?.parse_arg::<B16U>(0)?;
1178
        Ok(ConsensusAuthorityEntry {
1178
            dir_source,
1178
            contact,
1178
            vote_digest,
1178
            __non_exhaustive: (),
1178
        })
1178
    }
}
impl Default for RelayWeight {
2
    fn default() -> RelayWeight {
2
        RelayWeight::Unmeasured(0)
2
    }
}
impl RelayWeight {
    /// Parse a routerweight from a "w" line.
2126
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<RelayWeight> {
2126
        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
            );
2120
        }
2120
        let params = item.args_as_str().parse()?;
2118
        Self::from_net_params(&params).map_err(|e| e.at_pos(item.pos()))
2126
    }
    /// 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`.
2592
    fn from_net_params(params: &NetParams<u32>) -> Result<RelayWeight> {
2592
        let bw = params.params.get("Bandwidth");
2592
        let unmeas = params.params.get("Unmeasured");
2592
        let bw = match bw {
2
            None => return Ok(RelayWeight::Unmeasured(0)),
2590
            Some(b) => *b,
        };
2590
        match unmeas {
448
            None | Some(0) => Ok(RelayWeight::Measured(bw)),
2142
            Some(1) => Ok(RelayWeight::Unmeasured(bw)),
            _ => Err(EK::BadArgument.with_msg("unmeasured value")),
        }
2592
    }
}
/// `parse2` impls for types in this modulea
///
/// Separate module for a separate namespace.
mod parse2_impls {
    use super::*;
    pub(super) use parse2::{
        ArgumentError as AE, ArgumentStream, ErrorProblem as EP, ItemArgumentParseable,
        ItemValueParseable, NetdocParseableFields,
    };
    use std::result::Result;
    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 {
474
        fn from_unparsed(item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
474
            item.check_no_object()?;
474
            (|| {
474
                let params = item.args_copy().into_remaining().parse()?;
474
                Self::from_net_params(&params)
            })()
474
            .map_err(item.invalid_argument_handler("weights"))
474
        }
    }
    impl ItemValueParseable for rs::SoftwareVersion {
474
        fn from_unparsed(mut item: parse2::UnparsedItem<'_>) -> Result<Self, EP> {
474
            item.check_no_object()?;
474
            item.args_mut()
474
                .into_remaining()
474
                .parse()
474
                .map_err(item.invalid_argument_handler("version"))
474
        }
    }
    impl ItemArgumentParseable for IgnoredPublicationTimeSp {
460
        fn from_args(a: &mut ArgumentStream) -> Result<IgnoredPublicationTimeSp, AE> {
942
            let mut next_arg = || a.next().ok_or(AE::Missing);
460
            let _: &str = next_arg()?;
460
            let _: &str = next_arg()?;
460
            Ok(IgnoredPublicationTimeSp)
460
        }
    }
}
/// `encode` impls for types in this modulea
///
/// Separate module for a separate namespace.
mod encode_impls {
    use super::*;
    use std::result::Result;
    pub(crate) use {
        crate::encode::{ItemEncoder, ItemValueEncodable, NetdocEncodableFields},
        tor_error::Bug,
    };
    impl ItemValueEncodable for NetParams<i32> {
8
        fn write_item_value_onto(&self, mut out: ItemEncoder) -> Result<(), Bug> {
8
            for (k, v) in self.iter().collect::<BTreeSet<_>>() {
8
                if k.is_empty()
6
                    || k.chars()
17
                        .any(|c| c.is_whitespace() || c.is_control() || c == '=')
                {
                    // TODO torspec#401 see TODO in NetParams<T> definition
8
                    return Err(bad_api_usage!(
8
                        "tried to encode NetParms with unreasonable keyword {k:?}"
8
                    ));
                }
                out.args_raw_string(&format_args!("{k}={v}"));
            }
            Ok(())
8
        }
    }
}
impl Footer {
    /// Parse a directory footer from a footer section.
384
    fn from_section(sec: &Section<'_, NetstatusKwd>) -> Result<Footer> {
        use NetstatusKwd::*;
384
        sec.required(DIRECTORY_FOOTER)?;
384
        let weights = sec
384
            .maybe(BANDWIDTH_WEIGHTS)
384
            .args_as_str()
384
            .unwrap_or("")
384
            .parse()?;
382
        Ok(Footer { weights })
384
    }
}
/// `ProtoStatuses` parsing and encoding
///
/// Separate module for separate namespace
mod proto_statuses_parse2_encode {
    use super::encode_impls::*;
    use super::parse2_impls::*;
    use super::*;
    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 NetdocEncodableFields for ProtoStatuses {
            fn encode_fields(&self, out: &mut NetdocEncoder) -> Result<(), Bug> {
              $(
                self.$cr.$rr.write_item_value_onto(
                    out.item(stringify!([<$rr _ $cr _protocols>]))
                )?;
              )*
                Ok(())
            }
        }
    } } }
    impl_proto_statuses! {
        required client;
        required relay;
        recommended client;
        recommended relay;
    }
}
/// 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
1148
    fn from_item(item: &Item<'_, NetstatusKwd>) -> Result<Signature> {
1148
        if item.kwd() != NetstatusKwd::DIRECTORY_SIGNATURE {
            return Err(Error::from(internal!(
                "Wrong keyword {:?} for directory signature",
                item.kwd()
            ))
            .at_pos(item.pos()));
1148
        }
1148
        let (digest_algo, id_fp, sk_fp) = if item.n_args() > 2 {
            (
1140
                item.required_arg(0)?,
1140
                item.required_arg(1)?,
1140
                item.required_arg(2)?,
            )
        } else {
8
            ("sha1", item.required_arg(0)?, item.required_arg(1)?)
        };
1148
        let digest_algo = digest_algo.to_string().parse().void_unwrap();
1148
        let digest_algo = DigestAlgoInSignature(Some(digest_algo));
1148
        let id_fingerprint = id_fp.parse::<Fingerprint>()?.into();
1148
        let sk_fingerprint = sk_fp.parse::<Fingerprint>()?.into();
1148
        let key_ids = AuthCertKeyIds {
1148
            id_fingerprint,
1148
            sk_fingerprint,
1148
        };
1148
        let signature = item.obj("SIGNATURE")?;
1148
        Ok(Signature {
1148
            digest_algo,
1148
            key_ids,
1148
            signature,
1148
        })
1148
    }
    /// Return true if this signature has the identity key and signing key
    /// that match a given cert.
812
    fn matches_cert(&self, cert: &AuthCert) -> bool {
812
        cert.key_ids() == self.key_ids
812
    }
    /// If possible, find the right certificate for checking this signature
    /// from among a slice of certificates.
1192
    fn find_cert<'a>(&self, certs: &'a [AuthCert]) -> Option<&'a AuthCert> {
1286
        certs.iter().find(|&c| self.matches_cert(c))
1192
    }
    /// 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.
182
    fn check_signature(&self, signed_digest: &[u8], certs: &[AuthCert]) -> SigCheckResult {
182
        match self.find_cert(certs) {
58
            None => SigCheckResult::MissingCert,
124
            Some(cert) => {
124
                let key = cert.signing_key();
124
                match key.verify(signed_digest, &self.signature[..]) {
124
                    Ok(()) => SigCheckResult::Valid,
                    Err(_) => SigCheckResult::Invalid,
                }
            }
        }
182
    }
}
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.
336
    fn list_missing(&self, certs: &[AuthCert]) -> (usize, Vec<&Signature>) {
336
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
336
        let mut missing = Vec::new();
1010
        for sig in &self.signatures {
1010
            let id_fingerprint = &sig.key_ids.id_fingerprint;
1010
            if ok.contains(id_fingerprint) {
                continue;
1010
            }
1010
            if sig.find_cert(certs).is_some() {
186
                ok.insert(*id_fingerprint);
186
                continue;
824
            }
824
            missing.push(sig);
        }
336
        (ok.len(), missing)
336
    }
    /// Given a list of authority identity key fingerprints, return true if
    /// this signature group is _potentially_ well-signed according to those
    /// authorities.
280
    fn could_validate(&self, authorities: &[&RsaIdentity]) -> bool {
280
        let mut signed_by: HashSet<RsaIdentity> = HashSet::new();
844
        for sig in &self.signatures {
844
            let id_fp = &sig.key_ids.id_fingerprint;
844
            if signed_by.contains(id_fp) {
                // Already found this in the list.
                continue;
844
            }
844
            if authorities.contains(&id_fp) {
450
                signed_by.insert(*id_fp);
450
            }
        }
280
        signed_by.len() > (authorities.len() / 2)
280
    }
    /// 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.
60
    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.
60
        let mut ok: HashSet<RsaIdentity> = HashSet::new();
182
        for sig in &self.signatures {
182
            let id_fingerprint = &sig.key_ids.id_fingerprint;
182
            if ok.contains(id_fingerprint) {
                // We already checked at least one signature using this
                // authority's identity fingerprint.
                continue;
182
            }
            use DirectorySignatureHashAlgo as DSHA;
            use KeywordOrString as KOS;
182
            let d: Option<&[u8]> = match sig.digest_algo.algorithm() {
183
                KOS::Known(DSHA::Sha256) => self.sha256.as_ref().map(|a| &a[..]),
12
                KOS::Known(DSHA::Sha1) => self.sha1.as_ref().map(|a| &a[..]),
                _ => None, // We don't know how to find this digest.
            };
182
            if d.is_none() {
                // We don't support this kind of digest for this kind
                // of document.
                continue;
182
            }
            // Unwrap should be safe because of above `d.is_none()` check
            #[allow(clippy::unwrap_used)]
182
            match sig.check_signature(d.as_ref().unwrap(), certs) {
124
                SigCheckResult::Valid => {
124
                    ok.insert(*id_fingerprint);
124
                }
58
                _ => continue,
            }
        }
60
        ok.len() > (n_authorities / 2)
60
    }
}
#[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(feature = "incomplete")]
    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");
    const PLAIN_CERTS: &str = include_str!("../../testdata2/cached-certs");
    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]
    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(feature = "incomplete")]
    fn parse2_vote() -> anyhow::Result<()> {
        let file = "testdata2/v3-status-votes--1";
        let text = fs::read_to_string(file)?;
        // TODO DIRAUTH 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());
        for bad_kw in ["What=The", "", "\n", "\0"] {
            let p = [(bad_kw, 42)].into_iter().collect::<NetParams<i32>>();
            let mut d = NetdocEncoder::new();
            let d = (|| {
                let i = d.item("bad-psrams");
                p.write_item_value_onto(i)?;
                d.finish()
            })();
            let _: tor_error::Bug = d.expect_err(bad_kw);
        }
    }
    #[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);
    }
}