Make a TorClientBuilder API.

This is a defensive API choice to protect against the possibility
that we'll want to add a bunch of other non-config options in
the future.

Closes #350
This commit is contained in:
Nick Mathewson 2022-02-18 15:28:31 -05:00
parent b4c0bd6ef3
commit c910226b55
5 changed files with 117 additions and 51 deletions

View File

@ -1,5 +1,5 @@
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())

View File

@ -0,0 +1,78 @@
//! 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 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)
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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())?;
}