librats 0.7.0 → 0.7.2

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.
@@ -62,8 +62,19 @@ bool DhtClient::start() {
62
62
  }
63
63
 
64
64
  socket_ = create_udp_socket(port_, bind_address_);
65
+ // Fallback to free port
66
+ if (!is_valid_socket(socket_) && port_ > 0) {
67
+ // Requested port is not available, try ephemeral port as fallback
68
+ int original_port = port_;
69
+ LOG_DHT_WARN("UDP port " << original_port << " is not available, falling back to ephemeral port");
70
+ socket_ = create_udp_socket(0, bind_address_);
71
+ if (is_valid_socket(socket_)) {
72
+ port_ = get_bound_port(socket_);
73
+ LOG_DHT_INFO("Fell back from port " << original_port << " to ephemeral port " << port_);
74
+ }
75
+ }
65
76
  if (!is_valid_socket(socket_)) {
66
- LOG_DHT_ERROR("Failed to create dual-stack UDP socket");
77
+ LOG_DHT_ERROR("Failed to create dual-stack UDP socket on port " << port_);
67
78
  return false;
68
79
  }
69
80
 
@@ -380,7 +391,7 @@ void DhtClient::network_loop() {
380
391
 
381
392
  while (running_) {
382
393
  Peer sender;
383
- auto data = receive_udp_data(socket_, 1500, sender); // MTU size
394
+ auto data = receive_udp_data(socket_, 1500, sender, -1); // MTU size, blocking
384
395
 
385
396
  if (!data.empty()) {
386
397
  LOG_DHT_DEBUG("Received " << data.size() << " bytes from " << sender.ip << ":" << sender.port);
@@ -937,7 +948,8 @@ void DhtClient::handle_krpc_announce_peer(const KrpcMessage& message, const Peer
937
948
  }
938
949
 
939
950
  // Determine the actual port (BEP 5: implied_port support)
940
- uint16_t peer_port = message.port;
951
+ // If implied_port is set, use the UDP source port instead of the explicit port field
952
+ uint16_t peer_port = message.implied_port ? sender.port : message.port;
941
953
 
942
954
  // Store the peer announcement
943
955
  Peer announcing_peer(sender.ip, peer_port);
@@ -1072,7 +1084,7 @@ bool DhtClient::send_krpc_message(const KrpcMessage& message, const Peer& peer)
1072
1084
  }
1073
1085
 
1074
1086
  LOG_DHT_DEBUG("Sending KRPC message (" << data.size() << " bytes) to " << peer.ip << ":" << peer.port);
1075
- int result = send_udp_data(socket_, data, peer);
1087
+ int result = send_udp_data(socket_, data, peer.ip, peer.port);
1076
1088
 
1077
1089
  if (result > 0) {
1078
1090
  LOG_DHT_DEBUG("Successfully sent KRPC message to " << peer.ip << ":" << peer.port);
@@ -1101,9 +1113,9 @@ void DhtClient::send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash)
1101
1113
  send_krpc_message(message, peer);
1102
1114
  }
1103
1115
 
1104
- void DhtClient::send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token) {
1116
+ void DhtClient::send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port) {
1105
1117
  std::string transaction_id = KrpcProtocol::generate_transaction_id();
1106
- auto message = KrpcProtocol::create_announce_peer_query(transaction_id, node_id_, info_hash, port, token);
1118
+ auto message = KrpcProtocol::create_announce_peer_query(transaction_id, node_id_, info_hash, port, token, implied_port);
1107
1119
  send_krpc_message(message, peer);
1108
1120
  }
1109
1121
 
