use std::net::SocketAddr;
use derive_deftly::Deftly;
use tor_config::ConfigBuildError;
use tor_config::derive::prelude::*;
use tor_config_path::CfgPath;
use tor_linkspec::PtTransportName;
#[cfg(feature = "tor-channel-factory")]
use {crate::PtClientMethod, tor_socksproto::SocksVersion};
#[derive(Clone, Debug, Deftly, Eq, PartialEq)]
#[derive_deftly(TorConfig)]
#[deftly(tor_config(no_default_trait, pre_build = "Self::validate"))]
pub struct TransportConfig {
#[deftly(tor_config(no_magic, no_default))]
pub(crate) protocols: Vec<PtTransportName>,
#[deftly(tor_config(default, setter(strip_option)))]
pub(crate) path: Option<CfgPath>,
#[deftly(tor_config(no_magic, default))]
pub(crate) arguments: Vec<String>,
#[deftly(tor_config(default, setter(strip_option)))]
pub(crate) proxy_addr: Option<SocketAddr>,
#[deftly(tor_config(default))]
pub(crate) run_on_startup: bool,
}
impl TransportConfigBuilder {
pub fn get_protocols(&self) -> &[PtTransportName] {
self.protocols.as_deref().unwrap_or_default()
}
fn validate(&self) -> Result<(), ConfigBuildError> {
#[cfg(not(feature = "managed-pts"))]
if self.path.is_some() {
return Err(ConfigBuildError::NoCompileTimeSupport {
field: "path".into(),
problem:
"Indicates a managed transport, but support is not enabled by cargo features"
.into(),
});
}
match (&self.path, &self.proxy_addr) {
(Some(_), Some(_)) => Err(ConfigBuildError::Inconsistent {
fields: vec!["path".into(), "proxy_addr".into()],
problem: "Cannot provide both path and proxy_addr".into(),
}),
(None, None) => Err(ConfigBuildError::MissingField {
field: "{path or proxy_addr}".into(),
}),
(None, Some(_)) => {
if self.arguments.as_ref().is_some_and(|v| !v.is_empty()) {
Err(ConfigBuildError::Inconsistent {
fields: vec!["proxy_addr".into(), "arguments".into()],
problem: "Cannot provide arguments for an unmanaged transport".into(),
})
} else if self.run_on_startup.is_some() {
Err(ConfigBuildError::Inconsistent {
fields: vec!["proxy_addr".into(), "run_on_startup".into()],
problem: "run_on_startup is meaningless for an unmanaged transport".into(),
})
} else {
Ok(())
}
}
(Some(_), None) => Ok(()),
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum TransportOptions {
#[cfg(feature = "managed-pts")]
Managed(ManagedTransportOptions),
Unmanaged(UnmanagedTransportOptions),
}
impl TryFrom<TransportConfig> for TransportOptions {
type Error = tor_error::Bug;
fn try_from(config: TransportConfig) -> Result<Self, Self::Error> {
if let Some(path) = config.path {
cfg_if::cfg_if! {
if #[cfg(feature = "managed-pts")] {
Ok(TransportOptions::Managed(ManagedTransportOptions {
protocols: config.protocols,
path,
arguments: config.arguments,
run_on_startup: config.run_on_startup,
}))
} else {
let _ = path;
Err(tor_error::internal!(
"Path is set but 'managed-pts' feature is not enabled. How did this pass builder validation?"
))
}
}
} else if let Some(proxy_addr) = config.proxy_addr {
Ok(TransportOptions::Unmanaged(UnmanagedTransportOptions {
protocols: config.protocols,
proxy_addr,
}))
} else {
Err(tor_error::internal!(
"Neither path nor proxy are set. How did this pass builder validation?"
))
}
}
}
#[cfg(feature = "managed-pts")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ManagedTransportOptions {
pub(crate) protocols: Vec<PtTransportName>,
pub(crate) path: CfgPath,
pub(crate) arguments: Vec<String>,
pub(crate) run_on_startup: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct UnmanagedTransportOptions {
pub(crate) protocols: Vec<PtTransportName>,
pub(crate) proxy_addr: SocketAddr,
}
impl UnmanagedTransportOptions {
#[cfg(feature = "tor-channel-factory")]
pub(crate) fn cmethod(&self) -> PtClientMethod {
PtClientMethod {
kind: SocksVersion::V5,
endpoint: self.proxy_addr,
}
}
pub(crate) fn is_localhost(&self) -> bool {
self.proxy_addr.ip().is_loopback()
}
}