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: "0.0.0.0:9000".to_string(),
181 public_address: None,
182 ipv6_enabled: true,
183 max_connections: 10000,
184 connection_timeout: 30,
185 keepalive_interval: 60,
186 }
187 }
188}
189
190impl Default for SecurityConfig {
191 fn default() -> Self {
192 Self {
193 rate_limit: 1000,
194 connection_limit: 100,
195 encryption_enabled: true,
196 min_tls_version: "1.3".to_string(),
197 identity_security_level: "High".to_string(),
198 }
199 }
200}
201
202impl Default for StorageConfig {
203 fn default() -> Self {
204 Self {
205 path: PathBuf::from("./data"),
206 max_size: "10GB".to_string(),
207 cache_size: 256,
208 compression_enabled: true,
209 }
210 }
211}
212
213impl Default for McpConfig {
214 fn default() -> Self {
215 Self {
216 enabled: true,
217 port: 9001,
218 max_execution_time: 300,
219 monitoring_enabled: true,
220 }
221 }
222}
223
224impl Default for DhtConfig {
225 fn default() -> Self {
226 Self {
227 replication_factor: 8,
228 alpha: 3,
229 beta: 1,
230 record_ttl: 3600,
231 adaptive_routing: true,
232 }
233 }
234}
235
236impl Default for TransportConfig {
237 fn default() -> Self {
238 Self {
239 protocol: "quic".to_string(),
240 quic_enabled: true,
241 tcp_enabled: true,
242 webrtc_enabled: false,
243 buffer_size: 65536,
244 server_name: "p2p.local".to_string(),
245 }
246 }
247}
248
249impl Default for IdentityConfig {
250 fn default() -> Self {
251 Self {
252 derivation_path: "m/44'/0'/0'/0/0".to_string(),
253 rotation_interval: 90,
254 backup_enabled: true,
255 backup_interval: 24,
256 }
257 }
258}
259
260impl Config {
261 pub fn load() -> Result<Self> {
280 Self::load_with_path::<&str>(None)
281 }
282
283 pub fn load_with_path<P: AsRef<Path>>(path: Option<P>) -> Result<Self> {
299 let mut config = Self::default();
301
302 if let Some(path) = path {
304 config = Self::load_from_file(path)?;
305 } else {
306 for location in &["saorsa.toml", "config.toml", "/etc/saorsa/config.toml"] {
308 if Path::new(location).exists() {
309 info!("Loading config from: {}", location);
310 config = Self::load_from_file(location)?;
311 break;
312 }
313 }
314 }
315
316 config.apply_env_overrides()?;
318
319 config.validate()?;
321
322 Ok(config)
323 }
324
325 pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
327 let content = fs::read_to_string(&path).map_err(|e| {
328 P2PError::Config(ConfigError::IoError {
329 path: path.as_ref().to_string_lossy().to_string().into(),
330 source: e,
331 })
332 })?;
333
334 toml::from_str(&content)
335 .map_err(|e| P2PError::Config(ConfigError::ParseError(e.to_string().into())))
336 }
337
338 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
340 let content = toml::to_string_pretty(self)
341 .map_err(|e| P2PError::Config(ConfigError::ParseError(e.to_string().into())))?;
342
343 fs::write(&path, content).map_err(|e| {
344 P2PError::Config(ConfigError::IoError {
345 path: path.as_ref().to_string_lossy().to_string().into(),
346 source: e,
347 })
348 })?;
349
350 Ok(())
351 }
352
353 fn apply_env_overrides(&mut self) -> Result<()> {
355 if let Ok(val) = env::var("SAORSA_LISTEN_ADDRESS") {
357 self.network.listen_address = val;
358 }
359 if let Ok(val) = env::var("SAORSA_PUBLIC_ADDRESS") {
360 self.network.public_address = Some(val);
361 }
362 if let Ok(val) = env::var("SAORSA_BOOTSTRAP_NODES") {
363 self.network.bootstrap_nodes = val.split(',').map(String::from).collect();
364 }
365 if let Ok(val) = env::var("SAORSA_MAX_CONNECTIONS") {
366 self.network.max_connections = val.parse().map_err(|_| {
367 P2PError::Config(ConfigError::InvalidValue {
368 field: "max_connections".to_string().into(),
369 reason: "Invalid value".to_string().into(),
370 })
371 })?;
372 }
373
374 if let Ok(val) = env::var("SAORSA_RATE_LIMIT") {
376 self.security.rate_limit = val.parse().map_err(|_| {
377 P2PError::Config(ConfigError::InvalidValue {
378 field: "rate_limit".to_string().into(),
379 reason: "Invalid value".to_string().into(),
380 })
381 })?;
382 }
383 if let Ok(val) = env::var("SAORSA_ENCRYPTION_ENABLED") {
384 self.security.encryption_enabled = val.parse().map_err(|_| {
385 P2PError::Config(ConfigError::InvalidValue {
386 field: "encryption_enabled".to_string().into(),
387 reason: "Invalid value".to_string().into(),
388 })
389 })?;
390 }
391
392 if let Ok(val) = env::var("SAORSA_DATA_PATH") {
394 self.storage.path = PathBuf::from(val);
395 }
396 if let Ok(val) = env::var("SAORSA_MAX_STORAGE") {
397 self.storage.max_size = val;
398 }
399
400 if let Ok(val) = env::var("SAORSA_MCP_ENABLED") {
402 self.mcp.enabled = val.parse().map_err(|_| {
403 P2PError::Config(ConfigError::InvalidValue {
404 field: "mcp_enabled".to_string().into(),
405 reason: "Invalid value".to_string().into(),
406 })
407 })?;
408 }
409 if let Ok(val) = env::var("SAORSA_MCP_PORT") {
410 self.mcp.port = val.parse().map_err(|_| {
411 P2PError::Config(ConfigError::InvalidValue {
412 field: "mcp_port".to_string().into(),
413 reason: "Invalid value".to_string().into(),
414 })
415 })?;
416 }
417
418 Ok(())
419 }
420
421 pub fn validate(&self) -> Result<()> {
423 let mut errors = Vec::new();
424
425 if let Err(e) = self.validate_address(&self.network.listen_address, "listen_address") {
427 errors.push(e);
428 }
429
430 if let Some(addr) = &self.network.public_address
431 && let Err(e) = self.validate_address(addr, "public_address")
432 {
433 errors.push(e);
434 }
435
436 for (i, node) in self.network.bootstrap_nodes.iter().enumerate() {
437 if let Err(e) = self.validate_address(node, &format!("bootstrap_node[{}]", i)) {
438 errors.push(e);
439 }
440 }
441
442 if let Err(e) = validate_config_value(
444 &self.network.max_connections.to_string(),
445 Some(1_usize),
446 Some(100000_usize),
447 ) {
448 errors.push(P2PError::Config(ConfigError::InvalidValue {
449 field: "max_connections".to_string().into(),
450 reason: e.to_string().into(),
451 }));
452 }
453
454 if let Err(e) = validate_config_value(
455 &self.security.rate_limit.to_string(),
456 Some(1_u32),
457 Some(1000000_u32),
458 ) {
459 errors.push(P2PError::Config(ConfigError::InvalidValue {
460 field: "rate_limit".to_string().into(),
461 reason: e.to_string().into(),
462 }));
463 }
464
465 if let Err(e) = validate_file_path(&self.storage.path) {
467 errors.push(P2PError::Config(ConfigError::InvalidValue {
468 field: "storage.path".to_string().into(),
469 reason: e.to_string().into(),
470 }));
471 }
472
473 if !self.validate_size_format(&self.storage.max_size) {
475 errors.push(P2PError::Config(ConfigError::InvalidValue {
476 field: "max_size".to_string().into(),
477 reason: format!("Invalid storage size format: {}", self.storage.max_size).into(),
478 }));
479 }
480
481 match self.transport.protocol.as_str() {
483 "quic" | "tcp" | "webrtc" => {}
484 _ => errors.push(P2PError::Config(ConfigError::InvalidValue {
485 field: "protocol".to_string().into(),
486 reason: format!("Invalid transport protocol: {}", self.transport.protocol).into(),
487 })),
488 }
489
490 if errors.is_empty() {
491 Ok(())
492 } else {
493 Err(errors.into_iter().next().unwrap_or_else(|| {
495 P2PError::Config(ConfigError::InvalidValue {
496 field: "unknown".to_string().into(),
497 reason: "Validation failed with unknown error".to_string().into(),
498 })
499 }))
500 }
501 }
502
503 fn validate_address(&self, addr: &str, field: &str) -> Result<()> {
505 if let Ok(socket_addr) = SocketAddr::from_str(addr) {
507 let ctx = ValidationContext::default()
509 .allow_localhost() .allow_private_ips(); return validate_network_address(&socket_addr, &ctx).map_err(|e| {
513 P2PError::Config(ConfigError::InvalidValue {
514 field: field.to_string().into(),
515 reason: e.to_string().into(),
516 })
517 });
518 }
519
520 if let Ok(network_addr) = crate::NetworkAddress::from_four_words(addr) {
522 let ctx = ValidationContext::default()
524 .allow_localhost()
525 .allow_private_ips();
526
527 return validate_network_address(&network_addr.socket_addr(), &ctx).map_err(|e| {
528 P2PError::Config(ConfigError::InvalidValue {
529 field: field.to_string().into(),
530 reason: e.to_string().into(),
531 })
532 });
533 }
534
535 if addr.starts_with("/ip4/") || addr.starts_with("/ip6/") {
537 return Ok(());
539 }
540
541 Err(P2PError::Config(ConfigError::InvalidValue {
542 field: field.to_string().into(),
543 reason: format!("Invalid address format: {}", addr).into(),
544 }))
545 }
546
547 fn validate_size_format(&self, size: &str) -> bool {
549 thread_local! {
550 static SIZE_REGEX: std::result::Result<Regex, P2PError> = Regex::new(r"^\\d+(?:\\.\\d+)?\\s*(?:B|KB|MB|GB|TB)$")
551 .map_err(|e| P2PError::Config(ConfigError::InvalidValue { field: "size".to_string().into(), reason: e.to_string().into() }));
552 }
553 SIZE_REGEX.with(|re| re.as_ref().ok().map(|r| r.is_match(size)).unwrap_or(false))
554 }
555
556 pub fn development() -> Self {
558 let mut config = Self::default();
559 config.network.listen_address = "127.0.0.1:9000".to_string();
560 config.security.rate_limit = 10000;
561 config.security.connection_limit = 1000;
562 config.storage.path = PathBuf::from("./dev-data");
563 config
564 }
565
566 pub fn production() -> Self {
568 let mut config = Self::default();
569 config.network.listen_address = "0.0.0.0:9000".to_string();
570 config.security.min_tls_version = "1.3".to_string();
571 config.security.encryption_enabled = true;
572 config.storage.compression_enabled = true;
573 config.transport.buffer_size = 131072; config
575 }
576
577 pub fn listen_socket_addr(&self) -> Result<SocketAddr> {
579 SocketAddr::from_str(&self.network.listen_address).map_err(|e| {
580 P2PError::Config(ConfigError::InvalidValue {
581 field: "listen_address".to_string().into(),
582 reason: format!("Invalid address: {}", e).into(),
583 })
584 })
585 }
586
587 pub fn bootstrap_addrs(&self) -> Result<Vec<NetworkAddress>> {
589 self.network
590 .bootstrap_nodes
591 .iter()
592 .map(|addr| {
593 addr.parse::<NetworkAddress>().map_err(|e| {
594 P2PError::Config(ConfigError::InvalidValue {
595 field: "bootstrap_nodes".to_string().into(),
596 reason: format!("Invalid address: {}", e).into(),
597 })
598 })
599 })
600 .collect()
601 }
602
603 pub fn parse_size(size: &str) -> Result<u64> {
616 thread_local! {
617 static SIZE_REGEX: std::result::Result<Regex, P2PError> = Regex::new(r"^(\\d+(?:\\.\\d+)?)\\s*(B|KB|MB|GB|TB)$")
618 .map_err(|e| P2PError::Config(ConfigError::InvalidValue { field: "size".to_string().into(), reason: e.to_string().into() }));
619 }
620
621 SIZE_REGEX.with(|re| -> Result<u64> {
622 let re = match re {
623 Ok(r) => r,
624 Err(e) => {
625 return Err(P2PError::Config(ConfigError::InvalidValue {
626 field: "size".to_string().into(),
627 reason: e.to_string().into(),
628 }));
629 }
630 };
631 if let Some(captures) = re.captures(size) {
632 let value: f64 = captures
633 .get(1)
634 .and_then(|m| m.as_str().parse().ok())
635 .ok_or_else(|| {
636 P2PError::Config(ConfigError::InvalidValue {
637 field: "size".to_string().into(),
638 reason: "Invalid numeric value".to_string().into(),
639 })
640 })?;
641
642 let unit = captures.get(2).map(|m| m.as_str()).unwrap_or("B");
643 let multiplier = match unit {
644 "B" => 1u64,
645 "KB" => 1024,
646 "MB" => 1024 * 1024,
647 "GB" => 1024 * 1024 * 1024,
648 "TB" => 1024u64.pow(4),
649 _ => {
650 return Err(P2PError::Config(ConfigError::InvalidValue {
651 field: "size".to_string().into(),
652 reason: format!("Unknown unit: {}", unit).into(),
653 }));
654 }
655 };
656
657 Ok((value * multiplier as f64) as u64)
658 } else {
659 Err(P2PError::Config(ConfigError::InvalidValue {
660 field: "size".to_string().into(),
661 reason: format!("Invalid size format: {}", size).into(),
662 }))
663 }
664 })
665 }
666
667 pub fn storage_max_size_bytes(&self) -> Result<u64> {
669 Self::parse_size(&self.storage.max_size)
670 }
671}
672
673#[cfg(test)]
674mod tests {
675 use super::*;
676 use tempfile::NamedTempFile;
677
678 #[test]
679 fn test_default_config() {
680 let config = Config::default();
681 assert_eq!(config.network.listen_address, "0.0.0.0:9000");
682 assert_eq!(config.security.rate_limit, 1000);
683 assert!(config.security.encryption_enabled);
684 }
685
686 #[test]
687 fn test_development_config() {
688 let config = Config::development();
689 assert_eq!(config.network.listen_address, "127.0.0.1:9000");
690 assert_eq!(config.security.rate_limit, 10000);
691 }
692
693 #[test]
694 fn test_production_config() {
695 let config = Config::production();
696 assert_eq!(config.network.listen_address, "0.0.0.0:9000");
697 assert_eq!(config.transport.buffer_size, 131072);
698 }
699
700 #[test]
701 fn test_config_validation() {
702 let mut config = Config::default();
703 assert!(config.validate().is_ok());
704
705 config.network.listen_address = "invalid".to_string();
707 assert!(config.validate().is_err());
708
709 config.network.listen_address = "/ip4/127.0.0.1/tcp/9000".to_string();
711 assert!(config.validate().is_ok());
712 }
713
714 #[test]
715 fn test_save_and_load_config() {
716 let config = Config::development();
717 let file = NamedTempFile::new().unwrap();
718
719 config.save_to_file(file.path()).unwrap();
720
721 let loaded = Config::load_from_file(file.path()).unwrap();
722 assert_eq!(loaded.network.listen_address, config.network.listen_address);
723 }
724
725 #[test]
726 #[serial_test::serial]
727 #[allow(unsafe_code)] fn test_env_overrides() {
729 use std::sync::Mutex;
730
731 static ENV_MUTEX: Mutex<()> = Mutex::new(());
733 let _guard = ENV_MUTEX.lock().unwrap();
734
735 let orig_listen = env::var("SAORSA_LISTEN_ADDRESS").ok();
737 let orig_rate = env::var("SAORSA_RATE_LIMIT").ok();
738
739 unsafe {
741 env::set_var("SAORSA_LISTEN_ADDRESS", "127.0.0.1:8000");
742 env::set_var("SAORSA_RATE_LIMIT", "5000");
743 }
744
745 let config = Config::load().unwrap();
746 assert_eq!(config.network.listen_address, "127.0.0.1:8000");
747 assert_eq!(config.security.rate_limit, 5000);
748
749 unsafe {
751 match orig_listen {
752 Some(val) => env::set_var("SAORSA_LISTEN_ADDRESS", val),
753 None => env::remove_var("SAORSA_LISTEN_ADDRESS"),
754 }
755 match orig_rate {
756 Some(val) => env::set_var("SAORSA_RATE_LIMIT", val),
757 None => env::remove_var("SAORSA_RATE_LIMIT"),
758 }
759 }
760 }
761
762 #[test]
763 fn test_size_validation() {
764 let config = Config::default();
765 assert!(config.validate_size_format("10GB"));
766 assert!(config.validate_size_format("500MB"));
767 assert!(config.validate_size_format("1.5TB"));
768 assert!(!config.validate_size_format("10XB"));
769 assert!(!config.validate_size_format("invalid"));
770 }
771
772 #[test]
773 fn test_size_parsing() {
774 assert_eq!(Config::parse_size("10B").unwrap(), 10);
775 assert_eq!(Config::parse_size("1KB").unwrap(), 1024);
776 assert_eq!(Config::parse_size("5MB").unwrap(), 5 * 1024 * 1024);
777 assert_eq!(Config::parse_size("1GB").unwrap(), 1024 * 1024 * 1024);
778 assert_eq!(Config::parse_size("1.5GB").unwrap(), 1610612736);
779 assert_eq!(Config::parse_size("1TB").unwrap(), 1024u64.pow(4));
780
781 assert!(Config::parse_size("invalid").is_err());
783 assert!(Config::parse_size("10XB").is_err());
784 assert!(Config::parse_size("GB").is_err());
785 }
786}