diff --git a/crates/arti-client/src/client.rs b/crates/arti-client/src/client.rs index ef50109d0..7d7df7a1c 100644 --- a/crates/arti-client/src/client.rs +++ b/crates/arti-client/src/client.rs @@ -23,6 +23,10 @@ use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; use crate::{Error, Result}; +#[cfg(feature = "async-std")] +use tor_rtcompat::async_std::AsyncStdRuntime; +#[cfg(feature = "tokio")] +use tor_rtcompat::tokio::TokioRuntimeHandle; use tracing::{debug, error, info, warn}; /// An active client session on the Tor network. @@ -166,10 +170,49 @@ impl ConnectPrefs { // TODO: Add some way to be IPFlexible, and require exit to support both. } -impl TorClient { - /// Bootstrap a network connection configured by `dir_cfg` and `circ_cfg`. +#[cfg(feature = "tokio")] +impl TorClient { + /// Bootstrap a connection to the Tor network, using the current Tokio runtime. /// - /// Return a client once there is enough directory material to + /// Returns a client once there is enough directory material to + /// connect safely over the Tor network. + /// + /// This is a convenience wrapper around [`TorClient::bootstrap`]. + /// + /// # Panics + /// + /// Panics if called outside of the context of a Tokio runtime. + pub async fn bootstrap_with_tokio( + config: TorClientConfig, + ) -> Result> { + let rt = tor_rtcompat::tokio::current_runtime().expect("called outside of Tokio runtime"); + Self::bootstrap(rt, config).await + } +} + +#[cfg(feature = "async-std")] +impl TorClient { + /// Bootstrap a connection to the Tor network, using the current async-std runtime. + /// + /// Returns a client once there is enough directory material to + /// connect safely over the Tor network. + /// + /// This is a convenience wrapper around [`TorClient::bootstrap`]. + pub async fn bootstrap_with_async_std( + config: TorClientConfig, + ) -> Result> { + // FIXME(eta): not actually possible for this to fail + let rt = + tor_rtcompat::async_std::current_runtime().expect("failed to get async-std runtime"); + Self::bootstrap(rt, config).await + } +} + +impl TorClient { + /// 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. pub async fn bootstrap(runtime: R, config: TorClientConfig) -> Result> { let circ_cfg = config.get_circmgr_config()?; diff --git a/crates/arti-client/src/config.rs b/crates/arti-client/src/config.rs index 383a83f13..7330c13f4 100644 --- a/crates/arti-client/src/config.rs +++ b/crates/arti-client/src/config.rs @@ -8,9 +8,7 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use std::time::Duration; -use tor_config::CfgPath; - -pub use tor_config::ConfigBuildError; +pub use tor_config::{CfgPath, ConfigBuildError}; /// Types for configuring how Tor circuits are built. pub mod circ { diff --git a/crates/arti-client/src/lib.rs b/crates/arti-client/src/lib.rs index 470d965c7..d114818e8 100644 --- a/crates/arti-client/src/lib.rs +++ b/crates/arti-client/src/lib.rs @@ -182,7 +182,7 @@ pub use client::{ConnectPrefs, TorClient}; pub use config::TorClientConfig; pub use tor_circmgr::IsolationToken; -pub use tor_proto::stream::DataStream; +pub use tor_proto::stream::{DataReader, DataStream, DataWriter}; mod err; pub use err::Error; diff --git a/crates/tor-proto/src/stream.rs b/crates/tor-proto/src/stream.rs index 1c92a60a0..0fa24bf94 100644 --- a/crates/tor-proto/src/stream.rs +++ b/crates/tor-proto/src/stream.rs @@ -14,7 +14,7 @@ mod params; mod raw; mod resolve; -pub use data::DataStream; +pub use data::{DataReader, DataStream, DataWriter}; pub use params::StreamParameters; pub use raw::StreamReader; pub use resolve::ResolveStream; diff --git a/crates/tor-proto/src/stream/data.rs b/crates/tor-proto/src/stream/data.rs index 301be6443..79e6e06a8 100644 --- a/crates/tor-proto/src/stream/data.rs +++ b/crates/tor-proto/src/stream/data.rs @@ -74,6 +74,12 @@ use tor_cell::relaycell::msg::{Data, RelayMsg}; /// data has actually been sent, you need to make sure that /// [`AsyncWrite::poll_flush`] runs to completion: probably via /// [`AsyncWriteExt::flush`](futures::io::AsyncWriteExt::flush). +/// +/// # Splitting the type +/// +/// This type is internally composed of a [`DataReader`] and a [`DataWriter`]; the +/// `DataStream::split` method can be used to split it into those two parts, for more +/// convenient usage with e.g. stream combinators. // # Semver note // // Note that this type is re-exported as a part of the public API of @@ -86,11 +92,22 @@ pub struct DataStream { r: DataReader, } -/// Wrapper for the Write part of a DataStream. +/// The write half of a [`DataStream`], implementing [`futures::io::AsyncWrite`]. /// -/// Note that this implementation writes Tor cells lazily, so it is -/// essential to flush the stream when you need the data to be sent -/// out right away. +/// See the [`DataStream`] docs for more information. In particular, note +/// that this writer requires `poll_flush` to complete in order to guarantee that +/// all data has been written. +/// +/// # Usage with Tokio +/// +/// If the `tokio` crate feature is enabled, this type also implements +/// [`tokio::io::AsyncWrite`](tokio_crate::io::AsyncWrite) for easier integration +/// with code that expects that trait. +// # Semver note +// +// Note that this type is re-exported as a part of the public API of +// the `arti-client` crate. Any changes to its API here in +// `tor-proto` need to be reflected above. pub struct DataWriter { /// Internal state for this writer /// @@ -100,7 +117,21 @@ pub struct DataWriter { state: Option, } -/// Wrapper for the Read part of a DataStream +/// The read half of a [`DataStream`], implementing [`futures::io::AsyncRead`]. +/// +/// See the [`DataStream`] docs for more information. +/// +/// # Usage with Tokio +/// +/// If the `tokio` crate feature is enabled, this type also implements +/// [`tokio::io::AsyncRead`](tokio_crate::io::AsyncRead) for easier integration +/// with code that expects that trait. +// +// # Semver note +// +// Note that this type is re-exported as a part of the public API of +// the `arti-client` crate. Any changes to its API here in +// `tor-proto` need to be reflected above. pub struct DataReader { /// Internal state for this reader. /// diff --git a/crates/tor-rtcompat/src/async_std.rs b/crates/tor-rtcompat/src/async_std.rs index 784929e23..891e3db39 100644 --- a/crates/tor-rtcompat/src/async_std.rs +++ b/crates/tor-rtcompat/src/async_std.rs @@ -1,20 +1,23 @@ //! Entry points for use with async_std runtimes. pub use crate::impls::async_std::create_runtime as create_async_std_runtime; -use crate::{Runtime, SpawnBlocking}; +use crate::SpawnBlocking; -/// Return a new async-std-based [`Runtime`]. +/// A [`Runtime`](crate::Runtime) powered by async-std. +pub use async_executors::AsyncStd as AsyncStdRuntime; + +/// Return a new async-std-based [`Runtime`](crate::Runtime). /// /// Generally you should call this function only once, and then use /// [`Clone::clone()`] to create additional references to that /// runtime. -pub fn create_runtime() -> std::io::Result { +pub fn create_runtime() -> std::io::Result { Ok(create_async_std_runtime()) } /// Try to return an instance of the currently running async_std -/// [`Runtime`]. -pub fn current_runtime() -> std::io::Result { +/// [`Runtime`](crate::Runtime). +pub fn current_runtime() -> std::io::Result { // In async_std, the runtime is a global singleton. create_runtime() } @@ -22,7 +25,7 @@ pub fn current_runtime() -> std::io::Result { /// Run a test function using a freshly created async_std runtime. pub fn test_with_runtime(func: P) -> O where - P: FnOnce(async_executors::AsyncStd) -> F, + P: FnOnce(AsyncStdRuntime) -> F, F: futures::Future, { let runtime = create_async_std_runtime(); diff --git a/crates/tor-rtcompat/src/tokio.rs b/crates/tor-rtcompat/src/tokio.rs index 673e3c1dd..19653624c 100644 --- a/crates/tor-rtcompat/src/tokio.rs +++ b/crates/tor-rtcompat/src/tokio.rs @@ -32,7 +32,7 @@ pub fn create_runtime() -> std::io::Result { /// /// Once you have a runtime returned by this function, you should /// just create more handles to it via [`Clone`]. -pub fn current_runtime() -> std::io::Result { +pub fn current_runtime() -> std::io::Result { let handle = tokio_crate::runtime::Handle::try_current() .map_err(|e| IoError::new(ErrorKind::Other, e))?; Ok(TokioRuntimeHandle::new(handle))