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, RelayFamilyIds};
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: RelayFamilyIds,
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
468528
    pub(crate) fn new() -> Self {
45
468528
        MicrodescBuilder {
46
468528
            ntor_onion_key: None,
47
468528
            family: RelayFamily::new(),
48
468528
            family_ids: RelayFamilyIds::new(),
49
468528
            ipv4_policy: PortPolicy::new_reject_all(),
50
468528
            ipv6_policy: PortPolicy::new_reject_all(),
51
468528
            ed25519_id: None,
52
468528
        }
53
468528
    }
54

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

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

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

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

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

            
93
    /// Set the ipv6 exit policy of this relay.
94
    ///
95
    /// By default, this policy is `reject 1-65535`.
96
2122
    pub fn ipv6_policy(&mut self, policy: PortPolicy) -> &mut Self {
97
2122
        self.ipv6_policy = policy;
98
2122
        self
99
2122
    }
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
493962
    pub fn parse_ipv4_policy(&mut self, policy: &str) -> Result<&mut Self> {
111
493962
        Ok(self.ipv4_policy(policy.parse().map_err(ParseError::from)?))
112
493962
    }
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
2122
    pub fn parse_ipv6_policy(&mut self, policy: &str) -> Result<&mut Self> {
119
2122
        Ok(self.ipv6_policy(policy.parse().map_err(ParseError::from)?))
120
2122
    }
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
479128
    pub fn testing_md(&self) -> Result<Microdesc> {
135
479128
        let ntor_onion_key = self
136
479128
            .ntor_onion_key
137
479128
            .ok_or(Error::CannotBuild("Missing ntor_key"))?
138
479126
            .into();
139
479126
        let ed25519_id = self
140
479126
            .ed25519_id
141
479126
            .ok_or(Error::CannotBuild("Missing ed25519_id"))?
142
479124
            .into();
143

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

            
148
479124
        Ok(Microdesc {
149
479124
            // Including an empty onion_key is totally fine and valid.
150
479124
            onion_key: Default::default(),
151
479124
            sha256,
152
479124
            ntor_onion_key,
153
479124
            family: self.family.clone().intern(),
154
479124
            family_ids: self.family_ids.clone(),
155
479124
            ipv4_policy: self.ipv4_policy.clone().intern(),
156
479124
            ipv6_policy: self.ipv6_policy.clone().intern(),
157
479124
            ed25519_id,
158
479124
        })
159
479128
    }
160
}
161

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

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

            
184
        let md = MicrodescBuilder::new()
185
            .ed25519_id(ed)
186
            .ntor_key(ntor)
187
            .testing_md()
188
            .unwrap();
189

            
190
        assert_eq!(md.ed25519_id(), &ed);
191
        assert_eq!(md.ntor_key(), &ntor);
192

            
193
        assert_eq!(md.family().members().count(), 0);
194
    }
195

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

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

            
210
        assert_eq!(md.family().members().count(), 2);
211
        assert!(md.family().contains(&[0xaa; 20].into()));
212

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

            
217
        assert!(!md.ipv6_policy().allows_port(443));
218
        assert!(md.ipv6_policy().allows_port(80));
219
        assert!(md.ipv6_policy().allows_port(55));
220

            
221
        Ok(())
222
    }
223

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

            
229
        {
230
            let mut builder = Microdesc::builder();
231
            builder.ed25519_id(ed);
232
            assert!(builder.testing_md().is_err()); // no ntor
233
        }
234

            
235
        {
236
            let mut builder = Microdesc::builder();
237
            builder.ntor_key(ntor);
238
            assert!(builder.testing_md().is_err()); // no ed id.
239
        }
240
    }
241
}