@@ -2138,13 +2150,19 @@ void DhtClient::send_announce_to_closest_nodes(PendingSearch& search) {
2138
2150
 
2139
2151
  LOG_DHT_INFO("Announcing to " << announce_targets.size() << " closest nodes with tokens");
2140
2152
 
2153
+ // BEP 5: Use implied_port when the announce port is the same as the DHT port.
2154
+ // This tells the receiving node to use the UDP source port from the packet,
2155
+ // which is more accurate when behind NAT.
2156
+ bool use_implied_port = (search.announce_port == port_);
2157
+
2141
2158
  // Send announce_peer to each target
2142
2159
  for (const auto& [node, token] : announce_targets) {
2143
2160
  LOG_DHT_DEBUG("Sending announce_peer to node " << node_id_to_hex(node.id)
2144
2161
  << " at " << node.peer.ip << ":" << node.peer.port
2145
- << " with token (distance: " << get_bucket_index(node.id) << ")");
2162
+ << " with token (distance: " << get_bucket_index(node.id) << ")"
2163
+ << (use_implied_port ? " [implied_port]" : ""));
2146
2164
 
2147
- send_krpc_announce_peer(node.peer, search.info_hash, search.announce_port, token);
2165
+ send_krpc_announce_peer(node.peer, search.info_hash, search.announce_port, token, use_implied_port);
2148
2166
  }
2149
2167
 
2150
2168
  LOG_DHT_INFO("Announce completed: sent announce_peer to " << announce_targets.size()
@@ -541,7 +541,7 @@ private:
541
541
  void send_krpc_ping(const Peer& peer);
542
542
  void send_krpc_find_node(const Peer& peer, const NodeId& target);
543
543
  void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
544
- void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
544
+ void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
545
545
 
546
546
  void add_node(const DhtNode& node, bool confirmed = true, bool no_verify = false);
547
547
  std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
@@ -281,7 +281,7 @@ bool IceManager::ensure_socket() {
281
281
  return false;
282
282
  }
283
283
 
284
- local_port_ = static_cast<uint16_t>(get_ephemeral_port(socket_));
284
+ local_port_ = static_cast<uint16_t>(get_bound_port(socket_));
285
285
  LOG_ICE_DEBUG("Created ICE socket on port " << local_port_);
286
286
  return true;
287
287
  }
@@ -60,7 +60,7 @@ KrpcMessage KrpcProtocol::create_get_peers_query(const std::string& transaction_
60
60
  return message;
61
61
  }
62
62
 
