librats 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,564 @@ 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);
747
654
  }
748
-
749
- // Schedule reconnection if we have valid peer info
750
655
  if (should_schedule_reconnect && running_.load()) {
751
656
  schedule_reconnect(peer_copy_for_reconnect);
752
657
  }
753
-
754
658
  if (running_.load()) {
755
659
  add_managed_thread(std::thread([this]() {
756
- if (running_.load()) {
757
- save_configuration();
758
- }
660
+ if (running_.load()) save_configuration();
759
661
  }), "config-save-disconnect");
760
662
  }
761
663
  }
762
664
 
763
- LOG_CLIENT_INFO("Client disconnected: " << peer_hash_id);
665
+ LOG_CLIENT_INFO("Peer disconnected: " << peer_id);
666
+ }
667
+
668
+ // ---------------------------------------------------------------------------
669
+ // Poller registration helpers (thread-safe via io_mutex_)
670
+ // ---------------------------------------------------------------------------
671
+ void RatsClient::poller_add(socket_t fd, uint32_t events) {
672
+ std::lock_guard<std::mutex> lock(io_mutex_);
673
+ if (poller_) poller_->add(fd, events);
674
+ }
675
+
676
+ void RatsClient::poller_modify(socket_t fd, uint32_t events) {
677
+ std::lock_guard<std::mutex> lock(io_mutex_);
678
+ if (poller_) poller_->modify(fd, events);
679
+ }
680
+
681
+ void RatsClient::poller_remove(socket_t fd) {
682
+ std::lock_guard<std::mutex> lock(io_mutex_);
683
+ if (poller_) poller_->remove(fd);
684
+ }
685
+
686
+ // ---------------------------------------------------------------------------
687
+ // enqueue_message – build a length-prefixed frame and append to send buffer
688
+ // ---------------------------------------------------------------------------
689
+ bool RatsClient::enqueue_message(socket_t socket, const std::vector<uint8_t>& data) {
690
+ std::lock_guard<std::mutex> lock(peers_mutex_);
691
+ auto peer_it = find_peer_by_socket_unlocked(socket);
692
+ if (peer_it == peers_.end()) return false;
693
+ return enqueue_message_unlocked(peer_it->second, data);
694
+ }
695
+
696
+ bool RatsClient::enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data) {
697
+ // Build length-prefixed frame: [4-byte network-order length][payload]
698
+ uint32_t net_len = htonl(static_cast<uint32_t>(data.size()));
699
+
700
+ std::vector<uint8_t> frame;
701
+ frame.reserve(4 + data.size());
702
+ frame.insert(frame.end(),
703
+ reinterpret_cast<const uint8_t*>(&net_len),
704
+ reinterpret_cast<const uint8_t*>(&net_len) + 4);
705
+ frame.insert(frame.end(), data.begin(), data.end());
706
+
707
+ peer.io_.send_buffer.append(std::move(frame));
708
+
709
+ // Arm PollOut so io_loop flushes the buffer
710
+ {
711
+ std::lock_guard<std::mutex> io_lock(io_mutex_);
712
+ if (poller_) poller_->modify(peer.socket, PollIn | PollOut);
713
+ }
714
+
715
+ return true;
716
+ }
717
+
718
+ void RatsClient::management_loop() {
719
+ LOG_CLIENT_INFO("Management loop started");
720
+
721
+ auto last_thread_cleanup = std::chrono::steady_clock::now();
722
+ const auto thread_cleanup_interval = std::chrono::seconds(THREAD_CLEANUP_INTERVAL_SECONDS);
723
+
724
+ while (running_.load()) {
725
+ // Wait for interval or until shutdown (for responsive reconnection processing)
726
+ {
727
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
728
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(MANAGEMENT_LOOP_INTERVAL_SECONDS), [this] { return !running_.load(); })) {
729
+ break; // Exit if shutdown requested
730
+ }
731
+ }
732
+
733
+ // Check handshake timeouts (centralized, runs once for all peers)
734
+ try {
735
+ check_handshake_timeouts();
736
+ } catch (const std::exception& e) {
737
+ LOG_CLIENT_ERROR("Exception during handshake timeout check: " << e.what());
738
+ }
739
+
740
+ // Process reconnection queue
741
+ try {
742
+ process_reconnect_queue();
743
+ } catch (const std::exception& e) {
744
+ LOG_CLIENT_ERROR("Exception during reconnect queue processing: " << e.what());
745
+ }
746
+
747
+ // Periodically cleanup finished threads (every 30 seconds)
748
+ auto now = std::chrono::steady_clock::now();
749
+ if (now - last_thread_cleanup >= thread_cleanup_interval) {
750
+ try {
751
+ cleanup_finished_threads();
752
+ LOG_CLIENT_DEBUG("Periodic thread cleanup completed. Active threads: " << get_active_thread_count());
753
+ } catch (const std::exception& e) {
754
+ LOG_CLIENT_ERROR("Exception during thread cleanup: " << e.what());
755
+ }
756
+ last_thread_cleanup = now;
757
+ }
758
+ }
759
+
760
+ LOG_CLIENT_INFO("Management loop ended");
761
+ }
762
+
763
+ void RatsClient::handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy) {
764
+ // Remove from reconnection queue (successful connection)
765
+ remove_from_reconnect_queue(peer_copy.peer_id);
766
+
767
+ // Connection callback
768
+ if (connection_callback_) {
769
+ connection_callback_(socket, peer_copy.peer_id);
770
+ }
771
+
772
+ // GossipSub notification
773
+ if (gossipsub_) {
774
+ gossipsub_->handle_peer_connected(peer_copy.peer_id);
775
+ }
776
+
777
+ #ifdef RATS_STORAGE
778
+ // Storage manager notification
779
+ if (storage_manager_) {
780
+ storage_manager_->on_peer_connected(peer_copy.peer_id);
781
+ }
782
+ #endif
783
+
784
+ // Peer exchange broadcast
785
+ broadcast_peer_exchange_message(peer_copy);
786
+
787
+ // Request peers from newly connected peer (outgoing only)
788
+ if (peer_copy.is_outgoing) {
789
+ send_peers_request(socket, peer_copy.peer_id);
790
+ }
791
+
792
+ // Save configuration
793
+ if (running_.load()) {
794
+ add_managed_thread(std::thread([this]() {
795
+ if (running_.load()) {
796
+ save_configuration();
797
+ }
798
+ }), "config-save");
799
+ }
800
+ }
801
+
802
+ void RatsClient::process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& initial_peer_id) {
803
+ MessageHeader header;
804
+ std::vector<uint8_t> payload;
805
+
806
+ if (!parse_message_with_header(data, header, payload)) {
807
+ LOG_CLIENT_WARN("No header found in message from " << initial_peer_id);
808
+ return;
809
+ }
810
+
811
+ std::string peer_id = get_peer_id(socket);
812
+
813
+ switch (header.type) {
814
+ case MessageDataType::BINARY: {
815
+ LOG_CLIENT_DEBUG("Received BINARY message from " << peer_id << " (payload size: " << payload.size() << ")");
816
+ bool handled = false;
817
+ if (file_transfer_manager_) {
818
+ handled = file_transfer_manager_->handle_binary_data(peer_id, payload);
819
+ }
820
+ if (!handled && binary_data_callback_) {
821
+ binary_data_callback_(socket, peer_id, payload);
822
+ }
823
+ break;
824
+ }
825
+
826
+ case MessageDataType::STRING: {
827
+ LOG_CLIENT_DEBUG("Received STRING message from " << peer_id << " (payload size: " << payload.size() << ")");
828
+ if (string_data_callback_) {
829
+ std::string string_data(payload.begin(), payload.end());
830
+ string_data_callback_(socket, peer_id, string_data);
831
+ }
832
+ break;
833
+ }
834
+
835
+ case MessageDataType::JSON: {
836
+ LOG_CLIENT_DEBUG("Received JSON message from " << peer_id << " (payload size: " << payload.size() << ")");
837
+ try {
838
+ nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
839
+ if (json_msg.contains("rats_protocol") && json_msg["rats_protocol"] == true) {
840
+ handle_rats_message(socket, peer_id, json_msg);
841
+ } else if (json_data_callback_) {
842
+ json_data_callback_(socket, peer_id, json_msg);
843
+ }
844
+ } catch (const nlohmann::json::exception& e) {
845
+ LOG_CLIENT_ERROR("Received invalid JSON in JSON message from " << peer_id << ": " << e.what());
846
+ }
847
+ break;
848
+ }
849
+
850
+ default:
851
+ LOG_CLIENT_WARN("Received message with unknown data type " << static_cast<int>(header.type) << " from " << peer_id);
852
+ break;
853
+ }
764
854
  }
