dstar_gateway_server/reflector/
config.rs1use std::collections::HashSet;
10use std::marker::PhantomData;
11use std::net::SocketAddr;
12use std::time::Duration;
13
14use dstar_gateway_core::types::{Callsign, Module, ProtocolKind};
15
16#[derive(Debug)]
18pub struct Missing;
19
20#[derive(Debug)]
22pub struct Provided;
23
24#[non_exhaustive]
26#[derive(Debug, thiserror::Error)]
27pub enum ConfigError {
28 #[error("reflector module set must contain at least one module")]
30 EmptyModules,
31}
32
33#[non_exhaustive]
40#[derive(Debug, Clone)]
41pub struct ReflectorConfig {
42 pub callsign: Callsign,
44 pub modules: HashSet<Module>,
46 pub bind: SocketAddr,
48 pub max_clients_per_module: usize,
50 pub max_total_clients: usize,
52 pub enabled_protocols: HashSet<ProtocolKind>,
58 pub keepalive_interval: Duration,
60 pub keepalive_inactivity_timeout: Duration,
62 pub voice_inactivity_timeout: Duration,
64 pub cross_protocol_forwarding: bool,
66 pub tx_rate_limit_frames_per_sec: f64,
74}
75
76impl ReflectorConfig {
77 #[must_use]
79 pub fn is_enabled(&self, protocol: ProtocolKind) -> bool {
80 self.enabled_protocols.contains(&protocol)
81 }
82
83 #[must_use]
112 pub fn builder() -> ReflectorConfigBuilder<Missing, Missing, Missing> {
113 ReflectorConfigBuilder::empty()
114 }
115}
116
117#[derive(Debug)]
129pub struct ReflectorConfigBuilder<Cs, Ms, Bn> {
130 callsign: Option<Callsign>,
131 modules: Option<HashSet<Module>>,
132 bind: Option<SocketAddr>,
133 max_clients_per_module: usize,
134 max_total_clients: usize,
135 enabled_protocols: HashSet<ProtocolKind>,
136 keepalive_interval: Duration,
137 keepalive_inactivity_timeout: Duration,
138 voice_inactivity_timeout: Duration,
139 cross_protocol_forwarding: bool,
140 tx_rate_limit_frames_per_sec: f64,
141 _cs: PhantomData<Cs>,
142 _ms: PhantomData<Ms>,
143 _bn: PhantomData<Bn>,
144}
145
146impl ReflectorConfigBuilder<Missing, Missing, Missing> {
147 fn empty() -> Self {
152 let mut enabled = HashSet::new();
153 let _a = enabled.insert(ProtocolKind::DPlus);
154 let _b = enabled.insert(ProtocolKind::DExtra);
155 let _c = enabled.insert(ProtocolKind::Dcs);
156 Self {
157 callsign: None,
158 modules: None,
159 bind: None,
160 max_clients_per_module: 50,
161 max_total_clients: 250,
162 enabled_protocols: enabled,
163 keepalive_interval: Duration::from_secs(1),
164 keepalive_inactivity_timeout: Duration::from_secs(30),
165 voice_inactivity_timeout: Duration::from_secs(2),
166 cross_protocol_forwarding: false,
167 tx_rate_limit_frames_per_sec: 60.0,
168 _cs: PhantomData,
169 _ms: PhantomData,
170 _bn: PhantomData,
171 }
172 }
173}
174
175impl<Cs, Ms, Bn> ReflectorConfigBuilder<Cs, Ms, Bn> {
176 #[must_use]
178 pub fn callsign(self, callsign: Callsign) -> ReflectorConfigBuilder<Provided, Ms, Bn> {
179 ReflectorConfigBuilder {
180 callsign: Some(callsign),
181 modules: self.modules,
182 bind: self.bind,
183 max_clients_per_module: self.max_clients_per_module,
184 max_total_clients: self.max_total_clients,
185 enabled_protocols: self.enabled_protocols,
186 keepalive_interval: self.keepalive_interval,
187 keepalive_inactivity_timeout: self.keepalive_inactivity_timeout,
188 voice_inactivity_timeout: self.voice_inactivity_timeout,
189 cross_protocol_forwarding: self.cross_protocol_forwarding,
190 tx_rate_limit_frames_per_sec: self.tx_rate_limit_frames_per_sec,
191 _cs: PhantomData,
192 _ms: PhantomData,
193 _bn: PhantomData,
194 }
195 }
196
197 #[must_use]
199 pub fn module_set(self, modules: HashSet<Module>) -> ReflectorConfigBuilder<Cs, Provided, Bn> {
200 ReflectorConfigBuilder {
201 callsign: self.callsign,
202 modules: Some(modules),
203 bind: self.bind,
204 max_clients_per_module: self.max_clients_per_module,
205 max_total_clients: self.max_total_clients,
206 enabled_protocols: self.enabled_protocols,
207 keepalive_interval: self.keepalive_interval,
208 keepalive_inactivity_timeout: self.keepalive_inactivity_timeout,
209 voice_inactivity_timeout: self.voice_inactivity_timeout,
210 cross_protocol_forwarding: self.cross_protocol_forwarding,
211 tx_rate_limit_frames_per_sec: self.tx_rate_limit_frames_per_sec,
212 _cs: PhantomData,
213 _ms: PhantomData,
214 _bn: PhantomData,
215 }
216 }
217
218 #[must_use]
220 pub fn bind(self, bind: SocketAddr) -> ReflectorConfigBuilder<Cs, Ms, Provided> {
221 ReflectorConfigBuilder {
222 callsign: self.callsign,
223 modules: self.modules,
224 bind: Some(bind),
225 max_clients_per_module: self.max_clients_per_module,
226 max_total_clients: self.max_total_clients,
227 enabled_protocols: self.enabled_protocols,
228 keepalive_interval: self.keepalive_interval,
229 keepalive_inactivity_timeout: self.keepalive_inactivity_timeout,
230 voice_inactivity_timeout: self.voice_inactivity_timeout,
231 cross_protocol_forwarding: self.cross_protocol_forwarding,
232 tx_rate_limit_frames_per_sec: self.tx_rate_limit_frames_per_sec,
233 _cs: PhantomData,
234 _ms: PhantomData,
235 _bn: PhantomData,
236 }
237 }
238
239 #[must_use]
241 pub const fn max_clients_per_module(mut self, value: usize) -> Self {
242 self.max_clients_per_module = value;
243 self
244 }
245
246 #[must_use]
248 pub const fn max_total_clients(mut self, value: usize) -> Self {
249 self.max_total_clients = value;
250 self
251 }
252
253 #[must_use]
255 pub fn enable(mut self, protocol: ProtocolKind) -> Self {
256 let _inserted = self.enabled_protocols.insert(protocol);
257 self
258 }
259
260 #[must_use]
262 pub fn disable(mut self, protocol: ProtocolKind) -> Self {
263 let _removed = self.enabled_protocols.remove(&protocol);
264 self
265 }
266
267 #[must_use]
269 pub const fn keepalive_interval(mut self, value: Duration) -> Self {
270 self.keepalive_interval = value;
271 self
272 }
273
274 #[must_use]
276 pub const fn keepalive_inactivity_timeout(mut self, value: Duration) -> Self {
277 self.keepalive_inactivity_timeout = value;
278 self
279 }
280
281 #[must_use]
283 pub const fn voice_inactivity_timeout(mut self, value: Duration) -> Self {
284 self.voice_inactivity_timeout = value;
285 self
286 }
287
288 #[must_use]
290 pub const fn cross_protocol_forwarding(mut self, value: bool) -> Self {
291 self.cross_protocol_forwarding = value;
292 self
293 }
294
295 #[must_use]
303 pub const fn tx_rate_limit_frames_per_sec(mut self, value: f64) -> Self {
304 self.tx_rate_limit_frames_per_sec = value;
305 self
306 }
307}
308
309impl ReflectorConfigBuilder<Provided, Provided, Provided> {
310 pub fn build(self) -> Result<ReflectorConfig, ConfigError> {
318 let Some(callsign) = self.callsign else {
319 unreachable!("Provided marker guarantees callsign is Some");
320 };
321 let Some(modules) = self.modules else {
322 unreachable!("Provided marker guarantees modules is Some");
323 };
324 let Some(bind) = self.bind else {
325 unreachable!("Provided marker guarantees bind is Some");
326 };
327 if modules.is_empty() {
328 return Err(ConfigError::EmptyModules);
329 }
330 Ok(ReflectorConfig {
331 callsign,
332 modules,
333 bind,
334 max_clients_per_module: self.max_clients_per_module,
335 max_total_clients: self.max_total_clients,
336 enabled_protocols: self.enabled_protocols,
337 keepalive_interval: self.keepalive_interval,
338 keepalive_inactivity_timeout: self.keepalive_inactivity_timeout,
339 voice_inactivity_timeout: self.voice_inactivity_timeout,
340 cross_protocol_forwarding: self.cross_protocol_forwarding,
341 tx_rate_limit_frames_per_sec: self.tx_rate_limit_frames_per_sec,
342 })
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::{
349 Callsign, ConfigError, Duration, HashSet, Module, ProtocolKind, ReflectorConfig, SocketAddr,
350 };
351 use std::net::{IpAddr, Ipv4Addr};
352
353 type TestResult = Result<(), Box<dyn std::error::Error>>;
354
355 fn module_set(modules: &[Module]) -> HashSet<Module> {
356 let mut set = HashSet::new();
357 for &m in modules {
358 let _ = set.insert(m);
359 }
360 set
361 }
362
363 const BIND: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 30001);
364
365 #[test]
366 fn happy_path_builds_with_defaults() -> TestResult {
367 let config = ReflectorConfig::builder()
368 .callsign(Callsign::from_wire_bytes(*b"REF030 "))
369 .module_set(module_set(&[Module::A, Module::B, Module::C]))
370 .bind(BIND)
371 .build()?;
372 assert_eq!(config.callsign, Callsign::from_wire_bytes(*b"REF030 "));
373 assert_eq!(config.modules.len(), 3);
374 assert_eq!(config.bind, BIND);
375 assert_eq!(config.max_clients_per_module, 50);
377 assert_eq!(config.max_total_clients, 250);
378 assert!(config.is_enabled(ProtocolKind::DPlus));
379 assert!(config.is_enabled(ProtocolKind::DExtra));
380 assert!(config.is_enabled(ProtocolKind::Dcs));
381 assert_eq!(config.keepalive_interval, Duration::from_secs(1));
382 assert_eq!(config.keepalive_inactivity_timeout, Duration::from_secs(30));
383 assert_eq!(config.voice_inactivity_timeout, Duration::from_secs(2));
384 assert!(!config.cross_protocol_forwarding);
385 Ok(())
386 }
387
388 #[test]
389 fn empty_module_set_is_rejected() {
390 let result = ReflectorConfig::builder()
391 .callsign(Callsign::from_wire_bytes(*b"REF030 "))
392 .module_set(HashSet::new())
393 .bind(BIND)
394 .build();
395 assert!(matches!(result, Err(ConfigError::EmptyModules)));
396 }
397
398 #[test]
399 fn overrides_replace_defaults() -> TestResult {
400 let config = ReflectorConfig::builder()
401 .callsign(Callsign::from_wire_bytes(*b"REF030 "))
402 .module_set(module_set(&[Module::C]))
403 .bind(BIND)
404 .max_clients_per_module(10)
405 .max_total_clients(40)
406 .disable(ProtocolKind::DPlus)
407 .disable(ProtocolKind::Dcs)
408 .keepalive_interval(Duration::from_millis(500))
409 .keepalive_inactivity_timeout(Duration::from_secs(10))
410 .voice_inactivity_timeout(Duration::from_millis(1500))
411 .cross_protocol_forwarding(true)
412 .build()?;
413 assert_eq!(config.max_clients_per_module, 10);
414 assert_eq!(config.max_total_clients, 40);
415 assert!(!config.is_enabled(ProtocolKind::DPlus));
416 assert!(config.is_enabled(ProtocolKind::DExtra));
417 assert!(!config.is_enabled(ProtocolKind::Dcs));
418 assert_eq!(config.keepalive_interval, Duration::from_millis(500));
419 assert_eq!(config.keepalive_inactivity_timeout, Duration::from_secs(10));
420 assert_eq!(config.voice_inactivity_timeout, Duration::from_millis(1500));
421 assert!(config.cross_protocol_forwarding);
422 Ok(())
423 }
424
425 #[test]
426 fn cross_protocol_forwarding_defaults_false() -> TestResult {
427 let config = ReflectorConfig::builder()
428 .callsign(Callsign::from_wire_bytes(*b"REF030 "))
429 .module_set(module_set(&[Module::C]))
430 .bind(BIND)
431 .build()?;
432 assert!(!config.cross_protocol_forwarding);
433 Ok(())
434 }
435}