#[repr(C)]
#[derive(clap::ValueEnum, Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Role {
Server = 0,
#[default]
Client,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
pub enum ArgVerbosity {
Off = 0,
Error,
Warn,
#[default]
Info,
Debug,
Trace,
}
#[cfg(target_os = "android")]
impl TryFrom<jni::sys::jint> for ArgVerbosity {
type Error = std::io::Error;
fn try_from(value: jni::sys::jint) -> Result<Self, <Self as TryFrom<jni::sys::jint>>::Error> {
match value {
0 => Ok(ArgVerbosity::Off),
1 => Ok(ArgVerbosity::Error),
2 => Ok(ArgVerbosity::Warn),
3 => Ok(ArgVerbosity::Info),
4 => Ok(ArgVerbosity::Debug),
5 => Ok(ArgVerbosity::Trace),
_ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid verbosity level")),
}
}
}
impl From<ArgVerbosity> for log::LevelFilter {
fn from(verbosity: ArgVerbosity) -> Self {
match verbosity {
ArgVerbosity::Off => log::LevelFilter::Off,
ArgVerbosity::Error => log::LevelFilter::Error,
ArgVerbosity::Warn => log::LevelFilter::Warn,
ArgVerbosity::Info => log::LevelFilter::Info,
ArgVerbosity::Debug => log::LevelFilter::Debug,
ArgVerbosity::Trace => log::LevelFilter::Trace,
}
}
}
impl From<log::Level> for ArgVerbosity {
fn from(level: log::Level) -> Self {
match level {
log::Level::Error => ArgVerbosity::Error,
log::Level::Warn => ArgVerbosity::Warn,
log::Level::Info => ArgVerbosity::Info,
log::Level::Debug => ArgVerbosity::Debug,
log::Level::Trace => ArgVerbosity::Trace,
}
}
}
impl std::fmt::Display for ArgVerbosity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ArgVerbosity::Off => write!(f, "off"),
ArgVerbosity::Error => write!(f, "error"),
ArgVerbosity::Warn => write!(f, "warn"),
ArgVerbosity::Info => write!(f, "info"),
ArgVerbosity::Debug => write!(f, "debug"),
ArgVerbosity::Trace => write!(f, "trace"),
}
}
}
#[derive(clap::Parser, Debug, Clone, PartialEq, Eq, Default)]
#[command(author = clap::crate_authors!(", "), version = version_info(), about = about_info(), long_about = None)]
pub struct CmdOpt {
#[arg(short, long, value_enum, value_name = "role", default_value = "client")]
pub role: Role,
#[arg(short, long, value_name = "file path", conflicts_with = "url_of_node")]
pub config: Option<std::path::PathBuf>,
#[arg(short, long, value_name = "url", conflicts_with = "config")]
pub url_of_node: Option<String>,
#[arg(short, long, value_name = "addr:port", requires = "url_of_node", conflicts_with = "config")]
pub listen_addr: Option<std::net::SocketAddr>,
#[arg(long)]
pub cache_dns: bool,
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
pub verbosity: ArgVerbosity,
#[arg(short, long)]
pub daemonize: bool,
#[arg(short, long)]
pub generate_url: bool,
#[arg(long)]
pub c_api: bool,
#[arg(short, long, value_name = "size")]
pub pool_max_size: Option<usize>,
}
impl CmdOpt {
pub fn is_server(&self) -> bool {
self.role == Role::Server
}
pub fn parse_cmd() -> CmdOpt {
fn output_error_and_exit<T: std::fmt::Display>(msg: T) -> ! {
eprintln!("{msg}");
std::process::exit(1);
}
let args: CmdOpt = clap::Parser::parse();
if args.role == Role::Server {
if args.config.is_none() {
output_error_and_exit("Config file is required for server");
}
if args.c_api {
output_error_and_exit("C API is not supported for server");
}
if args.generate_url {
output_error_and_exit("Generate URL is not supported for server");
}
if args.listen_addr.is_some() {
output_error_and_exit("Listen address is not supported for server");
}
if args.url_of_node.is_some() {
output_error_and_exit("Node URL is not supported for server");
}
}
if args.role == Role::Client
&& let Some(size) = args.pool_max_size
&& size < 10
{
output_error_and_exit("Connection pool max size must be greater than 10");
}
if args.role == Role::Client && args.config.is_none() && args.url_of_node.is_none() {
output_error_and_exit("Config file or node URL is required for client");
}
args
}
}
pub(crate) const fn version_info() -> &'static str {
concat!(clap::crate_version!(), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")")
}
fn about_info() -> String {
format!("Proxy tunnel over tls.\nVersion {}", version_info())
}