765
855
 
766
856
  // Handshake protocol implementation
@@ -855,9 +945,8 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
855
945
  auto now = std::chrono::high_resolution_clock::now();
856
946
  auto current_timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
857
947
  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");
948
+ if (time_diff > TIMESTAMP_SKEW_TOLERANCE_MS) {
949
+ LOG_CLIENT_WARN("Handshake timestamp skew " << time_diff << "ms exceeds " << TIMESTAMP_SKEW_TOLERANCE_MS << "ms; accepting to be tolerant of clock skew");
861
950
  }
862
951
  }
863
952
 
@@ -893,52 +982,32 @@ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
893
982
  }
894
983
  }
895
984
 
896
- // Add this private helper function before send_handshake
897
- bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our_peer_id) {
985
+ bool RatsClient::send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id) {
898
986
  std::string handshake_msg = create_handshake_message("handshake", our_peer_id);
899
- LOG_CLIENT_DEBUG("Sending handshake to socket " << socket << ": " << handshake_msg);
987
+ LOG_CLIENT_DEBUG("Sending handshake to " << peer.peer_id << ": " << handshake_msg);
900
988
 
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.
989
+ // Handshakes are always unencrypted enqueue into the peer's send buffer
904
990
  std::vector<uint8_t> binary_data(handshake_msg.begin(), handshake_msg.end());
905
991
  std::vector<uint8_t> message_with_header = create_message_with_header(binary_data, MessageDataType::STRING);
906
992
 
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);
993
+ if (!enqueue_message_unlocked(peer, message_with_header)) {
994
+ LOG_CLIENT_ERROR("Failed to enqueue handshake for " << peer.peer_id);
914
995
  return false;
915
996
  }
916
997
 
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
- }
998
+ peer.handshake_state = RatsPeer::HandshakeState::SENT;
999
+ peer.handshake_start_time = std::chrono::steady_clock::now();
926
1000
 
927
1001
  return true;
928
1002
  }
929
1003
 
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) {
1004
+ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data) {
936
1005
  // Extract JSON payload from message header
937
1006
  MessageHeader header;
938
1007
  std::vector<uint8_t> payload;
939
1008
 
940
1009
  if (!parse_message_with_header(data, header, payload)) {
941
- LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
1010
+ LOG_CLIENT_ERROR("Failed to parse handshake message header from " << initial_peer_id);
942
1011
  return false;
943
1012
  }
944
1013
 
@@ -951,12 +1020,12 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
951
1020
  // Parse handshake message directly from payload (no string conversion)
952
1021
  HandshakeMessage handshake_msg;
953
1022
  if (!parse_handshake_message(payload, handshake_msg)) {
954
- LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
1023
+ LOG_CLIENT_ERROR("Failed to parse handshake message from " << initial_peer_id);
955
1024
  return false;
956
1025
  }
957
1026
 
958
1027
  if (!validate_handshake_message(handshake_msg)) {
959
- LOG_CLIENT_ERROR("Invalid handshake message from " << peer_hash_id);
1028
+ LOG_CLIENT_ERROR("Invalid handshake message from " << initial_peer_id);
960
1029
  return false;
961
1030
  }
962
1031
 
@@ -965,19 +1034,13 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
965
1034
  return false;
966
1035
  }
967
1036
 
968
- LOG_CLIENT_INFO("Received valid handshake from " << peer_hash_id
1037
+ LOG_CLIENT_INFO("Received valid handshake from " << initial_peer_id
969
1038
  << " (peer_id: " << handshake_msg.peer_id << ")");
970
1039
 
971
1040
  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);
1041
+ auto peer_it = find_peer_by_socket_unlocked(socket);
979
1042
  if (peer_it == peers_.end()) {
980
- LOG_CLIENT_ERROR("Peer " << peer_hash_id << " not found in peers");
1043
+ LOG_CLIENT_ERROR("Peer " << initial_peer_id << " not found for socket " << socket);
981
1044
  return false;
982
1045
  }
983
1046
 
@@ -993,22 +1056,18 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
993
1056
 
994
1057
  // Update peer mappings with new peer_id if it changed
995
1058
  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.
1059
+ // Move the peer (preserves io_ context with send/recv buffers)
1060
+ RatsPeer peer_moved = std::move(peer_it->second);
1000
1061
  peers_.erase(peer_it);
1001
1062
 
1002
- // Update the peer_id within the copied object.
1003
- peer_copy.peer_id = handshake_msg.peer_id;
1063
+ peer_moved.peer_id = handshake_msg.peer_id;
1004
1064
 
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);
1065
+ // Use emplace to avoid extra copy/move
1066
+ auto [new_it, ok] = peers_.emplace(peer_moved.peer_id, std::move(peer_moved));
1067
+ socket_to_peer_id_[socket] = new_it->second.peer_id;
1068
+ address_to_peer_id_[new_it->second.normalized_address] = new_it->second.peer_id;
1069
+
1070
+ peer_it = new_it;
1012
1071
  }
1013
1072
 
1014
1073
  RatsPeer& peer = peer_it->second;
@@ -1045,14 +1104,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1045
1104
  // Simplified handshake logic - just one message type
1046
1105
  if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1047
1106
  // This is an incoming handshake - send our handshake back
1048
- if (send_handshake_unlocked(socket, get_our_peer_id())) {
1107
+ if (send_handshake_unlocked(peer, get_our_peer_id())) {
1049
1108
  // If encryption is enabled, we need to do Noise handshake first
1050
1109
  // Set NOISE_PENDING to prevent other threads from sending messages
1051
1110
  if (peer.encryption_enabled) {
1052
1111
  peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1053
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1112
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1054
1113
  } else {
1055
1114
  peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1115
+ validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1056
1116
  log_handshake_completion_unlocked(peer);
1057
1117
  }
1058
1118
 
@@ -1062,7 +1122,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1062
1122
  return true;
1063
1123
  } else {
1064
1124
  peer.handshake_state = RatsPeer::HandshakeState::FAILED;
1065
- LOG_CLIENT_ERROR("Failed to send handshake response to " << peer_hash_id);
1125
+ LOG_CLIENT_ERROR("Failed to send handshake response to " << initial_peer_id);
1066
1126
  return false;
1067
1127
  }
1068
1128
  } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
