1use once_cell::sync::Lazy;
9use std::collections::HashMap;
10use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
11use std::path::Path;
12
13pub static CONFIG: Lazy<ApiConfig> = Lazy::new(ApiConfig::load);
14
15pub struct ApiConfig {
16 pub multicast_port: u16,
17 pub base_port: u16,
18 pub port_range: u16,
19 pub allow_random_ports: bool,
20 pub allow_ipv4: bool,
21 pub allow_ipv6: bool,
22 pub multicast_addresses: Vec<IpAddr>,
23 pub multicast_ttl: u32,
24 pub session_id: String,
25 pub use_protocol_version: i32,
26 pub smoothing_halftime: f32,
27 pub time_update_interval: f64,
28 pub time_probe_count: i32,
29 pub time_probe_interval: f64,
30 pub time_probe_max_rtt: f64,
31 pub time_update_minprobes: i32,
32}
33
34impl ApiConfig {
35 fn load() -> Self {
36 let file_cfg = load_config_file();
37
38 let get = |key: &str| -> Option<String> {
39 std::env::var(format!("LSL_{}", key.to_uppercase()))
41 .ok()
42 .or_else(|| file_cfg.get(key).cloned())
43 };
44
45 let get_bool = |key: &str, default: bool| -> bool {
46 get(key)
47 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
48 .unwrap_or(default)
49 };
50 let get_u16 = |key: &str, default: u16| -> u16 {
51 get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
52 };
53 let get_u32 = |key: &str, default: u32| -> u32 {
54 get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
55 };
56 let get_f32 = |key: &str, default: f32| -> f32 {
57 get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
58 };
59 let get_f64 = |key: &str, default: f64| -> f64 {
60 get(key).and_then(|v| v.parse().ok()).unwrap_or(default)
61 };
62
63 let allow_ipv6 = get_bool("ipv6", true);
64
65 let multicast_addresses = if let Some(addrs_str) = get("multicast_addresses") {
67 addrs_str
68 .split(&[',', ' '][..])
69 .filter_map(|s| s.trim().parse::<IpAddr>().ok())
70 .collect()
71 } else {
72 let mut addrs: Vec<IpAddr> = vec![
73 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)),
74 IpAddr::V4(Ipv4Addr::BROADCAST),
75 IpAddr::V4(Ipv4Addr::new(224, 0, 0, 1)),
76 IpAddr::V4(Ipv4Addr::new(224, 0, 0, 183)),
77 IpAddr::V4(Ipv4Addr::new(239, 255, 172, 215)),
78 ];
79 if allow_ipv6 {
80 addrs.extend([
81 IpAddr::V6(Ipv6Addr::LOCALHOST),
82 IpAddr::V6(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 1)),
83 IpAddr::V6(Ipv6Addr::new(0xff02, 0, 0, 0, 0, 0, 0, 0x113a)),
84 IpAddr::V6(Ipv6Addr::new(0xff05, 0, 0, 0, 0, 0, 0, 0x113a)),
85 ]);
86 }
87 addrs
88 };
89
90 Self {
91 multicast_port: get_u16("multicast_port", 16571),
92 base_port: get_u16("base_port", 16572),
93 port_range: get_u16("port_range", 32),
94 allow_random_ports: get_bool("allow_random_ports", true),
95 allow_ipv4: get_bool("ipv4", true),
96 allow_ipv6,
97 multicast_addresses,
98 multicast_ttl: get_u32("multicast_ttl", 24),
99 session_id: get("session_id").unwrap_or_else(|| "default".into()),
100 use_protocol_version: get("protocol_version")
101 .and_then(|v| v.parse().ok())
102 .unwrap_or(110),
103 smoothing_halftime: get_f32("smoothing_halftime", 90.0),
104 time_update_interval: get_f64("time_update_interval", 2.0),
105 time_probe_count: get("time_probe_count")
106 .and_then(|v| v.parse().ok())
107 .unwrap_or(8),
108 time_probe_interval: get_f64("time_probe_interval", 0.064),
109 time_probe_max_rtt: get_f64("time_probe_max_rtt", 0.128),
110 time_update_minprobes: get("time_update_minprobes")
111 .and_then(|v| v.parse().ok())
112 .unwrap_or(6),
113 }
114 }
115}
116
117fn load_config_file() -> HashMap<String, String> {
119 let candidates = [
120 Some(std::path::PathBuf::from("lsl_api.cfg")),
121 dirs_path("lsl_api.cfg"),
122 Some(std::path::PathBuf::from("/etc/lsl_api/lsl_api.cfg")),
123 ];
124
125 for candidate in candidates.iter().flatten() {
126 if candidate.exists() {
127 if let Ok(contents) = std::fs::read_to_string(candidate) {
128 log::info!("Loaded LSL config from {}", candidate.display());
129 return parse_ini(&contents);
130 }
131 }
132 }
133
134 HashMap::new()
135}
136
137fn dirs_path(filename: &str) -> Option<std::path::PathBuf> {
138 std::env::var("HOME")
139 .ok()
140 .map(|h| Path::new(&h).join(".lsl").join(filename))
141}
142
143fn parse_ini(contents: &str) -> HashMap<String, String> {
144 let mut map = HashMap::new();
145 for line in contents.lines() {
146 let line = line.trim();
147 if line.is_empty()
148 || line.starts_with('#')
149 || line.starts_with(';')
150 || line.starts_with('[')
151 {
152 continue;
153 }
154 if let Some(eq) = line.find('=') {
155 let key = line[..eq].trim().to_lowercase().replace('-', "_");
156 let val = line[eq + 1..].trim().to_string();
157 map.insert(key, val);
158 }
159 }
160 map
161}