librats 0.7.1 → 0.8.0

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 (42) hide show
  1. package/native-src/CMakeLists.txt +15 -2
  2. package/native-src/src/bt_client.cpp +18 -5
  3. package/native-src/src/bt_client.h +3 -1
  4. package/native-src/src/bt_network.cpp +297 -184
  5. package/native-src/src/bt_network.h +25 -5
  6. package/native-src/src/bt_peer_connection.cpp +11 -0
  7. package/native-src/src/bt_peer_connection.h +6 -0
  8. package/native-src/src/bt_torrent.cpp +17 -6
  9. package/native-src/src/bt_torrent.h +1 -0
  10. package/native-src/src/bt_types.h +287 -20
  11. package/native-src/src/dht.cpp +26 -8
  12. package/native-src/src/dht.h +1 -1
  13. package/native-src/src/ice.cpp +1 -1
  14. package/native-src/src/io_poller.cpp +910 -0
  15. package/native-src/src/io_poller.h +138 -0
  16. package/native-src/src/krpc.cpp +8 -1
  17. package/native-src/src/krpc.h +3 -2
  18. package/native-src/src/librats.cpp +824 -1213
  19. package/native-src/src/librats.h +167 -54
  20. package/native-src/src/librats_bittorrent.cpp +4 -7
  21. package/native-src/src/librats_c.cpp +13 -1
  22. package/native-src/src/librats_c.h +2 -0
  23. package/native-src/src/librats_discovery.cpp +309 -0
  24. package/native-src/src/librats_encryption.cpp +130 -283
  25. package/native-src/src/librats_file_transfer.cpp +2 -6
  26. package/native-src/src/librats_gossipsub.cpp +1 -5
  27. package/native-src/src/librats_ice.cpp +1 -1
  28. package/native-src/src/librats_log_macros.h +36 -0
  29. package/native-src/src/librats_logging.cpp +10 -7
  30. package/native-src/src/librats_mdns.cpp +1 -11
  31. package/native-src/src/librats_persistence.cpp +1 -11
  32. package/native-src/src/librats_reconnection.cpp +2 -13
  33. package/native-src/src/librats_statistic.cpp +97 -0
  34. package/native-src/src/logger.h +40 -4
  35. package/native-src/src/network_utils.cpp +175 -373
  36. package/native-src/src/network_utils.h +2 -113
  37. package/native-src/src/socket.cpp +485 -883
  38. package/native-src/src/socket.h +36 -130
  39. package/native-src/src/stun.cpp +3 -5
  40. package/native-src/src/tracker.cpp +3 -2
  41. package/native-src/src/turn.cpp +4 -6
  42. package/package.json +1 -1
@@ -1,42 +1,15 @@
1
1
  #include "librats.h"
2
- #include "gossipsub.h"
3
- #include "sha1.h"
4
2
  #include "os.h"
5
3
  #include "network_utils.h"
6
- #include "fs.h"
7
- #include "json.hpp" // nlohmann::json
8
4
  #include "version.h"
9
- #include <iostream>
10
5
  #include <algorithm>
11
- #include <chrono>
12
- #include <memory>
6
+ #include <array>
13
7
  #include <random>
14
8
  #include <sstream>
15
9
  #include <iomanip>
16
- #include <stdexcept>
17
10
  #include <string_view>
18
11
 
19
- #ifdef TESTING
20
- #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
21
- #define LOG_CLIENT_INFO(message) LOG_INFO("client", "[pointer: " << this << "] " << message)
22
- #define LOG_CLIENT_WARN(message) LOG_WARN("client", "[pointer: " << this << "] " << message)
23
- #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", "[pointer: " << this << "] " << message)
24
-
25
- #define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", "[pointer: " << this << "] " << message)
26
- #define LOG_SERVER_INFO(message) LOG_INFO("server", "[pointer: " << this << "] " << message)
27
- #define LOG_SERVER_WARN(message) LOG_WARN("server", "[pointer: " << this << "] " << message)
28
- #define LOG_SERVER_ERROR(message) LOG_ERROR("server", "[pointer: " << this << "] " << message)
29
- #else
30
- #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
31
- #define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
32
- #define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
33
- #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
34
-
35
- #define LOG_SERVER_DEBUG(message) LOG_DEBUG("server", message)
36
- #define LOG_SERVER_INFO(message) LOG_INFO("server", message)
37
- #define LOG_SERVER_WARN(message) LOG_WARN("server", message)
38
- #define LOG_SERVER_ERROR(message) LOG_ERROR("server", message)
39
- #endif
12
+ #include "librats_log_macros.h"
40
13
 
