librats 0.3.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +405 -405
  2. package/binding.gyp +96 -95
  3. package/lib/index.d.ts +522 -522
  4. package/lib/index.js +82 -82
  5. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  7. package/native-src/CMakeLists.txt +360 -0
  8. package/native-src/LICENSE +21 -0
  9. package/native-src/src/bencode.cpp +485 -0
  10. package/native-src/src/bencode.h +145 -0
  11. package/native-src/src/bittorrent.cpp +3682 -0
  12. package/native-src/src/bittorrent.h +731 -0
  13. package/native-src/src/dht.cpp +2342 -0
  14. package/native-src/src/dht.h +501 -0
  15. package/native-src/src/encrypted_socket.cpp +817 -0
  16. package/native-src/src/encrypted_socket.h +239 -0
  17. package/native-src/src/file_transfer.cpp +1808 -0
  18. package/native-src/src/file_transfer.h +567 -0
  19. package/native-src/src/fs.cpp +639 -0
  20. package/native-src/src/fs.h +108 -0
  21. package/native-src/src/gossipsub.cpp +1137 -0
  22. package/native-src/src/gossipsub.h +403 -0
  23. package/native-src/src/ice.cpp +1386 -0
  24. package/native-src/src/ice.h +328 -0
  25. package/native-src/src/json.hpp +25526 -0
  26. package/native-src/src/krpc.cpp +558 -0
  27. package/native-src/src/krpc.h +145 -0
  28. package/native-src/src/librats.cpp +2715 -0
  29. package/native-src/src/librats.h +1729 -0
  30. package/native-src/src/librats_bittorrent.cpp +167 -0
  31. package/native-src/src/librats_c.cpp +1317 -0
  32. package/native-src/src/librats_c.h +237 -0
  33. package/native-src/src/librats_encryption.cpp +123 -0
  34. package/native-src/src/librats_file_transfer.cpp +226 -0
  35. package/native-src/src/librats_gossipsub.cpp +293 -0
  36. package/native-src/src/librats_ice.cpp +515 -0
  37. package/native-src/src/librats_logging.cpp +158 -0
  38. package/native-src/src/librats_mdns.cpp +171 -0
  39. package/native-src/src/librats_nat.cpp +571 -0
  40. package/native-src/src/librats_persistence.cpp +815 -0
  41. package/native-src/src/logger.h +412 -0
  42. package/native-src/src/mdns.cpp +1178 -0
  43. package/native-src/src/mdns.h +253 -0
  44. package/native-src/src/network_utils.cpp +598 -0
  45. package/native-src/src/network_utils.h +162 -0
  46. package/native-src/src/noise.cpp +981 -0
  47. package/native-src/src/noise.h +227 -0
  48. package/native-src/src/os.cpp +371 -0
  49. package/native-src/src/os.h +40 -0
  50. package/native-src/src/rats_export.h +17 -0
  51. package/native-src/src/sha1.cpp +163 -0
  52. package/native-src/src/sha1.h +42 -0
  53. package/native-src/src/socket.cpp +1376 -0
  54. package/native-src/src/socket.h +309 -0
  55. package/native-src/src/stun.cpp +484 -0
  56. package/native-src/src/stun.h +349 -0
  57. package/native-src/src/threadmanager.cpp +105 -0
  58. package/native-src/src/threadmanager.h +53 -0
  59. package/native-src/src/tracker.cpp +1110 -0
  60. package/native-src/src/tracker.h +268 -0
  61. package/native-src/src/version.cpp +24 -0
  62. package/native-src/src/version.h.in +45 -0
  63. package/native-src/version.rc.in +31 -0
  64. package/package.json +62 -68
  65. package/scripts/build-librats.js +241 -194
  66. package/scripts/postinstall.js +52 -52
  67. package/scripts/prepare-package.js +187 -91
  68. package/scripts/verify-installation.js +119 -119
  69. package/src/librats_node.cpp +1174 -1174
