librats 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,309 @@
1
+ #include "librats.h"
2
+ #include "librats_log_macros.h"
3
+ #include "sha1.h"
4
+
5
+ namespace librats {
6
+
7
+ // =========================================================================
8
+ // Peer Discovery Methods
9
+ // =========================================================================
10
+
11
+ bool RatsClient::start_dht_discovery(int dht_port) {
12
+ if (dht_client_ && dht_client_->is_running()) {
13
+ LOG_CLIENT_WARN("DHT discovery is already running");
14
+ return true;
15
+ }
16
+
17
+ LOG_CLIENT_INFO("Starting DHT discovery on port " << dht_port <<
18
+ (bind_address_.empty() ? "" : " bound to " + bind_address_));
19
+
20
+ dht_client_ = std::make_unique<DhtClient>(dht_port, bind_address_, data_directory_);
21
+ if (!dht_client_->start()) {
22
+ LOG_CLIENT_ERROR("Failed to start DHT client");
23
+ dht_client_.reset();
24
+ return false;
25
+ }
26
+
27
+ // Bootstrap with default nodes
28
+ auto bootstrap_nodes = DhtClient::get_default_bootstrap_nodes();
29
+ if (!dht_client_->bootstrap(bootstrap_nodes)) {
30
+ LOG_CLIENT_WARN("Failed to bootstrap DHT");
31
+ }
32
+
33
+ // Start automatic peer discovery
34
+ start_automatic_peer_discovery();
35
+
36
+ LOG_CLIENT_INFO("DHT discovery started successfully");
37
+ return true;
38
+ }
39
+
40
+ void RatsClient::stop_dht_discovery() {
41
+ if (!dht_client_) {
42
+ return;
43
+ }
44
+
45
+ LOG_CLIENT_INFO("Stopping DHT discovery");
46
+
47
+ // Stop automatic peer discovery
48
+ stop_automatic_peer_discovery();
49
+
50
+ dht_client_->stop();
51
+ dht_client_.reset();
52
+ LOG_CLIENT_INFO("DHT discovery stopped");
53
+ }
54
+
55
+ bool RatsClient::find_peers_by_hash(const std::string& content_hash, std::function<void(const std::vector<std::string>&)> callback) {
56
+ if (!dht_client_ || !dht_client_->is_running()) {
57
+ LOG_CLIENT_ERROR("DHT client not running");
58
+ return false;
59
+ }
60
+
61
+ if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
62
+ LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
63
+ return false;
64
+ }
65
+
66
+ LOG_CLIENT_INFO("Finding peers for content hash: " << content_hash);
67
+
68
+ InfoHash info_hash = hex_to_node_id(content_hash);
69
+
70
+ return dht_client_->find_peers(info_hash, [this, callback](const std::vector<Peer>& peers, const InfoHash& info_hash) {
71
+ // Convert Peer to string addresses for callback
72
+ std::vector<std::string> peer_addresses;
73
+ for (const auto& peer : peers) {
74
+ peer_addresses.emplace_back(peer.ip + ":" + std::to_string(peer.port));
75
+ }
76
+
77
+ if (callback) {
78
+ callback(peer_addresses);
79
+ }
80
+ });
81
+ }
82
+
83
+ bool RatsClient::announce_for_hash(const std::string& content_hash, uint16_t port,
84
+ std::function<void(const std::vector<std::string>&)> callback) {
85
+ if (!dht_client_ || !dht_client_->is_running()) {
86
+ LOG_CLIENT_ERROR("DHT client not running");
87
+ return false;
88
+ }
89
+
90
+ if (content_hash.length() != CONTENT_HASH_HEX_LENGTH) {
91
+ LOG_CLIENT_ERROR("Invalid content hash length: " << content_hash.length() << " (expected " << CONTENT_HASH_HEX_LENGTH << ")");
92
+ return false;
93
+ }
94
+
95
+ if (port == 0) {
96
+ port = listen_port_;
97
+ }
98
+
99
+ LOG_CLIENT_INFO("Announcing for content hash: " << content_hash << " on port " << port
100
+ << (callback ? " with peer callback" : ""));
101
+
102
+ InfoHash info_hash = hex_to_node_id(content_hash);
103
+
104
+ // Create wrapper callback that converts Peer to string addresses (if callback provided)
105
+ PeerDiscoveryCallback peer_callback = nullptr;
106
+ if (callback) {
107
+ peer_callback = [callback](const std::vector<Peer>& peers, const InfoHash& hash) {
108
+ std::vector<std::string> peer_addresses;
109
+ peer_addresses.reserve(peers.size());
110
+ for (const auto& peer : peers) {
111
+ peer_addresses.push_back(peer.ip + ":" + std::to_string(peer.port));
112
+ }
113
+ callback(peer_addresses);
114
+ };
115
+ }
116
+
117
+ return dht_client_->announce_peer(info_hash, port, peer_callback);
118
+ }
119
+
120
+ bool RatsClient::is_dht_running() const {
121
+ return dht_client_ && dht_client_->is_running();
122
+ }
123
+
124
+ size_t RatsClient::get_dht_routing_table_size() const {
125
+ if (!dht_client_) {
126
+ return 0;
127
+ }
128
+ return dht_client_->get_routing_table_size();
129
+ }
130
+
131
+ void RatsClient::handle_dht_peer_discovery(const std::vector<Peer>& peers, const InfoHash& info_hash) {
132
+ LOG_CLIENT_INFO("DHT discovered " << peers.size() << " peers for info hash: " << node_id_to_hex(info_hash));
133
+
134
+ // Auto-connect to discovered peers
135
+ for (const auto& peer : peers) {
136
+ if (!can_connect_to_peer(peer.ip, peer.port)) {
137
+ continue;
138
+ }
139
+
140
+ LOG_CLIENT_DEBUG("Attempting to connect to discovered peer: " << peer.ip << ":" << peer.port);
141
+
142
+ // Try to connect to the peer (non-blocking, managed thread for graceful shutdown)
143
+ add_managed_thread(std::thread([this, peer]() {
144
+ if (connect_to_peer(peer.ip, peer.port)) {
145
+ LOG_CLIENT_INFO("Successfully connected to DHT discovered peer: " << peer.ip << ":" << peer.port);
146
+ } else {
147
+ LOG_CLIENT_DEBUG("Failed to connect to DHT discovered peer: " << peer.ip << ":" << peer.port);
148
+ }
149
+ }), "dht-connect-" + peer.ip);
150
+ }
151
+ }
152
+
153
+ void RatsClient::start_automatic_peer_discovery() {
154
+ if (auto_discovery_running_.load()) {
155
+ LOG_CLIENT_WARN("Automatic peer discovery is already running");
156
+ return;
157
+ }
158
+
159
+ LOG_CLIENT_INFO("Starting automatic rats peer discovery");
160
+ auto_discovery_running_.store(true);
161
+ auto_discovery_thread_ = std::thread(&RatsClient::automatic_discovery_loop, this);
162
+ }
163
+
164
+ void RatsClient::stop_automatic_peer_discovery() {
165
+ if (!auto_discovery_running_.load()) {
166
+ return;
167
+ }
168
+
169
+ LOG_CLIENT_INFO("Stopping automatic peer discovery");
170
+ auto_discovery_running_.store(false);
171
+
172
+ if (auto_discovery_thread_.joinable()) {
173
+ auto_discovery_thread_.join();
174
+ }
175
+
176
+ LOG_CLIENT_INFO("Automatic peer discovery stopped");
177
+ }
178
+
179
+ bool RatsClient::is_automatic_discovery_running() const {
180
+ return auto_discovery_running_.load();
181
+ }
182
+
183
+ std::chrono::seconds RatsClient::calculate_discovery_interval() const {
184
+ int peer_count = get_peer_count();
185
+
186
+ // No peers - aggressive discovery
187
+ if (peer_count == 0) {
188
+ return std::chrono::seconds(15);
189
+ }
190
+
191
+ // Calculate fill ratio
192
+ float fill_ratio = static_cast<float>(peer_count) / static_cast<float>(max_peers_);
193
+
194
+ // Graduated intervals based on fill ratio
195
+ if (fill_ratio < 0.25f) {
196
+ // Less than 25% full - still fairly aggressive
197
+ return std::chrono::seconds(60); // 1 minute
198
+ } else if (fill_ratio < 0.50f) {
199
+ // 25-50% full - moderate
200
+ return std::chrono::seconds(180); // 3 minutes
201
+ } else if (fill_ratio < 0.75f) {
202
+ // 50-75% full - relaxed
203
+ return std::chrono::seconds(600); // 10 minutes
204
+ } else {
205
+ // 75-100% full - very relaxed (mostly just re-announcing)
206
+ return std::chrono::seconds(1800); // 30 minutes
207
+ }
208
+ }
209
+
210
+ void RatsClient::automatic_discovery_loop() {
211
+ LOG_CLIENT_INFO("Automatic peer discovery loop started");
212
+
213
+ // Initial delay to let DHT bootstrap
214
+ {
215
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
216
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(INITIAL_DISCOVERY_DELAY_SECONDS), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
217
+ LOG_CLIENT_INFO("Automatic peer discovery loop stopped during initial delay");
218
+ return;
219
+ }
220
+ }
221
+
222
+ // Announce immediately - this also discovers peers during traversal
223
+ announce_rats_peer();
224
+
225
+ auto last_announce = std::chrono::steady_clock::now();
226
+
227
+ while (auto_discovery_running_.load()) {
228
+ auto now = std::chrono::steady_clock::now();
229
+
230
+ // Announce combines both announcing our presence and discovering peers
231
+ // Interval scales based on peer count: aggressive when empty, relaxed when nearly full
232
+ auto interval = calculate_discovery_interval();
233
+
234
+ if (now - last_announce >= interval) {
235
+ LOG_CLIENT_DEBUG("Discovery interval: " << interval.count() << "s (peers: "
236
+ << get_peer_count() << "/" << max_peers_ << ")");
237
+ announce_rats_peer();
238
+ last_announce = now;
239
+ }
240
+
241
+ // Use conditional variable for responsive shutdown
242
+ {
243
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
244
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(500), [this] { return !auto_discovery_running_.load() || !running_.load(); })) {
245
+ break;
246
+ }
247
+ }
248
+ }
249
+
250
+ LOG_CLIENT_INFO("Automatic peer discovery loop stopped");
251
+ }
252
+
253
+ void RatsClient::announce_rats_peer() {
254
+ if (!dht_client_ || !dht_client_->is_running()) {
255
+ LOG_CLIENT_WARN("DHT client not running, cannot announce peer");
256
+ return;
257
+ }
258
+
259
+ std::string discovery_hash = get_discovery_hash();
260
+ LOG_CLIENT_INFO("Announcing peer for discovery hash: " << discovery_hash << " on port " << listen_port_);
261
+
262
+ InfoHash info_hash = hex_to_node_id(discovery_hash);
263
+
264
+ if (dht_client_->is_announce_active(info_hash)) {
265
+ LOG_CLIENT_WARN("Announce already in progress for info hash: " << node_id_to_hex(info_hash));
266
+ return;
267
+ }
268
+
269
+ // Use announce with callback - combines announce and find_peers in one traversal
270
+ // Peers discovered during traversal will be returned through the callback
271
+ if (announce_for_hash(discovery_hash, listen_port_, [this, info_hash](const std::vector<std::string>& peer_addresses) {
272
+ LOG_CLIENT_INFO("Announce discovered " << peer_addresses.size() << " peers during traversal");
273
+
274
+ // Convert peer addresses to Peer objects for handle_dht_peer_discovery()
275
+ std::vector<Peer> peers;
276
+ peers.reserve(peer_addresses.size());
277
+ for (const auto& peer_address : peer_addresses) {
278
+ std::string ip;
279
+ int port;
280
+ if (parse_address_string(peer_address, ip, port)) {
281
+ peers.push_back(Peer(ip, port));
282
+ }
283
+ }
284
+
285
+ // Auto-connect to discovered peers
286
+ if (!peers.empty()) {
287
+ handle_dht_peer_discovery(peers, info_hash);
288
+ }
289
+ })) {
290
+ LOG_CLIENT_DEBUG("Successfully started announce with peer discovery for discovery hash");
291
+ } else {
292
+ LOG_CLIENT_WARN("Failed to announce peer for discovery");
293
+ }
294
+ }
295
+
296
+ std::string RatsClient::get_discovery_hash() const {
297
+ std::lock_guard<std::mutex> lock(protocol_config_mutex_);
298
+ // Generate discovery hash based on current protocol configuration
299
+ std::string discovery_string = custom_protocol_name_ + "_peer_discovery_v" + custom_protocol_version_;
300
+ return SHA1::hash(discovery_string);
301
+ }
302
+
303
+ std::string RatsClient::get_rats_peer_discovery_hash() {
304
+ // Well-known hash for rats peer discovery
305
+ // Compute SHA1 hash of "rats_peer_discovery_v1.0"
306
+ return SHA1::hash("rats_peer_discovery_v1.0");
307
+ }
308
+
309
+ }