librats 0.5.0 → 0.5.1

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 (63) hide show
  1. package/binding.gyp +1 -0
  2. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  3. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  4. package/native-src/CMakeLists.txt +360 -0
  5. package/native-src/LICENSE +21 -0
  6. package/native-src/src/bencode.cpp +485 -0
  7. package/native-src/src/bencode.h +145 -0
  8. package/native-src/src/bittorrent.cpp +3682 -0
  9. package/native-src/src/bittorrent.h +731 -0
  10. package/native-src/src/dht.cpp +2342 -0
  11. package/native-src/src/dht.h +501 -0
  12. package/native-src/src/encrypted_socket.cpp +817 -0
  13. package/native-src/src/encrypted_socket.h +239 -0
  14. package/native-src/src/file_transfer.cpp +1808 -0
  15. package/native-src/src/file_transfer.h +567 -0
  16. package/native-src/src/fs.cpp +639 -0
  17. package/native-src/src/fs.h +108 -0
  18. package/native-src/src/gossipsub.cpp +1137 -0
  19. package/native-src/src/gossipsub.h +403 -0
  20. package/native-src/src/ice.cpp +1386 -0
  21. package/native-src/src/ice.h +328 -0
  22. package/native-src/src/json.hpp +25526 -0
  23. package/native-src/src/krpc.cpp +558 -0
  24. package/native-src/src/krpc.h +145 -0
  25. package/native-src/src/librats.cpp +2715 -0
  26. package/native-src/src/librats.h +1729 -0
  27. package/native-src/src/librats_bittorrent.cpp +167 -0
  28. package/native-src/src/librats_c.cpp +1317 -0
  29. package/native-src/src/librats_c.h +237 -0
  30. package/native-src/src/librats_encryption.cpp +123 -0
  31. package/native-src/src/librats_file_transfer.cpp +226 -0
  32. package/native-src/src/librats_gossipsub.cpp +293 -0
  33. package/native-src/src/librats_ice.cpp +515 -0
  34. package/native-src/src/librats_logging.cpp +158 -0
  35. package/native-src/src/librats_mdns.cpp +171 -0
  36. package/native-src/src/librats_nat.cpp +571 -0
  37. package/native-src/src/librats_persistence.cpp +815 -0
  38. package/native-src/src/logger.h +412 -0
  39. package/native-src/src/mdns.cpp +1178 -0
  40. package/native-src/src/mdns.h +253 -0
  41. package/native-src/src/network_utils.cpp +598 -0
  42. package/native-src/src/network_utils.h +162 -0
  43. package/native-src/src/noise.cpp +981 -0
  44. package/native-src/src/noise.h +227 -0
  45. package/native-src/src/os.cpp +371 -0
  46. package/native-src/src/os.h +40 -0
  47. package/native-src/src/rats_export.h +17 -0
  48. package/native-src/src/sha1.cpp +163 -0
  49. package/native-src/src/sha1.h +42 -0
  50. package/native-src/src/socket.cpp +1376 -0
  51. package/native-src/src/socket.h +309 -0
  52. package/native-src/src/stun.cpp +484 -0
  53. package/native-src/src/stun.h +349 -0
  54. package/native-src/src/threadmanager.cpp +105 -0
  55. package/native-src/src/threadmanager.h +53 -0
  56. package/native-src/src/tracker.cpp +1110 -0
  57. package/native-src/src/tracker.h +268 -0
  58. package/native-src/src/version.cpp +24 -0
  59. package/native-src/src/version.h.in +45 -0
  60. package/native-src/version.rc.in +31 -0
  61. package/package.json +2 -8
  62. package/scripts/build-librats.js +59 -12
  63. package/scripts/prepare-package.js +133 -37
