Skip to main content

rings_node/native/
config.rs

1use std::env;
2use std::fs;
3use std::io;
4use std::path::PathBuf;
5
6use serde::Deserialize;
7use serde::Serialize;
8
9use crate::error::Error;
10use crate::error::Result;
11use crate::prelude::rings_core::ecc::SecretKey;
12use crate::prelude::SessionSk;
13use crate::processor::ProcessorConfig;
14use crate::processor::ProcessorConfigSerialized;
15use crate::util::ensure_parent_dir;
16use crate::util::expand_home;
17
18lazy_static::lazy_static! {
19  static ref DEFAULT_DATA_STORAGE_CONFIG: StorageConfig = StorageConfig {
20    path: get_storage_location(".rings", "data"),
21    capacity: DEFAULT_STORAGE_CAPACITY,
22  };
23  static ref DEFAULT_MEASURE_STORAGE_CONFIG: StorageConfig = StorageConfig {
24    path: get_storage_location(".rings", "measure"),
25    capacity: DEFAULT_STORAGE_CAPACITY,
26  };
27}
28
29pub const DEFAULT_NETWORK_ID: u32 = 1;
30pub const DEFAULT_INTERNAL_API_PORT: u16 = 50000;
31pub const DEFAULT_EXTERNAL_API_ADDR: &str = "127.0.0.1:50001";
32pub const DEFAULT_ENDPOINT_URL: &str = "http://127.0.0.1:50000";
33pub const DEFAULT_ICE_SERVERS: &str = "stun://stun.l.google.com:19302";
34pub const DEFAULT_STABILIZE_INTERVAL: u64 = 3;
35pub const DEFAULT_STORAGE_CAPACITY: u32 = 200000000;
36
37pub fn get_storage_location<P>(prefix: P, path: P) -> String
38where P: AsRef<std::path::Path> {
39    let home_dir = env::var_os("HOME").map(PathBuf::from);
40    let storage_path = match home_dir {
41        Some(dir) => dir.join(prefix).join(path),
42        None => std::path::Path::new("data").join(prefix).join(path),
43    };
44    storage_path.to_string_lossy().to_string()
45}
46
47#[derive(Debug, Clone, Deserialize, Serialize)]
48pub struct Config {
49    pub network_id: u32,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub ecdsa_key: Option<SecretKey>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub session_manager: Option<String>,
54    pub session_sk: Option<String>,
55    pub internal_api_port: u16,
56    pub external_api_addr: String,
57    pub endpoint_url: String,
58    pub ice_servers: String,
59    pub stabilize_interval: u64,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub external_ip: Option<String>,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub webrtc_udp_port_min: Option<u16>,
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub webrtc_udp_port_max: Option<u16>,
66    pub data_storage: StorageConfig,
67    pub measure_storage: StorageConfig,
68}
69
70impl TryFrom<Config> for ProcessorConfigSerialized {
71    type Error = Error;
72    fn try_from(config: Config) -> Result<Self> {
73        // Support old version
74        let session_sk: String = if let Some(sk) = config.ecdsa_key {
75            tracing::warn!("Field `ecdsa_key` is deprecated, use `session_sk` instead.");
76            SessionSk::new_with_seckey(&sk)
77                .and_then(|session_sk| session_sk.dump())
78                .map_err(|e| Error::VerifyError(e.to_string()))?
79        } else if let Some(ssk) = config.session_manager {
80            tracing::warn!("Field `session_manager` is deprecated, use `session_sk` instead.");
81            ssk
82        } else {
83            let Some(ssk_file) = config.session_sk else {
84                return Err(Error::InvalidData);
85            };
86            let ssk_file_expand_home = expand_home(&ssk_file)?;
87            fs::read_to_string(ssk_file_expand_home).unwrap_or_else(|e| {
88                tracing::warn!("Read session_sk file failed: {e:?}. Handling it as raw session_sk string. This mode is deprecated. please use a file path.");
89                ssk_file
90            })
91        };
92
93        let mut cs = Self::new(
94            config.network_id,
95            config.ice_servers,
96            session_sk,
97            config.stabilize_interval,
98        );
99
100        cs = if let Some(ext_ip) = config.external_ip {
101            cs.external_address(ext_ip)
102        } else {
103            cs
104        };
105        let udp_range = crate::processor::parse_webrtc_udp_port_range(
106            config.webrtc_udp_port_min,
107            config.webrtc_udp_port_max,
108        )?;
109        cs = if let Some(range) = udp_range {
110            cs.webrtc_udp_port_range(range)
111        } else {
112            cs
113        };
114
115        Ok(cs)
116    }
117}
118
119impl TryFrom<Config> for ProcessorConfig {
120    type Error = Error;
121    fn try_from(config: Config) -> Result<Self> {
122        ProcessorConfigSerialized::try_from(config).and_then(Self::try_from)
123    }
124}
125
126impl Config {
127    pub fn new<P>(session_sk: P) -> Self
128    where P: AsRef<std::path::Path> {
129        let session_sk = session_sk.as_ref().to_string_lossy().to_string();
130        Self {
131            network_id: DEFAULT_NETWORK_ID,
132            ecdsa_key: None,
133            session_manager: None,
134            session_sk: Some(session_sk),
135            internal_api_port: DEFAULT_INTERNAL_API_PORT,
136            external_api_addr: DEFAULT_EXTERNAL_API_ADDR.to_string(),
137            endpoint_url: DEFAULT_ENDPOINT_URL.to_string(),
138            ice_servers: DEFAULT_ICE_SERVERS.to_string(),
139            stabilize_interval: DEFAULT_STABILIZE_INTERVAL,
140            external_ip: None,
141            webrtc_udp_port_min: None,
142            webrtc_udp_port_max: None,
143            data_storage: DEFAULT_DATA_STORAGE_CONFIG.clone(),
144            measure_storage: DEFAULT_MEASURE_STORAGE_CONFIG.clone(),
145        }
146    }
147
148    pub fn write_fs<P>(&self, path: P) -> Result<String>
149    where P: AsRef<std::path::Path> {
150        let path = expand_home(path)?;
151        ensure_parent_dir(&path)?;
152        let f =
153            fs::File::create(path.as_path()).map_err(|e| Error::CreateFileError(e.to_string()))?;
154        let f_writer = io::BufWriter::new(f);
155        serde_yaml::to_writer(f_writer, self).map_err(|_| Error::EncodeError)?;
156        path.to_str()
157            .map(str::to_owned)
158            .ok_or_else(|| Error::PathUtf8Error(path.display().to_string()))
159    }
160
161    pub fn read_fs<P>(path: P) -> Result<Config>
162    where P: AsRef<std::path::Path> {
163        let path = expand_home(path)?;
164        tracing::debug!("Read config from: {:?}", path);
165        let f = fs::File::open(path).map_err(|e| Error::OpenFileError(e.to_string()))?;
166        let f_rdr = io::BufReader::new(f);
167        serde_yaml::from_reader(f_rdr).map_err(|_| Error::EncodeError)
168    }
169}
170
171#[derive(Debug, Clone, Deserialize, Serialize)]
172pub struct StorageConfig {
173    pub path: String,
174    pub capacity: u32,
175}
176
177impl StorageConfig {
178    pub fn new(path: &str, capacity: u32) -> Self {
179        Self {
180            path: path.to_string(),
181            capacity,
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    fn dumped_session_sk() -> String {
191        let key = SecretKey::random();
192        let session = match SessionSk::new_with_seckey(&key) {
193            Ok(session) => session,
194            Err(error) => panic!("session key construction failed: {error}"),
195        };
196        match session.dump() {
197            Ok(dump) => dump,
198            Err(error) => panic!("session key dump failed: {error}"),
199        }
200    }
201
202    #[test]
203    fn test_deserialization_with_missed_field() {
204        let yaml = r#"
205network_id: 1
206session_sk: session_sk
207internal_api_port: 50000
208external_api_addr: 127.0.0.1:50001
209endpoint_url: http://127.0.0.1:50000
210ice_servers: stun://stun.l.google.com:19302
211stabilize_interval: 3
212external_ip: null
213webrtc_udp_port_min: null
214webrtc_udp_port_max: null
215data_storage:
216  path: /Users/foo/.rings/data
217  capacity: 200000000
218measure_storage:
219  path: /Users/foo/.rings/measure
220  capacity: 200000000
221"#;
222        let cfg: Config = serde_yaml::from_str(yaml).unwrap();
223        assert_eq!(cfg.network_id, 1);
224    }
225
226    #[test]
227    fn config_with_valid_webrtc_udp_range_builds_processor_config() {
228        let mut config = Config::new(dumped_session_sk());
229        config.webrtc_udp_port_min = Some(49160);
230        config.webrtc_udp_port_max = Some(49200);
231
232        let processor_config = ProcessorConfig::try_from(config);
233
234        assert!(matches!(
235            processor_config.and_then(|config| config.webrtc_udp_port_range()),
236            Ok(Some(range)) if range.min() == 49160 && range.max() == 49200
237        ));
238    }
239
240    #[test]
241    fn config_with_partial_webrtc_udp_range_is_rejected() {
242        let mut config = Config::new(dumped_session_sk());
243        config.webrtc_udp_port_min = Some(49160);
244
245        let processor_config = ProcessorConfig::try_from(config);
246
247        assert!(matches!(
248            processor_config,
249            Err(Error::IncompleteWebrtcUdpPortRange {
250                min: Some(49160),
251                max: None
252            })
253        ));
254    }
255}