1use crate::address::NetworkAddress;
27use crate::error::ConfigError;
28use crate::validation::{
29 ValidationContext, validate_config_value, validate_file_path, validate_network_address,
30};
31use crate::{P2PError, Result};
32use regex::Regex;
33use serde::{Deserialize, Serialize};
34use std::env;
35use std::fs;
36use std::net::SocketAddr;
37use std::path::{Path, PathBuf};
38use std::str::FromStr;
39use tracing::info;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(default)]
44#[derive(Default)]
45pub struct Config {
46 pub network: NetworkConfig,
48 pub security: SecurityConfig,
50 pub storage: StorageConfig,
52 pub mcp: McpConfig,
54 pub dht: DhtConfig,
56 pub transport: TransportConfig,
58 pub identity: IdentityConfig,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(default)]
65pub struct NetworkConfig {
66 pub bootstrap_nodes: Vec<String>,
68 pub listen_address: String,
70 pub public_address: Option<String>,
72 pub ipv6_enabled: bool,
74 pub max_connections: usize,
76 pub connection_timeout: u64,
78 pub keepalive_interval: u64,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(default)]
85pub struct SecurityConfig {
86 pub rate_limit: u32,
88 pub connection_limit: u32,
90 pub encryption_enabled: bool,
92 pub min_tls_version: String,
94 pub identity_security_level: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100#[serde(default)]
101pub struct StorageConfig {
102 pub path: PathBuf,
104 pub max_size: String,
106 pub cache_size: u64,
108 pub compression_enabled: bool,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(default)]
115pub struct McpConfig {
116 pub enabled: bool,
118 pub port: u16,
120 pub max_execution_time: u64,
122 pub monitoring_enabled: bool,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(default)]
129pub struct DhtConfig {
130 pub replication_factor: u8,
132 pub alpha: u8,
134 pub beta: u8,
136 pub record_ttl: u64,
138 pub adaptive_routing: bool,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(default)]
145pub struct TransportConfig {
146 pub protocol: String,
148 pub quic_enabled: bool,
150 pub tcp_enabled: bool,
152 pub webrtc_enabled: bool,
154 pub buffer_size: usize,
156 pub server_name: String,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(default)]
163pub struct IdentityConfig {
164 pub derivation_path: String,
166 pub rotation_interval: u32,
168 pub backup_enabled: bool,
170 pub backup_interval: u32,
172}
173
174impl Default for NetworkConfig {
177 fn default() -> Self {
178 Self {
179 bootstrap_nodes: vec![],
180 listen_address: env::var("SAORSA_LISTEN_ADDRESS")
182 .unwrap_or_else(|_| "127.0.0.1:9000".to_string()),
183 public_address: None,
184 ipv6_enabled: true,
185 max_connections: 10000,
186 connection_timeout: 30,
187 keepalive_interval: 60,
188 }
189 }
190}
191
192impl Default for SecurityConfig {
193 fn default() -> Self {
194 Self {
195 rate_limit: 1000,
196 connection_limit: 100,
197 encryption_enabled: true,
198 min_tls_version: "1.3".to_string(),
199 identity_security_level: "High".to_string(),
200 }
201 }
202}
203
204impl Default for StorageConfig {
205 fn default() -> Self {
206 Self {
207 path: PathBuf::from("./data"),
208 max_size: "10GB".to_string(),
209 cache_size: 256,
210 compression_enabled: true,
211 }
212 }
213}
214
215impl Default for McpConfig {
216 fn default() -> Self {
217 Self {
218 enabled: true,
219 port: 9001,
220 max_execution_time: 300,
221 monitoring_enabled: true,
222 }
223 }
224}
225
226impl Default for DhtConfig {
227 fn default() -> Self {
228 Self {
229 replication_factor: 8,
230 alpha: 3,
231 beta: 1,
232 record_ttl: 3600,
233 adaptive_routing: true,
234 }
235 }
236}
237
238impl Default for TransportConfig {
239 fn default() -> Self {
240 Self {
241 protocol: "quic".to_string(),
242 quic_enabled: true,
243 tcp_enabled: true,
244 webrtc_enabled: false,
245 buffer_size: 65536,
246 server_name: "p2p.local".to_string(),
247 }
248 }
249}
250
251impl Default for IdentityConfig {
252 fn default() -> Self {
253 Self {
254 derivation_path: "m/44'/0'/0'/0/0".to_string(),
255 rotation_interval: 90,
256 backup_enabled: true,
257 backup_interval: 24,
258 }
259 }
260}
261
262impl Config {
263 pub fn load() -> Result<Self> {
282 Self::load_with_path::<&str>(None)
283 }
284
285 pub fn load_with_path<P: AsRef<Path>>(path: Option<P>) -> Result<Self> {
301 let mut config = Self::default();
303
304 if let Some(path) = path {
306 config = Self::load_from_file(path)?;
307 } else {
308 for location in &["saorsa.toml", "config.toml", "/etc/saorsa/config.toml"] {
310 if Path::new(location).exists() {
311 info!("Loading config from: {}", location);
312 config = Self::load_from_file(location)?;
313 break;
314 }
315 }
316 }
317
318 config.apply_env_overrides()?;
320
321 config.validate()?;
323
324 Ok(config)
325 }
326
327 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
329 let content = fs::read_to_string(&path).map_err(|e| {
330 P2PError::Config(ConfigError::IoError {
331 path: path.as_ref().to_string_lossy().to_string().into(),
332 source: e,
333 })
334 })?;
335
336 toml::from_str(&content)
337 .map_err(|e| P2PError::Config(ConfigError::ParseError(e.to_string().into())))
338 }
339
340 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
342 let content = toml::to_string_pretty(self)
343 .map_err(|e| P2PError::Config(ConfigError::ParseError(e.to_string().into())))?;
344
345 fs::write(&path, content).map_err(|e| {
346 P2PError::Config(ConfigError::IoError {
347 path: path.as_ref().to_string_lossy().to_string().into(),
348 source: e,
349 })
350 })?;
351
352 Ok(())
353 }
354
355 fn apply_env_overrides(&mut self) -> Result<()> {
357 if let Ok(val) = env::var("SAORSA_LISTEN_ADDRESS") {
359 self.network.listen_address = val;
360 }
361 if let Ok(val) = env::var("SAORSA_PUBLIC_ADDRESS") {
362 self.network.public_address = Some(val);
363 }
364 if let Ok(val) = env::var("SAORSA_BOOTSTRAP_NODES") {
365 self.network.bootstrap_nodes = val.split(',').map(String::from).collect();
366 }
367 if let Ok(val) = env::var("SAORSA_MAX_CONNECTIONS") {
368 self.network.max_connections = val.parse().map_err(|_| {
369 P2PError::Config(ConfigError::InvalidValue {
370 field: "max_connections".to_string().into(),
371 reason: "Invalid value".to_string().into(),
372 })
373 })?;
374 }
375
376 if let Ok(val) = env::var("SAORSA_RATE_LIMIT") {
378 self.security.rate_limit = val.parse().map_err(|_| {
379 P2PError::Config(ConfigError::InvalidValue {
380 field: "rate_limit".to_string().into(),
381 reason: "Invalid value".to_string().into(),
382 })
383 })?;
384 }
385 if let Ok(val) = env::var("SAORSA_ENCRYPTION_ENABLED") {
386 self.security.encryption_enabled = val.parse().map_err(|_| {
387 P2PError::Config(ConfigError::InvalidValue {
388 field: "encryption_enabled".to_string().into(),
389 reason: "Invalid value".to_string().into(),
390 })
391 })?;
392 }
393
394 if let Ok(val) = env::var("SAORSA_DATA_PATH") {
396 self.storage.path = PathBuf::from(val);
397 }
398 if let Ok(val) = env::var("SAORSA_MAX_STORAGE") {
399 self.storage.max_size = val;
400 }
401
402 if let Ok(val) = env::var("SAORSA_MCP_ENABLED") {
404 self.mcp.enabled = val.parse().map_err(|_| {
405 P2PError::Config(ConfigError::InvalidValue {
406 field: "mcp_enabled".to_string().into(),
407 reason: "Invalid value".to_string().into(),
408 })
409 })?;
410 }
411 if let Ok(val) = env::var("SAORSA_MCP_PORT") {
412 self.mcp.port = val.parse().map_err(|_| {
413 P2PError::Config(ConfigError::InvalidValue {
414 field: "mcp_port".to_string().into(),
415 reason: "Invalid value".to_string().into(),
416 })
417 })?;
418 }
419
420 Ok(())
421 }
422
423 pub fn validate(&self) -> Result<()> {
425 let mut errors = Vec::new();
426
427 if let Err(e) = self.validate_address(&self.network.listen_address, "listen_address") {
429 errors.push(e);
430 }
431
432 if let Some(addr) = &self.network.public_address
433 && let Err(e) = self.validate_address(addr, "public_address")
434 {
435 errors.push(e);
436 }
437
438 for (i, node) in self.network.bootstrap_nodes.iter().enumerate() {
439 if let Err(e) = self.validate_address(node, &format!("bootstrap_node[{}]", i)) {
440 errors.push(e);
441 }
442 }
443
444 if let Err(e) = validate_config_value(
446 &self.network.max_connections.to_string(),
447 Some(1_usize),
448 Some(100_000_usize),
449 ) {
450 errors.push(P2PError::Config(ConfigError::InvalidValue {
451 field: "max_connections".to_string().into(),
452 reason: e.to_string().into(),
453 }));
454 }
455
456 if let Err(e) = validate_config_value(
457 &self.security.rate_limit.to_string(),
458 Some(1_u32),
459 Some(1_000_000_u32),
460 ) {
461 errors.push(P2PError::Config(ConfigError::InvalidValue {
462 field: "rate_limit".to_string().into(),
463 reason: e.to_string().into(),
464 }));
465 }
466
467 if let Err(e) = validate_file_path(&self.storage.path) {
469 errors.push(P2PError::Config(ConfigError::InvalidValue {
470 field: "storage.path".to_string().into(),
471 reason: e.to_string().into(),
472 }));
473 }
474
475 if !self.validate_size_format(&self.storage.max_size) {
477 errors.push(P2PError::Config(ConfigError::InvalidValue {
478 field: "max_size".to_string().into(),
479 reason: format!("Invalid storage size format: {}", self.storage.max_size).into(),
480 }));
481 }
482
483 match self.transport.protocol.as_str() {
485 "quic" | "tcp" | "webrtc" => {}
486 _ => errors.push(P2PError::Config(ConfigError::InvalidValue {
487 field: "protocol".to_string().into(),
488 reason: format!("Invalid transport protocol: {}", self.transport.protocol).into(),
489 })),
490 }
491
492 if errors.is_empty() {
493 Ok(())
494 } else {
495 Err(errors.into_iter().next().unwrap_or_else(|| {
497 P2PError::Config(ConfigError::InvalidValue {
498 field: "unknown".to_string().into(),
499 reason: "Validation failed with unknown error".to_string().into(),
500 })
501 }))
502 }
503 }
504
505 fn validate_address(&self, addr: &str, field: &str) -> Result<()> {
507 if let Ok(socket_addr) = SocketAddr::from_str(addr) {
509 let ctx = ValidationContext::default()
511 .allow_localhost() .allow_private_ips(); return validate_network_address(&socket_addr, &ctx).map_err(|e| {
515 P2PError::Config(ConfigError::InvalidValue {
516 field: field.to_string().into(),
517 reason: e.to_string().into(),
518 })
519 });
520 }
521
522 if let Ok(network_addr) = crate::NetworkAddress::from_four_words(addr) {
524 let ctx = ValidationContext::default()
526 .allow_localhost()
527 .allow_private_ips();
528
529 return validate_network_address(&network_addr.socket_addr(), &ctx).map_err(|e| {
530 P2PError::Config(ConfigError::InvalidValue {
531 field: field.to_string().into(),
532 reason: e.to_string().into(),
533 })
534 });
535 }
536
537 if addr.starts_with("/ip4/") || addr.starts_with("/ip6/") {
539 return Ok(());
541 }
542
543 Err(P2PError::Config(ConfigError::InvalidValue {
544 field: field.to_string().into(),
545 reason: format!("Invalid address format: {}", addr).into(),
546 }))
547 }
548
549 fn validate_size_format(&self, size: &str) -> bool {
551 thread_local! {
552 static SIZE_REGEX: std::result::Result<Regex, P2PError> = Regex::new(r"^\\d+(?:\\.\\d+)?\\s*(?:B|KB|MB|GB|TB)$")
553 .map_err(|e| P2PError::Config(ConfigError::InvalidValue { field: "size".to_string().into(), reason: e.to_string().into() }));
554 }
555 SIZE_REGEX.with(|re| re.as_ref().ok().map(|r| r.is_match(size)).unwrap_or(false))
556 }
557
558 pub fn development() -> Self {
560 let mut config = Self::default();
561 config.network.listen_address = "127.0.0.1:9000".to_string();
562 config.security.rate_limit = 10000;
563 config.security.connection_limit = 1000;
564 config.storage.path = PathBuf::from("./dev-data");
565 config
566 }
567
568 pub fn production() -> Self {
570 let mut config = Self::default();
571 config.network.listen_address =
573 env::var("SAORSA_LISTEN_ADDRESS").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
574 config.security.rate_limit = 1000;
575 config.security.connection_limit = 100;
576 config.storage.path = PathBuf::from("/var/lib/saorsa");
577 config
578 }
579
580 pub fn listen_socket_addr(&self) -> Result<SocketAddr> {
582 SocketAddr::from_str(&self.network.listen_address).map_err(|e| {
583 P2PError::Config(ConfigError::InvalidValue {
584 field: "listen_address".to_string().into(),
585 reason: format!("Invalid address: {}", e).into(),
586 })
587 })
588 }
589
590 pub fn bootstrap_addrs(&self) -> Result<Vec<NetworkAddress>> {
592 self.network
593 .bootstrap_nodes
594 .iter()
595 .map(|addr| {
596 addr.parse::<NetworkAddress>().map_err(|e| {
597 P2PError::Config(ConfigError::InvalidValue {
598 field: "bootstrap_nodes".to_string().into(),
599 reason: format!("Invalid address: {}", e).into(),
600 })
601 })
602 })
603 .collect()
604 }
605
606 pub fn parse_size(size: &str) -> Result<u64> {
618 thread_local! {
619 static SIZE_REGEX: std::result::Result<Regex, P2PError> = Regex::new(r"^(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)$")
620 .map_err(|e| P2PError::Config(ConfigError::InvalidValue { field: "size".to_string().into(), reason: e.to_string().into() }));
621 }
622
623 SIZE_REGEX.with(|re| -> Result<u64> {
624 let re = match re {
625 Ok(r) => r,
626 Err(e) => {
627 return Err(P2PError::Config(ConfigError::InvalidValue {
628 field: "size".to_string().into(),
629 reason: e.to_string().into(),
630 }));
631 }
632 };
633 if let Some(captures) = re.captures(size) {
634 let value: f64 = captures
635 .get(1)
636 .and_then(|m| m.as_str().parse().ok())
637 .ok_or_else(|| {
638 P2PError::Config(ConfigError::InvalidValue {
639 field: "size".to_string().into(),
640 reason: "Invalid numeric value".to_string().into(),
641 })
642 })?;
643
644 let unit = captures.get(2).map(|m| m.as_str()).unwrap_or("B");
645 let multiplier = match unit {
646 "B" => 1u64,
647 "KB" => 1024,
648 "MB" => 1024 * 1024,
649 "GB" => 1024 * 1024 * 1024,
650 "TB" => 1024u64.pow(4),
651 _ => {
652 return Err(P2PError::Config(ConfigError::InvalidValue {
653 field: "size".to_string().into(),
654 reason: format!("Unknown unit: {}", unit).into(),
655 }));
656 }
657 };
658
659 Ok((value * multiplier as f64) as u64)
660 } else {
661 Err(P2PError::Config(ConfigError::InvalidValue {
662 field: "size".to_string().into(),
663 reason: format!("Invalid size format: {}", size).into(),
664 }))
665 }
666 })
667 }
668
669 pub fn storage_max_size_bytes(&self) -> Result<u64> {
671 Self::parse_size(&self.storage.max_size)
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678 use tempfile::NamedTempFile;
679
680 #[test]
681 fn test_default_config() {
682 let config = Config::default();
683 assert_eq!(config.network.listen_address, "0.0.0.0:9000");
684 assert_eq!(config.security.rate_limit, 1000);
685 assert!(config.security.encryption_enabled);
686 }
687
688 #[test]
689 fn test_development_config() {
690 let config = Config::development();
691 assert_eq!(config.network.listen_address, "127.0.0.1:9000");
692 assert_eq!(config.security.rate_limit, 10000);
693 }
694
695 #[test]
696 fn test_production_config() {
697 let config = Config::production();
698 assert_eq!(config.network.listen_address, "0.0.0.0:9000");
699 assert_eq!(config.transport.buffer_size, 131072);
700 }
701
702 #[test]
703 fn test_config_validation() {
704 let mut config = Config::default();
705 assert!(config.validate().is_ok());
706
707 config.network.listen_address = "invalid".to_string();
709 assert!(config.validate().is_err());
710
711 config.network.listen_address = "/ip4/127.0.0.1/tcp/9000".to_string();
713 assert!(config.validate().is_ok());
714 }
715
716 #[test]
717 fn test_save_and_load_config() {
718 let config = Config::development();
719 let file = NamedTempFile::new().unwrap();
720
721 config.save_to_file(file.path()).unwrap();
722
723 let loaded = Config::load_from_file(file.path()).unwrap();
724 assert_eq!(loaded.network.listen_address, config.network.listen_address);
725 }
726
727 #[test]
728 #[serial_test::serial]
729 #[allow(unsafe_code)] fn test_env_overrides() {
731 use std::sync::Mutex;
732
733 static ENV_MUTEX: Mutex<()> = Mutex::new(());
735 let _guard = ENV_MUTEX.lock().unwrap();
736
737 let orig_listen = env::var("SAORSA_LISTEN_ADDRESS").ok();
739 let orig_rate = env::var("SAORSA_RATE_LIMIT").ok();
740
741 unsafe {
743 env::set_var("SAORSA_LISTEN_ADDRESS", "127.0.0.1:8000");
744 env::set_var("SAORSA_RATE_LIMIT", "5000");
745 }
746
747 let config = Config::load().unwrap();
748 assert_eq!(config.network.listen_address, "127.0.0.1:8000");
749 assert_eq!(config.security.rate_limit, 5000);
750
751 unsafe {
753 match orig_listen {
754 Some(val) => env::set_var("SAORSA_LISTEN_ADDRESS", val),
755 None => env::remove_var("SAORSA_LISTEN_ADDRESS"),
756 }
757 match orig_rate {
758 Some(val) => env::set_var("SAORSA_RATE_LIMIT", val),
759 None => env::remove_var("SAORSA_RATE_LIMIT"),
760 }
761 }
762 }
763
764 #[test]
765 fn test_size_validation() {
766 let config = Config::default();
767 assert!(config.validate_size_format("10GB"));
768 assert!(config.validate_size_format("500MB"));
769 assert!(config.validate_size_format("1.5TB"));
770 assert!(!config.validate_size_format("10XB"));
771 assert!(!config.validate_size_format("invalid"));
772 }
773
774 #[test]
775 fn test_size_parsing() {
776 assert_eq!(Config::parse_size("10B").unwrap(), 10);
777 assert_eq!(Config::parse_size("1KB").unwrap(), 1024);
778 assert_eq!(Config::parse_size("5MB").unwrap(), 5 * 1024 * 1024);
779 assert_eq!(Config::parse_size("1GB").unwrap(), 1024 * 1024 * 1024);
780 assert_eq!(Config::parse_size("1.5GB").unwrap(), 1610612736);
781 assert_eq!(Config::parse_size("1TB").unwrap(), 1024u64.pow(4));
782
783 assert!(Config::parse_size("invalid").is_err());
785 assert!(Config::parse_size("10XB").is_err());
786 assert!(Config::parse_size("GB").is_err());
787 }
788}