From cb8212daa60d4df74a7e9b2eba7a38f49db53db9 Mon Sep 17 00:00:00 2001 From: pushkar-gr Date: Sun, 11 Jan 2026 19:31:38 +0530 Subject: [PATCH] feat(config): read config from a file, command line arguments --- Cargo.lock | 209 ++++++++++++++++++++++++++++ Cargo.toml | 11 +- README.md | 73 +++++++++- config.toml | 38 +++++ src/config.rs | 375 +++++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 2 +- 6 files changed, 697 insertions(+), 11 deletions(-) create mode 100644 config.toml diff --git a/Cargo.lock b/Cargo.lock index 4e7a3b0..539efb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "axum", "bincode", "chacha20poly1305", + "clap", "futures", "hkdf", "rand_core 0.6.4", @@ -21,6 +22,7 @@ dependencies = [ "tokio", "tokio-stream", "tokio_kcp", + "toml", "tower", "tower-http", "tracing", @@ -63,6 +65,56 @@ dependencies = [ "subtle", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + [[package]] name = "anyhow" version = "1.0.100" @@ -243,6 +295,52 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clap" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -335,6 +433,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "fiat-crypto" version = "0.2.9" @@ -488,6 +592,18 @@ dependencies = [ "polyval", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hkdf" version = "0.12.4" @@ -696,6 +812,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.4" @@ -711,6 +837,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itoa" version = "1.0.16" @@ -847,6 +979,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -1128,6 +1266,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1212,6 +1359,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "stun" version = "0.9.0" @@ -1373,6 +1526,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -1537,6 +1731,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "valuable" version = "0.1.1" @@ -1768,6 +1968,15 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 96e0773..b144579 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,14 +6,14 @@ edition = "2024" [dependencies] tokio = { version = "1", features = ["full"] } anyhow = "1.0" -stun = "0.9" +stun = "0.9" tracing = "0.1" tracing-subscriber = "0.3" -axum = "0.8.7" +axum = "0.8.7" tower = "0.5.2" tower-http = { version = "0.6.8", features = ["fs", "cors"] } serde_json = "1" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive"] } bincode = "1.3" futures = "0.3" tokio-stream = { version = "0.1.17", features = ["sync"] } @@ -23,4 +23,7 @@ aes-gcm = "0.10" x25519-dalek = { version = "2.0", features = ["static_secrets", "getrandom"] } rand_core = { version = "0.6", features = ["std"] } sha2 = "0.10" -hkdf = "0.12" \ No newline at end of file +hkdf = "0.12" +toml = "0.8" +clap = { version = "4.5", features = ["derive"] } + diff --git a/README.md b/README.md index 2129eb6..a69b188 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ GhostLink is a decentralized chat app. It connects users directly without using | Version | Status | Description | |---------|--------------|---------------------------------------------------------------------------------------------------------------| -| v1.0 | **Legacy** | First release. Plaintext messaging with reliable UDP transport. | +| v1.0 | **Legacy** | First release. Plaintext messaging with reliable UDP transport. | | v1.1 | **Stable** | **Added Security**. End-to-End Encryption (E2EE) using X25519 and ChaCha20-Poly1305 encryption. | --- @@ -60,6 +60,77 @@ Steps: --- + +## ⚙️ Configuration + +GhostLink can be configured using a configuration file, command-line arguments, or both. Configuration is applied in the following order of precedence (highest to lowest): + +1. **Command-line arguments** (highest priority) +2. **Configuration file** (`config.toml`) +3. **Built-in defaults** (lowest priority) + +### Configuration File + +```toml +# UDP port for client connections (0 = auto-assign) +client_port = 0 + +# STUN server for NAT traversal +stun_server = "stun.l.google.com:19302" + +# STUN verifier for NAT type detection +stun_verifier = "stun4.l.google.com:19302" + +# HTTP web server port +web_port = 8080 + +# Handshake timeout in seconds +handshake_timeout_secs = 30 + +# NAT keep-alive interval in seconds +punch_hole_secs = 15 + +# Disconnect timeout in milliseconds +disconnect_timeout_ms = 500 + +# Encryption mode: "chacha20poly1305" or "aes256gcm" +encryption_mode = "chacha20poly1305" +``` + +### Command-Line Arguments + +You can override any configuration option using command-line arguments: + +```bash +# Show all available options +cargo run --release -- --help + +# Use a custom configuration file +cargo run --release -- --config /path/to/config.toml + +# Override specific settings +cargo run --release -- --web-port 9000 --client-port 5000 + +# Combine config file with overrides +cargo run --release -- --config myconfig.toml --encryption-mode aes256gcm +``` + +### Available Options + +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--config` | `-c` | Path to configuration file | `config.toml` | +| `--client-port` | `-p` | UDP port for client connections (0 = auto) | `0` | +| `--stun-server` | `-s` | STUN server address | `stun.l.google.com:19302` | +| `--stun-verifier` | `-v` | STUN verifier address | `stun4.l.google.com:19302` | +| `--web-port` | `-w` | HTTP web server port | `8080` | +| `--handshake-timeout-secs` | `-t` | Handshake timeout in seconds | `30` | +| `--punch-hole-secs` | `-k` | NAT keep-alive interval in seconds | `15` | +| `--disconnect-timeout-ms` | `-d` | Disconnect timeout in milliseconds | `500` | +| `--encryption-mode` | `-e` | Encryption mode (chacha20poly1305 or aes256gcm) | `chacha20poly1305` | + +--- + ## 🛠️ Technology | Component | Technology | Purpose | diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..1c251b1 --- /dev/null +++ b/config.toml @@ -0,0 +1,38 @@ +# GhostLink Configuration File +# +# All fields are optional - if not specified, default values will be used. +# Command-line arguments will override values specified in this file. + +# UDP port for client connections (0 = auto-assign a random port) +# Default: 0 +client_port = 0 + +# STUN server address for NAT traversal and public IP resolution +# Default: "stun.l.google.com:19302" +stun_server = "stun.l.google.com:19302" + +# STUN verifier address for NAT type detection +# Default: "stun4.l.google.com:19302" +stun_verifier = "stun4.l.google.com:19302" + +# HTTP web server port for the web interface +# Default: 8080 +web_port = 8080 + +# Handshake timeout in seconds +# Default: 30 +handshake_timeout_secs = 30 + +# NAT keep-alive interval in seconds +# Periodic STUN requests to maintain NAT mapping +# Default: 15 +punch_hole_secs = 15 + +# Disconnect timeout in milliseconds +# Grace period before forced shutdown +# Default: 500 +disconnect_timeout_ms = 500 + +# Encryption mode: "chacha20poly1305" or "aes256gcm" +# Default: "chacha20poly1305" +encryption_mode = "chacha20poly1305" diff --git a/src/config.rs b/src/config.rs index 091d738..e3ebecb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,34 +1,399 @@ +//! Configuration system for GhostLink. +//! +//! Supports three-tier configuration precedence: +//! 1. Built-in defaults (lowest priority) +//! 2. TOML configuration file (medium priority) +//! 3. Command-line arguments (highest priority) +//! +//! ## Example +//! +//! ```toml +//! # config.toml +//! web_port = 9000 +//! encryption_mode = "aes256gcm" +//! ``` +//! +//! ```bash +//! # CLI overrides config file +//! ghostlink --config config.toml --web-port 7777 +//! ``` + +use anyhow::{Context, Result}; +use clap::Parser; use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::PathBuf; +use std::str::FromStr; + +/// Default configuration file name. +const DEFAULT_CONFIG_FILE: &str = "config.toml"; +/// Encryption algorithm for secure P2P communication. +/// +/// Supports two AEAD ciphers: +/// - ChaCha20-Poly1305: Fast software implementation, default choice +/// - AES-256-GCM: Hardware-accelerated on most modern CPUs #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum EncryptionMode { + /// ChaCha20-Poly1305 authenticated encryption. ChaCha20Poly1305, + /// AES-256-GCM authenticated encryption. Aes256Gcm, } -#[derive(Debug, Clone)] +impl FromStr for EncryptionMode { + type Err = anyhow::Error; + + /// Parses encryption mode from string (case-insensitive). + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// # use config::EncryptionMode; + /// let mode = EncryptionMode::from_str("chacha20poly1305").unwrap(); + /// let mode_upper = EncryptionMode::from_str("AES256GCM").unwrap(); + /// ``` + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "chacha20poly1305" => Ok(EncryptionMode::ChaCha20Poly1305), + "aes256gcm" => Ok(EncryptionMode::Aes256Gcm), + _ => anyhow::bail!( + "Invalid encryption mode: '{}'. Valid options are 'chacha20poly1305' or 'aes256gcm'", + s + ), + } + } +} + +/// Application configuration. +/// +/// All fields have default values and can be overridden via: +/// - TOML configuration file +/// - Command-line arguments +/// +/// Partial TOML files are supported; unspecified fields use defaults. +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Config { + /// UDP port for client connections (0 = auto-assign). + #[serde(default = "default_client_port")] pub client_port: u16, + + /// STUN server address for NAT traversal and public IP resolution. + #[serde(default = "default_stun_server")] pub stun_server: String, + + /// STUN verifier address for NAT type detection. + #[serde(default = "default_stun_verifier")] pub stun_verifier: String, + + /// HTTP web server port for the web interface. + #[serde(default = "default_web_port")] pub web_port: u16, + + /// Handshake timeout in seconds. + #[serde(default = "default_handshake_timeout_secs")] pub handshake_timeout_secs: u64, + + /// NAT keep-alive interval in seconds. + /// + /// Periodic STUN requests maintain the NAT mapping. + #[serde(default = "default_punch_hole_secs")] pub punch_hole_secs: u64, + + /// Disconnect timeout in milliseconds. + /// + /// Grace period before forced shutdown on disconnect. + #[serde(default = "default_disconnect_timeout_ms")] pub disconnect_timeout_ms: u64, + + /// Encryption algorithm for secure communication. + #[serde(default = "default_encryption_mode")] pub encryption_mode: EncryptionMode, } +// Default value functions for serde deserialization. +// These provide fallback values when fields are missing from TOML files. + +/// Default client port: 0 (auto-assign). +fn default_client_port() -> u16 { + 0 +} + +/// Default STUN server for NAT traversal. +fn default_stun_server() -> String { + "stun.l.google.com:19302".to_string() +} + +/// Default STUN verifier for NAT type detection. +fn default_stun_verifier() -> String { + "stun4.l.google.com:19302".to_string() +} + +/// Default web server port. +fn default_web_port() -> u16 { + 8080 +} + +/// Default handshake timeout: 30 seconds. +fn default_handshake_timeout_secs() -> u64 { + 30 +} + +/// Default NAT keep-alive interval: 15 seconds. +fn default_punch_hole_secs() -> u64 { + 15 +} + +/// Default disconnect timeout: 500 milliseconds. +fn default_disconnect_timeout_ms() -> u64 { + 500 +} + +/// Default encryption mode: ChaCha20-Poly1305. +fn default_encryption_mode() -> EncryptionMode { + EncryptionMode::ChaCha20Poly1305 +} + +/// Command-line arguments parser. +/// +/// All arguments are optional; unspecified values use config file or defaults. +#[derive(Parser, Debug)] +#[command(name = "GhostLink")] +#[command(author, version, about = "High-Performance Serverless P2P Messaging", long_about = None)] +pub struct CliArgs { + /// Path to configuration file + #[arg(short, long, value_name = "FILE")] + pub config: Option, + + /// UDP port for client connections (0 = auto-assign) + #[arg(short = 'p', long)] + pub client_port: Option, + + /// STUN server address for NAT traversal + #[arg(short = 's', long)] + pub stun_server: Option, + + /// STUN verifier address for NAT type detection + #[arg(short = 'v', long)] + pub stun_verifier: Option, + + /// HTTP web server port + #[arg(short = 'w', long)] + pub web_port: Option, + + /// Handshake timeout in seconds + #[arg(short = 't', long)] + pub handshake_timeout_secs: Option, + + /// NAT keep-alive interval in seconds + #[arg(short = 'k', long)] + pub punch_hole_secs: Option, + + /// Disconnect timeout in milliseconds + #[arg(short = 'd', long)] + pub disconnect_timeout_ms: Option, + + /// Encryption mode (chacha20poly1305 or aes256gcm) + #[arg(short = 'e', long)] + pub encryption_mode: Option, +} + impl Config { - pub fn load() -> Self { + /// Loads configuration with three-tier precedence. + /// + /// Priority (highest to lowest): + /// 1. Command-line arguments + /// 2. Configuration file (config.toml or --config path) + /// 3. Built-in defaults + /// + /// # Examples + /// + /// ```no_run + /// # use config::Config; + /// let config = Config::load().unwrap(); + /// println!("Web server port: {}", config.web_port); + /// ``` + /// + /// # Errors + /// + /// Returns error if: + /// - Config file exists but cannot be read + /// - Config file has invalid TOML syntax + /// - Encryption mode is invalid + pub fn load() -> Result { + let cli_args = CliArgs::parse(); + + // Start with built-in defaults + let mut config = Self::default_config(); + + // Layer 2: Load from config file if it exists + let config_path = cli_args + .config + .unwrap_or_else(|| PathBuf::from(DEFAULT_CONFIG_FILE)); + + if config_path.exists() { + let contents = fs::read_to_string(&config_path) + .with_context(|| format!("Failed to read config file: {:?}", config_path))?; + config = toml::from_str(&contents) + .with_context(|| format!("Failed to parse config file: {:?}", config_path))?; + } + + // Layer 3: Override with CLI arguments (highest priority) + if let Some(port) = cli_args.client_port { + config.client_port = port; + } + if let Some(stun) = cli_args.stun_server { + config.stun_server = stun; + } + if let Some(verifier) = cli_args.stun_verifier { + config.stun_verifier = verifier; + } + if let Some(web) = cli_args.web_port { + config.web_port = web; + } + if let Some(timeout) = cli_args.handshake_timeout_secs { + config.handshake_timeout_secs = timeout; + } + if let Some(punch) = cli_args.punch_hole_secs { + config.punch_hole_secs = punch; + } + if let Some(disconnect) = cli_args.disconnect_timeout_ms { + config.disconnect_timeout_ms = disconnect; + } + if let Some(mode_str) = cli_args.encryption_mode { + config.encryption_mode = mode_str.parse()?; + } + + Ok(config) + } + + /// Creates configuration with all default values. + fn default_config() -> Self { Self { + client_port: default_client_port(), + stun_server: default_stun_server(), + stun_verifier: default_stun_verifier(), + web_port: default_web_port(), + handshake_timeout_secs: default_handshake_timeout_secs(), + punch_hole_secs: default_punch_hole_secs(), + disconnect_timeout_ms: default_disconnect_timeout_ms(), + encryption_mode: default_encryption_mode(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_default_config() { + let config = Config::default_config(); + assert_eq!(config.client_port, 0); + assert_eq!(config.stun_server, "stun.l.google.com:19302"); + assert_eq!(config.stun_verifier, "stun4.l.google.com:19302"); + assert_eq!(config.web_port, 8080); + assert_eq!(config.handshake_timeout_secs, 30); + assert_eq!(config.punch_hole_secs, 15); + assert_eq!(config.disconnect_timeout_ms, 500); + assert_eq!(config.encryption_mode, EncryptionMode::ChaCha20Poly1305); + } + + #[test] + fn test_toml_deserialization() { + let toml_str = r#" + client_port = 3000 + stun_server = "stun.example.com:19302" + web_port = 9090 + encryption_mode = "aes256gcm" + "#; + + let config: Config = toml::from_str(toml_str).unwrap(); + assert_eq!(config.client_port, 3000); + assert_eq!(config.stun_server, "stun.example.com:19302"); + assert_eq!(config.web_port, 9090); + assert_eq!(config.encryption_mode, EncryptionMode::Aes256Gcm); + // Should have defaults for unspecified fields + assert_eq!(config.stun_verifier, "stun4.l.google.com:19302"); + } + + #[test] + fn test_encryption_mode_serialization() { + let config = Config { client_port: 0, - stun_server: "stun.l.google.com:19302".to_string(), - stun_verifier: "stun4.l.google.com:19302".to_string(), + stun_server: "stun.example.com:19302".to_string(), + stun_verifier: "stun.example.com:19303".to_string(), web_port: 8080, handshake_timeout_secs: 30, punch_hole_secs: 15, disconnect_timeout_ms: 500, encryption_mode: EncryptionMode::ChaCha20Poly1305, - } + }; + + let toml_str = toml::to_string(&config).unwrap(); + assert!(toml_str.contains("chacha20poly1305")); + + let config2 = Config { + encryption_mode: EncryptionMode::Aes256Gcm, + ..config + }; + + let toml_str2 = toml::to_string(&config2).unwrap(); + assert!(toml_str2.contains("aes256gcm")); + } + + #[test] + fn test_encryption_mode_from_str() { + // Test valid values + assert_eq!( + "chacha20poly1305".parse::().unwrap(), + EncryptionMode::ChaCha20Poly1305 + ); + assert_eq!( + "ChaCha20Poly1305".parse::().unwrap(), + EncryptionMode::ChaCha20Poly1305 + ); + assert_eq!( + "aes256gcm".parse::().unwrap(), + EncryptionMode::Aes256Gcm + ); + assert_eq!( + "AES256GCM".parse::().unwrap(), + EncryptionMode::Aes256Gcm + ); + + // Test invalid value + let result = "invalid_mode".parse::(); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("Invalid encryption mode") + ); + } + + #[test] + fn test_partial_toml_uses_defaults() { + // Test that unspecified fields in TOML use default values + let toml_str = r#" + web_port = 9999 + "#; + + let config: Config = toml::from_str(toml_str).unwrap(); + + // Specified field should use TOML value + assert_eq!(config.web_port, 9999); + + // Unspecified fields should use defaults + assert_eq!(config.client_port, 0); + assert_eq!(config.stun_server, "stun.l.google.com:19302"); + assert_eq!(config.stun_verifier, "stun4.l.google.com:19302"); + assert_eq!(config.handshake_timeout_secs, 30); + assert_eq!(config.punch_hole_secs, 15); + assert_eq!(config.disconnect_timeout_ms, 500); + assert_eq!(config.encryption_mode, EncryptionMode::ChaCha20Poly1305); } } diff --git a/src/main.rs b/src/main.rs index 4234885..401098c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ async fn main() -> Result<()> { info!("Starting GhostLink v1.1 (Secure)"); // 2. Load configuration - let config = Config::load(); + let config = Config::load()?; debug!("Configuration loaded: {:?}", config); // 3. Bind UDP socket