Skip to main content

deaddrop_core/config/
mod.rs

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    /// public | contacts | space-members | invite-only | hidden
41    #[serde(default)]
42    pub mode: DiscoveryMode,
43    /// Rotate LAN beacon identifiers daily. Handshake still uses the real PeerId.
44    #[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}