//! [TSM.ID].[11031972] -- Platform X Ecosystem //! xcu-dns-resolver -- Secure DNS resolver with DoH and DoT #![deny(warnings)] #![allow(dead_code)] use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; #[derive(Debug)] pub enum XcuError { InitFailed(String), InvalidConfig(String), OperationFailed(String), ResourceExhausted, NotFound(String), Timeout, IoError(String), SecurityViolation(String), } impl std::fmt::Display for XcuError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InitFailed(e) => write!(f, "Init failed: {e}"), Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), Self::OperationFailed(e) => write!(f, "Op failed: {e}"), Self::ResourceExhausted => write!(f, "Resource exhausted"), Self::NotFound(e) => write!(f, "Not found: {e}"), Self::Timeout => write!(f, "Timeout"), Self::IoError(e) => write!(f, "IO error: {e}"), Self::SecurityViolation(e) => write!(f, "Security violation: {e}"), } } } impl std::error::Error for XcuError {} pub type Result = std::result::Result; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceConfig { pub name: String, pub version: String, pub params: HashMap, pub enabled: bool, } impl ServiceConfig { pub fn new(name: &str) -> Self { Self { name: name.to_string(), version: "0.1.0".to_string(), params: HashMap::new(), enabled: true } } pub fn param(&mut self, k: &str, v: &str) -> &mut Self { self.params.insert(k.to_string(), v.to_string()); self } pub fn get_param(&self, k: &str) -> Result<&str> { self.params.get(k).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(k.into())) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ServiceState { Created, Initializing, Ready, Running, Degraded, Stopping, Stopped, Failed(String) } pub struct Service { config: ServiceConfig, state: Arc>, counters: Arc>>, } impl Service { pub fn new(config: ServiceConfig) -> Result { if config.name.is_empty() { return Err(XcuError::InvalidConfig("empty name".into())); } Ok(Self { config, state: Arc::new(Mutex::new(ServiceState::Created)), counters: Arc::new(Mutex::new(HashMap::new())), }) } pub fn init(&self) -> Result<()> { let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; *s = ServiceState::Initializing; *s = ServiceState::Ready; Ok(()) } pub fn start(&self) -> Result<()> { let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; match &*s { ServiceState::Ready | ServiceState::Stopped => { *s = ServiceState::Running; Ok(()) } other => Err(XcuError::InvalidConfig(format!("Cannot start from {other:?}"))), } } pub fn stop(&self) -> Result<()> { let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; *s = ServiceState::Stopping; *s = ServiceState::Stopped; Ok(()) } pub fn state(&self) -> Result { Ok(self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?.clone()) } pub fn increment(&self, key: &str) -> Result { let mut c = self.counters.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; let v = c.entry(key.to_string()).or_insert(0); *v += 1; Ok(*v) } pub fn config(&self) -> &ServiceConfig { &self.config } pub fn name(&self) -> &str { &self.config.name } pub fn version(&self) -> &str { &self.config.version } } #[cfg(test)] mod tests { use super::*; #[test] fn test_service_lifecycle() { let s = Service::new(ServiceConfig::new("xcu-dns-resolver")).unwrap(); assert_eq!(s.state().unwrap(), ServiceState::Created); s.init().unwrap(); assert_eq!(s.state().unwrap(), ServiceState::Ready); s.start().unwrap(); assert_eq!(s.state().unwrap(), ServiceState::Running); s.stop().unwrap(); assert_eq!(s.state().unwrap(), ServiceState::Stopped); } #[test] fn test_counter() { let s = Service::new(ServiceConfig::new("xcu-dns-resolver")).unwrap(); assert_eq!(s.increment("ops").unwrap(), 1); assert_eq!(s.increment("ops").unwrap(), 2); } }