librats 0.3.1 → 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 (69) hide show
  1. package/README.md +405 -405
  2. package/binding.gyp +96 -95
  3. package/lib/index.d.ts +522 -522
  4. package/lib/index.js +82 -82
  5. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  7. package/native-src/CMakeLists.txt +360 -0
  8. package/native-src/LICENSE +21 -0
  9. package/native-src/src/bencode.cpp +485 -0
  10. package/native-src/src/bencode.h +145 -0
  11. package/native-src/src/bittorrent.cpp +3682 -0
  12. package/native-src/src/bittorrent.h +731 -0
  13. package/native-src/src/dht.cpp +2342 -0
  14. package/native-src/src/dht.h +501 -0
  15. package/native-src/src/encrypted_socket.cpp +817 -0
  16. package/native-src/src/encrypted_socket.h +239 -0
  17. package/native-src/src/file_transfer.cpp +1808 -0
  18. package/native-src/src/file_transfer.h +567 -0
  19. package/native-src/src/fs.cpp +639 -0
  20. package/native-src/src/fs.h +108 -0
  21. package/native-src/src/gossipsub.cpp +1137 -0
  22. package/native-src/src/gossipsub.h +403 -0
  23. package/native-src/src/ice.cpp +1386 -0
  24. package/native-src/src/ice.h +328 -0
  25. package/native-src/src/json.hpp +25526 -0
  26. package/native-src/src/krpc.cpp +558 -0
  27. package/native-src/src/krpc.h +145 -0
  28. package/native-src/src/librats.cpp +2715 -0
  29. package/native-src/src/librats.h +1729 -0
  30. package/native-src/src/librats_bittorrent.cpp +167 -0
  31. package/native-src/src/librats_c.cpp +1317 -0
  32. package/native-src/src/librats_c.h +237 -0
  33. package/native-src/src/librats_encryption.cpp +123 -0
  34. package/native-src/src/librats_file_transfer.cpp +226 -0
  35. package/native-src/src/librats_gossipsub.cpp +293 -0
  36. package/native-src/src/librats_ice.cpp +515 -0
  37. package/native-src/src/librats_logging.cpp +158 -0
  38. package/native-src/src/librats_mdns.cpp +171 -0
  39. package/native-src/src/librats_nat.cpp +571 -0
  40. package/native-src/src/librats_persistence.cpp +815 -0
  41. package/native-src/src/logger.h +412 -0
  42. package/native-src/src/mdns.cpp +1178 -0
  43. package/native-src/src/mdns.h +253 -0
  44. package/native-src/src/network_utils.cpp +598 -0
  45. package/native-src/src/network_utils.h +162 -0
  46. package/native-src/src/noise.cpp +981 -0
  47. package/native-src/src/noise.h +227 -0
  48. package/native-src/src/os.cpp +371 -0
  49. package/native-src/src/os.h +40 -0
  50. package/native-src/src/rats_export.h +17 -0
  51. package/native-src/src/sha1.cpp +163 -0
  52. package/native-src/src/sha1.h +42 -0
  53. package/native-src/src/socket.cpp +1376 -0
  54. package/native-src/src/socket.h +309 -0
  55. package/native-src/src/stun.cpp +484 -0
  56. package/native-src/src/stun.h +349 -0
  57. package/native-src/src/threadmanager.cpp +105 -0
  58. package/native-src/src/threadmanager.h +53 -0
  59. package/native-src/src/tracker.cpp +1110 -0
  60. package/native-src/src/tracker.h +268 -0
  61. package/native-src/src/version.cpp +24 -0
  62. package/native-src/src/version.h.in +45 -0
  63. package/native-src/version.rc.in +31 -0
  64. package/package.json +62 -68
  65. package/scripts/build-librats.js +241 -194
  66. package/scripts/postinstall.js +52 -52
  67. package/scripts/prepare-package.js +187 -91
  68. package/scripts/verify-installation.js +119 -119
  69. package/src/librats_node.cpp +1174 -1174
