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, RelayFamilyId};
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::family_ids`]
33
    family_ids: Vec<RelayFamilyId>,
34
    /// See [`Microdesc::ipv4_policy`]
35
    ipv4_policy: PortPolicy,
36
    /// See [`Microdesc::ipv6_policy`]
37
    ipv6_policy: PortPolicy,
38
    /// See [`Microdesc::ed25519_id`]
39
    ed25519_id: Option<ed25519::Ed25519Identity>,
40
}
41

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

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

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

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

            
79
    /// Add `id` as a family ID for this relay.
80
1880
    pub fn add_family_id(&mut self, id: RelayFamilyId) -> &mut Self {
81
1880
        self.family_ids.push(id);
82
1880
        self
83
1880
    }
84

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

            
93
    /// Set the ipv6 exit policy of this relay.
94
    ///
95
    /// By default, this policy is `reject 1-65535`.
96
1882
    pub fn ipv6_policy(&mut self, policy: PortPolicy) -> &mut Self {
97
1882
        self.ipv6_policy = policy;
98
1882
        self
99
1882
    }
100

            
101
    /// Set the family of this relay based on parsing a string.
102
2
    pub fn parse_family(&mut self, family: &str) -> Result<&mut Self> {
103
2
        Ok(self.family(family.parse()?))
104
2
    }
105

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

            
114
    /// Set the ipv6 exit policy of this relay based on parsing
115
    /// a string.
116
    ///
117
    /// By default, this policy is `reject 1-65535`.
118
1882
    pub fn parse_ipv6_policy(&mut self, policy: &str) -> Result<&mut Self> {
119
1882
        Ok(self.ipv6_policy(policy.parse().map_err(ParseError::from)?))
120
1882
    }
121

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

            
142
        // We generate a random sha256 value here, since this is only
143
        // for testing.
144
345924
        let sha256 = rand::rng().random();
145
345924

            
146
345924
        Ok(Microdesc {
147
345924
            sha256,
148
345924
            ntor_onion_key,
149
345924
            family: self.family.clone().intern(),
150
345924
            family_ids: self.family_ids.clone(),
151
345924
            ipv4_policy: self.ipv4_policy.clone().intern(),
152
345924
            ipv6_policy: self.ipv6_policy.clone().intern(),
153
345924
            ed25519_id,
154
345924
        })
155
345928
    }
156
}
157

            
158
#[cfg(test)]
159
mod test {
160
    // @@ begin test lint list maintained by maint/add_warning @@
161
    #![allow(clippy::bool_assert_comparison)]
162
    #![allow(clippy::clone_on_copy)]
163
    #![allow(clippy::dbg_macro)]
164
    #![allow(clippy::mixed_attributes_style)]
165
    #![allow(clippy::print_stderr)]
166
    #![allow(clippy::print_stdout)]
167
    #![allow(clippy::single_char_pattern)]
168
    #![allow(clippy::unwrap_used)]
169
    #![allow(clippy::unchecked_duration_subtraction)]
170
    #![allow(clippy::useless_vec)]
171
    #![allow(clippy::needless_pass_by_value)]
172
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
173
    use super::*;
174

            
175
    #[test]
176
    fn minimal() {
177
        let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
178
        let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
179

            
180
        let md = MicrodescBuilder::new()
181
            .ed25519_id(ed)
182
            .ntor_key(ntor)
183
            .testing_md()
184
            .unwrap();
185

            
186
        assert_eq!(md.ed25519_id(), &ed);
187
        assert_eq!(md.ntor_key(), &ntor);
188

            
189
        assert_eq!(md.family().members().count(), 0);
190
    }
191

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

            
197
        let md = Microdesc::builder()
198
            .ed25519_id(ed)
199
            .ntor_key(ntor)
200
            .parse_ipv4_policy("accept 80,443")?
201
            .parse_ipv6_policy("accept 22-80")?
202
            .parse_family("$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")?
203
            .testing_md()
204
            .unwrap();
205

            
206
        assert_eq!(md.family().members().count(), 2);
207
        assert!(md.family().contains(&[0xaa; 20].into()));
208

            
209
        assert!(md.ipv4_policy().allows_port(443));
210
        assert!(md.ipv4_policy().allows_port(80));
211
        assert!(!md.ipv4_policy().allows_port(55));
212

            
213
        assert!(!md.ipv6_policy().allows_port(443));
214
        assert!(md.ipv6_policy().allows_port(80));
215
        assert!(md.ipv6_policy().allows_port(55));
216

            
217
        Ok(())
218
    }
219

            
220
    #[test]
221
    fn failing() {
222
        let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
223
        let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
224

            
225
        {
226
            let mut builder = Microdesc::builder();
227
            builder.ed25519_id(ed);
228
            assert!(builder.testing_md().is_err()); // no ntor
229
        }
230

            
231
        {
232
            let mut builder = Microdesc::builder();
233
            builder.ntor_key(ntor);
234
            assert!(builder.testing_md().is_err()); // no ed id.
235
        }
236
    }
237
}