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 sysinfo::System;
6        use lazy_static::*;
7        use directories::ProjectDirs;
8
9        lazy_static! {
10            static ref SYSTEM:System = {
11                sysinfo::System::new_with_specifics(
12                    sysinfo::RefreshKind::new().with_memory(sysinfo::MemoryRefreshKind::everything()),
13                )
14            };
15        }
16    }
17}
18
19/// Enable and configure HTTPS access to the Veilid node.
20///
21/// ```yaml
22/// https:
23///     enabled: false
24///     listen_address: ':5150'
25///     path: 'app'
26///     url: 'https://localhost:5150'
27/// ```
28///
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
30#[cfg_attr(
31    all(target_arch = "wasm32", target_os = "unknown"),
32    derive(Tsify),
33    tsify(into_wasm_abi, from_wasm_abi)
34)]
35#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
36#[must_use]
37pub struct VeilidConfigHTTPS {
38    pub enabled: bool,
39    pub listen_address: String,
40    pub path: String,
41    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
42    pub url: Option<String>, // Fixed URL is not optional for TLS-based protocols and is dynamically validated
43}
44impl Default for VeilidConfigHTTPS {
45    fn default() -> Self {
46        Self {
47            enabled: false,
48            listen_address: String::from(""),
49            path: String::from("app"),
50            url: None,
51        }
52    }
53}
54
55/// Enable and configure HTTP access to the Veilid node.
56///
57/// ```yaml
58/// http:
59///     enabled: false
60///     listen_address: ':5150'
61///     path: 'app"
62///     url: 'https://localhost:5150'
63/// ```
64///
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
66#[cfg_attr(
67    all(target_arch = "wasm32", target_os = "unknown"),
68    derive(Tsify),
69    tsify(into_wasm_abi, from_wasm_abi)
70)]
71#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
72#[must_use]
73pub struct VeilidConfigHTTP {
74    pub enabled: bool,
75    pub listen_address: String,
76    pub path: String,
77    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
78    pub url: Option<String>,
79}
80
81impl Default for VeilidConfigHTTP {
82    fn default() -> Self {
83        Self {
84            enabled: false,
85            listen_address: String::from(""),
86            path: String::from("app"),
87            url: None,
88        }
89    }
90}
91
92/// Enable and configure UDP.
93///
94/// ```yaml
95/// udp:
96///     enabled: true
97///     socket_pool_size: 0
98///     listen_address: ':5150'
99///     public_address: ''
100/// ```
101///
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
103#[cfg_attr(
104    all(target_arch = "wasm32", target_os = "unknown"),
105    derive(Tsify),
106    tsify(into_wasm_abi, from_wasm_abi)
107)]
108#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
109#[must_use]
110pub struct VeilidConfigUDP {
111    pub enabled: bool,
112    pub socket_pool_size: u32,
113    pub listen_address: String,
114    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
115    pub public_address: Option<String>,
116}
117
118impl Default for VeilidConfigUDP {
119    fn default() -> Self {
120        cfg_if::cfg_if! {
121            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
122                let enabled = false;
123            } else {
124                let enabled = true;
125            }
126        }
127        Self {
128            enabled,
129            socket_pool_size: 0,
130            listen_address: String::from(""),
131            public_address: None,
132        }
133    }
134}
135
136/// Enable and configure TCP.
137///
138/// ```yaml
139/// tcp:
140///     connect: true
141///     listen: true
142///     max_connections: 32
143///     listen_address: ':5150'
144///     public_address: ''
145///
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
147#[cfg_attr(
148    all(target_arch = "wasm32", target_os = "unknown"),
149    derive(Tsify),
150    tsify(into_wasm_abi, from_wasm_abi)
151)]
152#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
153#[must_use]
154pub struct VeilidConfigTCP {
155    pub connect: bool,
156    pub listen: bool,
157    pub max_connections: u32,
158    pub listen_address: String,
159    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
160    pub public_address: Option<String>,
161}
162
163impl Default for VeilidConfigTCP {
164    fn default() -> Self {
165        cfg_if::cfg_if! {
166            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
167                let connect = false;
168                let listen = false;
169            } else {
170                let connect = true;
171                let listen = true;
172            }
173        }
174        Self {
175            connect,
176            listen,
177            max_connections: 32,
178            listen_address: String::from(""),
179            public_address: None,
180        }
181    }
182}
183
184/// Enable and configure Web Sockets.
185///
186/// ```yaml
187/// ws:
188///     connect: true
189///     listen: true
190///     max_connections: 32
191///     listen_address: ':5150'
192///     path: 'ws'
193///     url: 'ws://localhost:5150/ws'
194///
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
196#[cfg_attr(
197    all(target_arch = "wasm32", target_os = "unknown"),
198    derive(Tsify),
199    tsify(into_wasm_abi, from_wasm_abi)
200)]
201#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
202#[must_use]
203pub struct VeilidConfigWS {
204    pub connect: bool,
205    pub listen: bool,
206    pub max_connections: u32,
207    pub listen_address: String,
208    pub path: String,
209    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
210    pub url: Option<String>,
211}
212
213impl Default for VeilidConfigWS {
214    fn default() -> Self {
215        cfg_if::cfg_if! {
216            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
217                let connect = true;
218                let listen = false;
219            } else {
220                let connect = true;
221                let listen = true;
222            }
223        }
224        Self {
225            connect,
226            listen,
227            max_connections: 32,
228            listen_address: String::from(""),
229            path: String::from("ws"),
230            url: None,
231        }
232    }
233}
234
235/// Enable and configure Secure Web Sockets.
236///
237/// ```yaml
238/// wss:
239///     connect: true
240///     listen: false
241///     max_connections: 32
242///     listen_address: ':5150'
243///     path: 'ws'
244///     url: ''
245///
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
247#[cfg_attr(
248    all(target_arch = "wasm32", target_os = "unknown"),
249    derive(Tsify),
250    tsify(into_wasm_abi, from_wasm_abi)
251)]
252#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
253#[must_use]
254#[cfg(feature = "enable-protocol-wss")]
255pub struct VeilidConfigWSS {
256    pub connect: bool,
257    pub listen: bool,
258    pub max_connections: u32,
259    pub listen_address: String,
260    pub path: String,
261    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
262    pub url: Option<String>, // Fixed URL is not optional for TLS-based protocols and is dynamically validated
263}
264
265#[cfg(feature = "enable-protocol-wss")]
266impl Default for VeilidConfigWSS {
267    fn default() -> Self {
268        Self {
269            connect: true,
270            listen: false,
271            max_connections: 32,
272            listen_address: String::from(""),
273            path: String::from("ws"),
274            url: None,
275        }
276    }
277}
278
279/// Configure Network Protocols.
280///
281/// Veilid can communicate over UDP, TCP, and Web Sockets.
282///
283/// All protocols are available by default, and the Veilid node will
284/// sort out which protocol is used for each peer connection.
285///
286#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
287#[cfg_attr(
288    all(target_arch = "wasm32", target_os = "unknown"),
289    derive(Tsify),
290    tsify(into_wasm_abi, from_wasm_abi)
291)]
292#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
293#[must_use]
294pub struct VeilidConfigProtocol {
295    pub udp: VeilidConfigUDP,
296    pub tcp: VeilidConfigTCP,
297    pub ws: VeilidConfigWS,
298    #[cfg(feature = "enable-protocol-wss")]
299    pub wss: VeilidConfigWSS,
300}
301
302/// Privacy preferences for routes.
303///
304/// ```yaml
305/// privacy:
306///     require_inbound_relay: false
307///     country_code_denylist: [] # only with `--features=geolocation`
308/// ```
309#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
310#[cfg_attr(
311    target_arch = "wasm32",
312    derive(Tsify),
313    tsify(into_wasm_abi, from_wasm_abi)
314)]
315#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
316#[must_use]
317pub struct VeilidConfigPrivacy {
318    pub require_inbound_relay: bool,
319    #[cfg(feature = "geolocation")]
320    pub country_code_denylist: Vec<CountryCode>,
321}
322
323/// Virtual networking client support for testing/simulation purposes
324///
325/// ```yaml
326/// virtual_network:
327///     enabled: false
328///     server_address: ""
329/// ```
330#[cfg(feature = "virtual-network")]
331#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
332#[cfg_attr(
333    target_arch = "wasm32",
334    derive(Tsify),
335    tsify(into_wasm_abi, from_wasm_abi)
336)]
337#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
338#[must_use]
339pub struct VeilidConfigVirtualNetwork {
340    pub enabled: bool,
341    pub server_address: String,
342}
343
344/// Configure TLS.
345///
346/// ```yaml
347/// tls:
348///     certificate_path: /path/to/cert
349///     private_key_path: /path/to/private/key
350///     connection_initial_timeout_ms: 2000
351///
352#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
353#[cfg_attr(
354    all(target_arch = "wasm32", target_os = "unknown"),
355    derive(Tsify),
356    tsify(into_wasm_abi, from_wasm_abi)
357)]
358#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
359#[must_use]
360pub struct VeilidConfigTLS {
361    pub certificate_path: String,
362    pub private_key_path: String,
363    pub connection_initial_timeout_ms: u32,
364}
365
366impl Default for VeilidConfigTLS {
367    fn default() -> Self {
368        Self {
369            certificate_path: "".to_string(),
370            private_key_path: "".to_string(),
371            connection_initial_timeout_ms: 2000,
372        }
373    }
374}
375
376#[cfg_attr(
377    all(target_arch = "wasm32", target_os = "unknown"),
378    allow(unused_variables)
379)]
380#[must_use]
381pub fn get_default_ssl_directory(
382    program_name: &str,
383    organization: &str,
384    qualifier: &str,
385    sub_path: &str,
386) -> String {
387    cfg_if::cfg_if! {
388        if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
389            "".to_owned()
390        } else {
391            use std::path::PathBuf;
392            ProjectDirs::from(qualifier, organization, program_name)
393                .map(|dirs| dirs.data_local_dir().join("ssl").join(sub_path))
394                .unwrap_or_else(|| PathBuf::from("./ssl").join(sub_path))
395                .to_string_lossy()
396                .into()
397        }
398    }
399}
400
401/// Configure the Distributed Hash Table (DHT).
402/// Defaults should be used here unless you are absolutely sure you know what you're doing.
403/// If you change the count/fanout/timeout parameters, you may render your node inoperable
404/// for correct DHT operations.
405#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
406#[cfg_attr(
407    all(target_arch = "wasm32", target_os = "unknown"),
408    derive(Tsify),
409    tsify(into_wasm_abi, from_wasm_abi)
410)]
411#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
412#[must_use]
413pub struct VeilidConfigDHT {
414    pub max_find_node_count: u32,
415    pub resolve_node_timeout_ms: u32,
416    pub resolve_node_count: u32,
417    pub resolve_node_fanout: u32,
418    pub get_value_timeout_ms: u32,
419    pub get_value_count: u32,
420    pub get_value_fanout: u32,
421    pub set_value_timeout_ms: u32,
422    pub set_value_count: u32,
423    pub set_value_fanout: u32,
424    pub consensus_width: u32,
425    pub min_peer_count: u32,
426    pub min_peer_refresh_time_ms: u32,
427    pub validate_dial_info_receipt_time_ms: u32,
428    pub local_subkey_cache_size: u32,
429    pub local_max_subkey_cache_memory_mb: u32,
430    pub remote_subkey_cache_size: u32,
431    pub remote_max_records: u32,
432    pub remote_max_subkey_cache_memory_mb: u32,
433    pub remote_max_storage_space_mb: u32,
434    pub public_watch_limit: u32,
435    pub member_watch_limit: u32,
436    pub max_watch_expiration_ms: u32,
437    pub public_transaction_limit: u32,
438    pub member_transaction_limit: u32,
439}
440
441impl Default for VeilidConfigDHT {
442    fn default() -> Self {
443        cfg_if::cfg_if! {
444            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
445                let local_subkey_cache_size = 128;
446                let local_max_subkey_cache_memory_mb = 256;
447                let remote_subkey_cache_size = 64;
448                let remote_max_records = 64;
449                let remote_max_subkey_cache_memory_mb = 256;
450                let remote_max_storage_space_mb = 128;
451            } else {
452                let local_subkey_cache_size = 1024;
453                let local_max_subkey_cache_memory_mb = if sysinfo::IS_SUPPORTED_SYSTEM {
454                    (SYSTEM.total_memory() / 32u64 / (1024u64 * 1024u64)) as u32
455                } else {
456                    256
457                };
458                let remote_subkey_cache_size = 128;
459                let remote_max_records = 128;
460                let remote_max_subkey_cache_memory_mb = if sysinfo::IS_SUPPORTED_SYSTEM {
461                    (SYSTEM.total_memory() / 32u64 / (1024u64 * 1024u64)) as u32
462                } else {
463                    256
464                };
465                let remote_max_storage_space_mb = 256;
466            }
467        }
468
469        Self {
470            max_find_node_count: 20,
471            resolve_node_timeout_ms: 10000,
472            resolve_node_count: 1,
473            resolve_node_fanout: 5,
474            get_value_timeout_ms: 10000,
475            get_value_count: 3,
476            get_value_fanout: 5,
477            set_value_timeout_ms: 10000,
478            set_value_count: 5,
479            set_value_fanout: 5,
480            consensus_width: 10,
481            min_peer_count: 20,
482            min_peer_refresh_time_ms: 60000,
483            validate_dial_info_receipt_time_ms: 2000,
484            local_subkey_cache_size,
485            local_max_subkey_cache_memory_mb,
486            remote_subkey_cache_size,
487            remote_max_records,
488            remote_max_subkey_cache_memory_mb,
489            remote_max_storage_space_mb,
490            public_watch_limit: 32,
491            member_watch_limit: 8,
492            max_watch_expiration_ms: 600000,
493            public_transaction_limit: 4,
494            member_transaction_limit: 1,
495        }
496    }
497}
498
499/// Configure RPC.
500///
501#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
502#[cfg_attr(
503    all(target_arch = "wasm32", target_os = "unknown"),
504    derive(Tsify),
505    tsify(into_wasm_abi, from_wasm_abi)
506)]
507#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
508#[must_use]
509pub struct VeilidConfigRPC {
510    pub concurrency: u32,
511    pub queue_size: u32,
512    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
513    pub max_timestamp_behind_ms: Option<u32>,
514    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
515    pub max_timestamp_ahead_ms: Option<u32>,
516    pub timeout_ms: u32,
517    pub max_route_hop_count: u8,
518    pub default_route_hop_count: u8,
519}
520
521impl Default for VeilidConfigRPC {
522    fn default() -> Self {
523        Self {
524            concurrency: 0,
525            queue_size: 1024,
526            max_timestamp_behind_ms: Some(10000),
527            max_timestamp_ahead_ms: Some(10000),
528            timeout_ms: 5000,
529            max_route_hop_count: 4,
530            default_route_hop_count: 1,
531        }
532    }
533}
534
535/// Configure the network routing table.
536///
537#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
538#[cfg_attr(
539    all(target_arch = "wasm32", target_os = "unknown"),
540    derive(Tsify),
541    tsify(into_wasm_abi, from_wasm_abi)
542)]
543#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
544#[must_use]
545pub struct VeilidConfigRoutingTable {
546    #[schemars(with = "Vec<String>")]
547    #[cfg_attr(
548        all(target_arch = "wasm32", target_os = "unknown"),
549        tsify(type = "string[]")
550    )]
551    pub public_keys: PublicKeyGroup,
552    #[schemars(with = "Vec<String>")]
553    #[cfg_attr(
554        all(target_arch = "wasm32", target_os = "unknown"),
555        tsify(type = "string[]")
556    )]
557    pub secret_keys: SecretKeyGroup,
558    pub bootstrap: Vec<String>,
559    #[schemars(with = "Vec<String>")]
560    #[cfg_attr(
561        all(target_arch = "wasm32", target_os = "unknown"),
562        tsify(type = "string[]")
563    )]
564    pub bootstrap_keys: Vec<PublicKey>,
565    pub limit_over_attached: u32,
566    pub limit_fully_attached: u32,
567    pub limit_attached_strong: u32,
568    pub limit_attached_good: u32,
569    pub limit_attached_weak: u32,
570    // xxx pub enable_public_internet: bool,
571    // xxx pub enable_local_network: bool,
572}
573
574impl Default for VeilidConfigRoutingTable {
575    fn default() -> Self {
576        cfg_if::cfg_if! {
577            if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
578                let bootstrap = vec!["ws://bootstrap-v1.veilid.net:5150/ws".to_string()];
579            } else {
580                let bootstrap = vec!["bootstrap-v1.veilid.net".to_string()];
581            }
582        }
583        let bootstrap_keys = vec![
584            // Primary Veilid Foundation bootstrap signing key
585            PublicKey::from_str("VLD0:Vj0lKDdUQXmQ5Ol1SZdlvXkBHUccBcQvGLN9vbLSI7k").unwrap_or_log(),
586            // Secondary Veilid Foundation bootstrap signing key
587            PublicKey::from_str("VLD0:QeQJorqbXtC7v3OlynCZ_W3m76wGNeB5NTF81ypqHAo").unwrap_or_log(),
588            // Backup Veilid Foundation bootstrap signing key
589            PublicKey::from_str("VLD0:QNdcl-0OiFfYVj9331XVR6IqZ49NG-E18d5P7lwi4TA").unwrap_or_log(),
590        ];
591
592        Self {
593            public_keys: PublicKeyGroup::default(),
594            secret_keys: SecretKeyGroup::default(),
595            bootstrap,
596            bootstrap_keys,
597            limit_over_attached: 64,
598            limit_fully_attached: 32,
599            limit_attached_strong: 16,
600            limit_attached_good: 8,
601            limit_attached_weak: 4,
602        }
603    }
604}
605
606#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
607#[cfg_attr(
608    all(target_arch = "wasm32", target_os = "unknown"),
609    derive(Tsify),
610    tsify(into_wasm_abi, from_wasm_abi)
611)]
612#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
613#[must_use]
614pub struct VeilidConfigNetwork {
615    pub connection_initial_timeout_ms: u32,
616    pub connection_inactivity_timeout_ms: u32,
617    pub max_connections_per_ip4: u32,
618    pub max_connections_per_ip6_prefix: u32,
619    pub max_connections_per_ip6_prefix_size: u32,
620    pub max_connection_frequency_per_min: u32,
621    pub client_allowlist_timeout_ms: u32,
622    pub reverse_connection_receipt_time_ms: u32,
623    pub hole_punch_receipt_time_ms: u32,
624    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
625    pub network_key_password: Option<String>,
626    pub routing_table: VeilidConfigRoutingTable,
627    pub rpc: VeilidConfigRPC,
628    pub dht: VeilidConfigDHT,
629    pub upnp: bool,
630    pub detect_address_changes: Option<bool>,
631    pub restricted_nat_retries: u32,
632    pub tls: VeilidConfigTLS,
633    pub protocol: VeilidConfigProtocol,
634    pub privacy: VeilidConfigPrivacy,
635    #[cfg(feature = "virtual-network")]
636    pub virtual_network: VeilidConfigVirtualNetwork,
637}
638
639impl Default for VeilidConfigNetwork {
640    fn default() -> Self {
641        Self {
642            connection_initial_timeout_ms: 2000,
643            connection_inactivity_timeout_ms: 60000,
644            max_connections_per_ip4: 32,
645            max_connections_per_ip6_prefix: 32,
646            max_connections_per_ip6_prefix_size: 56,
647            max_connection_frequency_per_min: 128,
648            client_allowlist_timeout_ms: 300000,
649            reverse_connection_receipt_time_ms: 5000,
650            hole_punch_receipt_time_ms: 5000,
651            network_key_password: None,
652            routing_table: VeilidConfigRoutingTable::default(),
653            rpc: VeilidConfigRPC::default(),
654            dht: VeilidConfigDHT::default(),
655            upnp: true,
656            detect_address_changes: Some(true),
657            restricted_nat_retries: 0,
658            tls: VeilidConfigTLS::default(),
659            protocol: VeilidConfigProtocol::default(),
660            privacy: VeilidConfigPrivacy::default(),
661            #[cfg(feature = "virtual-network")]
662            virtual_network: VeilidConfigVirtualNetwork::default(),
663        }
664    }
665}
666
667#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
668#[cfg_attr(
669    all(target_arch = "wasm32", target_os = "unknown"),
670    derive(Tsify),
671    tsify(into_wasm_abi, from_wasm_abi)
672)]
673#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
674#[must_use]
675pub struct VeilidConfigTableStore {
676    pub directory: String,
677    pub delete: bool,
678}
679
680#[cfg_attr(
681    all(target_arch = "wasm32", target_os = "unknown"),
682    allow(unused_variables)
683)]
684#[must_use]
685fn get_default_store_path(
686    program_name: &str,
687    organization: &str,
688    qualifier: &str,
689    store_type: &str,
690) -> String {
691    cfg_if::cfg_if! {
692        if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
693            "".to_owned()
694        } else {
695            use std::path::PathBuf;
696            ProjectDirs::from(qualifier, organization, program_name)
697                .map(|dirs| dirs.data_local_dir().to_path_buf())
698                .unwrap_or_else(|| PathBuf::from("./"))
699                .join(store_type)
700                .to_string_lossy()
701                .into()
702        }
703    }
704}
705
706#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
707#[cfg_attr(
708    all(target_arch = "wasm32", target_os = "unknown"),
709    derive(Tsify),
710    tsify(into_wasm_abi, from_wasm_abi)
711)]
712#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
713#[must_use]
714pub struct VeilidConfigBlockStore {
715    pub directory: String,
716    pub delete: bool,
717}
718
719impl Default for VeilidConfigBlockStore {
720    fn default() -> Self {
721        Self {
722            directory: "".to_string(),
723            delete: false,
724        }
725    }
726}
727
728#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
729#[cfg_attr(
730    all(target_arch = "wasm32", target_os = "unknown"),
731    derive(Tsify),
732    tsify(into_wasm_abi, from_wasm_abi)
733)]
734#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
735#[must_use]
736pub struct VeilidConfigProtectedStore {
737    pub allow_insecure_fallback: bool,
738    pub always_use_insecure_storage: bool,
739    pub directory: String,
740    pub delete: bool,
741    pub device_encryption_key_password: String,
742    #[cfg_attr(all(target_arch = "wasm32", target_os = "unknown"), tsify(optional))]
743    pub new_device_encryption_key_password: Option<String>,
744}
745
746impl Default for VeilidConfigProtectedStore {
747    fn default() -> Self {
748        Self {
749            allow_insecure_fallback: false,
750            always_use_insecure_storage: false,
751            directory: "".to_string(),
752            delete: false,
753            device_encryption_key_password: "".to_owned(),
754            new_device_encryption_key_password: None,
755        }
756    }
757}
758
759#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
760#[cfg_attr(
761    all(target_arch = "wasm32", target_os = "unknown"),
762    derive(Tsify),
763    tsify(into_wasm_abi, from_wasm_abi)
764)]
765#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
766#[must_use]
767pub struct VeilidConfigCapabilities {
768    pub disable: Vec<VeilidCapability>,
769}
770
771#[derive(
772    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize, JsonSchema,
773)]
774#[cfg_attr(
775    all(target_arch = "wasm32", target_os = "unknown"),
776    derive(Tsify),
777    tsify(namespace, into_wasm_abi, from_wasm_abi)
778)]
779#[must_use]
780#[derive(Default)]
781pub enum VeilidConfigLogLevel {
782    #[default]
783    Off,
784    Error,
785    Warn,
786    Info,
787    Debug,
788    Trace,
789}
790
791impl From<VeilidLogLevel> for VeilidConfigLogLevel {
792    fn from(value: VeilidLogLevel) -> Self {
793        match value {
794            VeilidLogLevel::Error => Self::Error,
795            VeilidLogLevel::Warn => Self::Warn,
796            VeilidLogLevel::Info => Self::Info,
797            VeilidLogLevel::Debug => Self::Debug,
798            VeilidLogLevel::Trace => Self::Trace,
799        }
800    }
801}
802
803impl From<Option<VeilidLogLevel>> for VeilidConfigLogLevel {
804    fn from(value: Option<VeilidLogLevel>) -> Self {
805        match value {
806            None => Self::Off,
807            Some(VeilidLogLevel::Error) => Self::Error,
808            Some(VeilidLogLevel::Warn) => Self::Warn,
809            Some(VeilidLogLevel::Info) => Self::Info,
810            Some(VeilidLogLevel::Debug) => Self::Debug,
811            Some(VeilidLogLevel::Trace) => Self::Trace,
812        }
813    }
814}
815
816impl From<tracing::level_filters::LevelFilter> for VeilidConfigLogLevel {
817    fn from(value: tracing::level_filters::LevelFilter) -> Self {
818        match value {
819            tracing::level_filters::LevelFilter::OFF => Self::Off,
820            tracing::level_filters::LevelFilter::ERROR => Self::Error,
821            tracing::level_filters::LevelFilter::WARN => Self::Warn,
822            tracing::level_filters::LevelFilter::INFO => Self::Info,
823            tracing::level_filters::LevelFilter::DEBUG => Self::Debug,
824            tracing::level_filters::LevelFilter::TRACE => Self::Trace,
825        }
826    }
827}
828
829impl From<VeilidConfigLogLevel> for tracing::level_filters::LevelFilter {
830    fn from(val: VeilidConfigLogLevel) -> Self {
831        match val {
832            VeilidConfigLogLevel::Off => tracing::level_filters::LevelFilter::OFF,
833            VeilidConfigLogLevel::Error => tracing::level_filters::LevelFilter::ERROR,
834            VeilidConfigLogLevel::Warn => tracing::level_filters::LevelFilter::WARN,
835            VeilidConfigLogLevel::Info => tracing::level_filters::LevelFilter::INFO,
836            VeilidConfigLogLevel::Debug => tracing::level_filters::LevelFilter::DEBUG,
837            VeilidConfigLogLevel::Trace => tracing::level_filters::LevelFilter::TRACE,
838        }
839    }
840}
841
842impl From<tracing::log::LevelFilter> for VeilidConfigLogLevel {
843    fn from(value: tracing::log::LevelFilter) -> Self {
844        match value {
845            tracing::log::LevelFilter::Off => Self::Off,
846            tracing::log::LevelFilter::Error => Self::Error,
847            tracing::log::LevelFilter::Warn => Self::Warn,
848            tracing::log::LevelFilter::Info => Self::Info,
849            tracing::log::LevelFilter::Debug => Self::Debug,
850            tracing::log::LevelFilter::Trace => Self::Trace,
851        }
852    }
853}
854
855impl From<VeilidConfigLogLevel> for tracing::log::LevelFilter {
856    fn from(val: VeilidConfigLogLevel) -> Self {
857        match val {
858            VeilidConfigLogLevel::Off => tracing::log::LevelFilter::Off,
859            VeilidConfigLogLevel::Error => tracing::log::LevelFilter::Error,
860            VeilidConfigLogLevel::Warn => tracing::log::LevelFilter::Warn,
861            VeilidConfigLogLevel::Info => tracing::log::LevelFilter::Info,
862            VeilidConfigLogLevel::Debug => tracing::log::LevelFilter::Debug,
863            VeilidConfigLogLevel::Trace => tracing::log::LevelFilter::Trace,
864        }
865    }
866}
867
868impl TryFrom<&str> for VeilidConfigLogLevel {
869    type Error = VeilidAPIError;
870
871    fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
872        Self::from_str(value)
873    }
874}
875
876impl TryFrom<String> for VeilidConfigLogLevel {
877    type Error = VeilidAPIError;
878
879    fn try_from(value: String) -> Result<Self, <Self as TryFrom<String>>::Error> {
880        Self::from_str(value.as_str())
881    }
882}
883
884impl TryFrom<&String> for VeilidConfigLogLevel {
885    type Error = VeilidAPIError;
886
887    fn try_from(value: &String) -> Result<Self, <Self as TryFrom<&String>>::Error> {
888        Self::from_str(value.as_str())
889    }
890}
891
892impl FromStr for VeilidConfigLogLevel {
893    type Err = VeilidAPIError;
894    fn from_str(s: &str) -> Result<Self, Self::Err> {
895        Ok(match s.to_ascii_lowercase().as_str() {
896            "off" => Self::Off,
897            "error" => Self::Error,
898            "warn" => Self::Warn,
899            "info" => Self::Info,
900            "debug" => Self::Debug,
901            "trace" => Self::Trace,
902            _ => {
903                apibail_invalid_argument!("invalid VeilidConfigLogLevel string", "s", s);
904            }
905        })
906    }
907}
908impl fmt::Display for VeilidConfigLogLevel {
909    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
910        let text = match self {
911            Self::Off => "Off",
912            Self::Error => "Error",
913            Self::Warn => "Warn",
914            Self::Info => "Info",
915            Self::Debug => "Debug",
916            Self::Trace => "Trace",
917        };
918        write!(f, "{}", text)
919    }
920}
921
922/// Top level of the Veilid configuration tree
923#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
924#[cfg_attr(
925    all(target_arch = "wasm32", target_os = "unknown"),
926    derive(Tsify),
927    tsify(into_wasm_abi, from_wasm_abi)
928)]
929#[cfg_attr(feature = "json-camel-case", serde(rename_all = "camelCase"))]
930#[must_use]
931pub struct VeilidConfig {
932    /// An identifier used to describe the program using veilid-core.
933    /// Used to partition storage locations in places like the ProtectedStore.
934    /// Must be non-empty and a valid filename for all Veilid-capable systems, which means
935    /// no backslashes or forward slashes in the name. Stick to a-z,0-9,_ and space and you should be fine.
936    ///
937    /// Caution: If you change this string, there is no migration support. Your app's protected store and
938    /// table store will very likely experience data loss. Pick a program name and stick with it. This is
939    /// not a 'visible' identifier and it should uniquely identify your application.
940    pub program_name: String,
941    /// To run multiple Veilid nodes within the same application, either through a single process running
942    /// api_startup/api_startup_json multiple times, or your application running mulitple times side-by-side
943    /// there needs to be a key used to partition the application's storage (in the TableStore, ProtectedStore, etc).
944    /// An empty value here is the default, but if you run multiple veilid nodes concurrently, you should set this
945    /// to a string that uniquely identifies this -instance- within the same 'program_name'.
946    /// Must be a valid filename for all Veilid-capable systems, which means no backslashes or forward slashes
947    /// in the name. Stick to a-z,0-9,_ and space and you should be fine.
948    pub namespace: String,
949    /// Capabilities to enable for your application/node
950    pub capabilities: VeilidConfigCapabilities,
951    /// Configuring the protected store (keychain/keyring/etc)
952    pub protected_store: VeilidConfigProtectedStore,
953    /// Configuring the table store (persistent encrypted database)
954    pub table_store: VeilidConfigTableStore,
955    /// Configuring the block store (storage of large content-addressable content)
956    pub block_store: VeilidConfigBlockStore,
957    /// Configuring how Veilid interacts with the low level network
958    pub network: VeilidConfigNetwork,
959}
960
961impl VeilidConfig {
962    /// Create a new 'VeilidConfig' for use with `setup_from_config`
963    /// Should match the application bundle name if used elsewhere in the format:
964    /// `qualifier.organization.program_name` - for example `org.veilid.veilidchat`
965    ///
966    /// The 'bundle name' will be used when choosing the default storage location for the
967    /// application in a platform-dependent fashion, unless 'storage_directory' is
968    /// specified to override this location
969    ///
970    /// * `program_name` - Pick a program name and do not change it from release to release,
971    ///   see `VeilidConfig::program_name` for details.
972    /// * `organization_name` - Similar to program_name, but for the organization publishing this app
973    /// * `qualifier` - Suffix for the application bundle name
974    /// * `storage_directory` - Override for the path where veilid-core stores its content
975    ///   such as the table store, protected store, and block store
976    /// * `config_directory` - Override for the path where veilid-core can retrieve extra configuration files
977    ///   such as certificates and keys
978    pub fn new(
979        program_name: &str,
980        organization: &str,
981        qualifier: &str,
982        storage_directory: Option<&str>,
983        config_directory: Option<&str>,
984    ) -> Self {
985        let mut out = Self {
986            program_name: program_name.to_owned(),
987            ..Default::default()
988        };
989
990        if let Some(storage_directory) = storage_directory {
991            out.protected_store.directory = (std::path::PathBuf::from(storage_directory)
992                .join("protected_store"))
993            .to_string_lossy()
994            .to_string();
995            out.table_store.directory = (std::path::PathBuf::from(storage_directory)
996                .join("table_store"))
997            .to_string_lossy()
998            .to_string();
999            out.block_store.directory = (std::path::PathBuf::from(storage_directory)
1000                .join("block_store"))
1001            .to_string_lossy()
1002            .to_string();
1003        } else {
1004            out.protected_store.directory =
1005                get_default_store_path(program_name, organization, qualifier, "protected_store");
1006            out.table_store.directory =
1007                get_default_store_path(program_name, organization, qualifier, "table_store");
1008            out.block_store.directory =
1009                get_default_store_path(program_name, organization, qualifier, "block_store");
1010        }
1011
1012        if let Some(config_directory) = config_directory {
1013            out.network.tls.certificate_path = (std::path::PathBuf::from(config_directory)
1014                .join("ssl/certs/server.crt"))
1015            .to_string_lossy()
1016            .to_string();
1017            out.network.tls.private_key_path = (std::path::PathBuf::from(config_directory)
1018                .join("ssl/keys/server.key"))
1019            .to_string_lossy()
1020            .to_string();
1021        } else {
1022            out.network.tls.certificate_path = get_default_ssl_directory(
1023                program_name,
1024                organization,
1025                qualifier,
1026                "certs/server.crt",
1027            );
1028            out.network.tls.private_key_path =
1029                get_default_ssl_directory(program_name, organization, qualifier, "keys/server.key");
1030        }
1031
1032        out
1033    }
1034
1035    #[must_use]
1036    pub fn safe(&self) -> Arc<VeilidConfig> {
1037        let mut safe_cfg = self.clone();
1038
1039        // Remove secrets
1040        safe_cfg.network.routing_table.secret_keys = SecretKeyGroup::new();
1041        "".clone_into(&mut safe_cfg.protected_store.device_encryption_key_password);
1042        safe_cfg.protected_store.new_device_encryption_key_password = None;
1043
1044        Arc::new(safe_cfg)
1045    }
1046
1047    pub fn get_key_json(&self, key: &str, pretty: bool) -> VeilidAPIResult<String> {
1048        // Generate json from whole config
1049        let jc = serde_json::to_string(self).map_err(VeilidAPIError::generic)?;
1050        let jvc = json::parse(&jc).map_err(VeilidAPIError::generic)?;
1051
1052        // Find requested subkey
1053        if key.is_empty() {
1054            Ok(if pretty {
1055                jvc.pretty(2)
1056            } else {
1057                jvc.to_string()
1058            })
1059        } else {
1060            // Split key into path parts
1061            let keypath: Vec<&str> = key.split('.').collect();
1062            let mut out = &jvc;
1063            for k in keypath {
1064                if !out.has_key(k) {
1065                    apibail_parse_error!(format!("invalid subkey in key '{}'", key), k);
1066                }
1067                out = &out[k];
1068            }
1069            Ok(if pretty {
1070                out.pretty(2)
1071            } else {
1072                out.to_string()
1073            })
1074        }
1075    }
1076
1077    fn validate_program_name(program_name: &str) -> VeilidAPIResult<()> {
1078        if program_name.is_empty() {
1079            apibail_generic!("Program name must not be empty in 'program_name'");
1080        }
1081        if !sanitize_filename::is_sanitized_with_options(
1082            program_name,
1083            sanitize_filename::OptionsForCheck {
1084                windows: true,
1085                truncate: true,
1086            },
1087        ) {
1088            apibail_generic!("'program_name' must not be an invalid filename");
1089        }
1090        Ok(())
1091    }
1092
1093    fn validate_namespace(namespace: &str) -> VeilidAPIResult<()> {
1094        if namespace.is_empty() {
1095            return Ok(());
1096        }
1097        if !sanitize_filename::is_sanitized_with_options(
1098            namespace,
1099            sanitize_filename::OptionsForCheck {
1100                windows: true,
1101                truncate: true,
1102            },
1103        ) {
1104            apibail_generic!("'namespace' must not be an invalid filename");
1105        }
1106
1107        Ok(())
1108    }
1109
1110    pub fn validate(&self) -> VeilidAPIResult<()> {
1111        Self::validate_program_name(&self.program_name)?;
1112        Self::validate_namespace(&self.namespace)?;
1113
1114        // if inner.network.protocol.udp.enabled {
1115        //     // Validate UDP settings
1116        // }
1117        if self.network.protocol.tcp.listen {
1118            // Validate TCP settings
1119            if self.network.protocol.tcp.max_connections == 0 {
1120                apibail_generic!("TCP max connections must be > 0 in config key 'network.protocol.tcp.max_connections'");
1121            }
1122        }
1123        if self.network.protocol.ws.listen {
1124            // Validate WS settings
1125            if self.network.protocol.ws.max_connections == 0 {
1126                apibail_generic!("WS max connections must be > 0 in config key 'network.protocol.ws.max_connections'");
1127            }
1128        }
1129        #[cfg(feature = "enable-protocol-wss")]
1130        if self.network.protocol.wss.listen {
1131            // Validate WSS settings
1132            if self.network.protocol.wss.max_connections == 0 {
1133                apibail_generic!("WSS max connections must be > 0 in config key 'network.protocol.wss.max_connections'");
1134            }
1135            if self
1136                .network
1137                .protocol
1138                .wss
1139                .url
1140                .as_ref()
1141                .map(|u| u.is_empty())
1142                .unwrap_or_default()
1143            {
1144                apibail_generic!(
1145                    "WSS URL must be specified in config key 'network.protocol.wss.url'"
1146                );
1147            }
1148        }
1149        if self.network.rpc.max_route_hop_count == 0 {
1150            apibail_generic!(
1151                "max route hop count must be >= 1 in 'network.rpc.max_route_hop_count'"
1152            );
1153        }
1154        if self.network.rpc.max_route_hop_count > 5 {
1155            apibail_generic!(
1156                "max route hop count must be <= 5 in 'network.rpc.max_route_hop_count'"
1157            );
1158        }
1159        if self.network.rpc.default_route_hop_count == 0 {
1160            apibail_generic!(
1161                "default route hop count must be >= 1 in 'network.rpc.default_route_hop_count'"
1162            );
1163        }
1164        if self.network.rpc.default_route_hop_count > self.network.rpc.max_route_hop_count {
1165            apibail_generic!(
1166                "default route hop count must be <= max route hop count in 'network.rpc.default_route_hop_count <= network.rpc.max_route_hop_count'"
1167            );
1168        }
1169        if self.network.rpc.queue_size < 256 {
1170            apibail_generic!("rpc queue size must be >= 256 in 'network.rpc.queue_size'");
1171        }
1172        if self.network.rpc.timeout_ms < 1000 {
1173            apibail_generic!("rpc timeout must be >= 1000 in 'network.rpc.timeout_ms'");
1174        }
1175        if self.network.dht.consensus_width < self.network.dht.set_value_count {
1176            apibail_generic!(
1177                "consensus width must be >= set value count in 'network.dht.consensus_width'"
1178            );
1179        }
1180        if self.network.dht.get_value_count <= (self.network.dht.set_value_count / 2) {
1181            apibail_generic!("get consensus count must be >= (set value count / 2) in 'network.dht.get_value_count'");
1182        }
1183        if self.network.dht.get_value_fanout < 1 {
1184            apibail_generic!("get value fanout must be >= 1 in 'network.dht.get_value_fanout'");
1185        }
1186        if self.network.dht.set_value_fanout < 1 {
1187            apibail_generic!("set value fanout must be >= 1 in 'network.dht.set_value_fanout'");
1188        }
1189        if self.network.dht.get_value_timeout_ms < (2 * self.network.rpc.timeout_ms) {
1190            apibail_generic!("get value timeout must be >= (2 * the rpc timeout) in 'network.dht.get_value_timeout_ms'");
1191        }
1192        if self.network.dht.set_value_timeout_ms < (2 * self.network.rpc.timeout_ms) {
1193            apibail_generic!("set value timeout must be >= (2 * the rpc timeout) in 'network.dht.set_value_timeout_ms'");
1194        }
1195
1196        if self.network.dht.public_watch_limit < 1 {
1197            apibail_generic!("public watch limit must be >= 1 in 'network.dht.public_watch_limit'");
1198        }
1199        if self.network.dht.member_watch_limit < 1 {
1200            apibail_generic!("member watch limit must be >= 1 in 'network.dht.member_watch_limit'");
1201        }
1202        if self.network.dht.max_watch_expiration_ms < (2 * self.network.rpc.timeout_ms) {
1203            apibail_generic!("max watch expiration must be >= (2 * rpc timeout) 'network.dht.max_watch_expiration_ms'");
1204        }
1205        if self.network.dht.public_transaction_limit < 1 {
1206            apibail_generic!(
1207                "public transaction limit must be >= 1 in 'network.dht.public_transaction_limit'"
1208            );
1209        }
1210        if self.network.dht.member_transaction_limit < 1 {
1211            apibail_generic!(
1212                "member transaction limit must be >= 1 in 'network.dht.member_transaction_limit'"
1213            );
1214        }
1215
1216        Ok(())
1217    }
1218}
1219
1220/// The configuration built for each Veilid node during API startup
1221#[derive(Clone)]
1222#[must_use]
1223pub struct VeilidStartupOptions {
1224    update_cb: UpdateCallback,
1225    config: Arc<VeilidConfig>,
1226}
1227
1228impl fmt::Debug for VeilidStartupOptions {
1229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1230        f.debug_struct("VeilidConfig")
1231            .field("config", self.config.as_ref())
1232            .finish()
1233    }
1234}
1235
1236impl VeilidStartupOptions {
1237    pub(crate) fn try_new(
1238        config: VeilidConfig,
1239        update_cb: UpdateCallback,
1240    ) -> VeilidAPIResult<Self> {
1241        config.validate()?;
1242
1243        Ok(Self {
1244            update_cb,
1245            config: Arc::new(config),
1246        })
1247    }
1248
1249    #[must_use]
1250    pub fn update_callback(&self) -> UpdateCallback {
1251        self.update_cb.clone()
1252    }
1253
1254    #[must_use]
1255    pub fn config(&self) -> Arc<VeilidConfig> {
1256        self.config.clone()
1257    }
1258}
1259
1260/// Return the default veilid config as a json object.
1261#[must_use]
1262pub fn default_veilid_config() -> String {
1263    serialize_json(VeilidConfig::default())
1264}