Commit 6ea0df16 authored by Nick Mathewson's avatar Nick Mathewson 🤹
Browse files

Merge branch 'client_builder' into 'main'

Make a TorClientBuilder API.

Closes #350

See merge request !337
parents 05257da7 fd288f90
Loading
Loading
Loading
Loading
+5 −7
Original line number Diff line number Diff line
use anyhow::Result;
use arti_client::{BootstrapBehavior, TorClient, TorClientConfig};
use arti_client::{TorClient, TorClientConfig};
use tokio_crate as tokio;
use tor_rtcompat::tokio::TokioNativeTlsRuntime;

@@ -29,12 +29,10 @@ pub fn get_tor_client() -> Result<TorClient<TokioNativeTlsRuntime>> {
        eprintln!("creating unbootstrapped Tor client");

        // Create an unbootstrapped Tor client. Bootstrapping will happen when the client is used,
        // since we specified `BootstrapBehavior::Ondemand`.
        Ok(TorClient::create_unbootstrapped(
            rt,
            config,
            BootstrapBehavior::OnDemand,
        )?)
        // since `BootstrapBehavior::OnDemand` is the default.
        Ok(TorClient::builder(rt)
            .config(config)
            .create_unbootstrapped()?)
    })?;

    Ok(client.clone())
+78 −0
Original line number Diff line number Diff line
//! Types for conveniently constructing TorClients.

#![allow(missing_docs, clippy::missing_docs_in_private_items)]

use crate::{err::ErrorDetail, BootstrapBehavior, Result, TorClient, TorClientConfig};
use tor_rtcompat::Runtime;

/// An object for constructing a [`TorClient`].
///
/// Returned by [`TorClient::builder()`].
#[derive(Debug, Clone)]
#[must_use]
pub struct TorClientBuilder<R> {
    /// The runtime for the client to use
    runtime: R,
    /// The client's configuration.
    config: TorClientConfig,
    /// How the client should behave when it is asked to do something on the Tor
    /// network before `bootstrap()` is called.
    bootstrap_behavior: BootstrapBehavior,
}

impl<R> TorClientBuilder<R> {
    /// Construct a new TorClientBuilder with the given runtime.
    pub(crate) fn new(runtime: R) -> Self {
        Self {
            runtime,
            config: TorClientConfig::default(),
            bootstrap_behavior: BootstrapBehavior::default(),
        }
    }

    /// Set the configuration for the `TorClient` under construction.
    ///
    /// If not called, then a compiled-in default configuration will be used.
    pub fn config(mut self, config: TorClientConfig) -> Self {
        self.config = config;
        self
    }

    /// Set the bootstrap behavior for the `TorClient` under construction.
    ///
    /// If not called, then the default ([`BootstrapBehavior::OnDemand`]) will
    /// be used.
    pub fn bootstrap_behavior(mut self, bootstrap_behavior: BootstrapBehavior) -> Self {
        self.bootstrap_behavior = bootstrap_behavior;
        self
    }
}

impl<R: Runtime> TorClientBuilder<R> {
    /// Create a `TorClient` from this builder, without automatically launching
    /// the bootstrap process.
    ///
    /// If you have left the default [`BootstrapBehavior`] in place, the client
    /// will bootstrap itself as soon any attempt is made to use it.  You can
    /// also bootstrap the client yourself by running its
    /// [`bootstrap()`](TorClient::bootstrap) method.
    ///
    /// If you have replaced the default behavior with [`BootstrapBehavior::Manual`],
    /// any attempts to use the client will fail with an error of kind
    /// [`ErrorKind::BootstrapRequired`](crate::ErrorKind::BootstrapRequired),
    /// until you have called [`TorClient::bootstrap`] yourself.  
    /// This option is useful if you wish to have control over the bootstrap
    /// process (for example, you might wish to avoid initiating network
    /// connections until explicit user confirmation is given).
    pub fn create_unbootstrapped(self) -> Result<TorClient<R>> {
        TorClient::create_inner(self.runtime, self.config, self.bootstrap_behavior)
            .map_err(ErrorDetail::into)
    }

    /// Create a TorClient from this builder, and try to bootstrap it.
    pub async fn create_bootstrapped(self) -> Result<TorClient<R>> {
        let r = self.create_unbootstrapped()?;
        r.bootstrap().await?;
        Ok(r)
    }
}
+28 −43
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;

use crate::err::ErrorDetail;
use crate::status;
use crate::{status, TorClientBuilder};
#[cfg(feature = "async-std")]
use tor_rtcompat::async_std::PreferredRuntime as PreferredAsyncStdRuntime;
#[cfg(feature = "tokio")]
@@ -88,8 +88,6 @@ pub struct TorClient<R: Runtime> {
}

