1
//! Routerstatus-specific parts of networkstatus parsing.
2
//!
3
//! This is a private module; relevant pieces are re-exported by its
4
//! parent.
5

            
6
#[cfg(feature = "build_docs")]
7
pub(crate) mod build;
8
pub(crate) mod md;
9
pub(crate) mod plain;
10
pub(crate) mod vote;
11

            
12
use super::{ConsensusFlavor, ConsensusMethods, consensus_methods_comma_separated};
13
use crate::doc::netstatus::{
14
    IgnoredPublicationTimeSp, NetParams, NetstatusKwd, Protocols, RelayWeight, RelayWeightsItem,
15
    VoteRelayWeightsItem,
16
};
17
use crate::encode::{EncodeOrd, ItemEncoder};
18
use crate::parse::parser::Section;
19
use crate::parse2::ItemArgumentParseable;
20
use crate::types::misc::*;
21
use crate::types::policy::PortPolicy;
22
use crate::types::relay_flags::{self, DocRelayFlags, RelayFlag, RelayFlags};
23
use crate::types::version::TorVersion;
24
use crate::{Error, NetdocErrorKind as EK, Result};
25
use derive_deftly::Deftly;
26
use itertools::chain;
27
use std::cmp::Ordering;
28
use std::result::Result as StdResult;
29
use std::{net, time};
30
use tor_basic_utils::intern::{Intern, InternCache};
31
use tor_error::{Bug, internal};
32
use tor_llcrypto::pk::rsa::RsaIdentity;
33

            
34
/// A version as presented in a router status.
35
///
36
/// This can either be a parsed Tor version, or an unparsed string.
37
//
38
// TODO: This might want to merge, at some point, with routerdesc::RelayPlatform.
39
#[derive(Clone, Debug, Eq, PartialEq, Hash, derive_more::Display)]
40
#[non_exhaustive]
41
pub enum SoftwareVersion {
42
    /// A Tor version
43
    #[display("Tor {_0}")]
44
    CTor(TorVersion),
45

            
46
    /// A string we couldn't parse.
47
    ///
48
    /// This may be a C tor version that we couldn't parse,
49
    /// or some other software.
50
    Other(Intern<str>),
51
}
52

            
53
/// A cache of unparsable version strings.
54
///
55
/// We use this because we expect there not to be very many distinct versions of
56
/// relay software in existence.
57
// TODO DIRAUTH: Improve the caching here.
58
static OTHER_VERSION_CACHE: InternCache<str> = InternCache::new();
59

            
60
/// `m` item in votes
61
///
62
/// <https://spec.torproject.org/dir-spec/consensus-formats.html#item:m>
63
///
64
/// This is different to the `m` line in microdesc consensuses.
65
/// Plain consensuses don't have `m` lines at all.
66
///
67
/// ### Non-invariants
68
///
69
///  * There may be overlapping or even contradictory information.
70
///  * It might not be sorted.
71
///    Users of the structure who need to emit reproducible document encodings.
72
///    must sort it.
73
///  * These non-invariants apply both within one instance of this struct,
74
///    and across multiple instances of it within a `RouterStatus`.
75
#[derive(Debug, Clone, Default, Eq, PartialEq, Ord, PartialOrd, Deftly)]
76
#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
77
#[non_exhaustive]
78
pub struct RouterStatusMdDigestsVote {
79
    /// The methods for which this document is applicable.
80
    #[deftly(netdoc(with = consensus_methods_comma_separated))]
81
    pub consensus_methods: ConsensusMethods,
82

            
83
    /// The various hashes of this document.
84
    pub digests: Vec<IdentifiedDigest>,
85
}
86

            
87
impl std::str::FromStr for SoftwareVersion {
88
    // This needs to stay infallible: if any version is unparsable,
89
    // then we may reject a consensus needlessly,
90
    // since authorities copy versions from what relays say.
91
    type Err = void::Void;
92

            
93
223416
    fn from_str(s: &str) -> StdResult<Self, void::Void> {
94
223416
        let mut elts = s.splitn(3, ' ');
95
223416
        if elts.next() == Some("Tor") {
96
223414
            if let Some(Ok(v)) = elts.next().map(str::parse) {
97
223414
                return Ok(SoftwareVersion::CTor(v));
98
            }
99
2
        }
100

            
101
2
        Ok(SoftwareVersion::Other(OTHER_VERSION_CACHE.intern_ref(s)))
102
223416
    }
103
}
104

            
105
/// Helper to decode a document digest in the format in which it
106
/// appears in a given kind of routerstatus.
107
trait FromRsString: Sized {
108
    /// Try to decode the given object.
109
    fn decode(s: &str) -> Result<Self>;
110
}
111

            
112
/// Implementation for parsing [`tor_protover::Protocols`] via its
113
/// [`from_str_c_compatible`](tor_protover::Protocols::from_str_c_compatible)
114
/// method.
115
///
116
/// We use this (for now) for parsing routerstatuses _and nothing else_.
117
/// It's okay to be strict about router descriptors and required/recommended versions,
118
/// but if we are strict about these lines in a routerstatus, we run the risk of rejecting
119
/// stuff that the C tor authorities thought was okay.
120
mod protovers_flexible {
121
    use tor_error::Bug;
122
    use tor_protover::Protocols;
123

            
124
    use crate::{
125
        encode::{ItemEncoder, ItemValueEncodable as _},
126
        parse2::{ErrorProblem, UnparsedItem},
127
    };
128

            
129
    /// Parse a [`Protocols`] using [`Protocols::from_str_c_compatible`].
130
    #[expect(clippy::needless_pass_by_value)]
131
221369
    pub(super) fn from_unparsed(item: UnparsedItem<'_>) -> Result<Protocols, ErrorProblem> {
132
221369
        item.check_no_object()?;
133
221369
        Protocols::from_str_c_compatible(item.args_copy().into_remaining())
134
221369
            .map_err(item.invalid_argument_handler("protocols"))
135
221369
    }
136

            
137
    /// Encode a [`Protocols`] in the usual manner.
138
    ///
139
    /// (We have to define this because there is no "parse_with", only a "with" that overrides
140
    /// parsing _and_ encoding.)
141
36
    pub(super) fn write_item_value_onto(
142
36
        protocols: &Protocols,
143
36
        out: ItemEncoder,
144
36
    ) -> Result<(), Bug> {
145
36
        protocols.write_item_value_onto(out)
146
36
    }
147
}