tor_netdoc/doc/microdesc/
build.rs

1//! Facilities to construct microdescriptor objects.
2//!
3//! (These are only for testing right now, since we don't yet
4//! support encoding.)
5
6use super::Microdesc;
7
8use crate::types::family::{RelayFamily, RelayFamilyId};
9use crate::types::policy::PortPolicy;
10use crate::{BuildError as Error, BuildResult as Result, Error as ParseError};
11use tor_llcrypto::pk::{curve25519, ed25519};
12
13use 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)]
23pub 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
42impl MicrodescBuilder {
43    /// Create a new MicrodescBuilder.
44    pub(crate) fn new() -> Self {
45        MicrodescBuilder {
46            ntor_onion_key: None,
47            family: RelayFamily::new(),
48            family_ids: Vec::new(),
49            ipv4_policy: PortPolicy::new_reject_all(),
50            ipv6_policy: PortPolicy::new_reject_all(),
51            ed25519_id: None,
52        }
53    }
54
55    /// Set the ntor onion key.
56    ///
57    /// This key is required for a well-formed microdescriptor.
58    pub fn ntor_key(&mut self, key: curve25519::PublicKey) -> &mut Self {
59        self.ntor_onion_key = Some(key);
60        self
61    }
62
63    /// Set the ed25519 identity key.
64    ///
65    /// This key is required for a well-formed microdescriptor.
66    pub fn ed25519_id(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
67        self.ed25519_id = Some(key);
68        self
69    }
70
71    /// Set the family of this relay.
72    ///
73    /// By default, this family is empty.
74    pub fn family(&mut self, family: RelayFamily) -> &mut Self {
75        self.family = family;
76        self
77    }
78
79    /// Add `id` as a family ID for this relay.
80    pub fn add_family_id(&mut self, id: RelayFamilyId) -> &mut Self {
81        self.family_ids.push(id);
82        self
83    }
84
85    /// Set the ipv4 exit policy of this relay.
86    ///
87    /// By default, this policy is `reject 1-65535`.
88    pub fn ipv4_policy(&mut self, policy: PortPolicy) -> &mut Self {
89        self.ipv4_policy = policy;
90        self
91    }
92
93    /// Set the ipv6 exit policy of this relay.
94    ///
95    /// By default, this policy is `reject 1-65535`.
96    pub fn ipv6_policy(&mut self, policy: PortPolicy) -> &mut Self {
97        self.ipv6_policy = policy;
98        self
99    }
100
101    /// Set the family of this relay based on parsing a string.
102    pub fn parse_family(&mut self, family: &str) -> Result<&mut Self> {
103        Ok(self.family(family.parse()?))
104    }
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    pub fn parse_ipv4_policy(&mut self, policy: &str) -> Result<&mut Self> {
111        Ok(self.ipv4_policy(policy.parse().map_err(ParseError::from)?))
112    }
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    pub fn parse_ipv6_policy(&mut self, policy: &str) -> Result<&mut Self> {
119        Ok(self.ipv6_policy(policy.parse().map_err(ParseError::from)?))
120    }
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    pub fn testing_md(&self) -> Result<Microdesc> {
135        let ntor_onion_key = self
136            .ntor_onion_key
137            .ok_or(Error::CannotBuild("Missing ntor_key"))?;
138        let ed25519_id = self
139            .ed25519_id
140            .ok_or(Error::CannotBuild("Missing ed25519_id"))?;
141
142        // We generate a random sha256 value here, since this is only
143        // for testing.
144        let sha256 = rand::rng().random();
145
146        Ok(Microdesc {
147            sha256,
148            ntor_onion_key,
149            family: self.family.clone().intern(),
150            family_ids: self.family_ids.clone(),
151            ipv4_policy: self.ipv4_policy.clone().intern(),
152            ipv6_policy: self.ipv6_policy.clone().intern(),
153            ed25519_id,
154        })
155    }
156}
157
158#[cfg(test)]
159mod 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}