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