41
14
  namespace librats {
42
15
 
@@ -112,7 +85,6 @@ void RatsClient::destroy_modules() {
112
85
  // Core Lifecycle Management
113
86
  // =========================================================================
114
87
 
115
-
116
88
  bool RatsClient::start() {
117
89
  if (running_.load()) {
118
90
  LOG_CLIENT_WARN("RatsClient is already running");
@@ -146,16 +118,27 @@ bool RatsClient::start() {
146
118
  initialize_local_addresses();
147
119
 
148
120
  // Create dual-stack server socket (supports both IPv4 and IPv6)
149
- server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
121
+ server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
122
+ // Fallback to free port
123
+ if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
124
+ // Requested port is not available, try ephemeral port as fallback
125
+ int original_port = listen_port_;
126
+ LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
127
+ server_socket_ = create_tcp_server(0, 5, bind_address_);
128
+ if (is_valid_socket(server_socket_)) {
129
+ listen_port_ = get_bound_port(server_socket_);
130
+ LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
131
+ }
132
+ }
150
133
  if (!is_valid_socket(server_socket_)) {
151
- LOG_CLIENT_ERROR("Failed to create dual-stack server socket on port " << listen_port_ <<
134
+ LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
152
135
  (bind_address_.empty() ? "" : " bound to " + bind_address_));
153
136
  return false;
154
137
  }
155
138
 
156
139
  // Update listen_port_ with actual bound port if ephemeral port was requested
157
140
  if (listen_port_ == 0) {
158
- listen_port_ = get_ephemeral_port(server_socket_);
141
+ listen_port_ = get_bound_port(server_socket_);
159
142
  if (listen_port_ == 0) {
160
143
  LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
161
144
  } else {
@@ -163,12 +146,20 @@ bool RatsClient::start() {
163
146
  }
164
147
  }
165
148
 
149
+ // Set server socket to non-blocking for the IO poller
150
+ set_socket_nonblocking(server_socket_);
151
+
152
+ // Create platform-optimal IO poller and register server socket
153
+ poller_ = IOPoller::create();
154
+ poller_->add(server_socket_, PollIn);
155
+ LOG_CLIENT_INFO("IO poller backend: " << poller_->name());
156
+
166
157
  running_.store(true);
167
158
 
168
- // Start server thread
169
- server_thread_ = std::thread(&RatsClient::server_loop, this);
159
+ // Start IO thread (single-threaded event loop for all sockets)
160
+ io_thread_ = std::thread(&RatsClient::io_loop, this);
170
161
 
171
- // Start management thread
162
+ // Start management thread (handshake timeouts, reconnection, thread cleanup)
172
163
  management_thread_ = std::thread(&RatsClient::management_loop, this);
173
164
 
174
165
  // Start GossipSub
@@ -181,7 +172,7 @@ bool RatsClient::start() {
181
172
  // Attempt to reconnect to saved peers
182
173
  add_managed_thread(std::thread([this]() {
183
174
  // Give the server some time to fully initialize
184
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
175
+ std::this_thread::sleep_for(std::chrono::milliseconds(PEER_RECONNECT_DELAY_MS));
185
176
  int reconnect_attempts = load_and_reconnect_peers();
186
177
  if (reconnect_attempts > 0) {
187
178
  LOG_CLIENT_INFO("Attempted to reconnect to " << reconnect_attempts << " saved peers");
@@ -189,7 +180,7 @@ bool RatsClient::start() {
189
180
 
190
181
  // Also attempt to reconnect to historical peers if not at peer limit
191
182
  if (!is_peer_limit_reached()) {
192
- std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Give current peers time to connect
183
+ std::this_thread::sleep_for(std::chrono::milliseconds(HISTORICAL_RECONNECT_DELAY_MS));
193
184
  int historical_attempts = load_and_reconnect_historical_peers();
194
185
  if (historical_attempts > 0) {
195
186
  LOG_CLIENT_INFO("Attempted to reconnect to " << historical_attempts << " historical peers");
@@ -211,8 +202,7 @@ void RatsClient::stop() {
211
202
  if (gossipsub_) {
212
203
  gossipsub_->stop();
213
204
  }
214
-
215
-
205
+
216
206
  // Trigger immediate shutdown of all background threads
217
207
  shutdown_all_threads();
218
208
 
@@ -235,25 +225,25 @@ void RatsClient::stop() {
235
225
  manual_disconnect_peers_.clear();
236
226
  }
237
227
 
238
- // Close all peer connections
228
+ // Close all peer connections and remove from poller
239
229
  {
240
230
  std::lock_guard<std::mutex> lock(peers_mutex_);
241
231
  LOG_CLIENT_INFO("Closing " << peers_.size() << " peer connections");
242
232
  for (const auto& pair : peers_) {
243
233
  const RatsPeer& peer = pair.second;
234
+ if (poller_) poller_->remove(peer.socket);
244
235
  close_socket(peer.socket, true);
245
236
  }
246
237
  peers_.clear();
247
238
  socket_to_peer_id_.clear();
248
239
  address_to_peer_id_.clear();
240
+ validated_peer_count_.store(0, std::memory_order_relaxed);
249
241
  }
250
242
 
251
-
252
-
253
- // Wait for server thread to finish
254
- if (server_thread_.joinable()) {
255
- LOG_CLIENT_DEBUG("Waiting for server thread to finish");
256
- server_thread_.join();
243
+ // Wait for IO thread to finish
244
+ if (io_thread_.joinable()) {
245
+ LOG_CLIENT_DEBUG("Waiting for IO thread to finish");
246
+ io_thread_.join();
257
247
  }
258
248
 
259
249
  // Wait for management thread to finish
@@ -262,11 +252,14 @@ void RatsClient::stop() {
262
252
  management_thread_.join();
263
253
  }
264
254
 
255
+ // Destroy poller after threads have stopped
256
+ poller_.reset();
257
+
265
258
  // Join all managed threads for graceful cleanup
266
259
  join_all_active_threads();
267
260
 
268
261
  cleanup_socket_library();
269
-
262
+
270
263
  // Save configuration before stopping
271
264
  save_configuration();
272
265
 
@@ -278,7 +271,7 @@ void RatsClient::shutdown_all_threads() {
278
271
 
279
272
  // Signal all threads to stop
280
273
  running_.store(false);
281
-
274
+
282
275
  // Call parent class to handle thread management shutdown
283
276
  ThreadManager::shutdown_all_threads();
284
277
  }
@@ -300,456 +293,564 @@ std::string RatsClient::get_bind_address() const {
300
293
  }
301
294
 
302
295
  // =========================================================================
303
- // Managment loops
296
+ // Async I/O – single-threaded event loop
304
297
  // =========================================================================
305
298
 
306
- void RatsClient::server_loop() {
307
- LOG_SERVER_INFO("Server loop started");
299
+ void RatsClient::io_loop() {
300
+ LOG_CLIENT_INFO("IO loop started (backend: " << poller_->name() << ")");
301
+
302
+ static constexpr int MAX_EVENTS = 256;
303
+ PollResult results[MAX_EVENTS];
308
304
 
309
305
  while (running_.load()) {
310
- socket_t client_socket = accept_client(server_socket_);
311
- if (!is_valid_socket(client_socket)) {
312
- if (running_.load()) {
313
- LOG_SERVER_ERROR("Failed to accept client connection");
306
+ int n = poller_->wait(results, MAX_EVENTS, IO_POLL_TIMEOUT_MS);
307
+
308
+ if (n < 0) {
309
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
310
+ continue;
311
+ }
312
+
313
+ // Collect sockets to disconnect (defer to avoid iterator issues)
314
+ std::vector<socket_t> to_disconnect;
315
+
316
+ for (int i = 0; i < n; ++i) {
317
+ socket_t fd = results[i].fd;
318
+ uint32_t events = results[i].events;
319
+
320
+ // Server socket – accept incoming connections
321
+ if (fd == server_socket_) {
322
+ if (events & PollIn) accept_incoming();
323
+ continue;
314
324
  }
315
- break;
325
+
326
+ bool should_close = false;
327
+
328
+ if (events & (PollErr | PollHup)) {
329
+ should_close = true;
330
+ }
331
+
332
+ if (!should_close && (events & PollIn)) {
333
+ should_close = handle_readable(fd);
334
+ }
335
+
336
+ if (!should_close && (events & PollOut)) {
337
+ should_close = handle_writable(fd);
338
+ }
339
+
340
+ if (should_close) {
341
+ to_disconnect.push_back(fd);
342
+ }
343
+ }
344
+
345
+ // Handle disconnections outside the event loop
346
+ for (socket_t fd : to_disconnect) {
347
+ handle_disconnect(fd);
316
348
  }
349
+ }
350
+
351
+ LOG_CLIENT_INFO("IO loop ended");
352
+ }
353
+
354
+ // ---------------------------------------------------------------------------
355
+ // accept_incoming – non-blocking accept of new TCP connections
356
+ // ---------------------------------------------------------------------------
357
+ void RatsClient::accept_incoming() {
358
+ // Accept as many pending connections as possible (level-triggered)
359
+ while (true) {
360
+ socket_t client = accept_client(server_socket_);
361
+ if (!is_valid_socket(client)) break;
317
362
 
318
- // Get peer address information
319
- std::string peer_address = get_peer_address(client_socket);
363
+ std::string peer_address = get_peer_address(client);
320
364
  if (peer_address.empty()) {
321
- LOG_SERVER_ERROR("Failed to get peer address for incoming connection");
322
- close_socket(client_socket);
365
+ close_socket(client);
323
366
  continue;
324
367
  }
325
368
 
326
- // Parse IP and port from peer_address
327
369
  std::string ip;
328
370
  int port = 0;
329
371
  if (!parse_address_string(peer_address, ip, port)) {
330
- LOG_SERVER_ERROR("Failed to parse peer address from incoming connection: " << peer_address);
331
- close_socket(client_socket);
372
+ close_socket(client);
332
373
  continue;
333
374
  }
334
375
 
335
- std::string normalized_peer_address = normalize_peer_address(ip, port);
376
+ std::string normalized = normalize_peer_address(ip, port);
336
377
 
337
- // Check if peer limit is reached
338
378
  if (is_peer_limit_reached()) {
339
- LOG_SERVER_INFO("Peer limit reached (" << max_peers_ << "), rejecting connection from " << normalized_peer_address);
340
- close_socket(client_socket);
379
+ LOG_SERVER_INFO("Peer limit reached, rejecting " << normalized);
380
+ close_socket(client);
341
381
  continue;
342
382
  }
343
383
 
344
- // Check if we're already connected to this peer
345
- if (is_already_connected_to_address(normalized_peer_address)) {
346
- LOG_SERVER_INFO("Already connected to peer " << normalized_peer_address << ", rejecting duplicate connection");
347
- close_socket(client_socket);
384
+ if (is_already_connected_to_address(normalized)) {
385
+ LOG_SERVER_DEBUG("Duplicate connection from " << normalized);
386
+ close_socket(client);
348
387
  continue;
349
388
  }
350
-
351
-
352
- // Generate unique hash ID for this incoming client
353
- std::string connection_info = "incoming_from_" + peer_address;
354
- std::string peer_hash_id = generate_peer_hash_id(client_socket, connection_info); // Temporary hash ID (real hash ID will be set after handshake)
355
389
 
356
- // Create RatsPeer object for incoming connection
390
+ // Make the new socket non-blocking and register with poller
391
+ set_socket_nonblocking(client);
392
+
393
+ std::string initial_id = generate_temporary_peer_id(client, "incoming_from_" + peer_address);
394
+
357
395
  {
358
396
  std::lock_guard<std::mutex> lock(peers_mutex_);
359
- RatsPeer new_peer(peer_hash_id, ip, port, client_socket, normalized_peer_address, false); // false = incoming connection
397
+ RatsPeer new_peer(initial_id, ip, port, client, normalized, false);
360
398
  new_peer.encryption_enabled = is_encryption_enabled();
361
399
  add_peer_unlocked(new_peer);
362
400
  }
363
401
 
364
- // Start a thread to handle this client
365
- LOG_SERVER_DEBUG("Starting thread for client " << peer_hash_id << " from " << peer_address);
366
- add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
367
- "client-handler-" + peer_hash_id.substr(0, 8));
402
+ poller_add(client, PollIn);
368
403
 
369
- // Note: Connection callback will be called after handshake completion in handle_client
404
+ LOG_SERVER_INFO("Accepted incoming connection from " << normalized << " (id: " << initial_id.substr(0, 8) << "…)");
370
405
  }
371
-
372
- LOG_SERVER_INFO("Server loop ended");
373
406
  }
374
407
 
375
- void RatsClient::management_loop() {
376
- LOG_CLIENT_INFO("Management loop started");
377
-
378
- auto last_thread_cleanup = std::chrono::steady_clock::now();
379
- const auto thread_cleanup_interval = std::chrono::seconds(30);
380
-
381
- while (running_.load()) {
382
- // Wait for 2 seconds or until shutdown (for responsive reconnection processing)
383
- {
384
- std::unique_lock<std::mutex> lock(shutdown_mutex_);
385
- if (shutdown_cv_.wait_for(lock, std::chrono::seconds(2), [this] { return !running_.load(); })) {
386
- break; // Exit if shutdown requested
387
- }
388
- }
389
-
390
- // Process reconnection queue
391
- try {
392
- process_reconnect_queue();
393
- } catch (const std::exception& e) {
394
- LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
395
- }
396
-
397
- // Periodically cleanup finished threads (every 30 seconds)
398
- auto now = std::chrono::steady_clock::now();
399
- if (now - last_thread_cleanup >= thread_cleanup_interval) {
400
- try {
401
- cleanup_finished_threads();
402
- LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
403
- } catch (const std::exception& e) {
404
- LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
405
- }
406
- last_thread_cleanup = now;
407
- }
408
- }
409
-
410
- LOG_CLIENT_INFO("Management loop ended");
411
- }
412
-
413
-
414
- void RatsClient::handle_client(socket_t client_socket, const std::string& peer_hash_id) {
415
- LOG_CLIENT_INFO("Started handling client: " << peer_hash_id);
408
+ // ---------------------------------------------------------------------------
409
+ // handle_readable – drain kernel buffer, parse length-prefixed frames
410
+ // Returns true if the peer should be disconnected.
411
+ // ---------------------------------------------------------------------------
412
+ bool RatsClient::handle_readable(socket_t socket) {
413
+ // ── Phase 1: drain data & extract complete frames under lock ──────────
414
+ struct PendingFrame {
415
+ std::vector<uint8_t> data;
416
+ RatsPeer::HandshakeState state;
417
+ std::string peer_id;
418
+ bool noise_encrypted;
419
+ std::shared_ptr<rats::NoiseCipherState> recv_cipher;
420
+ };
416
421
 
417
- // ===== INITIALIZATION =====
418
- bool handshake_completed = false;
419
- bool noise_handshake_done = false;
420
- bool encryption_enabled = is_encryption_enabled();
421
- bool is_outgoing = false;
422
- auto last_timeout_check = std::chrono::steady_clock::now();
422
+ std::vector<PendingFrame> frames;
423
+ bool peer_closed = false;
424
+ bool need_post_handshake = false;
425
+ RatsPeer peer_copy_for_post_handshake;
423
426
 
424
427
  {
425
428
  std::lock_guard<std::mutex> lock(peers_mutex_);
426
- auto sock_it = socket_to_peer_id_.find(client_socket);
427
- if (sock_it != socket_to_peer_id_.end()) {
428
- auto peer_it = peers_.find(sock_it->second);
429
- if (peer_it != peers_.end()) {
430
- is_outgoing = peer_it->second.is_outgoing;
429
+ auto peer_it = find_peer_by_socket_unlocked(socket);
430
+ if (peer_it == peers_.end()) return true;
431
+
432
+ RatsPeer& peer = peer_it->second;
433
+ auto& recv_buf = peer.io_.recv_buffer;
434
+
435
+ // Non-blocking recv loop
436
+ while (true) {
437
+ recv_buf.ensure_space(16384);
438
+ int bytes = ::recv(socket,
439
+ reinterpret_cast<char*>(recv_buf.write_ptr()),
440
+ static_cast<int>(recv_buf.write_space()), 0);
441
+
442
+ if (bytes > 0) {
443
+ recv_buf.received(static_cast<size_t>(bytes));
444
+ continue;
431
445
  }
432
- }
433
- }
434
-
435
- // ===== SEND INITIAL HANDSHAKE FOR OUTGOING CONNECTIONS =====
436
- if (is_outgoing) {
437
- LOG_CLIENT_DEBUG("Sending initial handshake for outgoing connection to " << peer_hash_id);
438
- if (!send_handshake(client_socket, get_our_peer_id())) {
439
- LOG_CLIENT_ERROR("Failed to send initial handshake for outgoing connection to " << peer_hash_id);
440
- remove_peer(client_socket);
441
- close_socket(client_socket);
442
- return;
443
- }
444
- }
445
-
446
- // ===== MAIN LOOP =====
447
- while (running_.load()) {
448
-
449
- // ----- 1. RECEIVE DATA -----
450
- LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
451
- std::vector<uint8_t> received_bytes = receive_tcp_message_framed(client_socket);
452
-
453
- if (received_bytes.empty()) {
454
- break; // Connection closed or error
446
+ if (bytes == 0) { peer_closed = true; break; }
447
+
448
+ #ifdef _WIN32
449
+ if (WSAGetLastError() == WSAEWOULDBLOCK) break;
450
+ #else
451
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
452
+ #endif
453
+ peer_closed = true;
454
+ break;
455
455
  }
456
456
 
457
- // ----- 2. DECRYPT IF NEEDED -----
458
- // Use vector directly to avoid unnecessary string conversions
459
- std::vector<uint8_t> data;
457
+ if (peer_closed && recv_buf.empty()) return true;
460
458
 
461
- if (noise_handshake_done) {
462
- std::string current_peer_id;
463
- rats::NoiseCipherState* recv_cipher = nullptr;
459
+ // Parse length-prefixed frames: [4-byte network-order length][payload]
460
+ while (recv_buf.size() >= 4) {
461
+ uint32_t net_len;
462
+ memcpy(&net_len, recv_buf.data(), 4);
463
+ uint32_t msg_len = ntohl(net_len);
464
464
 
465
- {
466
- std::lock_guard<std::mutex> lock(peers_mutex_);
467
- auto sock_it = socket_to_peer_id_.find(client_socket);
468
- if (sock_it != socket_to_peer_id_.end()) {
469
- auto peer_it = peers_.find(sock_it->second);
470
- if (peer_it != peers_.end() && peer_it->second.is_noise_encrypted()) {
471
- recv_cipher = peer_it->second.recv_cipher.get();
472
- current_peer_id = peer_it->second.peer_id;
473
- }
474
- }
465
+ if (msg_len > MAX_FRAME_SIZE) {
466
+ LOG_CLIENT_ERROR("Frame too large (" << msg_len << " bytes) from " << peer.peer_id);
467
+ return true;
475
468
  }
469
+ if (recv_buf.size() < 4 + msg_len) break; // incomplete frame
476
470
 
477
- if (recv_cipher) {
478
- if (received_bytes.size() < rats::NOISE_TAG_SIZE) {
479
- LOG_CLIENT_ERROR("Received encrypted message too small from " << current_peer_id);
480
- break;
481
- }
482
-
483
- std::vector<uint8_t> plaintext(received_bytes.size());
484
- size_t pt_len = recv_cipher->decrypt_with_ad(
485
- nullptr, 0,
486
- received_bytes.data(), received_bytes.size(),
487
- plaintext.data()
488
- );
471
+ // Handle Noise handshake messages inline (fast crypto, no callbacks)
472
+ if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
473
+ if (!handle_noise_frame(peer)) return true;
474
+ recv_buf.consume(4 + msg_len);
489
475
 
490
- if (pt_len == 0) {
491
- LOG_CLIENT_ERROR("Failed to decrypt message from " << current_peer_id << " - closing connection");
492
- break;
476
+ // Check if Noise just completed
477
+ if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
478
+ need_post_handshake = true;
479
+ peer_copy_for_post_handshake = peer;
493
480
  }
494
-
495
- plaintext.resize(pt_len);
496
- data = std::move(plaintext);
497
- LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
498
- } else {
499
- data = std::move(received_bytes);
500
- }
501
- } else {
502
- data = std::move(received_bytes);
503
- }
504
-
505
- // Log first 50 bytes for debugging
506
- size_t log_len = (std::min)(data.size(), static_cast<size_t>(50));
507
- LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << std::string(data.begin(), data.begin() + log_len) << (data.size() > 50 ? "..." : ""));
508
-
509
- // ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
510
- if (!handshake_completed) {
511
- auto now = std::chrono::steady_clock::now();
512
- if (now - last_timeout_check >= std::chrono::seconds(1)) {
513
- check_handshake_timeouts();
514
- last_timeout_check = now;
481
+ continue;
515
482
  }
516
483
 
517
- // Check for handshake failure
518
- bool handshake_failed = false;
519
- {
520
- std::lock_guard<std::mutex> lock(peers_mutex_);
521
- auto sock_it = socket_to_peer_id_.find(client_socket);
522
- if (sock_it != socket_to_peer_id_.end()) {
523
- auto peer_it = peers_.find(sock_it->second);
524
- if (peer_it != peers_.end() && peer_it->second.is_handshake_failed()) {
525
- handshake_failed = true;
526
- }
527
- }
528
- }
484
+ // Snapshot state for out-of-lock processing
485
+ PendingFrame pf;
486
+ pf.data.assign(recv_buf.data() + 4, recv_buf.data() + 4 + msg_len);
487
+ pf.state = peer.handshake_state;
488
+ pf.peer_id = peer.peer_id;
489
+ pf.noise_encrypted = peer.is_noise_encrypted();
490
+ if (pf.noise_encrypted) pf.recv_cipher = peer.recv_cipher;
529
491
 
530
- if (handshake_failed) {
531
- LOG_CLIENT_ERROR("Handshake failed for peer " << peer_hash_id);
532
- break;
533
- }
492
+ frames.push_back(std::move(pf));
493
+ recv_buf.consume(4 + msg_len);
534
494
  }
535
495
 
536
- // ----- 4. HANDSHAKE PHASE -----
537
- // Only check for handshake messages BEFORE handshake is completed
538
- // This avoids expensive JSON parsing on every message after handshake
539
- if (!handshake_completed && is_handshake_message(data)) {
540
- if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
541
- LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
542
- break;
543
- }
544
-
545
- // Check if rats handshake just completed (COMPLETED or NOISE_PENDING state)
546
- if (!handshake_completed) {
547
- RatsPeer peer_copy;
548
- bool rats_handshake_done = false;
549
- bool needs_noise_handshake = false;
496
+ // Compact if >32KB wasted at front
497
+ if (recv_buf.front_waste() > 32768) recv_buf.normalize();
498
+ }
499
+ // ── peers_mutex_ released ────────────────────────────────────────────
500
+
501
+ // Deferred post-handshake completion (includes callbacks – must be outside mutex)
502
+ if (need_post_handshake) {
503
+ handle_post_handshake_completion(socket, peer_copy_for_post_handshake);
504
+ }
505
+
506
+ // ── Phase 2: process frames outside lock ─────────────────────────────
507
+ for (auto& pf : frames) {
508
+ // Handshake phase – RATS JSON handshake messages
509
+ if (pf.state != RatsPeer::HandshakeState::COMPLETED) {
510
+ if (is_handshake_message(pf.data)) {
511
+ if (!handle_handshake_message(socket, pf.peer_id, pf.data)) {
512
+ return true;
513
+ }
550
514
 
515
+ // Check if handshake just completed and handle Noise / post-handshake
516
+ bool do_post_handshake = false;
517
+ RatsPeer post_hs_copy;
551
518
  {
552
519
  std::lock_guard<std::mutex> lock(peers_mutex_);
553
- auto sock_it = socket_to_peer_id_.find(client_socket);
554
- if (sock_it != socket_to_peer_id_.end()) {
555
- auto peer_it = peers_.find(sock_it->second);
556
- if (peer_it != peers_.end()) {
557
- // Check if rats handshake completed (either COMPLETED or NOISE_PENDING)
558
- if (peer_it->second.is_handshake_completed()) {
559
- rats_handshake_done = true;
560
- peer_copy = peer_it->second;
561
- } else if (peer_it->second.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
562
- rats_handshake_done = true;
563
- needs_noise_handshake = true;
564
- peer_copy = peer_it->second;
565
- }
566
- }
567
- }
568
- }
569
-
570
- // ----- POST-HANDSHAKE ACTIONS -----
571
- if (rats_handshake_done) {
572
- LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
573
-
574
- // Remove from reconnection queue if present (successful connection)
575
- remove_from_reconnect_queue(peer_copy.peer_id);
576
-
577
- // Noise encryption handshake - only if BOTH sides support encryption
578
- // peer_copy.encryption_enabled is already negotiated in handle_handshake_message()
579
- if (needs_noise_handshake) {
580
- LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
581
- if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
582
- noise_handshake_done = true;
583
- LOG_CLIENT_INFO("Noise handshake successful for peer " << peer_copy.peer_id);
584
-
585
- // Update state to COMPLETED after successful Noise handshake
586
- {
587
- std::lock_guard<std::mutex> lock(peers_mutex_);
588
- auto sock_it = socket_to_peer_id_.find(client_socket);
589
- if (sock_it != socket_to_peer_id_.end()) {
590
- auto peer_it = peers_.find(sock_it->second);
591
- if (peer_it != peers_.end()) {
592
- peer_it->second.handshake_state = RatsPeer::HandshakeState::COMPLETED;
593
- log_handshake_completion_unlocked(peer_it->second);
594
- }
595
- }
596
- }
597
- } else {
598
- LOG_CLIENT_ERROR("Noise handshake failed for peer " << peer_copy.peer_id);
599
- // Connection will be closed due to failed Noise handshake
600
- break;
601
- }
602
- }
603
-
604
- handshake_completed = true;
520
+ auto peer_it = find_peer_by_socket_unlocked(socket);
521
+ if (peer_it == peers_.end()) return true;
522
+ RatsPeer& peer = peer_it->second;
605
523
 
606
- // Connection callback
607
- if (connection_callback_) {
608
- connection_callback_(client_socket, peer_copy.peer_id);
609
- }
610
-
611
- // GossipSub notification
612
- if (gossipsub_) {
613
- gossipsub_->handle_peer_connected(peer_copy.peer_id);
614
- }
615
-
616
- #ifdef RATS_STORAGE
617
- // Storage manager notification
618
- if (storage_manager_) {
619
- storage_manager_->on_peer_connected(peer_copy.peer_id);
620
- }
621
- #endif
622
-
623
- // Peer exchange broadcast
624
- broadcast_peer_exchange_message(peer_copy);
625
-
626
- // Request peers from newly connected peer (outgoing only)
627
- if (peer_copy.is_outgoing) {
628
- send_peers_request(client_socket, peer_copy.peer_id);
629
- }
630
-
631
- // Save configuration
632
- if (running_.load()) {
633
- add_managed_thread(std::thread([this]() {
634
- if (running_.load()) {
635
- save_configuration();
636
- }
637
- }), "config-save");
524
+ if (peer.handshake_state == RatsPeer::HandshakeState::NOISE_PENDING) {
525
+ start_noise_handshake_async(peer);
526
+ } else if (peer.handshake_state == RatsPeer::HandshakeState::COMPLETED) {
527
+ post_hs_copy = peer;
528
+ do_post_handshake = true;
638
529
  }
639
530
  }
531
+ // peers_mutex_ released – safe to invoke callbacks
532
+ if (do_post_handshake) {
533
+ handle_post_handshake_completion(socket, post_hs_copy);
534
+ }
535
+ } else {
536
+ LOG_CLIENT_WARN("Non-handshake data from " << pf.peer_id << " before handshake – ignoring");
640
537
  }
641
-
642
538
  continue;
643
539
  }
644
540
 
645
- // ----- 5. DATA PROCESSING PHASE -----
646
- if (!handshake_completed) {
647
- LOG_CLIENT_WARN("Received non-handshake data from " << peer_hash_id << " before handshake completion - ignoring");
648
- continue;
541
+ // Data phase – decrypt if needed, then dispatch
542
+ std::vector<uint8_t> plaintext;
543
+ if (pf.noise_encrypted && pf.recv_cipher) {
544
+ if (pf.data.size() < rats::NOISE_TAG_SIZE) {
545
+ LOG_CLIENT_ERROR("Encrypted frame too small from " << pf.peer_id);
546
+ return true;
547
+ }
548
+ plaintext.resize(pf.data.size());
549
+ size_t pt_len = pf.recv_cipher->decrypt_with_ad(
550
+ nullptr, 0, pf.data.data(), pf.data.size(), plaintext.data());
551
+ if (pt_len == 0) {
552
+ LOG_CLIENT_ERROR("Decryption failed from " << pf.peer_id);
553
+ return true;
554
+ }
555
+ plaintext.resize(pt_len);
556
+ } else {
557
+ plaintext = std::move(pf.data);
649
558
  }
650
559
 
651
- // Use data directly - no need for extra copy
652
- MessageHeader header;
653
- std::vector<uint8_t> payload;
560
+ process_message(socket, plaintext, pf.peer_id);
561
+ }
562
+
563
+ return peer_closed;
564
+ }
565
+
566
+ // ---------------------------------------------------------------------------
567
+ // handle_writable – flush the peer's send buffer to the kernel
568
+ // Returns true if the peer should be disconnected.
569
+ // ---------------------------------------------------------------------------
570
+ bool RatsClient::handle_writable(socket_t socket) {
571
+ std::lock_guard<std::mutex> lock(peers_mutex_);
572
+ auto peer_it = find_peer_by_socket_unlocked(socket);
573
+ if (peer_it == peers_.end()) return true;
574
+
575
+ auto& send_buf = peer_it->second.io_.send_buffer;
576
+
577
+ while (!send_buf.empty()) {
578
+ int bytes = ::send(socket,
579
+ reinterpret_cast<const char*>(send_buf.front_data()),
580
+ static_cast<int>(send_buf.front_size()),
581
+ #ifdef _WIN32
582
+ 0
583
+ #else
584
+ MSG_NOSIGNAL
585
+ #endif
586
+ );
654
587
 
655
- if (!parse_message_with_header(data, header, payload)) {
656
- LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
588
+ if (bytes > 0) {
589
+ send_buf.pop_front(static_cast<size_t>(bytes));
657
590
  continue;
658
591
  }
659
592
 
660
- std::string peer_id = get_peer_id(client_socket);
661
-
662
- switch (header.type) {
663
- case MessageDataType::BINARY: {
664
- LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
665
- bool handled = false;
666
- if (file_transfer_manager_) {
667
- handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
668
- }
669
- if (!handled && binary_data_callback_) {
670
- binary_data_callback_(client_socket, peer_id, payload);
671
- }
672
- break;
673
- }
674
-
675
- case MessageDataType::STRING: {
676
- LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
677
- if (string_data_callback_) {
678
- std::string string_data(payload.begin(), payload.end());
679
- string_data_callback_(client_socket, peer_id, string_data);
680
- }
681
- break;
682
- }
683
-
684
- case MessageDataType::JSON: {
685
- LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
686
- std::string json_string(payload.begin(), payload.end());
687
- nlohmann::json json_msg;
688
- if (parse_json_message(json_string, json_msg)) {
689
- if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
690
- handle_rats_message(client_socket, peer_id, json_msg);
691
- } else if (json_data_callback_) {
692
- json_data_callback_(client_socket, peer_id, json_msg);
693
- }
694
- } else {
695
- LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id);
696
- }
697
- break;
698
- }
699
-
700
- default:
701
- LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
702
- break;
593
+ if (bytes < 0) {
594
+ #ifdef _WIN32
595
+ if (WSAGetLastError() == WSAEWOULDBLOCK) break;
596
+ #else
597
+ if (errno == EAGAIN || errno == EWOULDBLOCK) break;
598
+ #endif
599
+ LOG_CLIENT_ERROR("Send error on socket " << socket);
600
+ return true;
703
601
  }
704
602
 
705
- } // end while
603
+ // bytes == 0 — shouldn't happen on a stream socket
604
+ break;
605
+ }
706
606
 
707
- // ===== CLEANUP =====
708
- std::string current_peer_id = get_peer_id(client_socket);
607
+ // If buffer fully flushed, stop watching for PollOut
608
+ if (send_buf.empty()) {
609
+ std::lock_guard<std::mutex> io_lock(io_mutex_);
610
+ if (poller_) poller_->modify(socket, PollIn);
611
+ }
709
612
 
710
- // Save peer info for potential reconnection BEFORE removing from peers list
613
+ return false;
614
+ }
615
+
616
+ // ---------------------------------------------------------------------------
617
+ // handle_disconnect – clean up peer on error / hangup / close
618
+ // ---------------------------------------------------------------------------
619
+ void RatsClient::handle_disconnect(socket_t socket) {
620
+ // Gather info before removing
621
+ std::string peer_id;
622
+ bool was_validated = false;
711
623
  RatsPeer peer_copy_for_reconnect;
712
624
  bool should_schedule_reconnect = false;
713
625
 
714
- if (handshake_completed) {
626
+ {
715
627
  std::lock_guard<std::mutex> lock(peers_mutex_);
716
- auto sock_it = socket_to_peer_id_.find(client_socket);
717
- if (sock_it != socket_to_peer_id_.end()) {
718
- auto peer_it = peers_.find(sock_it->second);
719
- if (peer_it != peers_.end()) {
720
- peer_copy_for_reconnect = peer_it->second;
721
- should_schedule_reconnect = true;
722
- }
628
+ auto peer_it = find_peer_by_socket_unlocked(socket);
629
+ if (peer_it == peers_.end()) {
630
+ // Already removed
631
+ poller_remove(socket);
632
+ close_socket(socket);
633
+ return;
634
+ }
635
+ peer_id = peer_it->second.peer_id;
636
+ was_validated = peer_it->second.is_handshake_completed();
637
+ if (was_validated) {
638
+ peer_copy_for_reconnect = peer_it->second;
639
+ should_schedule_reconnect = true;
723
640
  }
724
641
  }
725
642
 
726
- remove_peer(client_socket);
727
- close_socket(client_socket);
643
+ // Remove from poller, peers map, close socket
644
+ poller_remove(socket);
645
+ remove_peer(socket);
646
+ close_socket(socket);
728
647
 
729
- if (handshake_completed) {
648
+ if (was_validated) {
730
649
  if (disconnect_callback_) {
731
- disconnect_callback_(client_socket, current_peer_id);
650
+ disconnect_callback_(socket, peer_id);
732
651
  }
733
-
734
652
  if (gossipsub_) {
735
- gossipsub_->handle_peer_disconnected(current_peer_id);
653
+ gossipsub_->handle_peer_disconnected(peer_id);
736
654
  }
737
-
738
- // Schedule reconnection if we have valid peer info
739
655
  if (should_schedule_reconnect && running_.load()) {
740
656
  schedule_reconnect(peer_copy_for_reconnect);
741
657
  }
742
-
743
658
  if (running_.load()) {
744
659
  add_managed_thread(std::thread([this]() {
745
- if (running_.load()) {
746
- save_configuration();
747
- }
660
+ if (running_.load()) save_configuration();
748
661
  }), "config-save-disconnect");
749
662
  }
750
663
  }
751
664
 
752
- LOG_CLIENT_INFO("Client disconnected: " << peer_hash_id);
665
+ LOG_CLIENT_INFO("Peer disconnected: " << peer_id);
666
+ }
667
+
668
+ // ---------------------------------------------------------------------------
669
+ // Poller registration helpers (thread-safe via io_mutex_)
670
+ // ---------------------------------------------------------------------------
671
+ void RatsClient::poller_add(socket_t fd, uint32_t events) {
672
+ std::lock_guard<std::mutex> lock(io_mutex_);
673
+ if (poller_) poller_->add(fd, events);
674
+ }
675
+
676
+ void RatsClient::poller_modify(socket_t fd, uint32_t events) {
677
+ std::lock_guard<std::mutex> lock(io_mutex_);
678
+ if (poller_) poller_->modify(fd, events);
679
+ }
680
+
681
+ void RatsClient::poller_remove(socket_t fd) {
682
+ std::lock_guard<std::mutex> lock(io_mutex_);
683
+ if (poller_) poller_->remove(fd);
684
+ }
685
+
686
+ // ---------------------------------------------------------------------------
687
+ // enqueue_message – build a length-prefixed frame and append to send buffer
688
+ // ---------------------------------------------------------------------------
689
+ bool RatsClient::enqueue_message(socket_t socket, const std::vector<uint8_t>& data) {
690
+ std::lock_guard<std::mutex> lock(peers_mutex_);
691
+ auto peer_it = find_peer_by_socket_unlocked(socket);
692
+ if (peer_it == peers_.end()) return false;
693
+ return enqueue_message_unlocked(peer_it->second, data);
694
+ }
695
+
696
+ bool RatsClient::enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data) {
697
+ // Build length-prefixed frame: [4-byte network-order length][payload]
698
+ uint32_t net_len = htonl(static_cast<uint32_t>(data.size()));
699
+
700
+ std::vector<uint8_t> frame;
701
+ frame.reserve(4 + data.size());
702
+ frame.insert(frame.end(),
703
+ reinterpret_cast<const uint8_t*>(&net_len),
704
+ reinterpret_cast<const uint8_t*>(&net_len) + 4);
705
+ frame.insert(frame.end(), data.begin(), data.end());
706
+
707
+ peer.io_.send_buffer.append(std::move(frame));
708
+
709
+ // Arm PollOut so io_loop flushes the buffer
710
+ {
711
+ std::lock_guard<std::mutex> io_lock(io_mutex_);
712
+ if (poller_) poller_->modify(peer.socket, PollIn | PollOut);
713
+ }
714
+
715
+ return true;
716
+ }
717
+
718
+ void RatsClient::management_loop() {
719
+ LOG_CLIENT_INFO("Management loop started");
720
+
721
+ auto last_thread_cleanup = std::chrono::steady_clock::now();
722
+ const auto thread_cleanup_interval = std::chrono::seconds(THREAD_CLEANUP_INTERVAL_SECONDS);
723
+
724
+ while (running_.load()) {
725
+ // Wait for interval or until shutdown (for responsive reconnection processing)
726
+ {
727
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
728
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(MANAGEMENT_LOOP_INTERVAL_SECONDS), [this] { return !running_.load(); })) {
729
+ break; // Exit if shutdown requested
730
+ }
731
+ }
732
+
733
+ // Check handshake timeouts (centralized, runs once for all peers)
734
+ try {
735
+ check_handshake_timeouts();
736
+ } catch (const std::exception& e) {
737
+ LOG_CLIENT_ERROR("Exception during handshake timeout check: " << e.what());
738
+ }
739
+
740
+ // Process reconnection queue
741
+ try {
742
+ process_reconnect_queue();
743
+ } catch (const std::exception& e) {
744
+ LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
745
+ }
746
+
747
+ // Periodically cleanup finished threads (every 30 seconds)
748
+ auto now = std::chrono::steady_clock::now();
749
+ if (now - last_thread_cleanup >= thread_cleanup_interval) {
750
+ try {
751
+ cleanup_finished_threads();
752
+ LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
753
+ } catch (const std::exception& e) {
754
+ LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
755
+ }
756
+ last_thread_cleanup = now;
757
+ }
758
+ }
759
+
760
+ LOG_CLIENT_INFO("Management loop ended");
761
+ }
762
+
763
+ void RatsClient::handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy) {
764
+ // Remove from reconnection queue (successful connection)
765
+ remove_from_reconnect_queue(peer_copy.peer_id);
766
+
767
+ // Connection callback
768
+ if (connection_callback_) {
769
+ connection_callback_(socket, peer_copy.peer_id);
770
+ }
771
+
772
+ // GossipSub notification
773
+ if (gossipsub_) {
774
+ gossipsub_->handle_peer_connected(peer_copy.peer_id);
775
+ }
776
+
777
+ #ifdef RATS_STORAGE
778
+ // Storage manager notification
779
+ if (storage_manager_) {
780
+ storage_manager_->on_peer_connected(peer_copy.peer_id);
781
+ }
782
+ #endif
783
+
784
+ // Peer exchange broadcast
785
+ broadcast_peer_exchange_message(peer_copy);
786
+
787
+ // Request peers from newly connected peer (outgoing only)
788
+ if (peer_copy.is_outgoing) {
789
+ send_peers_request(socket, peer_copy.peer_id);
790
+ }
791
+
792
+ // Save configuration
793
+ if (running_.load()) {
794
+ add_managed_thread(std::thread([this]() {
795
+ if (running_.load()) {
796
+ save_configuration();
797
+ }
798
+ }), "config-save");
799
+ }
800
+ }
801
+
802
+ void RatsClient::process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& initial_peer_id) {
803
+ MessageHeader header;
804
+ std::vector<uint8_t> payload;
805
+
806
+ if (!parse_message_with_header(data, header, payload)) {
807
+ LOG_CLIENT_WARN("No header found in message from " << initial_peer_id);
808
+ return;
809
+ }
810
+
811
+ std::string peer_id = get_peer_id(socket);
812
+
813
+ switch (header.type) {
814
+ case MessageDataType::BINARY: {
815
+ LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
816
+ bool handled = false;
817
+ if (file_transfer_manager_) {
818
+ handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
819
+ }
820
+ if (!handled && binary_data_callback_) {
821
+ binary_data_callback_(socket, peer_id, payload);
822
+ }
823
+ break;
824
+ }
825
+
826
+ case MessageDataType::STRING: {
827
+ LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
828
+ if (string_data_callback_) {
829
+ std::string string_data(payload.begin(), payload.end());
830
+ string_data_callback_(socket, peer_id, string_data);
831
+ }
832
+ break;
833
+ }
834
+
835
+ case MessageDataType::JSON: {
836
+ LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
837
+ try {
838
+ nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
839
+ if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
840
+ handle_rats_message(socket, peer_id, json_msg);
841
+ } else if (json_data_callback_) {
842
+ json_data_callback_(socket, peer_id, json_msg);
843
+ }
844
+ } catch (const nlohmann::json::exception& e) {
845
+ LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id << ": " << e.what());
846
+ }
847
+ break;
848
+ }
849
+
850
+ default:
851
+ LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
852
+ break;
853
+ }
753
854
  }
754
855
 
755
856
  // Handshake protocol implementation
@@ -844,9 +945,8 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
844
945
  auto now = std::chrono::high_resolution_clock::now();
845
946
  auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
846
947
  int64_t time_diff = std::abs(current_timestamp - msg.timestamp);
847
- const int64_t allowed_skew_ms = 10LL * 60LL * 1000LL; // 10 minutes tolerance
848
- if (time_diff > allowed_skew_ms) {
849
- LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << allowed_skew_ms << "ms; accepting to be tolerant of clock skew");
948
+ if (time_diff > TIMESTAMP_SKEW_TOLERANCE_MS) {
949
+ LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << TIMESTAMP_SKEW_TOLERANCE_MS << "ms; accepting to be tolerant of clock skew");
850
950
  }
851
951
  }
852
952
 
@@ -882,52 +982,32 @@ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
882
982
  }
883
983
  }
884
984
 
885
- // Add this private helper function before send_handshake
886
- bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
985
+ bool RatsClient::send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id) {
887
986
  std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
888
- LOG_CLIENT_DEBUG("Sending handshake to socket " << socket << ": " << handshake_msg);
987
+ LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
889
988
 
890
- // Send handshake directly without going through send_binary_to_peer
891
- // (which would cause deadlock by trying to lock peers_mutex_ again).
892
- // Handshakes are always unencrypted since they happen before noise handshake.
989
+ // Handshakes are always unencrypted – enqueue into the peer's send buffer
893
990
  std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
894
991
  std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
895
992
 
896
- // Get socket-specific mutex for thread-safe sending
897
- auto socket_mutex = get_socket_send_mutex(socket);
898
- std::lock_guard<std::mutex> send_lock(*socket_mutex);
899
-
900
- int sent = send_tcp_message_framed(socket, message_with_header);
901
- if (sent <= 0) {
902
- LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
993
+ if (!enqueue_message_unlocked(peer, message_with_header)) {
994
+ LOG_CLIENT_ERROR("Failed to enqueue handshake for " << peer.peer_id);
903
995
  return false;
904
996
  }
905
997
 
906
- // Update peer state (assumes peers_mutex_ is already locked)
907
- auto it = socket_to_peer_id_.find(socket);
908
- if (it != socket_to_peer_id_.end()) {
909
- auto peer_it = peers_.find(it->second);
910
- if (peer_it != peers_.end()) {
911
- peer_it->second.handshake_state = RatsPeer::HandshakeState::SENT;
912
- peer_it->second.handshake_start_time = std::chrono::steady_clock::now();
913
- }
914
- }
998
+ peer.handshake_state = RatsPeer::HandshakeState::SENT;
999
+ peer.handshake_start_time = std::chrono::steady_clock::now();
915
1000
 
916
1001
  return true;
917
1002
  }
918
1003
 
919
- bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id) {
920
- std::lock_guard<std::mutex> lock(peers_mutex_);
921
- return send_handshake_unlocked(socket, our_peer_id);
922
- }
923
-
924
- bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data) {
1004
+ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data) {
925
1005
  // Extract JSON payload from message header
926
1006
  MessageHeader header;
927
1007
  std::vector<uint8_t> payload;
928
1008
 
929
1009
  if (!parse_message_with_header(data, header, payload)) {
930
- LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
1010
+ LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
931
1011
  return false;
932
1012
  }
933
1013
 
@@ -940,12 +1020,12 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
940
1020
  // Parse handshake message directly from payload (no string conversion)
941
1021
  HandshakeMessage handshake_msg;
942
1022
  if (!parse_handshake_message(payload, handshake_msg)) {
943
- LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
1023
+ LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
944
1024
  return false;
945
1025
  }
946
1026
 
947
1027
  if (!validate_handshake_message(handshake_msg)) {
948
- LOG_CLIENT_ERROR("Invalid handshake message from " << peer_hash_id);
1028
+ LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
949
1029
  return false;
950
1030
  }
951
1031
 
@@ -954,19 +1034,13 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
954
1034
  return false;
955
1035
  }
956
1036
 
957
- LOG_CLIENT_INFO("Received valid handshake from " << peer_hash_id
1037
+ LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
958
1038
  << " (peer_id: " << handshake_msg.peer_id << ")");
959
1039
 
960
1040
  std::lock_guard<std::mutex> lock(peers_mutex_);
961
- auto it = socket_to_peer_id_.find(socket);
962
- if (it == socket_to_peer_id_.end()) {
963
- LOG_CLIENT_ERROR("Socket " << socket << " not found in peer mapping");
964
- return false;
965
- }
966
-
967
- auto peer_it = peers_.find(it->second);
1041
+ auto peer_it = find_peer_by_socket_unlocked(socket);
968
1042
  if (peer_it == peers_.end()) {
969
- LOG_CLIENT_ERROR("Peer " << peer_hash_id << " not found in peers");
1043
+ LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
970
1044
  return false;
971
1045
  }
972
1046
 
@@ -982,22 +1056,18 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
982
1056
 
983
1057
  // Update peer mappings with new peer_id if it changed
984
1058
  if (old_peer_id != handshake_msg.peer_id) {
985
- // Create a copy of the peer object before erasing it.
986
- RatsPeer peer_copy = peer_it->second;
987
-
988
- // Erase the old entry from the main peers map.
1059
+ // Move the peer (preserves io_ context with send/recv buffers)
1060
+ RatsPeer peer_moved = std::move(peer_it->second);
989
1061
  peers_.erase(peer_it);
990
1062
 
991
- // Update the peer_id within the copied object.
992
- peer_copy.peer_id = handshake_msg.peer_id;
1063
+ peer_moved.peer_id = handshake_msg.peer_id;
993
1064
 
994
- // Insert the updated peer object back into the maps with the new peer_id.
995
- peers_[peer_copy.peer_id] = peer_copy;
996
- socket_to_peer_id_[socket] = peer_copy.peer_id;
997
- address_to_peer_id_[peer_copy.normalized_address] = peer_copy.peer_id;
998
-
999
- // Find the iterator for the newly inserted peer.
1000
- peer_it = peers_.find(peer_copy.peer_id);
1065
+ // Use emplace to avoid extra copy/move
1066
+ auto [new_it, ok] = peers_.emplace(peer_moved.peer_id, std::move(peer_moved));
1067
+ socket_to_peer_id_[socket] = new_it->second.peer_id;
1068
+ address_to_peer_id_[new_it->second.normalized_address] = new_it->second.peer_id;
1069
+
1070
+ peer_it = new_it;
1001
1071
  }
1002
1072
 
1003
1073
  RatsPeer& peer = peer_it->second;
@@ -1034,14 +1104,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1034
1104
  // Simplified handshake logic - just one message type
1035
1105
  if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1036
1106
  // This is an incoming handshake - send our handshake back
1037
- if (send_handshake_unlocked(socket, get_our_peer_id())) {
1107
+ if (send_handshake_unlocked(peer, get_our_peer_id())) {
1038
1108
  // If encryption is enabled, we need to do Noise handshake first
1039
1109
  // Set NOISE_PENDING to prevent other threads from sending messages
1040
1110
  if (peer.encryption_enabled) {
1041
1111
  peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1042
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1112
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1043
1113
  } else {
1044
1114
  peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1115
+ validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1045
1116
  log_handshake_completion_unlocked(peer);
1046
1117
  }
1047
1118
 
@@ -1051,7 +1122,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1051
1122
  return true;
1052
1123
  } else {
1053
1124
  peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1054
- LOG_CLIENT_ERROR("Failed to send handshake response to " << peer_hash_id);
1125
+ LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
1055
1126
  return false;
1056
1127
  }
1057
1128
  } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
@@ -1060,9 +1131,10 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1060
1131
  // Set NOISE_PENDING to prevent other threads from sending messages
1061
1132
  if (peer.encryption_enabled) {
1062
1133
  peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1063
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1134
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1064
1135
  } else {
1065
1136
  peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1137
+ validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1066
1138
  log_handshake_completion_unlocked(peer);
1067
1139
  }
1068
1140
 
@@ -1071,7 +1143,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1071
1143
 
1072
1144
  return true;
1073
1145
  } else {
1074
- LOG_CLIENT_WARN("Received handshake from " << peer_hash_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1146
+ LOG_CLIENT_WARN("Received handshake from " << initial_peer_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1075
1147
  return false;
1076
1148
  }
1077
1149
  }
@@ -1105,8 +1177,8 @@ void RatsClient::check_handshake_timeouts() {
1105
1177
  socket_t socket = peer_it->second.socket;
1106
1178
  LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
1107
1179
 
1108
- // Clean up peer data
1109
1180
  remove_peer_by_id_unlocked(peer_id);
1181
+ poller_remove(socket);
1110
1182
  close_socket(socket);
1111
1183
  }
1112
1184
  }
@@ -1143,70 +1215,108 @@ bool RatsClient::connect_to_peer(const std::string& host, int port) {
1143
1215
  return false;
1144
1216
  }
1145
1217
 
1146
- // Create TCP connection with timeout
1147
- socket_t client_socket = create_tcp_client(host, port, 10000); // 10 second timeout
1148
- if (!is_valid_socket(client_socket)) {
1149
- LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
1150
- return false;
1151
- }
1152
-
1153
- LOG_CLIENT_INFO("Successfully connected to " << host << ":" << port);
1154
-
1155
- // Generate peer hash ID
1156
- std::string peer_hash_id = generate_peer_hash_id(client_socket, normalized_address);
1157
-
1158
- // Create RatsPeer object for outgoing connection
1159
- {
1160
- std::lock_guard<std::mutex> lock(peers_mutex_);
1161
- RatsPeer new_peer(peer_hash_id, host, static_cast<uint16_t>(port), client_socket, normalized_address, true); // true = outgoing connection
1162
- new_peer.encryption_enabled = is_encryption_enabled();
1163
- add_peer_unlocked(new_peer);
1164
- }
1165
-
1166
- // Start a thread to handle this client
1167
- LOG_CLIENT_DEBUG("Starting thread for outgoing connection " << peer_hash_id);
1168
- add_managed_thread(std::thread(&RatsClient::handle_client, this, client_socket, peer_hash_id),
1169
- "client-handler-" + peer_hash_id.substr(0, 8));
1218
+ // Create TCP connection with timeout (blocking connect is done on a managed thread
1219
+ // to avoid blocking the caller, then the connected socket is handed to the IO loop).
1220
+ add_managed_thread(std::thread([this, host, port, normalized_address]() {
1221
+ socket_t client_socket = create_tcp_client(host, port, TCP_CONNECT_TIMEOUT_MS);
1222
+ if (!is_valid_socket(client_socket)) {
1223
+ LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
1224
+ return;
1225
+ }
1226
+
1227
+ LOG_CLIENT_INFO("TCP connected to " << host << ":" << port);
1228
+
1229
+ // Switch to non-blocking for the IO poller
1230
+ set_socket_nonblocking(client_socket);
1231
+
1232
+ std::string initial_peer_id = generate_temporary_peer_id(client_socket, normalized_address);
1233
+
1234
+ {
1235
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1236
+
1237
+ // Re-check peer limit and duplicate (could have changed while connecting)
1238
+ // NOTE: Use unlocked variant — peers_mutex_ is already held!
1239
+ if (get_peer_count_unlocked() >= max_peers_) {
1240
+ LOG_CLIENT_DEBUG("connect_to_peer: peer limit reached after TCP connect, aborting fd=" << client_socket);
1241
+ close_socket(client_socket);
1242
+ return;
1243
+ }
1244
+ if (address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end()) {
1245
+ LOG_CLIENT_DEBUG("connect_to_peer: duplicate address " << normalized_address << " after TCP connect, aborting fd=" << client_socket);
1246
+ close_socket(client_socket);
1247
+ return;
1248
+ }
1249
+
1250
+ RatsPeer new_peer(initial_peer_id, host, static_cast<uint16_t>(port),
1251
+ client_socket, normalized_address, true);
1252
+ new_peer.encryption_enabled = is_encryption_enabled();
1253
+ add_peer_unlocked(new_peer);
1254
+
1255
+ // Send initial handshake (enqueued into send buffer)
1256
+ auto peer_it = peers_.find(initial_peer_id);
1257
+ if (peer_it != peers_.end()) {
1258
+ if (!send_handshake_unlocked(peer_it->second, get_our_peer_id())) {
1259
+ LOG_CLIENT_ERROR("Failed to enqueue handshake for outgoing connection to " << host << ":" << port);
1260
+ remove_peer_by_id_unlocked(initial_peer_id);
1261
+ close_socket(client_socket);
1262
+ return;
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ // Register with poller – PollIn for reads, PollOut to flush the queued handshake
1268
+ poller_add(client_socket, PollIn | PollOut);
1269
+
1270
+ LOG_CLIENT_INFO("Outgoing connection to " << host << ":" << port << " registered with IO poller");
1271
+ }), "connect-" + host + ":" + std::to_string(port));
1170
1272
 
1171
1273
  return true;
1172
1274
  }
1173
1275
 
1276
+ void RatsClient::mark_manual_disconnect(const std::string& peer_id) {
1277
+ std::lock_guard<std::mutex> lock(reconnect_mutex_);
1278
+ manual_disconnect_peers_.insert(peer_id);
1279
+ reconnect_queue_.erase(peer_id);
1280
+ }
1281
+
1174
1282
  void RatsClient::disconnect_peer(socket_t socket) {
1175
- // Mark as manually disconnected to prevent auto-reconnection
1176
1283
  std::string peer_id = get_peer_id(socket);
1177
1284
  if (!peer_id.empty()) {
1178
- std::lock_guard<std::mutex> lock(reconnect_mutex_);
1179
- manual_disconnect_peers_.insert(peer_id);
1180
- // Also remove from reconnection queue if present
1181
- reconnect_queue_.erase(peer_id);
1285
+ mark_manual_disconnect(peer_id);
1182
1286
  }
1183
-
1287
+ poller_remove(socket);
1184
1288
  remove_peer(socket);
1185
1289
  close_socket(socket);
1186
1290
  }
1187
1291
 
1188
1292
  void RatsClient::disconnect_peer_by_id(const std::string& peer_id) {
1189
- // Mark as manually disconnected to prevent auto-reconnection
1190
- {
1191
- std::lock_guard<std::mutex> lock(reconnect_mutex_);
1192
- manual_disconnect_peers_.insert(peer_id);
1193
- // Also remove from reconnection queue if present
1194
- reconnect_queue_.erase(peer_id);
1195
- }
1196
-
1293
+ mark_manual_disconnect(peer_id);
1197
1294
  socket_t socket = get_peer_socket_by_id(peer_id);
1198
1295
  if (is_valid_socket(socket)) {
1296
+ poller_remove(socket);
1199
1297
  remove_peer(socket);
1200
1298
  close_socket(socket);
1201
1299
  }
1202
1300
  }
1203
1301
 
1204
- // Helper methods for peer management
1205
- void RatsClient::add_peer(const RatsPeer& peer) {
1206
- std::lock_guard<std::mutex> lock(peers_mutex_);
1207
- add_peer_unlocked(peer);
1302
+ // Peer lookup helpers (assumes peers_mutex_ is already locked)
1303
+ std::unordered_map<std::string, RatsPeer>::iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) {
1304
+ auto sock_it = socket_to_peer_id_.find(socket);
1305
+ if (sock_it != socket_to_peer_id_.end()) {
1306
+ return peers_.find(sock_it->second);
1307
+ }
1308
+ return peers_.end();
1208
1309
  }
1209
1310
 
1311
+ std::unordered_map<std::string, RatsPeer>::const_iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) const {
1312
+ auto sock_it = socket_to_peer_id_.find(socket);
1313
+ if (sock_it != socket_to_peer_id_.end()) {
1314
+ return peers_.find(sock_it->second);
1315
+ }
1316
+ return peers_.end();
1317
+ }
1318
+
1319
+ // Helper methods for peer management
1210
1320
  void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1211
1321
  // Assumes peers_mutex_ is already locked
1212
1322
  peers_[peer.peer_id] = peer;
@@ -1216,17 +1326,12 @@ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1216
1326
 
1217
1327
  void RatsClient::remove_peer(socket_t socket) {
1218
1328
  std::lock_guard<std::mutex> lock(peers_mutex_);
1219
- auto it = socket_to_peer_id_.find(socket);
1220
- if (it != socket_to_peer_id_.end()) {
1221
- remove_peer_by_id_unlocked(it->second);
1329
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1330
+ if (peer_it != peers_.end()) {
1331
+ remove_peer_by_id_unlocked(peer_it->second.peer_id);
1222
1332
  }
1223
1333
  }
1224
1334
 
1225
- void RatsClient::remove_peer_by_id(const std::string& peer_id) {
1226
- std::lock_guard<std::mutex> lock(peers_mutex_);
1227
- remove_peer_by_id_unlocked(peer_id);
1228
- }
1229
-
1230
1335
  void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1231
1336
  // Assumes peers_mutex_ is already locked
1232
1337
 
@@ -1235,6 +1340,11 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1235
1340
 
1236
1341
  auto it = peers_.find(peer_id_copy);
1237
1342
  if (it != peers_.end()) {
1343
+ // Decrement validated peer count if this peer had completed handshake
1344
+ if (it->second.is_handshake_completed()) {
1345
+ validated_peer_count_.fetch_sub(1, std::memory_order_relaxed);
1346
+ }
1347
+
1238
1348
  // Copy the values we need before erasing to avoid use-after-free
1239
1349
  socket_t peer_socket = it->second.socket;
1240
1350
  std::string peer_normalized_address = it->second.normalized_address;
@@ -1242,9 +1352,6 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1242
1352
  socket_to_peer_id_.erase(peer_socket);
1243
1353
  address_to_peer_id_.erase(peer_normalized_address);
1244
1354
  peers_.erase(it);
1245
-
1246
- // Clean up socket-specific mutex
1247
- cleanup_socket_send_mutex(peer_socket);
1248
1355
  }
1249
1356
  }
1250
1357
 
@@ -1256,9 +1363,8 @@ bool RatsClient::is_already_connected_to_address(const std::string& normalized_a
1256
1363
  void RatsClient::add_ignored_address(const std::string& ip_address) {
1257
1364
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1258
1365
 
1259
- // Check if already in the list
1260
- if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), ip_address) == local_interface_addresses_.end()) {
1261
- local_interface_addresses_.push_back(ip_address);
1366
+ auto [it, inserted] = local_interface_addresses_.insert(ip_address);
1367
+ if (inserted) {
1262
1368
  LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
1263
1369
  } else {
1264
1370
  LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
@@ -1266,7 +1372,7 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
1266
1372
  }
1267
1373
 
1268
1374
  //common localhost addresses
1269
- static constexpr std::array<std::string_view,4> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::"};
1375
+ static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::", "localhost"};
1270
1376
 
1271
1377
  // Local interface address blocking methods
1272
1378
  void RatsClient::initialize_local_addresses() {
@@ -1275,13 +1381,12 @@ void RatsClient::initialize_local_addresses() {
1275
1381
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1276
1382
 
1277
1383
  // Get all local interface addresses using network_utils
1278
- local_interface_addresses_ = network_utils::get_local_interface_addresses();
1384
+ auto addrs = network_utils::get_local_interface_addresses();
1385
+ local_interface_addresses_.insert(addrs.begin(), addrs.end());
1279
1386
 
1280
- // Add common localhost addresses if not already present
1387
+ // Add common localhost addresses
1281
1388
  for (const auto& addr : localhost_addrs) {
1282
- if (std::find(local_interface_addresses_.begin(), local_interface_addresses_.end(), addr) == local_interface_addresses_.end()) {
1283
- local_interface_addresses_.emplace_back(addr);
1284
- }
1389
+ local_interface_addresses_.emplace(std::string(addr));
1285
1390
  }
1286
1391
 
1287
1392
  LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
@@ -1290,36 +1395,47 @@ void RatsClient::initialize_local_addresses() {
1290
1395
  }
1291
1396
  }
1292
1397
 
1293
-
1294
1398
  bool RatsClient::is_blocked_address(const std::string& ip_address) const {
1295
1399
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1400
+ return local_interface_addresses_.count(ip_address) > 0;
1401
+ }
1402
+
1403
+ bool RatsClient::can_connect_to_peer(const std::string& ip, int port) const {
1404
+ if (should_ignore_peer(ip, port)) {
1405
+ LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - blocked address");
1406
+ return false;
1407
+ }
1296
1408
 
1297
- // Check against our stored local addresses
1298
- for (const auto& local_addr : local_interface_addresses_) {
1299
- if (local_addr == ip_address) {
1300
- return true;
1301
- }
1409
+ std::string normalized_address = normalize_peer_address(ip, port);
1410
+ if (is_already_connected_to_address(normalized_address)) {
1411
+ LOG_CLIENT_DEBUG("Already connected to " << normalized_address);
1412
+ return false;
1302
1413
  }
1303
1414
 
1304
- return false;
1415
+ if (is_peer_limit_reached()) {
1416
+ LOG_CLIENT_DEBUG("Peer limit reached, cannot connect to " << ip << ":" << port);
1417
+ return false;
1418
+ }
1419
+
1420
+ return true;
1305
1421
  }
1306
1422
 
1307
1423
  bool RatsClient::should_ignore_peer(const std::string& ip, int port) const {
1308
- // Always block connections to ourselves (same port)
1309
- if (port == listen_port_) {
1310
- if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost" || ip == "0.0.0.0" || ip == "::") {
1424
+ // Check if this is a well-known localhost address
1425
+ bool is_localhost = std::find(localhost_addrs.begin(), localhost_addrs.end(), ip) != localhost_addrs.end();
1426
+
1427
+ if (is_localhost) {
1428
+ // Block self-connections (same port on localhost)
1429
+ if (port == listen_port_) {
1311
1430
  LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
1312
1431
  return true;
1313
1432
  }
1314
- }
1315
-
1316
- // For localhost addresses on different ports, allow the connection (for testing)
1317
- if (ip == "127.0.0.1" || ip == "::1" || ip == "localhost") {
1433
+ // Allow localhost on different ports (for testing)
1318
1434
  LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
1319
1435
  return false;
1320
1436
  }
1321
1437
 
1322
- // Check if the IP is a non-localhost local interface address
1438
+ // Block non-localhost local interface addresses
1323
1439
  if (is_blocked_address(ip)) {
1324
1440
  LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
1325
1441
  return true;
@@ -1372,30 +1488,25 @@ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message,
1372
1488
  // Extract payload
1373
1489
  payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
1374
1490
 
1375
- LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
1376
1491
  return true;
1377
1492
  }
1378
1493
 
1379
- // Unlocked version - assumes peers_mutex_ is already locked or peer data is cached
1380
- // Takes pre-cached encryption data to avoid locking peers_mutex_ inside
1494
+ // Async send – enqueues header + (optionally encrypted) payload into the peer's
1495
+ // ChainedSendBuffer. Does NOT require peers_mutex_; caller passes cached peer data.
1496
+ // The shared_ptr keeps the cipher alive even if the peer is removed concurrently.
1381
1497
  bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
1382
1498
  MessageDataType message_type,
1383
- rats::NoiseCipherState* send_cipher,
1499
+ std::shared_ptr<rats::NoiseCipherState> send_cipher,
1384
1500
  const std::string& peer_id_for_logging) {
1385
1501
  if (!running_.load()) {
1386
1502
  return false;
1387
1503
  }
1388
1504
 
1389
- // Get socket-specific mutex for thread-safe sending
1390
- // Prevent framed messages corruption (like two-times sending the number of bytes instead number of bytes + message)
1391
- auto socket_mutex = get_socket_send_mutex(socket);
1392
- std::lock_guard<std::mutex> send_lock(*socket_mutex);
1393
-
1394
1505
  // Create message with specified header type
1395
1506
  std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
1396
1507
 
1397
1508
  if (send_cipher) {
1398
- // Encrypt the message before sending
1509
+ // Encrypt the message before enqueuing
1399
1510
  std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
1400
1511
  size_t ct_len = send_cipher->encrypt_with_ad(
1401
1512
  nullptr, 0,
@@ -1409,16 +1520,13 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
1409
1520
  }
1410
1521
 
1411
1522
  ciphertext.resize(ct_len);
1412
- LOG_CLIENT_DEBUG("Sending encrypted message to " << peer_id_for_logging << " (" << ct_len << " bytes)");
1523
+ LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
1413
1524
 
1414
- // Send encrypted message using framed protocol
1415
- int sent = send_tcp_message_framed(socket, ciphertext);
1416
- return sent > 0;
1525
+ return enqueue_message(socket, ciphertext);
1417
1526
  }
1418
1527
 
1419
- // Unencrypted path - use framed messages for reliable large message handling
1420
- int sent = send_tcp_message_framed(socket, message_with_header);
1421
- return sent > 0;
1528
+ // Unencrypted path
1529
+ return enqueue_message(socket, message_with_header);
1422
1530
  }
1423
1531
 
1424
1532
  bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
@@ -1426,25 +1534,22 @@ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>
1426
1534
  return false;
1427
1535
  }
1428
1536
 
1429
- // Cache peer encryption data under lock, then release lock before sending
1537
+ // Cache peer data under lock, then release lock before sending
1430
1538
  std::string peer_id;
1431
- rats::NoiseCipherState* send_cipher = nullptr;
1539
+ std::shared_ptr<rats::NoiseCipherState> send_cipher;
1432
1540
 
1433
1541
  {
1434
1542
  std::lock_guard<std::mutex> lock(peers_mutex_);
1435
- auto sock_it = socket_to_peer_id_.find(socket);
1436
- if (sock_it != socket_to_peer_id_.end()) {
1437
- auto peer_it = peers_.find(sock_it->second);
1438
- if (peer_it != peers_.end()) {
1439
- peer_id = peer_it->second.peer_id;
1440
- if (peer_it->second.is_noise_encrypted()) {
1441
- send_cipher = peer_it->second.send_cipher.get();
1442
- }
1543
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1544
+ if (peer_it != peers_.end()) {
1545
+ peer_id = peer_it->second.peer_id;
1546
+ if (peer_it->second.is_noise_encrypted()) {
1547
+ send_cipher = peer_it->second.send_cipher; // shared_ptr copy keeps cipher alive
1443
1548
  }
1444
1549
  }
1445
1550
  }
1446
1551
 
1447
- // Call unlocked version with cached data (peers_mutex_ is released)
1552
+ // peers_mutex_ released -- safe to do potentially slow TCP send
1448
1553
  return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1449
1554
  }
1450
1555
 
@@ -1454,43 +1559,50 @@ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
1454
1559
  return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
1455
1560
  }
1456
1561
 
1562
+ std::vector<uint8_t> RatsClient::json_to_binary(const nlohmann::json& data) {
1563
+ std::string s = data.dump();
1564
+ return {s.begin(), s.end()};
1565
+ }
1566
+
1457
1567
  bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
1458
1568
  try {
1459
- // Serialize JSON and convert to binary, then use the primary send_binary_to_peer method
1460
- std::string json_string = data.dump();
1461
- std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1462
- return send_binary_to_peer(socket, binary_data, MessageDataType::JSON);
1569
+ return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
1463
1570
  } catch (const nlohmann::json::exception& e) {
1464
1571
  LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1465
1572
  return false;
1466
1573
  }
1467
1574
  }
1468
1575
 
1469
- bool RatsClient::send_binary_to_peer_id(const std::string& peer_hash_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
1470
- std::lock_guard<std::mutex> lock(peers_mutex_);
1471
- auto it = peers_.find(peer_hash_id);
1472
- if (it == peers_.end() || !it->second.is_handshake_completed()) {
1473
- return false;
1576
+ bool RatsClient::send_binary_to_peer_id(const std::string& peer_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
1577
+ // Cache peer data under lock, then release before sending
1578
+ socket_t socket;
1579
+ std::shared_ptr<rats::NoiseCipherState> send_cipher;
1580
+
1581
+ {
1582
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1583
+ auto it = peers_.find(peer_id);
1584
+ if (it == peers_.end() || !it->second.is_handshake_completed()) {
1585
+ return false;
1586
+ }
1587
+ socket = it->second.socket;
1588
+ if (it->second.is_noise_encrypted()) {
1589
+ send_cipher = it->second.send_cipher; // shared_ptr copy keeps cipher alive
1590
+ }
1474
1591
  }
1475
1592
 
1476
- // Use unlocked version since we already hold peers_mutex_
1477
- const RatsPeer& peer = it->second;
1478
- rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
1479
- return send_binary_to_peer_unlocked(peer.socket, data, message_type, send_cipher, peer.peer_id);
1593
+ // peers_mutex_ released -- safe to do potentially slow TCP send
1594
+ return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1480
1595
  }
1481
1596
 
1482
- bool RatsClient::send_string_to_peer_id(const std::string& peer_hash_id, const std::string& data) {
1597
+ bool RatsClient::send_string_to_peer_id(const std::string& peer_id, const std::string& data) {
1483
1598
  // Convert string to binary and use primary binary method with STRING type
1484
1599
  std::vector<uint8_t> binary_data(data.begin(), data.end());
1485
- return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::STRING);
1600
+ return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
1486
1601
  }
1487
1602
 
1488
- bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlohmann::json& data) {
1603
+ bool RatsClient::send_json_to_peer_id(const std::string& peer_id, const nlohmann::json& data) {
1489
1604
  try {
1490
- // Serialize JSON and convert to binary, then use primary binary method with JSON type
1491
- std::string json_string = data.dump();
1492
- std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1493
- return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::JSON);
1605
+ return send_binary_to_peer_id(peer_id, json_to_binary(data), MessageDataType::JSON);
1494
1606
  } catch (const nlohmann::json::exception& e) {
1495
1607
  LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1496
1608
  return false;
@@ -1499,10 +1611,7 @@ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlo
1499
1611
 
1500
1612
  int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
1501
1613
  try {
1502
- // Serialize JSON and convert to binary, then use primary binary method with JSON type
1503
- std::string json_string = data.dump();
1504
- std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
1505
- return broadcast_binary_to_peers(binary_data, MessageDataType::JSON);
1614
+ return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
1506
1615
  } catch (const nlohmann::json::exception& e) {
1507
1616
  LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
1508
1617
  return 0;
@@ -1512,23 +1621,27 @@ int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
1512
1621
  int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, MessageDataType message_type) {
1513
1622
  if (!running_.load()) {
1514
1623
  return 0;
1515
- }
1516
-
1517
- int sent_count = 0;
1518
- std::lock_guard<std::mutex> lock(peers_mutex_);
1624
+ }
1519
1625
 
1520
- for (const auto& pair : peers_) {
1521
- const RatsPeer& peer = pair.second;
1522
- // Only send to peers that have completed handshake
1523
- if (peer.is_handshake_completed()) {
1524
- // Use unlocked version since we already hold peers_mutex_
1525
- rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
1526
- if (send_binary_to_peer_unlocked(peer.socket, data, message_type, send_cipher, peer.peer_id)) {
1527
- sent_count++;
1626
+ // Collect targets under lock, then enqueue outside
1627
+ std::vector<PeerSendTarget> targets;
1628
+ {
1629
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1630
+ targets.reserve(peers_.size());
1631
+ for (const auto& [id, peer] : peers_) {
1632
+ if (peer.is_handshake_completed()) {
1633
+ targets.push_back({peer.socket, peer.peer_id,
1634
+ peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
1528
1635
  }
1529
1636
  }
1530
1637
  }
1531
1638
 
1639
+ int sent_count = 0;
1640
+ for (const auto& t : targets) {
1641
+ if (send_binary_to_peer_unlocked(t.socket, data, message_type, t.send_cipher, t.peer_id)) {
1642
+ sent_count++;
1643
+ }
1644
+ }
1532
1645
  return sent_count;
1533
1646
  }
1534
1647
 
@@ -1538,35 +1651,6 @@ int RatsClient::broadcast_string_to_peers(const std::string& data) {
1538
1651
  return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
1539
1652
  }
1540
1653
 
1541
- bool RatsClient::parse_json_message(const std::string& message, nlohmann::json& out_json) {
1542
- try {
1543
- out_json = nlohmann::json::parse(message);
1544
- return true;
1545
- } catch (const nlohmann::json::exception& e) {
1546
- LOG_CLIENT_ERROR("Failed to parse JSON message: " << e.what());
1547
- return false;
1548
- }
1549
- }
1550
-
1551
- // Helpers
1552
-
1553
- // Per-socket synchronization helpers
1554
- std::shared_ptr<std::mutex> RatsClient::get_socket_send_mutex(socket_t socket) {
1555
- std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
1556
- auto it = socket_send_mutexes_.find(socket);
1557
- if (it == socket_send_mutexes_.end()) {
1558
- // Create new mutex for this socket
1559
- socket_send_mutexes_[socket] = std::make_shared<std::mutex>();
1560
- return socket_send_mutexes_[socket];
1561
- }
1562
- return it->second;
1563
- }
1564
-
1565
- void RatsClient::cleanup_socket_send_mutex(socket_t socket) {
1566
- std::lock_guard<std::mutex> lock(socket_send_mutexes_mutex_);
1567
- socket_send_mutexes_.erase(socket);
1568
- }
1569
-
1570
1654
  // =========================================================================
1571
1655
  // Peer Information and Management
1572
1656
  // =========================================================================
@@ -1576,32 +1660,18 @@ std::string RatsClient::get_our_peer_id() const {
1576
1660
  }
1577
1661
 
1578
1662
  int RatsClient::get_peer_count_unlocked() const {
1579
- // Assumes peers_mutex_ is already locked
1580
- int count = 0;
1581
- for (const auto& pair : peers_) {
1582
- if (pair.second.is_handshake_completed()) {
1583
- count++;
1584
- }
1585
- }
1586
- return count;
1663
+ // Returns the cached validated peer count (O(1) instead of O(N) scan)
1664
+ return validated_peer_count_.load(std::memory_order_relaxed);
1587
1665
  }
1588
1666
 
1589
1667
  int RatsClient::get_peer_count() const {
1590
- std::lock_guard<std::mutex> lock(peers_mutex_);
1591
- return get_peer_count_unlocked();
1668
+ return validated_peer_count_.load(std::memory_order_relaxed);
1592
1669
  }
1593
1670
 
1594
1671
  std::string RatsClient::get_peer_id(socket_t socket) const {
1595
- // Atomic operation - lock once and return copy to avoid race condition
1596
1672
  std::lock_guard<std::mutex> lock(peers_mutex_);
1597
- auto it = socket_to_peer_id_.find(socket);
1598
- if (it != socket_to_peer_id_.end()) {
1599
- auto peer_it = peers_.find(it->second);
1600
- if (peer_it != peers_.end()) {
1601
- return peer_it->second.peer_id;
1602
- }
1603
- }
1604
- return "";
1673
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1674
+ return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
1605
1675
  }
1606
1676
 
1607
1677
  socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
@@ -1639,7 +1709,6 @@ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
1639
1709
  return result;
1640
1710
  }
1641
1711
 
1642
-
1643
1712
  std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
1644
1713
  std::lock_guard<std::mutex> lock(peers_mutex_);
1645
1714
 
@@ -1670,23 +1739,24 @@ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::str
1670
1739
  return selected_peers;
1671
1740
  }
1672
1741
 
1673
- const RatsPeer* RatsClient::get_peer_by_id(const std::string& peer_id) const {
1742
+ std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
1674
1743
  std::lock_guard<std::mutex> lock(peers_mutex_);
1675
1744
  auto it = peers_.find(peer_id);
1676
- return (it != peers_.end()) ? &it->second : nullptr;
1745
+ if (it != peers_.end()) {
1746
+ return it->second;
1747
+ }
1748
+ return std::nullopt;
1677
1749
  }
1678
1750
 
1679
- const RatsPeer* RatsClient::get_peer_by_socket(socket_t socket) const {
1751
+ std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
1680
1752
  std::lock_guard<std::mutex> lock(peers_mutex_);
1681
- auto it = socket_to_peer_id_.find(socket);
1682
- if (it != socket_to_peer_id_.end()) {
1683
- auto peer_it = peers_.find(it->second);
1684
- return (peer_it != peers_.end()) ? &peer_it->second : nullptr;
1753
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1754
+ if (peer_it != peers_.end()) {
1755
+ return peer_it->second;
1685
1756
  }
1686
- return nullptr;
1757
+ return std::nullopt;
1687
1758
  }
1688
1759
 
1689
-
1690
1760
  // Peer limit management methods
1691
1761
  int RatsClient::get_max_peers() const {
1692
1762
  return max_peers_;
@@ -1699,15 +1769,10 @@ void RatsClient::set_max_peers(int max_peers) {
1699
1769
 
1700
1770
  bool RatsClient::is_peer_limit_reached() const {
1701
1771
  std::lock_guard<std::mutex> lock(peers_mutex_);
1702
- // Connected peers only enforcement (exclude handshake peers)
1703
- int connected_peers = get_peer_count_unlocked();
1704
- if (connected_peers >= max_peers_) {
1705
- return true;
1706
- }
1707
- return false;
1772
+ return get_peer_count_unlocked() >= max_peers_;
1708
1773
  }
1709
1774
 
1710
- std::string RatsClient::generate_peer_hash_id(socket_t socket, const std::string& connection_info) {
1775
+ std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
1711
1776
  // Generate unique hash ID using timestamp, socket, connection info, and random component
1712
1777
  auto now = std::chrono::high_resolution_clock::now();
1713
1778
  auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
@@ -1758,7 +1823,7 @@ std::string RatsClient::normalize_peer_address(const std::string& ip, int port)
1758
1823
 
1759
1824
  // =========================================================================
1760
1825
  // Callback Registration
1761
- // ========================================================================
1826
+ // =========================================================================
1762
1827
 
1763
1828
  void RatsClient::set_connection_callback(ConnectionCallback callback) {
1764
1829
  connection_callback_ = callback;
@@ -1780,298 +1845,6 @@ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
1780
1845
  disconnect_callback_ = callback;
1781
1846
  }
1782
1847
 
1783
- // =========================================================================
1784
- // Peer Discovery Methods
1785
- // =========================================================================
1786
-
1787
- bool RatsClient::start_dht_discovery(int dht_port) {
1788
- if (dht_client_ && dht_client_->is_running()) {
1789
- LOG_CLIENT_WARN("DHT discovery is already running");
1790
- return true;
1791
- }
1792
-
1793
- LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
1794
- (bind_address_.empty() ? "" : " bound to " + bind_address_));
1795
-
1796
- dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
1797
- if (!dht_client_->start()) {
1798
- LOG_CLIENT_ERROR("Failed to start DHT client");
1799
- dht_client_.reset();
1800
- return false;
1801
- }
1802
-
1803
- // Bootstrap with default nodes
1804
- auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
1805
- if (!dht_client_->bootstrap(bootstrap_nodes)) {
1806
- LOG_CLIENT_WARN("Failed to bootstrap DHT");
1807
- }
1808
-
1809
- // Start automatic peer discovery
1810
- start_automatic_peer_discovery();
1811
-
1812
- LOG_CLIENT_INFO("DHT discovery started successfully");
1813
- return true;
1814
- }
1815
-
1816
- void RatsClient::stop_dht_discovery() {
1817
- if (!dht_client_) {
1818
- return;
1819
- }
1820
-
1821
- LOG_CLIENT_INFO("Stopping DHT discovery");
1822
-
1823
- // Stop automatic peer discovery
1824
- stop_automatic_peer_discovery();
1825
-
1826
- dht_client_->stop();
1827
- dht_client_.reset();
1828
- LOG_CLIENT_INFO("DHT discovery stopped");
1829
- }
1830
-
1831
- bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
1832
- if (!dht_client_ || !dht_client_->is_running()) {
1833
- LOG_CLIENT_ERROR("DHT client not running");
1834
- return false;
1835
- }
1836
-
1837
- if (content_hash.length() != 40) { // 160-bit hash as hex string
1838
- LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
1839
- return false;
1840
- }
1841
-
1842
- LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
1843
-
1844
- InfoHash info_hash = hex_to_node_id(content_hash);
1845
-
1846
- return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
1847
- // Convert Peer to string addresses for callback
1848
- std::vector<std::string> peer_addresses;
1849
- for (const auto& peer : peers) {
1850
- peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
1851
- }
1852
-
1853
- if (callback) {
1854
- callback(peer_addresses);
1855
- }
1856
- });
1857
- }
1858
-
1859
- bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
1860
- std::function<void(const std::vector<std::string>&)> callback) {
1861
- if (!dht_client_ || !dht_client_->is_running()) {
1862
- LOG_CLIENT_ERROR("DHT client not running");
1863
- return false;
1864
- }
1865
-
1866
- if (content_hash.length() != 40) { // 160-bit hash as hex string
1867
- LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected 40)");
1868
- return false;
1869
- }
1870
-
1871
- if (port == 0) {
1872
- port = listen_port_;
1873
- }
1874
-
1875
- LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
1876
- << (callback ? " with peer callback" : ""));
1877
-
1878
- InfoHash info_hash = hex_to_node_id(content_hash);
1879
-
1880
- // Create wrapper callback that converts Peer to string addresses (if callback provided)
1881
- PeerDiscoveryCallback peer_callback = nullptr;
1882
- if (callback) {
1883
- peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
1884
- std::vector<std::string> peer_addresses;
1885
- peer_addresses.reserve(peers.size());
1886
- for (const auto& peer : peers) {
1887
- peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
1888
- }
1889
- callback(peer_addresses);
1890
- };
1891
- }
1892
-
1893
- return dht_client_->announce_peer(info_hash, port, peer_callback);
1894
- }
1895
-
1896
- bool RatsClient::is_dht_running() const {
1897
- return dht_client_ && dht_client_->is_running();
1898
- }
1899
-
1900
- size_t RatsClient::get_dht_routing_table_size() const {
1901
- if (!dht_client_) {
1902
- return 0;
1903
- }
1904
- return dht_client_->get_routing_table_size();
1905
- }
1906
-
1907
- void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
1908
- LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
1909
-
1910
- // Auto-connect to discovered peers (optional behavior)
1911
- for (const auto& peer : peers) {
1912
- // Check if this peer should be ignored (local interface)
1913
- if (should_ignore_peer(peer.ip, peer.port)) {
1914
- LOG_CLIENT_DEBUG("Ignoring discovered peer " << peer.ip << ":" << peer.port << " - local interface address");
1915
- continue;
1916
- }
1917
-
1918
- // Check if we're already connected to this peer
1919
- std::string normalized_peer_address = normalize_peer_address(peer.ip, peer.port);
1920
- bool already_connected = is_already_connected_to_address(normalized_peer_address);
1921
-
1922
- if (!already_connected) {
1923
- // Check if peer limit is reached
1924
- if (is_peer_limit_reached()) {
1925
- LOG_CLIENT_DEBUG("Peer limit reached, not connecting to DHT discovered peer " << peer.ip << ":" << peer.port);
1926
- continue;
1927
- }
1928
-
1929
- LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
1930
-
1931
- // Try to connect to the peer (non-blocking)
1932
- std::thread([this, peer]() {
1933
- if (connect_to_peer(peer.ip, peer.port)) {
1934
- LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
1935
- } else {
1936
- LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
1937
- }
1938
- }).detach();
1939
- } else {
1940
- LOG_CLIENT_DEBUG("Already connected to discovered peer: " << normalized_peer_address);
1941
- }
1942
- }
1943
- }
1944
-
1945
- void RatsClient::start_automatic_peer_discovery() {
1946
- if (auto_discovery_running_.load()) {
1947
- LOG_CLIENT_WARN("Automatic peer discovery is already running");
1948
- return;
1949
- }
1950
-
1951
- LOG_CLIENT_INFO("Starting automatic rats peer discovery");
1952
- auto_discovery_running_.store(true);
1953
- auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
1954
- }
1955
-
1956
- void RatsClient::stop_automatic_peer_discovery() {
1957
- if (!auto_discovery_running_.load()) {
1958
- return;
1959
- }
1960
-
1961
- LOG_CLIENT_INFO("Stopping automatic peer discovery");
1962
- auto_discovery_running_.store(false);
1963
-
1964
- if (auto_discovery_thread_.joinable()) {
1965
- auto_discovery_thread_.join();
1966
- }
1967
-
1968
- LOG_CLIENT_INFO("Automatic peer discovery stopped");
1969
- }
1970
-
1971
- bool RatsClient::is_automatic_discovery_running() const {
1972
- return auto_discovery_running_.load();
1973
- }
1974
-
1975
- void RatsClient::automatic_discovery_loop() {
1976
- LOG_CLIENT_INFO("Automatic peer discovery loop started");
1977
-
1978
- // Initial delay to let DHT bootstrap
1979
- {
1980
- std::unique_lock<std::mutex> lock(shutdown_mutex_);
1981
- if (shutdown_cv_.wait_for(lock, std::chrono::seconds(5), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
1982
- LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
1983
- return;
1984
- }
1985
- }
1986
-
1987
- // Announce immediately - this also discovers peers during traversal
1988
- announce_rats_peer();
1989
-
1990
- auto last_announce = std::chrono::steady_clock::now();
1991
-
1992
- while (auto_discovery_running_.load()) {
1993
- auto now = std::chrono::steady_clock::now();
1994
-
1995
- // Announce combines both announcing our presence and discovering peers
1996
- // Adjust frequency based on whether we have peers
1997
- auto interval = (get_peer_count() == 0)
1998
- ? std::chrono::seconds(15) // Aggressive when no peers
1999
- : std::chrono::minutes(10); // Less aggressive when connected
2000
-
2001
- if (now - last_announce >= interval) {
2002
- announce_rats_peer();
2003
- last_announce = now;
2004
- }
2005
-
2006
- // Use conditional variable for responsive shutdown
2007
- {
2008
- std::unique_lock<std::mutex> lock(shutdown_mutex_);
2009
- if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
2010
- break;
2011
- }
2012
- }
2013
- }
2014
-
2015
- LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
2016
- }
2017
-
2018
- void RatsClient::announce_rats_peer() {
2019
- if (!dht_client_ || !dht_client_->is_running()) {
2020
- LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
2021
- return;
2022
- }
2023
-
2024
- std::string discovery_hash = get_discovery_hash();
2025
- LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
2026
-
2027
- InfoHash info_hash = hex_to_node_id(discovery_hash);
2028
-
2029
- if (dht_client_->is_announce_active(info_hash)) {
2030
- LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
2031
- return;
2032
- }
2033
-
2034
- // Use announce with callback - combines announce and find_peers in one traversal
2035
- // Peers discovered during traversal will be returned through the callback
2036
- if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
2037
- LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
2038
-
2039
- // Convert peer addresses to Peer objects for handle_dht_peer_discovery()
2040
- std::vector<Peer> peers;
2041
- peers.reserve(peer_addresses.size());
2042
- for (const auto& peer_address : peer_addresses) {
2043
- std::string ip;
2044
- int port;
2045
- if (parse_address_string(peer_address, ip, port)) {
2046
- peers.push_back(Peer(ip, port));
2047
- }
2048
- }
2049
-
2050
- // Auto-connect to discovered peers
2051
- if (!peers.empty()) {
2052
- handle_dht_peer_discovery(peers, info_hash);
2053
- }
2054
- })) {
2055
- LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
2056
- } else {
2057
- LOG_CLIENT_WARN("Failed to announce peer for discovery");
2058
- }
2059
- }
2060
-
2061
-
2062
- std::string RatsClient::get_discovery_hash() const {
2063
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
2064
- // Generate discovery hash based on current protocol configuration
2065
- std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
2066
- return SHA1::hash(discovery_string);
2067
- }
2068
-
2069
- std::string RatsClient::get_rats_peer_discovery_hash() {
2070
- // Well-known hash for rats peer discovery
2071
- // Compute SHA1 hash of "rats_peer_discovery_v1.0"
2072
- return SHA1::hash("rats_peer_discovery_v1.0");
2073
- }
2074
-
2075
1848
  // =========================================================================
2076
1849
  // Protocol Configuration
2077
1850
  // =========================================================================
@@ -2102,11 +1875,10 @@ std::string RatsClient::get_protocol_version() const {
2102
1875
  // Message Exchange API
2103
1876
  // =========================================================================
2104
1877
 
2105
-
2106
1878
  void RatsClient::on(const std::string& message_type, MessageCallback callback) {
2107
1879
  std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2108
1880
  message_handlers_[message_type].emplace_back(callback, false); // false = not once
2109
- LOG_CLIENT_INFO("Registered persistent handler for message type: " << message_type << " (total handlers: " << message_handlers_[message_type].size() << ")");
1881
+ LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
2110
1882
  }
2111
1883
 
2112
1884
  void RatsClient::once(const std::string& message_type, MessageCallback callback) {
@@ -2134,15 +1906,15 @@ void RatsClient::send(const std::string& message_type, const nlohmann::json& dat
2134
1906
  return;
2135
1907
  }
2136
1908
 
2137
- LOG_CLIENT_INFO("Sending broadcast message type '" << message_type << "' with data: " << data.dump());
1909
+ LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
2138
1910
 
2139
1911
  // Create rats message
2140
1912
  nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
2141
1913
 
2142
1914
  // Broadcast to all validated peers
2143
- int sent_count = broadcast_rats_message_to_validated_peers(message);
1915
+ int sent_count = broadcast_rats_message(message);
2144
1916
 
2145
- LOG_CLIENT_INFO("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
1917
+ LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
2146
1918
 
2147
1919
  if (callback) {
2148
1920
  if (sent_count > 0) {
@@ -2163,7 +1935,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2163
1935
  return;
2164
1936
  }
2165
1937
 
2166
- LOG_CLIENT_INFO("Sending targeted message type '" << message_type << "' to peer " << peer_id << " with data: " << data.dump());
1938
+ LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
2167
1939
 
2168
1940
  // Create rats message
2169
1941
  nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
@@ -2203,7 +1975,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2203
1975
 
2204
1976
  bool success = send_json_to_peer(target_socket, message);
2205
1977
 
2206
- LOG_CLIENT_INFO("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
1978
+ LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
2207
1979
 
2208
1980
  if (callback) {
2209
1981
  if (success) {
@@ -2217,54 +1989,41 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2217
1989
  // Message exchange system helpers
2218
1990
  void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
2219
1991
  std::vector<MessageHandler> handlers_to_call;
2220
- std::vector<MessageHandler> remaining_handlers;
2221
1992
 
2222
- LOG_CLIENT_INFO("Calling message handlers for type '" << message_type << "' from peer " << peer_id << " with data: " << data.dump());
2223
-
2224
- // Get handlers to call and identify once handlers
1993
+ // Get handlers to call and remove once handlers atomically
2225
1994
  {
2226
1995
  std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2227
1996
  auto it = message_handlers_.find(message_type);
2228
- if (it != message_handlers_.end()) {
2229
- handlers_to_call = it->second; // Copy handlers
2230
-
2231
- // Keep only non-once handlers for the remaining list
2232
- for (const auto& handler : it->second) {
2233
- if (!handler.is_once) {
2234
- remaining_handlers.push_back(handler);
2235
- }
2236
- }
2237
-
2238
- // Update the handlers list (removes once handlers)
2239
- it->second = remaining_handlers;
2240
- } else {
2241
- LOG_CLIENT_WARN("No handlers registered for message type '" << message_type << "'");
1997
+ if (it == message_handlers_.end()) {
1998
+ LOG_CLIENT_DEBUG("No handlers registered for message type '" << message_type << "'");
1999
+ return;
2242
2000
  }
2001
+
2002
+ handlers_to_call = it->second;
2003
+
2004
+ // Remove once handlers using erase-remove idiom
2005
+ it->second.erase(
2006
+ std::remove_if(it->second.begin(), it->second.end(),
2007
+ [](const MessageHandler& h) { return h.is_once; }),
2008
+ it->second.end());
2243
2009
  }
2244
2010
 
2245
- LOG_CLIENT_INFO("Found " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2011
+ LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2246
2012
 
2247
2013
  // Call handlers outside of mutex to avoid deadlock
2248
2014
  for (const auto& handler : handlers_to_call) {
2249
2015
  try {
2250
- LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
2251
2016
  handler.callback(peer_id, data);
2252
- LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
2253
2017
  } catch (const std::exception& e) {
2254
2018
  LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
2255
2019
  } catch (...) {
2256
2020
  LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
2257
2021
  }
2258
2022
  }
2259
-
2260
- if (!handlers_to_call.empty()) {
2261
- LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2262
- }
2263
2023
  }
2264
2024
 
2265
-
2266
2025
  // =========================================================================
2267
- // Rats messages protocol / Message handling system
2026
+ // Rats Protocol Message Handling
2268
2027
  // =========================================================================
2269
2028
 
2270
2029
  nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
@@ -2279,26 +2038,26 @@ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nl
2279
2038
  return message;
2280
2039
  }
2281
2040
 
2282
- void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& message) {
2041
+ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message) {
2283
2042
  try {
2284
2043
  std::string message_type = message.value("type", "");
2285
2044
  nlohmann::json payload = message.value("payload", nlohmann::json::object());
2286
2045
  std::string sender_peer_id = message.value("sender_peer_id", "");
2287
2046
 
2288
- LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_hash_id);
2047
+ LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
2289
2048
 
2290
2049
  // Call registered message handlers for all message types (including custom ones)
2291
- call_message_handlers(message_type, sender_peer_id.empty() ? peer_hash_id : sender_peer_id, payload);
2050
+ call_message_handlers(message_type, sender_peer_id.empty() ? peer_id : sender_peer_id, payload);
2292
2051
 
2293
2052
  // Handle built-in message types for internal functionality
2294
2053
  if (message_type == "peer") {
2295
- handle_peer_exchange_message(socket, peer_hash_id, payload);
2054
+ handle_peer_exchange_message(socket, peer_id, payload);
2296
2055
  }
2297
2056
  else if (message_type == "peers_request") {
2298
- handle_peers_request_message(socket, peer_hash_id, payload);
2057
+ handle_peers_request_message(socket, peer_id, payload);
2299
2058
  }
2300
2059
  else if (message_type == "peers_response") {
2301
- handle_peers_response_message(socket, peer_hash_id, payload);
2060
+ handle_peers_response_message(socket, peer_id, payload);
2302
2061
  }
2303
2062
  // Custom message types are now handled by registered handlers above
2304
2063
  // No need for else clause - all message types are valid if they have registered handlers
@@ -2308,54 +2067,39 @@ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_ha
2308
2067
  }
2309
2068
  }
2310
2069
 
2311
- void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2070
+ void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2312
2071
  try {
2313
- std::string peer_ip = payload.value("ip", "");
2314
- int peer_port = payload.value("port", 0);
2315
- std::string peer_id = payload.value("peer_id", "");
2316
-
2317
- if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
2318
- LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_hash_id);
2319
- return;
2320
- }
2321
-
2322
- LOG_CLIENT_INFO("Received peer exchange: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
2072
+ std::string exchanged_ip = payload.value("ip", "");
2073
+ int exchanged_port = payload.value("port", 0);
2074
+ std::string exchanged_peer_id = payload.value("peer_id", "");
2323
2075
 
2324
- // Check if we should ignore this peer (local interface)
2325
- if (should_ignore_peer(peer_ip, peer_port)) {
2326
- LOG_CLIENT_DEBUG("Ignoring exchanged peer " << peer_ip << ":" << peer_port << " - local interface address");
2076
+ if (exchanged_ip.empty() || exchanged_port <= 0 || exchanged_peer_id.empty()) {
2077
+ LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_id);
2327
2078
  return;
2328
2079
  }
2329
2080
 
2330
- // Check if we're already connected to this peer
2331
- std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
2332
- if (is_already_connected_to_address(normalized_peer_address)) {
2333
- LOG_CLIENT_DEBUG("Already connected to exchanged peer " << normalized_peer_address);
2334
- return;
2335
- }
2081
+ LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
2336
2082
 
2337
- // Check if peer limit is reached
2338
- if (is_peer_limit_reached()) {
2339
- LOG_CLIENT_DEBUG("Peer limit reached, not connecting to exchanged peer " << peer_ip << ":" << peer_port);
2083
+ if (!can_connect_to_peer(exchanged_ip, exchanged_port)) {
2340
2084
  return;
2341
2085
  }
2342
2086
 
2343
2087
  // Try to connect to the exchanged peer (non-blocking)
2344
- add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
2345
- if (connect_to_peer(peer_ip, peer_port)) {
2346
- LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << peer_ip << ":" << peer_port);
2088
+ add_managed_thread(std::thread([this, exchanged_ip, exchanged_port, exchanged_peer_id]() {
2089
+ if (connect_to_peer(exchanged_ip, exchanged_port)) {
2090
+ LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2347
2091
  } else {
2348
- LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << peer_ip << ":" << peer_port);
2092
+ LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2349
2093
  }
2350
- }), "peer-exchange-connect-" + peer_id.substr(0, 8));
2094
+ }), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
2351
2095
 
2352
2096
  } catch (const nlohmann::json::exception& e) {
2353
2097
  LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
2354
2098
  }
2355
2099
  }
2356
2100
 
2357
- // General broadcasting functions
2358
- int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
2101
+ // General broadcasting function
2102
+ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id, bool validated_only) {
2359
2103
  // Serialize JSON once before iterating
2360
2104
  std::string json_string;
2361
2105
  try {
@@ -2366,53 +2110,27 @@ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std:
2366
2110
  }
2367
2111
  std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
2368
2112
 
2369
- int sent_count = 0;
2113
+ // Collect targets under lock, then enqueue outside
2114
+ std::vector<PeerSendTarget> targets;
2370
2115
  {
2371
2116
  std::lock_guard<std::mutex> lock(peers_mutex_);
2372
- for (const auto& pair : peers_) {
2373
- const RatsPeer& peer = pair.second;
2374
- // Don't send to excluded peer
2117
+ targets.reserve(peers_.size());
2118
+ for (const auto& [id, peer] : peers_) {
2375
2119
  if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
2376
2120
  continue;
2377
2121
  }
2378
-
2379
- // Use unlocked version since we already hold peers_mutex_
2380
- rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
2381
- if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
2382
- sent_count++;
2122
+ if (validated_only && !peer.is_handshake_completed()) {
2123
+ continue;
2383
2124
  }
2125
+ targets.push_back({peer.socket, peer.peer_id,
2126
+ peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
2384
2127
  }
2385
2128
  }
2386
- return sent_count;
2387
- }
2388
-
2389
- int RatsClient::broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id) {
2390
- // Serialize JSON once before iterating
2391
- std::string json_string;
2392
- try {
2393
- json_string = message.dump();
2394
- } catch (const nlohmann::json::exception& e) {
2395
- LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
2396
- return 0;
2397
- }
2398
- std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
2399
2129
 
2400
2130
  int sent_count = 0;
2401
- {
2402
- std::lock_guard<std::mutex> lock(peers_mutex_);
2403
- for (const auto& pair : peers_) {
2404
- const RatsPeer& peer = pair.second;
2405
- // Don't send to excluded peer and only send to peers with completed handshake
2406
- if ((!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) ||
2407
- !peer.is_handshake_completed()) {
2408
- continue;
2409
- }
2410
-
2411
- // Use unlocked version since we already hold peers_mutex_
2412
- rats::NoiseCipherState* send_cipher = peer.is_noise_encrypted() ? peer.send_cipher.get() : nullptr;
2413
- if (send_binary_to_peer_unlocked(peer.socket, binary_data, MessageDataType::JSON, send_cipher, peer.peer_id)) {
2414
- sent_count++;
2415
- }
2131
+ for (const auto& t : targets) {
2132
+ if (send_binary_to_peer_unlocked(t.socket, binary_data, MessageDataType::JSON, t.send_cipher, t.peer_id)) {
2133
+ sent_count++;
2416
2134
  }
2417
2135
  }
2418
2136
  return sent_count;
@@ -2441,7 +2159,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2441
2159
  nlohmann::json message = create_peer_exchange_message(new_peer);
2442
2160
 
2443
2161
  // Broadcast to all validated peers except the new peer
2444
- int sent_count = broadcast_rats_message_to_validated_peers(message, new_peer.peer_id);
2162
+ int sent_count = broadcast_rats_message(message, new_peer.peer_id);
2445
2163
 
2446
2164
  LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
2447
2165
  << " to " << sent_count << " peers");
@@ -2450,7 +2168,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2450
2168
  // Peers request/response system implementation
2451
2169
  nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
2452
2170
  nlohmann::json payload;
2453
- payload["max_peers"] = 5; // Request up to 5 peers
2171
+ payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
2454
2172
  payload["requester_info"] = {
2455
2173
  {"listen_port", listen_port_},
2456
2174
  {"peer_count", get_peer_count()}
@@ -2464,39 +2182,38 @@ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsP
2464
2182
  nlohmann::json peers_array = nlohmann::json::array();
2465
2183
 
2466
2184
  for (const auto& peer : peers) {
2467
- nlohmann::json peer_info;
2468
- peer_info["ip"] = peer.ip;
2469
- peer_info["port"] = peer.port;
2470
- peer_info["peer_id"] = peer.peer_id;
2471
- peer_info["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2472
- peers_array.push_back(peer_info);
2185
+ peers_array.push_back({
2186
+ {"ip", peer.ip},
2187
+ {"port", peer.port},
2188
+ {"peer_id", peer.peer_id},
2189
+ {"connection_type", peer.is_outgoing ? "outgoing" : "incoming"}
2190
+ });
2473
2191
  }
2474
2192
 
2475
- payload["peers"] = peers_array;
2193
+ payload["peers"] = std::move(peers_array);
2476
2194
  payload["total_peers"] = get_peer_count();
2477
2195
 
2478
2196
  return create_rats_message("peers_response", payload, sender_peer_id);
2479
2197
  }
2480
2198
 
2481
-
2482
- void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2199
+ void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2483
2200
  try {
2484
- int max_peers = payload.value("max_peers", 5);
2201
+ int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
2485
2202
 
2486
- LOG_CLIENT_INFO("Received peers request from " << peer_hash_id << " for up to " << max_peers << " peers");
2203
+ LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
2487
2204
 
2488
2205
  // Get random peers excluding the requester
2489
- std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_hash_id);
2206
+ std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
2490
2207
 
2491
- LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_hash_id);
2208
+ LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
2492
2209
 
2493
2210
  // Create and send peers response
2494
- nlohmann::json response_message = create_peers_response_message(random_peers, peer_hash_id);
2211
+ nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
2495
2212
 
2496
2213
  if (!send_json_to_peer(socket, response_message)) {
2497
- LOG_CLIENT_ERROR("Failed to send peers response to " << peer_hash_id);
2214
+ LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
2498
2215
  } else {
2499
- LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_hash_id);
2216
+ LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_id);
2500
2217
  }
2501
2218
 
2502
2219
  } catch (const nlohmann::json::exception& e) {
@@ -2504,55 +2221,39 @@ void RatsClient::handle_peers_request_message(socket_t socket, const std::string
2504
2221
  }
2505
2222
  }
2506
2223
 
2507
- void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2224
+ void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2508
2225
  try {
2509
2226
  nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
2510
2227
  int total_peers = payload.value("total_peers", 0);
2511
2228
 
2512
- LOG_CLIENT_INFO("Received peers response from " << peer_hash_id << " with " << peers_array.size()
2229
+ LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
2513
2230
  << " peers (total: " << total_peers << ")");
2514
2231
 
2515
2232
  // Process each peer in the response
2516
2233
  for (const auto& peer_info : peers_array) {
2517
- std::string peer_ip = peer_info.value("ip", "");
2518
- int peer_port = peer_info.value("port", 0);
2519
- std::string peer_id = peer_info.value("peer_id", "");
2520
-
2521
- if (peer_ip.empty() || peer_port <= 0 || peer_id.empty()) {
2522
- LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_hash_id);
2523
- continue;
2524
- }
2234
+ std::string resp_ip = peer_info.value("ip", "");
2235
+ int resp_port = peer_info.value("port", 0);
2236
+ std::string resp_peer_id = peer_info.value("peer_id", "");
2525
2237
 
2526
- LOG_CLIENT_DEBUG("Processing peer from response: " << peer_ip << ":" << peer_port << " (peer_id: " << peer_id << ")");
2527
-
2528
- // Check if we should ignore this peer (local interface)
2529
- if (should_ignore_peer(peer_ip, peer_port)) {
2530
- LOG_CLIENT_DEBUG("Ignoring peer from response " << peer_ip << ":" << peer_port << " - local interface address");
2238
+ if (resp_ip.empty() || resp_port <= 0 || resp_peer_id.empty()) {
2239
+ LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_id);
2531
2240
  continue;
2532
2241
  }
2533
2242
 
2534
- // Check if we're already connected to this peer
2535
- std::string normalized_peer_address = normalize_peer_address(peer_ip, peer_port);
2536
- if (is_already_connected_to_address(normalized_peer_address)) {
2537
- LOG_CLIENT_DEBUG("Already connected to peer from response " << normalized_peer_address);
2538
- continue;
2539
- }
2243
+ LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
2540
2244
 
2541
- // Check if peer limit is reached
2542
- if (is_peer_limit_reached()) {
2543
- LOG_CLIENT_DEBUG("Peer limit reached, not connecting to peer from response " << peer_ip << ":" << peer_port);
2245
+ if (!can_connect_to_peer(resp_ip, resp_port)) {
2544
2246
  continue;
2545
2247
  }
2546
2248
 
2547
- // Try to connect to the peer (non-blocking)
2548
- LOG_CLIENT_INFO("Attempting to connect to peer from response: " << peer_ip << ":" << peer_port);
2549
- add_managed_thread(std::thread([this, peer_ip, peer_port, peer_id]() {
2550
- if (connect_to_peer(peer_ip, peer_port)) {
2551
- LOG_CLIENT_INFO("Successfully connected to peer from response: " << peer_ip << ":" << peer_port);
2249
+ LOG_CLIENT_DEBUG("Attempting to connect to peer from response: " << resp_ip << ":" << resp_port);
2250
+ add_managed_thread(std::thread([this, resp_ip, resp_port, resp_peer_id]() {
2251
+ if (connect_to_peer(resp_ip, resp_port)) {
2252
+ LOG_CLIENT_INFO("Successfully connected to peer from response: " << resp_ip << ":" << resp_port);
2552
2253
  } else {
2553
- LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << peer_ip << ":" << peer_port);
2254
+ LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << resp_ip << ":" << resp_port);
2554
2255
  }
2555
- }), "peer-response-connect-" + peer_id.substr(0, 8));
2256
+ }), "peer-response-connect-" + resp_peer_id.substr(0, 8));
2556
2257
  }
2557
2258
 
2558
2259
  } catch (const nlohmann::json::exception& e) {
@@ -2571,49 +2272,7 @@ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer
2571
2272
  }
2572
2273
 
2573
2274
  // =========================================================================
2574
- // Statistics and Information
2575
- // =========================================================================
2576
-
2577
- nlohmann::json RatsClient::get_connection_statistics() const {
2578
- nlohmann::json stats;
2579
-
2580
- {
2581
- std::lock_guard<std::mutex> lock(peers_mutex_);
2582
- stats["total_peers"] = peers_.size();
2583
- stats["validated_peers"] = get_peer_count_unlocked();
2584
- stats["max_peers"] = max_peers_;
2585
- }
2586
-
2587
- stats["running"] = is_running();
2588
- stats["listen_port"] = listen_port_;
2589
- stats["our_peer_id"] = get_our_peer_id();
2590
- stats["encryption_enabled"] = is_encryption_enabled();
2591
-
2592
- // DHT statistics
2593
- if (dht_client_ && dht_client_->is_running()) {
2594
- stats["dht_running"] = true;
2595
- stats["dht_routing_table_size"] = get_dht_routing_table_size();
2596
- } else {
2597
- stats["dht_running"] = false;
2598
- }
2599
-
2600
- // mDNS statistics
2601
- stats["mdns_running"] = is_mdns_running();
2602
-
2603
- // Reconnection statistics
2604
- {
2605
- std::lock_guard<std::mutex> lock(reconnect_mutex_);
2606
- stats["reconnect_enabled"] = reconnect_config_.enabled;
2607
- stats["reconnect_queue_size"] = reconnect_queue_.size();
2608
- stats["reconnect_max_attempts"] = reconnect_config_.max_attempts;
2609
- }
2610
-
2611
- return stats;
2612
- }
2613
-
2614
-
2615
- // =========================================================================
2616
- // Helper functions
2275
+ // Helper Functions
2617
2276
  // =========================================================================
2618
2277
 
2619
2278
  std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
@@ -2683,52 +2342,4 @@ bool RatsClient::parse_address_string(const std::string& address_str, std::strin
2683
2342
  return !out_ip.empty() && out_port > 0 && out_port <= 65535;
2684
2343
  }
2685
2344
 
2686
- // Cached formatting helpers - computed once on first use
2687
- static const std::string& get_box_separator() {
2688
- static const std::string separator = supports_unicode() ?
2689
- "════════════════════════════════════════════════════════════════════" :
2690
- "=====================================================================";
2691
- return separator;
2692
- }
2693
-
2694
- static const std::string& get_box_vertical() {
2695
- static const std::string vertical = supports_unicode() ? "│" : "|";
2696
- return vertical;
2697
- }
2698
-
2699
- static const std::string& get_checkmark() {
2700
- static const std::string checkmark = supports_unicode() ? "✓" : "[*]";
2701
- return checkmark;
2702
- }
2703
-
2704
- void RatsClient::log_handshake_completion_unlocked(const RatsPeer& peer) {
2705
- // Calculate connection duration
2706
- auto now = std::chrono::steady_clock::now();
2707
- auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - peer.connected_at);
2708
-
2709
- // Get current peer count (assumes peers_mutex_ is already locked)
2710
- int current_peer_count = get_peer_count_unlocked();
2711
-
2712
- // Create visually appealing log output
2713
- std::string connection_type = peer.is_outgoing ? "OUTGOING" : "INCOMING";
2714
- const std::string& separator = get_box_separator();
2715
- const std::string& vertical = get_box_vertical();
2716
- const std::string& checkmark = get_checkmark();
2717
-
2718
- LOG_CLIENT_INFO("");
2719
- LOG_CLIENT_INFO(separator);
2720
- LOG_CLIENT_INFO(checkmark << " HANDSHAKE COMPLETED - NEW PEER CONNECTED");
2721
- LOG_CLIENT_INFO(separator);
2722
- LOG_CLIENT_INFO(vertical << " Peer ID : " << peer.peer_id);
2723
- LOG_CLIENT_INFO(vertical << " Address : " << peer.ip << ":" << peer.port);
2724
- LOG_CLIENT_INFO(vertical << " Connection : " << connection_type);
2725
- LOG_CLIENT_INFO(vertical << " Protocol Ver. : " << peer.version);
2726
- LOG_CLIENT_INFO(vertical << " Socket : " << peer.socket);
2727
- LOG_CLIENT_INFO(vertical << " Duration : " << duration.count() << "ms");
2728
- LOG_CLIENT_INFO(vertical << " Network Peers : " << current_peer_count << "/" << max_peers_);
2729
-
2730
- LOG_CLIENT_INFO(separator);
2731
- LOG_CLIENT_INFO("");
2732
- }
2733
-
2734
2345
  } // namespace librats