@@ -0,0 +1,815 @@
1
+ #include "librats.h"
2
+ #include "sha1.h"
3
+ #include "os.h"
4
+ #include "fs.h"
5
+ #include "json.hpp" // nlohmann::json
6
+ #include <iostream>
7
+ #include <algorithm>
8
+ #include <chrono>
9
+ #include <memory>
10
+ #include <random>
11
+ #include <sstream>
12
+ #include <iomanip>
13
+ #include <stdexcept>
14
+
15
+ #ifdef TESTING
16
+ #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", "[pointer: " << this << "] " << message)
17
+ #define LOG_CLIENT_INFO(message) LOG_INFO("client", "[pointer: " << this << "] " << message)
18
+ #define LOG_CLIENT_WARN(message) LOG_WARN("client", "[pointer: " << this << "] " << message)
19
+ #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", "[pointer: " << this << "] " << message)
20
+ #else
21
+ #define LOG_CLIENT_DEBUG(message) LOG_DEBUG("client", message)
22
+ #define LOG_CLIENT_INFO(message) LOG_INFO("client", message)
23
+ #define LOG_CLIENT_WARN(message) LOG_WARN("client", message)
24
+ #define LOG_CLIENT_ERROR(message) LOG_ERROR("client", message)
25
+ #endif
26
+
27
+ namespace librats {
28
+
29
+ // =========================================================================
30
+ // Configuration Persistence Implementation
31
+ // =========================================================================
32
+
33
+ bool RatsClient::load_configuration() {
34
+ std::lock_guard<std::mutex> lock(config_mutex_);
35
+
36
+ LOG_CLIENT_INFO("Loading configuration from " << get_config_file_path());
37
+
38
+ // Check if config file exists
39
+ if (!file_or_directory_exists(get_config_file_path())) {
40
+ LOG_CLIENT_INFO("No existing configuration found, generating new peer ID");
41
+ our_peer_id_ = generate_persistent_peer_id();
42
+
43
+ // Save the new configuration immediately
44
+ {
45
+ nlohmann::json config;
46
+ config["peer_id"] = our_peer_id_;
47
+ config["version"] = RATS_PROTOCOL_VERSION;
48
+ config["listen_port"] = listen_port_;
49
+ config["max_peers"] = max_peers_;
50
+
51
+ auto now = std::chrono::high_resolution_clock::now();
52
+ auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
53
+ config["created_at"] = timestamp;
54
+ config["last_updated"] = timestamp;
55
+
56
+ std::string config_data = config.dump(4); // Pretty print with 4 spaces
57
+ if (create_file(get_config_file_path(), config_data)) {
58
+ LOG_CLIENT_INFO("Created new configuration file with peer ID: " << our_peer_id_);
59
+ } else {
60
+ LOG_CLIENT_ERROR("Failed to create configuration file");
61
+ return false;
62
+ }
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ // Load existing configuration
69
+ try {
70
+ std::string config_data = read_file_text_cpp(get_config_file_path());
71
+ if (config_data.empty()) {
72
+ LOG_CLIENT_ERROR("Configuration file is empty");
73
+ return false;
74
+ }
75
+
76
+ nlohmann::json config = nlohmann::json::parse(config_data);
77
+
78
+ // Load peer ID
79
+ our_peer_id_ = config.value("peer_id", "");
80
+ if (our_peer_id_.empty()) {
81
+ LOG_CLIENT_WARN("No peer ID in configuration, generating new one");
82
+ our_peer_id_ = generate_persistent_peer_id();
83
+ return save_configuration(); // Save the new peer ID
84
+ }
85
+
86
+ LOG_CLIENT_INFO("Loaded configuration with peer ID: " << our_peer_id_);
87
+
88
+ // Load encryption settings
89
+ if (config.contains("encryption_enabled")) {
90
+ encryption_enabled_ = config.value("encryption_enabled", true);
91
+ }
92
+
93
+ if (config.contains("encryption_key")) {
94
+ std::string key_hex = config.value("encryption_key", "");
95
+ if (!key_hex.empty()) {
96
+ NoiseKey loaded_key = EncryptedSocket::string_to_key(key_hex);
97
+ // Validate key
98
+ bool is_valid = false;
99
+ for (uint8_t byte : loaded_key) {
100
+ if (byte != 0) {
101
+ is_valid = true;
102
+ break;
103
+ }
104
+ }
105
+ if (is_valid) {
106
+ static_encryption_key_ = loaded_key;
107
+ LOG_CLIENT_INFO("Loaded encryption key from configuration");
108
+ } else {
109
+ LOG_CLIENT_WARN("Invalid encryption key in configuration, using generated key");
110
+ }
111
+ }
112
+ }
113
+
114
+ // Update last_updated timestamp
115
+ auto now = std::chrono::high_resolution_clock::now();
116
+ auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
117
+ config["last_updated"] = timestamp;
118
+
119
+ // Save updated config
120
+ std::string updated_config_data = config.dump(4);
121
+ create_file(get_config_file_path(), updated_config_data);
122
+
123
+ return true;
124
+
125
+ } catch (const nlohmann::json::exception& e) {
126
+ LOG_CLIENT_ERROR("Failed to parse configuration file: " << e.what());
127
+ return false;
128
+ } catch (const std::exception& e) {
129
+ LOG_CLIENT_ERROR("Failed to load configuration: " << e.what());
130
+ return false;
131
+ }
132
+ }
133
+
134
+ bool RatsClient::save_configuration() {
135
+ std::lock_guard<std::mutex> lock(config_mutex_);
136
+
137
+ if (our_peer_id_.empty()) {
138
+ LOG_CLIENT_WARN("No peer ID to save");
139
+ return false;
140
+ }
141
+
142
+ LOG_CLIENT_DEBUG("Saving configuration to " << get_config_file_path());
143
+
144
+ try {
145
+ // Create configuration JSON
146
+ nlohmann::json config;
147
+ config["peer_id"] = our_peer_id_;
148
+ config["version"] = RATS_PROTOCOL_VERSION;
149
+ config["listen_port"] = listen_port_;
150
+ config["max_peers"] = max_peers_;
151
+ config["encryption_enabled"] = encryption_enabled_;
152
+ config["encryption_key"] = get_encryption_key();
153
+
154
+ auto now = std::chrono::high_resolution_clock::now();
155
+ auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
156
+ config["last_updated"] = timestamp;
157
+
158
+ // If config file exists, preserve created_at timestamp
159
+ if (file_or_directory_exists(get_config_file_path())) {
160
+ try {
161
+ std::string existing_config_data = read_file_text_cpp(get_config_file_path());
162
+ nlohmann::json existing_config = nlohmann::json::parse(existing_config_data);
163
+ if (existing_config.contains("created_at")) {
164
+ config["created_at"] = existing_config["created_at"];
165
+ }
166
+ } catch (const std::exception&) {
167
+ // If we can't read existing config, just use current timestamp
168
+ config["created_at"] = timestamp;
169
+ }
170
+ } else {
171
+ config["created_at"] = timestamp;
172
+ }
173
+
174
+ // Save configuration
175
+ std::string config_data = config.dump(4);
176
+ if (create_file(get_config_file_path(), config_data)) {
177
+ LOG_CLIENT_DEBUG("Configuration saved successfully");
178
+ } else {
179
+ LOG_CLIENT_ERROR("Failed to save configuration file");
180
+ return false;
181
+ }
182
+
183
+ // Save peers
184
+ return save_peers_to_file();
185
+
186
+ } catch (const nlohmann::json::exception& e) {
187
+ LOG_CLIENT_ERROR("Failed to create configuration JSON: " << e.what());
188
+ return false;
189
+ } catch (const std::exception& e) {
190
+ LOG_CLIENT_ERROR("Failed to save configuration: " << e.what());
191
+ return false;
192
+ }
193
+ }
194
+
195
+ bool RatsClient::save_peers_to_file() {
196
+ // This method assumes config_mutex_ is already locked by save_configuration()
197
+
198
+ LOG_CLIENT_DEBUG("Saving peers to " << get_peers_file_path());
199
+
200
+ try {
201
+ nlohmann::json peers_json = nlohmann::json::array();
202
+
203
+ // Get validated peers for saving
204
+ {
205
+ std::lock_guard<std::mutex> peers_lock(peers_mutex_);
206
+ for (const auto& pair : peers_) {
207
+ const RatsPeer& peer = pair.second;
208
+ // Only save peers that have completed handshake and have valid peer IDs
209
+ if (peer.is_handshake_completed() && !peer.peer_id.empty()) {
210
+ // Don't save ourselves
211
+ if (peer.peer_id != our_peer_id_) {
212
+ peers_json.push_back(serialize_peer_for_persistence(peer));
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ LOG_CLIENT_INFO("Saving " << peers_json.size() << " peers to persistence file");
219
+
220
+ // Save peers file
221
+ std::string peers_data = peers_json.dump(4);
222
+ if (create_file(get_peers_file_path(), peers_data)) {
223
+ LOG_CLIENT_DEBUG("Peers saved successfully");
224
+ return true;
225
+ } else {
226
+ LOG_CLIENT_ERROR("Failed to save peers file");
227
+ return false;
228
+ }
229
+
230
+ } catch (const nlohmann::json::exception& e) {
231
+ LOG_CLIENT_ERROR("Failed to serialize peers: " << e.what());
232
+ return false;
233
+ } catch (const std::exception& e) {
234
+ LOG_CLIENT_ERROR("Failed to save peers: " << e.what());
235
+ return false;
236
+ }
237
+ }
238
+
239
+ int RatsClient::load_and_reconnect_peers() {
240
+ if (!running_.load()) {
241
+ LOG_CLIENT_DEBUG("Client not running, skipping peer reconnection");
242
+ return 0;
243
+ }
244
+
245
+ std::string peers_file_path;
246
+ {
247
+ std::lock_guard<std::mutex> lock(config_mutex_);
248
+ peers_file_path = get_peers_file_path();
249
+ }
250
+
251
+ LOG_CLIENT_INFO("Loading saved peers from " << peers_file_path);
252
+
253
+ // Check if peers file exists
254
+ if (!file_or_directory_exists(peers_file_path)) {
255
+ LOG_CLIENT_INFO("No saved peers file found");
256
+ return 0;
257
+ }
258
+
259
+ try {
260
+ std::string peers_data = read_file_text_cpp(peers_file_path);
261
+ if (peers_data.empty()) {
262
+ LOG_CLIENT_INFO("Peers file is empty");
263
+ return 0;
264
+ }
265
+
266
+ nlohmann::json peers_json = nlohmann::json::parse(peers_data);
267
+
268
+ if (!peers_json.is_array()) {
269
+ LOG_CLIENT_ERROR("Invalid peers file format - expected array");
270
+ return 0;
271
+ }
272
+
273
+ int reconnect_attempts = 0;
274
+
275
+ for (const auto& peer_json : peers_json) {
276
+ std::string ip;
277
+ int port;
278
+ std::string peer_id;
279
+
280
+ if (!deserialize_peer_from_persistence(peer_json, ip, port, peer_id)) {
281
+ continue; // Skip invalid or old peers
282
+ }
283
+
284
+ // Don't connect to ourselves
285
+ if (peer_id == get_our_peer_id()) {
286
+ LOG_CLIENT_DEBUG("Skipping connection to ourselves: " << peer_id);
287
+ continue;
288
+ }
289
+
290
+ // Check if we should ignore this peer (local interface)
291
+ if (should_ignore_peer(ip, port)) {
292
+ LOG_CLIENT_DEBUG("Ignoring saved peer " << ip << ":" << port << " - local interface address");
293
+ continue;
294
+ }
295
+
296
+ // Check if we're already connected to this peer
297
+ std::string normalized_peer_address = normalize_peer_address(ip, port);
298
+ if (is_already_connected_to_address(normalized_peer_address)) {
299
+ LOG_CLIENT_DEBUG("Already connected to saved peer " << normalized_peer_address);
300
+ continue;
301
+ }
302
+
303
+ // Check if peer limit is reached
304
+ if (is_peer_limit_reached()) {
305
+ LOG_CLIENT_DEBUG("Peer limit reached, stopping reconnection attempts");
306
+ break;
307
+ }
308
+
309
+ LOG_CLIENT_INFO("Attempting to reconnect to saved peer: " << ip << ":" << port << " (peer_id: " << peer_id << ")");
310
+
311
+ // Attempt to connect (non-blocking)
312
+ add_managed_thread(std::thread([this, ip, port, peer_id]() {
313
+ if (connect_to_peer(ip, port)) {
314
+ LOG_CLIENT_INFO("Successfully reconnected to saved peer: " << ip << ":" << port);
315
+ } else {
316
+ LOG_CLIENT_DEBUG("Failed to reconnect to saved peer: " << ip << ":" << port);
317
+ }
318
+ }), "peer-reconnect-" + peer_id.substr(0, 8));
319
+
320
+ reconnect_attempts++;
321
+
322
+ // Small delay between connection attempts to avoid overwhelming the network
323
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
324
+ }
325
+
326
+ LOG_CLIENT_INFO("Processed " << peers_json.size() << " saved peers, attempted " << reconnect_attempts << " reconnections");
327
+ return reconnect_attempts;
328
+
329
+ } catch (const nlohmann::json::exception& e) {
330
+ LOG_CLIENT_ERROR("Failed to parse saved peers file: " << e.what());
331
+ return 0;
332
+ } catch (const std::exception& e) {
333
+ LOG_CLIENT_ERROR("Failed to load saved peers: " << e.what());
334
+ return 0;
335
+ }
336
+ }
337
+
338
+ bool RatsClient::append_peer_to_historical_file(const RatsPeer& peer) {
339
+ // Don't save ourselves
340
+ if (peer.peer_id == our_peer_id_) {
341
+ return true;
342
+ }
343
+
344
+ // Only save peers that have completed handshake and have valid peer IDs
345
+ if (!peer.is_handshake_completed() || peer.peer_id.empty()) {
346
+ return true;
347
+ }
348
+
349
+ try {
350
+ // Create historical peer entry with timestamp
351
+ nlohmann::json historical_peer = serialize_peer_for_persistence(peer);
352
+
353
+ // Add current timestamp as last_seen
354
+ auto now = std::chrono::high_resolution_clock::now();
355
+ auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
356
+ historical_peer["last_seen"] = timestamp;
357
+
358
+ // Check if file exists, if not create it as an empty array
359
+ std::string file_path = get_peers_ever_file_path();
360
+ nlohmann::json historical_peers;
361
+
362
+ if (file_or_directory_exists(file_path)) {
363
+ try {
364
+ std::string existing_data = read_file_text_cpp(file_path);
365
+ if (!existing_data.empty()) {
366
+ historical_peers = nlohmann::json::parse(existing_data);
367
+ if (!historical_peers.is_array()) {
368
+ LOG_CLIENT_WARN("Historical peers file format invalid, creating new array");
369
+ historical_peers = nlohmann::json::array();
370
+ }
371
+ } else {
372
+ historical_peers = nlohmann::json::array();
373
+ }
374
+ } catch (const std::exception& e) {
375
+ LOG_CLIENT_WARN("Failed to read historical peers file, creating new array: " << e.what());
376
+ historical_peers = nlohmann::json::array();
377
+ }
378
+ } else {
379
+ historical_peers = nlohmann::json::array();
380
+ }
381
+
382
+ // Check if this peer already exists in historical file
383
+ std::string peer_address = peer.ip + ":" + std::to_string(peer.port);
384
+ bool already_exists = false;
385
+
386
+ for (auto& existing_peer : historical_peers) {
387
+ std::string existing_ip = existing_peer.value("ip", "");
388
+ int existing_port = existing_peer.value("port", 0);
389
+ std::string existing_address = existing_ip + ":" + std::to_string(existing_port);
390
+
391
+ if (existing_address == peer_address || existing_peer.value("peer_id", "") == peer.peer_id) {
392
+ // Update timestamp for existing peer
393
+ existing_peer["last_seen"] = timestamp;
394
+ already_exists = true;
395
+ break;
396
+ }
397
+ }
398
+
399
+ // If peer doesn't exist, add it
400
+ if (!already_exists) {
401
+ historical_peers.push_back(historical_peer);
402
+ LOG_CLIENT_DEBUG("Added new peer to historical file: " << peer.ip << ":" << peer.port << " (peer_id: " << peer.peer_id << ")");
403
+ } else {
404
+ LOG_CLIENT_DEBUG("Updated timestamp for existing historical peer: " << peer.ip << ":" << peer.port);
405
+ }
406
+
407
+ // Save updated historical peers file
408
+ std::string historical_data = historical_peers.dump(4);
409
+ if (create_file(file_path, historical_data)) {
410
+ return true;
411
+ } else {
412
+ LOG_CLIENT_ERROR("Failed to save historical peers file");
413
+ return false;
414
+ }
415
+
416
+ } catch (const nlohmann::json::exception& e) {
417
+ LOG_CLIENT_ERROR("Failed to process historical peer: " << e.what());
418
+ return false;
419
+ } catch (const std::exception& e) {
420
+ LOG_CLIENT_ERROR("Failed to append peer to historical file: " << e.what());
421
+ return false;
422
+ }
423
+ }
424
+
425
+ bool RatsClient::load_historical_peers() {
426
+ LOG_CLIENT_INFO("Loading historical peers from " << get_peers_ever_file_path());
427
+
428
+ // Check if historical peers file exists
429
+ if (!file_or_directory_exists(get_peers_ever_file_path())) {
430
+ LOG_CLIENT_INFO("No historical peers file found");
431
+ return true; // Not an error
432
+ }
433
+
434
+ try {
435
+ std::string historical_data = read_file_text_cpp(get_peers_ever_file_path());
436
+ if (historical_data.empty()) {
437
+ LOG_CLIENT_INFO("Historical peers file is empty");
438
+ return true;
439
+ }
440
+
441
+ nlohmann::json historical_peers = nlohmann::json::parse(historical_data);
442
+
443
+ if (!historical_peers.is_array()) {
444
+ LOG_CLIENT_ERROR("Invalid historical peers file format - expected array");
445
+ return false;
446
+ }
447
+
448
+ LOG_CLIENT_INFO("Loaded " << historical_peers.size() << " historical peers from file");
449
+ return true;
450
+
451
+ } catch (const nlohmann::json::exception& e) {
452
+ LOG_CLIENT_ERROR("Failed to parse historical peers file: " << e.what());
453
+ return false;
454
+ } catch (const std::exception& e) {
455
+ LOG_CLIENT_ERROR("Failed to load historical peers file: " << e.what());
456
+ return false;
457
+ }
458
+ }
459
+
460
+ bool RatsClient::save_historical_peers() {
461
+ try {
462
+ // Get current peers and save them to historical file
463
+ std::vector<RatsPeer> current_peers = get_validated_peers();
464
+
465
+ for (const auto& peer : current_peers) {
466
+ if (!append_peer_to_historical_file(peer)) {
467
+ LOG_CLIENT_WARN("Failed to save peer to historical file: " << peer.ip << ":" << peer.port);
468
+ }
469
+ }
470
+
471
+ LOG_CLIENT_INFO("Saved " << current_peers.size() << " current peers to historical file");
472
+ return true;
473
+
474
+ } catch (const std::exception& e) {
475
+ LOG_CLIENT_ERROR("Failed to save historical peers: " << e.what());
476
+ return false;
477
+ }
478
+ }
479
+
480
+ void RatsClient::clear_historical_peers() {
481
+ std::string file_path = get_peers_ever_file_path();
482
+
483
+ try {
484
+ // Create empty array and save to file
485
+ nlohmann::json empty_array = nlohmann::json::array();
486
+ std::string empty_data = empty_array.dump(4);
487
+
488
+ if (create_file(file_path, empty_data)) {
489
+ LOG_CLIENT_INFO("Cleared historical peers file");
490
+ } else {
491
+ LOG_CLIENT_ERROR("Failed to clear historical peers file");
492
+ }
493
+
494
+ } catch (const std::exception& e) {
495
+ LOG_CLIENT_ERROR("Failed to clear historical peers: " << e.what());
496
+ }
497
+ }
498
+
499
+ std::vector<RatsPeer> RatsClient::get_historical_peers() const {
500
+ std::vector<RatsPeer> historical_peers;
501
+
502
+ // Check if historical peers file exists
503
+ if (!file_or_directory_exists(get_peers_ever_file_path())) {
504
+ return historical_peers; // Return empty vector
505
+ }
506
+
507
+ try {
508
+ std::string historical_data = read_file_text_cpp(get_peers_ever_file_path());
509
+ if (historical_data.empty()) {
510
+ return historical_peers;
511
+ }
512
+
513
+ nlohmann::json peers_json = nlohmann::json::parse(historical_data);
514
+
515
+ if (!peers_json.is_array()) {
516
+ LOG_CLIENT_ERROR("Invalid historical peers file format - expected array");
517
+ return historical_peers;
518
+ }
519
+
520
+ for (const auto& peer_json : peers_json) {
521
+ std::string ip;
522
+ int port;
523
+ std::string peer_id;
524
+
525
+ if (deserialize_peer_from_persistence(peer_json, ip, port, peer_id)) {
526
+ // Create a RatsPeer object for the historical peer
527
+ // Note: This won't have all the runtime fields populated
528
+ RatsPeer historical_peer(peer_id, ip, static_cast<uint16_t>(port),
529
+ INVALID_SOCKET_VALUE, ip + ":" + std::to_string(port), false);
530
+
531
+ // Set additional fields from JSON if available
532
+ historical_peer.version = peer_json.value("version", "");
533
+
534
+ historical_peers.push_back(historical_peer);
535
+ }
536
+ }
537
+
538
+ LOG_CLIENT_DEBUG("Retrieved " << historical_peers.size() << " historical peers");
539
+
540
+ } catch (const nlohmann::json::exception& e) {
541
+ LOG_CLIENT_ERROR("Failed to parse historical peers file: " << e.what());
542
+ } catch (const std::exception& e) {
543
+ LOG_CLIENT_ERROR("Failed to read historical peers file: " << e.what());
544
+ }
545
+
546
+ return historical_peers;
547
+ }
548
+
549
+ int RatsClient::load_and_reconnect_historical_peers() {
550
+ if (!running_.load()) {
551
+ LOG_CLIENT_DEBUG("Client not running, skipping historical peer reconnection");
552
+ return 0;
553
+ }
554
+
555
+ LOG_CLIENT_INFO("Loading historical peers from " << get_peers_ever_file_path());
556
+
557
+ // Check if historical peers file exists
558
+ if (!file_or_directory_exists(get_peers_ever_file_path())) {
559
+ LOG_CLIENT_INFO("No historical peers file found");
560
+ return 0;
561
+ }
562
+
563
+ try {
564
+ std::string historical_data = read_file_text_cpp(get_peers_ever_file_path());
565
+ if (historical_data.empty()) {
566
+ LOG_CLIENT_INFO("Historical peers file is empty");
567
+ return 0;
568
+ }
569
+
570
+ nlohmann::json historical_peers = nlohmann::json::parse(historical_data);
571
+
572
+ if (!historical_peers.is_array()) {
573
+ LOG_CLIENT_ERROR("Invalid historical peers file format - expected array");
574
+ return 0;
575
+ }
576
+
577
+ int reconnect_attempts = 0;
578
+
579
+ for (const auto& peer_json : historical_peers) {
580
+ std::string ip;
581
+ int port;
582
+ std::string peer_id;
583
+
584
+ if (!deserialize_peer_from_persistence(peer_json, ip, port, peer_id)) {
585
+ continue; // Skip invalid or old peers
586
+ }
587
+
588
+ // Don't connect to ourselves
589
+ if (peer_id == get_our_peer_id()) {
590
+ LOG_CLIENT_DEBUG("Skipping connection to ourselves: " << peer_id);
591
+ continue;
592
+ }
593
+
594
+ // Check if we should ignore this peer (local interface)
595
+ if (should_ignore_peer(ip, port)) {
596
+ LOG_CLIENT_DEBUG("Ignoring historical peer " << ip << ":" << port << " - local interface address");
597
+ continue;
598
+ }
599
+
600
+ // Check if we're already connected to this peer
601
+ std::string normalized_peer_address = normalize_peer_address(ip, port);
602
+ if (is_already_connected_to_address(normalized_peer_address)) {
603
+ LOG_CLIENT_DEBUG("Already connected to historical peer " << normalized_peer_address);
604
+ continue;
605
+ }
606
+
607
+ // Check if peer limit is reached
608
+ if (is_peer_limit_reached()) {
609
+ LOG_CLIENT_DEBUG("Peer limit reached, stopping historical reconnection attempts");
610
+ break;
611
+ }
612
+
613
+ LOG_CLIENT_INFO("Attempting to reconnect to historical peer: " << ip << ":" << port << " (peer_id: " << peer_id << ")");
614
+
615
+ // Attempt to connect (non-blocking)
616
+ add_managed_thread(std::thread([this, ip, port, peer_id]() {
617
+ if (connect_to_peer(ip, port)) {
618
+ LOG_CLIENT_INFO("Successfully reconnected to historical peer: " << ip << ":" << port);
619
+ } else {
620
+ LOG_CLIENT_DEBUG("Failed to reconnect to historical peer: " << ip << ":" << port);
621
+ }
622
+ }), "historical-reconnect-" + peer_id.substr(0, 8));
623
+
624
+ reconnect_attempts++;
625
+
626
+ // Small delay between connection attempts to avoid overwhelming the network
627
+ std::this_thread::sleep_for(std::chrono::milliseconds(150));
628
+ }
629
+
630
+ LOG_CLIENT_INFO("Processed " << historical_peers.size() << " historical peers, attempted " << reconnect_attempts << " reconnections");
631
+ return reconnect_attempts;
632
+
633
+ } catch (const nlohmann::json::exception& e) {
634
+ LOG_CLIENT_ERROR("Failed to parse historical peers file: " << e.what());
635
+ return 0;
636
+ } catch (const std::exception& e) {
637
+ LOG_CLIENT_ERROR("Failed to load historical peers: " << e.what());
638
+ return 0;
639
+ }
640
+ }
641
+
642
+ // Configuration persistence implementation
643
+ std::string RatsClient::generate_persistent_peer_id() const {
644
+ // Generate a unique peer ID using SHA1 hash of timestamp, random data, and hostname
645
+ auto now = std::chrono::high_resolution_clock::now();
646
+ auto timestamp = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
647
+
648
+ // Get system information for uniqueness
649
+ SystemInfo sys_info = get_system_info();
650
+
651
+ // Create random component
652
+ std::random_device rd;
653
+ std::mt19937 gen(rd());
654
+ std::uniform_int_distribution<> dis(0, 255);
655
+
656
+ // Build unique string
657
+ std::ostringstream unique_stream;
658
+ unique_stream << timestamp << "_" << sys_info.hostname << "_" << listen_port_ << "_";
659
+
660
+ // Add random component
661
+ for (int i = 0; i < 16; ++i) {
662
+ unique_stream << std::setfill('0') << std::setw(2) << std::hex << dis(gen);
663
+ }
664
+
665
+ // Generate SHA1 hash of the unique string
666
+ std::string unique_string = unique_stream.str();
667
+ std::string peer_id = SHA1::hash(unique_string);
668
+
669
+ LOG_CLIENT_INFO("Generated new persistent peer ID: " << peer_id);
670
+ return peer_id;
671
+ }
672
+
673
+ nlohmann::json RatsClient::serialize_peer_for_persistence(const RatsPeer& peer) const {
674
+ nlohmann::json peer_json;
675
+ peer_json["ip"] = peer.ip;
676
+ peer_json["port"] = peer.port;
677
+ peer_json["peer_id"] = peer.peer_id;
678
+ peer_json["normalized_address"] = peer.normalized_address;
679
+ peer_json["is_outgoing"] = peer.is_outgoing;
680
+ peer_json["version"] = peer.version;
681
+
682
+ // Add timestamp for cleanup of old peers
683
+ auto now = std::chrono::high_resolution_clock::now();
684
+ auto timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
685
+ peer_json["last_seen"] = timestamp;
686
+
687
+ return peer_json;
688
+ }
689
+
690
+ bool RatsClient::deserialize_peer_from_persistence(const nlohmann::json& json, std::string& ip, int& port, std::string& peer_id) const {
691
+ try {
692
+ ip = json.value("ip", "");
693
+ port = json.value("port", 0);
694
+ peer_id = json.value("peer_id", "");
695
+
696
+ // Validate required fields
697
+ if (ip.empty() || port <= 0 || port > 65535 || peer_id.empty()) {
698
+ return false;
699
+ }
700
+
701
+ // Check if peer data is not too old (optional - remove peers older than 7 days)
702
+ if (json.contains("last_seen")) {
703
+ auto now = std::chrono::high_resolution_clock::now();
704
+ auto current_timestamp = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
705
+ int64_t last_seen = json.value("last_seen", current_timestamp);
706
+
707
+ const int64_t MAX_PEER_AGE_SECONDS = 7 * 24 * 60 * 60; // 7 days
708
+ if (current_timestamp - last_seen > MAX_PEER_AGE_SECONDS) {
709
+ LOG_CLIENT_DEBUG("Skipping old peer " << ip << ":" << port << " (last seen " << (current_timestamp - last_seen) << " seconds ago)");
710
+ return false;
711
+ }
712
+ }
713
+
714
+ return true;
715
+
716
+ } catch (const nlohmann::json::exception& e) {
717
+ LOG_CLIENT_ERROR("Failed to deserialize peer: " << e.what());
718
+ return false;
719
+ }
720
+ }
721
+
722
+ std::string RatsClient::get_config_file_path() const {
723
+ #ifdef TESTING
724
+ // For testing with ephemeral ports (port 0), use a unique identifier to avoid conflicts
725
+ if (listen_port_ == 0) {
726
+ // Generate a unique file path based on object pointer to ensure uniqueness during testing
727
+ std::ostringstream oss;
728
+ oss << "config_" << this << ".json";
729
+ return oss.str();
730
+ }
731
+ return "config_" + std::to_string(listen_port_) + ".json";
732
+ #else
733
+ return data_directory_ + "/" + CONFIG_FILE_NAME;
734
+ #endif
735
+ }
736
+
737
+ std::string RatsClient::get_peers_file_path() const {
738
+ #ifdef TESTING
739
+ // For testing with ephemeral ports (port 0), use a unique identifier to avoid conflicts
740
+ if (listen_port_ == 0) {
741
+ // Generate a unique file path based on object pointer to ensure uniqueness during testing
742
+ std::ostringstream oss;
743
+ oss << "peers_" << this << ".json";
744
+ return oss.str();
745
+ }
746
+ return "peers_" + std::to_string(listen_port_) + ".json";
747
+ #else
748
+ return data_directory_ + "/" + PEERS_FILE_NAME;
749
+ #endif
750
+ }
751
+
752
+ std::string RatsClient::get_peers_ever_file_path() const {
753
+ #ifdef TESTING
754
+ // For testing with ephemeral ports (port 0), use a unique identifier to avoid conflicts
755
+ if (listen_port_ == 0) {
756
+ // Generate a unique file path based on object pointer to ensure uniqueness during testing
757
+ std::ostringstream oss;
758
+ oss << "peers_ever_" << this << ".json";
759
+ return oss.str();
760
+ }
761
+ return "peers_ever_" + std::to_string(listen_port_) + ".json";
762
+ #else
763
+ return data_directory_ + "/" + PEERS_EVER_FILE_NAME;
764
+ #endif
765
+ }
766
+
767
+ // =========================================================================
768
+ // Data directory management
769
+ // =========================================================================
770
+
771
+ bool RatsClient::set_data_directory(const std::string& directory_path) {
772
+ std::lock_guard<std::mutex> lock(config_mutex_);
773
+
774
+ // Normalize the path (remove trailing slashes)
775
+ std::string normalized_path = directory_path;
776
+ while (!normalized_path.empty() && (normalized_path.back() == '/' || normalized_path.back() == '\\')) {
777
+ normalized_path.pop_back();
778
+ }
779
+
780
+ // Use current directory if empty
781
+ if (normalized_path.empty()) {
782
+ normalized_path = ".";
783
+ }
784
+
785
+ // Check if directory exists
786
+ if (!directory_exists(normalized_path)) {
787
+ // Try to create the directory
788
+ if (!create_directories(normalized_path.c_str())) {
789
+ LOG_CLIENT_ERROR("Failed to create data directory: " << normalized_path);
790
+ return false;
791
+ }
792
+ LOG_CLIENT_INFO("Created data directory: " << normalized_path);
793
+ }
794
+
795
+ // Test if we can write to the directory by creating a temporary file
796
+ std::string test_file = normalized_path + "/test_write_access.tmp";
797
+ if (!create_file(test_file, "test")) {
798
+ LOG_CLIENT_ERROR("Cannot write to data directory: " << normalized_path);
799
+ return false;
800
+ }
801
+
802
+ // Clean up test file
803
+ delete_file(test_file.c_str());
804
+
805
+ data_directory_ = normalized_path;
806
+ LOG_CLIENT_INFO("Data directory set to: " << data_directory_);
807
+ return true;
808
+ }
809
+
810
+ std::string RatsClient::get_data_directory() const {
811
+ std::lock_guard<std::mutex> lock(config_mutex_);
812
+ return data_directory_;
813
+ }
814
+
815
+ } // namespace librats