overtls/
cmdopt.rs

1#[repr(C)]
2#[derive(clap::ValueEnum, Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
3pub enum Role {
4    Server = 0,
5    #[default]
6    Client,
7}
8
9#[repr(C)]
10#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
11pub enum ArgVerbosity {
12    Off = 0,
13    Error,
14    Warn,
15    #[default]
16    Info,
17    Debug,
18    Trace,
19}
20
21#[cfg(target_os = "android")]
22impl TryFrom<jni::sys::jint> for ArgVerbosity {
23    type Error = std::io::Error;
24    fn try_from(value: jni::sys::jint) -> Result<Self, <Self as TryFrom<jni::sys::jint>>::Error> {
25        match value {
26            0 => Ok(ArgVerbosity::Off),
27            1 => Ok(ArgVerbosity::Error),
28            2 => Ok(ArgVerbosity::Warn),
29            3 => Ok(ArgVerbosity::Info),
30            4 => Ok(ArgVerbosity::Debug),
31            5 => Ok(ArgVerbosity::Trace),
32            _ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid verbosity level")),
33        }
34    }
35}
36
37impl From<ArgVerbosity> for log::LevelFilter {
38    fn from(verbosity: ArgVerbosity) -> Self {
39        match verbosity {
40            ArgVerbosity::Off => log::LevelFilter::Off,
41            ArgVerbosity::Error => log::LevelFilter::Error,
42            ArgVerbosity::Warn => log::LevelFilter::Warn,
43            ArgVerbosity::Info => log::LevelFilter::Info,
44            ArgVerbosity::Debug => log::LevelFilter::Debug,
45            ArgVerbosity::Trace => log::LevelFilter::Trace,
46        }
47    }
48}
49
50impl From<log::Level> for ArgVerbosity {
51    fn from(level: log::Level) -> Self {
52        match level {
53            log::Level::Error => ArgVerbosity::Error,
54            log::Level::Warn => ArgVerbosity::Warn,
55            log::Level::Info => ArgVerbosity::Info,
56            log::Level::Debug => ArgVerbosity::Debug,
57            log::Level::Trace => ArgVerbosity::Trace,
58        }
59    }
60}
61
62impl std::fmt::Display for ArgVerbosity {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        match self {
65            ArgVerbosity::Off => write!(f, "off"),
66            ArgVerbosity::Error => write!(f, "error"),
67            ArgVerbosity::Warn => write!(f, "warn"),
68            ArgVerbosity::Info => write!(f, "info"),
69            ArgVerbosity::Debug => write!(f, "debug"),
70            ArgVerbosity::Trace => write!(f, "trace"),
71        }
72    }
73}
74
75/// Proxy tunnel over tls
76#[derive(clap::Parser, Debug, Clone, PartialEq, Eq, Default)]
77#[command(author = clap::crate_authors!(", "), version = version_info(), about = about_info(), long_about = None)]
78pub struct CmdOpt {
79    /// Role of server or client
80    #[arg(short, long, value_enum, value_name = "role", default_value = "client")]
81    pub role: Role,
82
83    /// Config file path
84    #[arg(short, long, value_name = "file path", conflicts_with = "url_of_node")]
85    pub config: Option<std::path::PathBuf>,
86
87    /// URL of the server node used by client
88    #[arg(short, long, value_name = "url", conflicts_with = "config")]
89    pub url_of_node: Option<String>,
90
91    /// Local listening address associated with the URL
92    #[arg(short, long, value_name = "addr:port", requires = "url_of_node", conflicts_with = "config")]
93    pub listen_addr: Option<std::net::SocketAddr>,
94
95    /// Cache DNS Query result
96    #[arg(long)]
97    pub cache_dns: bool,
98
99    /// Verbosity level
100    #[arg(short, long, value_name = "level", value_enum, default_value = "info")]
101    pub verbosity: ArgVerbosity,
102
103    #[arg(short, long)]
104    /// Daemonize for unix family or run as service for windows
105    pub daemonize: bool,
106
107    /// Generate URL of the server node for client.
108    #[arg(short, long)]
109    pub generate_url: bool,
110
111    /// Use C API for client.
112    #[arg(long)]
113    pub c_api: bool,
114
115    /// Connection pool max size
116    #[arg(short, long, value_name = "size")]
117    pub pool_max_size: Option<usize>,
118}
119
120impl CmdOpt {
121    pub fn is_server(&self) -> bool {
122        self.role == Role::Server
123    }
124
125    pub fn parse_cmd() -> CmdOpt {
126        fn output_error_and_exit<T: std::fmt::Display>(msg: T) -> ! {
127            eprintln!("{msg}");
128            std::process::exit(1);
129        }
130
131        let args: CmdOpt = clap::Parser::parse();
132        if args.role == Role::Server {
133            if args.config.is_none() {
134                output_error_and_exit("Config file is required for server");
135            }
136            if args.c_api {
137                output_error_and_exit("C API is not supported for server");
138            }
139            if args.generate_url {
140                output_error_and_exit("Generate URL is not supported for server");
141            }
142            if args.listen_addr.is_some() {
143                output_error_and_exit("Listen address is not supported for server");
144            }
145            if args.url_of_node.is_some() {
146                output_error_and_exit("Node URL is not supported for server");
147            }
148        }
149        if args.role == Role::Client
150            && let Some(size) = args.pool_max_size
151            && size < 10
152        {
153            output_error_and_exit("Connection pool max size must be greater than 10");
154        }
155        if args.role == Role::Client && args.config.is_none() && args.url_of_node.is_none() {
156            output_error_and_exit("Config file or node URL is required for client");
157        }
158        args
159    }
160}
161
162pub(crate) const fn version_info() -> &'static str {
163    concat!(clap::crate_version!(), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")")
164}
165
166fn about_info() -> String {
167    format!("Proxy tunnel over tls.\nVersion {}", version_info())
168}