1
//! Facilities to construct microdescriptor objects.
2
//!
3
//! (These are only for testing right now, since we don't yet
4
//! support encoding.)
5

            
6
use super::Microdesc;
7

            
8
use crate::types::family::RelayFamily;
9
use crate::types::policy::PortPolicy;
10
use crate::{BuildError as Error, BuildResult as Result, Error as ParseError};
11
use tor_llcrypto::pk::{curve25519, ed25519};
12

            
13
use rand::Rng;
14

            
15
/// A builder object used to construct a microdescriptor.
16
///
17
/// Create one of these with the [`Microdesc::builder`] method.
18
///
19
/// This facility is only enabled when the crate is built with
20
/// the `build_docs` feature.
21
#[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))]
22
#[derive(Debug, Clone)]
23
pub struct MicrodescBuilder {
24
    /// The ntor onion key we'll be using.
25
    ///
26
    /// See [`Microdesc::ntor_onion_key`].
27
    ntor_onion_key: Option<curve25519::PublicKey>,
28
    /// The relay family we'll be using.
29
    ///
30
    /// See [`Microdesc::family`].
31
    family: RelayFamily,
32
    /// See [`Microdesc::ipv4_policy`]
33
    ipv4_policy: PortPolicy,
34
    /// See [`Microdesc::ipv6_policy`]
35
    ipv6_policy: PortPolicy,
36
    /// See [`Microdesc::ed25519_id`]
37
    ed25519_id: Option<ed25519::Ed25519Identity>,
38
}
39

            
40
impl MicrodescBuilder {
41
    /// Create a new MicrodescBuilder.
42
291928
    pub(crate) fn new() -> Self {
43
291928
        MicrodescBuilder {
44
291928
            ntor_onion_key: None,
45
291928
            family: RelayFamily::new(),
46
291928
            ipv4_policy: PortPolicy::new_reject_all(),
47
291928
            ipv6_policy: PortPolicy::new_reject_all(),
48
291928
            ed25519_id: None,
49
291928
        }
50
291928
    }
51

            
52
    /// Set the ntor onion key.
53
    ///
54
    /// This key is required for a well-formed microdescriptor.
55
291926
    pub fn ntor_key(&mut self, key: curve25519::PublicKey) -> &mut Self {
56
291926
        self.ntor_onion_key = Some(key);
57
291926
        self
58
291926
    }
59

            
60
    /// Set the ed25519 identity key.
61
    ///
62
    /// This key is required for a well-formed microdescriptor.
63
291926
    pub fn ed25519_id(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
64
291926
        self.ed25519_id = Some(key);
65
291926
        self
66
291926
    }
67

            
68
    /// Set the family of this relay.
69
    ///
70
    /// By default, this family is empty.
71
299261
    pub fn family(&mut self, family: RelayFamily) -> &mut Self {
72
299261
        self.family = family;
73
299261
        self
74
299261
    }
75

            
76
    /// Set the ipv4 exit policy of this relay.
77
    ///
78
    /// By default, this policy is `reject 1-65535`.
79
305042
    pub fn ipv4_policy(&mut self, policy: PortPolicy) -> &mut Self {
80
305042
        self.ipv4_policy = policy;
81
305042
        self
82
305042
    }
83

            
84
    /// Set the ipv6 exit policy of this relay.
85
    ///
86
    /// By default, this policy is `reject 1-65535`.
87
1642
    pub fn ipv6_policy(&mut self, policy: PortPolicy) -> &mut Self {
88
1642
        self.ipv6_policy = policy;
89
1642
        self
90
1642
    }
91

            
92
    /// Set the family of this relay based on parsing a string.
93
2
    pub fn parse_family(&mut self, family: &str) -> Result<&mut Self> {
94
2
        Ok(self.family(family.parse()?))
95
2
    }
96

            
97
    /// Set the ipv4 exit policy of this relay based on parsing
98
    /// a string.
99
    ///
100
    /// By default, this policy is `reject 1-65535`.
101
305042
    pub fn parse_ipv4_policy(&mut self, policy: &str) -> Result<&mut Self> {
102
305042
        Ok(self.ipv4_policy(policy.parse().map_err(ParseError::from)?))
103
305042
    }
104

            
105
    /// Set the ipv6 exit policy of this relay based on parsing
106
    /// a string.
107
    ///
108
    /// By default, this policy is `reject 1-65535`.
109
1642
    pub fn parse_ipv6_policy(&mut self, policy: &str) -> Result<&mut Self> {
110
1642
        Ok(self.ipv6_policy(policy.parse().map_err(ParseError::from)?))
111
1642
    }
112

            
113
    /// Try to build a microdescriptor from the settings on this builder.
114
    ///
115
    /// Give an error if any required fields are not set.
116
    ///
117
    /// # Limitations
118
    ///
119
    /// This is only for testing, since it does actually encode the
120
    /// information in a string, and since it sets the sha256 digest
121
    /// field at random.
122
    ///
123
    /// In the future, when we have authority support, we'll need an
124
    /// encoder function instead.
125
300128
    pub fn testing_md(&self) -> Result<Microdesc> {
126
300128
        let ntor_onion_key = self
127
300128
            .ntor_onion_key
128
300128
            .ok_or(Error::CannotBuild("Missing ntor_key"))?;
129
300126
        let ed25519_id = self
130
300126
            .ed25519_id
131
300126
            .ok_or(Error::CannotBuild("Missing ed25519_id"))?;
132

            
133
        // We generate a random sha256 value here, since this is only
134
        // for testing.
135
300124
        let sha256 = rand::thread_rng().gen();
136
300124

            
137
300124
        Ok(Microdesc {
138
300124
            sha256,
139
300124
            ntor_onion_key,
140
300124
            family: self.family.clone().intern(),
141
300124
            ipv4_policy: self.ipv4_policy.clone().intern(),
142
300124
            ipv6_policy: self.ipv6_policy.clone().intern(),
143
300124
            ed25519_id,
144
300124
        })
145
300128
    }
146
}
147

            
148
#[cfg(test)]
149
mod test {
150
    // @@ begin test lint list maintained by maint/add_warning @@
151
    #![allow(clippy::bool_assert_comparison)]
152
    #![allow(clippy::clone_on_copy)]
153
    #![allow(clippy::dbg_macro)]
154
    #![allow(clippy::mixed_attributes_style)]
155
    #![allow(clippy::print_stderr)]
156
    #![allow(clippy::print_stdout)]
157
    #![allow(clippy::single_char_pattern)]
158
    #![allow(clippy::unwrap_used)]
159
    #![allow(clippy::unchecked_duration_subtraction)]
160
    #![allow(clippy::useless_vec)]
161
    #![allow(clippy::needless_pass_by_value)]
162
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
163
    use super::*;
164

            
165
    #[test]
166
    fn minimal() {
167
        let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
168
        let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
169

            
170
        let md = MicrodescBuilder::new()
171
            .ed25519_id(ed)
172
            .ntor_key(ntor)
173
            .testing_md()
174
            .unwrap();
175

            
176
        assert_eq!(md.ed25519_id(), &ed);
177
        assert_eq!(md.ntor_key(), &ntor);
178

            
179
        assert_eq!(md.family().members().count(), 0);
180
    }
181

            
182
    #[test]
183
    fn maximal() -> Result<()> {
184
        let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
185
        let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
186

            
187
        let md = Microdesc::builder()
188
            .ed25519_id(ed)
189
            .ntor_key(ntor)
190
            .parse_ipv4_policy("accept 80,443")?
191
            .parse_ipv6_policy("accept 22-80")?
192
            .parse_family("$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")?
193
            .testing_md()
194
            .unwrap();
195

            
196
        assert_eq!(md.family().members().count(), 2);
197
        assert!(md.family().contains(&[0xaa; 20].into()));
198

            
199
        assert!(md.ipv4_policy().allows_port(443));
200
        assert!(md.ipv4_policy().allows_port(80));
201
        assert!(!md.ipv4_policy().allows_port(55));
202

            
203
        assert!(!md.ipv6_policy().allows_port(443));
204
        assert!(md.ipv6_policy().allows_port(80));
205
        assert!(md.ipv6_policy().allows_port(55));
206

            
207
        Ok(())
208
    }
209

            
210
    #[test]
211
    fn failing() {
212
        let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
213
        let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
214

            
215
        {
216
            let mut builder = Microdesc::builder();
217
            builder.ed25519_id(ed);
218
            assert!(builder.testing_md().is_err()); // no ntor
219
        }
220

            
221
        {
222
            let mut builder = Microdesc::builder();
223
            builder.ntor_key(ntor);
224
            assert!(builder.testing_md().is_err()); // no ed id.
225
        }
226
    }
227
}