63
- KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token) {
63
+ KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port) {
64
64
  KrpcMessage message;
65
65
  message.type = KrpcMessageType::Query;
66
66
  message.transaction_id = transaction_id;
@@ -68,6 +68,7 @@ KrpcMessage KrpcProtocol::create_announce_peer_query(const std::string& transact
68
68
  message.sender_id = sender_id;
69
69
  message.info_hash = info_hash;
70
70
  message.port = port;
71
+ message.implied_port = implied_port;
71
72
  message.token = token;
72
73
  return message;
73
74
  }
@@ -176,6 +177,9 @@ BencodeValue KrpcProtocol::encode_query(const KrpcMessage& message) {
176
177
  args["info_hash"] = BencodeValue(node_id_to_string(message.info_hash));
177
178
  args["port"] = BencodeValue(static_cast<int64_t>(message.port));
178
179
  args["token"] = BencodeValue(message.token);
180
+ if (message.implied_port) {
181
+ args["implied_port"] = BencodeValue(static_cast<int64_t>(1));
182
+ }
179
183
  break;
180
184
  }
181
185
 
@@ -316,6 +320,9 @@ std::unique_ptr<KrpcMessage> KrpcProtocol::decode_query(const BencodeValue& data
316
320
  if (args.has_key("token")) {
317
321
  message->token = args["token"].as_string();
318
322
  }
323
+ if (args.has_key("implied_port")) {
324
+ message->implied_port = (args["implied_port"].as_integer() != 0);
325
+ }
319
326
  break;
320
327
  }
321
328
 
@@ -68,6 +68,7 @@ struct KrpcMessage {
68
68
  NodeId target_id;
69
69
  InfoHash info_hash;
70
70
  uint16_t port;
71
+ bool implied_port; // BEP 5: if true, use UDP source port instead of 'port' field
71
72
  std::string token;
72
73
 
73
74
  // For responses
@@ -79,7 +80,7 @@ struct KrpcMessage {
79
80
  KrpcErrorCode error_code;
80
81
  std::string error_message;
81
82
 
82
- KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), response_id(), error_code(KrpcErrorCode::GenericError) {}
83
+ KrpcMessage() : type(KrpcMessageType::Query), query_type(KrpcQueryType::Ping), sender_id(), target_id(), info_hash(), port(0), implied_port(false), response_id(), error_code(KrpcErrorCode::GenericError) {}
83
84
  };
84
85
 
85
86
  /**
@@ -96,7 +97,7 @@ public:
96
97
  static KrpcMessage create_ping_query(const std::string& transaction_id, const NodeId& sender_id);
97
98
  static KrpcMessage create_find_node_query(const std::string& transaction_id, const NodeId& sender_id, const NodeId& target_id);
98
99
  static KrpcMessage create_get_peers_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash);
99
- static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token);
100
+ static KrpcMessage create_announce_peer_query(const std::string& transaction_id, const NodeId& sender_id, const InfoHash& info_hash, uint16_t port, const std::string& token, bool implied_port = false);
100
101
 
101
102
  static KrpcMessage create_ping_response(const std::string& transaction_id, const NodeId& response_id);
102
103
  static KrpcMessage create_find_node_response(const std::string& transaction_id, const NodeId& response_id, const std::vector<KrpcNode>& nodes);
@@ -146,16 +146,27 @@ bool RatsClient::start() {
146
146
  initialize_local_addresses();
147
147
 
148
148
  // Create dual-stack server socket (supports both IPv4 and IPv6)
149
- server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
149
+ server_socket_ = create_tcp_server(listen_port_, 5, bind_address_);
150
+ // Fallback to free port
151
+ if (!is_valid_socket(server_socket_) && listen_port_ > 0) {
152
+ // Requested port is not available, try ephemeral port as fallback
153
+ int original_port = listen_port_;
154
+ LOG_CLIENT_WARN("TCP port " << original_port << " is not available, falling back to ephemeral port");
155
+ server_socket_ = create_tcp_server(0, 5, bind_address_);
156
+ if (is_valid_socket(server_socket_)) {
157
+ listen_port_ = get_bound_port(server_socket_);
158
+ LOG_CLIENT_INFO("Fell back from port " << original_port << " to ephemeral port " << listen_port_);
159
+ }
160
+ }
150
161
  if (!is_valid_socket(server_socket_)) {
151
- LOG_CLIENT_ERROR("Failed to create dual-stack server socket on port " << listen_port_ <<
162
+ LOG_CLIENT_ERROR("Failed to create server socket on port " << listen_port_ <<
152
163
  (bind_address_.empty() ? "" : " bound to " + bind_address_));
153
164
  return false;
154
165
  }
155
166
 
156
167
  // Update listen_port_ with actual bound port if ephemeral port was requested
157
168
  if (listen_port_ == 0) {
158
- listen_port_ = get_ephemeral_port(server_socket_);
169
+ listen_port_ = get_bound_port(server_socket_);
159
170
  if (listen_port_ == 0) {
160
171
  LOG_CLIENT_WARN("Failed to get actual bound port - using port 0");
161
172
  } else {
@@ -448,14 +459,15 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
448
459
 
449
460
  // ----- 1. RECEIVE DATA -----
450
461
  LOG_CLIENT_DEBUG("Receiving data from socket " << client_socket);
451
- std::vector<uint8_t> received_bytes = receive_tcp_message_framed(client_socket);
462
+ std::vector<uint8_t> received_bytes = receive_tcp_message(client_socket);
452
463
 
453
464
  if (received_bytes.empty()) {
454
465
  break; // Connection closed or error
455
466
  }
456
467
 
457
468
  // ----- 2. DECRYPT IF NEEDED -----
458
- std::string data;
469
+ // Use vector directly to avoid unnecessary string conversions
470
+ std::vector<uint8_t> data;
459
471
 
460
472
  if (noise_handshake_done) {
461
473
  std::string current_peer_id;
@@ -492,16 +504,18 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
492
504
  }
493
505
 
494
506
  plaintext.resize(pt_len);
495
- data = std::string(plaintext.begin(), plaintext.end());
507
+ data = std::move(plaintext);
496
508
  LOG_CLIENT_DEBUG("Decrypted message from " << current_peer_id << " (" << pt_len << " bytes)");
497
509
  } else {
498
- data = std::string(received_bytes.begin(), received_bytes.end());
510
+ data = std::move(received_bytes);
499
511
  }
500
512
  } else {
501
- data = std::string(received_bytes.begin(), received_bytes.end());
513
+ data = std::move(received_bytes);
502
514
  }
503
515
 
504
- LOG_CLIENT_DEBUG("Received data from " << peer_hash_id << ": " << data.substr(0, 50) << (data.length() > 50 ? "..." : ""));
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 ? "..." : ""));
505
519
 
506
520
  // ----- 3. CONNECTION STATE CHECK (during handshake phase only) -----
507
521
  if (!handshake_completed) {
@@ -531,51 +545,75 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
531
545
  }
532
546
 
533
547
  // ----- 4. HANDSHAKE PHASE -----
534
- if (is_handshake_message(data)) {
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)) {
535
551
  if (!handle_handshake_message(client_socket, peer_hash_id, data)) {
536
552
  LOG_CLIENT_ERROR("Failed to handle handshake message from " << peer_hash_id);
537
553
  break;
538
554
  }
539
555
 
540
- // Check if handshake just completed
556
+ // Check if rats handshake just completed (COMPLETED or NOISE_PENDING state)
541
557
  if (!handshake_completed) {
542
558
  RatsPeer peer_copy;
543
- bool just_completed = false;
559
+ bool rats_handshake_done = false;
560
+ bool needs_noise_handshake = false;
544
561
 
545
562
  {
546
563
  std::lock_guard<std::mutex> lock(peers_mutex_);
547
564
  auto sock_it = socket_to_peer_id_.find(client_socket);
548
565
  if (sock_it != socket_to_peer_id_.end()) {
549
566
  auto peer_it = peers_.find(sock_it->second);
550
- if (peer_it != peers_.end() && peer_it->second.is_handshake_completed()) {
551
- just_completed = true;
552
- peer_copy = peer_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
+ }
553
577
  }
554
578
  }
555
579
  }
556
580
 
557
581
  // ----- POST-HANDSHAKE ACTIONS -----
558
- if (just_completed) {
559
- handshake_completed = true;
560
- LOG_CLIENT_INFO("Handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
582
+ if (rats_handshake_done) {
583
+ LOG_CLIENT_INFO("Rats handshake completed for peer " << peer_hash_id << " (peer_id: " << peer_copy.peer_id << ")");
561
584
 
562
585
  // Remove from reconnection queue if present (successful connection)
563
586
  remove_from_reconnect_queue(peer_copy.peer_id);
564
587
 
565
588
  // Noise encryption handshake - only if BOTH sides support encryption
566
589
  // peer_copy.encryption_enabled is already negotiated in handle_handshake_message()
567
- if (peer_copy.encryption_enabled) {
590
+ if (needs_noise_handshake) {
568
591
  LOG_CLIENT_INFO("Starting Noise handshake for peer " << peer_copy.peer_id);
569
592
  if (perform_noise_handshake(client_socket, peer_copy.peer_id, peer_copy.is_outgoing)) {
570
593
  noise_handshake_done = true;
571
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
+ }
572
608
  } else {
573
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;
574
612
  }
575
- } else {
576
- LOG_CLIENT_DEBUG("Skipping Noise handshake for peer " << peer_copy.peer_id << " - encryption not negotiated");
577
613
  }
578
614
 
615
+ handshake_completed = true;
616
+
579
617
  // Connection callback
580
618
  if (connection_callback_) {
581
619
  connection_callback_(client_socket, peer_copy.peer_id);
@@ -621,11 +659,11 @@ void RatsClient::handle_client(socket_t client_socket, const std::string& peer_h
621
659
  continue;
622
660
  }
623
661
 
624
- std::vector<uint8_t> received_data(data.begin(), data.end());
662
+ // Use data directly - no need for extra copy
625
663
  MessageHeader header;
626
664
  std::vector<uint8_t> payload;
627
665
 
628
- if (!parse_message_with_header(received_data, header, payload)) {
666
+ if (!parse_message_with_header(data, header, payload)) {
629
667
  LOG_CLIENT_WARN("No header found in message from " << peer_hash_id);
630
668
  continue;
631
669
  }
@@ -741,14 +779,15 @@ std::string RatsClient::create_handshake_message(const std::string& message_type
741
779
  handshake_msg["message_type"] = message_type;
742
780
  handshake_msg["timestamp"] = timestamp;
743
781
  handshake_msg["encryption_enabled"] = is_encryption_enabled();
782
+ handshake_msg["listen_port"] = listen_port_;
744
783
 
745
784
  return handshake_msg.dump();
746
785
  }
747
786
 
748
- bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMessage& out_msg) const {
787
+ bool RatsClient::parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const {
749
788
  try {
750
- // Use nlohmann::json for proper JSON parsing
751
- nlohmann::json json_msg = nlohmann::json::parse(message);
789
+ // Use nlohmann::json with iterators to avoid string conversion
790
+ nlohmann::json json_msg = nlohmann::json::parse(data.begin(), data.end());
752
791
 
753
792
  // Clear the output structure
754
793
  out_msg = HandshakeMessage{};
@@ -762,6 +801,8 @@ bool RatsClient::parse_handshake_message(const std::string& message, HandshakeMe
762
801
  out_msg.timestamp = json_msg.value("timestamp", static_cast<int64_t>(0));
763
802
  // Parse encryption_enabled (default to false for backward compatibility)
764
803
  out_msg.encryption_enabled = json_msg.value("encryption_enabled", false);
804
+ // Parse listen_port (default to 0 for backward compatibility with older clients)
805
+ out_msg.listen_port = json_msg.value("listen_port", static_cast<uint16_t>(0));
765
806
 
766
807
  return true;
767
808
 
@@ -823,37 +864,30 @@ bool RatsClient::validate_handshake_message(const HandshakeMessage& msg) const {
823
864
  return true;
824
865
  }
825
866
 
826
- bool RatsClient::is_handshake_message(const std::string& message) const {
867
+ bool RatsClient::is_handshake_message(const std::vector<uint8_t>& data) const {
827
868
  try {
828
- std::string json_to_parse = message;
829
-
830
869
  // Check if message has our message header (starts with "RATS" magic)
831
- std::vector<uint8_t> message_data(message.begin(), message.end());
832
870
  MessageHeader header;
833
871
  std::vector<uint8_t> payload;
834
872
 
835
- if (parse_message_with_header(message_data, header, payload)) {
873
+ if (parse_message_with_header(data, header, payload)) {
836
874
  // Message has valid header - extract the JSON payload
837
875
  if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
838
- json_to_parse = std::string(payload.begin(), payload.end());
839
- } else {
840
- // Handshake messages should be string/JSON type
841
- return false;
876
+ // Parse the JSON message directly from payload
877
+ nlohmann::json json_msg = nlohmann::json::parse(payload.begin(), payload.end());
878
+ std::string expected_protocol;
879
+ {
880
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
881
+ expected_protocol = custom_protocol_name_;
882
+ }
883
+ return json_msg.value("protocol", "") == expected_protocol &&
884
+ json_msg.value("message_type", "") == "handshake";
842
885
  }
843
- } else {
844
- // Message has no header
886
+ // Handshake messages should be string/JSON type
845
887
  return false;
846
888
  }
847
-
848
- // Parse the JSON message
849
- nlohmann::json json_msg = nlohmann::json::parse(json_to_parse);
850
- std::string expected_protocol;
851
- {
852
- std::lock_guard<std::mutex> lock(protocol_config_mutex_);
853
- expected_protocol = custom_protocol_name_;
854
- }
855
- return json_msg.value("protocol", "") == expected_protocol &&
856
- json_msg.value("message_type", "") == "handshake";
889
+ // Message has no header
890
+ return false;
857
891
  } catch (const std::exception&) {
858
892
  return false;
859
893
  }
@@ -874,7 +908,7 @@ bool RatsClient::send_handshake_unlocked(socket_t socket, const std::string& our
874
908
  auto socket_mutex = get_socket_send_mutex(socket);
875
909
  std::lock_guard<std::mutex> send_lock(*socket_mutex);
876
910
 
877
- int sent = send_tcp_message_framed(socket, message_with_header);
911
+ int sent = send_tcp_message(socket, message_with_header);
878
912
  if (sent <= 0) {
879
913
  LOG_CLIENT_ERROR("Failed to send handshake to socket " << socket);
880
914
  return false;
@@ -898,28 +932,25 @@ bool RatsClient::send_handshake(socket_t socket, const std::string& our_peer_id)
898
932
  return send_handshake_unlocked(socket, our_peer_id);
899
933
  }
900
934
 
901
- bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::string& message) {
902
- // Extract JSON payload from message header if present
903
- std::string json_to_parse = message;
904
- std::vector<uint8_t> message_data(message.begin(), message.end());
935
+ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data) {
936
+ // Extract JSON payload from message header
905
937
  MessageHeader header;
906
938
  std::vector<uint8_t> payload;
907
939
 
908
- if (parse_message_with_header(message_data, header, payload)) {
909
- // Message has valid header - extract the JSON payload
910
- if (header.type == MessageDataType::STRING || header.type == MessageDataType::JSON) {
911
- json_to_parse = std::string(payload.begin(), payload.end());
912
- } else {
913
- LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
914
- return false;
915
- }
916
- } else {
940
+ if (!parse_message_with_header(data, header, payload)) {
917
941
  LOG_CLIENT_ERROR("Failed to parse handshake message header from " << peer_hash_id);
918
942
  return false;
919
943
  }
920
944
 
945
+ // Message has valid header - check the type
946
+ if (header.type != MessageDataType::STRING && header.type != MessageDataType::JSON) {
947
+ LOG_CLIENT_ERROR("Invalid message type for handshake: " << static_cast<int>(header.type));
948
+ return false;
949
+ }
950
+
951
+ // Parse handshake message directly from payload (no string conversion)
921
952
  HandshakeMessage handshake_msg;
922
- if (!parse_handshake_message(json_to_parse, handshake_msg)) {
953
+ if (!parse_handshake_message(payload, handshake_msg)) {
923
954
  LOG_CLIENT_ERROR("Failed to parse handshake message from " << peer_hash_id);
924
955
  return false;
925
956
  }
@@ -995,12 +1026,35 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
995
1026
  << ", remote=" << remote_encryption
996
1027
  << ", result=" << peer.encryption_enabled);
997
1028
 
1029
+ // For incoming connections, update port to the peer's actual listen port
1030
+ // This is critical for peer exchange to work correctly
1031
+ if (!peer.is_outgoing && handshake_msg.listen_port > 0) {
1032
+ // Remove old address mapping
1033
+ address_to_peer_id_.erase(peer.normalized_address);
1034
+
1035
+ // Update port and normalized address
1036
+ peer.port = handshake_msg.listen_port;
1037
+ peer.normalized_address = normalize_peer_address(peer.ip, peer.port);
1038
+
1039
+ // Add new address mapping
1040
+ address_to_peer_id_[peer.normalized_address] = peer.peer_id;
1041
+
1042
+ LOG_CLIENT_INFO("Updated incoming peer port to listen_port: " << peer.ip << ":" << peer.port);
1043
+ }
1044
+
998
1045
  // Simplified handshake logic - just one message type
999
1046
  if (peer.handshake_state == RatsPeer::HandshakeState::PENDING) {
1000
1047
  // This is an incoming handshake - send our handshake back
1001
1048
  if (send_handshake_unlocked(socket, get_our_peer_id())) {
1002
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1003
- log_handshake_completion_unlocked(peer);
1049
+ // If encryption is enabled, we need to do Noise handshake first
1050
+ // Set NOISE_PENDING to prevent other threads from sending messages
1051
+ if (peer.encryption_enabled) {
1052
+ peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1053
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1054
+ } else {
1055
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1056
+ log_handshake_completion_unlocked(peer);
1057
+ }
1004
1058
 
1005
1059
  // Append to historical peers file after successful connection
1006
1060
  append_peer_to_historical_file(peer);
@@ -1013,8 +1067,15 @@ bool RatsClient::handle_handshake_message(socket_t socket, const std::string& pe
1013
1067
  }
1014
1068
  } else if (peer.handshake_state == RatsPeer::HandshakeState::SENT) {
1015
1069
  // This is a response to our handshake
1016
- peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1017
- log_handshake_completion_unlocked(peer);
1070
+ // If encryption is enabled, we need to do Noise handshake first
1071
+ // Set NOISE_PENDING to prevent other threads from sending messages
1072
+ if (peer.encryption_enabled) {
1073
+ peer.handshake_state = RatsPeer::HandshakeState::NOISE_PENDING;
1074
+ LOG_CLIENT_DEBUG("Rats handshake done, entering NOISE_PENDING state for " << peer_hash_id);
1075
+ } else {
1076
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
1077
+ log_handshake_completion_unlocked(peer);
1078
+ }
1018
1079
 
1019
1080
  // Append to historical peers file after successful connection
1020
1081
  append_peer_to_historical_file(peer);
@@ -1362,12 +1423,12 @@ bool RatsClient::send_binary_to_peer_unlocked(socket_t socket, const std::vector
1362
1423
  LOG_CLIENT_DEBUG("Sending encrypted message to " << peer_id_for_logging << " (" << ct_len << " bytes)");
1363
1424
 
1364
1425
  // Send encrypted message using framed protocol
1365
- int sent = send_tcp_message_framed(socket, ciphertext);
1426
+ int sent = send_tcp_message(socket, ciphertext);
1366
1427
  return sent > 0;
1367
1428
  }
1368
1429
 
1369
1430
  // Unencrypted path - use framed messages for reliable large message handling
1370
- int sent = send_tcp_message_framed(socket, message_with_header);
1431
+ int sent = send_tcp_message(socket, message_with_header);
1371
1432
  return sent > 0;
1372
1433
  }
1373
1434
 
@@ -1922,6 +1983,33 @@ bool RatsClient::is_automatic_discovery_running() const {
1922
1983
  return auto_discovery_running_.load();
1923
1984
  }
1924
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
+
1925
2013
  void RatsClient::automatic_discovery_loop() {
1926
2014
  LOG_CLIENT_INFO("Automatic peer discovery loop started");
1927
2015
 
@@ -1943,12 +2031,12 @@ void RatsClient::automatic_discovery_loop() {
1943
2031
  auto now = std::chrono::steady_clock::now();
1944
2032
 
1945
2033
  // Announce combines both announcing our presence and discovering peers
1946
- // Adjust frequency based on whether we have peers
1947
- auto interval = (get_peer_count() == 0)
1948
- ? std::chrono::seconds(15) // Aggressive when no peers
1949
- : std::chrono::minutes(10); // Less aggressive when connected
2034
+ // Interval scales based on peer count: aggressive when empty, relaxed when nearly full
2035
+ auto interval = calculate_discovery_interval();
1950
2036
 
1951
2037
  if (now - last_announce >= interval) {
2038
+ LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
2039
+ << get_peer_count() << "/" << max_peers_ << ")");
1952
2040
  announce_rats_peer();
1953
2041
  last_announce = now;
1954
2042
  }
@@ -2377,8 +2465,8 @@ nlohmann::json RatsClient::create_peer_exchange_message(const RatsPeer& peer) {
2377
2465
  payload["peer_id"] = peer.peer_id;
2378
2466
  payload["connection_type"] = peer.is_outgoing ? "outgoing" : "incoming";
2379
2467
 
2380
- // Create rats message
2381
- return create_rats_message("peer", payload, peer.peer_id);
2468
+ // Create rats message - use OUR peer_id as sender, not the advertised peer's id
2469
+ return create_rats_message("peer", payload, get_our_peer_id());
2382
2470
  }
2383
2471
 
2384
2472
  void RatsClient::broadcast_peer_exchange_message(const RatsPeer& new_peer) {