1use std::{collections::HashMap, fs::read_to_string, net::SocketAddr, str::FromStr};
2
3use anyhow::anyhow;
4use clap::Parser;
5use itertools::Itertools;
6use serde::{Deserialize, Serialize};
7
8#[repr(C)]
9#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, Eq)]
10#[serde(rename_all = "lowercase")]
11pub enum Transport {
12 TCP = 0,
13 UDP = 1,
14}
15
16impl FromStr for Transport {
17 type Err = anyhow::Error;
18
19 fn from_str(value: &str) -> Result<Self, Self::Err> {
20 Ok(match value {
21 "udp" => Self::UDP,
22 "tcp" => Self::TCP,
23 _ => return Err(anyhow!("unknown transport: {value}")),
24 })
25 }
26}
27
28#[derive(Deserialize, Serialize, Debug, Clone)]
29pub struct Interface {
30 pub transport: Transport,
31 pub bind: SocketAddr,
33 pub external: SocketAddr,
40}
41
42impl FromStr for Interface {
43 type Err = anyhow::Error;
44
45 fn from_str(s: &str) -> Result<Self, Self::Err> {
46 let (transport, addrs) = s
47 .split('@')
48 .collect_tuple()
49 .ok_or_else(|| anyhow!("invalid interface transport: {}", s))?;
50
51 let (bind, external) = addrs
52 .split('/')
53 .collect_tuple()
54 .ok_or_else(|| anyhow!("invalid interface address: {}", s))?;
55
56 Ok(Interface {
57 external: external.parse::<SocketAddr>()?,
58 bind: bind.parse::<SocketAddr>()?,
59 transport: transport.parse()?,
60 })
61 }
62}
63
64#[derive(Deserialize, Debug)]
65pub struct Turn {
66 #[serde(default = "Turn::realm")]
73 pub realm: String,
74
75 #[serde(default = "Turn::interfaces")]
81 pub interfaces: Vec<Interface>,
82}
83
84impl Turn {
85 pub fn get_externals(&self) -> Vec<SocketAddr> {
86 self.interfaces.iter().map(|item| item.external).collect()
87 }
88}
89
90impl Turn {
91 fn realm() -> String {
92 "localhost".to_string()
93 }
94
95 fn interfaces() -> Vec<Interface> {
96 vec![]
97 }
98}
99
100impl Default for Turn {
101 fn default() -> Self {
102 Self {
103 realm: Self::realm(),
104 interfaces: Self::interfaces(),
105 }
106 }
107}
108
109#[derive(Deserialize, Debug)]
110pub struct Api {
111 #[serde(default = "Api::bind")]
121 pub bind: SocketAddr,
122}
123
124impl Api {
125 fn bind() -> SocketAddr {
126 "127.0.0.1:3000".parse().unwrap()
127 }
128}
129
130impl Default for Api {
131 fn default() -> Self {
132 Self { bind: Self::bind() }
133 }
134}
135
136#[derive(Deserialize, Debug, Clone, Copy)]
137#[serde(rename_all = "lowercase")]
138pub enum LogLevel {
139 Error,
140 Warn,
141 Info,
142 Debug,
143 Trace,
144}
145
146impl FromStr for LogLevel {
147 type Err = String;
148
149 fn from_str(value: &str) -> Result<Self, Self::Err> {
150 Ok(match value {
151 "trace" => Self::Trace,
152 "debug" => Self::Debug,
153 "info" => Self::Info,
154 "warn" => Self::Warn,
155 "error" => Self::Error,
156 _ => return Err(format!("unknown log level: {value}")),
157 })
158 }
159}
160
161impl Default for LogLevel {
162 fn default() -> Self {
163 Self::Info
164 }
165}
166
167impl LogLevel {
168 pub fn as_level(&self) -> log::Level {
169 match *self {
170 Self::Error => log::Level::Error,
171 Self::Debug => log::Level::Debug,
172 Self::Trace => log::Level::Trace,
173 Self::Warn => log::Level::Warn,
174 Self::Info => log::Level::Info,
175 }
176 }
177}
178
179#[derive(Deserialize, Debug, Default)]
180pub struct Log {
181 #[serde(default)]
185 pub level: LogLevel,
186}
187
188#[derive(Deserialize, Debug, Default)]
189pub struct Auth {
190 #[serde(default)]
198 pub static_credentials: HashMap<String, String>,
199 pub static_auth_secret: Option<String>,
205}
206
207#[derive(Deserialize, Debug)]
208pub struct Config {
209 #[serde(default)]
210 pub turn: Turn,
211 #[serde(default)]
212 pub api: Api,
213 #[serde(default)]
214 pub log: Log,
215 #[serde(default)]
216 pub auth: Auth,
217}
218
219#[derive(Parser, Debug)]
220#[command(
221 about = env!("CARGO_PKG_DESCRIPTION"),
222 version = env!("CARGO_PKG_VERSION"),
223 author = env!("CARGO_PKG_AUTHORS"),
224)]
225struct Cli {
226 #[arg(long, short)]
230 config: Option<String>,
231 #[arg(long, value_parser = Cli::parse_credential)]
235 auth_static_credentials: Option<Vec<(String, String)>>,
236 #[arg(long)]
239 auth_static_auth_secret: Option<String>,
240 #[arg(
242 long,
243 value_parser = clap::value_parser!(LogLevel),
244 )]
245 log_level: Option<LogLevel>,
246 #[arg(long)]
249 api_bind: Option<SocketAddr>,
250 #[arg(long)]
252 turn_realm: Option<String>,
253 #[arg(long)]
257 turn_interfaces: Option<Vec<Interface>>,
258}
259
260impl Cli {
261 fn parse_credential(s: &str) -> Result<(String, String), anyhow::Error> {
263 let (username, password) = s
264 .split('=')
265 .collect_tuple()
266 .ok_or_else(|| anyhow!("invalid credential str: {}", s))?;
267 Ok((username.to_string(), password.to_string()))
268 }
269}
270
271impl Config {
272 pub fn load() -> anyhow::Result<Self> {
278 let cli = Cli::parse();
279 let mut config = toml::from_str::<Self>(
280 &cli.config
281 .and_then(|path| read_to_string(path).ok())
282 .unwrap_or("".to_string()),
283 )?;
284
285 {
289 if let Some(credentials) = cli.auth_static_credentials {
290 for (k, v) in credentials {
291 config.auth.static_credentials.insert(k, v);
292 }
293 }
294
295 if let Some(secret) = cli.auth_static_auth_secret {
296 config.auth.static_auth_secret.replace(secret);
297 }
298
299 if let Some(level) = cli.log_level {
300 config.log.level = level;
301 }
302
303 if let Some(bind) = cli.api_bind {
304 config.api.bind = bind;
305 }
306
307 if let Some(realm) = cli.turn_realm {
308 config.turn.realm = realm;
309 }
310
311 if let Some(interfaces) = cli.turn_interfaces {
312 for interface in interfaces {
313 config.turn.interfaces.push(interface);
314 }
315 }
316 }
317
318 {
320 let mut interfaces = Vec::with_capacity(config.turn.interfaces.len());
321
322 {
323 for it in &config.turn.interfaces {
324 #[cfg(feature = "udp")]
325 if it.transport == Transport::UDP {
326 interfaces.push(it.clone());
327 }
328
329 #[cfg(feature = "tcp")]
330 if it.transport == Transport::TCP {
331 interfaces.push(it.clone());
332 }
333 }
334 }
335
336 config.turn.interfaces = interfaces;
337 }
338
339 Ok(config)
340 }
341}