1use serde::Deserialize;
4use trojan_config::{LoggingConfig, TcpConfig};
5
6use crate::error::ClientError;
7
8#[derive(Debug, Clone, Deserialize)]
10pub struct ClientConfig {
11 pub client: ClientSettings,
12 #[serde(default)]
13 pub logging: LoggingConfig,
14}
15
16#[derive(Debug, Clone, Deserialize)]
18pub struct ClientSettings {
19 pub listen: String,
21
22 pub remote: String,
24
25 pub password: String,
27
28 #[serde(default)]
30 pub tls: ClientTlsConfig,
31
32 #[serde(default)]
34 pub tcp: TcpConfig,
35}
36
37#[derive(Debug, Clone, Deserialize)]
39pub struct ClientTlsConfig {
40 pub sni: Option<String>,
42
43 #[serde(default = "default_alpn")]
45 pub alpn: Vec<String>,
46
47 #[serde(default)]
49 pub skip_verify: bool,
50
51 pub ca: Option<String>,
53}
54
55impl Default for ClientTlsConfig {
56 fn default() -> Self {
57 Self {
58 sni: None,
59 alpn: default_alpn(),
60 skip_verify: false,
61 ca: None,
62 }
63 }
64}
65
66fn default_alpn() -> Vec<String> {
67 vec!["h2".into(), "http/1.1".into()]
68}
69
70pub fn load_client_config(path: &std::path::Path) -> Result<ClientConfig, ClientError> {
74 let content = std::fs::read_to_string(path)
75 .map_err(|e| ClientError::Config(format!("failed to read config: {e}")))?;
76
77 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("toml");
78
79 match ext {
80 "toml" => toml::from_str(&content)
81 .map_err(|e| ClientError::Config(format!("TOML parse error: {e}"))),
82 "json" | "jsonc" => {
83 let stripped: String = content
85 .lines()
86 .map(|line| {
87 if let Some(idx) = line.find("//") {
88 let before = &line[..idx];
90 let quotes = before.chars().filter(|&c| c == '"').count();
91 if quotes % 2 == 0 { before } else { line }
92 } else {
93 line
94 }
95 })
96 .collect::<Vec<_>>()
97 .join("\n");
98 serde_json::from_str(&stripped)
99 .map_err(|e| ClientError::Config(format!("JSON parse error: {e}")))
100 }
101 _ => toml::from_str(&content)
102 .map_err(|e| ClientError::Config(format!("config parse error: {e}"))),
103 }
104}