use std::{fmt, io, path::Path, time::Duration};
use arti_client::{TorClient, config::TorClientConfigBuilder};
use tor_rtcompat::PreferredRuntime;
use tracing::debug;
#[cfg(feature = "lightwalletd-tonic-tls-webpki-roots")]
mod grpc;
pub mod http;
pub use arti_client::DormantMode;
#[derive(Clone, Copy, Debug)]
pub struct Timeouts {
connect: Duration,
request: Duration,
response_body: Duration,
grpc_keepalive_interval: Duration,
grpc_keepalive_timeout: Duration,
}
impl Default for Timeouts {
fn default() -> Self {
Self {
connect: Duration::from_secs(30),
request: Duration::from_secs(60),
response_body: Duration::from_secs(60),
grpc_keepalive_interval: Duration::from_secs(30),
grpc_keepalive_timeout: Duration::from_secs(20),
}
}
}
impl Timeouts {
#[must_use]
pub fn with_connect(mut self, timeout: Duration) -> Self {
self.connect = timeout;
self
}
#[must_use]
pub fn with_request(mut self, timeout: Duration) -> Self {
self.request = timeout;
self
}
#[must_use]
pub fn with_response_body(mut self, timeout: Duration) -> Self {
self.response_body = timeout;
self
}
#[must_use]
pub fn with_grpc_keepalive(mut self, interval: Duration, timeout: Duration) -> Self {
self.grpc_keepalive_interval = interval;
self.grpc_keepalive_timeout = timeout;
self
}
}
#[derive(Clone)]
pub struct Client {
inner: TorClient<PreferredRuntime>,
timeouts: Timeouts,
}
impl Client {
pub async fn create(
tor_dir: &Path,
with_permissions: impl FnOnce(&mut fs_mistrust::MistrustBuilder),
) -> Result<Self, Error> {
Self::create_with_timeouts(tor_dir, with_permissions, Timeouts::default()).await
}
pub async fn create_with_timeouts(
tor_dir: &Path,
with_permissions: impl FnOnce(&mut fs_mistrust::MistrustBuilder),
timeouts: Timeouts,
) -> Result<Self, Error> {
let runtime = PreferredRuntime::current()?;
if !tokio::fs::try_exists(tor_dir).await? {
return Err(Error::MissingTorDirectory);
}
let mut config_builder = TorClientConfigBuilder::from_directories(
tor_dir.join("arti-data"),
tor_dir.join("arti-cache"),
);
with_permissions(config_builder.storage().permissions());
let config = config_builder
.build()
.expect("all required fields initialized");
let client_builder = TorClient::with_runtime(runtime).config(config);
debug!("Bootstrapping Tor");
let inner = client_builder.create_bootstrapped().await?;
debug!("Tor bootstrapped");
Ok(Self { inner, timeouts })
}
async fn ensure_bootstrapped(&self) -> Result<(), Error> {
if !self.inner.bootstrap_status().ready_for_traffic() {
debug!("Re-bootstrapping Tor");
self.inner.bootstrap().await?;
debug!("Tor re-bootstrapped");
}
Ok(())
}
#[must_use]
pub fn isolated_client(&self) -> Self {
Self {
inner: self.inner.isolated_client(),
timeouts: self.timeouts,
}
}
pub fn set_dormant(&self, mode: DormantMode) {
self.inner.set_dormant(mode);
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
MissingTorDirectory,
#[cfg(feature = "lightwalletd-tonic-tls-webpki-roots")]
Grpc(self::grpc::GrpcError),
Http(self::http::HttpError),
Io(io::Error),
Tor(arti_client::Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::MissingTorDirectory => write!(f, "Tor directory is missing"),
#[cfg(feature = "lightwalletd-tonic-tls-webpki-roots")]
Error::Grpc(e) => write!(f, "gRPC-over-Tor error: {e}"),
Error::Http(e) => write!(f, "HTTP-over-Tor error: {e}"),
Error::Io(e) => write!(f, "IO error: {e}"),
Error::Tor(e) => write!(f, "Tor error: {e}"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::MissingTorDirectory => None,
#[cfg(feature = "lightwalletd-tonic-tls-webpki-roots")]
Error::Grpc(e) => Some(e),
Error::Http(e) => Some(e),
Error::Io(e) => Some(e),
Error::Tor(e) => Some(e),
}
}
}
#[cfg(feature = "lightwalletd-tonic-tls-webpki-roots")]
impl From<self::grpc::GrpcError> for Error {
fn from(e: self::grpc::GrpcError) -> Self {
Error::Grpc(e)
}
}
impl From<self::http::HttpError> for Error {
fn from(e: self::http::HttpError) -> Self {
Error::Http(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
impl From<arti_client::Error> for Error {
fn from(e: arti_client::Error) -> Self {
Error::Tor(e)
}
}