@@ -1071,9 +1131,10 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1071
1131
  // Set NOISE_PENDING to prevent other threads from sending messages
1072
1132
  if (peer.encryption_enabled) {
1073
1133
  peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1074
- LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1134
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << initial_peer_id);
1075
1135
  } else {
1076
1136
  peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1137
+ validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
1077
1138
  log_handshake_completion_unlocked(peer);
1078
1139
  }
1079
1140
 
@@ -1082,7 +1143,7 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1082
1143
 
1083
1144
  return true;
1084
1145
  } else {
1085
- LOG_CLIENT_WARN("Received handshake from " << peer_hash_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1146
+ LOG_CLIENT_WARN("Received handshake from " << initial_peer_id << " but handshake state is " << static_cast<int>(peer.handshake_state));
1086
1147
  return false;
1087
1148
  }
1088
1149
  }
@@ -1116,8 +1177,8 @@ void RatsClient::check_handshake_timeouts() {
1116
1177
  socket_t socket = peer_it->second.socket;
1117
1178
  LOG_CLIENT_INFO("Disconnecting peer " << peer_id << " due to handshake timeout");
1118
1179
 
1119
- // Clean up peer data
1120
1180
  remove_peer_by_id_unlocked(peer_id);
1181
+ poller_remove(socket);
1121
1182
  close_socket(socket);
1122
1183
  }
1123
1184
  }
@@ -1154,70 +1215,108 @@ bool RatsClient::connect_to_peer(const std::string& host, int port) {
1154
1215
  return false;
1155
1216
  }
1156
1217
 
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));
1218
+ // Create TCP connection with timeout (blocking connect is done on a managed thread
1219
+ // to avoid blocking the caller, then the connected socket is handed to the IO loop).
1220
+ add_managed_thread(std::thread([this, host, port, normalized_address]() {
1221
+ socket_t client_socket = create_tcp_client(host, port, TCP_CONNECT_TIMEOUT_MS);
1222
+ if (!is_valid_socket(client_socket)) {
1223
+ LOG_CLIENT_DEBUG("Failed to connect to " << host << ":" << port);
1224
+ return;
1225
+ }
1226
+
1227
+ LOG_CLIENT_INFO("TCP connected to " << host << ":" << port);
1228
+
1229
+ // Switch to non-blocking for the IO poller
1230
+ set_socket_nonblocking(client_socket);
1231
+
1232
+ std::string initial_peer_id = generate_temporary_peer_id(client_socket, normalized_address);
1233
+
1234
+ {
1235
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1236
+
1237
+ // Re-check peer limit and duplicate (could have changed while connecting)
1238
+ // NOTE: Use unlocked variant peers_mutex_ is already held!
1239
+ if (get_peer_count_unlocked() >= max_peers_) {
1240
+ LOG_CLIENT_DEBUG("connect_to_peer: peer limit reached after TCP connect, aborting fd=" << client_socket);
1241
+ close_socket(client_socket);
1242
+ return;
1243
+ }
1244
+ if (address_to_peer_id_.find(normalized_address) != address_to_peer_id_.end()) {
1245
+ LOG_CLIENT_DEBUG("connect_to_peer: duplicate address " << normalized_address << " after TCP connect, aborting fd=" << client_socket);
1246
+ close_socket(client_socket);
1247
+ return;
1248
+ }
1249
+
1250
+ RatsPeer new_peer(initial_peer_id, host, static_cast<uint16_t>(port),
1251
+ client_socket, normalized_address, true);
1252
+ new_peer.encryption_enabled = is_encryption_enabled();
1253
+ add_peer_unlocked(new_peer);
1254
+
1255
+ // Send initial handshake (enqueued into send buffer)
1256
+ auto peer_it = peers_.find(initial_peer_id);
1257
+ if (peer_it != peers_.end()) {
1258
+ if (!send_handshake_unlocked(peer_it->second, get_our_peer_id())) {
1259
+ LOG_CLIENT_ERROR("Failed to enqueue handshake for outgoing connection to " << host << ":" << port);
1260
+ remove_peer_by_id_unlocked(initial_peer_id);
1261
+ close_socket(client_socket);
1262
+ return;
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ // Register with poller – PollIn for reads, PollOut to flush the queued handshake
1268
+ poller_add(client_socket, PollIn | PollOut);
1269
+
1270
+ LOG_CLIENT_INFO("Outgoing connection to " << host << ":" << port << " registered with IO poller");
1271
+ }), "connect-" + host + ":" + std::to_string(port));
1181
1272
 
1182
1273
  return true;
1183
1274
  }
1184
1275
 
1276
+ void RatsClient::mark_manual_disconnect(const std::string& peer_id) {
1277
+ std::lock_guard<std::mutex> lock(reconnect_mutex_);
1278
+ manual_disconnect_peers_.insert(peer_id);
1279
+ reconnect_queue_.erase(peer_id);
1280
+ }
1281
+
1185
1282
  void RatsClient::disconnect_peer(socket_t socket) {
1186
- // Mark as manually disconnected to prevent auto-reconnection
1187
1283
  std::string peer_id = get_peer_id(socket);
1188
1284
  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);
1285
+ mark_manual_disconnect(peer_id);
1193
1286
  }
1194
-
1287
+ poller_remove(socket);
1195
1288
  remove_peer(socket);
1196
1289
  close_socket(socket);
1197
1290
  }
1198
1291
 
1199
1292
  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
-
1293
+ mark_manual_disconnect(peer_id);
1208
1294
  socket_t socket = get_peer_socket_by_id(peer_id);
1209
1295
  if (is_valid_socket(socket)) {
1296
+ poller_remove(socket);
1210
1297
  remove_peer(socket);
1211
1298
  close_socket(socket);
1212
1299
  }
1213
1300
  }
1214
1301
 
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);
1302
+ // Peer lookup helpers (assumes peers_mutex_ is already locked)
1303
+ std::unordered_map<std::string, RatsPeer>::iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) {
1304
+ auto sock_it = socket_to_peer_id_.find(socket);
1305
+ if (sock_it != socket_to_peer_id_.end()) {
1306
+ return peers_.find(sock_it->second);
1307
+ }
1308
+ return peers_.end();
1309
+ }
1310
+
1311
+ std::unordered_map<std::string, RatsPeer>::const_iterator RatsClient::find_peer_by_socket_unlocked(socket_t socket) const {
1312
+ auto sock_it = socket_to_peer_id_.find(socket);
1313
+ if (sock_it != socket_to_peer_id_.end()) {
1314
+ return peers_.find(sock_it->second);
1315
+ }
1316
+ return peers_.end();
1219
1317
  }
1220
1318
 
1319
+ // Helper methods for peer management
1221
1320
  void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1222
1321
  // Assumes peers_mutex_ is already locked
1223
1322
  peers_[peer.peer_id] = peer;
@@ -1227,17 +1326,12 @@ void RatsClient::add_peer_unlocked(const RatsPeer& peer) {
1227
1326
 
1228
1327
  void RatsClient::remove_peer(socket_t socket) {
1229
1328
  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);
1329
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1330
+ if (peer_it != peers_.end()) {
1331
+ remove_peer_by_id_unlocked(peer_it->second.peer_id);
1233
1332
  }
1234
1333
  }
