librats 0.7.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +9 -4
  2. package/lib/index.d.ts +9 -22
  3. package/native-src/CMakeLists.txt +132 -50
  4. package/native-src/cmake/ratsConfig.cmake.in +8 -0
  5. package/native-src/src/bt_network.cpp +296 -183
  6. package/native-src/src/bt_network.h +25 -5
  7. package/native-src/src/crypto/sha256.c +1 -1
  8. package/native-src/src/dht.cpp +297 -47
  9. package/native-src/src/dht.h +70 -6
  10. package/native-src/src/file_transfer.cpp +1185 -1578
  11. package/native-src/src/file_transfer.h +240 -521
  12. package/native-src/src/io_poller.cpp +917 -0
  13. package/native-src/src/io_poller.h +138 -0
  14. package/native-src/src/krpc.cpp +161 -96
  15. package/native-src/src/krpc.h +18 -4
  16. package/native-src/src/librats.cpp +812 -1236
  17. package/native-src/src/librats.h +207 -208
  18. package/native-src/src/librats_bittorrent.cpp +1 -5
  19. package/native-src/src/librats_c.cpp +22 -39
  20. package/native-src/src/librats_discovery.cpp +377 -0
  21. package/native-src/src/librats_encryption.cpp +130 -283
  22. package/native-src/src/librats_file_transfer.cpp +27 -109
  23. package/native-src/src/librats_gossipsub.cpp +1 -5
  24. package/native-src/src/librats_ice.cpp +5 -1
  25. package/native-src/src/librats_log_macros.h +36 -0
  26. package/native-src/src/librats_logging.cpp +1 -7
  27. package/native-src/src/librats_mdns.cpp +1 -11
  28. package/native-src/src/librats_persistence.cpp +1 -11
  29. package/native-src/src/librats_reconnection.cpp +2 -13
  30. package/native-src/src/librats_statistic.cpp +105 -0
  31. package/native-src/src/socket.cpp +15 -3
  32. package/package.json +1 -1
  33. package/scripts/build-librats.js +3 -0
  34. package/scripts/prepare-package.js +10 -0
  35. package/src/librats_node.cpp +53 -64
@@ -1,10 +1,5 @@
1
1
  #include "librats.h"
2
- #include "logger.h"
3
-
4
- #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
5
- #define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
6
- #define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
7
- #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
2
+ #include "librats_log_macros.h"
8
3
 
