Skip to main content

veilid_core/
veilid_config.rs

1use crate::*;
2
3cfg_if::cfg_if! {
4    if #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] {
5        use directories::ProjectDirs;
6    }
7}
8
9cfg_if::cfg_if! {
10    if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
11        /// Minimum allowed total `network.max_connections` on wasm32/browser.
12        pub const MAX_CONNECTIONS_MIN: u32 = 16;
13        /// Maximum allowed total `network.max_connections` on wasm32/browser.
14        pub const MAX_CONNECTIONS_MAX: u32 = 64;
15    } else {
16        /// Minimum allowed total `network.max_connections` on native platforms.
17        pub const MAX_CONNECTIONS_MIN: u32 = 32;
18        /// Maximum allowed total `network.max_connections` on native platforms.
19        pub const MAX_CONNECTIONS_MAX: u32 = 512;
20    }
21}
22
23/// Enable and configure UDP.
24///
25/// ```yaml
26/// udp:
27///     enabled: true
28///     socket_pool_size: 0
29///     listen_address: ':5150'
30///     public_address: ''
31/// ```
32///
33#[apply(api_data_struct!)]
34#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
35pub struct VeilidConfigUDP {
36    /// Enable the UDP protocol.
37    pub enabled: bool,
38    /// Local address to bind, as `ip:port` (empty binds the default port).
39    pub listen_address: String,
40    /// Externally-reachable `ip:port` to advertise, if behind NAT/port mapping.
41    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
42    pub public_address: Option<String>,
43}
44
45impl Default for VeilidConfigUDP {
46    fn default() -> Self {
47        cfg_if::cfg_if! {
48            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
49                let enabled = false;
50            } else {
51                let enabled = true;
52            }
53        }
54        Self {
55            enabled,
56            listen_address: String::from(""),
57            public_address: None,
58        }
59    }
60}
61
62/// Enable and configure TCP.
63///
64/// ```yaml
65/// tcp:
66///     connect: true
67///     listen: true
68///     listen_address: ':5150'
69///     public_address: ''
70///
71#[apply(api_data_struct!)]
72#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
73pub struct VeilidConfigTCP {
74    /// Allow outbound TCP connections.
75    pub connect: bool,
76    /// Accept inbound TCP connections.
77    pub listen: bool,
78    /// Local address to bind, as `ip:port` (empty binds the default port).
79    pub listen_address: String,
80    /// Externally-reachable `ip:port` to advertise, if behind NAT/port mapping.
81    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
82    pub public_address: Option<String>,
83}
84
85impl Default for VeilidConfigTCP {
86    fn default() -> Self {
87        cfg_if::cfg_if! {
88            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
89                let connect = false;
90                let listen = false;
91            } else {
92                let connect = true;
93                let listen = true;
94            }
95        }
96        Self {
97            connect,
98            listen,
99            listen_address: String::from(""),
100            public_address: None,
101        }
102    }
103}
104
105/// Enable and configure Web Sockets.
106///
107/// ```yaml
108/// ws:
109///     connect: true
110///     listen: true
111///     listen_address: ':5150'
112///     path: 'ws'
113///     url: 'ws://localhost:5150/ws'
114///
115#[apply(api_data_struct!)]
116#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
117pub struct VeilidConfigWS {
118    /// Allow outbound WebSocket connections.
119    pub connect: bool,
120    /// Accept inbound WebSocket connections.
121    pub listen: bool,
122    /// Local address to bind, as `ip:port` (empty binds the default port).
123    pub listen_address: String,
124    /// URL path served by the WebSocket listener.
125    pub path: String,
126    /// Externally-reachable URL to advertise, if behind NAT/proxy.
127    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
128    pub url: Option<String>,
129}
130
131impl Default for VeilidConfigWS {
132    fn default() -> Self {
133        cfg_if::cfg_if! {
134            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
135                let connect = true;
136                let listen = false;
137            } else {
138                let connect = true;
139                let listen = true;
140            }
141        }
142        Self {
143            connect,
144            listen,
145            listen_address: String::from(""),
146            path: String::from("ws"),
147            url: None,
148        }
149    }
150}
151
152/// Enable and configure Secure Web Sockets.
153///
154/// ```yaml
155/// wss:
156///     connect: true
157///     listen: false
158///     listen_address: ':5150'
159///     path: 'ws'
160///     url: ''
161///
162#[cfg(feature = "enable-protocol-wss")]
163#[apply(api_data_struct!)]
164#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
165pub struct VeilidConfigWSS {
166    /// Allow outbound secure WebSocket connections.
167    pub connect: bool,
168    /// Accept inbound secure WebSocket connections.
169    pub listen: bool,
170    /// Local address to bind, as `ip:port` (empty binds the default port).
171    pub listen_address: String,
172    /// URL path served by the secure WebSocket listener.
173    pub path: String,
174    /// Externally-reachable URL to advertise (required and validated for TLS protocols).
175    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
176    pub url: Option<String>, // Fixed URL is not optional for TLS-based protocols and is dynamically validated
177}
178
179#[cfg(feature = "enable-protocol-wss")]
180impl Default for VeilidConfigWSS {
181    fn default() -> Self {
182        Self {
183            connect: true,
184            listen: false,
185            listen_address: String::from(""),
186            path: String::from("ws"),
187            url: None,
188        }
189    }
190}
191
192/// Configure Network Protocols.
193///
194/// Veilid can communicate over UDP, TCP, and Web Sockets.
195///
196/// All protocols are available by default, and the Veilid node will
197/// sort out which protocol is used for each peer connection.
198///
199#[apply(api_data_struct!)]
200#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
201pub struct VeilidConfigProtocol {
202    /// UDP protocol configuration.
203    pub udp: VeilidConfigUDP,
204    /// TCP protocol configuration.
205    pub tcp: VeilidConfigTCP,
206    /// WebSocket protocol configuration.
207    pub ws: VeilidConfigWS,
208    /// Secure WebSocket protocol configuration.
209    #[cfg(feature = "enable-protocol-wss")]
210    pub wss: VeilidConfigWSS,
211}
212
213/// Privacy preferences for routes.
214///
215/// ```yaml
216/// privacy:
217///     require_inbound_relay: false
218///     country_code_denylist: [] # only with `--features=geolocation`
219/// ```
220#[apply(api_data_struct!)]
221#[api(eq, default)]
222#[cfg_attr(
223    target_arch = "wasm32",
224    derive(Tsify),
225    tsify(into_wasm_abi, from_wasm_abi)
226)]
227pub struct VeilidConfigPrivacy {
228    /// Always use an inbound relay; never accept direct inbound connections.
229    pub require_inbound_relay: bool,
230    /// Two-letter country codes to refuse routing through (requires `geolocation`).
231    #[cfg(feature = "geolocation")]
232    pub country_code_denylist: Vec<CountryCode>,
233}
234
235/// Virtual networking client support for testing/simulation purposes
236///
237/// ```yaml
238/// virtual_network:
239///     enabled: false
240///     server_address: ""
241/// ```
242#[cfg(feature = "virtual-network")]
243#[apply(api_data_struct!)]
244#[api(eq, default)]
245#[cfg_attr(
246    target_arch = "wasm32",
247    derive(Tsify),
248    tsify(into_wasm_abi, from_wasm_abi)
249)]
250pub struct VeilidConfigVirtualNetwork {
251    /// Route all networking through the virtual network server.
252    pub enabled: bool,
253    /// Address of the virtual network server, as `host:port`.
254    pub server_address: String,
255}
256
257/// Configure TLS.
258///
259/// ```yaml
260/// tls:
261///     certificate_path: /path/to/cert
262///     private_key_path: /path/to/private/key
263///     connection_initial_timeout_ms: 2000
264///
265#[apply(api_data_struct!)]
266#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
267pub struct VeilidConfigTLS {
268    /// Path to the TLS certificate (PEM) for inbound TLS protocols.
269    pub certificate_path: String,
270    /// Path to the TLS private key (PEM) for inbound TLS protocols.
271    pub private_key_path: String,
272    /// Timeout for completing a TLS handshake, in milliseconds.
273    pub connection_initial_timeout_ms: u32,
274}
275
276impl Default for VeilidConfigTLS {
277    fn default() -> Self {
278        Self {
279            certificate_path: "".to_string(),
280            private_key_path: "".to_string(),
281            connection_initial_timeout_ms: 2000,
282        }
283    }
284}
285
286#[cfg_attr(
287    all(target_arch = "wasm32", target_os = "unknown"),
288    allow(unused_variables)
289)]
290/// Default directory for TLS certificates and keys, given the program identity and a relative sub-path.
291#[must_use]
292pub fn get_default_ssl_directory(
293    program_name: &str,
294    organization: &str,
295    qualifier: &str,
296    sub_path: &str,
297) -> String {
298    cfg_if::cfg_if! {
299        if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
300            "".to_owned()
301        } else {
302            use std::path::PathBuf;
303            ProjectDirs::from(qualifier, organization, program_name)
304                .map(|dirs| dirs.data_local_dir().join("ssl").join(sub_path))
305                .unwrap_or_else(|| PathBuf::from("./ssl").join(sub_path))
306                .to_string_lossy()
307                .into()
308        }
309    }
310}
311
312/// Configure the Distributed Hash Table (DHT).
313/// Defaults should be used here unless you are absolutely sure you know what you're doing.
314/// If you change the count/fanout/timeout parameters, you may render your node inoperable
315/// for correct DHT operations.
316#[apply(api_data_struct!)]
317#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
318pub struct VeilidConfigDHT {
319    /// Number of subkeys cached for locally-created DHT records.
320    pub local_subkey_cache_size: u32,
321    /// Memory cap for the local subkey cache, in megabytes.
322    pub local_max_subkey_cache_memory_mb: u32,
323    /// Number of subkeys cached for DHT records stored on behalf of others.
324    pub remote_subkey_cache_size: u32,
325    /// Maximum number of remote DHT records stored on behalf of others.
326    pub remote_max_records: u32,
327    /// Memory cap for the remote subkey cache, in megabytes.
328    pub remote_max_subkey_cache_memory_mb: u32,
329    /// Disk cap for remote DHT record storage, in megabytes.
330    pub remote_max_storage_space_mb: u32,
331    /// Max concurrent DHT network operations in flight (local-only ops exempt).
332    #[serde(default = "default_dht_max_concurrent_operations")]
333    pub max_concurrent_operations: u32,
334}
335
336/// Per-platform default for max_concurrent_operations (single source for serde + Default).
337fn default_dht_max_concurrent_operations() -> u32 {
338    cfg_if::cfg_if! {
339        if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
340            16
341        } else {
342            16
343        }
344    }
345}
346
347impl Default for VeilidConfigDHT {
348    fn default() -> Self {
349        cfg_if::cfg_if! {
350            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
351                let local_subkey_cache_size = 128;
352                let local_max_subkey_cache_memory_mb = 256;
353                let remote_subkey_cache_size = 64;
354                let remote_max_records = 64;
355                let remote_max_subkey_cache_memory_mb = 256;
356                let remote_max_storage_space_mb = 128;
357            } else {
358                let local_subkey_cache_size = 1024;
359                let local_max_subkey_cache_memory_mb = match total_memory_bytes() {
360                    Some(mem) => (mem / 32u64 / (1024u64 * 1024u64)) as u32,
361                    None => 256,
362                };
363                let remote_subkey_cache_size = 128;
364                let remote_max_records = 128;
365                let remote_max_subkey_cache_memory_mb = match total_memory_bytes() {
366                    Some(mem) => (mem / 32u64 / (1024u64 * 1024u64)) as u32,
367                    None => 256,
368                };
369                let remote_max_storage_space_mb = 256;
370            }
371        }
372
373        let max_concurrent_operations = default_dht_max_concurrent_operations();
374
375        Self {
376            local_subkey_cache_size,
377            local_max_subkey_cache_memory_mb,
378            remote_subkey_cache_size,
379            remote_max_records,
380            remote_max_subkey_cache_memory_mb,
381            remote_max_storage_space_mb,
382            max_concurrent_operations,
383        }
384    }
385}
386
387/// Configure RPC.
388///
389#[apply(api_data_struct!)]
390#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
391pub struct VeilidConfigRPC {
392    /// Default number of hops used when allocating private routes.
393    pub default_route_hop_count: u8,
394}
395
396impl Default for VeilidConfigRPC {
397    fn default() -> Self {
398        Self {
399            default_route_hop_count: 1,
400        }
401    }
402}
403
404/// Configure the network routing table.
405///
406#[apply(api_data_struct!)]
407#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
408pub struct VeilidConfigRoutingTable {
409    /// This node's identity public keys, by crypto kind (empty = generate fresh).
410    #[cfg_attr(feature = "schemars", schemars(with = "Vec<String>"))]
411    #[cfg_attr(
412        all(target_arch = "wasm32", target_os = "unknown"),
413        tsify(type = "string[]")
414    )]
415    pub public_keys: PublicKeyGroup,
416    /// Node identity secret keys matching `public_keys` (empty = generate fresh).
417    #[cfg_attr(feature = "schemars", schemars(with = "Vec<String>"))]
418    #[cfg_attr(
419        all(target_arch = "wasm32", target_os = "unknown"),
420        tsify(type = "string[]")
421    )]
422    pub secret_keys: SecretKeyGroup,
423    /// Bootstrap server hostnames/URLs used to join the network.
424    pub bootstrap: Vec<String>,
425    /// Public keys trusted to sign bootstrap records.
426    #[cfg_attr(feature = "schemars", schemars(with = "Vec<String>"))]
427    #[cfg_attr(
428        all(target_arch = "wasm32", target_os = "unknown"),
429        tsify(type = "string[]")
430    )]
431    pub bootstrap_keys: Vec<PublicKey>,
432    // xxx pub enable_public_internet: bool,
433    // xxx pub enable_local_network: bool,
434}
435
436impl Default for VeilidConfigRoutingTable {
437    fn default() -> Self {
438        cfg_if::cfg_if! {
439            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
440                let bootstrap = vec!["ws://bootstrap-v1.veilid.net:5150/ws".to_string()];
441            } else {
442                let bootstrap = vec!["bootstrap-v1.veilid.net".to_string()];
443            }
444        }
445        let bootstrap_keys = vec![
446            // Primary Veilid Foundation bootstrap signing key
447            PublicKey::from_str("VLD0:Vj0lKDdUQXmQ5Ol1SZdlvXkBHUccBcQvGLN9vbLSI7k").unwrap_or_log(),
448            // Secondary Veilid Foundation bootstrap signing key
449            PublicKey::from_str("VLD0:QeQJorqbXtC7v3OlynCZ_W3m76wGNeB5NTF81ypqHAo").unwrap_or_log(),
450            // Backup Veilid Foundation bootstrap signing key
451            PublicKey::from_str("VLD0:QNdcl-0OiFfYVj9331XVR6IqZ49NG-E18d5P7lwi4TA").unwrap_or_log(),
452        ];
453
454        Self {
455            public_keys: PublicKeyGroup::default(),
456            secret_keys: SecretKeyGroup::default(),
457            bootstrap,
458            bootstrap_keys,
459        }
460    }
461}
462
463/// An IP address family (IP version) the node may use.
464#[apply(api_data_enum!)]
465#[api(eq, copy, ord, hash, ts(namespace, into_wasm_abi, from_wasm_abi))]
466pub enum VeilidConfigAddressType {
467    /// IPv4 (32-bit) addresses.
468    #[serde(rename = "IPV4", alias = "ipv4", alias = "v4", alias = "4")]
469    Ipv4,
470    /// IPv6 (128-bit) addresses.
471    #[serde(rename = "IPV6", alias = "ipv6", alias = "v6", alias = "6")]
472    Ipv6,
473}
474
475impl fmt::Display for VeilidConfigAddressType {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        match self {
478            VeilidConfigAddressType::Ipv4 => write!(f, "IPV4"),
479            VeilidConfigAddressType::Ipv6 => write!(f, "IPV6"),
480        }
481    }
482}
483
484impl FromStr for VeilidConfigAddressType {
485    type Err = VeilidAPIError;
486    fn from_str(s: &str) -> VeilidAPIResult<VeilidConfigAddressType> {
487        match s.to_ascii_lowercase().as_str() {
488            "v4" | "4" | "ipv4" => Ok(VeilidConfigAddressType::Ipv4),
489            "v6" | "6" | "ipv6" => Ok(VeilidConfigAddressType::Ipv6),
490            _ => apibail_invalid_argument!("invalid VeilidConfigAddressType string", "s", s),
491        }
492    }
493}
494
495/// Network subsystem configuration: connections, routing table, RPC, DHT, transports, and privacy.
496#[apply(api_data_struct!)]
497#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
498pub struct VeilidConfigNetwork {
499    /// Maximum total simultaneous connections across all protocols.
500    /// Range: native 32-512, wasm32/browser 16-64.
501    pub max_connections: u32,
502    /// Optional password; its presence joins a private network with a derived network key.
503    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
504    pub network_key_password: Option<String>,
505    /// Routing table identity and bootstrap configuration.
506    pub routing_table: VeilidConfigRoutingTable,
507    /// RPC configuration.
508    pub rpc: VeilidConfigRPC,
509    /// DHT cache and storage configuration.
510    pub dht: VeilidConfigDHT,
511    /// Enabled IP address families (empty = all available).
512    #[serde(default)]
513    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
514    pub address_types: Vec<VeilidConfigAddressType>,
515    /// Use UPnP to map ports on the local gateway.
516    pub upnp: bool,
517    /// Watch for and react to local address changes (`None` = auto-detect).
518    pub detect_address_changes: Option<bool>,
519    /// TLS configuration for inbound secure protocols.
520    pub tls: VeilidConfigTLS,
521    /// Per-protocol (UDP/TCP/WS/WSS) configuration.
522    pub protocol: VeilidConfigProtocol,
523    /// Privacy and relay preferences.
524    pub privacy: VeilidConfigPrivacy,
525    /// Virtual network client configuration (testing/simulation).
526    #[cfg(feature = "virtual-network")]
527    pub virtual_network: VeilidConfigVirtualNetwork,
528}
529
530impl Default for VeilidConfigNetwork {
531    fn default() -> Self {
532        Self {
533            max_connections: 32,
534            network_key_password: None,
535            address_types: Vec::new(),
536            routing_table: VeilidConfigRoutingTable::default(),
537            rpc: VeilidConfigRPC::default(),
538            dht: VeilidConfigDHT::default(),
539            upnp: true,
540            detect_address_changes: Some(true),
541            tls: VeilidConfigTLS::default(),
542            protocol: VeilidConfigProtocol::default(),
543            privacy: VeilidConfigPrivacy::default(),
544            #[cfg(feature = "virtual-network")]
545            virtual_network: VeilidConfigVirtualNetwork::default(),
546        }
547    }
548}
549
550/// Table store configuration: the encrypted key-value database backing node state.
551#[apply(api_data_struct!)]
552#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
553pub struct VeilidConfigTableStore {
554    /// Directory holding the table store database (empty = platform default).
555    pub directory: String,
556    /// Delete the table store on startup.
557    pub delete: bool,
558    /// Wipe the table store on an invalid device encryption key, rather than failing.
559    pub wipe_on_invalid_device_encryption_key: bool,
560    /// Maximum size of a single stored value, in megabytes.
561    pub max_value_size_mb: u32,
562}
563
564impl Default for VeilidConfigTableStore {
565    fn default() -> Self {
566        Self {
567            directory: "".to_string(),
568            delete: false,
569            wipe_on_invalid_device_encryption_key: true,
570            max_value_size_mb: 64,
571        }
572    }
573}
574
575#[cfg_attr(
576    all(target_arch = "wasm32", target_os = "unknown"),
577    allow(unused_variables)
578)]
579#[must_use]
580fn get_default_store_path(
581    program_name: &str,
582    organization: &str,
583    qualifier: &str,
584    store_type: &str,
585) -> String {
586    cfg_if::cfg_if! {
587        if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
588            "".to_owned()
589        } else {
590            use std::path::PathBuf;
591            ProjectDirs::from(qualifier, organization, program_name)
592                .map(|dirs| dirs.data_local_dir().to_path_buf())
593                .unwrap_or_else(|| PathBuf::from("./"))
594                .join(store_type)
595                .to_string_lossy()
596                .into()
597        }
598    }
599}
600
601/// Block store configuration: content-addressed block storage.
602#[apply(api_data_struct!)]
603#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
604pub struct VeilidConfigBlockStore {
605    /// Directory holding the block store (empty = platform default).
606    pub directory: String,
607    /// Delete the block store on startup.
608    pub delete: bool,
609}
610
611impl Default for VeilidConfigBlockStore {
612    fn default() -> Self {
613        Self {
614            directory: "".to_string(),
615            delete: false,
616        }
617    }
618}
619
620/// Protected store configuration: where secrets such as the device encryption key are kept.
621#[apply(api_data_struct!)]
622#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
623pub struct VeilidConfigProtectedStore {
624    /// Fall back to insecure file storage if no OS keychain/keyring is available.
625    pub allow_insecure_fallback: bool,
626    /// Always use insecure file storage, ignoring any OS keychain/keyring.
627    pub always_use_insecure_storage: bool,
628    /// Directory for insecure-fallback storage (empty = platform default).
629    pub directory: String,
630    /// Delete the protected store on startup.
631    pub delete: bool,
632    /// Password used to encrypt the device encryption key.
633    pub device_encryption_key_password: String,
634    /// New password to re-encrypt the device encryption key with, triggering a rotation.
635    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
636    pub new_device_encryption_key_password: Option<String>,
637}
638
639impl Default for VeilidConfigProtectedStore {
640    fn default() -> Self {
641        Self {
642            allow_insecure_fallback: false,
643            always_use_insecure_storage: false,
644            directory: "".to_string(),
645            delete: false,
646            device_encryption_key_password: "".to_owned(),
647            new_device_encryption_key_password: None,
648        }
649    }
650}
651
652/// Capabilities advertised by this node.
653#[apply(api_data_struct!)]
654#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
655pub struct VeilidConfigCapabilities {
656    /// Capabilities to disable (advertised as unavailable).
657    pub disable: Vec<VeilidCapability>,
658}
659
660/// Logging level threshold (`Off` disables logging).
661#[apply(api_data_enum!)]
662#[api(eq, copy, ord, default, ts(namespace, into_wasm_abi, from_wasm_abi))]
663pub enum VeilidConfigLogLevel {
664    /// Logging disabled.
665    #[default]
666    Off,
667    /// Errors only.
668    Error,
669    /// Warnings and above.
670    Warn,
671    /// Informational messages and above.
672    Info,
673    /// Debug messages and above.
674    Debug,
675    /// All messages, including trace.
676    Trace,
677}
678
679impl From<VeilidLogLevel> for VeilidConfigLogLevel {
680    fn from(value: VeilidLogLevel) -> Self {
681        match value {
682            VeilidLogLevel::Error => Self::Error,
683            VeilidLogLevel::Warn => Self::Warn,
684            VeilidLogLevel::Info => Self::Info,
685            VeilidLogLevel::Debug => Self::Debug,
686            VeilidLogLevel::Trace => Self::Trace,
687        }
688    }
689}
690
691impl From<Option<VeilidLogLevel>> for VeilidConfigLogLevel {
692    fn from(value: Option<VeilidLogLevel>) -> Self {
693        match value {
694            None => Self::Off,
695            Some(VeilidLogLevel::Error) => Self::Error,
696            Some(VeilidLogLevel::Warn) => Self::Warn,
697            Some(VeilidLogLevel::Info) => Self::Info,
698            Some(VeilidLogLevel::Debug) => Self::Debug,
699            Some(VeilidLogLevel::Trace) => Self::Trace,
700        }
701    }
702}
703
704impl From<tracing::level_filters::LevelFilter> for VeilidConfigLogLevel {
705    fn from(value: tracing::level_filters::LevelFilter) -> Self {
706        match value {
707            tracing::level_filters::LevelFilter::OFF => Self::Off,
708            tracing::level_filters::LevelFilter::ERROR => Self::Error,
709            tracing::level_filters::LevelFilter::WARN => Self::Warn,
710            tracing::level_filters::LevelFilter::INFO => Self::Info,
711            tracing::level_filters::LevelFilter::DEBUG => Self::Debug,
712            tracing::level_filters::LevelFilter::TRACE => Self::Trace,
713        }
714    }
715}
716
717impl From<VeilidConfigLogLevel> for tracing::level_filters::LevelFilter {
718    fn from(val: VeilidConfigLogLevel) -> Self {
719        match val {
720            VeilidConfigLogLevel::Off => tracing::level_filters::LevelFilter::OFF,
721            VeilidConfigLogLevel::Error => tracing::level_filters::LevelFilter::ERROR,
722            VeilidConfigLogLevel::Warn => tracing::level_filters::LevelFilter::WARN,
723            VeilidConfigLogLevel::Info => tracing::level_filters::LevelFilter::INFO,
724            VeilidConfigLogLevel::Debug => tracing::level_filters::LevelFilter::DEBUG,
725            VeilidConfigLogLevel::Trace => tracing::level_filters::LevelFilter::TRACE,
726        }
727    }
728}
729
730impl From<tracing::log::LevelFilter> for VeilidConfigLogLevel {
731    fn from(value: tracing::log::LevelFilter) -> Self {
732        match value {
733            tracing::log::LevelFilter::Off => Self::Off,
734            tracing::log::LevelFilter::Error => Self::Error,
735            tracing::log::LevelFilter::Warn => Self::Warn,
736            tracing::log::LevelFilter::Info => Self::Info,
737            tracing::log::LevelFilter::Debug => Self::Debug,
738            tracing::log::LevelFilter::Trace => Self::Trace,
739        }
740    }
741}
742
743impl From<VeilidConfigLogLevel> for tracing::log::LevelFilter {
744    fn from(val: VeilidConfigLogLevel) -> Self {
745        match val {
746            VeilidConfigLogLevel::Off => tracing::log::LevelFilter::Off,
747            VeilidConfigLogLevel::Error => tracing::log::LevelFilter::Error,
748            VeilidConfigLogLevel::Warn => tracing::log::LevelFilter::Warn,
749            VeilidConfigLogLevel::Info => tracing::log::LevelFilter::Info,
750            VeilidConfigLogLevel::Debug => tracing::log::LevelFilter::Debug,
751            VeilidConfigLogLevel::Trace => tracing::log::LevelFilter::Trace,
752        }
753    }
754}
755
756impl TryFrom<&str> for VeilidConfigLogLevel {
757    type Error = VeilidAPIError;
758
759    fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
760        Self::from_str(value)
761    }
762}
763
764impl TryFrom<String> for VeilidConfigLogLevel {
765    type Error = VeilidAPIError;
766
767    fn try_from(value: String) -> Result<Self, <Self as TryFrom<String>>::Error> {
768        Self::from_str(value.as_str())
769    }
770}
771
772impl TryFrom<&String> for VeilidConfigLogLevel {
773    type Error = VeilidAPIError;
774
775    fn try_from(value: &String) -> Result<Self, <Self as TryFrom<&String>>::Error> {
776        Self::from_str(value.as_str())
777    }
778}
779
780impl FromStr for VeilidConfigLogLevel {
781    type Err = VeilidAPIError;
782    fn from_str(s: &str) -> Result<Self, Self::Err> {
783        Ok(match s.to_ascii_lowercase().as_str() {
784            "off" => Self::Off,
785            "error" => Self::Error,
786            "warn" => Self::Warn,
787            "info" => Self::Info,
788            "debug" => Self::Debug,
789            "trace" => Self::Trace,
790            _ => {
791                apibail_invalid_argument!("invalid VeilidConfigLogLevel string", "s", s);
792            }
793        })
794    }
795}
796impl fmt::Display for VeilidConfigLogLevel {
797    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
798        let text = match self {
799            Self::Off => "Off",
800            Self::Error => "Error",
801            Self::Warn => "Warn",
802            Self::Info => "Info",
803            Self::Debug => "Debug",
804            Self::Trace => "Trace",
805        };
806        write!(f, "{}", text)
807    }
808}
809
810/// Internal "footgun" UDP configuration. See [VeilidConfigInternal].
811#[apply(api_data_struct!)]
812#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
813pub struct VeilidConfigInternalUDP {
814    /// Number of UDP sockets in the send/receive pool (0 = automatic).
815    pub socket_pool_size: u32,
816}
817
818/// Internal "footgun" per-protocol configuration. See [VeilidConfigInternal].
819#[apply(api_data_struct!)]
820#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
821pub struct VeilidConfigInternalProtocol {
822    /// Internal UDP tuning.
823    pub udp: VeilidConfigInternalUDP,
824}
825
826/// Internal "footgun" RPC configuration. See [VeilidConfigInternal].
827#[apply(api_data_struct!)]
828#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
829pub struct VeilidConfigInternalRPC {
830    /// Number of concurrent RPC worker tasks (0 = automatic).
831    pub concurrency: u32,
832    /// Maximum number of queued RPC operations.
833    pub queue_size: u32,
834    /// Reject messages timestamped more than this many ms in the past (`None` = no limit).
835    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
836    pub max_timestamp_behind_ms: Option<u32>,
837    /// Reject messages timestamped more than this many ms in the future (`None` = no limit).
838    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
839    pub max_timestamp_ahead_ms: Option<u32>,
840    /// Timeout for an RPC round-trip, in milliseconds.
841    pub timeout_ms: u32,
842    /// Maximum number of hops allowed in a route.
843    pub max_route_hop_count: u8,
844}
845impl Default for VeilidConfigInternalRPC {
846    fn default() -> Self {
847        Self {
848            concurrency: 0,
849            queue_size: 1024,
850            max_timestamp_behind_ms: Some(10000),
851            max_timestamp_ahead_ms: Some(10000),
852            timeout_ms: 5000,
853            max_route_hop_count: 4,
854        }
855    }
856}
857
858/// Internal "footgun" DHT configuration. See [VeilidConfigInternal].
859/// Changing the count/fanout/timeout parameters may render your node inoperable for
860/// correct DHT operations.
861#[apply(api_data_struct!)]
862#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
863pub struct VeilidConfigInternalDHT {
864    /// Maximum number of nodes returned by a FindNode query.
865    pub max_find_node_count: u32,
866    /// Timeout for resolving a node, in milliseconds.
867    pub resolve_node_timeout_ms: u32,
868    /// Number of nodes sought when resolving a node.
869    pub resolve_node_count: u32,
870    /// Parallel fanout width when resolving a node.
871    pub resolve_node_fanout: u32,
872    /// Timeout for a GetValue operation, in milliseconds.
873    pub get_value_timeout_ms: u32,
874    /// Number of matching values sought for GetValue consensus.
875    pub get_value_count: u32,
876    /// Parallel fanout width for GetValue.
877    pub get_value_fanout: u32,
878    /// Timeout for a SetValue operation, in milliseconds.
879    pub set_value_timeout_ms: u32,
880    /// Number of nodes that must accept a SetValue for consensus.
881    pub set_value_count: u32,
882    /// Parallel fanout width for SetValue.
883    pub set_value_fanout: u32,
884    /// Maximum number of nodes considered 'close to a record key' for storing a record.
885    pub consensus_width: u32,
886    /// Minimum number of peers to keep in the routing table.
887    pub min_peer_count: u32,
888    /// Minimum interval between peer-refresh rounds, in milliseconds.
889    pub min_peer_refresh_time_ms: u32,
890    /// Time allowed to receive a dial-info validation receipt, in milliseconds.
891    pub validate_dial_info_receipt_time_ms: u32,
892    /// Maximum lifetime of a DHT watch, in milliseconds.
893    pub max_watch_expiration_ms: u32,
894    /// Maximum concurrent watches by anonymous watchers (signer not a schema member).
895    pub public_watch_limit: u32,
896    /// Reserved watch slots for watchers whose signer is a schema member of the record.
897    pub member_watch_limit: u32,
898    /// Maximum concurrent transactions by anonymous signers (not a schema member).
899    pub public_transaction_limit: u32,
900    /// Reserved transaction slots for signers who are schema members of the record.
901    pub member_transaction_limit: u32,
902}
903impl Default for VeilidConfigInternalDHT {
904    fn default() -> Self {
905        Self {
906            max_find_node_count: 20,
907            resolve_node_timeout_ms: 10000,
908            resolve_node_count: 1,
909            resolve_node_fanout: 5,
910            get_value_timeout_ms: 10000,
911            get_value_count: 3,
912            get_value_fanout: 5,
913            set_value_timeout_ms: 10000,
914            set_value_count: 5,
915            set_value_fanout: 6,
916            consensus_width: 10,
917            min_peer_count: 20,
918            min_peer_refresh_time_ms: 60000,
919            validate_dial_info_receipt_time_ms: 1000,
920            max_watch_expiration_ms: 600000,
921            public_watch_limit: 32,
922            member_watch_limit: 8,
923            public_transaction_limit: 4,
924            member_transaction_limit: 1,
925        }
926    }
927}
928
929/// Internal "footgun" network configuration. See [VeilidConfigInternal].
930#[apply(api_data_struct!)]
931#[api(eq, ts(into_wasm_abi, from_wasm_abi))]
932pub struct VeilidConfigInternalNetwork {
933    /// Timeout to establish a new connection, in milliseconds.
934    pub connection_initial_timeout_ms: u32,
935    /// Idle time before an inactive connection is dropped, in milliseconds.
936    pub connection_inactivity_timeout_ms: u32,
937    /// Maximum simultaneous connections from a single IPv4 address.
938    pub max_connections_per_ip4: u32,
939    /// Maximum simultaneous connections from a single IPv6 prefix.
940    pub max_connections_per_ip6_prefix: u32,
941    /// IPv6 prefix length (bits) used to group connections for the per-prefix limit.
942    pub max_connections_per_ip6_prefix_size: u32,
943    /// Maximum new connections accepted per minute from one source.
944    pub max_connection_frequency_per_min: u32,
945    /// Time a client stays on the allowlist after connecting, in milliseconds.
946    pub client_allowlist_timeout_ms: u32,
947    /// Time allowed to receive a reverse-connection receipt, in milliseconds.
948    pub reverse_connection_receipt_time_ms: u32,
949    /// Time allowed to receive a hole-punch receipt, in milliseconds.
950    pub hole_punch_receipt_time_ms: u32,
951    /// NAT-detection retries during dial-info confirmation for port/address-restricted NAT
952    /// (some NATs open to full-cone after a few attempts; off by default).
953    pub restricted_nat_retries: u32,
954    /// Internal RPC tuning.
955    pub rpc: VeilidConfigInternalRPC,
956    /// Internal DHT tuning.
957    pub dht: VeilidConfigInternalDHT,
958    /// Internal per-protocol tuning.
959    pub protocol: VeilidConfigInternalProtocol,
960}
961impl Default for VeilidConfigInternalNetwork {
962    fn default() -> Self {
963        Self {
964            connection_initial_timeout_ms: 2000,
965            connection_inactivity_timeout_ms: 60000,
966            max_connections_per_ip4: 32,
967            max_connections_per_ip6_prefix: 32,
968            max_connections_per_ip6_prefix_size: 56,
969            max_connection_frequency_per_min: 128,
970            client_allowlist_timeout_ms: 300000,
971            reverse_connection_receipt_time_ms: 5000,
972            hole_punch_receipt_time_ms: 5000,
973            restricted_nat_retries: 0,
974            rpc: VeilidConfigInternalRPC::default(),
975            dht: VeilidConfigInternalDHT::default(),
976            protocol: VeilidConfigInternalProtocol::default(),
977        }
978    }
979}
980
981/// Internal "footgun" configuration tree, parallel to the main config.
982///
983/// These fields tune low-level network/DHT timing, fanout, consensus and connection limits.
984/// Changing them from the defaults can render a node inoperable. They are only honored when
985/// veilid-core is built with the `footgun-config` feature; without it, any non-default values
986/// here are reset to defaults at startup (with a warning).
987#[apply(api_data_struct!)]
988#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
989pub struct VeilidConfigInternal {
990    /// Internal network tuning.
991    pub network: VeilidConfigInternalNetwork,
992}
993
994/// Top level of the Veilid configuration tree
995#[apply(api_data_struct!)]
996#[api(eq, default, ts(into_wasm_abi, from_wasm_abi))]
997pub struct VeilidConfig {
998    /// An identifier used to describe the program using veilid-core.
999    /// Used to partition storage locations in places like the ProtectedStore.
1000    /// Must be non-empty and a valid filename for all Veilid-capable systems, which means
1001    /// no backslashes or forward slashes in the name. Stick to a-z,0-9,_ and space and you should be fine.
1002    ///
1003    /// Caution: If you change this string, there is no migration support. Your app's protected store and
1004    /// table store will very likely experience data loss. Pick a program name and stick with it. This is
1005    /// not a 'visible' identifier and it should uniquely identify your application.
1006    pub program_name: String,
1007    /// To run multiple Veilid nodes within the same application, either through a single process running
1008    /// api_startup/api_startup_json multiple times, or your application running mulitple times side-by-side
1009    /// there needs to be a key used to partition the application's storage (in the TableStore, ProtectedStore, etc).
1010    /// An empty value here is the default, but if you run multiple veilid nodes concurrently, you should set this
1011    /// to a string that uniquely identifies this -instance- within the same 'program_name'.
1012    /// Must be a valid filename for all Veilid-capable systems, which means no backslashes or forward slashes
1013    /// in the name. Stick to a-z,0-9,_ and space and you should be fine.
1014    pub namespace: String,
1015    /// Capabilities to enable for your application/node
1016    pub capabilities: VeilidConfigCapabilities,
1017    /// Configuring the protected store (keychain/keyring/etc)
1018    pub protected_store: VeilidConfigProtectedStore,
1019    /// Configuring the table store (persistent encrypted database)
1020    pub table_store: VeilidConfigTableStore,
1021    /// Configuring the block store (storage of large content-addressable content)
1022    pub block_store: VeilidConfigBlockStore,
1023    /// Configuring how Veilid interacts with the low level network
1024    pub network: VeilidConfigNetwork,
1025    /// Internal "footgun" tuning. `None` uses safe defaults. Only honored with the
1026    /// `footgun-config` feature; otherwise ignored (a warning is logged at startup).
1027    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
1028    pub internal: Option<VeilidConfigInternal>,
1029}
1030
1031impl VeilidConfig {
1032    /// The effective internal config: configured values with the `footgun-config` feature,
1033    /// otherwise always the built-in defaults.
1034    pub fn internal(&self) -> &VeilidConfigInternal {
1035        static DEFAULT: std::sync::OnceLock<VeilidConfigInternal> = std::sync::OnceLock::new();
1036        let default = || DEFAULT.get_or_init(VeilidConfigInternal::default);
1037        #[cfg(feature = "footgun-config")]
1038        {
1039            self.internal.as_ref().unwrap_or_else(default)
1040        }
1041        #[cfg(not(feature = "footgun-config"))]
1042        {
1043            let _ = &self.internal;
1044            default()
1045        }
1046    }
1047    /// Create a new 'VeilidConfig' for use with `setup_from_config`
1048    /// Should match the application bundle name if used elsewhere in the format:
1049    /// `qualifier.organization.program_name` - for example `org.veilid.veilidchat`
1050    ///
1051    /// The 'bundle name' will be used when choosing the default storage location for the
1052    /// application in a platform-dependent fashion, unless 'storage_directory' is
1053    /// specified to override this location
1054    ///
1055    /// * `program_name` - Pick a program name and do not change it from release to release,
1056    ///   see `VeilidConfig::program_name` for details.
1057    /// * `organization_name` - Similar to program_name, but for the organization publishing this app
1058    /// * `qualifier` - Suffix for the application bundle name
1059    /// * `storage_directory` - Override for the path where veilid-core stores its content
1060    ///   such as the table store, protected store, and block store
1061    /// * `config_directory` - Override for the path where veilid-core can retrieve extra configuration files
1062    ///   such as certificates and keys
1063    pub fn new(
1064        program_name: &str,
1065        organization: &str,
1066        qualifier: &str,
1067        storage_directory: Option<&str>,
1068        config_directory: Option<&str>,
1069    ) -> Self {
1070        let mut out = Self {
1071            program_name: program_name.to_owned(),
1072            ..Default::default()
1073        };
1074
1075        if let Some(storage_directory) = storage_directory {
1076            out.protected_store.directory = (std::path::PathBuf::from(storage_directory)
1077                .join("protected_store"))
1078            .to_string_lossy()
1079            .to_string();
1080            out.table_store.directory = (std::path::PathBuf::from(storage_directory)
1081                .join("table_store"))
1082            .to_string_lossy()
1083            .to_string();
1084            out.block_store.directory = (std::path::PathBuf::from(storage_directory)
1085                .join("block_store"))
1086            .to_string_lossy()
1087            .to_string();
1088        } else {
1089            out.protected_store.directory =
1090                get_default_store_path(program_name, organization, qualifier, "protected_store");
1091            out.table_store.directory =
1092                get_default_store_path(program_name, organization, qualifier, "table_store");
1093            out.block_store.directory =
1094                get_default_store_path(program_name, organization, qualifier, "block_store");
1095        }
1096
1097        if let Some(config_directory) = config_directory {
1098            out.network.tls.certificate_path = (std::path::PathBuf::from(config_directory)
1099                .join("ssl/certs/server.crt"))
1100            .to_string_lossy()
1101            .to_string();
1102            out.network.tls.private_key_path = (std::path::PathBuf::from(config_directory)
1103                .join("ssl/keys/server.key"))
1104            .to_string_lossy()
1105            .to_string();
1106        } else {
1107            out.network.tls.certificate_path = get_default_ssl_directory(
1108                program_name,
1109                organization,
1110                qualifier,
1111                "certs/server.crt",
1112            );
1113            out.network.tls.private_key_path =
1114                get_default_ssl_directory(program_name, organization, qualifier, "keys/server.key");
1115        }
1116
1117        out
1118    }
1119
1120    /// Clone the config with secrets stripped (routing-table secret keys and encryption-key passwords), safe to log or serialize.
1121    #[must_use]
1122    pub fn safe(&self) -> Arc<VeilidConfig> {
1123        let mut safe_cfg = self.clone();
1124
1125        // Remove secrets
1126        safe_cfg.network.routing_table.secret_keys = SecretKeyGroup::new();
1127        "".clone_into(&mut safe_cfg.protected_store.device_encryption_key_password);
1128        safe_cfg.protected_store.new_device_encryption_key_password = None;
1129
1130        Arc::new(safe_cfg)
1131    }
1132
1133    /// Serialize the config, or the subtree at a dot-separated `key` path, to JSON. Empty `key` returns the whole config.
1134    pub fn get_key_json(&self, key: &str, pretty: bool) -> VeilidAPIResult<String> {
1135        // Generate json from whole config
1136        let jvc = serde_json::to_value(self).map_err(VeilidAPIError::generic)?;
1137
1138        // Find requested subkey
1139        if key.is_empty() {
1140            Ok(if pretty {
1141                serde_json::to_string_pretty(&jvc).map_err(VeilidAPIError::generic)?
1142            } else {
1143                serde_json::to_string(&jvc).map_err(VeilidAPIError::generic)?
1144            })
1145        } else {
1146            // Split key into path parts
1147            let keypath: Vec<&str> = key.split('.').collect();
1148            let mut out = &jvc;
1149            for k in keypath {
1150                let Some(next_out) = out.get(k) else {
1151                    apibail_parse_error!(format!("invalid subkey in key '{}'", key), k);
1152                };
1153                out = next_out;
1154            }
1155            if pretty {
1156                serde_json::to_string_pretty(out).map_err(VeilidAPIError::generic)
1157            } else {
1158                serde_json::to_string(out).map_err(VeilidAPIError::generic)
1159            }
1160        }
1161    }
1162
1163    // Rejects illegal/control chars, Windows-reserved names, trailing dot/space, len > 255
1164    fn is_valid_filename(s: &str) -> bool {
1165        if s.len() > 255 {
1166            return false;
1167        }
1168        if s.chars().any(|c| {
1169            matches!(c, '/' | '?' | '<' | '>' | '\\' | ':' | '*' | '|' | '"')
1170                || c <= '\u{1f}'
1171                || ('\u{80}'..='\u{9f}').contains(&c)
1172        }) {
1173            return false;
1174        }
1175        if !s.is_empty() && s.bytes().all(|b| b == b'.') {
1176            return false;
1177        }
1178        if s.ends_with('.') || s.ends_with(' ') {
1179            return false;
1180        }
1181        let base = s.split('.').next().unwrap_or_default().as_bytes();
1182        match base.len() {
1183            3 => {
1184                !(base.eq_ignore_ascii_case(b"con")
1185                    || base.eq_ignore_ascii_case(b"prn")
1186                    || base.eq_ignore_ascii_case(b"aux")
1187                    || base.eq_ignore_ascii_case(b"nul"))
1188            }
1189            4 => {
1190                !((base[..3].eq_ignore_ascii_case(b"com")
1191                    || base[..3].eq_ignore_ascii_case(b"lpt"))
1192                    && base[3].is_ascii_digit())
1193            }
1194            _ => true,
1195        }
1196    }
1197
1198    fn validate_program_name(program_name: &str) -> VeilidAPIResult<()> {
1199        if program_name.is_empty() {
1200            apibail_generic!("Program name must not be empty in 'program_name'");
1201        }
1202        if !Self::is_valid_filename(program_name) {
1203            apibail_generic!("'program_name' must not be an invalid filename");
1204        }
1205        Ok(())
1206    }
1207
1208    fn validate_namespace(namespace: &str) -> VeilidAPIResult<()> {
1209        if namespace.is_empty() {
1210            return Ok(());
1211        }
1212        if !Self::is_valid_filename(namespace) {
1213            apibail_generic!("'namespace' must not be an invalid filename");
1214        }
1215
1216        Ok(())
1217    }
1218
1219    fn validate_max_connections(max_connections: u32, key: &str) -> VeilidAPIResult<()> {
1220        if !(MAX_CONNECTIONS_MIN..=MAX_CONNECTIONS_MAX).contains(&max_connections) {
1221            apibail_generic!(format!(
1222                "max connections must be in the range {}-{} in config key '{}'",
1223                MAX_CONNECTIONS_MIN, MAX_CONNECTIONS_MAX, key
1224            ));
1225        }
1226        Ok(())
1227    }
1228
1229    /// Check the config for invalid or out-of-range values.
1230    pub fn validate(&self) -> VeilidAPIResult<()> {
1231        Self::validate_program_name(&self.program_name)?;
1232        Self::validate_namespace(&self.namespace)?;
1233
1234        // Total connection cap across all protocols
1235        Self::validate_max_connections(self.network.max_connections, "network.max_connections")?;
1236
1237        // if inner.network.protocol.udp.enabled {
1238        //     // Validate UDP settings
1239        // }
1240        #[cfg(feature = "enable-protocol-wss")]
1241        if self.network.protocol.wss.listen {
1242            // Validate WSS settings
1243            if self
1244                .network
1245                .protocol
1246                .wss
1247                .url
1248                .as_ref()
1249                .map(|u| u.is_empty())
1250                .unwrap_or_default()
1251            {
1252                apibail_generic!(
1253                    "WSS URL must be specified in config key 'network.protocol.wss.url'"
1254                );
1255            }
1256        }
1257        if self.internal().network.rpc.max_route_hop_count == 0 {
1258            apibail_generic!(
1259                "max route hop count must be >= 1 in 'network.rpc.max_route_hop_count'"
1260            );
1261        }
1262        if self.internal().network.rpc.max_route_hop_count > 5 {
1263            apibail_generic!(
1264                "max route hop count must be <= 5 in 'network.rpc.max_route_hop_count'"
1265            );
1266        }
1267        if self.network.rpc.default_route_hop_count == 0 {
1268            apibail_generic!(
1269                "default route hop count must be >= 1 in 'network.rpc.default_route_hop_count'"
1270            );
1271        }
1272        if self.network.rpc.default_route_hop_count
1273            > self.internal().network.rpc.max_route_hop_count
1274        {
1275            apibail_generic!(
1276                "default route hop count must be <= max route hop count in 'network.rpc.default_route_hop_count <= network.rpc.max_route_hop_count'"
1277            );
1278        }
1279        if self.internal().network.rpc.queue_size < 256 {
1280            apibail_generic!("rpc queue size must be >= 256 in 'network.rpc.queue_size'");
1281        }
1282        if self.internal().network.rpc.timeout_ms < 1000 {
1283            apibail_generic!("rpc timeout must be >= 1000 in 'network.rpc.timeout_ms'");
1284        }
1285        if self.internal().network.dht.consensus_width < self.internal().network.dht.set_value_count
1286        {
1287            apibail_generic!(
1288                "consensus width must be >= set value count in 'network.dht.consensus_width'"
1289            );
1290        }
1291        if self.internal().network.dht.get_value_count
1292            <= (self.internal().network.dht.set_value_count / 2)
1293        {
1294            apibail_generic!("get consensus count must be >= (set value count / 2) in 'network.dht.get_value_count'");
1295        }
1296        if self.internal().network.dht.get_value_fanout < 1 {
1297            apibail_generic!("get value fanout must be >= 1 in 'network.dht.get_value_fanout'");
1298        }
1299        if self.internal().network.dht.set_value_fanout < 1 {
1300            apibail_generic!("set value fanout must be >= 1 in 'network.dht.set_value_fanout'");
1301        }
1302        if self.internal().network.dht.get_value_timeout_ms
1303            < (2 * self.internal().network.rpc.timeout_ms)
1304        {
1305            apibail_generic!("get value timeout must be >= (2 * the rpc timeout) in 'network.dht.get_value_timeout_ms'");
1306        }
1307        if self.internal().network.dht.set_value_timeout_ms
1308            < (2 * self.internal().network.rpc.timeout_ms)
1309        {
1310            apibail_generic!("set value timeout must be >= (2 * the rpc timeout) in 'network.dht.set_value_timeout_ms'");
1311        }
1312
1313        if self.internal().network.dht.public_watch_limit < 1 {
1314            apibail_generic!("public watch limit must be >= 1 in 'network.dht.public_watch_limit'");
1315        }
1316        if self.internal().network.dht.member_watch_limit < 1 {
1317            apibail_generic!("member watch limit must be >= 1 in 'network.dht.member_watch_limit'");
1318        }
1319        if self.internal().network.dht.max_watch_expiration_ms
1320            < (2 * self.internal().network.rpc.timeout_ms)
1321        {
1322            apibail_generic!("max watch expiration must be >= (2 * rpc timeout) 'network.dht.max_watch_expiration_ms'");
1323        }
1324        if self.internal().network.dht.public_transaction_limit < 1 {
1325            apibail_generic!(
1326                "public transaction limit must be >= 1 in 'network.dht.public_transaction_limit'"
1327            );
1328        }
1329        if self.internal().network.dht.member_transaction_limit < 1 {
1330            apibail_generic!(
1331                "member transaction limit must be >= 1 in 'network.dht.member_transaction_limit'"
1332            );
1333        }
1334
1335        Ok(())
1336    }
1337}
1338
1339/// The configuration built for each Veilid node during API startup
1340#[derive(Clone)]
1341#[must_use]
1342pub struct VeilidStartupOptions {
1343    update_cb: UpdateCallback,
1344    config: Arc<VeilidConfig>,
1345}
1346
1347impl fmt::Debug for VeilidStartupOptions {
1348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1349        f.debug_struct("VeilidConfig")
1350            .field("config", self.config.as_ref())
1351            .finish()
1352    }
1353}
1354
1355impl VeilidStartupOptions {
1356    pub(crate) fn try_new(
1357        config: VeilidConfig,
1358        update_cb: UpdateCallback,
1359    ) -> VeilidAPIResult<Self> {
1360        config.validate()?;
1361
1362        Ok(Self {
1363            update_cb,
1364            config: Arc::new(config),
1365        })
1366    }
1367
1368    /// The callback invoked to deliver `VeilidUpdate` events to the application.
1369    #[must_use]
1370    pub fn update_callback(&self) -> UpdateCallback {
1371        self.update_cb.clone()
1372    }
1373
1374    /// The validated configuration for this node.
1375    #[must_use]
1376    pub fn config(&self) -> Arc<VeilidConfig> {
1377        self.config.clone()
1378    }
1379}
1380
1381/// Return the default veilid config as a json object.
1382#[must_use]
1383pub fn default_veilid_config() -> String {
1384    serialize_json(VeilidConfig::default())
1385}