Lines
100 %
Functions
95.65 %
Branches
//! Facilities to construct microdescriptor objects.
//!
//! (These are only for testing right now, since we don't yet
//! support encoding.)
use super::Microdesc;
use crate::types::family::RelayFamily;
use crate::types::policy::PortPolicy;
use crate::{BuildError as Error, BuildResult as Result, Error as ParseError};
use tor_llcrypto::pk::{curve25519, ed25519};
use rand::Rng;
/// A builder object used to construct a microdescriptor.
///
/// Create one of these with the [`Microdesc::builder`] method.
/// This facility is only enabled when the crate is built with
/// the `build_docs` feature.
#[cfg_attr(docsrs, doc(cfg(feature = "build_docs")))]
#[derive(Debug, Clone)]
pub struct MicrodescBuilder {
/// The ntor onion key we'll be using.
/// See [`Microdesc::ntor_onion_key`].
ntor_onion_key: Option<curve25519::PublicKey>,
/// The relay family we'll be using.
/// See [`Microdesc::family`].
family: RelayFamily,
/// See [`Microdesc::ipv4_policy`]
ipv4_policy: PortPolicy,
/// See [`Microdesc::ipv6_policy`]
ipv6_policy: PortPolicy,
/// See [`Microdesc::ed25519_id`]
ed25519_id: Option<ed25519::Ed25519Identity>,
}
impl MicrodescBuilder {
/// Create a new MicrodescBuilder.
pub(crate) fn new() -> Self {
MicrodescBuilder {
ntor_onion_key: None,
family: RelayFamily::new(),
ipv4_policy: PortPolicy::new_reject_all(),
ipv6_policy: PortPolicy::new_reject_all(),
ed25519_id: None,
/// Set the ntor onion key.
/// This key is required for a well-formed microdescriptor.
pub fn ntor_key(&mut self, key: curve25519::PublicKey) -> &mut Self {
self.ntor_onion_key = Some(key);
self
/// Set the ed25519 identity key.
pub fn ed25519_id(&mut self, key: ed25519::Ed25519Identity) -> &mut Self {
self.ed25519_id = Some(key);
/// Set the family of this relay.
/// By default, this family is empty.
pub fn family(&mut self, family: RelayFamily) -> &mut Self {
self.family = family;
/// Set the ipv4 exit policy of this relay.
/// By default, this policy is `reject 1-65535`.
pub fn ipv4_policy(&mut self, policy: PortPolicy) -> &mut Self {
self.ipv4_policy = policy;
/// Set the ipv6 exit policy of this relay.
pub fn ipv6_policy(&mut self, policy: PortPolicy) -> &mut Self {
self.ipv6_policy = policy;
/// Set the family of this relay based on parsing a string.
pub fn parse_family(&mut self, family: &str) -> Result<&mut Self> {
Ok(self.family(family.parse()?))
/// Set the ipv4 exit policy of this relay based on parsing
/// a string.
pub fn parse_ipv4_policy(&mut self, policy: &str) -> Result<&mut Self> {
Ok(self.ipv4_policy(policy.parse().map_err(ParseError::from)?))
/// Set the ipv6 exit policy of this relay based on parsing
pub fn parse_ipv6_policy(&mut self, policy: &str) -> Result<&mut Self> {
Ok(self.ipv6_policy(policy.parse().map_err(ParseError::from)?))
/// Try to build a microdescriptor from the settings on this builder.
/// Give an error if any required fields are not set.
/// # Limitations
/// This is only for testing, since it does actually encode the
/// information in a string, and since it sets the sha256 digest
/// field at random.
/// In the future, when we have authority support, we'll need an
/// encoder function instead.
pub fn testing_md(&self) -> Result<Microdesc> {
let ntor_onion_key = self
.ntor_onion_key
.ok_or(Error::CannotBuild("Missing ntor_key"))?;
let ed25519_id = self
.ed25519_id
.ok_or(Error::CannotBuild("Missing ed25519_id"))?;
// We generate a random sha256 value here, since this is only
// for testing.
let sha256 = rand::thread_rng().gen();
Ok(Microdesc {
sha256,
ntor_onion_key,
family: self.family.clone().intern(),
ipv4_policy: self.ipv4_policy.clone().intern(),
ipv6_policy: self.ipv6_policy.clone().intern(),
ed25519_id,
})
#[cfg(test)]
mod test {
// @@ begin test lint list maintained by maint/add_warning @@
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
#[test]
fn minimal() {
let ed: ed25519::Ed25519Identity = (*b"this is not much of a public key").into();
let ntor: curve25519::PublicKey = (*b"but fortunately nothing cares...").into();
let md = MicrodescBuilder::new()
.ed25519_id(ed)
.ntor_key(ntor)
.testing_md()
.unwrap();
assert_eq!(md.ed25519_id(), &ed);
assert_eq!(md.ntor_key(), &ntor);
assert_eq!(md.family().members().count(), 0);
fn maximal() -> Result<()> {
let md = Microdesc::builder()
.parse_ipv4_policy("accept 80,443")?
.parse_ipv6_policy("accept 22-80")?
.parse_family("$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa $bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")?
assert_eq!(md.family().members().count(), 2);
assert!(md.family().contains(&[0xaa; 20].into()));
assert!(md.ipv4_policy().allows_port(443));
assert!(md.ipv4_policy().allows_port(80));
assert!(!md.ipv4_policy().allows_port(55));
assert!(!md.ipv6_policy().allows_port(443));
assert!(md.ipv6_policy().allows_port(80));
assert!(md.ipv6_policy().allows_port(55));
Ok(())
fn failing() {
{
let mut builder = Microdesc::builder();
builder.ed25519_id(ed);
assert!(builder.testing_md().is_err()); // no ntor
builder.ntor_key(ntor);
assert!(builder.testing_md().is_err()); // no ed id.