9
4
  namespace librats {
10
5
 
@@ -102,73 +97,28 @@ std::vector<uint8_t> RatsClient::get_peer_handshake_hash(const std::string& peer
102
97
  return std::vector<uint8_t>();
103
98
  }
104
99
 
105
- bool RatsClient::send_noise_message(socket_t socket, const uint8_t* data, size_t len) {
106
- // Get socket-specific mutex for thread-safe sending
107
- // This prevents race conditions with other threads sending rats protocol messages
108
- auto socket_mutex = get_socket_send_mutex(socket);
109
- std::lock_guard<std::mutex> send_lock(*socket_mutex);
110
-
111
- // Send length-prefixed message for Noise handshake
112
- uint32_t network_len = htonl(static_cast<uint32_t>(len));
113
-
114
- // Send length
115
- int sent = ::send(socket, reinterpret_cast<const char*>(&network_len), 4, 0);
116
- if (sent != 4) {
117
- LOG_CLIENT_ERROR("Failed to send Noise message length");
118
- return false;
119
- }
120
-
121
- // Send data
122
- sent = ::send(socket, reinterpret_cast<const char*>(data), static_cast<int>(len), 0);
123
- if (sent != static_cast<int>(len)) {
124
- LOG_CLIENT_ERROR("Failed to send Noise message data");
125
- return false;
126
- }
127
-
128
- return true;
129
- }
100
+ // =========================================================================
101
+ // Async Noise Handshake (non-blocking, driven by io_loop)
102
+ // =========================================================================
130
103
 
131
- bool RatsClient::recv_noise_message(socket_t socket, std::vector<uint8_t>& out_data, int timeout_ms) {
132
- // Set socket timeout (platform-specific)
133
- #ifdef _WIN32
134
- // Windows: SO_RCVTIMEO expects DWORD (milliseconds)
135
- DWORD tv = static_cast<DWORD>(timeout_ms);
136
- setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&tv), sizeof(tv));
137
- #else
138
- // Unix: SO_RCVTIMEO expects struct timeval
139
- struct timeval tv;
140
- tv.tv_sec = timeout_ms / 1000;
141
- tv.tv_usec = (timeout_ms % 1000) * 1000;
142
- setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&tv), sizeof(tv));
143
- #endif
144
-
145
- // Receive length
146
- uint32_t network_len;
147
- int received = recv(socket, reinterpret_cast<char*>(&network_len), 4, MSG_WAITALL);
148
- if (received != 4) {
149
- LOG_CLIENT_ERROR("Failed to receive Noise message length");
150
- return false;
151
- }
152
-
153
- uint32_t len = ntohl(network_len);
154
- if (len > rats::NOISE_MAX_MESSAGE_SIZE) {
155
- LOG_CLIENT_ERROR("Noise message too large: " << len);
156
- return false;
157
- }
158
-
159
- // Receive data
160
- out_data.resize(len);
161
- received = recv(socket, reinterpret_cast<char*>(out_data.data()), static_cast<int>(len), MSG_WAITALL);
162
- if (received != static_cast<int>(len)) {
163
- LOG_CLIENT_ERROR("Failed to receive Noise message data");
164
- return false;
165
- }
166
-
167
- return true;
168
- }
104
+ /*
105
+ * Noise XX pattern (3 messages):
106
+ * Initiator: write msg0 → read msg1 → write msg2 → split
107
+ * Responder: read msg0 → write msg1 → read msg2 → split
108
+ *
109
+ * noise_step tracks where we are in this sequence:
110
+ * Initiator: 0 = need write, 1 = need read, 2 = need write, 3 = done
111
+ * Responder: 0 = need read, 1 = need write, 2 = need read, 3 = done
112
+ *
113
+ * start_noise_handshake_async() initialises noise_hs and writes the first
114
+ * outgoing message (if initiator). Subsequent messages are processed by
115
+ * handle_noise_frame() which is called from handle_readable when
116
+ * handshake_state == NOISE_PENDING.
117
+ */
169
118
 
