1#[cfg(not(feature = "compile-time-config"))]
5use std::net::SocketAddr;
6#[cfg(not(feature = "compile-time-config"))]
7use std::path::PathBuf;
8#[cfg(not(feature = "compile-time-config"))]
9use std::time::Duration;
10
11#[cfg(not(feature = "compile-time-config"))]
12use crate::clock::ClockSource;
13#[cfg(not(feature = "compile-time-config"))]
14use crate::tracker::EvictionPolicy;
15#[cfg(not(feature = "compile-time-config"))]
21use crate::tracker::{
22 DEFAULT_CAPACITY, DEFAULT_EVICTION_SCAN_WINDOW, MAX_EVICTION_SCAN_WINDOW,
23 MIN_EVICTION_SCAN_WINDOW,
24};
25
26#[cfg(not(feature = "compile-time-config"))]
27use super::parse_helpers::{parse_octal, parse_u16, parse_u64};
28#[cfg(not(feature = "compile-time-config"))]
29use super::types::{
30 Config, ConfigError, DEFAULT_PROM_RATE_LIMIT_BURST, DEFAULT_PROM_RATE_LIMIT_PER_SEC,
31 DEFAULT_READ_TIMEOUT_MS, DEFAULT_RECOVERY_CAPTURE_BYTES, DEFAULT_RECOVERY_DEBOUNCE_MS,
32 DEFAULT_SHUTDOWN_GRACE_MS, DEFAULT_SOCKET_MODE, MAX_ITERATION_BUDGET_MS,
33 MAX_RECOVERY_CAPTURE_BYTES, MAX_SCRAPE_BUDGET_MS, MIN_ITERATION_BUDGET_MS,
34 MIN_SCRAPE_BUDGET_MS, MIN_SHUTDOWN_GRACE_MS, MIN_THRESHOLD_MS,
35};
36
37#[cfg(not(feature = "compile-time-config"))]
38impl Config {
39 pub fn from_args(args: impl IntoIterator<Item = String>) -> Result<Config, ConfigError> {
44 let mut socket: Option<PathBuf> = None;
45 let mut threshold_ms: Option<u64> = None;
46 let mut recovery_exec_cmd: Option<String> = None;
47 let mut recovery_exec_file: Option<PathBuf> = None;
48 let mut recovery_debounce_ms: Option<u64> = None;
49 let mut recovery_env: Vec<String> = Vec::new();
50 let mut recovery_inherit_env: bool = false;
51 let mut file_export: Option<PathBuf> = None;
52 let mut export_file_max_bytes: Option<u64> = None;
53 let mut export_file_sync_every: u32 = 0;
54 #[allow(unused_mut)]
60 let mut prom_addr: Option<SocketAddr> = None;
61 #[allow(unused_mut)]
62 let mut prom_token_file: Option<PathBuf> = None;
63 let mut shutdown_after_secs: Option<u64> = None;
64 let mut recovery_timeout_ms: Option<u64> = None;
65 let mut shutdown_grace_ms: Option<u64> = None;
66 let mut socket_mode: Option<u32> = None;
67 let mut read_timeout_ms: Option<u64> = None;
68 let mut tracker_capacity: Option<usize> = None;
69 let mut tracker_eviction_policy: Option<EvictionPolicy> = None;
70 let mut clock_source: Option<ClockSource> = None;
71 let mut eviction_scan_window: Option<usize> = None;
72 let mut udp_port: Option<u16> = None;
73 let mut udp_bind_addr: Option<std::net::IpAddr> = None;
74 let mut secure_key_file: Option<PathBuf> = None;
75 let mut accepted_key_file: Option<PathBuf> = None;
76 let mut master_key_file: Option<PathBuf> = None;
77 let mut max_beat_rate: Option<u32> = None;
78 let mut global_beat_rate: Option<u32> = None;
79 let mut global_beat_burst: Option<u32> = None;
80 let mut uds_rcvbuf_bytes: Option<u32> = None;
81 let mut heartbeat_file: Option<PathBuf> = None;
82 let mut self_watchdog: Option<Duration> = None;
83 let mut hw_watchdog: Option<PathBuf> = None;
84 #[allow(unused_mut)]
85 let mut prom_rate_limit_per_sec: Option<u32> = None;
86 #[allow(unused_mut)]
87 let mut prom_rate_limit_burst: Option<u32> = None;
88 let mut i_accept_plaintext_udp = false;
89 let mut i_accept_recovery_on_secure_udp = false;
90 let mut i_accept_recovery_on_plaintext_udp = false;
91 let mut i_accept_secure_udp_non_loopback = false;
92 let mut allow_cross_namespace_agents = false;
93 let mut strict_namespace_check = false;
94 let mut recovery_audit_file: Option<PathBuf> = None;
95 let mut recovery_audit_max_bytes: Option<u64> = None;
96 let mut recovery_audit_sync_every: Option<u32> = None;
97 let mut recovery_capture_stdio = false;
98 let mut recovery_capture_bytes: Option<u32> = None;
99 let mut iteration_budget_ms: Option<u64> = None;
100 let mut scrape_budget_ms: Option<u64> = None;
101 let mut audit_fsync_budget_ms: Option<u32> = None;
102 let mut audit_sync_interval_ms: Option<u32> = None;
103 let mut audit_rotation_budget_ms: Option<u32> = None;
104 #[cfg(feature = "test-hooks")]
105 let mut inject_wedge_ms: Option<u64> = None;
106 let mut signal_handler_mode: Option<crate::signal_install::SignalHandlerMode> = None;
107
108 let mut iter = args.into_iter();
109 while let Some(tok) = iter.next() {
110 match tok.as_str() {
111 "--help" | "-h" => return Err(ConfigError::HelpRequested),
112 "--socket" => {
113 let v = iter.next().ok_or(ConfigError::MissingValue("--socket"))?;
114 socket = Some(PathBuf::from(v));
115 }
116 "--threshold-ms" => {
117 let v = iter
118 .next()
119 .ok_or(ConfigError::MissingValue("--threshold-ms"))?;
120 threshold_ms = Some(parse_u64("--threshold-ms", &v)?);
121 }
122 "--recovery-cmd" => {
123 return Err(ConfigError::RemovedFlag {
124 flag: "--recovery-cmd",
125 replacement: "--recovery-exec",
126 });
127 }
128 "--recovery-cmd-file" => {
129 return Err(ConfigError::RemovedFlag {
130 flag: "--recovery-cmd-file",
131 replacement: "--recovery-exec-file",
132 });
133 }
134 "--i-accept-shell-risk" => {
135 return Err(ConfigError::RemovedFlag {
136 flag: "--i-accept-shell-risk",
137 replacement: "--recovery-exec",
138 });
139 }
140 "--recovery-exec" => {
141 let v = iter
142 .next()
143 .ok_or(ConfigError::MissingValue("--recovery-exec"))?;
144 recovery_exec_cmd = Some(v);
145 }
146 "--recovery-exec-file" => {
147 let v = iter
148 .next()
149 .ok_or(ConfigError::MissingValue("--recovery-exec-file"))?;
150 recovery_exec_file = Some(PathBuf::from(v));
151 }
152 "--recovery-debounce-ms" => {
153 let v = iter
154 .next()
155 .ok_or(ConfigError::MissingValue("--recovery-debounce-ms"))?;
156 recovery_debounce_ms = Some(parse_u64("--recovery-debounce-ms", &v)?);
157 }
158 "--recovery-env" => {
159 let v = iter
160 .next()
161 .ok_or(ConfigError::MissingValue("--recovery-env"))?;
162 recovery_env.push(v);
163 }
164 "--recovery-inherit-env" => {
165 recovery_inherit_env = true;
166 }
167 "--socket-mode" => {
168 let v = iter
169 .next()
170 .ok_or(ConfigError::MissingValue("--socket-mode"))?;
171 socket_mode = Some(parse_octal(&v)?);
172 }
173 "--export-file" => {
174 let v = iter
175 .next()
176 .ok_or(ConfigError::MissingValue("--export-file"))?;
177 file_export = Some(PathBuf::from(v));
178 }
179 "--export-file-max-bytes" => {
180 let v = iter
181 .next()
182 .ok_or(ConfigError::MissingValue("--export-file-max-bytes"))?;
183 export_file_max_bytes = Some(parse_u64("--export-file-max-bytes", &v)?);
184 }
185 "--export-file-sync-every" => {
186 let v = iter
187 .next()
188 .ok_or(ConfigError::MissingValue("--export-file-sync-every"))?;
189 let parsed = parse_u64("--export-file-sync-every", &v)?;
190 if parsed > u32::MAX as u64 {
191 return Err(ConfigError::BadValue {
192 flag: "--export-file-sync-every",
193 raw: v,
194 });
195 }
196 export_file_sync_every = parsed as u32;
197 }
198 #[cfg(feature = "prometheus-exporter")]
199 "--prom-addr" => {
200 let v = iter
201 .next()
202 .ok_or(ConfigError::MissingValue("--prom-addr"))?;
203 prom_addr = Some(
204 v.parse::<SocketAddr>()
205 .map_err(|_| ConfigError::BadAddr(v))?,
206 );
207 }
208 #[cfg(feature = "prometheus-exporter")]
209 "--prom-token-file" => {
210 let v = iter
211 .next()
212 .ok_or(ConfigError::MissingValue("--prom-token-file"))?;
213 prom_token_file = Some(PathBuf::from(v));
214 }
215 "--recovery-timeout-ms" => {
216 let v = iter
217 .next()
218 .ok_or(ConfigError::MissingValue("--recovery-timeout-ms"))?;
219 recovery_timeout_ms = Some(parse_u64("--recovery-timeout-ms", &v)?);
220 }
221 "--shutdown-grace-ms" => {
222 let v = iter
223 .next()
224 .ok_or(ConfigError::MissingValue("--shutdown-grace-ms"))?;
225 shutdown_grace_ms = Some(parse_u64("--shutdown-grace-ms", &v)?);
226 }
227 "--read-timeout-ms" => {
228 let v = iter
229 .next()
230 .ok_or(ConfigError::MissingValue("--read-timeout-ms"))?;
231 read_timeout_ms = Some(parse_u64("--read-timeout-ms", &v)?);
232 }
233 "--tracker-capacity" => {
234 let v = iter
235 .next()
236 .ok_or(ConfigError::MissingValue("--tracker-capacity"))?;
237 tracker_capacity =
238 Some(v.parse::<usize>().map_err(|_| ConfigError::BadInteger {
239 flag: "--tracker-capacity",
240 raw: v,
241 })?);
242 }
243 "--eviction-scan-window" => {
244 let v = iter
245 .next()
246 .ok_or(ConfigError::MissingValue("--eviction-scan-window"))?;
247 eviction_scan_window =
248 Some(v.parse::<usize>().map_err(|_| ConfigError::BadInteger {
249 flag: "--eviction-scan-window",
250 raw: v,
251 })?);
252 }
253 "--tracker-eviction-policy" => {
254 let v = iter
255 .next()
256 .ok_or(ConfigError::MissingValue("--tracker-eviction-policy"))?;
257 tracker_eviction_policy = Some(match v.as_str() {
258 "strict" => EvictionPolicy::Strict,
259 "balanced" => EvictionPolicy::Balanced,
260 _ => {
261 return Err(ConfigError::BadValue {
262 flag: "--tracker-eviction-policy",
263 raw: v,
264 })
265 }
266 });
267 }
268 "--clock-source" => {
269 let v = iter
270 .next()
271 .ok_or(ConfigError::MissingValue("--clock-source"))?;
272 clock_source =
273 Some(
274 v.parse::<ClockSource>()
275 .map_err(|_| ConfigError::BadValue {
276 flag: "--clock-source",
277 raw: v,
278 })?,
279 );
280 }
281 "--signal-handler-mode" => {
282 let v = iter
283 .next()
284 .ok_or(ConfigError::MissingValue("--signal-handler-mode"))?;
285 signal_handler_mode = Some(
286 v.parse::<crate::signal_install::SignalHandlerMode>()
287 .map_err(|_| ConfigError::BadValue {
288 flag: "--signal-handler-mode",
289 raw: v,
290 })?,
291 );
292 }
293 "--shutdown-after-secs" => {
294 let v = iter
295 .next()
296 .ok_or(ConfigError::MissingValue("--shutdown-after-secs"))?;
297 shutdown_after_secs = Some(parse_u64("--shutdown-after-secs", &v)?);
298 }
299 "--udp-port" => {
300 let v = iter.next().ok_or(ConfigError::MissingValue("--udp-port"))?;
301 udp_port = Some(parse_u16("--udp-port", &v)?);
302 }
303 "--udp-bind-addr" => {
304 let v = iter
305 .next()
306 .ok_or(ConfigError::MissingValue("--udp-bind-addr"))?;
307 udp_bind_addr = Some(
308 v.parse::<std::net::IpAddr>()
309 .map_err(|_| ConfigError::BadAddr(v))?,
310 );
311 }
312 "--key-file" => {
313 let v = iter.next().ok_or(ConfigError::MissingValue("--key-file"))?;
314 secure_key_file = Some(PathBuf::from(v));
315 }
316 "--accepted-key-file" => {
317 let v = iter
318 .next()
319 .ok_or(ConfigError::MissingValue("--accepted-key-file"))?;
320 accepted_key_file = Some(PathBuf::from(v));
321 }
322 "--master-key-file" => {
323 let v = iter
324 .next()
325 .ok_or(ConfigError::MissingValue("--master-key-file"))?;
326 master_key_file = Some(PathBuf::from(v));
327 }
328 "--key-env" | "--master-key-env" | "--accepted-key-env" => {
329 let flag = match tok.as_str() {
333 "--key-env" => "--key-env",
334 "--master-key-env" => "--master-key-env",
335 _ => "--accepted-key-env",
336 };
337 return Err(ConfigError::RemovedFlag {
338 flag,
339 replacement: "--key-file (mode 0600, owned by the observer UID)",
340 });
341 }
342 "--max-beat-rate" => {
343 let v = iter
344 .next()
345 .ok_or(ConfigError::MissingValue("--max-beat-rate"))?;
346 max_beat_rate =
347 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
348 flag: "--max-beat-rate",
349 raw: v,
350 })?);
351 }
352 "--global-beat-rate" => {
353 let v = iter
354 .next()
355 .ok_or(ConfigError::MissingValue("--global-beat-rate"))?;
356 global_beat_rate =
357 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
358 flag: "--global-beat-rate",
359 raw: v,
360 })?);
361 }
362 "--global-beat-burst" => {
363 let v = iter
364 .next()
365 .ok_or(ConfigError::MissingValue("--global-beat-burst"))?;
366 global_beat_burst =
367 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
368 flag: "--global-beat-burst",
369 raw: v,
370 })?);
371 }
372 "--uds-rcvbuf-bytes" => {
373 let v = iter
374 .next()
375 .ok_or(ConfigError::MissingValue("--uds-rcvbuf-bytes"))?;
376 uds_rcvbuf_bytes =
377 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
378 flag: "--uds-rcvbuf-bytes",
379 raw: v,
380 })?);
381 }
382 "--heartbeat-file" => {
383 let v = iter
384 .next()
385 .ok_or(ConfigError::MissingValue("--heartbeat-file"))?;
386 heartbeat_file = Some(PathBuf::from(v));
387 }
388 "--self-watchdog-secs" => {
389 let v = iter
390 .next()
391 .ok_or(ConfigError::MissingValue("--self-watchdog-secs"))?;
392 let secs = v.parse::<u64>().map_err(|_| ConfigError::BadInteger {
393 flag: "--self-watchdog-secs",
394 raw: v,
395 })?;
396 self_watchdog = Some(Duration::from_secs(secs));
397 }
398 "--hw-watchdog" => {
399 let v = iter
400 .next()
401 .ok_or(ConfigError::MissingValue("--hw-watchdog"))?;
402 hw_watchdog = Some(PathBuf::from(v));
403 }
404 #[cfg(feature = "test-hooks")]
405 "--inject-wedge-ms" => {
406 let v = iter
407 .next()
408 .ok_or(ConfigError::MissingValue("--inject-wedge-ms"))?;
409 let ms = v.parse::<u64>().map_err(|_| ConfigError::BadInteger {
410 flag: "--inject-wedge-ms",
411 raw: v,
412 })?;
413 inject_wedge_ms = Some(ms);
414 }
415 #[cfg(feature = "prometheus-exporter")]
416 "--prom-rate-limit-per-sec" => {
417 let v = iter
418 .next()
419 .ok_or(ConfigError::MissingValue("--prom-rate-limit-per-sec"))?;
420 prom_rate_limit_per_sec =
421 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
422 flag: "--prom-rate-limit-per-sec",
423 raw: v,
424 })?);
425 }
426 #[cfg(feature = "prometheus-exporter")]
427 "--prom-rate-limit-burst" => {
428 let v = iter
429 .next()
430 .ok_or(ConfigError::MissingValue("--prom-rate-limit-burst"))?;
431 prom_rate_limit_burst =
432 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
433 flag: "--prom-rate-limit-burst",
434 raw: v,
435 })?);
436 }
437 "--i-accept-plaintext-udp" => {
438 i_accept_plaintext_udp = true;
439 }
440 "--secure-udp-i-accept-recovery-on-unauthenticated-transport" => {
441 i_accept_recovery_on_secure_udp = true;
442 }
443 "--plaintext-udp-i-accept-recovery-on-unauthenticated-transport" => {
444 i_accept_recovery_on_plaintext_udp = true;
445 }
446 "--i-accept-secure-udp-non-loopback" => {
447 i_accept_secure_udp_non_loopback = true;
448 }
449 "--allow-cross-namespace-agents" => {
450 allow_cross_namespace_agents = true;
451 }
452 "--strict-namespace-check" => {
453 strict_namespace_check = true;
454 }
455 "--recovery-audit-file" => {
456 let v = iter
457 .next()
458 .ok_or(ConfigError::MissingValue("--recovery-audit-file"))?;
459 recovery_audit_file = Some(PathBuf::from(v));
460 }
461 "--recovery-audit-max-bytes" => {
462 let v = iter
463 .next()
464 .ok_or(ConfigError::MissingValue("--recovery-audit-max-bytes"))?;
465 recovery_audit_max_bytes = Some(parse_u64("--recovery-audit-max-bytes", &v)?);
466 }
467 "--recovery-audit-sync-every" => {
468 let v = iter
469 .next()
470 .ok_or(ConfigError::MissingValue("--recovery-audit-sync-every"))?;
471 let parsed = v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
472 flag: "--recovery-audit-sync-every",
473 raw: v.clone(),
474 })?;
475 if parsed == 0 {
476 return Err(ConfigError::BadInteger {
477 flag: "--recovery-audit-sync-every",
478 raw: v,
479 });
480 }
481 recovery_audit_sync_every = Some(parsed);
482 }
483 "--recovery-capture-stdio" => {
484 recovery_capture_stdio = true;
485 }
486 "--recovery-capture-bytes" => {
487 let v = iter
488 .next()
489 .ok_or(ConfigError::MissingValue("--recovery-capture-bytes"))?;
490 recovery_capture_bytes =
491 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
492 flag: "--recovery-capture-bytes",
493 raw: v,
494 })?);
495 }
496 "--iteration-budget-ms" => {
497 let v = iter
498 .next()
499 .ok_or(ConfigError::MissingValue("--iteration-budget-ms"))?;
500 iteration_budget_ms = Some(parse_u64("--iteration-budget-ms", &v)?);
501 }
502 "--scrape-budget-ms" => {
503 let v = iter
504 .next()
505 .ok_or(ConfigError::MissingValue("--scrape-budget-ms"))?;
506 scrape_budget_ms = Some(parse_u64("--scrape-budget-ms", &v)?);
507 }
508 "--audit-fsync-budget-ms" => {
509 let v = iter
510 .next()
511 .ok_or(ConfigError::MissingValue("--audit-fsync-budget-ms"))?;
512 let parsed = v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
513 flag: "--audit-fsync-budget-ms",
514 raw: v.clone(),
515 })?;
516 if parsed == 0 {
517 return Err(ConfigError::BadInteger {
518 flag: "--audit-fsync-budget-ms",
519 raw: v,
520 });
521 }
522 audit_fsync_budget_ms = Some(parsed);
523 }
524 "--audit-sync-interval-ms" => {
525 let v = iter
526 .next()
527 .ok_or(ConfigError::MissingValue("--audit-sync-interval-ms"))?;
528 audit_sync_interval_ms =
529 Some(v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
530 flag: "--audit-sync-interval-ms",
531 raw: v,
532 })?);
533 }
534 "--audit-rotation-budget-ms" => {
535 let v = iter
536 .next()
537 .ok_or(ConfigError::MissingValue("--audit-rotation-budget-ms"))?;
538 let parsed = v.parse::<u32>().map_err(|_| ConfigError::BadInteger {
539 flag: "--audit-rotation-budget-ms",
540 raw: v.clone(),
541 })?;
542 if parsed == 0 {
543 return Err(ConfigError::BadInteger {
544 flag: "--audit-rotation-budget-ms",
545 raw: v,
546 });
547 }
548 audit_rotation_budget_ms = Some(parsed);
549 }
550 other => return Err(ConfigError::UnknownFlag(other.to_string())),
551 }
552 }
553
554 let socket = socket.ok_or(ConfigError::MissingRequired("--socket"))?;
555 let threshold_ms = threshold_ms.ok_or(ConfigError::MissingRequired("--threshold-ms"))?;
556
557 if threshold_ms < MIN_THRESHOLD_MS {
558 return Err(ConfigError::ThresholdTooLow {
559 value: threshold_ms,
560 min: MIN_THRESHOLD_MS,
561 });
562 }
563
564 if prom_addr.is_some() && prom_token_file.is_none() {
569 return Err(ConfigError::PromAddrRequiresToken);
570 }
571 if prom_token_file.is_some() && prom_addr.is_none() {
572 return Err(ConfigError::MutuallyExclusive {
573 a: "--prom-token-file",
574 b: "(missing --prom-addr)",
575 });
576 }
577
578 let shutdown_grace_ms = shutdown_grace_ms.unwrap_or(DEFAULT_SHUTDOWN_GRACE_MS);
579 if shutdown_grace_ms < MIN_SHUTDOWN_GRACE_MS {
580 return Err(ConfigError::ShutdownGraceTooLow {
581 value: shutdown_grace_ms,
582 min: MIN_SHUTDOWN_GRACE_MS,
583 });
584 }
585
586 let recovery_debounce =
587 Duration::from_millis(recovery_debounce_ms.unwrap_or(DEFAULT_RECOVERY_DEBOUNCE_MS));
588
589 let recovery_capture_bytes_resolved =
590 recovery_capture_bytes.unwrap_or(DEFAULT_RECOVERY_CAPTURE_BYTES);
591 if recovery_capture_bytes_resolved > MAX_RECOVERY_CAPTURE_BYTES {
592 return Err(ConfigError::RecoveryCaptureBytesTooLarge {
593 value: recovery_capture_bytes_resolved,
594 max: MAX_RECOVERY_CAPTURE_BYTES,
595 });
596 }
597
598 if recovery_capture_stdio && recovery_exec_cmd.is_none() && recovery_exec_file.is_none() {
602 return Err(ConfigError::RecoveryCaptureRequiresRecovery);
603 }
604
605 let any_recovery_configured = recovery_exec_cmd.is_some() || recovery_exec_file.is_some();
618 if any_recovery_configured {
619 if let Some(port) = udp_port {
620 let bind_ip =
621 udp_bind_addr.unwrap_or(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED));
622 let udp_addr = format!("{bind_ip}:{port}");
623 let secure_udp = secure_key_file.is_some()
625 || accepted_key_file.is_some()
626 || master_key_file.is_some();
627 let plaintext_udp = i_accept_plaintext_udp && !secure_udp;
629
630 if secure_udp && !i_accept_recovery_on_secure_udp {
631 return Err(ConfigError::RecoveryRequiresAuthenticatedTransport { udp_addr });
632 }
633 if plaintext_udp && !i_accept_recovery_on_plaintext_udp {
634 return Err(ConfigError::RecoveryRequiresAuthenticatedTransport { udp_addr });
635 }
636 }
637 }
638
639 if let Some(port) = udp_port {
647 let secure_udp = secure_key_file.is_some()
648 || accepted_key_file.is_some()
649 || master_key_file.is_some();
650 if secure_udp {
651 if let Some(ip) = udp_bind_addr {
652 if !ip.is_loopback() && !i_accept_secure_udp_non_loopback {
653 return Err(ConfigError::SecureUdpRequiresLoopbackBind {
654 udp_addr: format!("{ip}:{port}"),
655 });
656 }
657 }
658 }
659 }
660
661 let iteration_budget = match iteration_budget_ms {
667 Some(ms) => {
668 if !(MIN_ITERATION_BUDGET_MS..=MAX_ITERATION_BUDGET_MS).contains(&ms) {
669 return Err(ConfigError::IterationBudgetOutOfRange {
670 value: ms,
671 min: MIN_ITERATION_BUDGET_MS,
672 max: MAX_ITERATION_BUDGET_MS,
673 });
674 }
675 Duration::from_millis(ms)
676 }
677 None => crate::exporter::DEFAULT_ITERATION_BUDGET,
678 };
679
680 let scrape_budget = match scrape_budget_ms {
686 Some(ms) => {
687 if !(MIN_SCRAPE_BUDGET_MS..=MAX_SCRAPE_BUDGET_MS).contains(&ms) {
688 return Err(ConfigError::ScrapeBudgetOutOfRange {
689 value: ms,
690 min: MIN_SCRAPE_BUDGET_MS,
691 max: MAX_SCRAPE_BUDGET_MS,
692 });
693 }
694 Duration::from_millis(ms)
695 }
696 None => crate::exporter::DEFAULT_SCRAPE_BUDGET,
697 };
698
699 let eviction_scan_window_resolved = match eviction_scan_window {
700 Some(v) => {
701 if !(MIN_EVICTION_SCAN_WINDOW..=MAX_EVICTION_SCAN_WINDOW).contains(&v) {
702 return Err(ConfigError::EvictionScanWindowOutOfRange {
703 value: v,
704 min: MIN_EVICTION_SCAN_WINDOW,
705 max: MAX_EVICTION_SCAN_WINDOW,
706 });
707 }
708 v
709 }
710 None => DEFAULT_EVICTION_SCAN_WINDOW,
711 };
712
713 if let Some(src) = clock_source {
717 if src.clk_id().is_none() {
718 return Err(ConfigError::ClockSourceUnsupported {
719 source: src,
720 platform: std::env::consts::OS,
721 });
722 }
723 }
724
725 Ok(Config {
726 socket,
727 threshold: Duration::from_millis(threshold_ms),
728 recovery_exec_cmd,
729 recovery_exec_file,
730 recovery_debounce,
731 recovery_env,
732 recovery_inherit_env,
733 file_export,
734 export_file_max_bytes,
735 export_file_sync_every,
736 prom_addr,
737 prom_token_file,
738 shutdown_after: shutdown_after_secs.map(Duration::from_secs),
739 recovery_timeout: recovery_timeout_ms.map(Duration::from_millis),
740 shutdown_grace: Duration::from_millis(shutdown_grace_ms),
741 socket_mode: socket_mode.unwrap_or(DEFAULT_SOCKET_MODE),
742 read_timeout: Duration::from_millis(read_timeout_ms.unwrap_or(DEFAULT_READ_TIMEOUT_MS)),
743 tracker_capacity: tracker_capacity.unwrap_or(DEFAULT_CAPACITY),
744 tracker_eviction_policy: tracker_eviction_policy.unwrap_or(EvictionPolicy::Strict),
745 eviction_scan_window: eviction_scan_window_resolved,
746 udp_port,
747 udp_bind_addr,
748 secure_key_file,
749 accepted_key_file,
750 master_key_file,
751 max_beat_rate: match max_beat_rate {
752 None => Some(super::types::DEFAULT_MAX_BEAT_RATE),
753 Some(0) => None,
754 Some(n) => Some(n),
755 },
756 global_beat_rate: global_beat_rate.unwrap_or(super::types::DEFAULT_GLOBAL_BEAT_RATE),
757 global_beat_burst: global_beat_burst.unwrap_or(super::types::DEFAULT_GLOBAL_BEAT_BURST),
758 uds_rcvbuf_bytes: uds_rcvbuf_bytes.unwrap_or(super::types::DEFAULT_UDS_RCVBUF_BYTES),
759 heartbeat_file,
760 self_watchdog,
761 hw_watchdog,
762 prom_rate_limit_per_sec: prom_rate_limit_per_sec
763 .unwrap_or(DEFAULT_PROM_RATE_LIMIT_PER_SEC),
764 prom_rate_limit_burst: prom_rate_limit_burst.unwrap_or(DEFAULT_PROM_RATE_LIMIT_BURST),
765 i_accept_plaintext_udp,
766 i_accept_recovery_on_secure_udp,
767 i_accept_recovery_on_plaintext_udp,
768 i_accept_secure_udp_non_loopback,
769 allow_cross_namespace_agents,
770 strict_namespace_check,
771 recovery_audit_file,
772 recovery_audit_max_bytes,
773 recovery_audit_sync_every: recovery_audit_sync_every.unwrap_or(1),
774 recovery_capture_stdio,
775 recovery_capture_bytes: recovery_capture_bytes_resolved,
776 iteration_budget,
777 scrape_budget,
778 audit_fsync_budget_ms: audit_fsync_budget_ms
779 .unwrap_or(super::types::DEFAULT_AUDIT_FSYNC_BUDGET_MS),
780 audit_sync_interval_ms: audit_sync_interval_ms
781 .unwrap_or(super::types::DEFAULT_AUDIT_SYNC_INTERVAL_MS),
782 audit_rotation_budget_ms: audit_rotation_budget_ms
783 .unwrap_or(super::types::DEFAULT_AUDIT_ROTATION_BUDGET_MS),
784 #[cfg(feature = "test-hooks")]
785 inject_wedge_ms,
786 clock_source: clock_source.unwrap_or(ClockSource::Monotonic),
787 signal_handler_mode: signal_handler_mode.unwrap_or_default(),
788 })
789 }
790}