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#[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 #[arg(short, long, value_enum, value_name = "role", default_value = "client")]
81 pub role: Role,
82
83 #[arg(short, long, value_name = "file path", conflicts_with = "url_of_node")]
85 pub config: Option<std::path::PathBuf>,
86
87 #[arg(short, long, value_name = "url", conflicts_with = "config")]
89 pub url_of_node: Option<String>,
90
91 #[arg(short, long, value_name = "addr:port", requires = "url_of_node", conflicts_with = "config")]
93 pub listen_addr: Option<std::net::SocketAddr>,
94
95 #[arg(long)]
97 pub cache_dns: bool,
98
99 #[arg(short, long, value_name = "level", value_enum, default_value = "info")]
101 pub verbosity: ArgVerbosity,
102
103 #[arg(short, long)]
104 pub daemonize: bool,
106
107 #[arg(short, long)]
109 pub generate_url: bool,
110
111 #[arg(long)]
113 pub c_api: bool,
114
115 #[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}