1235
1334
 
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
1335
  void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1242
1336
  // Assumes peers_mutex_ is already locked
1243
1337
 
@@ -1246,6 +1340,11 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1246
1340
 
1247
1341
  auto it = peers_.find(peer_id_copy);
1248
1342
  if (it != peers_.end()) {
1343
+ // Decrement validated peer count if this peer had completed handshake
1344
+ if (it->second.is_handshake_completed()) {
1345
+ validated_peer_count_.fetch_sub(1, std::memory_order_relaxed);
1346
+ }
1347
+
1249
1348
  // Copy the values we need before erasing to avoid use-after-free
1250
1349
  socket_t peer_socket = it->second.socket;
1251
1350
  std::string peer_normalized_address = it->second.normalized_address;
@@ -1253,9 +1352,6 @@ void RatsClient::remove_peer_by_id_unlocked(const std::string& peer_id) {
1253
1352
  socket_to_peer_id_.erase(peer_socket);
1254
1353
  address_to_peer_id_.erase(peer_normalized_address);
1255
1354
  peers_.erase(it);
1256
-
1257
- // Clean up socket-specific mutex
1258
- cleanup_socket_send_mutex(peer_socket);
1259
1355
  }
1260
1356
  }
1261
1357
 
@@ -1267,9 +1363,8 @@ bool RatsClient::is_already_connected_to_address(const std::string& normalized_a
1267
1363
  void RatsClient::add_ignored_address(const std::string& ip_address) {
1268
1364
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1269
1365
 
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);
1366
+ auto [it, inserted] = local_interface_addresses_.insert(ip_address);
1367
+ if (inserted) {
1273
1368
  LOG_CLIENT_INFO("Added " << ip_address << " to ignore list");
1274
1369
  } else {
1275
1370
  LOG_CLIENT_DEBUG("IP address " << ip_address << " already in ignore list");
@@ -1277,7 +1372,7 @@ void RatsClient::add_ignored_address(const std::string& ip_address) {
1277
1372
  }
1278
1373
 
1279
1374
  //common localhost addresses
1280
- static constexpr std::array<std::string_view,4> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::"};
1375
+ static constexpr std::array<std::string_view,5> localhost_addrs{"127.0.0.1", "::1", "0.0.0.0", "::", "localhost"};
1281
1376
 
1282
1377
  // Local interface address blocking methods
1283
1378
  void RatsClient::initialize_local_addresses() {
@@ -1286,13 +1381,12 @@ void RatsClient::initialize_local_addresses() {
1286
1381
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1287
1382
 
1288
1383
  // Get all local interface addresses using network_utils
1289
- local_interface_addresses_ = network_utils::get_local_interface_addresses();
1384
+ auto addrs = network_utils::get_local_interface_addresses();
1385
+ local_interface_addresses_.insert(addrs.begin(), addrs.end());
1290
1386
 
1291
- // Add common localhost addresses if not already present
1387
+ // Add common localhost addresses
1292
1388
  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
- }
1389
+ local_interface_addresses_.emplace(std::string(addr));
1296
1390
  }
1297
1391
 
1298
1392
  LOG_CLIENT_INFO("Found " << local_interface_addresses_.size() << " local addresses to block:");
@@ -1301,36 +1395,47 @@ void RatsClient::initialize_local_addresses() {
1301
1395
  }
1302
1396
  }
1303
1397
 
1304
-
1305
1398
  bool RatsClient::is_blocked_address(const std::string& ip_address) const {
1306
1399
  std::lock_guard<std::mutex> lock(local_addresses_mutex_);
1400
+ return local_interface_addresses_.count(ip_address) > 0;
1401
+ }
1402
+
1403
+ bool RatsClient::can_connect_to_peer(const std::string& ip, int port) const {
1404
+ if (should_ignore_peer(ip, port)) {
1405
+ LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - blocked address");
1406
+ return false;
1407
+ }
1307
1408
 
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
- }
1409
+ std::string normalized_address = normalize_peer_address(ip, port);
1410
+ if (is_already_connected_to_address(normalized_address)) {
1411
+ LOG_CLIENT_DEBUG("Already connected to " << normalized_address);
1412
+ return false;
1313
1413
  }
1314
1414
 
1315
- return false;
1415
+ if (is_peer_limit_reached()) {
1416
+ LOG_CLIENT_DEBUG("Peer limit reached, cannot connect to " << ip << ":" << port);
1417
+ return false;
1418
+ }
1419
+
1420
+ return true;
1316
1421
  }
1317
1422
 
1318
1423
  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 == "::") {
1424
+ // Check if this is a well-known localhost address
1425
+ bool is_localhost = std::find(localhost_addrs.begin(), localhost_addrs.end(), ip) != localhost_addrs.end();
1426
+
1427
+ if (is_localhost) {
1428
+ // Block self-connections (same port on localhost)
1429
+ if (port == listen_port_) {
1322
1430
  LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - localhost with same port");
1323
1431
  return true;
1324
1432
  }
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") {
1433
+ // Allow localhost on different ports (for testing)
1329
1434
  LOG_CLIENT_DEBUG("Allowing localhost peer " << ip << ":" << port << " on different port");
1330
1435
  return false;
1331
1436
  }
1332
1437
 
1333
- // Check if the IP is a non-localhost local interface address
1438
+ // Block non-localhost local interface addresses
1334
1439
  if (is_blocked_address(ip)) {
1335
1440
  LOG_CLIENT_DEBUG("Ignoring peer " << ip << ":" << port << " - matches local interface address");
1336
1441
  return true;
@@ -1383,30 +1488,25 @@ bool RatsClient::parse_message_with_header(const std::vector<uint8_t>& message,
1383
1488
  // Extract payload
1384
1489
  payload.assign(message.begin() + MessageHeader::HEADER_SIZE, message.end());
1385
1490
 
1386
- LOG_CLIENT_DEBUG("Parsed message header: type=" << static_cast<int>(header.type) << ", payload_size=" << payload.size());
1387
1491
  return true;
1388
1492
  }
1389
1493
 
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
1494
+ // Async send enqueues header + (optionally encrypted) payload into the peer's
1495
+ // ChainedSendBuffer. Does NOT require peers_mutex_; caller passes cached peer data.
1496
+ // The shared_ptr keeps the cipher alive even if the peer is removed concurrently.
1392
1497
  bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
1393
1498
  MessageDataType message_type,
1394
- rats::NoiseCipherState* send_cipher,
1499
+ std::shared_ptr<rats::NoiseCipherState> send_cipher,
1395
1500
  const std::string& peer_id_for_logging) {
1396
1501
  if (!running_.load()) {
1397
1502
  return false;
1398
1503
  }
1399
1504
 
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
1505
  // Create message with specified header type
1406
1506
  std::vector<uint8_t> message_with_header = create_message_with_header(data, message_type);
1407
1507
 
1408
1508
  if (send_cipher) {
1409
- // Encrypt the message before sending
1509
+ // Encrypt the message before enqueuing
1410
1510
  std::vector<uint8_t> ciphertext(message_with_header.size() + rats::NOISE_TAG_SIZE);
1411
1511
  size_t ct_len = send_cipher->encrypt_with_ad(
1412
1512
  nullptr, 0,
@@ -1420,16 +1520,13 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
1420
1520
  }
1421
1521
 
1422
1522
  ciphertext.resize(ct_len);
1423
- LOG_CLIENT_DEBUG("Sending encrypted message to " << peer_id_for_logging << " (" << ct_len << " bytes)");
1523
+ LOG_CLIENT_DEBUG("Enqueuing encrypted message for " << peer_id_for_logging << " (" << ct_len << " bytes)");
1424
1524
 
1425
- // Send encrypted message using framed protocol
1426
- int sent = send_tcp_message(socket, ciphertext);
1427
- return sent > 0;
1525
+ return enqueue_message(socket, ciphertext);
1428
1526
  }
1429
1527
 
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;
1528
+ // Unencrypted path
1529
+ return enqueue_message(socket, message_with_header);
1433
1530
  }
1434
1531
 
1435
1532
  bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>& data, MessageDataType message_type) {
@@ -1437,25 +1534,22 @@ bool RatsClient::send_binary_to_peer(socket_t socket, const std::vector<uint8_t>
1437
1534
  return false;
1438
1535
  }
1439
1536
 
1440
- // Cache peer encryption data under lock, then release lock before sending
1537
+ // Cache peer data under lock, then release lock before sending
1441
1538
  std::string peer_id;
1442
- rats::NoiseCipherState* send_cipher = nullptr;
1539
+ std::shared_ptr<rats::NoiseCipherState> send_cipher;
1443
1540
 
1444
1541
  {
1445
1542
  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
- }
1543
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1544
+ if (peer_it != peers_.end()) {
1545
+ peer_id = peer_it->second.peer_id;
1546
+ if (peer_it->second.is_noise_encrypted()) {
1547
+ send_cipher = peer_it->second.send_cipher; // shared_ptr copy keeps cipher alive
1454
1548
  }
1455
1549
  }
1456
1550
  }