/// Preferences for whether a [`TorClient`] should bootstrap on its own or not.
///
/// *[See the documentation for `create_unbootstrapped` for a full explanation.](TorClient::create_unbootstrapped)*
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BootstrapBehavior {
@@ -105,6 +103,12 @@ pub enum BootstrapBehavior {
    Manual,
}

impl Default for BootstrapBehavior {
    fn default() -> Self {
        BootstrapBehavior::OnDemand
    }
}

/// Preferences for how to route a stream over the Tor network.
#[derive(Debug, Clone, Default)]
pub struct StreamPrefs {
@@ -312,57 +316,31 @@ impl TorClient<PreferredAsyncStdRuntime> {
}

impl<R: Runtime> TorClient<R> {
    /// Return a new builder for creating TorClient objects.
    pub fn builder(runtime: R) -> TorClientBuilder<R> {
        TorClientBuilder::new(runtime)
    }

    /// Bootstrap a connection to the Tor network, using the provided `config` and `runtime` (a
    /// [`tor_rtcompat`] [`Runtime`](tor_rtcompat::Runtime)).
    ///
    /// Returns a client once there is enough directory material to
    /// connect safely over the Tor network.
    ///
    /// Consider using [`create_unbootstrapped`](TorClient::create_unbootstrapped)
    /// if you wish to create a client immediately, and defer bootstrapping until later.
    /// Consider using a [`TorClientBuilder`] for more fine-grained control.
    pub async fn create_bootstrapped(
        runtime: R,
        config: TorClientConfig,
    ) -> crate::Result<TorClient<R>> {
        let ret = TorClient::create_unbootstrapped(runtime, config, BootstrapBehavior::Manual)?;
        ret.bootstrap().await?;
        Ok(ret)
    }

    /// Create a `TorClient` without bootstrapping a connection to the network.
    ///
    /// The behaviour of this client depends on the value of `autobootstrap`.
    ///
    /// ## If `autobootstrap` is [`Manual`](BootstrapBehavior::Manual)
    ///
    /// If `autobootstrap` is set to `Manual`, the returned `TorClient` (and its clones) will never
    /// attempt to bootstrap themselves. You must manually call [`bootstrap`](TorClient::bootstrap)
    /// in order for the client(s) to become usable.
    ///
    /// Attempts to use the client (e.g. by creating connections or resolving hosts over the Tor
    /// network) before calling [`bootstrap`](TorClient::bootstrap) will fail, and
    /// return an error that has kind [`ErrorKind::BootstrapRequired`](crate::ErrorKind::BootstrapRequired).
    ///
    /// This option is useful if you wish to have control over the bootstrap process (for example,
    /// you might wish to avoid initiating network connections until explicit user confirmation
    /// is given).
    ///
    /// ## If `autobootstrap` is [`OnDemand`](BootstrapBehavior::OnDemand)
    ///
    /// If `autoboostrap` is set to `OnDemand`, the returned `TorClient` (and its clones) will
    /// automatically bootstrap before doing any operation that would require them to be
    /// bootstrapped.
    pub fn create_unbootstrapped(
        runtime: R,
        config: TorClientConfig,
        autobootstrap: BootstrapBehavior,
    ) -> crate::Result<Self> {
        TorClient::create_inner(runtime, config, autobootstrap).map_err(ErrorDetail::into)
        Self::builder(runtime)
            .config(config)
            .create_bootstrapped()
            .await
    }

    /// Implementation of `create_unbootstrapped`, split out in order to avoid manually specifying
    /// double error conversions.
    fn create_inner(
    pub(crate) fn create_inner(
        runtime: R,
        config: TorClientConfig,
        autobootstrap: BootstrapBehavior,
@@ -1085,7 +1063,11 @@ mod test {
            let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
                .build()
                .unwrap();
            let _ = TorClient::create_unbootstrapped(rt, cfg, BootstrapBehavior::Manual).unwrap();
            let _ = TorClient::builder(rt)
                .config(cfg)
                .bootstrap_behavior(BootstrapBehavior::Manual)
                .create_unbootstrapped()
                .unwrap();
        });
    }

@@ -1097,8 +1079,11 @@ mod test {
            let cfg = TorClientConfigBuilder::from_directories(state_dir, cache_dir)
                .build()
                .unwrap();
            let client =
                TorClient::create_unbootstrapped(rt, cfg, BootstrapBehavior::Manual).unwrap();
            let client = TorClient::builder(rt)
                .config(cfg)
                .bootstrap_behavior(BootstrapBehavior::Manual)
                .create_unbootstrapped()
                .unwrap();
            let result = client.connect("example.com:80").await;
            assert!(result.is_err());
            assert_eq!(result.err().unwrap().kind(), ErrorKind::BootstrapRequired);
+2 −0
Original line number Diff line number Diff line
@@ -190,12 +190,14 @@
#![deny(clippy::unwrap_used)]

mod address;
mod builder;
mod client;

pub mod config;
pub mod status;

pub use address::{DangerouslyIntoTorAddr, IntoTorAddr, TorAddr, TorAddrError};
pub use builder::TorClientBuilder;
pub use client::{BootstrapBehavior, StreamPrefs, TorClient};
pub use config::TorClientConfig;

+4 −1
Original line number Diff line number Diff line
@@ -113,7 +113,10 @@ async fn run<R: Runtime>(
    // for bootstrap to complete, rather than getting errors.
    use arti_client::BootstrapBehavior::OnDemand;
    use futures::FutureExt;
    let client = TorClient::create_unbootstrapped(runtime.clone(), client_config, OnDemand)?;
    let client = TorClient::builder(runtime.clone())
        .config(client_config)
        .bootstrap_behavior(OnDemand)
        .create_unbootstrapped()?;
    if arti_config.application().watch_configuration() {
        watch_cfg::watch_for_config_changes(config_sources, arti_config, client.clone())?;
    }