librats 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/binding.gyp +1 -0
- package/lib/index.d.ts +2 -1
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2460 -0
- package/native-src/src/dht.h +508 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2735 -0
- package/native-src/src/librats.h +1732 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1333 -0
- package/native-src/src/librats_c.h +239 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +2 -8
- package/scripts/build-librats.js +59 -12
- package/scripts/prepare-package.js +133 -37
- package/src/librats_node.cpp +46 -1
|
@@ -0,0 +1,508 @@
|
|
|
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
|
+
* @param callback Optional callback to receive discovered peers during traversal
|
|
202
|
+
* @return true if announcement started successfully, false otherwise
|
|
203
|
+
*/
|
|
204
|
+
bool announce_peer(const InfoHash& info_hash, uint16_t port = 0, PeerDiscoveryCallback callback = nullptr);
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get our node ID
|
|
208
|
+
* @return The node ID
|
|
209
|
+
*/
|
|
210
|
+
const NodeId& get_node_id() const { return node_id_; }
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get number of nodes in routing table
|
|
214
|
+
* @return Number of nodes
|
|
215
|
+
*/
|
|
216
|
+
size_t get_routing_table_size() const;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get number of pending ping verifications
|
|
220
|
+
* @return Number of pending ping verifications
|
|
221
|
+
*/
|
|
222
|
+
size_t get_pending_ping_verifications_count() const;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if a search is currently active for an info hash
|
|
226
|
+
* @param info_hash The info hash to check
|
|
227
|
+
* @return true if search is active, false otherwise
|
|
228
|
+
*/
|
|
229
|
+
bool is_search_active(const InfoHash& info_hash) const;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if an announce is currently active for an info hash
|
|
233
|
+
* @param info_hash The info hash to check
|
|
234
|
+
* @return true if announce is active, false otherwise
|
|
235
|
+
*/
|
|
236
|
+
bool is_announce_active(const InfoHash& info_hash) const;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get number of active searches
|
|
240
|
+
* @return Number of active searches
|
|
241
|
+
*/
|
|
242
|
+
size_t get_active_searches_count() const;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Get number of active announces
|
|
246
|
+
* @return Number of active announces
|
|
247
|
+
*/
|
|
248
|
+
size_t get_active_announces_count() const;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if DHT is running
|
|
252
|
+
* @return true if running, false otherwise
|
|
253
|
+
*/
|
|
254
|
+
bool is_running() const { return running_; }
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get default BitTorrent DHT bootstrap nodes
|
|
258
|
+
* @return Vector of bootstrap nodes
|
|
259
|
+
*/
|
|
260
|
+
static std::vector<Peer> get_default_bootstrap_nodes();
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Save routing table to disk
|
|
264
|
+
* @return true if successful, false otherwise
|
|
265
|
+
*/
|
|
266
|
+
bool save_routing_table();
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Load routing table from disk
|
|
270
|
+
* @return true if successful, false otherwise
|
|
271
|
+
*/
|
|
272
|
+
bool load_routing_table();
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Set data directory for persistence
|
|
276
|
+
* @param directory Directory path
|
|
277
|
+
*/
|
|
278
|
+
void set_data_directory(const std::string& directory);
|
|
279
|
+
|
|
280
|
+
private:
|
|
281
|
+
int port_;
|
|
282
|
+
std::string bind_address_;
|
|
283
|
+
std::string data_directory_;
|
|
284
|
+
NodeId node_id_;
|
|
285
|
+
socket_t socket_;
|
|
286
|
+
std::atomic<bool> running_;
|
|
287
|
+
|
|
288
|
+
// ============================================================================
|
|
289
|
+
// MUTEX LOCK ORDER - CRITICAL: Always acquire mutexes in this order to avoid deadlocks
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// When acquiring multiple mutexes, ALWAYS follow this order:
|
|
292
|
+
//
|
|
293
|
+
// 1. pending_pings_mutex_ (Ping verification state, nodes_being_replaced_)
|
|
294
|
+
// 2. pending_searches_mutex_ (Search state and transaction mappings)
|
|
295
|
+
// 3. routing_table_mutex_ (core routing data)
|
|
296
|
+
// 4. announced_peers_mutex_ (Stored peer data)
|
|
297
|
+
// 5. peer_tokens_mutex_ (Token validation data)
|
|
298
|
+
// 6. shutdown_mutex_ (Lowest priority - can be locked independently)
|
|
299
|
+
//
|
|
300
|
+
// Routing table (k-buckets)
|
|
301
|
+
std::vector<std::vector<DhtNode>> routing_table_;
|
|
302
|
+
mutable std::mutex routing_table_mutex_; // Lock order: 3
|
|
303
|
+
|
|
304
|
+
// Tokens for peers (use Peer directly as key for efficiency)
|
|
305
|
+
struct PeerToken {
|
|
306
|
+
std::string token;
|
|
307
|
+
std::chrono::steady_clock::time_point created_at;
|
|
308
|
+
|
|
309
|
+
PeerToken() : created_at(std::chrono::steady_clock::now()) {}
|
|
310
|
+
PeerToken(const std::string& t)
|
|
311
|
+
: token(t), created_at(std::chrono::steady_clock::now()) {}
|
|
312
|
+
};
|
|
313
|
+
std::unordered_map<Peer, PeerToken> peer_tokens_;
|
|
314
|
+
std::mutex peer_tokens_mutex_; // Lock order: 6
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
// Pending find_peers tracking (to map transaction IDs to info_hash)
|
|
319
|
+
struct PendingSearch {
|
|
320
|
+
InfoHash info_hash;
|
|
321
|
+
std::chrono::steady_clock::time_point created_at;
|
|
322
|
+
|
|
323
|
+
// Iterative search state - search_nodes is sorted by distance to info_hash (closest first)
|
|
324
|
+
std::vector<DhtNode> search_nodes;
|
|
325
|
+
std::vector<Peer> found_peers; // found peers for this search
|
|
326
|
+
// Single map tracking node states using SearchNodeFlags bitfield
|
|
327
|
+
// A node is "known" if it exists in this map (any flags set or value 0)
|
|
328
|
+
std::unordered_map<NodeId, uint8_t> node_states;
|
|
329
|
+
|
|
330
|
+
int invoke_count; // number of outstanding requests
|
|
331
|
+
int branch_factor; // adaptive concurrency limit (starts at ALPHA)
|
|
332
|
+
bool is_finished; // whether the search is finished
|
|
333
|
+
|
|
334
|
+
// Callbacks to invoke when peers are found (supports multiple concurrent searches for same info_hash)
|
|
335
|
+
std::vector<PeerDiscoveryCallback> callbacks;
|
|
336
|
+
|
|
337
|
+
// Announce support: tokens collected during traversal (BEP 5 compliant)
|
|
338
|
+
// Maps node_id -> write_token received from that node
|
|
339
|
+
std::unordered_map<NodeId, std::string> write_tokens;
|
|
340
|
+
bool is_announce; // true if this search is for announce_peer
|
|
341
|
+
uint16_t announce_port; // port to announce (only valid if is_announce)
|
|
342
|
+
|
|
343
|
+
PendingSearch(const InfoHash& hash)
|
|
344
|
+
: info_hash(hash), created_at(std::chrono::steady_clock::now()),
|
|
345
|
+
invoke_count(0), branch_factor(ALPHA), is_finished(false),
|
|
346
|
+
is_announce(false), announce_port(0) {}
|
|
347
|
+
};
|
|
348
|
+
std::unordered_map<std::string, PendingSearch> pending_searches_; // info_hash (hex) -> PendingSearch
|
|
349
|
+
mutable std::mutex pending_searches_mutex_; // Lock order: 2
|
|
350
|
+
|
|
351
|
+
// Transaction tracking with queried node info for proper responded_nodes tracking
|
|
352
|
+
struct SearchTransaction {
|
|
353
|
+
std::string info_hash_hex;
|
|
354
|
+
NodeId queried_node_id;
|
|
355
|
+
std::chrono::steady_clock::time_point sent_at;
|
|
356
|
+
|
|
357
|
+
SearchTransaction() = default;
|
|
358
|
+
SearchTransaction(const std::string& hash, const NodeId& id)
|
|
359
|
+
: info_hash_hex(hash), queried_node_id(id),
|
|
360
|
+
sent_at(std::chrono::steady_clock::now()) {}
|
|
361
|
+
};
|
|
362
|
+
std::unordered_map<std::string, SearchTransaction> transaction_to_search_; // transaction_id -> SearchTransaction
|
|
363
|
+
|
|
364
|
+
// Peer announcement storage (BEP 5 compliant)
|
|
365
|
+
struct AnnouncedPeer {
|
|
366
|
+
Peer peer;
|
|
367
|
+
std::chrono::steady_clock::time_point announced_at;
|
|
368
|
+
|
|
369
|
+
AnnouncedPeer(const Peer& p)
|
|
370
|
+
: peer(p), announced_at(std::chrono::steady_clock::now()) {}
|
|
371
|
+
};
|
|
372
|
+
// Map from info_hash (as hex string) to list of announced peers
|
|
373
|
+
std::unordered_map<std::string, std::vector<AnnouncedPeer>> announced_peers_;
|
|
374
|
+
std::mutex announced_peers_mutex_; // Lock order: 5
|
|
375
|
+
|
|
376
|
+
// Ping-before-replace eviction tracking (BEP 5 compliant)
|
|
377
|
+
// When a bucket is full and a new node wants to join:
|
|
378
|
+
// 1. We ping the WORST node in the bucket (highest RTT) to check if it's still alive
|
|
379
|
+
// 2. If old node responds -> keep it, discard candidate
|
|
380
|
+
// 3. If old node times out -> replace it with candidate
|
|
381
|
+
struct PingVerification {
|
|
382
|
+
DhtNode candidate_node; // The new node waiting to be added
|
|
383
|
+
DhtNode old_node; // The existing node we're pinging to verify it's alive
|
|
384
|
+
int bucket_index; // Which bucket this affects
|
|
385
|
+
std::chrono::steady_clock::time_point ping_sent_at;
|
|
386
|
+
|
|
387
|
+
PingVerification(const DhtNode& candidate, const DhtNode& old, int bucket_idx)
|
|
388
|
+
: candidate_node(candidate), old_node(old), bucket_index(bucket_idx),
|
|
389
|
+
ping_sent_at(std::chrono::steady_clock::now()) {}
|
|
390
|
+
};
|
|
391
|
+
std::unordered_map<std::string, PingVerification> pending_pings_; // transaction_id -> PingVerification
|
|
392
|
+
std::unordered_set<NodeId> nodes_being_replaced_; // Track old nodes that have pending ping verifications
|
|
393
|
+
mutable std::mutex pending_pings_mutex_; // Lock order: 1 (protects pending_pings_, nodes_being_replaced_)
|
|
394
|
+
|
|
395
|
+
// Network thread
|
|
396
|
+
std::thread network_thread_;
|
|
397
|
+
std::thread maintenance_thread_;
|
|
398
|
+
|
|
399
|
+
// Conditional variables for immediate shutdown
|
|
400
|
+
std::condition_variable shutdown_cv_;
|
|
401
|
+
std::mutex shutdown_mutex_; // Lock order: 7 (can be locked independently)
|
|
402
|
+
|
|
403
|
+
// Helper functions
|
|
404
|
+
void network_loop();
|
|
405
|
+
void maintenance_loop();
|
|
406
|
+
void handle_message(const std::vector<uint8_t>& data, const Peer& sender);
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
// KRPC protocol handlers
|
|
411
|
+
void handle_krpc_message(const KrpcMessage& message, const Peer& sender);
|
|
412
|
+
void handle_krpc_ping(const KrpcMessage& message, const Peer& sender);
|
|
413
|
+
void handle_krpc_find_node(const KrpcMessage& message, const Peer& sender);
|
|
414
|
+
void handle_krpc_get_peers(const KrpcMessage& message, const Peer& sender);
|
|
415
|
+
void handle_krpc_announce_peer(const KrpcMessage& message, const Peer& sender);
|
|
416
|
+
void handle_krpc_response(const KrpcMessage& message, const Peer& sender);
|
|
417
|
+
void handle_krpc_error(const KrpcMessage& message, const Peer& sender);
|
|
418
|
+
|
|
419
|
+
// KRPC protocol sending
|
|
420
|
+
bool send_krpc_message(const KrpcMessage& message, const Peer& peer);
|
|
421
|
+
void send_krpc_ping(const Peer& peer);
|
|
422
|
+
void send_krpc_find_node(const Peer& peer, const NodeId& target);
|
|
423
|
+
void send_krpc_get_peers(const Peer& peer, const InfoHash& info_hash);
|
|
424
|
+
void send_krpc_announce_peer(const Peer& peer, const InfoHash& info_hash, uint16_t port, const std::string& token);
|
|
425
|
+
|
|
426
|
+
void add_node(const DhtNode& node, bool confirmed = true);
|
|
427
|
+
std::vector<DhtNode> find_closest_nodes(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
428
|
+
std::vector<DhtNode> find_closest_nodes_unlocked(const NodeId& target, size_t count = K_BUCKET_SIZE);
|
|
429
|
+
int get_bucket_index(const NodeId& id);
|
|
430
|
+
|
|
431
|
+
NodeId generate_node_id();
|
|
432
|
+
NodeId xor_distance(const NodeId& a, const NodeId& b);
|
|
433
|
+
bool is_closer(const NodeId& a, const NodeId& b, const NodeId& target);
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
std::string generate_token(const Peer& peer);
|
|
437
|
+
bool verify_token(const Peer& peer, const std::string& token);
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
void cleanup_stale_nodes();
|
|
442
|
+
void cleanup_stale_peer_tokens();
|
|
443
|
+
void refresh_buckets();
|
|
444
|
+
void print_statistics();
|
|
445
|
+
|
|
446
|
+
// Pending search management
|
|
447
|
+
void cleanup_stale_searches();
|
|
448
|
+
void cleanup_timed_out_search_requests();
|
|
449
|
+
void cleanup_search_node_states();
|
|
450
|
+
void handle_get_peers_response_for_search(const std::string& transaction_id, const Peer& responder, const std::vector<Peer>& peers);
|
|
451
|
+
void handle_get_peers_response_with_nodes(const std::string& transaction_id, const Peer& responder, const std::vector<KrpcNode>& nodes);
|
|
452
|
+
void handle_get_peers_empty_response(const std::string& transaction_id, const Peer& responder);
|
|
453
|
+
void save_write_token(PendingSearch& search, const NodeId& node_id, const std::string& token);
|
|
454
|
+
bool add_search_requests(PendingSearch& search, DeferredCallbacks& deferred);
|
|
455
|
+
void add_node_to_search(PendingSearch& search, const DhtNode& node);
|
|
456
|
+
void send_announce_to_closest_nodes(PendingSearch& search);
|
|
457
|
+
|
|
458
|
+
// Peer announcement storage management
|
|
459
|
+
void store_announced_peer(const InfoHash& info_hash, const Peer& peer);
|
|
460
|
+
std::vector<Peer> get_announced_peers(const InfoHash& info_hash);
|
|
461
|
+
void cleanup_stale_announced_peers();
|
|
462
|
+
|
|
463
|
+
// Ping-before-replace eviction management
|
|
464
|
+
void initiate_ping_verification(const DhtNode& candidate_node, const DhtNode& old_node, int bucket_index);
|
|
465
|
+
void handle_ping_verification_response(const std::string& transaction_id, const NodeId& responder_id, const Peer& responder);
|
|
466
|
+
void cleanup_stale_ping_verifications();
|
|
467
|
+
bool perform_replacement(const DhtNode& candidate_node, const DhtNode& node_to_replace, int bucket_index);
|
|
468
|
+
|
|
469
|
+
// Conversion utilities
|
|
470
|
+
static KrpcNode dht_node_to_krpc_node(const DhtNode& node);
|
|
471
|
+
static DhtNode krpc_node_to_dht_node(const KrpcNode& node);
|
|
472
|
+
static std::vector<KrpcNode> dht_nodes_to_krpc_nodes(const std::vector<DhtNode>& nodes);
|
|
473
|
+
static std::vector<DhtNode> krpc_nodes_to_dht_nodes(const std::vector<KrpcNode>& nodes);
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Utility functions
|
|
478
|
+
*/
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Convert string to NodeId
|
|
482
|
+
* @param str The string to convert (must be 20 bytes)
|
|
483
|
+
* @return NodeId
|
|
484
|
+
*/
|
|
485
|
+
NodeId string_to_node_id(const std::string& str);
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Convert NodeId to string
|
|
489
|
+
* @param id The NodeId to convert
|
|
490
|
+
* @return String representation
|
|
491
|
+
*/
|
|
492
|
+
std::string node_id_to_string(const NodeId& id);
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Convert hex string to NodeId
|
|
496
|
+
* @param hex The hex string to convert (must be 40 characters)
|
|
497
|
+
* @return NodeId
|
|
498
|
+
*/
|
|
499
|
+
NodeId hex_to_node_id(const std::string& hex);
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Convert NodeId to hex string
|
|
503
|
+
* @param id The NodeId to convert
|
|
504
|
+
* @return Hex string representation
|
|
505
|
+
*/
|
|
506
|
+
std::string node_id_to_hex(const NodeId& id);
|
|
507
|
+
|
|
508
|
+
} // namespace librats
|