Skip to main content

trojan_config/
lib.rs

1//! Configuration loading and CLI definitions.
2
3use std::{fs, path::Path};
4
5use clap::Parser;
6use serde::{Deserialize, Serialize};
7use trojan_core::defaults;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Config {
11    pub server: ServerConfig,
12    pub tls: TlsConfig,
13    pub auth: AuthConfig,
14    #[serde(default)]
15    pub websocket: WebSocketConfig,
16    #[serde(default)]
17    pub metrics: MetricsConfig,
18    #[serde(default)]
19    pub logging: LoggingConfig,
20    #[serde(default)]
21    pub analytics: AnalyticsConfig,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ServerConfig {
26    pub listen: String,
27    pub fallback: String,
28    #[serde(default = "default_tcp_timeout_secs")]
29    pub tcp_idle_timeout_secs: u64,
30    #[serde(default = "default_udp_timeout_secs")]
31    pub udp_timeout_secs: u64,
32    #[serde(default = "default_max_udp_payload")]
33    pub max_udp_payload: usize,
34    #[serde(default = "default_max_udp_buffer_bytes")]
35    pub max_udp_buffer_bytes: usize,
36    #[serde(default = "default_max_header_bytes")]
37    pub max_header_bytes: usize,
38    /// Maximum concurrent connections (None = unlimited)
39    #[serde(default)]
40    pub max_connections: Option<usize>,
41    /// Per-IP rate limiting configuration
42    #[serde(default)]
43    pub rate_limit: Option<RateLimitConfig>,
44    /// Fallback connection pool configuration
45    #[serde(default)]
46    pub fallback_pool: Option<FallbackPoolConfig>,
47    /// Resource limits configuration
48    #[serde(default)]
49    pub resource_limits: Option<ResourceLimitsConfig>,
50}
51
52/// Configuration for fallback connection warm pool.
53///
54/// Warm pool semantics:
55/// - Pre-connects up to `max_idle` fresh connections in the background.
56/// - Connections are handed out once and NOT returned to the pool.
57/// - Pool is periodically refilled.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct FallbackPoolConfig {
60    /// Maximum idle connections to keep in pool.
61    #[serde(default = "default_pool_max_idle")]
62    pub max_idle: usize,
63    /// Maximum age of pooled connections in seconds.
64    #[serde(default = "default_pool_max_age_secs")]
65    pub max_age_secs: u64,
66    /// Warm-fill batch size per cycle (1..=max_idle).
67    #[serde(default = "default_pool_fill_batch")]
68    pub fill_batch: usize,
69    /// Delay (ms) between each connection attempt within a batch.
70    #[serde(default = "default_pool_fill_delay_ms")]
71    pub fill_delay_ms: u64,
72}
73
74/// Configuration for resource limits.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct ResourceLimitsConfig {
77    /// Buffer size for TCP relay (bytes).
78    #[serde(default = "default_relay_buffer_size")]
79    pub relay_buffer_size: usize,
80    /// TCP socket send buffer size (SO_SNDBUF). If 0, uses OS default.
81    #[serde(default)]
82    pub tcp_send_buffer: usize,
83    /// TCP socket receive buffer size (SO_RCVBUF). If 0, uses OS default.
84    #[serde(default)]
85    pub tcp_recv_buffer: usize,
86    /// TCP listener backlog (pending connections queue size).
87    #[serde(default = "default_connection_backlog")]
88    pub connection_backlog: u32,
89}
90
91/// Rate limiting configuration for per-IP connection throttling.
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct RateLimitConfig {
94    /// Maximum new connections per IP within the time window.
95    #[serde(default = "default_rate_limit_max_connections")]
96    pub max_connections_per_ip: u32,
97    /// Time window in seconds for rate limiting.
98    #[serde(default = "default_rate_limit_window_secs")]
99    pub window_secs: u64,
100    /// Cleanup interval in seconds for expired entries.
101    #[serde(default = "default_rate_limit_cleanup_secs")]
102    pub cleanup_interval_secs: u64,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct TlsConfig {
107    /// Server certificate file path (PEM format).
108    pub cert: String,
109    /// Server private key file path (PEM format).
110    pub key: String,
111    /// ALPN protocols to advertise.
112    #[serde(default)]
113    pub alpn: Vec<String>,
114    /// Minimum TLS version (tls12, tls13). Default: tls12
115    #[serde(default = "default_min_tls_version")]
116    pub min_version: String,
117    /// Maximum TLS version (tls12, tls13). Default: tls13
118    #[serde(default = "default_max_tls_version")]
119    pub max_version: String,
120    /// Path to CA certificate for client authentication (mTLS).
121    /// If set, client certificates will be required and verified.
122    #[serde(default)]
123    pub client_ca: Option<String>,
124    /// Cipher suites to use. If empty, uses rustls defaults.
125    /// Example: ["TLS13_AES_256_GCM_SHA384", "TLS13_CHACHA20_POLY1305_SHA256"]
126    #[serde(default)]
127    pub cipher_suites: Vec<String>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131pub struct WebSocketConfig {
132    #[serde(default = "default_ws_enabled")]
133    pub enabled: bool,
134    #[serde(default = "default_ws_mode")]
135    pub mode: String,
136    #[serde(default = "default_ws_path")]
137    pub path: String,
138    #[serde(default)]
139    pub host: Option<String>,
140    #[serde(default)]
141    pub listen: Option<String>,
142    #[serde(default = "default_ws_max_frame_bytes")]
143    pub max_frame_bytes: usize,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct AuthConfig {
148    pub passwords: Vec<String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, Default)]
152pub struct MetricsConfig {
153    pub listen: Option<String>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, Default)]
157pub struct LoggingConfig {
158    /// Log level (trace, debug, info, warn, error).
159    pub level: Option<String>,
160    /// Log format: json, pretty, or compact. Default: pretty.
161    pub format: Option<String>,
162    /// Output target: stdout or stderr. Default: stderr.
163    pub output: Option<String>,
164    /// Per-module log level filters (e.g., {"trojan_auth": "debug", "rustls": "warn"}).
165    #[serde(default)]
166    pub filters: std::collections::HashMap<String, String>,
167}
168
169// ============================================================================
170// Analytics Configuration
171// ============================================================================
172
173/// Analytics configuration for connection event collection.
174#[derive(Debug, Clone, Serialize, Deserialize, Default)]
175pub struct AnalyticsConfig {
176    /// Whether analytics is enabled (runtime switch).
177    #[serde(default)]
178    pub enabled: bool,
179
180    /// ClickHouse configuration.
181    #[serde(default)]
182    pub clickhouse: Option<ClickHouseConfig>,
183
184    /// Buffer configuration.
185    #[serde(default)]
186    pub buffer: AnalyticsBufferConfig,
187
188    /// Sampling configuration.
189    #[serde(default)]
190    pub sampling: AnalyticsSamplingConfig,
191
192    /// Privacy configuration.
193    #[serde(default)]
194    pub privacy: AnalyticsPrivacyConfig,
195
196    /// Server identifier for multi-instance deployments.
197    #[serde(default)]
198    pub server_id: Option<String>,
199}
200
201/// ClickHouse connection configuration.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ClickHouseConfig {
204    /// ClickHouse URL (e.g., "http://localhost:8123").
205    pub url: String,
206
207    /// Database name.
208    #[serde(default = "default_analytics_database")]
209    pub database: String,
210
211    /// Table name.
212    #[serde(default = "default_analytics_table")]
213    pub table: String,
214
215    /// Username (optional).
216    #[serde(default)]
217    pub username: Option<String>,
218
219    /// Password (optional).
220    #[serde(default)]
221    pub password: Option<String>,
222
223    /// Connection timeout in seconds.
224    #[serde(default = "default_analytics_connect_timeout")]
225    pub connect_timeout_secs: u64,
226
227    /// Write timeout in seconds.
228    #[serde(default = "default_analytics_write_timeout")]
229    pub write_timeout_secs: u64,
230}
231
232/// Buffer configuration for event batching.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct AnalyticsBufferConfig {
235    /// Maximum number of events to buffer in memory.
236    #[serde(default = "default_analytics_buffer_size")]
237    pub size: usize,
238
239    /// Flush interval in seconds.
240    #[serde(default = "default_analytics_flush_interval")]
241    pub flush_interval_secs: u64,
242
243    /// Batch size for writes.
244    #[serde(default = "default_analytics_batch_size")]
245    pub batch_size: usize,
246
247    /// Fallback file path for failed writes.
248    #[serde(default)]
249    pub fallback_path: Option<String>,
250}
251
252impl Default for AnalyticsBufferConfig {
253    fn default() -> Self {
254        Self {
255            size: default_analytics_buffer_size(),
256            flush_interval_secs: default_analytics_flush_interval(),
257            batch_size: default_analytics_batch_size(),
258            fallback_path: None,
259        }
260    }
261}
262
263/// Sampling configuration for high-traffic scenarios.
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct AnalyticsSamplingConfig {
266    /// Sampling rate (0.0 - 1.0, where 1.0 = 100%).
267    #[serde(default = "default_analytics_sample_rate")]
268    pub rate: f64,
269
270    /// Users to always record (not affected by sampling).
271    #[serde(default)]
272    pub always_record_users: Vec<String>,
273}
274
275impl Default for AnalyticsSamplingConfig {
276    fn default() -> Self {
277        Self {
278            rate: default_analytics_sample_rate(),
279            always_record_users: Vec::new(),
280        }
281    }
282}
283
284/// Privacy configuration for data collection.
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct AnalyticsPrivacyConfig {
287    /// Whether to record client IP addresses.
288    #[serde(default = "default_true")]
289    pub record_peer_ip: bool,
290
291    /// Whether to record full user ID (false = prefix only).
292    #[serde(default)]
293    pub full_user_id: bool,
294
295    /// User ID prefix length when full_user_id is false.
296    #[serde(default = "default_analytics_user_id_prefix_len")]
297    pub user_id_prefix_len: usize,
298
299    /// Whether to record SNI.
300    #[serde(default = "default_true")]
301    pub record_sni: bool,
302}
303
304impl Default for AnalyticsPrivacyConfig {
305    fn default() -> Self {
306        Self {
307            record_peer_ip: true,
308            full_user_id: false,
309            user_id_prefix_len: default_analytics_user_id_prefix_len(),
310            record_sni: true,
311        }
312    }
313}
314
315// Analytics default value functions
316fn default_analytics_database() -> String {
317    "trojan".to_string()
318}
319
320fn default_analytics_table() -> String {
321    "connections".to_string()
322}
323
324fn default_analytics_buffer_size() -> usize {
325    10000
326}
327
328fn default_analytics_flush_interval() -> u64 {
329    5
330}
331
332fn default_analytics_batch_size() -> usize {
333    1000
334}
335
336fn default_analytics_sample_rate() -> f64 {
337    1.0
338}
339
340fn default_analytics_user_id_prefix_len() -> usize {
341    8
342}
343
344fn default_analytics_connect_timeout() -> u64 {
345    10
346}
347
348fn default_analytics_write_timeout() -> u64 {
349    30
350}
351
352fn default_true() -> bool {
353    true
354}
355
356#[derive(Debug, Clone, Parser, Default)]
357pub struct CliOverrides {
358    /// Override server listen address, e.g. 0.0.0.0:443
359    #[arg(long)]
360    pub listen: Option<String>,
361    /// Override fallback backend address, e.g. 127.0.0.1:80
362    #[arg(long)]
363    pub fallback: Option<String>,
364    /// Override TLS cert path
365    #[arg(long)]
366    pub tls_cert: Option<String>,
367    /// Override TLS key path
368    #[arg(long)]
369    pub tls_key: Option<String>,
370    /// Override ALPN list (repeatable or comma-separated)
371    #[arg(long, num_args = 1.., value_delimiter = ',')]
372    pub alpn: Option<Vec<String>>,
373    /// Override password list (repeatable or comma-separated)
374    #[arg(long, num_args = 1.., value_delimiter = ',')]
375    pub password: Option<Vec<String>>,
376    /// Override TCP idle timeout (seconds)
377    #[arg(long)]
378    pub tcp_idle_timeout_secs: Option<u64>,
379    /// Override UDP idle timeout (seconds)
380    #[arg(long)]
381    pub udp_timeout_secs: Option<u64>,
382    /// Override maximum Trojan header bytes
383    #[arg(long)]
384    pub max_header_bytes: Option<usize>,
385    /// Override maximum UDP payload size
386    #[arg(long)]
387    pub max_udp_payload: Option<usize>,
388    /// Override maximum UDP buffer bytes
389    #[arg(long)]
390    pub max_udp_buffer_bytes: Option<usize>,
391    /// Override maximum concurrent connections (0 = unlimited)
392    #[arg(long)]
393    pub max_connections: Option<usize>,
394    /// Override metrics listen address
395    #[arg(long)]
396    pub metrics_listen: Option<String>,
397    /// Override log level (trace/debug/info/warn/error)
398    #[arg(long)]
399    pub log_level: Option<String>,
400    /// Enable rate limiting with max connections per IP (0 = disabled)
401    #[arg(long)]
402    pub rate_limit_max_per_ip: Option<u32>,
403    /// Rate limit time window in seconds
404    #[arg(long)]
405    pub rate_limit_window_secs: Option<u64>,
406    /// Minimum TLS version (tls12, tls13)
407    #[arg(long)]
408    pub tls_min_version: Option<String>,
409    /// Maximum TLS version (tls12, tls13)
410    #[arg(long)]
411    pub tls_max_version: Option<String>,
412    /// Path to CA certificate for client authentication (mTLS)
413    #[arg(long)]
414    pub tls_client_ca: Option<String>,
415    /// Buffer size for TCP relay (bytes)
416    #[arg(long)]
417    pub relay_buffer_size: Option<usize>,
418    /// TCP socket send buffer size (SO_SNDBUF, 0 = OS default)
419    #[arg(long)]
420    pub tcp_send_buffer: Option<usize>,
421    /// TCP socket receive buffer size (SO_RCVBUF, 0 = OS default)
422    #[arg(long)]
423    pub tcp_recv_buffer: Option<usize>,
424    /// TCP listener backlog size
425    #[arg(long)]
426    pub connection_backlog: Option<u32>,
427    /// Enable WebSocket transport (default true)
428    #[arg(long)]
429    pub ws_enabled: Option<bool>,
430    /// WebSocket mode: mixed | split
431    #[arg(long)]
432    pub ws_mode: Option<String>,
433    /// WebSocket path
434    #[arg(long)]
435    pub ws_path: Option<String>,
436    /// WebSocket host (optional)
437    #[arg(long)]
438    pub ws_host: Option<String>,
439    /// WebSocket listen address for split mode
440    #[arg(long)]
441    pub ws_listen: Option<String>,
442    /// WebSocket max frame bytes
443    #[arg(long)]
444    pub ws_max_frame_bytes: Option<usize>,
445}
446
447#[derive(Debug, thiserror::Error)]
448pub enum ConfigError {
449    #[error("io: {0}")]
450    Io(#[from] std::io::Error),
451    #[error("json: {0}")]
452    Json(#[from] serde_json::Error),
453    #[error("yaml: {0}")]
454    Yaml(#[from] serde_yaml::Error),
455    #[error("toml: {0}")]
456    Toml(#[from] toml::de::Error),
457    #[error("unsupported config format")]
458    UnsupportedFormat,
459    #[error("validation: {0}")]
460    Validation(String),
461}
462
463pub fn load_config(path: impl AsRef<Path>) -> Result<Config, ConfigError> {
464    let path = path.as_ref();
465    let data = fs::read_to_string(path)?;
466    match path.extension().and_then(|s| s.to_str()).unwrap_or("") {
467        "json" => Ok(serde_json::from_str(&data)?),
468        "yaml" | "yml" => Ok(serde_yaml::from_str(&data)?),
469        "toml" => Ok(toml::from_str(&data)?),
470        _ => Err(ConfigError::UnsupportedFormat),
471    }
472}
473
474pub fn apply_overrides(config: &mut Config, overrides: &CliOverrides) {
475    if let Some(v) = &overrides.listen {
476        config.server.listen = v.clone();
477    }
478    if let Some(v) = &overrides.fallback {
479        config.server.fallback = v.clone();
480    }
481    if let Some(v) = overrides.tcp_idle_timeout_secs {
482        config.server.tcp_idle_timeout_secs = v;
483    }
484    if let Some(v) = overrides.udp_timeout_secs {
485        config.server.udp_timeout_secs = v;
486    }
487    if let Some(v) = overrides.max_header_bytes {
488        config.server.max_header_bytes = v;
489    }
490    if let Some(v) = overrides.max_udp_payload {
491        config.server.max_udp_payload = v;
492    }
493    if let Some(v) = overrides.max_udp_buffer_bytes {
494        config.server.max_udp_buffer_bytes = v;
495    }
496    if let Some(v) = overrides.max_connections {
497        config.server.max_connections = if v == 0 { None } else { Some(v) };
498    }
499    if let Some(v) = &overrides.tls_cert {
500        config.tls.cert = v.clone();
501    }
502    if let Some(v) = &overrides.tls_key {
503        config.tls.key = v.clone();
504    }
505    if let Some(v) = &overrides.alpn {
506        config.tls.alpn = v.clone();
507    }
508    if let Some(v) = &overrides.password {
509        config.auth.passwords = v.clone();
510    }
511    if let Some(v) = &overrides.metrics_listen {
512        config.metrics.listen = Some(v.clone());
513    }
514    if let Some(v) = &overrides.log_level {
515        config.logging.level = Some(v.clone());
516    }
517    // Rate limiting: 0 disables, > 0 enables with that limit
518    if let Some(max) = overrides.rate_limit_max_per_ip {
519        if max == 0 {
520            config.server.rate_limit = None;
521        } else {
522            let rl = config
523                .server
524                .rate_limit
525                .get_or_insert_with(|| RateLimitConfig {
526                    max_connections_per_ip: default_rate_limit_max_connections(),
527                    window_secs: default_rate_limit_window_secs(),
528                    cleanup_interval_secs: default_rate_limit_cleanup_secs(),
529                });
530            rl.max_connections_per_ip = max;
531        }
532    }
533    if let Some(window) = overrides.rate_limit_window_secs
534        && let Some(ref mut rl) = config.server.rate_limit
535    {
536        rl.window_secs = window;
537    }
538    // TLS version overrides
539    if let Some(v) = &overrides.tls_min_version {
540        config.tls.min_version = v.clone();
541    }
542    if let Some(v) = &overrides.tls_max_version {
543        config.tls.max_version = v.clone();
544    }
545    if let Some(v) = &overrides.tls_client_ca {
546        config.tls.client_ca = Some(v.clone());
547    }
548    // Resource limits
549    if overrides.relay_buffer_size.is_some()
550        || overrides.tcp_send_buffer.is_some()
551        || overrides.tcp_recv_buffer.is_some()
552        || overrides.connection_backlog.is_some()
553    {
554        let rl = config
555            .server
556            .resource_limits
557            .get_or_insert_with(|| ResourceLimitsConfig {
558                relay_buffer_size: default_relay_buffer_size(),
559                tcp_send_buffer: 0,
560                tcp_recv_buffer: 0,
561                connection_backlog: default_connection_backlog(),
562            });
563        if let Some(v) = overrides.relay_buffer_size {
564            rl.relay_buffer_size = v;
565        }
566        if let Some(v) = overrides.tcp_send_buffer {
567            rl.tcp_send_buffer = v;
568        }
569        if let Some(v) = overrides.tcp_recv_buffer {
570            rl.tcp_recv_buffer = v;
571        }
572        if let Some(v) = overrides.connection_backlog {
573            rl.connection_backlog = v;
574        }
575    }
576    if let Some(v) = overrides.ws_enabled {
577        config.websocket.enabled = v;
578    }
579    if let Some(v) = &overrides.ws_mode {
580        config.websocket.mode = v.clone();
581    }
582    if let Some(v) = &overrides.ws_path {
583        config.websocket.path = v.clone();
584    }
585    if let Some(v) = &overrides.ws_host {
586        config.websocket.host = Some(v.clone());
587    }
588    if let Some(v) = &overrides.ws_listen {
589        config.websocket.listen = Some(v.clone());
590    }
591    if let Some(v) = overrides.ws_max_frame_bytes {
592        config.websocket.max_frame_bytes = v;
593    }
594}
595
596pub fn validate_config(config: &Config) -> Result<(), ConfigError> {
597    if config.server.listen.trim().is_empty() {
598        return Err(ConfigError::Validation("server.listen is empty".into()));
599    }
600    if config.server.fallback.trim().is_empty() {
601        return Err(ConfigError::Validation("server.fallback is empty".into()));
602    }
603    if config.tls.cert.trim().is_empty() {
604        return Err(ConfigError::Validation("tls.cert is empty".into()));
605    }
606    if config.tls.key.trim().is_empty() {
607        return Err(ConfigError::Validation("tls.key is empty".into()));
608    }
609    if config.auth.passwords.is_empty() {
610        return Err(ConfigError::Validation("auth.passwords is empty".into()));
611    }
612    if config.server.tcp_idle_timeout_secs == 0 {
613        return Err(ConfigError::Validation(
614            "server.tcp_idle_timeout_secs must be > 0".into(),
615        ));
616    }
617    if config.server.udp_timeout_secs == 0 {
618        return Err(ConfigError::Validation(
619            "server.udp_timeout_secs must be > 0".into(),
620        ));
621    }
622    if config.server.max_header_bytes < min_header_bytes() {
623        return Err(ConfigError::Validation(format!(
624            "server.max_header_bytes too small (min {})",
625            min_header_bytes()
626        )));
627    }
628    if config.server.max_udp_payload == 0 || config.server.max_udp_payload > u16::MAX as usize {
629        return Err(ConfigError::Validation(
630            "server.max_udp_payload must be 1..=65535".into(),
631        ));
632    }
633    if config.server.max_udp_buffer_bytes == 0 {
634        return Err(ConfigError::Validation(
635            "server.max_udp_buffer_bytes must be > 0".into(),
636        ));
637    }
638    if config.server.max_udp_buffer_bytes < config.server.max_udp_payload + 8 {
639        return Err(ConfigError::Validation(
640            "server.max_udp_buffer_bytes must be >= max_udp_payload + 8".into(),
641        ));
642    }
643    // Validate TLS versions
644    let valid_versions = ["tls12", "tls13"];
645    if !valid_versions.contains(&config.tls.min_version.as_str()) {
646        return Err(ConfigError::Validation(format!(
647            "tls.min_version must be one of: {:?}",
648            valid_versions
649        )));
650    }
651    if !valid_versions.contains(&config.tls.max_version.as_str()) {
652        return Err(ConfigError::Validation(format!(
653            "tls.max_version must be one of: {:?}",
654            valid_versions
655        )));
656    }
657    // tls13 > tls12
658    let min_ord = if config.tls.min_version == "tls13" {
659        1
660    } else {
661        0
662    };
663    let max_ord = if config.tls.max_version == "tls13" {
664        1
665    } else {
666        0
667    };
668    if min_ord > max_ord {
669        return Err(ConfigError::Validation(
670            "tls.min_version cannot be greater than tls.max_version".into(),
671        ));
672    }
673    // Validate resource limits
674    if let Some(ref rl) = config.server.resource_limits {
675        if rl.relay_buffer_size < 1024 {
676            return Err(ConfigError::Validation(
677                "resource_limits.relay_buffer_size must be >= 1024".into(),
678            ));
679        }
680        if rl.relay_buffer_size > 1024 * 1024 {
681            return Err(ConfigError::Validation(
682                "resource_limits.relay_buffer_size must be <= 1MB".into(),
683            ));
684        }
685        if rl.connection_backlog == 0 {
686            return Err(ConfigError::Validation(
687                "resource_limits.connection_backlog must be > 0".into(),
688            ));
689        }
690    }
691    if let Some(ref pool) = config.server.fallback_pool {
692        if pool.max_idle == 0 {
693            return Err(ConfigError::Validation(
694                "fallback_pool.max_idle must be > 0".into(),
695            ));
696        }
697        if pool.max_age_secs == 0 {
698            return Err(ConfigError::Validation(
699                "fallback_pool.max_age_secs must be > 0".into(),
700            ));
701        }
702        if pool.fill_batch == 0 || pool.fill_batch > pool.max_idle {
703            return Err(ConfigError::Validation(
704                "fallback_pool.fill_batch must be 1..=max_idle".into(),
705            ));
706        }
707    }
708    if config.websocket.mode != "mixed" && config.websocket.mode != "split" {
709        return Err(ConfigError::Validation(
710            "websocket.mode must be 'mixed' or 'split'".into(),
711        ));
712    }
713    if config.websocket.path.is_empty() {
714        return Err(ConfigError::Validation("websocket.path is empty".into()));
715    }
716    if config.websocket.enabled
717        && config.websocket.mode == "split"
718        && config.websocket.listen.as_deref().unwrap_or("").is_empty()
719    {
720        return Err(ConfigError::Validation(
721            "websocket.listen is required in split mode".into(),
722        ));
723    }
724    Ok(())
725}
726
727// ============================================================================
728// Default Value Functions (for serde)
729// ============================================================================
730
731/// Generate default value functions that forward to trojan_core::defaults constants.
732macro_rules! default_fns {
733    // For Copy types (integers, bool, etc.)
734    ($($fn_name:ident => $const_name:ident : $ty:ty),* $(,)?) => {
735        $(
736            fn $fn_name() -> $ty {
737                defaults::$const_name
738            }
739        )*
740    };
741}
742
743/// Generate default value functions that return String from &str constants.
744macro_rules! default_string_fns {
745    ($($fn_name:ident => $const_name:ident),* $(,)?) => {
746        $(
747            fn $fn_name() -> String {
748                defaults::$const_name.to_string()
749            }
750        )*
751    };
752}
753
754default_fns! {
755    default_udp_timeout_secs      => DEFAULT_UDP_TIMEOUT_SECS: u64,
756    default_tcp_timeout_secs      => DEFAULT_TCP_TIMEOUT_SECS: u64,
757    default_max_udp_payload       => DEFAULT_MAX_UDP_PAYLOAD: usize,
758    default_max_udp_buffer_bytes  => DEFAULT_MAX_UDP_BUFFER_BYTES: usize,
759    default_max_header_bytes      => DEFAULT_MAX_HEADER_BYTES: usize,
760    min_header_bytes              => MIN_HEADER_BYTES: usize,
761    default_rate_limit_max_connections => DEFAULT_RATE_LIMIT_MAX_CONNECTIONS: u32,
762    default_rate_limit_window_secs     => DEFAULT_RATE_LIMIT_WINDOW_SECS: u64,
763    default_rate_limit_cleanup_secs    => DEFAULT_RATE_LIMIT_CLEANUP_SECS: u64,
764    default_pool_max_idle         => DEFAULT_POOL_MAX_IDLE: usize,
765    default_pool_max_age_secs     => DEFAULT_POOL_MAX_AGE_SECS: u64,
766    default_pool_fill_batch       => DEFAULT_POOL_FILL_BATCH: usize,
767    default_pool_fill_delay_ms    => DEFAULT_POOL_FILL_DELAY_MS: u64,
768    default_relay_buffer_size     => DEFAULT_RELAY_BUFFER_SIZE: usize,
769    default_connection_backlog    => DEFAULT_CONNECTION_BACKLOG: u32,
770    default_ws_enabled            => DEFAULT_WS_ENABLED: bool,
771    default_ws_max_frame_bytes    => DEFAULT_WS_MAX_FRAME_BYTES: usize,
772}
773
774default_string_fns! {
775    default_min_tls_version => DEFAULT_TLS_MIN_VERSION,
776    default_max_tls_version => DEFAULT_TLS_MAX_VERSION,
777    default_ws_mode         => DEFAULT_WS_MODE,
778    default_ws_path         => DEFAULT_WS_PATH,
779}