1457
1551
 
1458
- // Call unlocked version with cached data (peers_mutex_ is released)
1552
+ // peers_mutex_ released -- safe to do potentially slow TCP send
1459
1553
  return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1460
1554
  }
1461
1555
 
@@ -1465,43 +1559,50 @@ bool RatsClient::send_string_to_peer(socket_t socket, const std::string& data) {
1465
1559
  return send_binary_to_peer(socket, binary_data, MessageDataType::STRING);
1466
1560
  }
1467
1561
 
1562
+ std::vector<uint8_t> RatsClient::json_to_binary(const nlohmann::json& data) {
1563
+ std::string s = data.dump();
1564
+ return {s.begin(), s.end()};
1565
+ }
1566
+
1468
1567
  bool RatsClient::send_json_to_peer(socket_t socket, const nlohmann::json& data) {
1469
1568
  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);
1569
+ return send_binary_to_peer(socket, json_to_binary(data), MessageDataType::JSON);
1474
1570
  } catch (const nlohmann::json::exception& e) {
1475
1571
  LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1476
1572
  return false;
1477
1573
  }
1478
1574
  }
1479
1575
 
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;
1576
+ bool RatsClient::send_binary_to_peer_id(const std::string& peer_id, const std::vector<uint8_t>& data, MessageDataType message_type) {
1577
+ // Cache peer data under lock, then release before sending
1578
+ socket_t socket;
1579
+ std::shared_ptr<rats::NoiseCipherState> send_cipher;
1580
+
1581
+ {
1582
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1583
+ auto it = peers_.find(peer_id);
1584
+ if (it == peers_.end() || !it->second.is_handshake_completed()) {
1585
+ return false;
1586
+ }
1587
+ socket = it->second.socket;
1588
+ if (it->second.is_noise_encrypted()) {
1589
+ send_cipher = it->second.send_cipher; // shared_ptr copy keeps cipher alive
1590
+ }
1485
1591
  }
1486
1592
 
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);
1593
+ // peers_mutex_ released -- safe to do potentially slow TCP send
1594
+ return send_binary_to_peer_unlocked(socket, data, message_type, send_cipher, peer_id);
1491
1595
  }
1492
1596
 