@@ -0,0 +1,2715 @@
1
+ #include "librats.h"
2
+ #include "gossipsub.h"
3
+ #include "sha1.h"
4
+ #include "os.h"
5
+ #include "network_utils.h"
6
+ #include "fs.h"
7
+ #include "json.hpp" // nlohmann::json
8
+ #include "version.h"
9
+ #include <iostream>
10
+ #include <algorithm>
11
+ #include <chrono>
12
+ #include <memory>
13
+ #include <random>
14
+ #include <sstream>
15
+ #include <iomanip>
16
+ #include <stdexcept>
17
+
18
+ #ifdef TESTING
19
+ #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
20
+ #define LOG_CLIENT_INFO(message) LOG_INFO("client", "[pointer: " << this << "] " << message)
21
+ #define LOG_CLIENT_WARN(message) LOG_WARN("client", "[pointer: " << this << "] " << message)
22
+ #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", "[pointer: " << this << "] " << message)
23
+
24
+ #define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", "[pointer: " << this << "] " << message)
25
+ #define LOG_SERVER_INFO(message) LOG_INFO("server", "[pointer: " << this << "] " << message)
26
+ #define LOG_SERVER_WARN(message) LOG_WARN("server", "[pointer: " << this << "] " << message)
27
+ #define LOG_SERVER_ERROR(message) LOG_ERROR("server", "[pointer: " << this << "] " << message)
28
+ #else
29
+ #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
30
+ #define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
31
+ #define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
32
+ #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
33
+
34
+ #define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", message)
35
+ #define LOG_SERVER_INFO(message) LOG_INFO("server", message)
36
+ #define LOG_SERVER_WARN(message) LOG_WARN("server", message)
37
+ #define LOG_SERVER_ERROR(message) LOG_ERROR("server", message)
38
+ #endif
39
+
40
+ namespace librats {
41
+
42
+ // Configuration file constants
43
+ const std::string RatsClient::CONFIG_FILE_NAME = "config.json";
44
+ const std::string RatsClient::PEERS_FILE_NAME = "peers.rats";
45
+ const std::string RatsClient::PEERS_EVER_FILE_NAME = "peers_ever.rats";
46
+
47
+ // =========================================================================
48
+ // Constructor and Destructor
49
+ // =========================================================================
50
+
51
+ RatsClient::RatsClient(int listen_port, int max_peers, const NatTraversalConfig& nat_config, const std::string& bind_address)
52
+ : listen_port_(listen_port),
53
+ bind_address_(bind_address),
54
+ max_peers_(max_peers),
55
+ nat_config_(nat_config),
56
+ server_socket_(INVALID_SOCKET_VALUE),
57
+ running_(false),
58
+ auto_discovery_running_(false),
59
+ encryption_enabled_(false),
60
+ detected_nat_type_(NatType::UNKNOWN),
61
+ data_directory_("."),
62
+ custom_protocol_name_("rats"),
63
+ custom_protocol_version_("1.0") {
64
+ // Initialize STUN client
65
+ stun_client_ = std::make_unique<StunClient>();
66
+
67
+ // Initialize NAT detector for advanced NAT characteristics detection
68
+ nat_detector_ = std::make_unique<AdvancedNatDetector>();
69
+
70
+ // Initialize ICE agent if enabled
71
+ initialize_ice_agent();
72
+
73
+ // Generate encryption key
74
+ static_encryption_key_ = encrypted_communication::generate_node_key();
75
+
76
+ // Load configuration (this will generate peer ID if needed)
77
+ load_configuration();
78
+
79
+ // Initialize modules
80
+ initialize_modules();
81
+
82
+ // Initialize NAT traversal
83
+ initialize_nat_traversal();
84
+ }
85
+
86
+ RatsClient::~RatsClient() {
87
+ stop();
88
+ // Destroy modules
89
+ destroy_modules();
90
+ }
91
+
92
+ // =========================================================================
93
+ // Modules Initialization and Destruction
94
+ // =========================================================================
95
+
96
+ void RatsClient::initialize_modules() {
97
+ // Initialize GossipSub
98
+ if (!gossipsub_) {
99
+ LOG_CLIENT_INFO("Initializing GossipSub");
100
+ gossipsub_ = std::make_unique<GossipSub>(*this);
101
+ }
102
+
103
+ // Initialize File Transfer Manager
104
+ if (!file_transfer_manager_) {
105
+ LOG_CLIENT_INFO("Initializing File Transfer Manager");
106
+ file_transfer_manager_ = std::make_unique<FileTransferManager>(*this);
107
+ }
108
+ }
109
+
110
+ void RatsClient::destroy_modules() {
111
+ if (gossipsub_) {
112
+ LOG_CLIENT_INFO("Destroying GossipSub");
113
+ gossipsub_.reset();
114
+ }
115
+
116
+ if (file_transfer_manager_) {
117
+ LOG_CLIENT_INFO("Destroying File Transfer Manager");
118
+ file_transfer_manager_.reset();
119
+ }
120
+ }
121
+
122
+ // =========================================================================
123
+ // Core Lifecycle Management
124
+ // =========================================================================
125
+
126
+
127
+ bool RatsClient::start() {
128
+ if (running_.load()) {
129
+ LOG_CLIENT_WARN("RatsClient is already running");
130
+ return false;
131
+ }
132
+
133
+ LOG_CLIENT_INFO("Starting RatsClient on port " << listen_port_ <<
134
+ (bind_address_.empty() ? "" : " bound to " + bind_address_));
135
+
136
+ // Print system information for debugging and log analysis
137
+ SystemInfo sys_info = get_system_info();
138
+ LOG_CLIENT_INFO("=== System Information ===");
139
+ LOG_CLIENT_INFO("OS: " << sys_info.os_name << " " << sys_info.os_version);
140
+ LOG_CLIENT_INFO("Architecture: " << sys_info.architecture);
141
+ LOG_CLIENT_INFO("Hostname: " << sys_info.hostname);
142
+ LOG_CLIENT_INFO("CPU: " << sys_info.cpu_model);
143
+ LOG_CLIENT_INFO("CPU Cores: " << sys_info.cpu_cores << " physical, " << sys_info.cpu_logical_cores << " logical");
144
+ LOG_CLIENT_INFO("Memory: " << sys_info.total_memory_mb << " MB total, " << sys_info.available_memory_mb << " MB available");
145
+ LOG_CLIENT_INFO("===========================");
146
+
147
+ // Initialize socket library first (required for all socket operations)
148
+ init_socket_library();
149
+
150
+ // Initialize encryption
151
+ if (!initialize_encryption(encryption_enabled_)) {
152
+ LOG_CLIENT_ERROR("Failed to initialize encryption");
153
+ return false;
154
+ }
155
+
156
+ // Initialize local interface addresses for connection blocking
157
+ initialize_local_addresses();
158
+
159
+ // Discover public IP address via STUN and add to ignore list
160
+ if (!discover_and_ignore_public_ip()) {
161
+ LOG_CLIENT_WARN("Failed to discover public IP via STUN - continuing without it");
162
+ }
163
+
164
+ // Create dual-stack server socket (supports both IPv4 and IPv6)
165
+ server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
166
+ if (!is_valid_socket(server_socket_)) {
167
+ LOG_CLIENT_ERROR("Failed to create dual-stack server socket on port " << listen_port_ <<
168
+ (bind_address_.empty() ? "" : " bound to " + bind_address_));
169
+ return false;
170
+ }
171
+
172
+ // Update listen_port_ with actual bound port if ephemeral port was requested
173
+ if (listen_port_ == 0) {
174
+ listen_port_ = get_ephemeral_port(server_socket_);
175
+ if (listen_port_ == 0) {
176
+ LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
177
+ } else {
178
+ LOG_CLIENT_INFO("Server bound to ephemeral port " << listen_port_);
179
+ }
180
+ }
181
+
182
+ running_.store(true);
183
+
184
+ // Start server thread
185
+ server_thread_ = std::thread(&RatsClient::server_loop, this);
186
+
187
+ // Start management thread
188
+ management_thread_ = std::thread(&RatsClient::management_loop, this);
189
+
190
+ // Start GossipSub
191
+ if (gossipsub_ && !gossipsub_->start()) {
192
+ LOG_CLIENT_WARN("Failed to start GossipSub - continuing without it");
193
+ }
194
+
195
+ LOG_CLIENT_INFO("RatsClient started successfully on port " << listen_port_);
196
+
197
+ // Attempt to reconnect to saved peers
198
+ add_managed_thread(std::thread([this]() {
199
+ // Give the server some time to fully initialize
200
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
201
+ int reconnect_attempts = load_and_reconnect_peers();
202
+ if (reconnect_attempts > 0) {
203
+ LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
204
+ }
205
+
206
+ // Also attempt to reconnect to historical peers if not at peer limit
207
+ if (!is_peer_limit_reached()) {
208
+ std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Give current peers time to connect
209
+ int historical_attempts = load_and_reconnect_historical_peers();
210
+ if (historical_attempts > 0) {
211
+ LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
212
+ }
213
+ }
214
+ }), "peer-reconnection");
215
+
216
+ return true;
217
+ }
218
+
219
+ void RatsClient::stop() {
220
+ if (!running_.load()) {
221
+ return;
222
+ }
223
+
224
+ LOG_CLIENT_INFO("Stopping RatsClient");
225
+
226
+ // Stop GossipSub (can broadcast stop message)
227
+ if (gossipsub_) {
228
+ gossipsub_->stop();
229
+ }
230
+
231
+
232
+ // Trigger immediate shutdown of all background threads
233
+ shutdown_all_threads();
234
+
235
+ // Stop DHT discovery (this will also stop automatic discovery)
236
+ stop_dht_discovery();
237
+
238
+ // Stop mDNS discovery
239
+ stop_mdns_discovery();
240
+
241
+ // Cleanup ICE resources
242
+ cleanup_ice_resources();
243
+
244
+ // Close server socket to break accept loop
245
+ if (is_valid_socket(server_socket_)) {
246
+ close_socket(server_socket_, true);
247
+ server_socket_ = INVALID_SOCKET_VALUE;
248
+ }
249
+
250
+ // Close all peer connections
251
+ {
252
+ std::lock_guard<std::mutex> lock(peers_mutex_);
253
+ LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
254
+ for (const auto& pair : peers_) {
255
+ const RatsPeer& peer = pair.second;
256
+ close_socket(peer.socket, true);
257
+ }
258
+ peers_.clear();
259
+ socket_to_peer_id_.clear();
260
+ address_to_peer_id_.clear();
261
+ }
262
+
263
+
264
+
265
+ // Wait for server thread to finish
266
+ if (server_thread_.joinable()) {
267
+ LOG_CLIENT_DEBUG("Waiting for server thread to finish");
268
+ server_thread_.join();
269
+ }
270
+
271
+ // Wait for management thread to finish
272
+ if (management_thread_.joinable()) {
273
+ LOG_CLIENT_DEBUG("Waiting for management thread to finish");
274
+ management_thread_.join();
275
+ }
276
+
277
+ // Join all managed threads for graceful cleanup
278
+ join_all_active_threads();
279
+
280
+ cleanup_socket_library();
281
+
282
+ // Save configuration before stopping
283
+ save_configuration();
284
+
285
+ LOG_CLIENT_INFO("RatsClient stopped successfully");
286
+ }
287
+
288
+ void RatsClient::shutdown_all_threads() {
289
+ LOG_CLIENT_INFO("Initiating shutdown of all background threads");
290
+
291
+ // Signal all threads to stop
292
+ running_.store(false);
293
+
294
+ // Call parent class to handle thread management shutdown
295
+ ThreadManager::shutdown_all_threads();
296
+ }
297
+
298
+ bool RatsClient::is_running() const {
299
+ return running_.load();
300
+ }
301
+
302
+ // =========================================================================
303
+ // Utility Methods
304
+ // =========================================================================
305
+
306
+ int RatsClient::get_listen_port() const {
307
+ return listen_port_;
308
+ }
309
+
310
+ std::string RatsClient::get_bind_address() const {
311
+ return bind_address_;
312
+ }
313
+
314
+ // =========================================================================
315
+ // Managment loops
316
+ // =========================================================================
317
+
318
+ void RatsClient::server_loop() {
319
+ LOG_SERVER_INFO("Server loop started");
320
+
321
+ while (running_.load()) {
322
+ socket_t client_socket = accept_client(server_socket_);
323
+ if (!is_valid_socket(client_socket)) {
324
+ if (running_.load()) {
325
+ LOG_SERVER_ERROR("Failed to accept client connection");
326
+ }
327
+ break;
328
+ }
329
+
330
+ // Get peer address information
331
+ std::string peer_address = get_peer_address(client_socket);
332
+ if (peer_address.empty()) {
333
+ LOG_SERVER_ERROR("Failed to get peer address for incoming connection");
334
+ close_socket(client_socket);
335
+ continue;
336
+ }
337
+
338
+ // Parse IP and port from peer_address
339
+ std::string ip;
340
+ int port = 0;
341
+ if (!parse_address_string(peer_address, ip, port)) {
342
+ LOG_SERVER_ERROR("Failed to parse peer address from incoming connection: " << peer_address);
343
+ close_socket(client_socket);
344
+ continue;
345
+ }
346
+
347
+ std::string normalized_peer_address = normalize_peer_address(ip, port);
348
+
349
+ // Check if peer limit is reached
350
+ if (is_peer_limit_reached()) {
351
+ LOG_SERVER_INFO("Peer limit reached (" << max_peers_ << "), rejecting connection from " << normalized_peer_address);
352
+ close_socket(client_socket);
353
+ continue;
354
+ }
355
+
356
+ // Check if we're already connected to this peer
357
+ if (is_already_connected_to_address(normalized_peer_address)) {
358
+ LOG_SERVER_INFO("Already connected to peer " << normalized_peer_address << ", rejecting duplicate connection");
359
+ close_socket(client_socket);
360
+ continue;
361
+ }
362
+
363
+ // Initialize encryption for incoming connection
364
+ if (is_encryption_enabled()) {
365
+ if (!encrypted_communication::initialize_incoming_connection(client_socket)) {
366
+ LOG_SERVER_ERROR("Failed to initialize encryption for incoming connection from " << normalized_peer_address);
367
+ close_socket(client_socket);
368
+ continue;
369
+ }
370
+ }
371
+
372
+ // Generate unique hash ID for this incoming client
373
+ std::string connection_info = "incoming_from_" + peer_address;
374
+ std::string peer_hash_id = generate_peer_hash_id(client_socket, connection_info); // Temporary hash ID (real hash ID will be set after handshake)
375
+
376
+ // Create RatsPeer object for incoming connection
377
+ {
378
+ std::lock_guard<std::mutex> lock(peers_mutex_);
379
+ RatsPeer new_peer(peer_hash_id, ip, port, client_socket, normalized_peer_address, false); // false = incoming connection
380
+ new_peer.encryption_enabled = is_encryption_enabled();
381
+ add_peer_unlocked(new_peer);
382
+ }
383
+
384
+ // Start a thread to handle this client
385
+ LOG_SERVER_DEBUG("Starting thread for client " << peer_hash_id << " from " << peer_address);
386
+ add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
387
+ "client-handler-" + peer_hash_id.substr(0, 8));
388
+
389
+ // Note: Connection callback will be called after handshake completion in handle_client
390
+ }
391
+
392
+ LOG_SERVER_INFO("Server loop ended");
393
+ }
394
+
395
+ void RatsClient::management_loop() {
396
+ LOG_CLIENT_INFO("Management loop started");
397
+
398
+ while (running_.load()) {
399
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
400
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(30), [this] { return !running_.load(); })) {
401
+ break; // Exit if shutdown requested
402
+ }
403
+
404
+ // Periodically cleanup finished threads
405
+ try {
406
+ cleanup_finished_threads();
407
+ LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
408
+ } catch (const std::exception& e) {
409
+ LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
410
+ }
411
+ }
412
+
413
+ LOG_CLIENT_INFO("Management loop ended");
414
+ }
415
+
416
+
417
+ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_hash_id) {
418
+ LOG_CLIENT_INFO("Started handling client: " << peer_hash_id);
419
+
420
+ bool handshake_completed = false;
421
+ bool noise_handshake_completed = false;
422
+ auto last_timeout_check = std::chrono::steady_clock::now();
423
+
424
+ // Check if encryption is enabled for this connection
425
+ bool encryption_enabled = is_encryption_enabled();
426
+
427
+ while (running_.load()) {
428
+ std::string data;
429
+
430
+ // Handle encryption handshake first if enabled
431
+ LOG_CLIENT_DEBUG("Checking handshake state: encryption_enabled=" << encryption_enabled << ", noise_handshake_completed=" << noise_handshake_completed);
432
+ if (encryption_enabled && !noise_handshake_completed) {
433
+ LOG_CLIENT_DEBUG("Entering encryption handshake handling for socket " << client_socket);
434
+ // Check if noise handshake is completed
435
+ if (encrypted_communication::is_handshake_completed(client_socket)) {
436
+ noise_handshake_completed = true;
437
+ LOG_CLIENT_INFO("Noise handshake completed for peer " << peer_hash_id);
438
+ // Update peer state
439
+ {
440
+ std::lock_guard<std::mutex> lock(peers_mutex_);
441
+ auto it = socket_to_peer_id_.find(client_socket);
442
+ if (it != socket_to_peer_id_.end()) {
443
+ auto peer_it = peers_.find(it->second);
444
+ if (peer_it != peers_.end()) {
445
+ peer_it->second.noise_handshake_completed = true;
446
+ // For outgoing connections, send application handshake after noise handshake completes
447
+ if (peer_it->second.is_outgoing) {
448
+ if (!send_handshake_unlocked(client_socket, get_our_peer_id())) {
449
+ LOG_CLIENT_ERROR("Failed to send application handshake after noise completion for peer " << peer_hash_id);
450
+ break;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ } else {
457
+ // For responders and ongoing handshake processing, try to continue the handshake
458
+ LOG_CLIENT_DEBUG("Attempting to process handshake for socket " << client_socket);
459
+ if (!encrypted_communication::perform_handshake(client_socket)) {
460
+ LOG_CLIENT_ERROR("Encryption handshake failed for peer " << peer_hash_id);
461
+ break;
462
+ }
463
+ // Continue to check if handshake completed
464
+ continue;
465
+ }
466
+ }
467
+
468
+ // Receive data (encrypted or unencrypted based on settings)
469
+ LOG_CLIENT_DEBUG("Receiving data: encryption_enabled=" << encryption_enabled << ", noise_handshake_completed=" << noise_handshake_completed);
470
+ if (encryption_enabled && noise_handshake_completed) {
471
+ LOG_CLIENT_DEBUG("Receiving encrypted data from socket " << client_socket);
472
+ auto binary_data = encrypted_communication::receive_tcp_data_encrypted(client_socket);
473
+ data = std::string(binary_data.begin(), binary_data.end());
474
+ } else if (encryption_enabled && !noise_handshake_completed) {
475
+ LOG_CLIENT_DEBUG("Processing handshake for socket " << client_socket);
476
+ // During handshake, let the encrypted socket handle data reception and processing
477
+ // The perform_handshake function will internally call receive_tcp_data and process handshake messages
478
+ if (!encrypted_communication::perform_handshake(client_socket)) {
479
+ LOG_CLIENT_ERROR("Encryption handshake failed for peer " << peer_hash_id);
480
+ break;
481
+ }
482
+ // Continue to check if handshake completed
483
+ continue;
484
+ } else {
485
+ LOG_CLIENT_DEBUG("Receiving unencrypted data from socket " << client_socket);
486
+ // Always use framed message reception for reliable large message handling
487
+ data = receive_tcp_string_framed(client_socket);
488
+ }
489
+
490
+ if (data.empty()) {
491
+ // Check if this is a timeout or actual connection close
492
+ if (!handshake_completed) {
493
+ // Check for handshake timeout one more time before giving up
494
+ auto now = std::chrono::steady_clock::now();
495
+ if (now - last_timeout_check >= std::chrono::seconds(1)) {
496
+ check_handshake_timeouts();
497
+ last_timeout_check = now;
498
+
499
+ // Check if handshake has failed due to timeout
500
+ std::lock_guard<std::mutex> lock(peers_mutex_);
501
+ auto it = socket_to_peer_id_.find(client_socket);
502
+ if (it != socket_to_peer_id_.end()) {
503
+ auto peer_it = peers_.find(it->second);
504
+ if (peer_it != peers_.end() && peer_it->second.is_handshake_failed()) {
505
+ LOG_CLIENT_ERROR("Handshake failed for peer " << peer_hash_id);
506
+ break;
507
+ }
508
+ }
509
+ }
510
+ }
511
+ break; // Connection closed or error
512
+ }
513
+
514
+ LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << data.substr(0, 50) << (data.length() > 50 ? "..." : ""));
515
+
516
+ // Check for handshake timeout and failure
517
+ if (!handshake_completed) {
518
+ bool should_exit = false;
519
+
520
+ {
521
+ std::lock_guard<std::mutex> lock(peers_mutex_);
522
+ auto it = socket_to_peer_id_.find(client_socket);
523
+ if (it != socket_to_peer_id_.end()) {
524
+ auto peer_it = peers_.find(it->second);
525
+ if (peer_it != peers_.end()) {
526
+ const RatsPeer& peer = peer_it->second;
527
+ if (peer.is_handshake_failed()) {
528
+ LOG_CLIENT_ERROR("Handshake failed for peer " << peer_hash_id);
529
+ should_exit = true;
530
+ }
531
+ }
532
+ }
533
+ }
534
+
535
+ if (should_exit) {
536
+ break; // Exit loop to disconnect
537
+ }
538
+
539
+ // Check for handshake timeout
540
+ auto now = std::chrono::steady_clock::now();
541
+ if (now - last_timeout_check >= std::chrono::seconds(1)) {
542
+ check_handshake_timeouts();
543
+ last_timeout_check = now;
544
+ }
545
+ }
546
+
547
+ // Handle handshake messages
548
+ if (is_handshake_message(data)) {
549
+ if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
550
+ LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
551
+ break; // Exit loop to disconnect
552
+ }
553
+
554
+ // Check if handshake just completed and trigger notifications
555
+ if (!handshake_completed) {
556
+ RatsPeer peer_copy;
557
+ bool should_notify_connection = false;
558
+ bool should_broadcast_exchange = false;
559
+
560
+ {
561
+ std::lock_guard<std::mutex> lock(peers_mutex_);
562
+ auto it = socket_to_peer_id_.find(client_socket);
563
+ if (it != socket_to_peer_id_.end()) {
564
+ auto peer_it = peers_.find(it->second);
565
+ if (peer_it != peers_.end()) {
566
+ const RatsPeer& peer = peer_it->second;
567
+ if (peer.is_handshake_completed()) {
568
+ handshake_completed = true;
569
+ should_notify_connection = true;
570
+ should_broadcast_exchange = true;
571
+ peer_copy = peer; // Copy peer data
572
+
573
+ LOG_CLIENT_INFO("Handshake completed for peer " << peer_hash_id << " (peer_id: " << peer.peer_id << ")");
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ // Call callbacks and methods outside of mutex to avoid deadlock
580
+ if (should_notify_connection) {
581
+ if (connection_callback_) {
582
+ connection_callback_(client_socket, peer_copy.peer_id);
583
+ }
584
+
585
+ if (gossipsub_) {
586
+ gossipsub_->handle_peer_connected(peer_copy.peer_id);
587
+ }
588
+
589
+ // Broadcast peer exchange message to other peers
590
+ broadcast_peer_exchange_message(peer_copy);
591
+
592
+ // Send peers request to the newly connected peer to discover more peers
593
+ if (peer_copy.is_outgoing) {
594
+ send_peers_request(client_socket, peer_copy.peer_id);
595
+ }
596
+
597
+ // Initiate NAT traversal information exchange
598
+ send_nat_info_to_peer(client_socket, peer_copy.peer_id);
599
+
600
+ // If ICE is enabled and this is an outgoing connection, initiate ICE coordination
601
+ if (ice_agent_ && ice_agent_->is_running() && peer_copy.is_outgoing) {
602
+ if (should_initiate_ice_coordination(peer_copy.peer_id)) {
603
+ mark_ice_coordination_in_progress(peer_copy.peer_id);
604
+
605
+ // Start ICE coordination in background thread with proper exception handling
606
+ add_managed_thread(std::thread([this, peer_id = peer_copy.peer_id, host = peer_copy.ip, port = peer_copy.port]() {
607
+ try {
608
+ // Use conditional variable for responsive shutdown
609
+ {
610
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
611
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !running_.load(); })) {
612
+ // Clean up tracking before exit
613
+ cleanup_ice_coordination_for_peer(peer_id);
614
+ return; // Exit if shutdown requested
615
+ }
616
+ }
617
+
618
+ // Initiate ICE coordination
619
+ initiate_ice_with_peer(peer_id, host, port);
620
+
621
+ } catch (const std::exception& e) {
622
+ LOG_CLIENT_ERROR("Exception in ICE coordination thread for peer " << peer_id << ": " << e.what());
623
+ } catch (...) {
624
+ LOG_CLIENT_ERROR("Unknown exception in ICE coordination thread for peer " << peer_id);
625
+ }
626
+
627
+ // Clean up tracking when done
628
+ cleanup_ice_coordination_for_peer(peer_id);
629
+ }), "ice-coordination-" + peer_copy.peer_id.substr(0, 8));
630
+ } else {
631
+ LOG_CLIENT_DEBUG("ICE coordination already in progress for peer " << peer_copy.peer_id << " - skipping duplicate attempt");
632
+ }
633
+ }
634
+
635
+ // Save configuration after a new peer connects to keep peer list current
636
+ if (running_.load()) {
637
+ add_managed_thread(std::thread([this]() {
638
+ if (running_.load()) {
639
+ save_configuration();
640
+ }
641
+ }), "config-save");
642
+ }
643
+ }
644
+ }
645
+
646
+ continue; // Don't process handshake messages as regular data
647
+ }
648
+
649
+ // Only process regular data after handshake is completed (and noise handshake if encryption is enabled)
650
+ if (handshake_completed && (!encryption_enabled || noise_handshake_completed)) {
651
+ // Convert string data to binary for header parsing
652
+ std::vector<uint8_t> received_data(data.begin(), data.end());
653
+
654
+ // Try to parse message header first
655
+ MessageHeader header;
656
+ std::vector<uint8_t> payload;
657
+ if (parse_message_with_header(received_data, header, payload)) {
658
+ // Message has valid header - call appropriate callback based on type
659
+ std::string peer_id = get_peer_id(client_socket);
660
+
661
+ switch (header.type) {
662
+ case MessageDataType::BINARY: {
663
+ LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
664
+ // Try to handle as file transfer data first
665
+ bool handled = false;
666
+ if (file_transfer_manager_) {
667
+ handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
668
+ }
669
+
670
+ // If not a file transfer chunk, call user's binary callback
671
+ if (!handled && binary_data_callback_) {
672
+ binary_data_callback_(client_socket, peer_id, payload);
673
+ }
674
+ break;
675
+ }
676
+
677
+ case MessageDataType::STRING:
678
+ LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
679
+ if (string_data_callback_) {
680
+ std::string string_data(payload.begin(), payload.end());
681
+ string_data_callback_(client_socket, peer_id, string_data);
682
+ }
683
+ break;
684
+
685
+ case MessageDataType::JSON: {
686
+ LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
687
+ // Parse JSON payload
688
+ std::string json_string(payload.begin(), payload.end());
689
+ nlohmann::json json_msg;
690
+ if (parse_json_message(json_string, json_msg)) {
691
+ // Check if it's a rats protocol message
692
+ if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
693
+ handle_rats_message(client_socket, peer_id, json_msg);
694
+ } else {
695
+ // Regular JSON data - call JSON callback
696
+ if (json_data_callback_) {
697
+ json_data_callback_(client_socket, peer_id, json_msg);
698
+ }
699
+ }
700
+ } else {
701
+ LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id);
702
+ }
703
+ break;
704
+ }
705
+
706
+ default:
707
+ LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
708
+ break;
709
+ }
710
+ } else {
711
+ // No header found
712
+ LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
713
+ }
714
+ } else {
715
+ LOG_CLIENT_WARN("Received non-handshake data from " << peer_hash_id << " before handshake completion - ignoring");
716
+ }
717
+ }
718
+
719
+ // Get current peer ID before cleanup for disconnect callback
720
+ std::string current_peer_id = get_peer_id(client_socket);
721
+
722
+ // Clean up
723
+ remove_peer(client_socket);
724
+
725
+ // Clean up encryption state
726
+ if (encryption_enabled) {
727
+ encrypted_communication::cleanup_socket(client_socket);
728
+ }
729
+
730
+ close_socket(client_socket);
731
+
732
+ // Notify disconnect callback only if handshake was completed
733
+ if (handshake_completed && disconnect_callback_) {
734
+ disconnect_callback_(client_socket, current_peer_id);
735
+ }
736
+
737
+ if (handshake_completed && gossipsub_) {
738
+ gossipsub_->handle_peer_disconnected(current_peer_id);
739
+ }
740
+
741
+ // Save configuration after a validated peer disconnects to update the saved peer list
742
+ if (handshake_completed && running_.load()) {
743
+ // Save configuration in a separate thread to avoid blocking
744
+ add_managed_thread(std::thread([this]() {
745
+ if (running_.load()) {
746
+ save_configuration();
747
+ }
748
+ }), "config-save-disconnect");
749
+ }
750
+
751
+ LOG_CLIENT_INFO("Client disconnected: " << peer_hash_id);
752
+ }
753
+
754
+ // Handshake protocol implementation
755
+ std::string RatsClient::create_handshake_message(const std::string& message_type, const std::string& our_peer_id) const {
756
+ auto now = std::chrono::high_resolution_clock::now();
757
+ auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
758
+
759
+ // Use nlohmann::json for proper JSON serialization
760
+ nlohmann::json handshake_msg;
761
+ {
762
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
763
+ handshake_msg["protocol"] = custom_protocol_name_;
764
+ handshake_msg["version"] = custom_protocol_version_;
765
+ }
766
+ handshake_msg["peer_id"] = our_peer_id;
767
+ handshake_msg["message_type"] = message_type;
768
+ handshake_msg["timestamp"] = timestamp;
769
+
770
+ return handshake_msg.dump();
771
+ }
772
+
773
+ bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMessage& out_msg) const {
774
+ try {
775
+ // Use nlohmann::json for proper JSON parsing
776
+ nlohmann::json json_msg = nlohmann::json::parse(message);
777
+
778
+ // Clear the output structure
779
+ out_msg = HandshakeMessage{};
780
+
781
+ // Extract fields using nlohmann::json
782
+ out_msg.protocol = json_msg.value("protocol", "");
783
+ out_msg.version = json_msg.value("version", "");
784
+ out_msg.peer_id = json_msg.value("peer_id", "");
785
+ out_msg.message_type = json_msg.value("message_type", "");
786
+ // Tolerate missing timestamp to avoid hard dependency on remote system clock
787
+ out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
788
+
789
+ return true;
790
+
791
+ } catch (const nlohmann::json::exception& e) {
792
+ LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
793
+ return false;
794
+ } catch (const std::exception& e) {
795
+ LOG_CLIENT_ERROR("Failed to parse handshake message: " << e.what());
796
+ return false;
797
+ }
798
+ }
799
+
800
+ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
801
+ std::string expected_protocol;
802
+ std::string expected_version;
803
+ {
804
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
805
+ expected_protocol = custom_protocol_name_;
806
+ expected_version = custom_protocol_version_;
807
+ }
808
+
809
+ // Validate protocol
810
+ if (msg.protocol != expected_protocol) {
811
+ LOG_CLIENT_WARN("Invalid handshake protocol: " << msg.protocol << " (expected: " << expected_protocol << ")");
812
+ return false;
813
+ }
814
+
815
+ // Validate version (for now, only accept exact version match)
816
+ if (msg.version != expected_version) {
817
+ LOG_CLIENT_WARN("Unsupported protocol version: " << msg.version << " (expected: " << expected_version << ")");
818
+ return false;
819
+ }
820
+
821
+ // Validate message type
822
+ if (msg.message_type != "handshake") {
823
+ LOG_CLIENT_WARN("Invalid handshake message type: " << msg.message_type);
824
+ return false;
825
+ }
826
+
827
+ // Validate peer_id (must not be empty)
828
+ if (msg.peer_id.empty()) {
829
+ LOG_CLIENT_WARN("Empty peer_id in handshake message");
830
+ return false;
831
+ }
832
+
833
+ // Soft-validate timestamp to avoid rejecting valid peers due to clock skew
834
+ if (msg.timestamp == 0) {
835
+ LOG_CLIENT_WARN("Handshake missing timestamp; accepting to avoid clock-skew rejection");
836
+ } else {
837
+ auto now = std::chrono::high_resolution_clock::now();
838
+ auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
839
+ int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
840
+ const int64_t allowed_skew_ms = 10LL * 60LL * 1000LL; // 10 minutes tolerance
841
+ if (time_diff > allowed_skew_ms) {
842
+ LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << allowed_skew_ms << "ms; accepting to be tolerant of clock skew");
843
+ }
844
+ }
845
+
846
+ return true;
847
+ }
848
+
849
+ bool RatsClient::is_handshake_message(const std::string& message) const {
850
+ try {
851
+ std::string json_to_parse = message;
852
+
853
+ // Check if message has our message header (starts with "RATS" magic)
854
+ std::vector<uint8_t> message_data(message.begin(), message.end());
855
+ MessageHeader header;
856
+ std::vector<uint8_t> payload;
857
+
858
+ if (parse_message_with_header(message_data, header, payload)) {
859
+ // Message has valid header - extract the JSON payload
860
+ if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
861
+ json_to_parse = std::string(payload.begin(), payload.end());
862
+ } else {
863
+ // Handshake messages should be string/JSON type
864
+ return false;
865
+ }
866
+ } else {
867
+ // Message has no header
868
+ return false;
869
+ }
870
+
871
+ // Parse the JSON message
872
+ nlohmann::json json_msg = nlohmann::json::parse(json_to_parse);
873
+ std::string expected_protocol;
874
+ {
875
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
876
+ expected_protocol = custom_protocol_name_;
877
+ }
878
+ return json_msg.value("protocol", "") == expected_protocol &&
879
+ json_msg.value("message_type", "") == "handshake";
880
+ } catch (const std::exception&) {
881
+ return false;
882
+ }
883
+ }
884
+
885
+ // Add this private helper function before send_handshake
886
+ bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
887
+ std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
888
+ LOG_CLIENT_DEBUG("Sending handshake to socket " << socket << ": " << handshake_msg);
889
+
890
+ if (!send_string_to_peer(socket, handshake_msg)) {
891
+ LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
892
+ return false;
893
+ }
894
+
895
+ // Update peer state (assumes peers_mutex_ is already locked)
896
+ auto it = socket_to_peer_id_.find(socket);
897
+ if (it != socket_to_peer_id_.end()) {
898
+ auto peer_it = peers_.find(it->second);
899
+ if (peer_it != peers_.end()) {
900
+ peer_it->second.handshake_state = RatsPeer::HandshakeState::SENT;
901
+ peer_it->second.handshake_start_time = std::chrono::steady_clock::now();
902
+ }
903
+ }
904
+
905
+ return true;
906
+ }
907
+
908
+ bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id) {
909
+ std::lock_guard<std::mutex> lock(peers_mutex_);
910
+ return send_handshake_unlocked(socket, our_peer_id);
911
+ }
912
+
913
+ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::string& message) {
914
+ // Extract JSON payload from message header if present
915
+ std::string json_to_parse = message;
916
+ std::vector<uint8_t> message_data(message.begin(), message.end());
917
+ MessageHeader header;
918
+ std::vector<uint8_t> payload;
919
+
920
+ if (parse_message_with_header(message_data, header, payload)) {
921
+ // Message has valid header - extract the JSON payload
922
+ if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
923
+ json_to_parse = std::string(payload.begin(), payload.end());
924
+ } else {
925
+ LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
926
+ return false;
927
+ }
928
+ } else {
929
+ LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
930
+ return false;
931
+ }
932
+
933
+ HandshakeMessage handshake_msg;
934
+ if (!parse_handshake_message(json_to_parse, handshake_msg)) {
935
+ LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
936
+ return false;
937
+ }
938
+
939
+ if (!validate_handshake_message(handshake_msg)) {
940
+ LOG_CLIENT_ERROR("Invalid handshake message from " << peer_hash_id);
941
+ return false;
942
+ }
943
+
944
+ if (handshake_msg.peer_id == get_our_peer_id()) {
945
+ LOG_CLIENT_INFO("Received handshake from ourselves, ignoring");
946
+ return false;
947
+ }
948
+
949
+ LOG_CLIENT_INFO("Received valid handshake from " << peer_hash_id
950
+ << " (peer_id: " << handshake_msg.peer_id << ")");
951
+
952
+ std::lock_guard<std::mutex> lock(peers_mutex_);
953
+ auto it = socket_to_peer_id_.find(socket);
954
+ if (it == socket_to_peer_id_.end()) {
955
+ LOG_CLIENT_ERROR("Socket " << socket << " not found in peer mapping");
956
+ return false;
957
+ }
958
+
959
+ auto peer_it = peers_.find(it->second);
960
+ if (peer_it == peers_.end()) {
961
+ LOG_CLIENT_ERROR("Peer " << peer_hash_id << " not found in peers");
962
+ return false;
963
+ }
964
+
965
+ if (peers_.find(handshake_msg.peer_id) != peers_.end()) {
966
+ LOG_CLIENT_INFO("Peer " << handshake_msg.peer_id << " already connected, closing duplicate connection");
967
+ // This is a duplicate connection - the existing connection should remain stable
968
+ // Return false to close this duplicate connection, but this is expected behavior
969
+ return false;
970
+ }
971
+
972
+ // Store old peer ID for mapping updates
973
+ std::string old_peer_id = peer_it->second.peer_id;
974
+
975
+ // Update peer mappings with new peer_id if it changed
976
+ if (old_peer_id != handshake_msg.peer_id) {
977
+ // Create a copy of the peer object before erasing it.
978
+ RatsPeer peer_copy = peer_it->second;
979
+
980
+ // Erase the old entry from the main peers map.
981
+ peers_.erase(peer_it);
982
+
983
+ // Update the peer_id within the copied object.
984
+ peer_copy.peer_id = handshake_msg.peer_id;
985
+
986
+ // Insert the updated peer object back into the maps with the new peer_id.
987
+ peers_[peer_copy.peer_id] = peer_copy;
988
+ socket_to_peer_id_[socket] = peer_copy.peer_id;
989
+ address_to_peer_id_[peer_copy.normalized_address] = peer_copy.peer_id;
990
+
991
+ // Find the iterator for the newly inserted peer.
992
+ peer_it = peers_.find(peer_copy.peer_id);
993
+ }
994
+
995
+ RatsPeer& peer = peer_it->second;
996
+
997
+ // Store remote peer information
998
+ peer.version = handshake_msg.version;
999
+
1000
+ // Simplified handshake logic - just one message type
1001
+ if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1002
+ // This is an incoming handshake - send our handshake back
1003
+ if (send_handshake_unlocked(socket, get_our_peer_id())) {
1004
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1005
+ log_handshake_completion_unlocked(peer);
1006
+
1007
+ // Append to historical peers file after successful connection
1008
+ append_peer_to_historical_file(peer);
1009
+
1010
+ return true;
1011
+ } else {
1012
+ peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1013
+ LOG_CLIENT_ERROR("Failed to send handshake response to " << peer_hash_id);
1014
+ return false;
1015
+ }
1016
+ } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
1017
+ // This is a response to our handshake
1018
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1019
+ log_handshake_completion_unlocked(peer);
1020
+
1021
+ // Append to historical peers file after successful connection
1022
+ append_peer_to_historical_file(peer);
1023
+
1024
+ return true;
1025
+ } else {
1026
+ LOG_CLIENT_WARN("Received handshake from " << peer_hash_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1027
+ return false;
1028
+ }
1029
+ }
1030
+
1031
+ void RatsClient::check_handshake_timeouts() {
1032
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1033
+ auto now = std::chrono::steady_clock::now();
1034
+
1035
+ std::vector<std::string> peers_to_remove;
1036
+
1037
+ for (auto& pair : peers_) {
1038
+ RatsPeer& peer = pair.second;
1039
+
1040
+ if (peer.handshake_state != RatsPeer::HandshakeState::COMPLETED &&
1041
+ peer.handshake_state != RatsPeer::HandshakeState::FAILED) {
1042
+
1043
+ auto handshake_duration = std::chrono::duration_cast<std::chrono::seconds>(now - peer.handshake_start_time);
1044
+
1045
+ if (handshake_duration.count() > HANDSHAKE_TIMEOUT_SECONDS) {
1046
+ LOG_CLIENT_WARN("Handshake timeout for peer " << peer.peer_id << " after " << handshake_duration.count() << " seconds");
1047
+ peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1048
+ peers_to_remove.push_back(peer.peer_id);
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ // Remove timed out peers
1054
+ for (const auto& peer_id : peers_to_remove) {
1055
+ auto peer_it = peers_.find(peer_id);
1056
+ if (peer_it != peers_.end()) {
1057
+ socket_t socket = peer_it->second.socket;
1058
+ LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
1059
+
1060
+ // Clean up peer data
1061
+ remove_peer_by_id_unlocked(peer_id);
1062
+ close_socket(socket);
1063
+ }
1064
+ }
1065
+ }
1066
+
1067
+ // =========================================================================
1068
+ // Connection Management
1069
+ // =========================================================================
1070
+
1071
+ bool RatsClient::connect_to_peer(const std::string& host, int port, ConnectionStrategy strategy) {
1072
+ if (!running_.load()) {
1073
+ LOG_CLIENT_ERROR("RatsClient is not running");
1074
+ return false;
1075
+ }
1076
+
1077
+ LOG_CLIENT_INFO("Connecting to peer " << host << ":" << port << " using strategy: " << static_cast<int>(strategy));
1078
+
1079
+ // Create connection attempt result to track the attempt
1080
+ ConnectionAttemptResult result;
1081
+ result.local_nat_type = detect_nat_type();
1082
+ result.remote_nat_type = NatType::UNKNOWN;
1083
+
1084
+ auto start_time = std::chrono::high_resolution_clock::now();
1085
+ bool success = false;
1086
+
1087
+ // Execute connection strategy
1088
+ switch (strategy) {
1089
+ case ConnectionStrategy::DIRECT_ONLY:
1090
+ success = attempt_direct_connection(host, port, result);
1091
+ result.method = "direct";
1092
+ break;
1093
+
1094
+ case ConnectionStrategy::STUN_ASSISTED:
1095
+ success = attempt_stun_assisted_connection(host, port, result);
1096
+ result.method = "stun";
1097
+ break;
1098
+
1099
+ case ConnectionStrategy::ICE_FULL:
1100
+ success = attempt_ice_connection(host, port, result);
1101
+ result.method = "ice";
1102
+ break;
1103
+
1104
+ case ConnectionStrategy::TURN_RELAY:
1105
+ success = attempt_turn_relay_connection(host, port, result);
1106
+ result.method = "turn";
1107
+ break;
1108
+
1109
+ case ConnectionStrategy::AUTO_ADAPTIVE:
1110
+ // Select best strategy based on NAT type
1111
+ std::string best_strategy = select_best_connection_strategy(host, port);
1112
+ result.method = best_strategy;
1113
+
1114
+ if (best_strategy == "direct") {
1115
+ success = attempt_direct_connection(host, port, result);
1116
+ } else if (best_strategy == "stun") {
1117
+ success = attempt_stun_assisted_connection(host, port, result);
1118
+ } else if (best_strategy == "ice") {
1119
+ success = attempt_ice_connection(host, port, result);
1120
+ } else if (best_strategy == "turn") {
1121
+ success = attempt_turn_relay_connection(host, port, result);
1122
+ } else {
1123
+ success = attempt_hole_punch_connection(host, port, result);
1124
+ }
1125
+ break;
1126
+ }
1127
+
1128
+ auto end_time = std::chrono::high_resolution_clock::now();
1129
+ result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
1130
+ result.success = success;
1131
+
1132
+ // Log connection attempt result
1133
+ LOG_CLIENT_INFO("Connection attempt to " << host << ":" << port << " via " << result.method
1134
+ << " completed: " << (success ? "SUCCESS" : "FAILED")
1135
+ << " in " << result.duration.count() << "ms");
1136
+
1137
+ if (!result.error_message.empty()) {
1138
+ LOG_CLIENT_DEBUG("Error details: " << result.error_message);
1139
+ }
1140
+
1141
+ // Update connection statistics
1142
+ std::string peer_address = normalize_peer_address(host, port);
1143
+ update_connection_statistics(peer_address, result);
1144
+
1145
+ // Call advanced connection callback if set
1146
+ if (success && advanced_connection_callback_) {
1147
+ // Find the socket for this connection to pass to callback
1148
+ socket_t connected_socket = INVALID_SOCKET_VALUE;
1149
+ std::string peer_id;
1150
+
1151
+ {
1152
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1153
+ auto addr_it = address_to_peer_id_.find(peer_address);
1154
+ if (addr_it != address_to_peer_id_.end()) {
1155
+ auto peer_it = peers_.find(addr_it->second);
1156
+ if (peer_it != peers_.end()) {
1157
+ connected_socket = peer_it->second.socket;
1158
+ peer_id = peer_it->second.peer_id;
1159
+ }
1160
+ }
1161
+ }
1162
+
1163
+ if (is_valid_socket(connected_socket)) {
1164
+ advanced_connection_callback_(connected_socket, peer_id, result);
1165
+ }
1166
+ }
1167
+
1168
+ return success;
1169
+ }
1170
+
1171
+ void RatsClient::disconnect_peer(socket_t socket) {
1172
+ remove_peer(socket);
1173
+
1174
+ // Clean up encryption state
1175
+ if (is_encryption_enabled()) {
1176
+ encrypted_communication::cleanup_socket(socket);
1177
+ }
1178
+
1179
+ close_socket(socket);
1180
+ }
1181
+
1182
+ void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
1183
+ socket_t socket = get_peer_socket_by_id(peer_id);
1184
+ if (is_valid_socket(socket)) {
1185
+ disconnect_peer(socket);
1186
+ }
1187
+ }
1188
+
1189
+ // Helper methods for peer management
1190
+ void RatsClient::add_peer(const RatsPeer& peer) {
1191
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1192
+ add_peer_unlocked(peer);
1193
+ }
1194
+
1195
+ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1196
+ // Assumes peers_mutex_ is already locked
1197
+ peers_[peer.peer_id] = peer;
1198
+ socket_to_peer_id_[peer.socket] = peer.peer_id;
1199
+ address_to_peer_id_[peer.normalized_address] = peer.peer_id;
1200
+ }
1201
+
1202
+ void RatsClient::remove_peer(socket_t socket) {
1203
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1204
+ auto it = socket_to_peer_id_.find(socket);
1205
+ if (it != socket_to_peer_id_.end()) {
1206
+ remove_peer_by_id_unlocked(it->second);
1207
+ }
1208
+ }
1209
+
1210
+ void RatsClient::remove_peer_by_id(const std::string& peer_id) {
1211
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1212
+ remove_peer_by_id_unlocked(peer_id);
1213
+ }
1214
+
1215
+ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1216
+ // Assumes peers_mutex_ is already locked
1217
+
1218
+ // Make a copy of peer_id to avoid use-after-free if the reference points to memory that gets freed
1219
+ std::string peer_id_copy = peer_id;
1220
+
1221
+ auto it = peers_.find(peer_id_copy);
1222
+ if (it != peers_.end()) {
1223
+ // Copy the values we need before erasing to avoid use-after-free
1224
+ socket_t peer_socket = it->second.socket;
1225
+ std::string peer_normalized_address = it->second.normalized_address;
1226
+
1227
+ socket_to_peer_id_.erase(peer_socket);
1228
+ address_to_peer_id_.erase(peer_normalized_address);
1229
+ peers_.erase(it);
1230
+
1231
+ // Clean up socket-specific mutex
1232
+ cleanup_socket_send_mutex(peer_socket);
1233
+
1234
+ // Clean up ICE coordination tracking for this peer
1235
+ cleanup_ice_coordination_for_peer(peer_id_copy);
1236
+ }
1237
+ }
1238
+
1239
+ bool RatsClient::is_already_connected_to_address(const std::string& normalized_address) const {
1240
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1241
+ return address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end();
1242
+ }
1243
+
1244
+ void RatsClient::add_ignored_address(const std::string& ip_address) {
1245
+ std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1246
+
1247
+ // Check if already in the list
1248
+ if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), ip_address) == local_interface_addresses_.end()) {
1249
+ local_interface_addresses_.push_back(ip_address);
1250
+ LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
1251
+ } else {
1252
+ LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
1253
+ }
1254
+ }
1255
+
1256
+
1257
+ // Local interface address blocking methods
1258
+ void RatsClient::initialize_local_addresses() {
1259
+ LOG_CLIENT_INFO("Initializing local interface addresses for connection blocking");
1260
+
1261
+ std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1262
+
1263
+ // Get all local interface addresses using network_utils
1264
+ local_interface_addresses_ = network_utils::get_local_interface_addresses();
1265
+
1266
+ // Add common localhost addresses if not already present
1267
+ std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
1268
+ for (const auto& addr : localhost_addrs) {
1269
+ if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
1270
+ local_interface_addresses_.push_back(addr);
1271
+ }
1272
+ }
1273
+
1274
+ LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
1275
+ for (const auto& addr : local_interface_addresses_) {
1276
+ LOG_CLIENT_INFO(" - " << addr);
1277
+ }
1278
+ }
1279
+
1280
+ void RatsClient::refresh_local_addresses() {
1281
+ LOG_CLIENT_DEBUG("Refreshing local interface addresses");
1282
+
1283
+ std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1284
+
1285
+ // Clear old addresses and get fresh ones
1286
+ local_interface_addresses_.clear();
1287
+ local_interface_addresses_ = network_utils::get_local_interface_addresses();
1288
+
1289
+ // Add common localhost addresses if not already present
1290
+ std::vector<std::string> localhost_addrs = {"127.0.0.1", "::1", "0.0.0.0", "::"};
1291
+ for (const auto& addr : localhost_addrs) {
1292
+ if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
1293
+ local_interface_addresses_.push_back(addr);
1294
+ }
1295
+ }
1296
+
1297
+ LOG_CLIENT_DEBUG("Refreshed " << local_interface_addresses_.size() << " local addresses");
1298
+ }
1299
+
1300
+ bool RatsClient::is_blocked_address(const std::string& ip_address) const {
1301
+ std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1302
+
1303
+ // Check against our stored local addresses
1304
+ for (const auto& local_addr : local_interface_addresses_) {
1305
+ if (local_addr == ip_address) {
1306
+ return true;
1307
+ }
1308
+ }
1309
+
1310
+ return false;
1311
+ }
1312
+
1313
+ bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
1314
+ // Always block connections to ourselves (same port)
1315
+ if (port == listen_port_) {
1316
+ if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost" || ip == "0.0.0.0" || ip == "::") {
1317
+ LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
1318
+ return true;
1319
+ }
1320
+ }
1321
+
1322
+ // For localhost addresses on different ports, allow the connection (for testing)
1323
+ if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost") {
1324
+ LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
1325
+ return false;
1326
+ }
1327
+
1328
+ // Check if the IP is a non-localhost local interface address
1329
+ if (is_blocked_address(ip)) {
1330
+ LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
1331
+ return true;
1332
+ }
1333
+
1334
+ return false;
1335
+ }
1336
+
1337
+ // =========================================================================
1338
+ // Data Transmission Methods
1339
+ // =========================================================================
1340
+
1341
+ // Helper method to create a message with header
1342
+ std::vector<uint8_t> RatsClient::create_message_with_header(const std::vector<uint8_t>& payload, MessageDataType type) {
1343
+ MessageHeader header(type);
1344
+ std::vector<uint8_t> header_bytes = header.serialize();
1345
+
1346
+ // Combine header + payload
1347
+ std::vector<uint8_t> message;
1348
+ message.reserve(header_bytes.size() + payload.size());
1349
+ message.insert(message.end(), header_bytes.begin(), header_bytes.end());
1350
+ message.insert(message.end(), payload.begin(), payload.end());
1351
+
1352
+ return message;
1353
+ }
1354
+
1355
+ // Helper method to parse message header and extract payload
1356
+ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message, MessageHeader& header, std::vector<uint8_t>& payload) const {
1357
+ // Check if message is large enough to contain header
1358
+ if (message.size() < MessageHeader::HEADER_SIZE) {
1359
+ LOG_CLIENT_DEBUG("Message too small to contain header: " << message.size() << " bytes");
1360
+ return false;
1361
+ }
1362
+
1363
+ // Extract header bytes
1364
+ std::vector<uint8_t> header_bytes(message.begin(), message.begin() + MessageHeader::HEADER_SIZE);
1365
+
1366
+ // Parse header
1367
+ if (!MessageHeader::deserialize(header_bytes, header)) {
1368
+ LOG_CLIENT_DEBUG("Failed to parse message header - invalid magic number or format");
1369
+ return false;
1370
+ }
1371
+
1372
+ // Validate header
1373
+ if (!header.is_valid_type()) {
1374
+ LOG_CLIENT_WARN("Invalid message data type: " << static_cast<int>(header.type));
1375
+ return false;
1376
+ }
1377
+
1378
+ // Extract payload
1379
+ payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
1380
+
1381
+ LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
1382
+ return true;
1383
+ }
1384
+
1385
+ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
1386
+ if (!running_.load()) {
1387
+ return false;
1388
+ }
1389
+
1390
+ // Get socket-specific mutex for thread-safe sending
1391
+ // Prevent framed messages corruption (like two-times sending the number of bytes instead number of bytes + message)
1392
+ auto socket_mutex = get_socket_send_mutex(socket);
1393
+ std::lock_guard<std::mutex> send_lock(*socket_mutex);
1394
+
1395
+ // Create message with specified header type
1396
+ std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
1397
+
1398
+ // Use encrypted communication if encryption is enabled
1399
+ if (is_encryption_enabled()) {
1400
+ // Check if handshake is completed before sending data
1401
+ if (!encrypted_communication::is_handshake_completed(socket)) {
1402
+ std::string type_name = (message_type == MessageDataType::BINARY) ? "binary" :
1403
+ (message_type == MessageDataType::STRING) ? "string" : "JSON";
1404
+ LOG_CLIENT_WARN("Cannot send " << type_name << " data to socket " << socket << " - encryption handshake not completed");
1405
+ return false;
1406
+ }
1407
+
1408
+ int sent = encrypted_communication::send_tcp_data_encrypted(socket, message_with_header);
1409
+ return sent > 0;
1410
+ } else {
1411
+ // Use framed messages for reliable large message handling
1412
+ int sent = send_tcp_message_framed(socket, message_with_header);
1413
+ return sent > 0;
1414
+ }
1415
+ }
1416
+
1417
+ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
1418
+ // Convert string to binary and use the primary send_binary_to_peer method
1419
+ std::vector<uint8_t> binary_data(data.begin(), data.end());
1420
+ return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
1421
+ }
1422
+
1423
+ bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
1424
+ try {
1425
+ // Serialize JSON and convert to binary, then use the primary send_binary_to_peer method
1426
+ std::string json_string = data.dump();
1427
+ std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1428
+ return send_binary_to_peer(socket, binary_data, MessageDataType::JSON);
1429
+ } catch (const nlohmann::json::exception& e) {
1430
+ LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1431
+ return false;
1432
+ }
1433
+ }
1434
+
1435
+ bool RatsClient::send_binary_to_peer_id(const std::string& peer_hash_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
1436
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1437
+ auto it = peers_.find(peer_hash_id);
1438
+ if (it == peers_.end() || !it->second.is_handshake_completed()) {
1439
+ return false;
1440
+ }
1441
+
1442
+ return send_binary_to_peer(it->second.socket, data, message_type);
1443
+ }
1444
+
1445
+ bool RatsClient::send_string_to_peer_id(const std::string& peer_hash_id, const std::string& data) {
1446
+ // Convert string to binary and use primary binary method with STRING type
1447
+ std::vector<uint8_t> binary_data(data.begin(), data.end());
1448
+ return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::STRING);
1449
+ }
1450
+
1451
+ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlohmann::json& data) {
1452
+ try {
1453
+ // Serialize JSON and convert to binary, then use primary binary method with JSON type
1454
+ std::string json_string = data.dump();
1455
+ std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1456
+ return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::JSON);
1457
+ } catch (const nlohmann::json::exception& e) {
1458
+ LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1459
+ return false;
1460
+ }
1461
+ }
1462
+
1463
+ int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
1464
+ try {
1465
+ // Serialize JSON and convert to binary, then use primary binary method with JSON type
1466
+ std::string json_string = data.dump();
1467
+ std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1468
+ return broadcast_binary_to_peers(binary_data, MessageDataType::JSON);
1469
+ } catch (const nlohmann::json::exception& e) {
1470
+ LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
1471
+ return 0;
1472
+ }
1473
+ }
1474
+
1475
+ int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, MessageDataType message_type) {
1476
+ if (!running_.load()) {
1477
+ return 0;
1478
+ }
1479
+
1480
+ int sent_count = 0;
1481
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1482
+
1483
+ for (const auto& pair : peers_) {
1484
+ const RatsPeer& peer = pair.second;
1485
+ // Only send to peers that have completed handshake
1486
+ if (peer.is_handshake_completed()) {
1487
+ if (send_binary_to_peer(peer.socket, data, message_type)) {
1488
+ sent_count++;
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ return sent_count;
1494
+ }
1495
+
1496
+ int RatsClient::broadcast_string_to_peers(const std::string& data) {
1497
+ // Convert string to binary and use primary binary method with STRING type
1498
+ std::vector<uint8_t> binary_data(data.begin(), data.end());
1499
+ return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
1500
+ }
1501
+
1502
+ bool RatsClient::parse_json_message(const std::string& message, nlohmann::json& out_json) {
1503
+ try {
1504
+ out_json = nlohmann::json::parse(message);
1505
+ return true;
1506
+ } catch (const nlohmann::json::exception& e) {
1507
+ LOG_CLIENT_ERROR("Failed to parse JSON message: " << e.what());
1508
+ return false;
1509
+ }
1510
+ }
1511
+
1512
+ // Helpers
1513
+
1514
+ // Per-socket synchronization helpers
1515
+ std::shared_ptr<std::mutex> RatsClient::get_socket_send_mutex(socket_t socket) {
1516
+ std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
1517
+ auto it = socket_send_mutexes_.find(socket);
1518
+ if (it == socket_send_mutexes_.end()) {
1519
+ // Create new mutex for this socket
1520
+ socket_send_mutexes_[socket] = std::make_shared<std::mutex>();
1521
+ return socket_send_mutexes_[socket];
1522
+ }
1523
+ return it->second;
1524
+ }
1525
+
1526
+ void RatsClient::cleanup_socket_send_mutex(socket_t socket) {
1527
+ std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
1528
+ socket_send_mutexes_.erase(socket);
1529
+ }
1530
+
1531
+ // =========================================================================
1532
+ // Peer Information and Management
1533
+ // =========================================================================
1534
+
1535
+ std::string RatsClient::get_our_peer_id() const {
1536
+ return our_peer_id_;
1537
+ }
1538
+
1539
+ int RatsClient::get_peer_count_unlocked() const {
1540
+ // Assumes peers_mutex_ is already locked
1541
+ int count = 0;
1542
+ for (const auto& pair : peers_) {
1543
+ if (pair.second.is_handshake_completed()) {
1544
+ count++;
1545
+ }
1546
+ }
1547
+ return count;
1548
+ }
1549
+
1550
+ int RatsClient::get_peer_count() const {
1551
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1552
+ return get_peer_count_unlocked();
1553
+ }
1554
+
1555
+ std::string RatsClient::get_peer_id(socket_t socket) const {
1556
+ const RatsPeer* peer = get_peer_by_socket(socket);
1557
+ return peer ? peer->peer_id : "";
1558
+ }
1559
+
1560
+ socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
1561
+ const RatsPeer* peer = get_peer_by_id(peer_id);
1562
+ return peer ? peer->socket : INVALID_SOCKET_VALUE;
1563
+ }
1564
+
1565
+ std::vector<RatsPeer> RatsClient::get_all_peers() const {
1566
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1567
+ std::vector<RatsPeer> result;
1568
+ result.reserve(peers_.size());
1569
+
1570
+ for (const auto& pair : peers_) {
1571
+ result.push_back(pair.second);
1572
+ }
1573
+
1574
+ return result;
1575
+ }
1576
+
1577
+ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
1578
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1579
+ std::vector<RatsPeer> result;
1580
+
1581
+ for (const auto& pair : peers_) {
1582
+ if (pair.second.is_handshake_completed()) {
1583
+ result.push_back(pair.second);
1584
+ }
1585
+ }
1586
+
1587
+ return result;
1588
+ }
1589
+
1590
+
1591
+ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
1592
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1593
+
1594
+ std::vector<RatsPeer> all_validated_peers;
1595
+
1596
+ // Get all validated peers excluding the specified peer
1597
+ for (const auto& pair : peers_) {
1598
+ const RatsPeer& peer = pair.second;
1599
+ if (peer.is_handshake_completed() && peer.peer_id != exclude_peer_id) {
1600
+ all_validated_peers.push_back(peer);
1601
+ }
1602
+ }
1603
+
1604
+ // If we have fewer peers than requested, return all
1605
+ if (all_validated_peers.size() <= static_cast<size_t>(max_count)) {
1606
+ return all_validated_peers;
1607
+ }
1608
+
1609
+ // Randomly select peers
1610
+ std::vector<RatsPeer> selected_peers;
1611
+ std::random_device rd;
1612
+ std::mt19937 gen(rd());
1613
+
1614
+ // Use random sampling to select peers
1615
+ std::sample(all_validated_peers.begin(), all_validated_peers.end(),
1616
+ std::back_inserter(selected_peers), max_count, gen);
1617
+
1618
+ return selected_peers;
1619
+ }
1620
+
1621
+ const RatsPeer* RatsClient::get_peer_by_id(const std::string& peer_id) const {
1622
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1623
+ auto it = peers_.find(peer_id);
1624
+ return (it != peers_.end()) ? &it->second : nullptr;
1625
+ }
1626
+
1627
+ const RatsPeer* RatsClient::get_peer_by_socket(socket_t socket) const {
1628
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1629
+ auto it = socket_to_peer_id_.find(socket);
1630
+ if (it != socket_to_peer_id_.end()) {
1631
+ auto peer_it = peers_.find(it->second);
1632
+ return (peer_it != peers_.end()) ? &peer_it->second : nullptr;
1633
+ }
1634
+ return nullptr;
1635
+ }
1636
+
1637
+
1638
+ // Peer limit management methods
1639
+ int RatsClient::get_max_peers() const {
1640
+ return max_peers_;
1641
+ }
1642
+
1643
+ void RatsClient::set_max_peers(int max_peers) {
1644
+ max_peers_ = max_peers;
1645
+ LOG_CLIENT_INFO("Maximum peers set to " << max_peers_);
1646
+ }
1647
+
1648
+ bool RatsClient::is_peer_limit_reached() const {
1649
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1650
+ // Connected peers only enforcement (exclude handshake peers)
1651
+ int connected_peers = get_peer_count_unlocked();
1652
+ if (connected_peers >= max_peers_) {
1653
+ return true;
1654
+ }
1655
+ return false;
1656
+ }
1657
+
1658
+ std::string RatsClient::generate_peer_hash_id(socket_t socket, const std::string& connection_info) {
1659
+ // Generate unique hash ID using timestamp, socket, connection info, and random component
1660
+ auto now = std::chrono::high_resolution_clock::now();
1661
+ auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
1662
+
1663
+ // Create a random component
1664
+ std::random_device rd;
1665
+ std::mt19937 gen(rd());
1666
+ std::uniform_int_distribution<> dis(0, 255);
1667
+
1668
+ // Build hash string
1669
+ std::ostringstream hash_stream;
1670
+ hash_stream << std::hex << timestamp << "_" << socket << "_";
1671
+
1672
+ // Add connection info hash
1673
+ std::hash<std::string> hasher;
1674
+ hash_stream << hasher(connection_info) << "_";
1675
+
1676
+ // Add random component
1677
+ for (int i = 0; i < 8; ++i) {
1678
+ hash_stream << std::setfill('0') << std::setw(2) << dis(gen);
1679
+ }
1680
+
1681
+ return hash_stream.str();
1682
+ }
1683
+
1684
+ std::string RatsClient::normalize_peer_address(const std::string& ip, int port) const {
1685
+ // Normalize IPv6 addresses and create consistent format
1686
+ std::string normalized_ip = ip;
1687
+
1688
+ // Remove brackets from IPv6 addresses if present
1689
+ if (!normalized_ip.empty() && normalized_ip.front() == '[' && normalized_ip.back() == ']') {
1690
+ normalized_ip = normalized_ip.substr(1, normalized_ip.length() - 2);
1691
+ }
1692
+
1693
+ // Handle localhost variations
1694
+ if (normalized_ip == "localhost" || normalized_ip == "::1") {
1695
+ normalized_ip = "127.0.0.1";
1696
+ }
1697
+
1698
+ // For IPv6 addresses, add brackets for consistency
1699
+ if (normalized_ip.find(':') != std::string::npos && normalized_ip.find('.') == std::string::npos) {
1700
+ // This is likely an IPv6 address (contains colons but no dots)
1701
+ return "[" + normalized_ip + "]:" + std::to_string(port);
1702
+ }
1703
+
1704
+ return normalized_ip + ":" + std::to_string(port);
1705
+ }
1706
+
1707
+ // =========================================================================
1708
+ // Callback Registration
1709
+ // ========================================================================
1710
+
1711
+ void RatsClient::set_connection_callback(ConnectionCallback callback) {
1712
+ connection_callback_ = callback;
1713
+ }
1714
+
1715
+ void RatsClient::set_binary_data_callback(BinaryDataCallback callback) {
1716
+ binary_data_callback_ = callback;
1717
+ }
1718
+
1719
+ void RatsClient::set_string_data_callback(StringDataCallback callback) {
1720
+ string_data_callback_ = callback;
1721
+ }
1722
+
1723
+ void RatsClient::set_json_data_callback(JsonDataCallback callback) {
1724
+ json_data_callback_ = callback;
1725
+ }
1726
+
1727
+ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
1728
+ disconnect_callback_ = callback;
1729
+ }
1730
+
1731
+ // NAT Traversal and ICE Callback Registration
1732
+ void RatsClient::set_advanced_connection_callback(AdvancedConnectionCallback callback) {
1733
+ advanced_connection_callback_ = callback;
1734
+ }
1735
+
1736
+ void RatsClient::set_nat_traversal_progress_callback(NatTraversalProgressCallback callback) {
1737
+ nat_progress_callback_ = callback;
1738
+ }
1739
+
1740
+ void RatsClient::set_ice_candidate_callback(IceCandidateDiscoveredCallback callback) {
1741
+ ice_candidate_callback_ = callback;
1742
+ }
1743
+
1744
+ // =========================================================================
1745
+ // Peer Discovery Methods
1746
+ // =========================================================================
1747
+
1748
+ bool RatsClient::start_dht_discovery(int dht_port) {
1749
+ if (dht_client_ && dht_client_->is_running()) {
1750
+ LOG_CLIENT_WARN("DHT discovery is already running");
1751
+ return true;
1752
+ }
1753
+
1754
+ LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
1755
+ (bind_address_.empty() ? "" : " bound to " + bind_address_));
1756
+
1757
+ dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
1758
+ if (!dht_client_->start()) {
1759
+ LOG_CLIENT_ERROR("Failed to start DHT client");
1760
+ dht_client_.reset();
1761
+ return false;
1762
+ }
1763
+
1764
+ // Bootstrap with default nodes
1765
+ auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
1766
+ if (!dht_client_->bootstrap(bootstrap_nodes)) {
1767
+ LOG_CLIENT_WARN("Failed to bootstrap DHT");
1768
+ }
1769
+
1770
+ // Start automatic peer discovery
1771
+ start_automatic_peer_discovery();
1772
+
1773
+ LOG_CLIENT_INFO("DHT discovery started successfully");
1774
+ return true;
1775
+ }
1776
+
1777
+ void RatsClient::stop_dht_discovery() {
1778
+ if (!dht_client_) {
1779
+ return;
1780
+ }
1781
+
1782
+ LOG_CLIENT_INFO("Stopping DHT discovery");
1783
+
1784
+ // Stop automatic peer discovery
1785
+ stop_automatic_peer_discovery();
1786
+
1787
+ dht_client_->stop();
1788
+ dht_client_.reset();
1789
+ LOG_CLIENT_INFO("DHT discovery stopped");
1790
+ }
1791
+
1792
+ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
1793
+ if (!dht_client_ || !dht_client_->is_running()) {
1794
+ LOG_CLIENT_ERROR("DHT client not running");
1795
+ return false;
1796
+ }
1797
+
1798
+ if (content_hash.length() != 40) { // 160-bit hash as hex string
1799
+ LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
1800
+ return false;
1801
+ }
1802
+
1803
+ LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
1804
+
1805
+ InfoHash info_hash = hex_to_node_id(content_hash);
1806
+
1807
+ return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
1808
+ // Convert Peer to string addresses for callback
1809
+ std::vector<std::string> peer_addresses;
1810
+ for (const auto& peer : peers) {
1811
+ peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
1812
+ }
1813
+
1814
+ if (callback) {
1815
+ callback(peer_addresses);
1816
+ }
1817
+ });
1818
+ }
1819
+
1820
+ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port) {
1821
+ if (!dht_client_ || !dht_client_->is_running()) {
1822
+ LOG_CLIENT_ERROR("DHT client not running");
1823
+ return false;
1824
+ }
1825
+
1826
+ if (content_hash.length() != 40) { // 160-bit hash as hex string
1827
+ LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
1828
+ return false;
1829
+ }
1830
+
1831
+ if (port == 0) {
1832
+ port = listen_port_;
1833
+ }
1834
+
1835
+ LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port);
1836
+
1837
+ InfoHash info_hash = hex_to_node_id(content_hash);
1838
+ return dht_client_->announce_peer(info_hash, port);
1839
+ }
1840
+
1841
+ bool RatsClient::is_dht_running() const {
1842
+ return dht_client_ && dht_client_->is_running();
1843
+ }
1844
+
1845
+ size_t RatsClient::get_dht_routing_table_size() const {
1846
+ if (!dht_client_) {
1847
+ return 0;
1848
+ }
1849
+ return dht_client_->get_routing_table_size();
1850
+ }
1851
+
1852
+ void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
1853
+ LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
1854
+
1855
+ // Auto-connect to discovered peers (optional behavior)
1856
+ for (const auto& peer : peers) {
1857
+ // Check if this peer should be ignored (local interface)
1858
+ if (should_ignore_peer(peer.ip, peer.port)) {
1859
+ LOG_CLIENT_DEBUG("Ignoring discovered peer " << peer.ip << ":" << peer.port << " - local interface address");
1860
+ continue;
1861
+ }
1862
+
1863
+ // Check if we're already connected to this peer
1864
+ std::string normalized_peer_address = normalize_peer_address(peer.ip, peer.port);
1865
+ bool already_connected = is_already_connected_to_address(normalized_peer_address);
1866
+
1867
+ if (!already_connected) {
1868
+ // Check if peer limit is reached
1869
+ if (is_peer_limit_reached()) {
1870
+ LOG_CLIENT_DEBUG("Peer limit reached, not connecting to DHT discovered peer " << peer.ip << ":" << peer.port);
1871
+ continue;
1872
+ }
1873
+
1874
+ LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
1875
+
1876
+ // Try to connect to the peer (non-blocking)
1877
+ std::thread([this, peer]() {
1878
+ if (connect_to_peer(peer.ip, peer.port)) {
1879
+ LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
1880
+ } else {
1881
+ LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
1882
+ }
1883
+ }).detach();
1884
+ } else {
1885
+ LOG_CLIENT_DEBUG("Already connected to discovered peer: " << normalized_peer_address);
1886
+ }
1887
+ }
1888
+ }
1889
+
1890
+ void RatsClient::start_automatic_peer_discovery() {
1891
+ if (auto_discovery_running_.load()) {
1892
+ LOG_CLIENT_WARN("Automatic peer discovery is already running");
1893
+ return;
1894
+ }
1895
+
1896
+ LOG_CLIENT_INFO("Starting automatic rats peer discovery");
1897
+ auto_discovery_running_.store(true);
1898
+ auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
1899
+ }
1900
+
1901
+ void RatsClient::stop_automatic_peer_discovery() {
1902
+ if (!auto_discovery_running_.load()) {
1903
+ return;
1904
+ }
1905
+
1906
+ LOG_CLIENT_INFO("Stopping automatic peer discovery");
1907
+ auto_discovery_running_.store(false);
1908
+
1909
+ if (auto_discovery_thread_.joinable()) {
1910
+ auto_discovery_thread_.join();
1911
+ }
1912
+
1913
+ LOG_CLIENT_INFO("Automatic peer discovery stopped");
1914
+ }
1915
+
1916
+ bool RatsClient::is_automatic_discovery_running() const {
1917
+ return auto_discovery_running_.load();
1918
+ }
1919
+
1920
+ // This function will be removed - implementation moved to end of file
1921
+
1922
+ void RatsClient::automatic_discovery_loop() {
1923
+ LOG_CLIENT_INFO("Automatic peer discovery loop started");
1924
+
1925
+ // Initial delay to let DHT bootstrap
1926
+ {
1927
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
1928
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(5), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
1929
+ LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
1930
+ return;
1931
+ }
1932
+ }
1933
+
1934
+ // Search immediately
1935
+ search_rats_peers();
1936
+
1937
+ {
1938
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
1939
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(10), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
1940
+ LOG_CLIENT_INFO("Automatic peer discovery loop stopped during search delay");
1941
+ return;
1942
+ }
1943
+ }
1944
+
1945
+ // Announce immediately
1946
+ announce_rats_peer();
1947
+
1948
+ auto last_announce = std::chrono::steady_clock::now();
1949
+ auto last_search = std::chrono::steady_clock::now();
1950
+
1951
+ while (auto_discovery_running_.load()) {
1952
+ auto now = std::chrono::steady_clock::now();
1953
+
1954
+ if (get_peer_count() == 0) {
1955
+ // No peers: aggressive search and announce
1956
+ if (now - last_search >= std::chrono::seconds(5)) {
1957
+ search_rats_peers();
1958
+ last_search = now;
1959
+ }
1960
+ if (now - last_announce >= std::chrono::seconds(20)) {
1961
+ announce_rats_peer();
1962
+ last_announce = now;
1963
+ }
1964
+ } else {
1965
+ // Peers connected: less aggressive, similar to original logic
1966
+ if (now - last_search >= std::chrono::minutes(5)) {
1967
+ search_rats_peers();
1968
+ last_search = now;
1969
+ }
1970
+ if (now - last_announce >= std::chrono::minutes(10)) {
1971
+ announce_rats_peer();
1972
+ last_announce = now;
1973
+ }
1974
+ }
1975
+
1976
+ // Use conditional variable for responsive shutdown
1977
+ {
1978
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
1979
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
1980
+ break;
1981
+ }
1982
+ }
1983
+ }
1984
+
1985
+ LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
1986
+ }
1987
+
1988
+ void RatsClient::announce_rats_peer() {
1989
+ if (!dht_client_ || !dht_client_->is_running()) {
1990
+ LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
1991
+ return;
1992
+ }
1993
+
1994
+ std::string discovery_hash = get_discovery_hash();
1995
+ LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
1996
+
1997
+ if (announce_for_hash(discovery_hash, listen_port_)) {
1998
+ LOG_CLIENT_DEBUG("Successfully announced peer for discovery");
1999
+ } else {
2000
+ LOG_CLIENT_WARN("Failed to announce peer for discovery");
2001
+ }
2002
+ }
2003
+
2004
+ void RatsClient::search_rats_peers() {
2005
+ if (!dht_client_ || !dht_client_->is_running()) {
2006
+ LOG_CLIENT_WARN("DHT client not running, cannot search for peers");
2007
+ return;
2008
+ }
2009
+
2010
+ std::string discovery_hash = get_discovery_hash();
2011
+ LOG_CLIENT_INFO("Searching for peers using discovery hash: " << discovery_hash);
2012
+
2013
+ InfoHash info_hash = hex_to_node_id(discovery_hash);
2014
+
2015
+ if (dht_client_->is_search_active(info_hash)) {
2016
+ LOG_CLIENT_WARN("Search already in progress for info hash " << discovery_hash);
2017
+ return;
2018
+ }
2019
+
2020
+ find_peers_by_hash(discovery_hash, [this, info_hash](const std::vector<std::string>& peer_addresses) {
2021
+ LOG_CLIENT_INFO("Found " << peer_addresses.size() << " peers through DHT discovery");
2022
+
2023
+ // Convert peer addresses back to Peer objects for handle_dht_peer_discovery()
2024
+ std::vector<Peer> peers;
2025
+ for (const auto& peer_address : peer_addresses) {
2026
+ std::string ip;
2027
+ int port;
2028
+ if (parse_address_string(peer_address, ip, port)) {
2029
+ peers.push_back(Peer(ip, port));
2030
+ LOG_CLIENT_DEBUG("Discovered peer: " << peer_address);
2031
+ }
2032
+ }
2033
+
2034
+ // Auto-connect to discovered RATS peers
2035
+ handle_dht_peer_discovery(peers, info_hash);
2036
+ });
2037
+ }
2038
+
2039
+ std::string RatsClient::get_discovery_hash() const {
2040
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2041
+ // Generate discovery hash based on current protocol configuration
2042
+ std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
2043
+ return SHA1::hash(discovery_string);
2044
+ }
2045
+
2046
+ std::string RatsClient::get_rats_peer_discovery_hash() {
2047
+ // Well-known hash for rats peer discovery
2048
+ // Compute SHA1 hash of "rats_peer_discovery_v1.0"
2049
+ return SHA1::hash("rats_peer_discovery_v1.0");
2050
+ }
2051
+
2052
+
2053
+
2054
+ // =========================================================================
2055
+ // Protocol Configuration
2056
+ // =========================================================================
2057
+
2058
+ void RatsClient::set_protocol_name(const std::string& protocol_name) {
2059
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2060
+ custom_protocol_name_ = protocol_name;
2061
+ LOG_CLIENT_INFO("Protocol name set to: " << protocol_name);
2062
+ }
2063
+
2064
+ void RatsClient::set_protocol_version(const std::string& protocol_version) {
2065
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2066
+ custom_protocol_version_ = protocol_version;
2067
+ LOG_CLIENT_INFO("Protocol version set to: " << protocol_version);
2068
+ }
2069
+
2070
+ std::string RatsClient::get_protocol_name() const {
2071
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2072
+ return custom_protocol_name_;
2073
+ }
2074
+
2075
+ std::string RatsClient::get_protocol_version() const {
2076
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2077
+ return custom_protocol_version_;
2078
+ }
2079
+
2080
+ // =========================================================================
2081
+ // Message Exchange API
2082
+ // =========================================================================
2083
+
2084
+
2085
+ void RatsClient::on(const std::string& message_type, MessageCallback callback) {
2086
+ std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2087
+ message_handlers_[message_type].emplace_back(callback, false); // false = not once
2088
+ LOG_CLIENT_INFO("Registered persistent handler for message type: " << message_type << " (total handlers: " << message_handlers_[message_type].size() << ")");
2089
+ }
2090
+
2091
+ void RatsClient::once(const std::string& message_type, MessageCallback callback) {
2092
+ std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2093
+ message_handlers_[message_type].emplace_back(callback, true); // true = once
2094
+ LOG_CLIENT_DEBUG("Registered one-time handler for message type: " << message_type);
2095
+ }
2096
+
2097
+ void RatsClient::off(const std::string& message_type) {
2098
+ std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2099
+ auto it = message_handlers_.find(message_type);
2100
+ if (it != message_handlers_.end()) {
2101
+ size_t removed_count = it->second.size();
2102
+ message_handlers_.erase(it);
2103
+ LOG_CLIENT_DEBUG("Removed " << removed_count << " handlers for message type: " << message_type);
2104
+ }
2105
+ }
2106
+
2107
+ void RatsClient::send(const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
2108
+ if (!running_.load()) {
2109
+ LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - client is not running");
2110
+ if (callback) {
2111
+ callback(false, "Client is not running");
2112
+ }
2113
+ return;
2114
+ }
2115
+
2116
+ LOG_CLIENT_INFO("Sending broadcast message type '" << message_type << "' with data: " << data.dump());
2117
+
2118
+ // Create rats message
2119
+ nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
2120
+
2121
+ // Broadcast to all validated peers
2122
+ int sent_count = broadcast_rats_message_to_validated_peers(message);
2123
+
2124
+ LOG_CLIENT_INFO("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
2125
+
2126
+ if (callback) {
2127
+ if (sent_count > 0) {
2128
+ callback(true, "");
2129
+ } else {
2130
+ LOG_CLIENT_WARN("No peers to send message to");
2131
+ callback(false, "No peers to send message to");
2132
+ }
2133
+ }
2134
+ }
2135
+
2136
+ void RatsClient::send(const std::string& peer_id, const std::string& message_type, const nlohmann::json& data, SendCallback callback) {
2137
+ if (!running_.load()) {
2138
+ LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' to peer " << peer_id << " - client is not running");
2139
+ if (callback) {
2140
+ callback(false, "Client is not running");
2141
+ }
2142
+ return;
2143
+ }
2144
+
2145
+ LOG_CLIENT_INFO("Sending targeted message type '" << message_type << "' to peer " << peer_id << " with data: " << data.dump());
2146
+
2147
+ // Create rats message
2148
+ nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
2149
+
2150
+ // Send to specific peer
2151
+ socket_t target_socket = INVALID_SOCKET_VALUE;
2152
+ bool peer_found = false;
2153
+ bool handshake_completed = false;
2154
+
2155
+ {
2156
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2157
+ auto it = peers_.find(peer_id);
2158
+ if (it != peers_.end()) {
2159
+ peer_found = true;
2160
+ handshake_completed = it->second.is_handshake_completed();
2161
+ if (handshake_completed) {
2162
+ target_socket = it->second.socket;
2163
+ }
2164
+ }
2165
+ }
2166
+
2167
+ if (!peer_found) {
2168
+ LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer not found: " << peer_id);
2169
+ if (callback) {
2170
+ callback(false, "Peer not found: " + peer_id);
2171
+ }
2172
+ return;
2173
+ }
2174
+
2175
+ if (!handshake_completed) {
2176
+ LOG_CLIENT_ERROR("Cannot send message '" << message_type << "' - peer handshake not completed: " << peer_id);
2177
+ if (callback) {
2178
+ callback(false, "Peer handshake not completed: " + peer_id);
2179
+ }
2180
+ return;
2181
+ }
2182
+
2183
+ bool success = send_json_to_peer(target_socket, message);
2184
+
2185
+ LOG_CLIENT_INFO("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
2186
+
2187
+ if (callback) {
2188
+ if (success) {
2189
+ callback(true, "");
2190
+ } else {
2191
+ callback(false, "Failed to send message to peer: " + peer_id);
2192
+ }
2193
+ }
2194
+ }
2195
+
2196
+ // Message exchange system helpers
2197
+ void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
2198
+ std::vector<MessageHandler> handlers_to_call;
2199
+ std::vector<MessageHandler> remaining_handlers;
2200
+
2201
+ LOG_CLIENT_INFO("Calling message handlers for type '" << message_type << "' from peer " << peer_id << " with data: " << data.dump());
2202
+
2203
+ // Get handlers to call and identify once handlers
2204
+ {
2205
+ std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2206
+ auto it = message_handlers_.find(message_type);
2207
+ if (it != message_handlers_.end()) {
2208
+ handlers_to_call = it->second; // Copy handlers
2209
+
2210
+ // Keep only non-once handlers for the remaining list
2211
+ for (const auto& handler : it->second) {
2212
+ if (!handler.is_once) {
2213
+ remaining_handlers.push_back(handler);
2214
+ }
2215
+ }
2216
+
2217
+ // Update the handlers list (removes once handlers)
2218
+ it->second = remaining_handlers;
2219
+ } else {
2220
+ LOG_CLIENT_WARN("No handlers registered for message type '" << message_type << "'");
2221
+ }
2222
+ }
2223
+
2224
+ LOG_CLIENT_INFO("Found " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2225
+
2226
+ // Call handlers outside of mutex to avoid deadlock
2227
+ for (const auto& handler : handlers_to_call) {
2228
+ try {
2229
+ LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
2230
+ handler.callback(peer_id, data);
2231
+ LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
2232
+ } catch (const std::exception& e) {
2233
+ LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
2234
+ } catch (...) {
2235
+ LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
2236
+ }
2237
+ }
2238
+
2239
+ if (!handlers_to_call.empty()) {
2240
+ LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2241
+ }
2242
+ }
2243
+
2244
+ void RatsClient::remove_once_handlers(const std::string& message_type) {
2245
+ std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2246
+ auto it = message_handlers_.find(message_type);
2247
+ if (it != message_handlers_.end()) {
2248
+ auto& handlers = it->second;
2249
+ auto new_end = std::remove_if(handlers.begin(), handlers.end(),
2250
+ [](const MessageHandler& handler) { return handler.is_once; });
2251
+ handlers.erase(new_end, handlers.end());
2252
+
2253
+ // Remove the entire entry if no handlers remain
2254
+ if (handlers.empty()) {
2255
+ message_handlers_.erase(it);
2256
+ }
2257
+ }
2258
+ }
2259
+
2260
+ // =========================================================================
2261
+ // Rats messages protocol / Message handling system
2262
+ // =========================================================================
2263
+
2264
+ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
2265
+ nlohmann::json message;
2266
+ message["rats_protocol"] = true;
2267
+ message["type"] = type;
2268
+ message["payload"] = payload;
2269
+ message["sender_peer_id"] = sender_peer_id;
2270
+ message["timestamp"] = std::chrono::duration_cast<std::chrono::milliseconds>(
2271
+ std::chrono::high_resolution_clock::now().time_since_epoch()).count();
2272
+
2273
+ return message;
2274
+ }
2275
+
2276
+ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& message) {
2277
+ try {
2278
+ std::string message_type = message.value("type", "");
2279
+ nlohmann::json payload = message.value("payload", nlohmann::json::object());
2280
+ std::string sender_peer_id = message.value("sender_peer_id", "");
2281
+
2282
+ LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_hash_id);
2283
+
2284
+ // Call registered message handlers for all message types (including custom ones)
2285
+ call_message_handlers(message_type, sender_peer_id.empty() ? peer_hash_id : sender_peer_id, payload);
2286
+
2287
+ // Handle built-in message types for internal functionality
2288
+ if (message_type == "peer") {
2289
+ handle_peer_exchange_message(socket, peer_hash_id, payload);
2290
+ }
2291
+ else if (message_type == "peers_request") {
2292
+ handle_peers_request_message(socket, peer_hash_id, payload);
2293
+ }
2294
+ else if (message_type == "peers_response") {
2295
+ handle_peers_response_message(socket, peer_hash_id, payload);
2296
+ }
2297
+ // ICE coordination messages
2298
+ else if (message_type == "ice_offer") {
2299
+ handle_ice_offer_message(socket, peer_hash_id, payload);
2300
+ }
2301
+ else if (message_type == "ice_answer") {
2302
+ handle_ice_answer_message(socket, peer_hash_id, payload);
2303
+ }
2304
+ else if (message_type == "ice_candidate") {
2305
+ handle_ice_candidate_message(socket, peer_hash_id, payload);
2306
+ }
2307
+ // NAT traversal coordination messages
2308
+ else if (message_type == "hole_punch_coordination") {
2309
+ handle_hole_punch_coordination_message(socket, peer_hash_id, payload);
2310
+ }
2311
+ else if (message_type == "nat_info_exchange") {
2312
+ handle_nat_info_exchange_message(socket, peer_hash_id, payload);
2313
+ }
2314
+ // Custom message types are now handled by registered handlers above
2315
+ // No need for else clause - all message types are valid if they have registered handlers
2316
+
2317
+ } catch (const nlohmann::json::exception& e) {
2318
+ LOG_CLIENT_ERROR("Failed to handle rats message: " << e.what());
2319
+ }
2320
+ }
2321
+
2322
+ void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2323
+ try {
2324
+ std::string peer_ip = payload.value("ip", "");
2325
+ int peer_port = payload.value("port", 0);
2326
+ std::string peer_id = payload.value("peer_id", "");
2327
+
2328
+ if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
2329
+ LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_hash_id);
2330
+ return;
2331
+ }
2332
+
2333
+ LOG_CLIENT_INFO("Received peer exchange: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
2334
+
2335
+ // Check if we should ignore this peer (local interface)
2336
+ if (should_ignore_peer(peer_ip, peer_port)) {
2337
+ LOG_CLIENT_DEBUG("Ignoring exchanged peer " << peer_ip << ":" << peer_port << " - local interface address");
2338
+ return;
2339
+ }
2340
+
2341
+ // Check if we're already connected to this peer
2342
+ std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
2343
+ if (is_already_connected_to_address(normalized_peer_address)) {
2344
+ LOG_CLIENT_DEBUG("Already connected to exchanged peer " << normalized_peer_address);
2345
+ return;
2346
+ }
2347
+
2348
+ // Check if peer limit is reached
2349
+ if (is_peer_limit_reached()) {
2350
+ LOG_CLIENT_DEBUG("Peer limit reached, not connecting to exchanged peer " << peer_ip << ":" << peer_port);
2351
+ return;
2352
+ }
2353
+
2354
+ // Try to connect to the exchanged peer (non-blocking)
2355
+ add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
2356
+ if (connect_to_peer(peer_ip, peer_port)) {
2357
+ LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << peer_ip << ":" << peer_port);
2358
+ } else {
2359
+ LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << peer_ip << ":" << peer_port);
2360
+ }
2361
+ }), "peer-exchange-connect-" + peer_id.substr(0, 8));
2362
+
2363
+ } catch (const nlohmann::json::exception& e) {
2364
+ LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
2365
+ }
2366
+ }
2367
+
2368
+ // General broadcasting functions
2369
+ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
2370
+ int sent_count = 0;
2371
+ {
2372
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2373
+ for (const auto& pair : peers_) {
2374
+ const RatsPeer& peer = pair.second;
2375
+ // Don't send to excluded peer
2376
+ if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
2377
+ continue;
2378
+ }
2379
+
2380
+ if (send_json_to_peer(peer.socket, message)) {
2381
+ sent_count++;
2382
+ }
2383
+ }
2384
+ }
2385
+ return sent_count;
2386
+ }
2387
+
2388
+ int RatsClient::broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id) {
2389
+ int sent_count = 0;
2390
+ {
2391
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2392
+ for (const auto& pair : peers_) {
2393
+ const RatsPeer& peer = pair.second;
2394
+ // Don't send to excluded peer and only send to peers with completed handshake
2395
+ if ((!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) ||
2396
+ !peer.is_handshake_completed()) {
2397
+ continue;
2398
+ }
2399
+
2400
+ if (send_json_to_peer(peer.socket, message)) {
2401
+ sent_count++;
2402
+ }
2403
+ }
2404
+ }
2405
+ return sent_count;
2406
+ }
2407
+
2408
+ // Specific message creation functions
2409
+ nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
2410
+ // Create peer exchange payload
2411
+ nlohmann::json payload;
2412
+ payload["ip"] = peer.ip;
2413
+ payload["port"] = peer.port;
2414
+ payload["peer_id"] = peer.peer_id;
2415
+ payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2416
+
2417
+ // Create rats message
2418
+ return create_rats_message("peer", payload, peer.peer_id);
2419
+ }
2420
+
2421
+ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2422
+ // Don't broadcast exchange messages for ourselves
2423
+ if (new_peer.peer_id.empty()) {
2424
+ return;
2425
+ }
2426
+
2427
+ // Create peer exchange message
2428
+ nlohmann::json message = create_peer_exchange_message(new_peer);
2429
+
2430
+ // Broadcast to all validated peers except the new peer
2431
+ int sent_count = broadcast_rats_message_to_validated_peers(message, new_peer.peer_id);
2432
+
2433
+ LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
2434
+ << " to " << sent_count << " peers");
2435
+ }
2436
+
2437
+ // Peers request/response system implementation
2438
+ nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
2439
+ nlohmann::json payload;
2440
+ payload["max_peers"] = 5; // Request up to 5 peers
2441
+ payload["requester_info"] = {
2442
+ {"listen_port", listen_port_},
2443
+ {"peer_count", get_peer_count()}
2444
+ };
2445
+
2446
+ return create_rats_message("peers_request", payload, sender_peer_id);
2447
+ }
2448
+
2449
+ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsPeer>& peers, const std::string& sender_peer_id) {
2450
+ nlohmann::json payload;
2451
+ nlohmann::json peers_array = nlohmann::json::array();
2452
+
2453
+ for (const auto& peer : peers) {
2454
+ nlohmann::json peer_info;
2455
+ peer_info["ip"] = peer.ip;
2456
+ peer_info["port"] = peer.port;
2457
+ peer_info["peer_id"] = peer.peer_id;
2458
+ peer_info["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2459
+ peers_array.push_back(peer_info);
2460
+ }
2461
+
2462
+ payload["peers"] = peers_array;
2463
+ payload["total_peers"] = get_peer_count();
2464
+
2465
+ return create_rats_message("peers_response", payload, sender_peer_id);
2466
+ }
2467
+
2468
+
2469
+ void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2470
+ try {
2471
+ int max_peers = payload.value("max_peers", 5);
2472
+
2473
+ LOG_CLIENT_INFO("Received peers request from " << peer_hash_id << " for up to " << max_peers << " peers");
2474
+
2475
+ // Get random peers excluding the requester
2476
+ std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_hash_id);
2477
+
2478
+ LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_hash_id);
2479
+
2480
+ // Create and send peers response
2481
+ nlohmann::json response_message = create_peers_response_message(random_peers, peer_hash_id);
2482
+
2483
+ if (!send_json_to_peer(socket, response_message)) {
2484
+ LOG_CLIENT_ERROR("Failed to send peers response to " << peer_hash_id);
2485
+ } else {
2486
+ LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_hash_id);
2487
+ }
2488
+
2489
+ } catch (const nlohmann::json::exception& e) {
2490
+ LOG_CLIENT_ERROR("Failed to handle peers request message: " << e.what());
2491
+ }
2492
+ }
2493
+
2494
+ void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2495
+ try {
2496
+ nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
2497
+ int total_peers = payload.value("total_peers", 0);
2498
+
2499
+ LOG_CLIENT_INFO("Received peers response from " << peer_hash_id << " with " << peers_array.size()
2500
+ << " peers (total: " << total_peers << ")");
2501
+
2502
+ // Process each peer in the response
2503
+ for (const auto& peer_info : peers_array) {
2504
+ std::string peer_ip = peer_info.value("ip", "");
2505
+ int peer_port = peer_info.value("port", 0);
2506
+ std::string peer_id = peer_info.value("peer_id", "");
2507
+
2508
+ if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
2509
+ LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_hash_id);
2510
+ continue;
2511
+ }
2512
+
2513
+ LOG_CLIENT_DEBUG("Processing peer from response: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
2514
+
2515
+ // Check if we should ignore this peer (local interface)
2516
+ if (should_ignore_peer(peer_ip, peer_port)) {
2517
+ LOG_CLIENT_DEBUG("Ignoring peer from response " << peer_ip << ":" << peer_port << " - local interface address");
2518
+ continue;
2519
+ }
2520
+
2521
+ // Check if we're already connected to this peer
2522
+ std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
2523
+ if (is_already_connected_to_address(normalized_peer_address)) {
2524
+ LOG_CLIENT_DEBUG("Already connected to peer from response " << normalized_peer_address);
2525
+ continue;
2526
+ }
2527
+
2528
+ // Check if peer limit is reached
2529
+ if (is_peer_limit_reached()) {
2530
+ LOG_CLIENT_DEBUG("Peer limit reached, not connecting to peer from response " << peer_ip << ":" << peer_port);
2531
+ continue;
2532
+ }
2533
+
2534
+ // Try to connect to the peer (non-blocking)
2535
+ LOG_CLIENT_INFO("Attempting to connect to peer from response: " << peer_ip << ":" << peer_port);
2536
+ add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
2537
+ if (connect_to_peer(peer_ip, peer_port)) {
2538
+ LOG_CLIENT_INFO("Successfully connected to peer from response: " << peer_ip << ":" << peer_port);
2539
+ } else {
2540
+ LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << peer_ip << ":" << peer_port);
2541
+ }
2542
+ }), "peer-response-connect-" + peer_id.substr(0, 8));
2543
+ }
2544
+
2545
+ } catch (const nlohmann::json::exception& e) {
2546
+ LOG_CLIENT_ERROR("Failed to handle peers response message: " << e.what());
2547
+ }
2548
+ }
2549
+
2550
+ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer_id) {
2551
+ nlohmann::json request_message = create_peers_request_message(our_peer_id);
2552
+
2553
+ if (send_json_to_peer(socket, request_message)) {
2554
+ LOG_CLIENT_INFO("Sent peers request to socket " << socket);
2555
+ } else {
2556
+ LOG_CLIENT_ERROR("Failed to send peers request to socket " << socket);
2557
+ }
2558
+ }
2559
+
2560
+ // =========================================================================
2561
+ // Statistics and Information
2562
+ // =========================================================================
2563
+
2564
+ nlohmann::json RatsClient::get_connection_statistics() const {
2565
+ nlohmann::json stats;
2566
+
2567
+ {
2568
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2569
+ stats["total_peers"] = peers_.size();
2570
+ stats["validated_peers"] = get_peer_count_unlocked();
2571
+ stats["max_peers"] = max_peers_;
2572
+ }
2573
+
2574
+ stats["running"] = is_running();
2575
+ stats["listen_port"] = listen_port_;
2576
+ stats["our_peer_id"] = get_our_peer_id();
2577
+ stats["encryption_enabled"] = is_encryption_enabled();
2578
+ stats["public_ip"] = get_public_ip();
2579
+
2580
+ // DHT statistics
2581
+ if (dht_client_ && dht_client_->is_running()) {
2582
+ stats["dht_running"] = true;
2583
+ stats["dht_routing_table_size"] = get_dht_routing_table_size();
2584
+ } else {
2585
+ stats["dht_running"] = false;
2586
+ }
2587
+
2588
+ // mDNS statistics
2589
+ stats["mdns_running"] = is_mdns_running();
2590
+
2591
+ return stats;
2592
+ }
2593
+
2594
+
2595
+ // =========================================================================
2596
+ // Helper functions
2597
+ // =========================================================================
2598
+
2599
+ std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
2600
+ auto client = std::make_unique<RatsClient>(listen_port, 10); // Default 10 max peers
2601
+ if (!client->start()) {
2602
+ return nullptr;
2603
+ }
2604
+ return client;
2605
+ }
2606
+
2607
+ // Version query functions
2608
+ const char* rats_get_library_version_string() {
2609
+ return librats::version::STRING;
2610
+ }
2611
+
2612
+ void rats_get_library_version(int* major, int* minor, int* patch, int* build) {
2613
+ if (major) *major = librats::version::MAJOR;
2614
+ if (minor) *minor = librats::version::MINOR;
2615
+ if (patch) *patch = librats::version::PATCH;
2616
+ if (build) *build = librats::version::BUILD;
2617
+ }
2618
+
2619
+ const char* rats_get_library_git_describe() {
2620
+ return librats::version::GIT_DESCRIBE;
2621
+ }
2622
+
2623
+ uint32_t rats_get_library_abi() {
2624
+ // ABI policy: MAJOR bumps on breaking changes; MINOR for additive; PATCH ignored in ABI id
2625
+ return (static_cast<uint32_t>(librats::version::MAJOR) << 16) |
2626
+ (static_cast<uint32_t>(librats::version::MINOR) << 8) |
2627
+ (static_cast<uint32_t>(librats::version::PATCH));
2628
+ }
2629
+
2630
+ bool RatsClient::parse_address_string(const std::string& address_str, std::string& out_ip, int& out_port) {
2631
+ if (address_str.empty()) {
2632
+ return false;
2633
+ }
2634
+
2635
+ size_t colon_pos;
2636
+ if (address_str.front() == '[') {
2637
+ // IPv6 format: [ip]:port
2638
+ size_t bracket_end = address_str.find(']');
2639
+ if (bracket_end == std::string::npos || bracket_end < 2) { // Must be at least [a]
2640
+ return false;
2641
+ }
2642
+ out_ip = address_str.substr(1, bracket_end - 1);
2643
+ colon_pos = address_str.find(':', bracket_end);
2644
+ } else {
2645
+ // IPv4 or IPv6 without brackets
2646
+ colon_pos = address_str.find_last_of(':');
2647
+ if (colon_pos == std::string::npos || colon_pos == 0) {
2648
+ return false;
2649
+ }
2650
+ out_ip = address_str.substr(0, colon_pos);
2651
+ }
2652
+
2653
+ if (colon_pos == std::string::npos || colon_pos + 1 >= address_str.length()) {
2654
+ return false;
2655
+ }
2656
+
2657
+ try {
2658
+ out_port = std::stoi(address_str.substr(colon_pos + 1));
2659
+ } catch (const std::exception&) {
2660
+ return false;
2661
+ }
2662
+
2663
+ return !out_ip.empty() && out_port > 0 && out_port <= 65535;
2664
+ }
2665
+
2666
+ std::string get_box_separator() {
2667
+ return supports_unicode() ?
2668
+ "════════════════════════════════════════════════════════════════════" :
2669
+ "=====================================================================";
2670
+ }
2671
+
2672
+ std::string get_box_vertical() {
2673
+ return supports_unicode() ? "│" : "|";
2674
+ }
2675
+
2676
+ std::string get_checkmark() {
2677
+ return supports_unicode() ? "✓" : "[*]";
2678
+ }
2679
+
2680
+ void RatsClient::log_handshake_completion_unlocked(const RatsPeer& peer) {
2681
+ // Calculate connection duration
2682
+ auto now = std::chrono::steady_clock::now();
2683
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - peer.connected_at);
2684
+
2685
+ // Get current peer count (assumes peers_mutex_ is already locked)
2686
+ int current_peer_count = get_peer_count_unlocked();
2687
+
2688
+ // Create visually appealing log output
2689
+ std::string connection_type = peer.is_outgoing ? "OUTGOING" : "INCOMING";
2690
+ std::string separator = get_box_separator();
2691
+ std::string vertical = get_box_vertical();
2692
+ std::string checkmark = get_checkmark();
2693
+
2694
+ LOG_CLIENT_INFO("");
2695
+ LOG_CLIENT_INFO(separator);
2696
+ LOG_CLIENT_INFO(checkmark << " HANDSHAKE COMPLETED - NEW PEER CONNECTED");
2697
+ LOG_CLIENT_INFO(separator);
2698
+ LOG_CLIENT_INFO(vertical << " Peer ID : " << peer.peer_id);
2699
+ LOG_CLIENT_INFO(vertical << " Address : " << peer.ip << ":" << peer.port);
2700
+ LOG_CLIENT_INFO(vertical << " Connection : " << connection_type);
2701
+ LOG_CLIENT_INFO(vertical << " Protocol Ver. : " << peer.version);
2702
+ LOG_CLIENT_INFO(vertical << " Socket : " << peer.socket);
2703
+ LOG_CLIENT_INFO(vertical << " Duration : " << duration.count() << "ms");
2704
+ LOG_CLIENT_INFO(vertical << " Network Peers : " << current_peer_count << "/" << max_peers_);
2705
+
2706
+ LOG_CLIENT_INFO(separator);
2707
+ LOG_CLIENT_INFO("");
2708
+ }
2709
+
2710
+ void RatsClient::log_handshake_completion(const RatsPeer& peer) {
2711
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2712
+ log_handshake_completion_unlocked(peer);
2713
+ }
2714
+
2715
+ } // namespace librats