1- // Implementation - util/config.cpp
21#include " tor/util/config.hpp"
2+ #include < algorithm>
33#include < fstream>
44#include < iostream>
55#include < sstream>
6+ #include < unordered_map>
67
78namespace tor ::util {
89
10+ // --- Minimal TOML parser (standard-library only) ---
11+
12+ namespace {
13+
14+ std::string trim (const std::string& s) {
15+ auto start = s.find_first_not_of (" \t\r\n " );
16+ if (start == std::string::npos) return " " ;
17+ auto end = s.find_last_not_of (" \t\r\n " );
18+ return s.substr (start, end - start + 1 );
19+ }
20+
21+ std::string unquote (const std::string& s) {
22+ if (s.size () >= 2 && s.front () == ' "' && s.back () == ' "' ) {
23+ return s.substr (1 , s.size () - 2 );
24+ }
25+ return s;
26+ }
27+
28+ // Flat key-value store: "section.subsection.key" -> "value"
29+ using TomlMap = std::unordered_map<std::string, std::string>;
30+
31+ TomlMap parse_toml_simple (const std::string& content) {
32+ TomlMap result;
33+ std::string current_section;
34+ std::istringstream stream (content);
35+ std::string line;
36+
37+ while (std::getline (stream, line)) {
38+ line = trim (line);
39+
40+ // Skip empty lines and comments
41+ if (line.empty () || line[0 ] == ' #' ) continue ;
42+
43+ // Section header: [section] or [section.subsection]
44+ if (line.front () == ' [' && line.back () == ' ]' ) {
45+ current_section = line.substr (1 , line.size () - 2 );
46+ current_section = trim (current_section);
47+ continue ;
48+ }
49+
50+ // Key = value
51+ auto eq = line.find (' =' );
52+ if (eq == std::string::npos) continue ;
53+
54+ auto key = trim (line.substr (0 , eq));
55+ auto value = trim (line.substr (eq + 1 ));
56+ value = unquote (value);
57+
58+ // Build fully qualified key
59+ std::string fqkey = current_section.empty () ? key : current_section + " ." + key;
60+ result[fqkey] = value;
61+ }
62+
63+ return result;
64+ }
65+
66+ std::string get (const TomlMap& m, const std::string& key, const std::string& def = " " ) {
67+ auto it = m.find (key);
68+ return it != m.end () ? it->second : def;
69+ }
70+
71+ int get_int (const TomlMap& m, const std::string& key, int def = 0 ) {
72+ auto it = m.find (key);
73+ if (it == m.end ()) return def;
74+ try { return std::stoi (it->second ); } catch (...) { return def; }
75+ }
76+
77+ bool get_bool (const TomlMap& m, const std::string& key, bool def = false ) {
78+ auto it = m.find (key);
79+ if (it == m.end ()) return def;
80+ return it->second == " true" || it->second == " 1" ;
81+ }
82+
83+ modes::BridgeRelay::Distribution parse_distribution (const std::string& s) {
84+ if (s == " https" ) return modes::BridgeRelay::Distribution::Https;
85+ if (s == " email" ) return modes::BridgeRelay::Distribution::Email;
86+ if (s == " moat" ) return modes::BridgeRelay::Distribution::Moat;
87+ if (s == " any" ) return modes::BridgeRelay::Distribution::Any;
88+ return modes::BridgeRelay::Distribution::None;
89+ }
90+
91+ } // namespace
92+
93+ // --- Config implementation ---
94+
995std::expected<Config, ConfigError> Config::load_from_file (const std::filesystem::path& path) {
1096 if (!std::filesystem::exists (path)) {
1197 return std::unexpected (ConfigError::FileNotFound);
@@ -18,14 +104,105 @@ std::expected<Config, ConfigError> Config::load_from_file(const std::filesystem:
18104
19105 std::stringstream buffer;
20106 buffer << file.rdbuf ();
21-
107+
22108 return load_from_string (buffer.str ());
23109}
24110
25111std::expected<Config, ConfigError> Config::load_from_string (const std::string& toml_content) {
26112 Config config;
27- // Basic TOML parsing - in a real implementation, use a TOML library
28- // For now, return default config
113+
114+ auto m = parse_toml_simple (toml_content);
115+
116+ // [relay]
117+ auto nickname = get (m, " relay.nickname" );
118+ if (!nickname.empty ()) config.relay .nickname = nickname;
119+
120+ auto mode_str = get (m, " relay.mode" );
121+ if (!mode_str.empty ()) {
122+ auto mode_result = modes::parse_relay_mode (mode_str);
123+ if (mode_result) {
124+ config.relay .mode = *mode_result;
125+ }
126+ }
127+
128+ auto or_port = get_int (m, " relay.or_port" , 0 );
129+ if (or_port > 0 ) config.relay .or_port = static_cast <uint16_t >(or_port);
130+
131+ auto dir_port = get_int (m, " relay.dir_port" , -1 );
132+ if (dir_port >= 0 ) config.relay .dir_port = static_cast <uint16_t >(dir_port);
133+
134+ auto contact = get (m, " relay.contact" );
135+ if (!contact.empty ()) config.relay .contact_info = contact;
136+
137+ auto address = get (m, " relay.address" );
138+ if (!address.empty ()) config.relay .address = address;
139+
140+ auto bind_address = get (m, " relay.bind_address" );
141+ if (!bind_address.empty ()) config.relay .address = bind_address;
142+
143+ // [relay.bandwidth]
144+ auto bw_rate = get_int (m, " relay.bandwidth.rate" , 0 );
145+ auto bw_burst = get_int (m, " relay.bandwidth.burst" , 0 );
146+ if (bw_rate > 0 ) {
147+ config.relay .bandwidth .rate_bytes_per_sec = static_cast <size_t >(bw_rate);
148+ }
149+ if (bw_burst > 0 ) {
150+ config.relay .bandwidth .burst_bytes = static_cast <size_t >(bw_burst);
151+ }
152+
153+ // [bridge]
154+ auto distribution = get (m, " bridge.distribution" );
155+ if (!distribution.empty ()) {
156+ config.bridge .distribution = parse_distribution (distribution);
157+ }
158+
159+ // [bridge.transport]
160+ auto transport_type = get (m, " bridge.transport.type" );
161+ if (!transport_type.empty ()) {
162+ config.bridge .transport = transport_type;
163+ }
164+
165+ auto transport_port = get_int (m, " bridge.transport.port" , 0 );
166+ if (transport_port > 0 ) {
167+ config.bridge .transport_port = static_cast <uint16_t >(transport_port);
168+ }
169+
170+ auto iat_mode = get_int (m, " bridge.transport.iat_mode" , 0 );
171+ config.bridge .iat_mode = static_cast <uint8_t >(iat_mode);
172+
173+ // [directory]
174+ auto publish = get (m, " directory.publish_server_descriptor" );
175+ if (!publish.empty ()) {
176+ config.directory .publish_server_descriptor = (publish == " true" || publish == " 1" );
177+ }
178+
179+ auto fetch_interval = get_int (m, " directory.fetch_interval" , 0 );
180+ if (fetch_interval > 0 ) {
181+ config.directory .publish_interval = std::chrono::seconds (fetch_interval);
182+ }
183+
184+ // [logging]
185+ auto log_level = get (m, " logging.level" );
186+ if (!log_level.empty ()) config.logging .level = log_level;
187+
188+ auto log_file = get (m, " logging.file" );
189+ if (!log_file.empty ()) config.logging .log_file = log_file;
190+
191+ // [data]
192+ auto data_dir = get (m, " data.directory" );
193+ if (!data_dir.empty ()) config.relay .data_dir = data_dir;
194+
195+ // [network]
196+ auto connect_timeout = get_int (m, " network.connect_timeout" , 0 );
197+ (void )connect_timeout; // Stored in relay runtime, not config
198+
199+ // [security]
200+ auto sandbox = get_bool (m, " security.sandbox" , false );
201+ config.security .sandbox_enabled = sandbox;
202+
203+ auto secure_memory = get_bool (m, " security.secure_memory" , true );
204+ (void )secure_memory; // Applied at runtime
205+
29206 return config;
30207}
31208
@@ -55,14 +232,15 @@ std::expected<void, ConfigError> Config::validate() const {
55232}
56233
57234void Config::apply_cli_args (int argc, char * argv[]) {
58- // CLI arg parsing would go here
235+ // CLI arg parsing is handled in main.cpp
236+ (void )argc;
237+ (void )argv;
59238}
60239
61240policy::ExitPolicy Config::effective_exit_policy () const {
62241 if (relay.mode == modes::RelayMode::Exit) {
63242 return exit.exit_policy ;
64243 }
65- // Non-exit relays reject all
66244 return policy::ExitPolicy::reject_all ();
67245}
68246
@@ -107,7 +285,6 @@ std::string config_error_message(ConfigError err) {
107285
108286std::expected<CliArgs, std::string> parse_cli_args (int argc, char * argv[]) {
109287 CliArgs args;
110- // Basic CLI parsing
111288 for (int i = 1 ; i < argc; ++i) {
112289 std::string arg = argv[i];
113290 if (arg == " --help" || arg == " -h" ) {
0 commit comments