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.
@@ -9,6 +9,9 @@
9
9
  #include "file_transfer.h" // File transfer functionality
10
10
  #include "noise.h" // Noise Protocol encryption
11
11
  #include "ice.h" // ICE-lite NAT traversal
12
+ #include "io_poller.h" // Platform-optimal I/O multiplexing
13
+ #include "receive_buffer.h" // Efficient receive buffer for async I/O
14
+ #include "chained_send_buffer.h" // Zero-copy chained send buffer
12
15
  #ifdef RATS_STORAGE
13
16
  #include "storage.h" // Distributed storage functionality
14
17
  #endif
@@ -25,14 +28,32 @@
25
28
  #include <unordered_map>
26
29
  #include <memory>
27
30
  #include <chrono>
28
- #include <condition_variable>
29
- #include <unordered_set> // Added for unordered_set
31
+ #include <unordered_set>
30
32
  #include <cstdint>
31
33
  #include <cstring>
34
+ #include <optional>
32
35
  #include "rats_export.h"
33
36
 
34
37
  namespace librats {
35
38
 
39
+ /**
40
+ * PeerIOContext - Per-peer async I/O buffers and framing state
41
+ *
42
+ * Used by the single-threaded IO loop for non-blocking message framing:
43
+ * recv_buffer – incoming bytes from recv(); frames parsed incrementally
44
+ * send_buffer – outgoing frames queued for non-blocking send()
45
+ * noise_hs – transient Noise XX handshake state (only during NOISE_PENDING)
46
+ * noise_step – current step in the 3-message XX pattern
47
+ */
48
+ struct PeerIOContext {
49
+ ReceiveBuffer recv_buffer{8192};
50
+ ChainedSendBuffer send_buffer;
51
+
52
+ // Async Noise handshake (only valid while handshake_state == NOISE_PENDING)
53
+ std::unique_ptr<rats::NoiseHandshakeState> noise_hs;
54
+ int noise_step = 0; // XX pattern: 0→initial, advances per message
55
+ };
56
+
36
57
  /**
37
58
  * RatsPeer struct - comprehensive information about a connected rats peer
38
59
  */
@@ -65,6 +86,9 @@ struct RatsPeer {
65
86
  std::shared_ptr<rats::NoiseCipherState> recv_cipher; // Cipher for receiving encrypted data
66
87
  std::vector<uint8_t> remote_static_key; // Remote peer's static public key (for identity verification)
67
88
 
89
+ // Async I/O context (per-peer buffers for non-blocking I/O)
90
+ PeerIOContext io_;
91
+
68
92
  RatsPeer() : handshake_state(HandshakeState::PENDING),
69
93
  encryption_enabled(false),
70
94
  noise_handshake_completed(false) {
@@ -83,6 +107,45 @@ struct RatsPeer {
83
107
  handshake_start_time = connected_at;
84
108
  }
85
109
 
110
+ // Custom copy: copies all fields except io_ (non-copyable due to unique_ptr).
111
+ // Copies are used for snapshots passed to callbacks – they never need the IO context.
112
+ RatsPeer(const RatsPeer& o)
113
+ : peer_id(o.peer_id), ip(o.ip), port(o.port), socket(o.socket),
114
+ normalized_address(o.normalized_address), connected_at(o.connected_at),
115
+ is_outgoing(o.is_outgoing), handshake_state(o.handshake_state),
116
+ version(o.version), handshake_start_time(o.handshake_start_time),
117
+ encryption_enabled(o.encryption_enabled),
118
+ noise_handshake_completed(o.noise_handshake_completed),
119
+ send_cipher(o.send_cipher), recv_cipher(o.recv_cipher),
120
+ remote_static_key(o.remote_static_key)
121
+ /* io_ default-constructed (fresh, empty) */ {}
122
+
123
+ RatsPeer& operator=(const RatsPeer& o) {
124
+ if (this != &o) {
125
+ peer_id = o.peer_id;
126
+ ip = o.ip;
127
+ port = o.port;
128
+ socket = o.socket;
129
+ normalized_address = o.normalized_address;
130
+ connected_at = o.connected_at;
131
+ is_outgoing = o.is_outgoing;
132
+ handshake_state = o.handshake_state;
133
+ version = o.version;
134
+ handshake_start_time = o.handshake_start_time;
135
+ encryption_enabled = o.encryption_enabled;
136
+ noise_handshake_completed = o.noise_handshake_completed;
137
+ send_cipher = o.send_cipher;
138
+ recv_cipher = o.recv_cipher;
139
+ remote_static_key = o.remote_static_key;
140
+ // io_ left unchanged in the destination (no copy)
141
+ }
142
+ return *this;
143
+ }
144
+
145
+ // Default move operations are fine
146
+ RatsPeer(RatsPeer&&) = default;
147
+ RatsPeer& operator=(RatsPeer&&) = default;
148
+
86
149
  // Check if peer has completed Noise handshake and is ready for encrypted communication
87
150
  bool is_noise_encrypted() const {
88
151
  return noise_handshake_completed && send_cipher && recv_cipher;
@@ -261,7 +324,6 @@ public:
261
324
  */
262
325
  bool is_running() const;
263
326
 
264
-
265
327
  // =========================================================================
266
328
  // Utility Methods
267
329
  // =========================================================================
@@ -387,7 +449,6 @@ public:
387
449
  */
388
450
  int get_peer_count() const;
389
451
 
390
-
391
452
  /**
392
453
  * Get peer_id for a peer by socket (preferred)
393
454
  * @param socket Peer socket
@@ -423,16 +484,16 @@ public:
423
484
  /**
424
485
  * Get peer information by peer ID
425
486
  * @param peer_id The peer ID to look up
426
- * @return Pointer to RatsPeer object, or nullptr if not found
487
+ * @return Copy of RatsPeer object, or std::nullopt if not found
427
488
  */
428
- const RatsPeer* get_peer_by_id(const std::string& peer_id) const;
489
+ std::optional<RatsPeer> get_peer_by_id(const std::string& peer_id) const;
429
490
 
430
491
  /**
431
492
  * Get peer information by socket
432
493
  * @param socket The socket handle to look up
433
- * @return Pointer to RatsPeer object, or nullptr if not found
494
+ * @return Copy of RatsPeer object, or std::nullopt if not found
434
495
  */
435
- const RatsPeer* get_peer_by_socket(socket_t socket) const;
496
+ std::optional<RatsPeer> get_peer_by_socket(socket_t socket) const;
436
497
 
437
498
  /**
438
499
  * Get maximum number of peers
@@ -476,9 +537,9 @@ public:
476
537
 
477
538
  /**
478
539
  * Get current reconnection configuration
479
- * @return Current reconnection configuration
540
+ * @return Copy of current reconnection configuration
480
541
  */
481
- const ReconnectConfig& get_reconnect_config() const;
542
+ ReconnectConfig get_reconnect_config() const;
482
543
 
483
544
  /**
484
545
  * Get the number of peers pending reconnection
@@ -730,14 +791,6 @@ public:
730
791
  */
731
792
  void send(const std::string& peer_id, const std::string& message_type, const nlohmann::json& data, SendCallback callback = nullptr);
732
793
 
733
- /**
734
- * Parse a JSON message
735
- * @param message Raw message string
736
- * @param out_json Parsed JSON output
737
- * @return true if parsed successfully
738
- */
739
- bool parse_json_message(const std::string& message, nlohmann::json& out_json);
740
-
741
794
  // =========================================================================
742
795
  // Encryption Functionality
743
796
  // =========================================================================
@@ -1254,7 +1307,7 @@ public:
1254
1307
  * Get current file transfer configuration
1255
1308
  * @return Current configuration settings
1256
1309
  */
1257
- const FileTransferConfig& get_file_transfer_config() const;
1310
+ FileTransferConfig get_file_transfer_config() const;
1258
1311
 
1259
1312
  // Event Handlers
1260
1313
  /**
@@ -1463,7 +1516,7 @@ public:
1463
1516
  * Get current ICE configuration
1464
1517
  * @return Current ICE configuration
1465
1518
  */
1466
- const IceConfig& get_ice_config() const;
1519
+ IceConfig get_ice_config() const;
1467
1520
 
1468
1521
  // ICE Lifecycle
1469
1522
  /**
@@ -1959,7 +2012,7 @@ private:
1959
2012
  // 3. encryption_mutex_ (Encryption settings and keys)
1960
2013
  // 4. local_addresses_mutex_ (Local interface addresses)
1961
2014
  // 5. peers_mutex_ (Peer management - most frequently locked)
1962
- // 6. socket_send_mutexes_mutex_ (Socket send mutex management)
2015
+ // 6. io_mutex_ (I/O poller and send buffer access)
1963
2016
  // 7. message_handlers_mutex_ (Message handler registration)
1964
2017
  // 8. reconnect_mutex_ (Reconnection queue management)
1965
2018
  // =========================================================================
@@ -1985,20 +2038,19 @@ private:
1985
2038
 
1986
2039
  // [4] Local interface address blocking (protected by local_addresses_mutex_)
1987
2040
  mutable std::mutex local_addresses_mutex_; // [4] Protects local interface addresses
1988
- std::vector<std::string> local_interface_addresses_;
2041
+ std::unordered_set<std::string> local_interface_addresses_;
1989
2042
 
1990
2043
  // [5] Organized peer management using RatsPeer struct (protected by peers_mutex_)
1991
2044
  mutable std::mutex peers_mutex_; // [5] Protects peer data (most frequently locked)
1992
2045
  std::unordered_map<std::string, RatsPeer> peers_; // keyed by peer_id
1993
2046
  std::unordered_map<socket_t, std::string> socket_to_peer_id_; // for quick socket->peer_id lookup
1994
2047
  std::unordered_map<std::string, std::string> address_to_peer_id_; // for duplicate detection (normalized_address->peer_id)
2048
+ std::atomic<int> validated_peer_count_{0}; // Cached count of peers with COMPLETED handshake
1995
2049
 
1996
- // [6] Per-socket synchronization for thread-safe message sending (protected by socket_send_mutexes_mutex_)
1997
- mutable std::mutex socket_send_mutexes_mutex_; // [6] Protects socket send mutex map
1998
- std::unordered_map<socket_t, std::shared_ptr<std::mutex>> socket_send_mutexes_;
1999
-
2000
- // Server and client management
2001
- std::thread server_thread_;
2050
+ // [6] Async I/O (poller + io thread)
2051
+ std::unique_ptr<IOPoller> poller_;
2052
+ std::mutex io_mutex_; // Protects poller_ and send-buffer writes from non-IO threads
2053
+ std::thread io_thread_;
2002
2054
  std::thread management_thread_;
2003
2055
 
2004
2056
  ConnectionCallback connection_callback_;
@@ -2036,11 +2088,31 @@ private:
2036
2088
  void initialize_modules();
2037
2089
  void destroy_modules();
2038
2090
 
2039
- void server_loop();
2091
+ // Async I/O loop (single thread for all sockets)
2092
+ void io_loop();
2040
2093
  void management_loop();
2041
- void handle_client(socket_t client_socket, const std::string& peer_hash_id);
2094
+
2095
+ // I/O event handlers (called from io_loop)
2096
+ void accept_incoming();
2097
+ bool handle_readable(socket_t socket);
2098
+ bool handle_writable(socket_t socket);
2099
+ void handle_disconnect(socket_t socket);
2100
+
2101
+ // Poller registration helpers
2102
+ void poller_add(socket_t fd, uint32_t events);
2103
+ void poller_modify(socket_t fd, uint32_t events);
2104
+ void poller_remove(socket_t fd);
2105
+
2106
+ // Helpers – post-handshake and message routing
2107
+ void handle_post_handshake_completion(socket_t socket, const RatsPeer& peer_copy);
2108
+ void process_message(socket_t socket, const std::vector<uint8_t>& data, const std::string& peer_id);
2109
+
2110
+ // Peer lookup helper (assumes peers_mutex_ is already locked)
2111
+ std::unordered_map<std::string, RatsPeer>::iterator find_peer_by_socket_unlocked(socket_t socket);
2112
+ std::unordered_map<std::string, RatsPeer>::const_iterator find_peer_by_socket_unlocked(socket_t socket) const;
2113
+
2042
2114
  void remove_peer(socket_t socket);
2043
- std::string generate_peer_hash_id(socket_t socket, const std::string& connection_info);
2115
+ std::string generate_temporary_peer_id(socket_t socket, const std::string& connection_info);
2044
2116
  void handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash);
2045
2117
  void handle_mdns_service_discovery(const MdnsService& service, bool is_new);
2046
2118
 
@@ -2049,31 +2121,62 @@ private:
2049
2121
  bool parse_message_with_header(const std::vector<uint8_t>& message, MessageHeader& header, std::vector<uint8_t>& payload) const;
2050
2122
 
2051
2123
  // Peer management methods using RatsPeer
2052
- void add_peer(const RatsPeer& peer);
2053
2124
  void add_peer_unlocked(const RatsPeer& peer); // Assumes peers_mutex_ is already locked
2054
- void remove_peer_by_id(const std::string& peer_id);
2055
2125
  void remove_peer_by_id_unlocked(const std::string& peer_id); // Assumes peers_mutex_ is already locked
2126
+ void mark_manual_disconnect(const std::string& peer_id);
2127
+ static std::vector<uint8_t> json_to_binary(const nlohmann::json& data);
2056
2128
  bool is_already_connected_to_address(const std::string& normalized_address) const;
2057
2129
  std::string normalize_peer_address(const std::string& ip, int port) const;
2058
2130
 
2059
- // Data transmission helper - assumes peers_mutex_ is already locked or peer data is cached
2131
+ // Async send enqueues a framed (length-prefixed) message into the peer's
2132
+ // ChainedSendBuffer and arms PollOut. Thread-safe (acquires io_mutex_).
2133
+ // Returns false if the peer was not found.
2134
+ bool enqueue_message(socket_t socket, const std::vector<uint8_t>& data);
2135
+ // Unlocked variant – caller must hold peers_mutex_
2136
+ bool enqueue_message_unlocked(RatsPeer& peer, const std::vector<uint8_t>& data);
2137
+
2138
+ // Lightweight snapshot of peer data needed for sending (avoids holding peers_mutex_ during enqueue)
2139
+ struct PeerSendTarget {
2140
+ socket_t socket;
2141
+ std::string peer_id;
2142
+ std::shared_ptr<rats::NoiseCipherState> send_cipher;
2143
+ };
2144
+
2060
2145
  bool send_binary_to_peer_unlocked(socket_t socket, const std::vector<uint8_t>& data,
2061
2146
  MessageDataType message_type,
2062
- rats::NoiseCipherState* send_cipher,
2147
+ std::shared_ptr<rats::NoiseCipherState> send_cipher,
2063
2148
  const std::string& peer_id_for_logging);
2149
+
2150
+ // Async Noise handshake – processes one Noise XX message received from peer.
2151
+ // Returns false if peer should be disconnected. Called from handle_readable.
2152
+ bool handle_noise_frame(RatsPeer& peer);
2153
+ // Kick-off: initialises noise_hs and writes first outgoing message if initiator.
2154
+ void start_noise_handshake_async(RatsPeer& peer);
2064
2155
 
2065
2156
  // Local interface address blocking helper functions
2066
2157
  void initialize_local_addresses();
2067
2158
  bool is_blocked_address(const std::string& ip_address) const;
2068
2159
  bool should_ignore_peer(const std::string& ip, int port) const;
2160
+ bool can_connect_to_peer(const std::string& ip, int port) const;
2069
2161
  static bool parse_address_string(const std::string& address_str, std::string& out_ip, int& out_port);
2070
2162
 
2071
2163
  // Helper functions that assume mutex is already locked
2072
2164
  int get_peer_count_unlocked() const; // Helper that assumes peers_mutex_ is already locked
2073
2165
 
2074
- // Handshake protocol
2166
+ // Protocol constants
2075
2167
  static constexpr const char* RATS_PROTOCOL_VERSION = "1.0";
2076
2168
  static constexpr int HANDSHAKE_TIMEOUT_SECONDS = 10;
2169
+ static constexpr int TCP_CONNECT_TIMEOUT_MS = 10000; // 10 second TCP connection timeout
2170
+ static constexpr int IO_POLL_TIMEOUT_MS = 100; // IO poller tick interval (ms)
2171
+ static constexpr size_t MAX_FRAME_SIZE = 100 * 1024 * 1024; // 100 MB max single frame
2172
+ static constexpr int PEER_RECONNECT_DELAY_MS = 100; // Delay before reconnecting saved peers
2173
+ static constexpr int HISTORICAL_RECONNECT_DELAY_MS = 500; // Delay before reconnecting historical peers
2174
+ static constexpr int MANAGEMENT_LOOP_INTERVAL_SECONDS = 2; // Management loop tick interval
2175
+ static constexpr int THREAD_CLEANUP_INTERVAL_SECONDS = 30; // Thread cleanup interval
2176
+ static constexpr int INITIAL_DISCOVERY_DELAY_SECONDS = 5; // DHT bootstrap delay
2177
+ static constexpr int MAX_PEERS_REQUEST_COUNT = 5; // Max peers to request/respond
2178
+ static constexpr int CONTENT_HASH_HEX_LENGTH = 40; // 160-bit hash as hex
2179
+ static constexpr int64_t TIMESTAMP_SKEW_TOLERANCE_MS = 10LL * 60LL * 1000LL; // 10 minutes
2077
2180
 
2078
2181
  struct HandshakeMessage {
2079
2182
  std::string protocol;
@@ -2089,9 +2192,8 @@ private:
2089
2192
  bool parse_handshake_message(const std::vector<uint8_t>& data, HandshakeMessage& out_msg) const;
2090
2193
  bool validate_handshake_message(const HandshakeMessage& msg) const;
2091
2194
  bool is_handshake_message(const std::vector<uint8_t>& data) const;
2092
- bool send_handshake(socket_t socket, const std::string& our_peer_id);
2093
- bool send_handshake_unlocked(socket_t socket, const std::string& our_peer_id);
2094
- bool handle_handshake_message(socket_t socket, const std::string& peer_hash_id, const std::vector<uint8_t>& data);
2195
+ bool send_handshake_unlocked(RatsPeer& peer, const std::string& our_peer_id); // enqueues via send buffer (peers_mutex_ held)
2196
+ bool handle_handshake_message(socket_t socket, const std::string& initial_peer_id, const std::vector<uint8_t>& data);
2095
2197
  void check_handshake_timeouts();
2096
2198
  void log_handshake_completion_unlocked(const RatsPeer& peer);
2097
2199
 
@@ -2103,12 +2205,12 @@ private:
2103
2205
 
2104
2206
  // Message handling system
2105
2207
  nlohmann::json create_rats_message(const std::string& type, const nlohmann::json& payload, const std::string& sender_peer_id);
2106
- void handle_rats_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& message);
2208
+ void handle_rats_message(socket_t socket, const std::string& peer_id, const nlohmann::json& message);
2107
2209
 
2108
2210
  // Specific message handlers
2109
- void handle_peer_exchange_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload);
2110
- void handle_peers_request_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload);
2111
- void handle_peers_response_message(socket_t socket, const std::string& peer_hash_id, const nlohmann::json& payload);
2211
+ void handle_peer_exchange_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload);
2212
+ void handle_peers_request_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload);
2213
+ void handle_peers_response_message(socket_t socket, const std::string& peer_id, const nlohmann::json& payload);
2112
2214
 
2113
2215
  // Message creation and broadcasting
2114
2216
  nlohmann::json create_peer_exchange_message(const RatsPeer& peer);
@@ -2118,8 +2220,7 @@ private:
2118
2220
  std::vector<RatsPeer> get_random_peers(int max_count, const std::string& exclude_peer_id = "") const;
2119
2221
  void send_peers_request(socket_t socket, const std::string& our_peer_id);
2120
2222
 
2121
- int broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id = "");
2122
- int broadcast_rats_message_to_validated_peers(const nlohmann::json& message, const std::string& exclude_peer_id = "");
2223
+ int broadcast_rats_message(const nlohmann::json& message, const std::string& exclude_peer_id = "", bool validated_only = true);
2123
2224
 
2124
2225
  // [7] Message exchange API implementation (protected by message_handlers_mutex_)
2125
2226
  mutable std::mutex message_handlers_mutex_; // [7] Protects message handlers
@@ -2145,10 +2246,6 @@ private:
2145
2246
  void remove_from_reconnect_queue(const std::string& peer_id);
2146
2247
  int get_retry_interval_seconds(int attempt, bool is_stable) const;
2147
2248
 
2148
- // Per-socket synchronization helpers
2149
- std::shared_ptr<std::mutex> get_socket_send_mutex(socket_t socket);
2150
- void cleanup_socket_send_mutex(socket_t socket);
2151
-
2152
2249
  // Configuration persistence helpers
2153
2250
  std::string generate_persistent_peer_id() const;
2154
2251
  nlohmann::json serialize_peer_for_persistence(const RatsPeer& peer) const;
@@ -2162,11 +2259,6 @@ private:
2162
2259
 
2163
2260
  // Noise Protocol encryption helpers
2164
2261
  void initialize_noise_keypair();
2165
- bool perform_noise_handshake(socket_t socket, const std::string& peer_id, bool is_initiator);
2166
- bool send_noise_message(socket_t socket, const uint8_t* data, size_t len);
2167
- bool recv_noise_message(socket_t socket, std::vector<uint8_t>& out_data, int timeout_ms = 10000);
2168
- bool encrypt_and_send(socket_t socket, const std::string& peer_id, const std::vector<uint8_t>& plaintext);
2169
- bool receive_and_decrypt(socket_t socket, const std::string& peer_id, std::vector<uint8_t>& plaintext);
2170
2262
  };
2171
2263
 
2172
2264
  // Utility functions
@@ -4,11 +4,7 @@
4
4
 
5
5
  #ifdef RATS_SEARCH_FEATURES
6
6
 
7
- // Logging macros for BitTorrent client
8
- #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
9
- #define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
10
- #define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
11
- #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
7
+ #include "librats_log_macros.h"
12
8
 
13
9
  namespace librats {
14
10
 
@@ -847,7 +847,7 @@ char* rats_get_peer_info_json(rats_client_t handle, const char* peer_id) {
847
847
  if (!handle || !peer_id) return nullptr;
848
848
  rats_client_wrapper* wrap = static_cast<rats_client_wrapper*>(handle);
849
849
 
850
- const RatsPeer* peer = wrap->client->get_peer_by_id(std::string(peer_id));
850
+ auto peer = wrap->client->get_peer_by_id(std::string(peer_id));
851
851
  if (!peer) return nullptr;
852
852
 
853
853
  // Create JSON representation of peer info