1use crate::{DdError, NodeMode, Result, RoutingPolicyKind};
2use serde::{Deserialize, Serialize};
3use std::path::Path;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Config {
7 pub node: NodeSection,
8 pub storage: StorageSection,
9 pub routing: RoutingSection,
10 pub discovery: DiscoverySection,
11 pub transport: TransportSection,
12 pub bootstrap: Vec<Bootstrap>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct NodeSection {
17 pub mode: NodeMode,
18 #[serde(default)]
19 pub role: crate::NodeRole,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct StorageSection {
24 pub maximum: String,
25 pub reserved_local: String,
26 pub relay_budget: String,
27 pub temporary: String,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct RoutingSection {
32 pub strategy: String,
33 pub replication_budget: u32,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct DiscoverySection {
38 pub lan: bool,
39 pub lan_port: u16,
40 #[serde(default)]
42 pub mode: DiscoveryMode,
43 #[serde(default)]
45 pub ephemeral_ids: bool,
46}
47
48#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
49#[serde(rename_all = "kebab-case")]
50pub enum DiscoveryMode {
51 #[default]
52 Public,
53 Contacts,
54 SpaceMembers,
55 InviteOnly,
56 Hidden,
57}
58
59impl DiscoveryMode {
60 pub fn advertise_lan(self) -> bool {
61 !matches!(self, Self::Hidden)
62 }
63
64 pub fn parse_cli(s: &str) -> Option<Self> {
65 match s.trim().to_ascii_lowercase().as_str() {
66 "public" => Some(Self::Public),
67 "contacts" => Some(Self::Contacts),
68 "space-members" | "space_members" => Some(Self::SpaceMembers),
69 "invite-only" | "invite_only" => Some(Self::InviteOnly),
70 "hidden" => Some(Self::Hidden),
71 _ => None,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct TransportSection {
78 pub quic: bool,
79 pub listen: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Bootstrap {
84 pub endpoint: String,
85}
86
87impl Default for Config {
88 fn default() -> Self {
89 Self {
90 node: NodeSection {
91 mode: NodeMode::Balanced,
92 role: crate::NodeRole::Personal,
93 },
94 storage: StorageSection {
95 maximum: "20GB".into(),
96 reserved_local: "5GB".into(),
97 relay_budget: "10GB".into(),
98 temporary: "5GB".into(),
99 },
100 routing: RoutingSection {
101 strategy: "adaptive".into(),
102 replication_budget: 8,
103 },
104 discovery: DiscoverySection {
105 lan: true,
106 lan_port: 7946,
107 mode: DiscoveryMode::Public,
108 ephemeral_ids: false,
109 },
110 transport: TransportSection {
111 quic: true,
112 listen: "0.0.0.0:7947".into(),
113 },
114 bootstrap: vec![],
115 }
116 }
117}
118
119impl Config {
120 pub fn load(path: &Path) -> Result<Self> {
121 if !path.exists() {
122 return Ok(Self::default());
123 }
124 let s = std::fs::read_to_string(path)?;
125 toml::from_str(&s).map_err(|e| DdError::invalid_frame(format!("config: {e}")))
126 }
127
128 pub fn strategy(&self) -> RoutingPolicyKind {
129 match self.routing.strategy.as_str() {
130 "direct" => RoutingPolicyKind::Direct,
131 "epidemic" => RoutingPolicyKind::Epidemic,
132 "encounter" => RoutingPolicyKind::Encounter,
133 "utility" => RoutingPolicyKind::Utility,
134 "adaptive" => RoutingPolicyKind::Adaptive,
135 _ => RoutingPolicyKind::SprayAndWait,
136 }
137 }
138
139 pub fn parse_bytes(s: &str) -> u64 {
140 let t = s.trim().to_ascii_uppercase();
141 let (n, mul) = if let Some(x) = t.strip_suffix("GB") {
142 (x, 1024u64 * 1024 * 1024)
143 } else if let Some(x) = t.strip_suffix("MB") {
144 (x, 1024 * 1024)
145 } else if let Some(x) = t.strip_suffix("KB") {
146 (x, 1024)
147 } else {
148 (t.as_str(), 1)
149 };
150 n.trim().parse::<f64>().unwrap_or(0.0) as u64 * mul
151 }
152}