1use crate::media::{ambiance::AmbianceOption, recorder::RecorderFormat};
2use crate::useragent::RegisterOption;
3use anyhow::{Error, Result};
4use clap::Parser;
5use rustrtc::IceServer;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Parser, Debug)]
10#[command(version)]
11pub struct Cli {
12 #[clap(long)]
13 pub conf: Option<String>,
14
15 #[clap(long)]
16 pub http: Option<String>,
17
18 #[clap(long)]
19 pub sip: Option<String>,
20}
21
22pub(crate) fn default_config_recorder_path() -> String {
23 #[cfg(target_os = "windows")]
24 return "./config/recorders".to_string();
25 #[cfg(not(target_os = "windows"))]
26 return "./config/recorders".to_string();
27}
28
29fn default_config_media_cache_path() -> String {
30 #[cfg(target_os = "windows")]
31 return "./config/mediacache".to_string();
32 #[cfg(not(target_os = "windows"))]
33 return "./config/mediacache".to_string();
34}
35
36fn default_config_http_addr() -> String {
37 "0.0.0.0:8080".to_string()
38}
39
40fn default_sip_addr() -> String {
41 "0.0.0.0".to_string()
42}
43
44fn default_sip_port() -> u16 {
45 25060
46}
47
48fn default_config_rtp_start_port() -> Option<u16> {
49 Some(12000)
50}
51
52fn default_config_rtp_end_port() -> Option<u16> {
53 Some(42000)
54}
55
56fn default_codecs() -> Option<Vec<String>> {
57 let mut codecs = vec![
58 "pcmu".to_string(),
59 "pcma".to_string(),
60 "g722".to_string(),
61 "g729".to_string(),
62 "telephone_event".to_string(),
63 ];
64
65 #[cfg(feature = "opus")]
66 {
67 codecs.push("opus".to_string());
68 }
69
70 Some(codecs)
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize, Default)]
74#[serde(rename_all = "snake_case")]
75pub struct RecordingPolicy {
76 #[serde(default)]
77 pub enabled: bool,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub auto_start: Option<bool>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub filename_pattern: Option<String>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
83 pub samplerate: Option<u32>,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub ptime: Option<u32>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub path: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub format: Option<RecorderFormat>,
90}
91
92impl RecordingPolicy {
93 pub fn recorder_path(&self) -> String {
94 self.path
95 .as_ref()
96 .map(|p| p.trim())
97 .filter(|p| !p.is_empty())
98 .map(|p| p.to_string())
99 .unwrap_or_else(default_config_recorder_path)
100 }
101
102 pub fn recorder_format(&self) -> RecorderFormat {
103 self.format.unwrap_or_default()
104 }
105
106 pub fn ensure_defaults(&mut self) -> bool {
107 if self
108 .path
109 .as_ref()
110 .map(|p| p.trim().is_empty())
111 .unwrap_or(true)
112 {
113 self.path = Some(default_config_recorder_path());
114 }
115
116 false
117 }
118}
119
120#[derive(Debug, Clone, Deserialize, Serialize)]
121pub struct RewriteRule {
122 pub r#match: String,
123 pub rewrite: String,
124}
125
126#[derive(Debug, Deserialize, Serialize)]
127pub struct Config {
128 #[serde(default = "default_config_http_addr")]
129 pub http_addr: String,
130 pub addr: String,
131 pub udp_port: u16,
132
133 pub log_level: Option<String>,
134 pub log_file: Option<String>,
135 #[serde(default, skip_serializing_if = "Vec::is_empty")]
136 pub http_access_skip_paths: Vec<String>,
137
138 pub useragent: Option<String>,
139 pub register_users: Option<Vec<RegisterOption>>,
140 pub graceful_shutdown: Option<bool>,
141 pub handler: Option<InviteHandlerConfig>,
142 pub accept_timeout: Option<String>,
143 #[serde(default = "default_codecs")]
144 pub codecs: Option<Vec<String>>,
145 pub external_ip: Option<String>,
146 #[serde(default = "default_config_rtp_start_port")]
147 pub rtp_start_port: Option<u16>,
148 #[serde(default = "default_config_rtp_end_port")]
149 pub rtp_end_port: Option<u16>,
150
151 pub callrecord: Option<CallRecordConfig>,
152 #[serde(default = "default_config_media_cache_path")]
153 pub media_cache_path: String,
154 pub ambiance: Option<AmbianceOption>,
155 pub ice_servers: Option<Vec<IceServer>>,
156 #[serde(default)]
157 pub recording: Option<RecordingPolicy>,
158 pub rewrites: Option<Vec<RewriteRule>>,
159}
160
161#[derive(Debug, Deserialize, Clone, Serialize)]
162#[serde(rename_all = "snake_case")]
163#[serde(tag = "type")]
164pub enum InviteHandlerConfig {
165 Webhook {
166 url: Option<String>,
167 urls: Option<Vec<String>>,
168 method: Option<String>,
169 headers: Option<Vec<(String, String)>>,
170 },
171}
172
173#[derive(Debug, Deserialize, Clone, Serialize)]
174#[serde(rename_all = "snake_case")]
175pub enum S3Vendor {
176 Aliyun,
177 Tencent,
178 Minio,
179 AWS,
180 GCP,
181 Azure,
182 DigitalOcean,
183}
184
185#[derive(Debug, Deserialize, Clone, Serialize)]
186#[serde(tag = "type")]
187#[serde(rename_all = "snake_case")]
188pub enum CallRecordConfig {
189 Local {
190 root: String,
191 },
192 S3 {
193 vendor: S3Vendor,
194 bucket: String,
195 region: String,
196 access_key: String,
197 secret_key: String,
198 endpoint: String,
199 root: String,
200 with_media: Option<bool>,
201 keep_media_copy: Option<bool>,
202 },
203 Http {
204 url: String,
205 headers: Option<HashMap<String, String>>,
206 with_media: Option<bool>,
207 keep_media_copy: Option<bool>,
208 },
209}
210
211impl Default for CallRecordConfig {
212 fn default() -> Self {
213 Self::Local {
214 #[cfg(target_os = "windows")]
215 root: "./config/cdr".to_string(),
216 #[cfg(not(target_os = "windows"))]
217 root: "./config/cdr".to_string(),
218 }
219 }
220}
221
222impl Default for Config {
223 fn default() -> Self {
224 Self {
225 http_addr: default_config_http_addr(),
226 log_level: None,
227 log_file: None,
228 http_access_skip_paths: Vec::new(),
229 addr: default_sip_addr(),
230 udp_port: default_sip_port(),
231 useragent: None,
232 register_users: None,
233 graceful_shutdown: Some(true),
234 handler: None,
235 accept_timeout: Some("50s".to_string()),
236 media_cache_path: default_config_media_cache_path(),
237 ambiance: None,
238 callrecord: None,
239 ice_servers: None,
240 codecs: None,
241 external_ip: None,
242 rtp_start_port: default_config_rtp_start_port(),
243 rtp_end_port: default_config_rtp_end_port(),
244 recording: None,
245 rewrites: None,
246 }
247 }
248}
249
250impl Clone for Config {
251 fn clone(&self) -> Self {
252 let s = toml::to_string(self).unwrap();
255 toml::from_str(&s).unwrap()
256 }
257}
258
259impl Config {
260 pub fn load(path: &str) -> Result<Self, Error> {
261 let config: Self = toml::from_str(
262 &std::fs::read_to_string(path).map_err(|e| anyhow::anyhow!("{}: {}", e, path))?,
263 )?;
264 Ok(config)
265 }
266
267 pub fn recorder_path(&self) -> String {
268 self.recording
269 .as_ref()
270 .map(|policy| policy.recorder_path())
271 .unwrap_or_else(default_config_recorder_path)
272 }
273
274 pub fn recorder_format(&self) -> RecorderFormat {
275 self.recording
276 .as_ref()
277 .map(|policy| policy.recorder_format())
278 .unwrap_or_default()
279 }
280
281 pub fn ensure_recording_defaults(&mut self) -> bool {
282 let mut fallback = false;
283
284 if let Some(policy) = self.recording.as_mut() {
285 fallback |= policy.ensure_defaults();
286 }
287
288 fallback
289 }
290}