1493
- bool RatsClient::send_string_to_peer_id(const std::string& peer_hash_id, const std::string& data) {
1597
+ bool RatsClient::send_string_to_peer_id(const std::string& peer_id, const std::string& data) {
1494
1598
  // Convert string to binary and use primary binary method with STRING type
1495
1599
  std::vector<uint8_t> binary_data(data.begin(), data.end());
1496
- return send_binary_to_peer_id(peer_hash_id, binary_data, MessageDataType::STRING);
1600
+ return send_binary_to_peer_id(peer_id, binary_data, MessageDataType::STRING);
1497
1601
  }
1498
1602
 
1499
- bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlohmann::json& data) {
1603
+ bool RatsClient::send_json_to_peer_id(const std::string& peer_id, const nlohmann::json& data) {
1500
1604
  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);
1605
+ return send_binary_to_peer_id(peer_id, json_to_binary(data), MessageDataType::JSON);
1505
1606
  } catch (const nlohmann::json::exception& e) {
1506
1607
  LOG_CLIENT_ERROR("Failed to serialize JSON message: " << e.what());
1507
1608
  return false;
@@ -1510,10 +1611,7 @@ bool RatsClient::send_json_to_peer_id(const std::string& peer_hash_id, const nlo
1510
1611
 
1511
1612
  int RatsClient::broadcast_json_to_peers(const nlohmann::json& data) {
1512
1613
  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);
1614
+ return broadcast_binary_to_peers(json_to_binary(data), MessageDataType::JSON);
1517
1615
  } catch (const nlohmann::json::exception& e) {
1518
1616
  LOG_CLIENT_ERROR("Failed to serialize JSON message for broadcast: " << e.what());
1519
1617
  return 0;
@@ -1525,21 +1623,25 @@ int RatsClient::broadcast_binary_to_peers(const std::vector<uint8_t>& data, Mess
1525
1623
  return 0;
1526
1624
  }
1527
1625
 
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++;
1626
+ // Collect targets under lock, then enqueue outside
1627
+ std::vector<PeerSendTarget> targets;
1628
+ {
1629
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1630
+ targets.reserve(peers_.size());
1631
+ for (const auto& [id, peer] : peers_) {
1632
+ if (peer.is_handshake_completed()) {
1633
+ targets.push_back({peer.socket, peer.peer_id,
1634
+ peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
1539
1635
  }
1540
1636
  }
1541
1637
  }
1542
1638
 
1639
+ int sent_count = 0;
1640
+ for (const auto& t : targets) {
1641
+ if (send_binary_to_peer_unlocked(t.socket, data, message_type, t.send_cipher, t.peer_id)) {
1642
+ sent_count++;
1643
+ }
1644
+ }
1543
1645
  return sent_count;
1544
1646
  }
1545
1647
 
@@ -1549,35 +1651,6 @@ int RatsClient::broadcast_string_to_peers(const std::string& data) {
1549
1651
  return broadcast_binary_to_peers(binary_data, MessageDataType::STRING);
1550
1652
  }
1551
1653
 
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
1654
  // =========================================================================
1582
1655
  // Peer Information and Management
1583
1656
  // =========================================================================
@@ -1587,32 +1660,18 @@ std::string RatsClient::get_our_peer_id() const {
1587
1660
  }
1588
1661
 
1589
1662
  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;
1663
+ // Returns the cached validated peer count (O(1) instead of O(N) scan)
1664
+ return validated_peer_count_.load(std::memory_order_relaxed);
1598
1665
  }
1599
1666
 
1600
1667
  int RatsClient::get_peer_count() const {
1601
- std::lock_guard<std::mutex> lock(peers_mutex_);
1602
- return get_peer_count_unlocked();
1668
+ return validated_peer_count_.load(std::memory_order_relaxed);
1603
1669
  }
1604
1670
 
1605
1671
  std::string RatsClient::get_peer_id(socket_t socket) const {
1606
- // Atomic operation - lock once and return copy to avoid race condition
1607
1672
  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 "";
1673
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1674
+ return (peer_it != peers_.end()) ? peer_it->second.peer_id : "";
1616
1675
  }
1617
1676
 
1618
1677
  socket_t RatsClient::get_peer_socket_by_id(const std::string& peer_id) const {
@@ -1650,7 +1709,6 @@ std::vector<RatsPeer> RatsClient::get_validated_peers() const {
1650
1709
  return result;
1651
1710
  }
1652
1711
 
1653
-
1654
1712
  std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::string& exclude_peer_id) const {
1655
1713
  std::lock_guard<std::mutex> lock(peers_mutex_);
1656
1714
 
@@ -1681,23 +1739,24 @@ std::vector<RatsPeer> RatsClient::get_random_peers(int max_count, const std::str
1681
1739
  return selected_peers;
1682
1740
  }
1683
1741
 
1684
- const RatsPeer* RatsClient::get_peer_by_id(const std::string& peer_id) const {
1742
+ std::optional<RatsPeer> RatsClient::get_peer_by_id(const std::string& peer_id) const {
1685
1743
  std::lock_guard<std::mutex> lock(peers_mutex_);
1686
1744
  auto it = peers_.find(peer_id);
1687
- return (it != peers_.end()) ? &it->second : nullptr;
1745
+ if (it != peers_.end()) {
1746
+ return it->second;
1747
+ }
1748
+ return std::nullopt;
1688
1749
  }
1689
1750
 
1690
- const RatsPeer* RatsClient::get_peer_by_socket(socket_t socket) const {
1751
+ std::optional<RatsPeer> RatsClient::get_peer_by_socket(socket_t socket) const {
1691
1752
  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;
1753
+ auto peer_it = find_peer_by_socket_unlocked(socket);
1754
+ if (peer_it != peers_.end()) {
1755
+ return peer_it->second;
1696
1756
  }
1697
- return nullptr;
1757
+ return std::nullopt;
1698
1758
  }
1699
1759
 
1700
-
1701
1760
  // Peer limit management methods
1702
1761
  int RatsClient::get_max_peers() const {
1703
1762
  return max_peers_;
@@ -1710,15 +1769,10 @@ void RatsClient::set_max_peers(int max_peers) {
1710
1769
 
1711
1770
  bool RatsClient::is_peer_limit_reached() const {
1712
1771
  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;
1772
+ return get_peer_count_unlocked() >= max_peers_;
1719
1773
  }
1720
1774
 
1721
- std::string RatsClient::generate_peer_hash_id(socket_t socket, const std::string& connection_info) {
1775
+ std::string RatsClient::generate_temporary_peer_id(socket_t socket, const std::string& connection_info) {
1722
1776
  // Generate unique hash ID using timestamp, socket, connection info, and random component
1723
1777
  auto now = std::chrono::high_resolution_clock::now();
1724
1778
  auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
@@ -1769,7 +1823,7 @@ std::string RatsClient::normalize_peer_address(const std::string& ip, int port)
1769
1823
 
1770
1824
  // =========================================================================
1771
1825
  // Callback Registration
1772
- // ========================================================================
1826
+ // =========================================================================
1773
1827
 
1774
1828
  void RatsClient::set_connection_callback(ConnectionCallback callback) {
1775
1829
  connection_callback_ = callback;
@@ -1791,325 +1845,6 @@ void RatsClient::set_disconnect_callback(DisconnectCallback callback) {
1791
1845
  disconnect_callback_ = callback;
1792
1846
  }
1793
1847
 
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
1848
  // =========================================================================
2114
1849
  // Protocol Configuration
2115
1850
  // =========================================================================
@@ -2140,11 +1875,10 @@ std::string RatsClient::get_protocol_version() const {
2140
1875
  // Message Exchange API
2141
1876
  // =========================================================================
2142
1877
 
2143
-
2144
1878
  void RatsClient::on(const std::string& message_type, MessageCallback callback) {
2145
1879
  std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2146
1880
  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() << ")");
1881
+ LOG_CLIENT_DEBUG("Registered handler for message type: " << message_type);
2148
1882
  }
2149
1883
 
2150
1884
  void RatsClient::once(const std::string& message_type, MessageCallback callback) {
@@ -2172,15 +1906,15 @@ void RatsClient::send(const std::string& message_type, const nlohmann::json& dat
2172
1906
  return;
2173
1907
  }
2174
1908
 
2175
- LOG_CLIENT_INFO("Sending broadcast message type '" << message_type << "' with data: " << data.dump());
1909
+ LOG_CLIENT_DEBUG("Sending broadcast message type '" << message_type << "'");
2176
1910
 
2177
1911
  // Create rats message
2178
1912
  nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
2179
1913
 
2180
1914
  // Broadcast to all validated peers
2181
- int sent_count = broadcast_rats_message_to_validated_peers(message);
1915
+ int sent_count = broadcast_rats_message(message);
2182
1916
 
2183
- LOG_CLIENT_INFO("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
1917
+ LOG_CLIENT_DEBUG("Broadcasted message type '" << message_type << "' to " << sent_count << " peers");
2184
1918
 
2185
1919
  if (callback) {
2186
1920
  if (sent_count > 0) {
@@ -2201,7 +1935,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2201
1935
  return;
2202
1936
  }
2203
1937
 
2204
- LOG_CLIENT_INFO("Sending targeted message type '" << message_type << "' to peer " << peer_id << " with data: " << data.dump());
1938
+ LOG_CLIENT_DEBUG("Sending targeted message type '" << message_type << "' to peer " << peer_id);
2205
1939
 
2206
1940
  // Create rats message
2207
1941
  nlohmann::json message = create_rats_message(message_type, data, get_our_peer_id());
@@ -2241,7 +1975,7 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2241
1975
 
2242
1976
  bool success = send_json_to_peer(target_socket, message);
2243
1977
 
2244
- LOG_CLIENT_INFO("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
1978
+ LOG_CLIENT_DEBUG("Sent message type '" << message_type << "' to peer " << peer_id << " - " << (success ? "success" : "failed"));
2245
1979
 
2246
1980
  if (callback) {
2247
1981
  if (success) {
@@ -2255,54 +1989,41 @@ void RatsClient::send(const std::string& peer_id, const std::string& message_typ
2255
1989
  // Message exchange system helpers
2256
1990
  void RatsClient::call_message_handlers(const std::string& message_type, const std::string& peer_id, const nlohmann::json& data) {
2257
1991
  std::vector<MessageHandler> handlers_to_call;
2258
- std::vector<MessageHandler> remaining_handlers;
2259
1992
 
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
1993
+ // Get handlers to call and remove once handlers atomically
2263
1994
  {
2264
1995
  std::lock_guard<std::mutex> lock(message_handlers_mutex_);
2265
1996
  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 << "'");
1997
+ if (it == message_handlers_.end()) {
1998
+ LOG_CLIENT_DEBUG("No handlers registered for message type '" << message_type << "'");
1999
+ return;
2280
2000
  }
2001
+
2002
+ handlers_to_call = it->second;
2003
+
2004
+ // Remove once handlers using erase-remove idiom
2005
+ it->second.erase(
2006
+ std::remove_if(it->second.begin(), it->second.end(),
2007
+ [](const MessageHandler& h) { return h.is_once; }),
2008
+ it->second.end());
2281
2009
  }
2282
2010
 
2283
- LOG_CLIENT_INFO("Found " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2011
+ LOG_CLIENT_DEBUG("Calling " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2284
2012
 
2285
2013
  // Call handlers outside of mutex to avoid deadlock
2286
2014
  for (const auto& handler : handlers_to_call) {
2287
2015
  try {
2288
- LOG_CLIENT_INFO("Calling handler for message type '" << message_type << "'");
2289
2016
  handler.callback(peer_id, data);
2290
- LOG_CLIENT_INFO("Handler for message type '" << message_type << "' completed successfully");
2291
2017
  } catch (const std::exception& e) {
2292
2018
  LOG_CLIENT_ERROR("Exception in message handler for type '" << message_type << "': " << e.what());
2293
2019
  } catch (...) {
2294
2020
  LOG_CLIENT_ERROR("Unknown exception in message handler for type '" << message_type << "'");
2295
2021
  }
2296
2022
  }
2297
-
2298
- if (!handlers_to_call.empty()) {
2299
- LOG_CLIENT_INFO("Called " << handlers_to_call.size() << " handlers for message type '" << message_type << "'");
2300
- }
2301
2023
  }
2302
2024
 
2303
-
2304
2025
  // =========================================================================
2305
- // Rats messages protocol / Message handling system
2026
+ // Rats Protocol Message Handling
2306
2027
  // =========================================================================
2307
2028
 
2308
2029
  nlohmann::json RatsClient::create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id) {
@@ -2317,26 +2038,26 @@ nlohmann::json RatsClient::create_rats_message(const std::string& type, const nl
2317
2038
  return message;
2318
2039
  }
2319
2040
 
2320
- void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& message) {
2041
+ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message) {
2321
2042
  try {
2322
2043
  std::string message_type = message.value("type", "");
2323
2044
  nlohmann::json payload = message.value("payload", nlohmann::json::object());
2324
2045
  std::string sender_peer_id = message.value("sender_peer_id", "");
2325
2046
 
2326
- LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_hash_id);
2047
+ LOG_CLIENT_DEBUG("Received rats message type '" << message_type << "' from " << peer_id);
2327
2048
 
2328
2049
  // 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);
2050
+ call_message_handlers(message_type, sender_peer_id.empty() ? peer_id : sender_peer_id, payload);
2330
2051
 
2331
2052
  // Handle built-in message types for internal functionality
2332
2053
  if (message_type == "peer") {
2333
- handle_peer_exchange_message(socket, peer_hash_id, payload);
2054
+ handle_peer_exchange_message(socket, peer_id, payload);
2334
2055
  }
2335
2056
  else if (message_type == "peers_request") {
2336
- handle_peers_request_message(socket, peer_hash_id, payload);
2057
+ handle_peers_request_message(socket, peer_id, payload);
2337
2058
  }
2338
2059
  else if (message_type == "peers_response") {
2339
- handle_peers_response_message(socket, peer_hash_id, payload);
2060
+ handle_peers_response_message(socket, peer_id, payload);
2340
2061
  }
2341
2062
  // Custom message types are now handled by registered handlers above
2342
2063
  // No need for else clause - all message types are valid if they have registered handlers
@@ -2346,54 +2067,39 @@ void RatsClient::handle_rats_message(socket_t socket, const std::string& peer_ha
2346
2067
  }
2347
2068
  }
2348
2069
 
2349
- void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2070
+ void RatsClient::handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2350
2071
  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 << ")");
2072
+ std::string exchanged_ip = payload.value("ip", "");
2073
+ int exchanged_port = payload.value("port", 0);
2074
+ std::string exchanged_peer_id = payload.value("peer_id", "");
2361
2075
 
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");
2076
+ if (exchanged_ip.empty() || exchanged_port <= 0 || exchanged_peer_id.empty()) {
2077
+ LOG_CLIENT_WARN("Invalid peer exchange message from " << peer_id);
2365
2078
  return;
2366
2079
  }
2367
2080
 
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
- }
2081
+ LOG_CLIENT_INFO("Received peer exchange: " << exchanged_ip << ":" << exchanged_port << " (peer_id: " << exchanged_peer_id << ")");
2374
2082
 
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);
2083
+ if (!can_connect_to_peer(exchanged_ip, exchanged_port)) {
2378
2084
  return;
2379
2085
  }
2380
2086
 
2381
2087
  // 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);
2088
+ add_managed_thread(std::thread([this, exchanged_ip, exchanged_port, exchanged_peer_id]() {
2089
+ if (connect_to_peer(exchanged_ip, exchanged_port)) {
2090
+ LOG_CLIENT_INFO("Successfully connected to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2385
2091
  } else {
2386
- LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << peer_ip << ":" << peer_port);
2092
+ LOG_CLIENT_DEBUG("Failed to connect to exchanged peer: " << exchanged_ip << ":" << exchanged_port);
2387
2093
  }
2388
- }), "peer-exchange-connect-" + peer_id.substr(0, 8));
2094
+ }), "peer-exchange-connect-" + exchanged_peer_id.substr(0, 8));
2389
2095
 
2390
2096
  } catch (const nlohmann::json::exception& e) {
2391
2097
  LOG_CLIENT_ERROR("Failed to handle peer exchange message: " << e.what());
2392
2098
  }
2393
2099
  }
2394
2100
 
2395
- // General broadcasting functions
2396
- int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id) {
2101
+ // General broadcasting function
2102
+ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id, bool validated_only) {
2397
2103
  // Serialize JSON once before iterating
2398
2104
  std::string json_string;
2399
2105
  try {
@@ -2404,53 +2110,27 @@ int RatsClient::broadcast_rats_message(const nlohmann::json& message, const std:
2404
2110
  }
2405
2111
  std::vector<uint8_t> binary_data(json_string.begin(), json_string.end());
2406
2112
 
2407
- int sent_count = 0;
2113
+ // Collect targets under lock, then enqueue outside
2114
+ std::vector<PeerSendTarget> targets;
2408
2115
  {
2409
2116
  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
2117
+ targets.reserve(peers_.size());
2118
+ for (const auto& [id, peer] : peers_) {
2413
2119
  if (!exclude_peer_id.empty() && peer.peer_id == exclude_peer_id) {
2414
2120
  continue;
2415
2121
  }
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++;
2122
+ if (validated_only && !peer.is_handshake_completed()) {
2123
+ continue;
2421
2124
  }
2125
+ targets.push_back({peer.socket, peer.peer_id,
2126
+ peer.is_noise_encrypted() ? peer.send_cipher : nullptr});
2422
2127
  }
2423
2128
  }
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
2129
 
2438
2130
  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
- }
2131
+ for (const auto& t : targets) {
2132
+ if (send_binary_to_peer_unlocked(t.socket, binary_data, MessageDataType::JSON, t.send_cipher, t.peer_id)) {
2133
+ sent_count++;
2454
2134
  }
2455
2135
  }
2456
2136
  return sent_count;
@@ -2479,7 +2159,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2479
2159
  nlohmann::json message = create_peer_exchange_message(new_peer);
2480
2160
 
2481
2161
  // Broadcast to all validated peers except the new peer
2482
- int sent_count = broadcast_rats_message_to_validated_peers(message, new_peer.peer_id);
2162
+ int sent_count = broadcast_rats_message(message, new_peer.peer_id);
2483
2163
 
2484
2164
  LOG_CLIENT_INFO("Broadcasted peer exchange message for " << new_peer.ip << ":" << new_peer.port
2485
2165
  << " to " << sent_count << " peers");
@@ -2488,7 +2168,7 @@ void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {
2488
2168
  // Peers request/response system implementation
2489
2169
  nlohmann::json RatsClient::create_peers_request_message(const std::string& sender_peer_id) {
2490
2170
  nlohmann::json payload;
2491
- payload["max_peers"] = 5; // Request up to 5 peers
2171
+ payload["max_peers"] = MAX_PEERS_REQUEST_COUNT;
2492
2172
  payload["requester_info"] = {
2493
2173
  {"listen_port", listen_port_},
2494
2174
  {"peer_count", get_peer_count()}
@@ -2502,39 +2182,38 @@ nlohmann::json RatsClient::create_peers_response_message(const std::vector<RatsP
2502
2182
  nlohmann::json peers_array = nlohmann::json::array();
2503
2183
 
2504
2184
  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);
2185
+ peers_array.push_back({
2186
+ {"ip", peer.ip},
2187
+ {"port", peer.port},
2188
+ {"peer_id", peer.peer_id},
2189
+ {"connection_type", peer.is_outgoing ? "outgoing" : "incoming"}
2190
+ });
2511
2191
  }
2512
2192
 
2513
- payload["peers"] = peers_array;
2193
+ payload["peers"] = std::move(peers_array);
2514
2194
  payload["total_peers"] = get_peer_count();
2515
2195
 
2516
2196
  return create_rats_message("peers_response", payload, sender_peer_id);
2517
2197
  }
2518
2198
 
2519
-
2520
- void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2199
+ void RatsClient::handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2521
2200
  try {
2522
- int max_peers = payload.value("max_peers", 5);
2201
+ int max_peers = payload.value("max_peers", MAX_PEERS_REQUEST_COUNT);
2523
2202
 
2524
- LOG_CLIENT_INFO("Received peers request from " << peer_hash_id << " for up to " << max_peers << " peers");
2203
+ LOG_CLIENT_INFO("Received peers request from " << peer_id << " for up to " << max_peers << " peers");
2525
2204
 
2526
2205
  // Get random peers excluding the requester
2527
- std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_hash_id);
2206
+ std::vector<RatsPeer> random_peers = get_random_peers(max_peers, peer_id);
2528
2207
 
2529
- LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_hash_id);
2208
+ LOG_CLIENT_DEBUG("Sending " << random_peers.size() << " peers to " << peer_id);
2530
2209
 
2531
2210
  // Create and send peers response
2532
- nlohmann::json response_message = create_peers_response_message(random_peers, peer_hash_id);
2211
+ nlohmann::json response_message = create_peers_response_message(random_peers, peer_id);
2533
2212
 
2534
2213
  if (!send_json_to_peer(socket, response_message)) {
2535
- LOG_CLIENT_ERROR("Failed to send peers response to " << peer_hash_id);
2214
+ LOG_CLIENT_ERROR("Failed to send peers response to " << peer_id);
2536
2215
  } else {
2537
- LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_hash_id);
2216
+ LOG_CLIENT_DEBUG("Sent peers response with " << random_peers.size() << " peers to " << peer_id);
2538
2217
  }
2539
2218
 
2540
2219
  } catch (const nlohmann::json::exception& e) {
@@ -2542,55 +2221,39 @@ void RatsClient::handle_peers_request_message(socket_t socket, const std::string
2542
2221
  }
2543
2222
  }
2544
2223
 
2545
- void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload) {
2224
+ void RatsClient::handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload) {
2546
2225
  try {
2547
2226
  nlohmann::json peers_array = payload.value("peers", nlohmann::json::array());
2548
2227
  int total_peers = payload.value("total_peers", 0);
2549
2228
 
2550
- LOG_CLIENT_INFO("Received peers response from " << peer_hash_id << " with " << peers_array.size()
2229
+ LOG_CLIENT_INFO("Received peers response from " << peer_id << " with " << peers_array.size()
2551
2230
  << " peers (total: " << total_peers << ")");
2552
2231
 
2553
2232
  // Process each peer in the response
2554
2233
  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
- }
2234
+ std::string resp_ip = peer_info.value("ip", "");
2235
+ int resp_port = peer_info.value("port", 0);
2236
+ std::string resp_peer_id = peer_info.value("peer_id", "");
2563
2237
 
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");
2238
+ if (resp_ip.empty() || resp_port <= 0 || resp_peer_id.empty()) {
2239
+ LOG_CLIENT_WARN("Invalid peer info in peers response from " << peer_id);
2569
2240
  continue;
2570
2241
  }
2571
2242
 
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
- }
2243
+ LOG_CLIENT_DEBUG("Processing peer from response: " << resp_ip << ":" << resp_port << " (peer_id: " << resp_peer_id << ")");
2578
2244
 
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);
2245
+ if (!can_connect_to_peer(resp_ip, resp_port)) {
2582
2246
  continue;
2583
2247
  }
2584
2248
 
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);
2249
+ LOG_CLIENT_DEBUG("Attempting to connect to peer from response: " << resp_ip << ":" << resp_port);
2250
+ add_managed_thread(std::thread([this, resp_ip, resp_port, resp_peer_id]() {
2251
+ if (connect_to_peer(resp_ip, resp_port)) {
2252
+ LOG_CLIENT_INFO("Successfully connected to peer from response: " << resp_ip << ":" << resp_port);
2590
2253
  } else {
2591
- LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << peer_ip << ":" << peer_port);
2254
+ LOG_CLIENT_DEBUG("Failed to connect to peer from response: " << resp_ip << ":" << resp_port);
2592
2255
  }
2593
- }), "peer-response-connect-" + peer_id.substr(0, 8));
2256
+ }), "peer-response-connect-" + resp_peer_id.substr(0, 8));
2594
2257
  }
2595
2258
 
2596
2259
  } catch (const nlohmann::json::exception& e) {
@@ -2609,49 +2272,7 @@ void RatsClient::send_peers_request(socket_t socket, const std::string& our_peer
2609
2272
  }
2610
2273
 
2611
2274
  // =========================================================================
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
2275
+ // Helper Functions
2655
2276
  // =========================================================================
2656
2277
 
2657
2278
  std::unique_ptr<RatsClient> create_rats_client(int listen_port) {
@@ -2721,52 +2342,4 @@ bool RatsClient::parse_address_string(const std::string& address_str, std::strin
2721
2342
  return !out_ip.empty() && out_port > 0 && out_port <= 65535;
2722
2343
  }
2723
2344
 
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
2345
  } // namespace librats