Skip to main content

fips_core/config/
node.rs

1//! Node configuration subsections.
2//!
3//! All the `node.*` configuration parameters: resource limits, rate limiting,
4//! retry/backoff, cache sizing, discovery, spanning tree, bloom filters,
5//! session management, and internal buffers.
6
7use serde::{Deserialize, Serialize};
8
9use super::IdentityConfig;
10use crate::mmp::{DEFAULT_LOG_INTERVAL_SECS, DEFAULT_OWD_WINDOW_SIZE, MmpConfig, MmpMode};
11
12// ============================================================================
13// Node Configuration Subsections
14// ============================================================================
15
16/// Resource limits (`node.limits.*`).
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct LimitsConfig {
19    /// Max handshake-phase connections (`node.limits.max_connections`).
20    #[serde(default = "LimitsConfig::default_max_connections")]
21    pub max_connections: usize,
22    /// Max authenticated peers (`node.limits.max_peers`).
23    #[serde(default = "LimitsConfig::default_max_peers")]
24    pub max_peers: usize,
25    /// Max active links (`node.limits.max_links`).
26    #[serde(default = "LimitsConfig::default_max_links")]
27    pub max_links: usize,
28    /// Max pending inbound handshakes (`node.limits.max_pending_inbound`).
29    #[serde(default = "LimitsConfig::default_max_pending_inbound")]
30    pub max_pending_inbound: usize,
31}
32
33impl Default for LimitsConfig {
34    fn default() -> Self {
35        Self {
36            max_connections: 256,
37            max_peers: 128,
38            max_links: 256,
39            max_pending_inbound: 1000,
40        }
41    }
42}
43
44impl LimitsConfig {
45    fn default_max_connections() -> usize {
46        256
47    }
48    fn default_max_peers() -> usize {
49        128
50    }
51    fn default_max_links() -> usize {
52        256
53    }
54    fn default_max_pending_inbound() -> usize {
55        1000
56    }
57}
58
59/// Rate limiting (`node.rate_limit.*`).
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct RateLimitConfig {
62    /// Token bucket burst capacity (`node.rate_limit.handshake_burst`).
63    #[serde(default = "RateLimitConfig::default_handshake_burst")]
64    pub handshake_burst: u32,
65    /// Tokens/sec refill rate (`node.rate_limit.handshake_rate`).
66    #[serde(default = "RateLimitConfig::default_handshake_rate")]
67    pub handshake_rate: f64,
68    /// Stale handshake cleanup timeout in seconds (`node.rate_limit.handshake_timeout_secs`).
69    #[serde(default = "RateLimitConfig::default_handshake_timeout_secs")]
70    pub handshake_timeout_secs: u64,
71    /// Initial handshake resend interval in ms (`node.rate_limit.handshake_resend_interval_ms`).
72    /// Handshake messages are resent with exponential backoff within the timeout window.
73    #[serde(default = "RateLimitConfig::default_handshake_resend_interval_ms")]
74    pub handshake_resend_interval_ms: u64,
75    /// Handshake resend backoff multiplier (`node.rate_limit.handshake_resend_backoff`).
76    #[serde(default = "RateLimitConfig::default_handshake_resend_backoff")]
77    pub handshake_resend_backoff: f64,
78    /// Max handshake resends per attempt (`node.rate_limit.handshake_max_resends`).
79    #[serde(default = "RateLimitConfig::default_handshake_max_resends")]
80    pub handshake_max_resends: u32,
81}
82
83impl Default for RateLimitConfig {
84    fn default() -> Self {
85        Self {
86            handshake_burst: 100,
87            handshake_rate: 10.0,
88            handshake_timeout_secs: 30,
89            handshake_resend_interval_ms: 1000,
90            handshake_resend_backoff: 2.0,
91            handshake_max_resends: 5,
92        }
93    }
94}
95
96impl RateLimitConfig {
97    fn default_handshake_burst() -> u32 {
98        100
99    }
100    fn default_handshake_rate() -> f64 {
101        10.0
102    }
103    fn default_handshake_timeout_secs() -> u64 {
104        30
105    }
106    fn default_handshake_resend_interval_ms() -> u64 {
107        1000
108    }
109    fn default_handshake_resend_backoff() -> f64 {
110        2.0
111    }
112    fn default_handshake_max_resends() -> u32 {
113        5
114    }
115}
116
117/// Retry/backoff configuration (`node.retry.*`).
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct RetryConfig {
120    /// Max connection retry attempts (`node.retry.max_retries`).
121    #[serde(default = "RetryConfig::default_max_retries")]
122    pub max_retries: u32,
123    /// Base backoff interval in seconds (`node.retry.base_interval_secs`).
124    #[serde(default = "RetryConfig::default_base_interval_secs")]
125    pub base_interval_secs: u64,
126    /// Cap on exponential backoff in seconds (`node.retry.max_backoff_secs`).
127    #[serde(default = "RetryConfig::default_max_backoff_secs")]
128    pub max_backoff_secs: u64,
129}
130
131impl Default for RetryConfig {
132    fn default() -> Self {
133        Self {
134            max_retries: 5,
135            base_interval_secs: 5,
136            max_backoff_secs: 300,
137        }
138    }
139}
140
141impl RetryConfig {
142    fn default_max_retries() -> u32 {
143        5
144    }
145    fn default_base_interval_secs() -> u64 {
146        5
147    }
148    fn default_max_backoff_secs() -> u64 {
149        300
150    }
151}
152
153/// Cache parameters (`node.cache.*`).
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct CacheConfig {
156    /// Max entries in coord cache (`node.cache.coord_size`).
157    #[serde(default = "CacheConfig::default_coord_size")]
158    pub coord_size: usize,
159    /// Coord cache entry TTL in seconds (`node.cache.coord_ttl_secs`).
160    #[serde(default = "CacheConfig::default_coord_ttl_secs")]
161    pub coord_ttl_secs: u64,
162    /// Max entries in identity cache (`node.cache.identity_size`).
163    #[serde(default = "CacheConfig::default_identity_size")]
164    pub identity_size: usize,
165}
166
167impl Default for CacheConfig {
168    fn default() -> Self {
169        Self {
170            coord_size: 50_000,
171            coord_ttl_secs: 300,
172            identity_size: 10_000,
173        }
174    }
175}
176
177impl CacheConfig {
178    fn default_coord_size() -> usize {
179        50_000
180    }
181    fn default_coord_ttl_secs() -> u64 {
182        300
183    }
184    fn default_identity_size() -> usize {
185        10_000
186    }
187}
188
189mod discovery;
190
191pub use discovery::{DiscoveryConfig, NostrDiscoveryConfig, NostrDiscoveryPolicy};
192
193/// Spanning tree (`node.tree.*`).
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct TreeConfig {
196    /// Per-peer TreeAnnounce rate limit in ms (`node.tree.announce_min_interval_ms`).
197    #[serde(default = "TreeConfig::default_announce_min_interval_ms")]
198    pub announce_min_interval_ms: u64,
199    /// Hysteresis factor for cost-based parent re-selection (`node.tree.parent_hysteresis`).
200    ///
201    /// Only switch parents when the candidate's effective_depth is better than
202    /// `current_effective_depth * (1.0 - parent_hysteresis)`. Range: 0.0-1.0.
203    /// Set to 0.0 to disable hysteresis (switch on any improvement).
204    #[serde(default = "TreeConfig::default_parent_hysteresis")]
205    pub parent_hysteresis: f64,
206    /// Hold-down period after parent switch in seconds (`node.tree.hold_down_secs`).
207    ///
208    /// After switching parents, suppress re-evaluation for this duration to allow
209    /// MMP metrics to stabilize on the new link. Set to 0 to disable.
210    #[serde(default = "TreeConfig::default_hold_down_secs")]
211    pub hold_down_secs: u64,
212    /// Periodic parent re-evaluation interval in seconds (`node.tree.reeval_interval_secs`).
213    ///
214    /// How often to re-evaluate parent selection based on current MMP link costs,
215    /// independent of TreeAnnounce traffic. Catches link degradation after the
216    /// tree has stabilized. Set to 0 to disable.
217    #[serde(default = "TreeConfig::default_reeval_interval_secs")]
218    pub reeval_interval_secs: u64,
219    /// Flap dampening: max parent switches before extended hold-down (`node.tree.flap_threshold`).
220    #[serde(default = "TreeConfig::default_flap_threshold")]
221    pub flap_threshold: u32,
222    /// Flap dampening: window in seconds for counting switches (`node.tree.flap_window_secs`).
223    #[serde(default = "TreeConfig::default_flap_window_secs")]
224    pub flap_window_secs: u64,
225    /// Flap dampening: extended hold-down duration in seconds (`node.tree.flap_dampening_secs`).
226    #[serde(default = "TreeConfig::default_flap_dampening_secs")]
227    pub flap_dampening_secs: u64,
228}
229
230impl Default for TreeConfig {
231    fn default() -> Self {
232        Self {
233            announce_min_interval_ms: 500,
234            parent_hysteresis: 0.2,
235            hold_down_secs: 30,
236            reeval_interval_secs: 60,
237            flap_threshold: 4,
238            flap_window_secs: 60,
239            flap_dampening_secs: 120,
240        }
241    }
242}
243
244impl TreeConfig {
245    fn default_announce_min_interval_ms() -> u64 {
246        500
247    }
248    fn default_parent_hysteresis() -> f64 {
249        0.2
250    }
251    fn default_hold_down_secs() -> u64 {
252        30
253    }
254    fn default_reeval_interval_secs() -> u64 {
255        60
256    }
257    fn default_flap_threshold() -> u32 {
258        4
259    }
260    fn default_flap_window_secs() -> u64 {
261        60
262    }
263    fn default_flap_dampening_secs() -> u64 {
264        120
265    }
266}
267
268/// Routing strategy selection (`node.routing.*`).
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct RoutingConfig {
271    /// Next-hop selection mode (`node.routing.mode`).
272    #[serde(default)]
273    pub mode: RoutingMode,
274    /// TTL for learned reverse-path routes in seconds (`node.routing.learned_ttl_secs`).
275    #[serde(default = "RoutingConfig::default_learned_ttl_secs")]
276    pub learned_ttl_secs: u64,
277    /// Maximum locally observed next-hop candidates kept per destination for
278    /// reply-learned multipath/exploration
279    /// (`node.routing.max_learned_routes_per_dest`).
280    #[serde(default = "RoutingConfig::default_max_learned_routes_per_dest")]
281    pub max_learned_routes_per_dest: usize,
282    /// Every N learned-route selections, try the coordinate/bloom/tree route
283    /// instead so new paths can be discovered (`0` disables fallback exploration).
284    #[serde(default = "RoutingConfig::default_learned_fallback_explore_interval")]
285    pub learned_fallback_explore_interval: u64,
286}
287
288impl Default for RoutingConfig {
289    fn default() -> Self {
290        Self {
291            mode: RoutingMode::default(),
292            learned_ttl_secs: 300,
293            max_learned_routes_per_dest: 4,
294            learned_fallback_explore_interval: 16,
295        }
296    }
297}
298
299impl RoutingConfig {
300    fn default_learned_ttl_secs() -> u64 {
301        300
302    }
303
304    fn default_max_learned_routes_per_dest() -> usize {
305        4
306    }
307
308    fn default_learned_fallback_explore_interval() -> u64 {
309        16
310    }
311}
312
313/// Daemon routing mode.
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
315#[serde(rename_all = "snake_case")]
316pub enum RoutingMode {
317    /// Current FIPS behavior: bloom-assisted greedy tree routing.
318    #[default]
319    Tree,
320    /// Prefer locally learned reverse paths before falling back to tree routing.
321    ///
322    /// Learned routes are populated only from local evidence: inbound
323    /// SessionDatagrams and verified LookupResponses.
324    ReplyLearned,
325}
326
327impl std::fmt::Display for RoutingMode {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        match self {
330            RoutingMode::Tree => write!(f, "tree"),
331            RoutingMode::ReplyLearned => write!(f, "reply_learned"),
332        }
333    }
334}
335
336/// Bloom filter (`node.bloom.*`).
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct BloomConfig {
339    /// Debounce interval for filter updates in ms (`node.bloom.update_debounce_ms`).
340    #[serde(default = "BloomConfig::default_update_debounce_ms")]
341    pub update_debounce_ms: u64,
342    /// Antipoison cap: reject inbound FilterAnnounce whose FPR exceeds
343    /// this value (`node.bloom.max_inbound_fpr`). Valid range `(0.0, 1.0)`.
344    /// Default `0.10` ≈ fill 0.631 at k=5 ≈ ~1,630 entries on the 1 KB
345    /// filter. This leaves headroom for legitimate aggregates near the
346    /// fixed-filter operating ceiling while still rejecting saturated or
347    /// poisoned filters. Conceptually distinct from future autoscaling
348    /// hysteresis setpoints — same unit, different knobs.
349    #[serde(default = "BloomConfig::default_max_inbound_fpr")]
350    pub max_inbound_fpr: f64,
351}
352
353impl Default for BloomConfig {
354    fn default() -> Self {
355        Self {
356            update_debounce_ms: 500,
357            max_inbound_fpr: 0.10,
358        }
359    }
360}
361
362impl BloomConfig {
363    fn default_update_debounce_ms() -> u64 {
364        500
365    }
366    fn default_max_inbound_fpr() -> f64 {
367        0.10
368    }
369}
370
371/// Session/data plane (`node.session.*`).
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct SessionConfig {
374    /// Default SessionDatagram TTL (`node.session.default_ttl`).
375    #[serde(default = "SessionConfig::default_ttl")]
376    pub default_ttl: u8,
377    /// Queue depth per dest during session establishment (`node.session.pending_packets_per_dest`).
378    #[serde(default = "SessionConfig::default_pending_packets_per_dest")]
379    pub pending_packets_per_dest: usize,
380    /// Max destinations with pending packets (`node.session.pending_max_destinations`).
381    #[serde(default = "SessionConfig::default_pending_max_destinations")]
382    pub pending_max_destinations: usize,
383    /// Idle session timeout in seconds (`node.session.idle_timeout_secs`).
384    /// Established sessions with no application data for this duration are
385    /// removed. MMP reports do not count as activity for this timer.
386    #[serde(default = "SessionConfig::default_idle_timeout_secs")]
387    pub idle_timeout_secs: u64,
388    /// Number of initial data packets per session that include COORDS_PRESENT
389    /// for transit cache warmup (`node.session.coords_warmup_packets`).
390    /// Also used as the reset count on CoordsRequired receipt.
391    #[serde(default = "SessionConfig::default_coords_warmup_packets")]
392    pub coords_warmup_packets: u8,
393    /// Minimum interval (ms) between standalone CoordsWarmup responses to
394    /// CoordsRequired/PathBroken signals, per destination
395    /// (`node.session.coords_response_interval_ms`).
396    #[serde(default = "SessionConfig::default_coords_response_interval_ms")]
397    pub coords_response_interval_ms: u64,
398}
399
400impl Default for SessionConfig {
401    fn default() -> Self {
402        Self {
403            default_ttl: 64,
404            pending_packets_per_dest: 16,
405            pending_max_destinations: 256,
406            idle_timeout_secs: 90,
407            coords_warmup_packets: 5,
408            coords_response_interval_ms: 2000,
409        }
410    }
411}
412
413impl SessionConfig {
414    fn default_ttl() -> u8 {
415        64
416    }
417    fn default_pending_packets_per_dest() -> usize {
418        16
419    }
420    fn default_pending_max_destinations() -> usize {
421        256
422    }
423    fn default_idle_timeout_secs() -> u64 {
424        90
425    }
426    fn default_coords_warmup_packets() -> u8 {
427        5
428    }
429    fn default_coords_response_interval_ms() -> u64 {
430        2000
431    }
432}
433
434/// Session-layer Metrics Measurement Protocol (`node.session_mmp.*`).
435///
436/// Separate from link-layer `node.mmp.*` to allow independent mode/interval
437/// configuration per layer. Session reports consume bandwidth on every transit
438/// link, so operators may want a lighter mode (e.g., Lightweight) for sessions
439/// while running Full mode on links.
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct SessionMmpConfig {
442    /// Operating mode (`node.session_mmp.mode`).
443    #[serde(default)]
444    pub mode: MmpMode,
445
446    /// Periodic operator log interval in seconds (`node.session_mmp.log_interval_secs`).
447    #[serde(default = "SessionMmpConfig::default_log_interval_secs")]
448    pub log_interval_secs: u64,
449
450    /// OWD trend ring buffer size (`node.session_mmp.owd_window_size`).
451    #[serde(default = "SessionMmpConfig::default_owd_window_size")]
452    pub owd_window_size: usize,
453}
454
455impl Default for SessionMmpConfig {
456    fn default() -> Self {
457        Self {
458            mode: MmpMode::default(),
459            log_interval_secs: DEFAULT_LOG_INTERVAL_SECS,
460            owd_window_size: DEFAULT_OWD_WINDOW_SIZE,
461        }
462    }
463}
464
465impl SessionMmpConfig {
466    fn default_log_interval_secs() -> u64 {
467        DEFAULT_LOG_INTERVAL_SECS
468    }
469    fn default_owd_window_size() -> usize {
470        DEFAULT_OWD_WINDOW_SIZE
471    }
472}
473
474/// Control socket configuration (`node.control.*`).
475#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct ControlConfig {
477    /// Enable the control socket (`node.control.enabled`).
478    #[serde(default = "ControlConfig::default_enabled")]
479    pub enabled: bool,
480    /// Unix socket path (`node.control.socket_path`).
481    #[serde(default = "ControlConfig::default_socket_path")]
482    pub socket_path: String,
483}
484
485impl Default for ControlConfig {
486    fn default() -> Self {
487        Self {
488            enabled: true,
489            socket_path: Self::default_socket_path(),
490        }
491    }
492}
493
494impl ControlConfig {
495    fn default_enabled() -> bool {
496        true
497    }
498
499    /// Default control socket path.
500    ///
501    /// On Unix, returns the shared `/run/fips`, `XDG_RUNTIME_DIR`, then `/tmp`
502    /// fallback used by fipsctl and fipstop. On Windows, returns a TCP port
503    /// number as a string since Windows does not support Unix domain sockets;
504    /// the control socket listens on localhost at this port.
505    fn default_socket_path() -> String {
506        #[cfg(unix)]
507        {
508            super::resolve_default_socket("control.sock")
509        }
510        #[cfg(windows)]
511        {
512            "21210".to_string()
513        }
514    }
515}
516
517const DEFAULT_PACKET_CHANNEL_CAPACITY: usize = 16_384;
518
519/// Internal buffers (`node.buffers.*`).
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct BuffersConfig {
522    /// Transport→Node bulk packet capacity (`node.buffers.packet_channel`).
523    ///
524    /// Priority/control packets use a reserved lane. This bounds bulk packet
525    /// backlog in packet units rather than receive-batch channel items.
526    #[serde(default = "BuffersConfig::default_packet_channel")]
527    pub packet_channel: usize,
528    /// TUN→Node outbound channel capacity (`node.buffers.tun_channel`).
529    #[serde(default = "BuffersConfig::default_tun_channel")]
530    pub tun_channel: usize,
531    /// DNS→Node identity channel capacity (`node.buffers.dns_channel`).
532    #[serde(default = "BuffersConfig::default_dns_channel")]
533    pub dns_channel: usize,
534}
535
536impl Default for BuffersConfig {
537    fn default() -> Self {
538        Self {
539            packet_channel: DEFAULT_PACKET_CHANNEL_CAPACITY,
540            tun_channel: 1024,
541            dns_channel: 64,
542        }
543    }
544}
545
546impl BuffersConfig {
547    fn default_packet_channel() -> usize {
548        DEFAULT_PACKET_CHANNEL_CAPACITY
549    }
550    fn default_tun_channel() -> usize {
551        1024
552    }
553    fn default_dns_channel() -> usize {
554        64
555    }
556}
557
558// ============================================================================
559// ECN Congestion Signaling
560// ============================================================================
561
562/// Rekey / session rekeying configuration (`node.rekey.*`).
563///
564/// Controls periodic full rekey for both FMP (link layer) and FSP
565/// (session layer) Noise sessions. Rekeying provides true forward secrecy
566/// with fresh DH randomness, nonce reset, and session index rotation.
567///
568/// Keep the packet-count default high for packet-tunnel workloads. A low value
569/// such as 65k packets can force multi-hundred-Mbit tunnels to rekey every few
570/// seconds, which creates avoidable cutover churn and can dominate throughput.
571/// Operators can still lower `node.rekey.after_messages` for CI stress tests or
572/// very conservative deployments; the time-based `after_secs` default remains
573/// the normal production rekey cadence.
574const DEFAULT_REKEY_AFTER_MESSAGES: u64 = 1 << 48;
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
577pub struct RekeyConfig {
578    /// Enable periodic rekey (`node.rekey.enabled`).
579    #[serde(default = "RekeyConfig::default_enabled")]
580    pub enabled: bool,
581
582    /// Initiate rekey after this many seconds (`node.rekey.after_secs`).
583    #[serde(default = "RekeyConfig::default_after_secs")]
584    pub after_secs: u64,
585
586    /// Initiate rekey after this many messages sent (`node.rekey.after_messages`).
587    #[serde(default = "RekeyConfig::default_after_messages")]
588    pub after_messages: u64,
589}
590
591impl Default for RekeyConfig {
592    fn default() -> Self {
593        Self {
594            enabled: true,
595            after_secs: 120,
596            after_messages: DEFAULT_REKEY_AFTER_MESSAGES,
597        }
598    }
599}
600
601impl RekeyConfig {
602    fn default_enabled() -> bool {
603        true
604    }
605    fn default_after_secs() -> u64 {
606        120
607    }
608    fn default_after_messages() -> u64 {
609        DEFAULT_REKEY_AFTER_MESSAGES
610    }
611}
612
613/// ECN congestion signaling configuration (`node.ecn.*`).
614///
615/// Controls the FMP CE relay chain: transit nodes detect congestion on outgoing
616/// links and set the CE flag in forwarded datagrams. The destination marks
617/// IPv6 ECN-CE on ECN-capable packets before TUN delivery.
618#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct EcnConfig {
620    /// Enable ECN congestion signaling (`node.ecn.enabled`).
621    #[serde(default = "EcnConfig::default_enabled")]
622    pub enabled: bool,
623
624    /// Loss rate threshold for marking CE (`node.ecn.loss_threshold`).
625    /// When the outgoing link's loss rate meets or exceeds this value,
626    /// the transit node sets CE on forwarded datagrams.
627    #[serde(default = "EcnConfig::default_loss_threshold")]
628    pub loss_threshold: f64,
629
630    /// ETX threshold for marking CE (`node.ecn.etx_threshold`).
631    /// When the outgoing link's ETX meets or exceeds this value,
632    /// the transit node sets CE on forwarded datagrams.
633    #[serde(default = "EcnConfig::default_etx_threshold")]
634    pub etx_threshold: f64,
635}
636
637impl Default for EcnConfig {
638    fn default() -> Self {
639        Self {
640            enabled: true,
641            loss_threshold: 0.05,
642            etx_threshold: 3.0,
643        }
644    }
645}
646
647impl EcnConfig {
648    fn default_enabled() -> bool {
649        true
650    }
651    fn default_loss_threshold() -> f64 {
652        0.05
653    }
654    fn default_etx_threshold() -> f64 {
655        3.0
656    }
657}
658
659// ============================================================================
660// Node Configuration (Root)
661// ============================================================================
662
663/// Node configuration (`node.*`).
664#[derive(Debug, Clone, Serialize, Deserialize)]
665pub struct NodeConfig {
666    /// Identity configuration (`node.identity.*`).
667    #[serde(default)]
668    pub identity: IdentityConfig,
669
670    /// Leaf-only mode (`node.leaf_only`).
671    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
672    pub leaf_only: bool,
673
674    /// RX loop maintenance tick period in seconds (`node.tick_interval_secs`).
675    #[serde(default = "NodeConfig::default_tick_interval_secs")]
676    pub tick_interval_secs: u64,
677
678    /// Initial RTT estimate for new links in ms (`node.base_rtt_ms`).
679    #[serde(default = "NodeConfig::default_base_rtt_ms")]
680    pub base_rtt_ms: u64,
681
682    /// Link heartbeat send interval in seconds (`node.heartbeat_interval_secs`).
683    #[serde(default = "NodeConfig::default_heartbeat_interval_secs")]
684    pub heartbeat_interval_secs: u64,
685
686    /// Link dead timeout in seconds (`node.link_dead_timeout_secs`).
687    /// Peers silent for this duration are removed.
688    #[serde(default = "NodeConfig::default_link_dead_timeout_secs")]
689    pub link_dead_timeout_secs: u64,
690
691    /// Accelerated link dead timeout in seconds, used in place of
692    /// `link_dead_timeout_secs` while a recent `transport.send` returned
693    /// a local-side errno (`NetworkUnreachable` / `HostUnreachable` /
694    /// `AddrNotAvailable`) — direct evidence our outbound path is broken
695    /// right now (interface vanished, default route flapped, etc.). No
696    /// reason to wait the full receive-silence window when the kernel
697    /// already told us we can't send. Steady-state behavior is unchanged
698    /// because the signal is cleared on the next successful send.
699    /// (`node.fast_link_dead_timeout_secs`)
700    #[serde(default = "NodeConfig::default_fast_link_dead_timeout_secs")]
701    pub fast_link_dead_timeout_secs: u64,
702
703    /// Resource limits (`node.limits.*`).
704    #[serde(default)]
705    pub limits: LimitsConfig,
706
707    /// Rate limiting (`node.rate_limit.*`).
708    #[serde(default)]
709    pub rate_limit: RateLimitConfig,
710
711    /// Retry/backoff (`node.retry.*`).
712    #[serde(default)]
713    pub retry: RetryConfig,
714
715    /// Cache parameters (`node.cache.*`).
716    #[serde(default)]
717    pub cache: CacheConfig,
718
719    /// Discovery protocol (`node.discovery.*`).
720    #[serde(default)]
721    pub discovery: DiscoveryConfig,
722
723    /// Spanning tree (`node.tree.*`).
724    #[serde(default)]
725    pub tree: TreeConfig,
726
727    /// Routing strategy (`node.routing.*`).
728    #[serde(default)]
729    pub routing: RoutingConfig,
730
731    /// Bloom filter (`node.bloom.*`).
732    #[serde(default)]
733    pub bloom: BloomConfig,
734
735    /// Session/data plane (`node.session.*`).
736    #[serde(default)]
737    pub session: SessionConfig,
738
739    /// Internal buffers (`node.buffers.*`).
740    #[serde(default)]
741    pub buffers: BuffersConfig,
742
743    /// Control socket (`node.control.*`).
744    #[serde(default)]
745    pub control: ControlConfig,
746
747    /// Metrics Measurement Protocol — link layer (`node.mmp.*`).
748    #[serde(default)]
749    pub mmp: MmpConfig,
750
751    /// Metrics Measurement Protocol — session layer (`node.session_mmp.*`).
752    #[serde(default)]
753    pub session_mmp: SessionMmpConfig,
754
755    /// ECN congestion signaling (`node.ecn.*`).
756    #[serde(default)]
757    pub ecn: EcnConfig,
758
759    /// Rekey / session rekeying (`node.rekey.*`).
760    #[serde(default)]
761    pub rekey: RekeyConfig,
762
763    /// Enable daemon-oriented system files such as `/etc/fips/hosts` and
764    /// `/etc/fips/peers.{allow,deny}`. Embedded endpoints disable this.
765    #[serde(default = "NodeConfig::default_system_files_enabled")]
766    pub system_files_enabled: bool,
767
768    /// Log level (`node.log_level`). Case-insensitive.
769    /// Valid values: trace, debug, info, warn, error. Default: info.
770    #[serde(default)]
771    pub log_level: Option<String>,
772}
773
774impl Default for NodeConfig {
775    fn default() -> Self {
776        Self {
777            identity: IdentityConfig::default(),
778            leaf_only: false,
779            tick_interval_secs: 1,
780            base_rtt_ms: 100,
781            heartbeat_interval_secs: 10,
782            link_dead_timeout_secs: 30,
783            fast_link_dead_timeout_secs: 5,
784            limits: LimitsConfig::default(),
785            rate_limit: RateLimitConfig::default(),
786            retry: RetryConfig::default(),
787            cache: CacheConfig::default(),
788            discovery: DiscoveryConfig::default(),
789            tree: TreeConfig::default(),
790            routing: RoutingConfig::default(),
791            bloom: BloomConfig::default(),
792            session: SessionConfig::default(),
793            buffers: BuffersConfig::default(),
794            control: ControlConfig::default(),
795            mmp: MmpConfig::default(),
796            session_mmp: SessionMmpConfig::default(),
797            ecn: EcnConfig::default(),
798            rekey: RekeyConfig::default(),
799            system_files_enabled: true,
800            log_level: None,
801        }
802    }
803}
804
805impl NodeConfig {
806    /// Get the log level as a tracing Level. Default: INFO.
807    pub fn log_level(&self) -> tracing::Level {
808        match self
809            .log_level
810            .as_deref()
811            .map(|s| s.to_lowercase())
812            .as_deref()
813        {
814            Some("trace") => tracing::Level::TRACE,
815            Some("debug") => tracing::Level::DEBUG,
816            Some("warn") | Some("warning") => tracing::Level::WARN,
817            Some("error") => tracing::Level::ERROR,
818            _ => tracing::Level::INFO,
819        }
820    }
821
822    fn default_tick_interval_secs() -> u64 {
823        1
824    }
825    fn default_base_rtt_ms() -> u64 {
826        100
827    }
828    fn default_heartbeat_interval_secs() -> u64 {
829        10
830    }
831    fn default_link_dead_timeout_secs() -> u64 {
832        30
833    }
834    fn default_fast_link_dead_timeout_secs() -> u64 {
835        5
836    }
837    fn default_system_files_enabled() -> bool {
838        true
839    }
840}
841
842#[cfg(test)]
843mod tests;