170
- bool RatsClient::perform_noise_handshake(socket_t socket, const std::string& peer_id, bool is_initiator) {
171
- LOG_CLIENT_INFO("Starting Noise handshake with " << peer_id << " (initiator: " << is_initiator << ")");
119
+ void RatsClient::start_noise_handshake_async(RatsPeer& peer) {
120
+ LOG_CLIENT_INFO("Starting async Noise handshake for " << peer.peer_id
121
+ << " (initiator: " << peer.is_outgoing << ")");
172
122
 
173
123
  // Get our static keypair
174
124
  rats::NoiseKeyPair static_keypair;
@@ -183,245 +133,142 @@ bool RatsClient::perform_noise_handshake(socket_t socket, const std::string& pee
183
133
  static_keypair.has_keys = true;
184
134
  }
185
135
 
186
- // Initialize handshake state
187
- rats::NoiseHandshakeState handshake;
188
- rats::NoiseError err = handshake.initialize(is_initiator, &static_keypair);
136
+ // Allocate handshake state on the peer's IO context
137
+ peer.io_.noise_hs = std::make_unique<rats::NoiseHandshakeState>();
138
+ peer.io_.noise_step = 0;
139
+
140
+ auto err = peer.io_.noise_hs->initialize(peer.is_outgoing, &static_keypair);
189
141
  if (err != rats::NoiseError::OK) {
190
- LOG_CLIENT_ERROR("Failed to initialize Noise handshake: " << static_cast<int>(err));
191
- return false;
142
+ LOG_CLIENT_ERROR("Failed to initialise Noise handshake: " << static_cast<int>(err));
143
+ peer.handshake_state = RatsPeer::HandshakeState::FAILED;
144
+ return;
192
145
  }
193
146
 
194
- uint8_t message_buf[256];
195
- size_t message_len;
196
- std::vector<uint8_t> received_data;
197
- uint8_t payload_buf[256];
198
- size_t payload_len;
199
-
200
- /*
201
- * XX Pattern:
202
- * -> e (message 0, initiator sends)
203
- * <- e, ee, s, es (message 1, responder sends)
204
- * -> s, se (message 2, initiator sends)
205
- */
206
-
207
- if (is_initiator) {
208
- // Message 0: -> e
209
- message_len = sizeof(message_buf);
210
- err = handshake.write_message(nullptr, 0, message_buf, &message_len);
211
- if (err != rats::NoiseError::OK) {
212
- LOG_CLIENT_ERROR("Failed to write Noise message 0");
213
- return false;
214
- }
215
-
216
- if (!send_noise_message(socket, message_buf, message_len)) {
217
- return false;
218
- }
219
- LOG_CLIENT_DEBUG("Sent Noise message 0 (e) - " << message_len << " bytes");
220
-
221
- // Receive message 1: <- e, ee, s, es
222
- if (!recv_noise_message(socket, received_data)) {
223
- return false;
224
- }
225
- LOG_CLIENT_DEBUG("Received Noise message 1 - " << received_data.size() << " bytes");
226
-
227
- payload_len = sizeof(payload_buf);
228
- err = handshake.read_message(received_data.data(), received_data.size(), payload_buf, &payload_len);
229
- if (err != rats::NoiseError::OK) {
230
- LOG_CLIENT_ERROR("Failed to read Noise message 1: " << static_cast<int>(err));
231
- return false;
232
- }
233
-
234
- // Message 2: -> s, se
235
- message_len = sizeof(message_buf);
236
- err = handshake.write_message(nullptr, 0, message_buf, &message_len);
147
+ // Initiator sends message 0 immediately
148
+ if (peer.is_outgoing) {
149
+ uint8_t msg_buf[256];
150
+ size_t msg_len = sizeof(msg_buf);
151
+ err = peer.io_.noise_hs->write_message(nullptr, 0, msg_buf, &msg_len);
237
152
  if (err != rats::NoiseError::OK) {
238
- LOG_CLIENT_ERROR("Failed to write Noise message 2");
239
- return false;
153
+ LOG_CLIENT_ERROR("Failed to write Noise msg 0: " << static_cast<int>(err));
154
+ peer.handshake_state = RatsPeer::HandshakeState::FAILED;
155
+ return;
240
156
  }
241
157
 
242
- if (!send_noise_message(socket, message_buf, message_len)) {
243
- return false;
244
- }
245
- LOG_CLIENT_DEBUG("Sent Noise message 2 (s, se) - " << message_len << " bytes");
246
-
247
- } else {
248
- // Responder
249
-
250
- // Receive message 0: -> e
251
- if (!recv_noise_message(socket, received_data)) {
252
- return false;
253
- }
254
- LOG_CLIENT_DEBUG("Received Noise message 0 - " << received_data.size() << " bytes");
255
-
256
- payload_len = sizeof(payload_buf);
257
- err = handshake.read_message(received_data.data(), received_data.size(), payload_buf, &payload_len);
258
- if (err != rats::NoiseError::OK) {
259
- LOG_CLIENT_ERROR("Failed to read Noise message 0: " << static_cast<int>(err));
260
- return false;
261
- }
262
-
263
- // Message 1: <- e, ee, s, es
264
- message_len = sizeof(message_buf);
265
- err = handshake.write_message(nullptr, 0, message_buf, &message_len);
266
- if (err != rats::NoiseError::OK) {
267
- LOG_CLIENT_ERROR("Failed to write Noise message 1");
268
- return false;
269
- }
270
-
271
- if (!send_noise_message(socket, message_buf, message_len)) {
272
- return false;
273
- }
274
- LOG_CLIENT_DEBUG("Sent Noise message 1 (e, ee, s, es) - " << message_len << " bytes");
275
-
276
- // Receive message 2: -> s, se
277
- if (!recv_noise_message(socket, received_data)) {
278
- return false;
279
- }
280
- LOG_CLIENT_DEBUG("Received Noise message 2 - " << received_data.size() << " bytes");
281
-
282
- payload_len = sizeof(payload_buf);
283
- err = handshake.read_message(received_data.data(), received_data.size(), payload_buf, &payload_len);
284
- if (err != rats::NoiseError::OK) {
285
- LOG_CLIENT_ERROR("Failed to read Noise message 2: " << static_cast<int>(err));
286
- return false;
287
- }
158
+ // Enqueue as a length-prefixed frame (same framing as data messages)
159
+ std::vector<uint8_t> payload(msg_buf, msg_buf + msg_len);
160
+ enqueue_message_unlocked(peer, payload);
161
+ peer.io_.noise_step = 1; // next: need to read msg 1
162
+ LOG_CLIENT_DEBUG("Sent Noise msg 0 (" << msg_len << " bytes) to " << peer.peer_id);
288
163
  }
164
+ // Responder waits for msg 0 from initiator (noise_step stays 0)
165
+ }
166
+
167
+ bool RatsClient::handle_noise_frame(RatsPeer& peer) {
168
+ // The caller (handle_readable) has already verified that a complete
169
+ // length-prefixed frame is available in the receive buffer, but hasn't
170
+ // consumed it yet. We read the payload from recv_buf (skip 4-byte length).
171
+ auto& recv_buf = peer.io_.recv_buffer;
289
172
 
290
- // Handshake should be complete now
291
- if (!handshake.is_handshake_complete()) {
292
- LOG_CLIENT_ERROR("Noise handshake not complete after all messages");
173
+ // Read payload length
174
+ uint32_t net_len;
175
+ memcpy(&net_len, recv_buf.data(), 4);
176
+ uint32_t frame_len = ntohl(net_len);
177
+
178
+ const uint8_t* frame_data = recv_buf.data() + 4;
179
+
180
+ if (!peer.io_.noise_hs) {
181
+ LOG_CLIENT_ERROR("Noise frame received but no handshake state for " << peer.peer_id);
293
182
  return false;
294
183
  }
295
184
 
296
- // Split into transport ciphers
297
- auto send_cipher = std::make_shared<rats::NoiseCipherState>();
298
- auto recv_cipher = std::make_shared<rats::NoiseCipherState>();
185
+ auto& hs = *peer.io_.noise_hs;
186
+ bool is_initiator = peer.is_outgoing;
187
+ rats::NoiseError err;
188
+ uint8_t out_buf[256];
189
+ size_t out_len;
299
190
 
300
- err = handshake.split(*send_cipher, *recv_cipher);
191
+ /*
192
+ * Step table (noise_step → action):
193
+ * Initiator: 0=write, 1=read, 2=write, 3=done
194
+ * Responder: 0=read, 1=write, 2=read, 3=done
195
+ *
196
+ * This function is only called when we received a frame, so the current
197
+ * step must expect a "read".
198
+ */
199
+
200
+ // ── Read the incoming Noise message ──
201
+ out_len = sizeof(out_buf);
202
+ err = hs.read_message(frame_data, frame_len, out_buf, &out_len);
301
203
  if (err != rats::NoiseError::OK) {
302
- LOG_CLIENT_ERROR("Failed to split Noise session: " << static_cast<int>(err));
204
+ LOG_CLIENT_ERROR("Noise read_message failed (step " << peer.io_.noise_step
205
+ << "): " << static_cast<int>(err));
303
206
  return false;
304
207
  }
208
+ LOG_CLIENT_DEBUG("Read Noise msg (step " << peer.io_.noise_step
209
+ << ", " << frame_len << " bytes) from " << peer.peer_id);
305
210
 
306
- // Store ciphers and remote static key in peer
307
- {
308
- std::lock_guard<std::mutex> lock(peers_mutex_);
309
- auto it = peers_.find(peer_id);
310
- if (it == peers_.end()) {
311
- // Try to find by socket
312
- auto sock_it = socket_to_peer_id_.find(socket);
313
- if (sock_it != socket_to_peer_id_.end()) {
314
- it = peers_.find(sock_it->second);
315
- }
316
- }
317
-
318
- if (it != peers_.end()) {
319
- it->second.send_cipher = std::move(send_cipher);
320
- it->second.recv_cipher = std::move(recv_cipher);
321
- it->second.noise_handshake_completed = true;
322
-
323
- // Store remote static key
324
- const uint8_t* remote_key = handshake.get_remote_static_public();
325
- if (handshake.has_remote_static()) {
326
- it->second.remote_static_key.assign(remote_key, remote_key + 32);
327
- }
328
-
329
- LOG_CLIENT_INFO("Noise handshake completed with " << it->second.peer_id);
330
- } else {
331
- LOG_CLIENT_ERROR("Peer not found after Noise handshake: " << peer_id);
332
- return false;
333
- }
334
- }
335
-
336
- // Reset socket timeout to infinite (0) for normal operation after handshake
337
- #ifdef _WIN32
338
- DWORD no_timeout = 0;
339
- setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&no_timeout), sizeof(no_timeout));
340
- #else
341
- struct timeval no_timeout;
342
- no_timeout.tv_sec = 0;
343
- no_timeout.tv_usec = 0;
344
- setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&no_timeout), sizeof(no_timeout));
345
- #endif
211
+ peer.io_.noise_step++;
346
212
 