@@ -0,0 +1,501 @@
1
+ #pragma once
2
+
3
+ #include "socket.h"
4
+ #include "krpc.h"
5
+ #include <string>
6
+ #include <vector>
7
+ #include <array>
8
+ #include <unordered_map>
9
+ #include <unordered_set>
10
+ #include <functional>
11
+ #include <thread>
12
+ #include <mutex>
13
+ #include <atomic>
14
+ #include <chrono>
15
+ #include <memory>
16
+ #include <condition_variable>
17
+
18
+ // Hash specialization for Peer and NodeId (must be defined before use in unordered_map/set)
19
+ namespace std {
20
+ template<>
21
+ struct hash<librats::Peer> {
22
+ std::size_t operator()(const librats::Peer& peer) const noexcept {
23
+ std::hash<std::string> hasher;
24
+ return hasher(peer.ip + ":" + std::to_string(peer.port));
25
+ }
26
+ };
27
+
28
+ template<>
29
+ struct hash<array<uint8_t, 20>> {
30
+ std::size_t operator()(const array<uint8_t, 20>& id) const noexcept {
31
+ std::size_t seed = 0;
32
+ std::hash<uint8_t> hasher;
33
+ for (const auto& byte : id) {
34
+ seed ^= hasher(byte) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
35
+ }
36
+ return seed;
37
+ }
38
+ };
39
+ }
40
+
41
+ namespace librats {
42
+
43
+ // Constants for Kademlia DHT
44
+ constexpr size_t NODE_ID_SIZE = 20; // 160 bits = 20 bytes
45
+ constexpr size_t K_BUCKET_SIZE = 8; // Maximum nodes per k-bucket
46
+ constexpr size_t ALPHA = 3; // Concurrency parameter
47
+ constexpr int DHT_PORT = 6881; // Standard BitTorrent DHT port
48
+
49
+ using NodeId = std::array<uint8_t, NODE_ID_SIZE>;
50
+ using InfoHash = std::array<uint8_t, NODE_ID_SIZE>;
51
+
52
+ /**
53
+ * Search node state flags (bitfield)
54
+ * Flags can be combined to track the full history of a node in a search.
55
+ */
56
+ namespace SearchNodeFlags {
57
+ constexpr uint8_t QUERIED = 1 << 0; // Query has been sent to this node
58
+ constexpr uint8_t SHORT_TIMEOUT = 1 << 1; // Node exceeded short timeout (slot freed, still waiting)
59
+ constexpr uint8_t RESPONDED = 1 << 2; // Node successfully responded
60
+ constexpr uint8_t TIMED_OUT = 1 << 3; // Node fully timed out (failed)
61
+ constexpr uint8_t ABANDONED = 1 << 4; // Node was discarded during search truncation
62
+ }
63
+
64
+ /**
65
+ * DHT Node information
66
+ * Based on libtorrent's node_entry with fail_count and RTT tracking
67
+ */
68
+ struct DhtNode {
69
+ NodeId id;
70
+ Peer peer;
71
+ std::chrono::steady_clock::time_point last_seen;
72
+
73
+ // Round-trip time in milliseconds (0xffff = unknown, lower is better)
74
+ uint16_t rtt = 0xffff;
75
+
76
+ // Number of consecutive failures (0xff = never pinged, 0 = confirmed good)
77
+ uint8_t fail_count = 0xff;
78
+
79
+ DhtNode() : last_seen(std::chrono::steady_clock::now()) {}
80
+ DhtNode(const NodeId& id, const Peer& peer)
81
+ : id(id), peer(peer), last_seen(std::chrono::steady_clock::now()) {}
82
+
83
+ // Has this node ever responded to us?
84
+ bool pinged() const { return fail_count != 0xff; }
85
+
86
+ // Is this node confirmed good? (responded with no recent failures)
87
+ bool confirmed() const { return fail_count == 0; }
88
+
89
+ // Mark node as failed (timed out)
90
+ void mark_failed() {
91
+ if (pinged() && fail_count < 0xfe) ++fail_count;
92
+ }
93
+
94
+ // Mark node as successful (responded)
95
+ void mark_success() {
96
+ fail_count = 0;
97
+ last_seen = std::chrono::steady_clock::now();
98
+ }
99
+
100
+ // Update RTT with exponential moving average
101
+ void update_rtt(uint16_t new_rtt) {
102
+ if (new_rtt == 0xffff) return;
103
+ if (rtt == 0xffff) {
104
+ rtt = new_rtt;
105
+ } else {
106
+ // Weighted average: 2/3 old + 1/3 new
107
+ rtt = static_cast<uint16_t>(rtt * 2 / 3 + new_rtt / 3);
108
+ }
109
+ }
110
+
111
+ // Compare nodes: "less" means "better" node
112
+ // Priority: confirmed > not confirmed, then lower fail_count, then lower RTT
113
+ bool is_worse_than(const DhtNode& other) const {
114
+ // Nodes with failures are worse
115
+ if (fail_count != other.fail_count) {
116
+ return fail_count > other.fail_count;
117
+ }
118
+ // Higher RTT is worse
119
+ return rtt > other.rtt;
120
+ }
121
+ };
122
+
123
+
124
+
125
+ /**
126
+ * Peer discovery callback
127
+ */
128
+ using PeerDiscoveryCallback = std::function<void(const std::vector<Peer>& peers, const InfoHash& info_hash)>;
129
+
130
+ /**
131
+ * Deferred callbacks structure for avoiding deadlock
132
+ * Callbacks are collected while holding the mutex, then invoked after releasing it
133
+ */
134
+ struct DeferredCallbacks {
135
+ std::vector<PeerDiscoveryCallback> callbacks;
136
+ std::vector<Peer> peers;
137
+ InfoHash info_hash;
138
+ bool should_invoke = false;
139
+
140
+ void invoke() {
141
+ if (should_invoke) {
142
+ for (const auto& cb : callbacks) {
143
+ if (cb) cb(peers, info_hash);
144
+ }
145
+ }
146
+ }
147
+ };
148
+
149
+ /**
150
+ * DHT Kademlia implementation
151
+ */
152
+ class DhtClient {
153
+ public:
154
+ /**
155
+ * Constructor
156
+ * @param port The UDP port to bind to (default: 6881)
157
+ * @param bind_address The interface IP address to bind to (empty for all interfaces)
158
+ */
159
+ DhtClient(int port = DHT_PORT, const std::string& bind_address = "", const std::string& data_directory = "");
160
+
161
+ /**
162
+ * Destructor
163
+ */
164
+ ~DhtClient();
165
+
166
+ /**
167
+ * Start the DHT client
168
+ * @return true if successful, false otherwise
169
+ */
170
+ bool start();
171
+
172
+ /**
173
+ * Stop the DHT client
174
+ */
175
+ void stop();
176
+
177
+ /**
178
+ * Trigger immediate shutdown of all background threads
179
+ */
180
+ void shutdown_immediate();
181
+
182
+ /**
183
+ * Bootstrap the DHT with known nodes
184
+ * @param bootstrap_nodes Vector of bootstrap nodes
185
+ * @return true if successful, false otherwise
186
+ */
187
+ bool bootstrap(const std::vector<Peer>& bootstrap_nodes);
188
+
189
+ /**
190
+ * Find peers for a specific info hash
191
+ * @param info_hash The info hash to search for
192
+ * @param callback Callback to receive discovered peers
193
+ * @return true if search started successfully, false otherwise
194
+ */
195
+ bool find_peers(const InfoHash& info_hash, PeerDiscoveryCallback callback);
196
+
197
+ /**
198
+ * Announce that this node is a peer for a specific info hash
199
+ * @param info_hash The info hash to announce
200
+ * @param port The port to announce (0 for DHT port)
201
+ * @return true if announcement started successfully, false otherwise
202
+ */
203
+ bool announce_peer(const InfoHash& info_hash, uint16_t port = 0);
204
+
205
+ /**
206
+ * Get our node ID
207
+ * @return The node ID
208
+ */
209
+ const NodeId& get_node_id() const { return node_id_; }
210
+
211
+ /**
212
+ * Get number of nodes in routing table
213
+ * @return Number of nodes
214
+ */
215
+ size_t get_routing_table_size() const;
216
+
217
+ /**
218
+ * Get number of pending ping verifications
219
+ * @return Number of pending ping verifications
220
+ */
221
+ size_t get_pending_ping_verifications_count() const;
222
+
223
+ /**
224
+ * Check if a search is currently active for an info hash
225
+ * @param info_hash The info hash to check
226
+ * @return true if search is active, false otherwise
227
+ */
228
+ bool is_search_active(const InfoHash& info_hash) const;
229
+
230
+ /**
231
+ * Get number of active searches
232
+ * @return Number of active searches
233
+ */
234
+ size_t get_active_searches_count() const;
235
+
236
+ /**
237
+ * Check if DHT is running
238
+ * @return true if running, false otherwise
239
+ */
240
+ bool is_running() const { return running_; }
241
+
242
+ /**
243
+ * Get default BitTorrent DHT bootstrap nodes
244
+ * @return Vector of bootstrap nodes
245
+ */
246
+ static std::vector<Peer> get_default_bootstrap_nodes();
247
+
248
+ /**
249
+ * Save routing table to disk
250
+ * @return true if successful, false otherwise
251
+ */
252
+ bool save_routing_table();
253
+
254
+ /**
255
+ * Load routing table from disk
256
+ * @return true if successful, false otherwise
257
+ */
258
+ bool load_routing_table();
259
+
260
+ /**
261
+ * Set data directory for persistence
262
+ * @param directory Directory path
263
+ */
264
+ void set_data_directory(const std::string& directory);
265
+
266
+ private:
267
+ int port_;
268
+ std::string bind_address_;
269
+ std::string data_directory_;
270
+ NodeId node_id_;
271
+ socket_t socket_;
272
+ std::atomic<bool> running_;
273
+
274
+ // ============================================================================
275
+ // MUTEX LOCK ORDER - CRITICAL: Always acquire mutexes in this order to avoid deadlocks
276
+ // ============================================================================
277
+ // When acquiring multiple mutexes, ALWAYS follow this order:
278
+ //
279
+ // 1. pending_pings_mutex_ (Ping verification state, nodes_being_replaced_)
280
+ // 2. pending_searches_mutex_ (Search state and transaction mappings)
281
+ // 3. routing_table_mutex_ (core routing data)
282
+ // 4. pending_announces_mutex_ (Announce state)
283
+ // 5. announced_peers_mutex_ (Stored peer data)
284
+ // 6. peer_tokens_mutex_ (Token validation data)
285
+ // 7. shutdown_mutex_ (Lowest priority - can be locked independently)
286
+ //
287
+ // Routing table (k-buckets)
288
+ std::vector<std::vector<DhtNode>> routing_table_;
289
+ mutable std::mutex routing_table_mutex_; // Lock order: 3
290
+
291
+ // Tokens for peers (use Peer directly as key for efficiency)
292
+ struct PeerToken {
293
+ std::string token;
294
+ std::chrono::steady_clock::time_point created_at;
295
+
296
+ PeerToken() : created_at(std::chrono::steady_clock::now()) {}
297
+ PeerToken(const std::string& t)
298
+ : token(t), created_at(std::chrono::steady_clock::now()) {}
299
+ };
300
+ std::unordered_map<Peer, PeerToken> peer_tokens_;
301
+ std::mutex peer_tokens_mutex_; // Lock order: 6
302
+
303
+
304
+ // Pending announce tracking (for BEP 5 compliance)
305
+ struct PendingAnnounce {
306
+ InfoHash info_hash;
307
+ uint16_t port;
308
+ std::chrono::steady_clock::time_point created_at;
309
+
310
+ PendingAnnounce(const InfoHash& hash, uint16_t p)
311
+ : info_hash(hash), port(p), created_at(std::chrono::steady_clock::now()) {}
312
+ };
313
+ std::unordered_map<std::string, PendingAnnounce> pending_announces_;
314
+ std::mutex pending_announces_mutex_; // Lock order: 4
315
+
316
+ // Pending find_peers tracking (to map transaction IDs to info_hash)
317
+ struct PendingSearch {
318
+ InfoHash info_hash;
319
+ std::chrono::steady_clock::time_point created_at;
320
+
321
+ // Iterative search state - search_nodes is sorted by distance to info_hash (closest first)
322
+ std::vector<DhtNode> search_nodes;
323
+ std::vector<Peer> found_peers; // found peers for this search
324
+ // Single map tracking node states using SearchNodeFlags bitfield
325
+ // A node is "known" if it exists in this map (any flags set or value 0)
326
+ std::unordered_map<NodeId, uint8_t> node_states;
327
+
328
+ int invoke_count; // number of outstanding requests
329
+ int branch_factor; // adaptive concurrency limit (starts at ALPHA)
330
+ bool is_finished; // whether the search is finished
331
+
332
+ // Callbacks to invoke when peers are found (supports multiple concurrent searches for same info_hash)
333
+ std::vector<PeerDiscoveryCallback> callbacks;
334
+
335
+ PendingSearch(const InfoHash& hash)
336
+ : info_hash(hash), created_at(std::chrono::steady_clock::now()),
337
+ invoke_count(0), branch_factor(ALPHA), is_finished(false) {}
338
+ };
339
+ std::unordered_map<std::string, PendingSearch> pending_searches_; // info_hash (hex) -> PendingSearch
340
+ mutable std::mutex pending_searches_mutex_; // Lock order: 2
341
+
342
+ // Transaction tracking with queried node info for proper responded_nodes tracking
343
+ struct SearchTransaction {
344
+ std::string info_hash_hex;
345
+ NodeId queried_node_id;
346
+ std::chrono::steady_clock::time_point sent_at;
347
+
348
+ SearchTransaction() = default;
349
+ SearchTransaction(const std::string& hash, const NodeId& id)
350
+ : info_hash_hex(hash), queried_node_id(id),
351
+ sent_at(std::chrono::steady_clock::now()) {}
352
+ };
353
+ std::unordered_map<std::string, SearchTransaction> transaction_to_search_; // transaction_id -> SearchTransaction
354
+
355
+ // Peer announcement storage (BEP 5 compliant)
356
+ struct AnnouncedPeer {
357
+ Peer peer;
358
+ std::chrono::steady_clock::time_point announced_at;
359
+
360
+ AnnouncedPeer(const Peer& p)
361
+ : peer(p), announced_at(std::chrono::steady_clock::now()) {}
362
+ };
363
+ // Map from info_hash (as hex string) to list of announced peers
364
+ std::unordered_map<std::string, std::vector<AnnouncedPeer>> announced_peers_;
365
+ std::mutex announced_peers_mutex_; // Lock order: 5
366
+
367
+ // Ping-before-replace eviction tracking (BEP 5 compliant)
368
+ // When a bucket is full and a new node wants to join:
369
+ // 1. We ping the WORST node in the bucket (highest RTT) to check if it's still alive
370
+ // 2. If old node responds -> keep it, discard candidate
371
+ // 3. If old node times out -> replace it with candidate
372
+ struct PingVerification {
373
+ DhtNode candidate_node; // The new node waiting to be added
374
+ DhtNode old_node; // The existing node we're pinging to verify it's alive
375
+ int bucket_index; // Which bucket this affects
376
+ std::chrono::steady_clock::time_point ping_sent_at;
377
+
378
+ PingVerification(const DhtNode& candidate, const DhtNode& old, int bucket_idx)
379
+ : candidate_node(candidate), old_node(old), bucket_index(bucket_idx),
380
+ ping_sent_at(std::chrono::steady_clock::now()) {}
381
+ };
382
+ std::unordered_map<std::string, PingVerification> pending_pings_; // transaction_id -> PingVerification
383
+ std::unordered_set<NodeId> nodes_being_replaced_; // Track old nodes that have pending ping verifications
384
+ mutable std::mutex pending_pings_mutex_; // Lock order: 1 (protects pending_pings_, nodes_being_replaced_)
385
+
386
+ // Network thread
387
+ std::thread network_thread_;
388
+ std::thread maintenance_thread_;
389
+
390
+ // Conditional variables for immediate shutdown
391
+ std::condition_variable shutdown_cv_;
392
+ std::mutex shutdown_mutex_; // Lock order: 7 (can be locked independently)
393
+
394
+ // Helper functions
395
+ void network_loop();
396
+ void maintenance_loop();
397
+ void handle_message(const std::vector<uint8_t>& data, const Peer& sender);
398
+
399
+
400
+
401
+ // KRPC protocol handlers
402
+ void handle_krpc_message(const KrpcMessage& message, const Peer& sender);
403
+ void handle_krpc_ping(const KrpcMessage& message, const Peer& sender);
404
+ void handle_krpc_find_node(const KrpcMessage& message, const Peer& sender);
405
+ void handle_krpc_get_peers(const KrpcMessage& message, const Peer& sender);
406
+ void handle_krpc_announce_peer(const KrpcMessage& message, const Peer& sender);
407
+ void handle_krpc_response(const KrpcMessage& message, const Peer& sender);
408
+ void handle_krpc_error(const KrpcMessage& message, const Peer& sender);
409
+
410
+ // KRPC protocol sending
411
+ bool send_krpc_message(const KrpcMessage& message, const Peer& peer);
412
+ void send_krpc_ping(const Peer& peer);
413
+ void send_krpc_find_node(const Peer& peer, const NodeId& target);
414
+ void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
415
+ void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
416
+
417
+ void add_node(const DhtNode& node, bool confirmed = true);
418
+ std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
419
+ std::vector<DhtNode> find_closest_nodes_unlocked(const NodeId& target, size_t count = K_BUCKET_SIZE);
420
+ int get_bucket_index(const NodeId& id);
421
+
422
+ NodeId generate_node_id();
423
+ NodeId xor_distance(const NodeId& a, const NodeId& b);
424
+ bool is_closer(const NodeId& a, const NodeId& b, const NodeId& target);
425
+
426
+
427
+ std::string generate_token(const Peer& peer);
428
+ bool verify_token(const Peer& peer, const std::string& token);
429
+
430
+
431
+
432
+ void cleanup_stale_nodes();
433
+ void cleanup_stale_peer_tokens();
434
+ void refresh_buckets();
435
+ void print_statistics();
436
+
437
+ // Pending announce management
438
+ void cleanup_stale_announces();
439
+ void handle_get_peers_response_for_announce(const std::string& transaction_id, const Peer& responder, const std::string& token);
440
+
441
+ // Pending search management
442
+ void cleanup_stale_searches();
443
+ void cleanup_timed_out_search_requests();
444
+ void cleanup_search_node_states();
445
+ void handle_get_peers_response_for_search(const std::string& transaction_id, const Peer& responder, const std::vector<Peer>& peers);
446
+ void handle_get_peers_response_with_nodes(const std::string& transaction_id, const Peer& responder, const std::vector<KrpcNode>& nodes);
447
+ void handle_get_peers_empty_response(const std::string& transaction_id, const Peer& responder);
448
+ bool add_search_requests(PendingSearch& search, DeferredCallbacks& deferred);
449
+ void add_node_to_search(PendingSearch& search, const DhtNode& node);
450
+
451
+ // Peer announcement storage management
452
+ void store_announced_peer(const InfoHash& info_hash, const Peer& peer);
453
+ std::vector<Peer> get_announced_peers(const InfoHash& info_hash);
454
+ void cleanup_stale_announced_peers();
455
+
456
+ // Ping-before-replace eviction management
457
+ void initiate_ping_verification(const DhtNode& candidate_node, const DhtNode& old_node, int bucket_index);
458
+ void handle_ping_verification_response(const std::string& transaction_id, const NodeId& responder_id, const Peer& responder);
459
+ void cleanup_stale_ping_verifications();
460
+ bool perform_replacement(const DhtNode& candidate_node, const DhtNode& node_to_replace, int bucket_index);
461
+
462
+ // Conversion utilities
463
+ static KrpcNode dht_node_to_krpc_node(const DhtNode& node);
464
+ static DhtNode krpc_node_to_dht_node(const KrpcNode& node);
465
+ static std::vector<KrpcNode> dht_nodes_to_krpc_nodes(const std::vector<DhtNode>& nodes);
466
+ static std::vector<DhtNode> krpc_nodes_to_dht_nodes(const std::vector<KrpcNode>& nodes);
467
+ };
468
+
469
+ /**
470
+ * Utility functions
471
+ */
472
+
473
+ /**
474
+ * Convert string to NodeId
475
+ * @param str The string to convert (must be 20 bytes)
476
+ * @return NodeId
477
+ */
478
+ NodeId string_to_node_id(const std::string& str);
479
+
480
+ /**
481
+ * Convert NodeId to string
482
+ * @param id The NodeId to convert
483
+ * @return String representation
484
+ */
485
+ std::string node_id_to_string(const NodeId& id);
486
+
487
+ /**
488
+ * Convert hex string to NodeId
489
+ * @param hex The hex string to convert (must be 40 characters)
490
+ * @return NodeId
491
+ */
492
+ NodeId hex_to_node_id(const std::string& hex);
493
+
494
+ /**
495
+ * Convert NodeId to hex string
496
+ * @param id The NodeId to convert
497
+ * @return Hex string representation
498
+ */
499
+ std::string node_id_to_hex(const NodeId& id);
500
+
501
+ } // namespace librats