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 pub hooks: Option<String>,
131}
132
133impl Api {
134 fn bind() -> SocketAddr {
135 "127.0.0.1:3000".parse().unwrap()
136 }
137}
138
139impl Default for Api {
140 fn default() -> Self {
141 Self {
142 hooks: None,
143 bind: Self::bind(),
144 }
145 }
146}
147
148#[derive(Deserialize, Debug, Clone, Copy)]
149#[serde(rename_all = "lowercase")]
150pub enum LogLevel {
151 Error,
152 Warn,
153 Info,
154 Debug,
155 Trace,
156}
157
158impl FromStr for LogLevel {
159 type Err = String;
160
161 fn from_str(value: &str) -> Result<Self, Self::Err> {
162 Ok(match value {
163 "trace" => Self::Trace,
164 "debug" => Self::Debug,
165 "info" => Self::Info,
166 "warn" => Self::Warn,
167 "error" => Self::Error,
168 _ => return Err(format!("unknown log level: {value}")),
169 })
170 }
171}
172
173impl Default for LogLevel {
174 fn default() -> Self {
175 Self::Info
176 }
177}
178
179impl LogLevel {
180 pub fn as_level(&self) -> log::Level {
181 match *self {
182 Self::Error => log::Level::Error,
183 Self::Debug => log::Level::Debug,
184 Self::Trace => log::Level::Trace,
185 Self::Warn => log::Level::Warn,
186 Self::Info => log::Level::Info,
187 }
188 }
189}
190
191#[derive(Deserialize, Debug, Default)]
192pub struct Log {
193 #[serde(default)]
197 pub level: LogLevel,
198}
199
200#[derive(Deserialize, Debug, Default)]
201pub struct Auth {
202 #[serde(default)]
210 pub static_credentials: HashMap<String, String>,
211 pub static_auth_secret: Option<String>,
217}
218
219#[derive(Deserialize, Debug)]
220pub struct Config {
221 #[serde(default)]
222 pub turn: Turn,
223 #[serde(default)]
224 pub api: Api,
225 #[serde(default)]
226 pub log: Log,
227 #[serde(default)]
228 pub auth: Auth,
229}
230
231#[derive(Parser, Debug)]
232#[command(
233 about = env!("CARGO_PKG_DESCRIPTION"),
234 version = env!("CARGO_PKG_VERSION"),
235 author = env!("CARGO_PKG_AUTHORS"),
236)]
237struct Cli {
238 #[arg(long, short)]
242 config: Option<String>,
243 #[arg(long, value_parser = Cli::parse_credential)]
247 auth_static_credentials: Option<Vec<(String, String)>>,
248 #[arg(long)]
251 auth_static_auth_secret: Option<String>,
252 #[arg(
254 long,
255 value_parser = clap::value_parser!(LogLevel),
256 )]
257 log_level: Option<LogLevel>,
258 #[arg(long)]
261 api_bind: Option<SocketAddr>,
262 #[arg(long)]
266 api_hooks: Option<String>,
267 #[arg(long)]
269 turn_realm: Option<String>,
270 #[arg(long)]
274 turn_interfaces: Option<Vec<Interface>>,
275}
276
277impl Cli {
278 fn parse_credential(s: &str) -> Result<(String, String), anyhow::Error> {
280 let (username, password) = s
281 .split('=')
282 .collect_tuple()
283 .ok_or_else(|| anyhow!("invalid credential str: {}", s))?;
284 Ok((username.to_string(), password.to_string()))
285 }
286}
287
288impl Config {
289 pub fn load() -> anyhow::Result<Self> {
295 let cli = Cli::parse();
296 let mut config = toml::from_str::<Self>(
297 &cli.config
298 .and_then(|path| read_to_string(path).ok())
299 .unwrap_or("".to_string()),
300 )?;
301
302 {
306 if let Some(credentials) = cli.auth_static_credentials {
307 for (k, v) in credentials {
308 config.auth.static_credentials.insert(k, v);
309 }
310 }
311
312 if let Some(secret) = cli.auth_static_auth_secret {
313 config.auth.static_auth_secret.replace(secret);
314 }
315
316 if let Some(level) = cli.log_level {
317 config.log.level = level;
318 }
319
320 if let Some(bind) = cli.api_bind {
321 config.api.bind = bind;
322 }
323
324 if let Some(hooks) = cli.api_hooks {
325 config.api.hooks.replace(hooks);
326 }
327
328 if let Some(realm) = cli.turn_realm {
329 config.turn.realm = realm;
330 }
331
332 if let Some(interfaces) = cli.turn_interfaces {
333 for interface in interfaces {
334 config.turn.interfaces.push(interface);
335 }
336 }
337 }
338
339 {
341 let mut interfaces = Vec::with_capacity(config.turn.interfaces.len());
342
343 {
344 for it in &config.turn.interfaces {
345 #[cfg(feature = "udp")]
346 if it.transport == Transport::UDP {
347 interfaces.push(it.clone());
348 }
349
350 #[cfg(feature = "tcp")]
351 if it.transport == Transport::TCP {
352 interfaces.push(it.clone());
353 }
354 }
355 }
356
357 config.turn.interfaces = interfaces;
358 }
359
360 Ok(config)
361 }
362}