347
- return true;
348
- }
349
-
350
- bool RatsClient::encrypt_and_send(socket_t socket, const std::string& peer_id, const std::vector<uint8_t>& plaintext) {
351
- rats::NoiseCipherState* cipher = nullptr;
213
+ // ── If there's a reply to write, do it now ──
214
+ bool need_write = false;
215
+ if (is_initiator) {
216
+ // Initiator writes on steps 0 and 2 → after reading (step becomes 2), write
217
+ need_write = (peer.io_.noise_step == 2);
218
+ } else {
219
+ // Responder writes on step 1 → after reading (step becomes 1), write
220
+ need_write = (peer.io_.noise_step == 1);
221
+ }
352
222
 
353
- {
354
- std::lock_guard<std::mutex> lock(peers_mutex_);
355
- auto it = peers_.find(peer_id);
356
- if (it == peers_.end()) {
357
- auto sock_it = socket_to_peer_id_.find(socket);
358
- if (sock_it != socket_to_peer_id_.end()) {
359
- it = peers_.find(sock_it->second);
360
- }
223
+ if (need_write) {
224
+ out_len = sizeof(out_buf);
225
+ err = hs.write_message(nullptr, 0, out_buf, &out_len);
226
+ if (err != rats::NoiseError::OK) {
227
+ LOG_CLIENT_ERROR("Noise write_message failed (step " << peer.io_.noise_step
228
+ << "): " << static_cast<int>(err));
229
+ return false;
361
230
  }
362
231
 
363
- if (it != peers_.end() && it->second.send_cipher) {
364
- cipher = it->second.send_cipher.get();
365
- }
366
- }
367
-
368
- if (!cipher) {
369
- LOG_CLIENT_ERROR("No send cipher for peer: " << peer_id);
370
- return false;
232
+ std::vector<uint8_t> payload(out_buf, out_buf + out_len);
233
+ enqueue_message_unlocked(peer, payload);
234
+ LOG_CLIENT_DEBUG("Sent Noise msg (step " << peer.io_.noise_step
235
+ << ", " << out_len << " bytes) to " << peer.peer_id);
236
+ peer.io_.noise_step++;
371
237
  }
372
238
 
373
- // Encrypt
374
- std::vector<uint8_t> ciphertext(plaintext.size() + rats::NOISE_TAG_SIZE);
375
- size_t ct_len = cipher->encrypt_with_ad(nullptr, 0, plaintext.data(), plaintext.size(), ciphertext.data());
376
- if (ct_len == 0) {
377
- LOG_CLIENT_ERROR("Encryption failed for peer: " << peer_id);
378
- return false;
379
- }
380
-
381
- ciphertext.resize(ct_len);
382
-
383
- // Send as framed message
384
- return send_noise_message(socket, ciphertext.data(), ciphertext.size());
385
- }
386
-
387
- bool RatsClient::receive_and_decrypt(socket_t socket, const std::string& peer_id, std::vector<uint8_t>& plaintext) {
388
- rats::NoiseCipherState* cipher = nullptr;
389
-
390
- {
391
- std::lock_guard<std::mutex> lock(peers_mutex_);
392
- auto it = peers_.find(peer_id);
393
- if (it == peers_.end()) {
394
- auto sock_it = socket_to_peer_id_.find(socket);
395
- if (sock_it != socket_to_peer_id_.end()) {
396
- it = peers_.find(sock_it->second);
397
- }
239
+ // ── Check if handshake is complete ──
240
+ if (hs.is_handshake_complete()) {
241
+ auto send_cipher = std::make_shared<rats::NoiseCipherState>();
242
+ auto recv_cipher = std::make_shared<rats::NoiseCipherState>();
243
+
244
+ err = hs.split(*send_cipher, *recv_cipher);
245
+ if (err != rats::NoiseError::OK) {
246
+ LOG_CLIENT_ERROR("Noise split failed: " << static_cast<int>(err));
247
+ return false;
398
248
  }
399
249
 
400
- if (it != peers_.end() && it->second.recv_cipher) {
401
- cipher = it->second.recv_cipher.get();
250
+ // Store ciphers and remote static key
251
+ peer.send_cipher = std::move(send_cipher);
252
+ peer.recv_cipher = std::move(recv_cipher);
253
+ peer.noise_handshake_completed = true;
254
+
255
+ if (hs.has_remote_static()) {
256
+ const uint8_t* remote_key = hs.get_remote_static_public();
257
+ peer.remote_static_key.assign(remote_key, remote_key + 32);
402
258
  }
259
+
260
+ // Free handshake state (no longer needed)
261
+ peer.io_.noise_hs.reset();
262
+ peer.io_.noise_step = 0;
263
+
264
+ // Mark handshake completed
265
+ peer.handshake_state = RatsPeer::HandshakeState::COMPLETED;
266
+ validated_peer_count_.fetch_add(1, std::memory_order_relaxed);
267
+ log_handshake_completion_unlocked(peer);
268
+
269
+ LOG_CLIENT_INFO("Noise handshake completed with " << peer.peer_id);
403
270
  }
404
271
 
405
- if (!cipher) {
406
- LOG_CLIENT_ERROR("No recv cipher for peer: " << peer_id);
407
- return false;
408
- }
409
-
410
- // Receive ciphertext
411
- std::vector<uint8_t> ciphertext;
412
- if (!recv_noise_message(socket, ciphertext)) {
413
- return false;
414
- }
415
-
416
- // Decrypt
417
- plaintext.resize(ciphertext.size());
418
- size_t pt_len = cipher->decrypt_with_ad(nullptr, 0, ciphertext.data(), ciphertext.size(), plaintext.data());
419
- if (pt_len == 0) {
420
- LOG_CLIENT_ERROR("Decryption failed for peer: " << peer_id);
421
- return false;
422
- }
423
-
424
- plaintext.resize(pt_len);
425
272
  return true;
426
273
  }
427
274