active_call/
config.rs

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        // This is a bit expensive but Config is not cloned often in hot paths
253        // and implementing Clone manually for all nested structs is tedious
254        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}