guard/src/config/mod.rs
loki5512344 15f474486a
feat!: universal redesign — drop Minecraft stack, single-crate architecture
- remove Java plugins (velocity/paper), dashboard, all MC-specific code
  (handshake, death_code, varint, hostname-HMAC); available in history pre-v0.2
- merge crates/* into one package with src/bin/{rampart,rampart-manager,rampart-cli}
- ProtocolHandler trait + registry (no implementations yet), universal PoW kept
- XDP: universal L3/L4 filter (xdp/core/) + pluggable hook API (xdp/hooks/),
  fix IPv6 saddr bug; clang build verified
- docs: bilingual knowledge base (docs/kb/: attacks x4, defense-levels,
  practice x3), rewrite README/architecture for universal concept
- TODO.md v4.0: <=300-line module limit, competitor benchmark section (ref/)
- deploy/CI/docs cleanup: no MC references, new binary names

cargo build/clippy(-D warnings)/test green (55 tests)
2026-08-24 01:50:22 +02:00

143 lines
3.8 KiB
Rust

//! Единый конфиг платформы Rampart.
mod sections;
pub use sections::{
BackendConfig, BanConfig, BindConfig, LimitsConfig, LoggingConfig, MetricsConfig, PowConfig, StoreConfig,
WorkerConfig, XdpConfig,
};
use serde::Deserialize;
use std::fs;
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Config {
#[serde(default)]
pub bind: BindConfig,
#[serde(default)]
pub backend: BackendConfig,
#[serde(default)]
pub workers: WorkerConfig,
#[serde(default)]
pub limits: LimitsConfig,
#[serde(default)]
pub ban: BanConfig,
#[serde(default)]
pub store: StoreConfig,
#[serde(default)]
pub xdp: XdpConfig,
#[serde(default)]
pub logging: LoggingConfig,
#[serde(default)]
pub metrics: MetricsConfig,
#[serde(default)]
pub pow: PowConfig,
#[serde(default)]
pub whitelist: Vec<String>,
}
impl Config {
/// Парсит конфиг из TOML-строки и валидирует семантику.
///
/// # Errors
/// Возвращает ошибку при невалидном TOML или нарушении инвариантов
/// (пустой список upstream-бэкендов, некорректные адреса).
pub fn parse_str(contents: &str) -> anyhow::Result<Self> {
let config: Config = toml::from_str(contents)?;
config.validate()?;
Ok(config)
}
/// Читает и парсит конфиг из файла.
///
/// # Errors
/// Возвращает ошибку при невозможности прочитать файл или невалидном содержимом.
pub fn from_file(path: &str) -> anyhow::Result<Self> {
let contents = fs::read_to_string(path)?;
Self::parse_str(&contents)
}
fn validate(&self) -> anyhow::Result<()> {
if self.backend.upstreams.is_empty() {
anyhow::bail!("backend.upstreams must not be empty");
}
for upstream in &self.backend.upstreams {
if upstream.parse::<std::net::SocketAddr>().is_err() {
anyhow::bail!("invalid upstream address: {upstream}");
}
}
for entry in &self.whitelist {
if entry.parse::<std::net::IpAddr>().is_err() {
anyhow::bail!("invalid whitelist entry: {entry}");
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_config_uses_defaults() {
let config = Config::parse_str("").expect("empty config should parse");
assert_eq!(config.bind.address, "0.0.0.0");
assert_eq!(config.bind.port, 25565);
assert_eq!(config.backend.upstreams, vec!["127.0.0.1:25566"]);
assert!(!config.pow.enabled);
assert!(!config.xdp.enabled);
}
#[test]
fn full_config_parses() {
let config = Config::parse_str(
r#"
[bind]
address = "10.0.0.1"
port = 443
[backend]
upstreams = ["192.168.1.10:8080", "192.168.1.11:8080"]
[pow]
enabled = true
difficulty = 6
whitelist = ["203.0.113.7"]
"#,
)
.expect("config should parse");
assert_eq!(config.bind.port, 443);
assert_eq!(config.backend.upstreams.len(), 2);
assert!(config.pow.enabled);
assert_eq!(config.pow.difficulty, 6);
}
#[test]
fn empty_upstreams_rejected() {
let result = Config::parse_str(
r#"
[backend]
upstreams = []
"#,
);
assert!(result.is_err());
}
#[test]
fn invalid_upstream_rejected() {
let result = Config::parse_str(
r#"
[backend]
upstreams = ["not-an-addr"]
"#,
);
assert!(result.is_err());
}
#[test]
fn invalid_whitelist_entry_rejected() {
let result = Config::parse_str("whitelist = [\"999.999.1.1\"]");
assert!(result.is_err());
}
}