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,1110 @@
|
|
|
1
|
+
#include "tracker.h"
|
|
2
|
+
#include "bencode.h"
|
|
3
|
+
#include "logger.h"
|
|
4
|
+
#include <sstream>
|
|
5
|
+
#include <iomanip>
|
|
6
|
+
#include <random>
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
|
|
10
|
+
#define LOG_TRACKER_DEBUG(message) LOG_DEBUG("tracker", message)
|
|
11
|
+
#define LOG_TRACKER_INFO(message) LOG_INFO("tracker", message)
|
|
12
|
+
#define LOG_TRACKER_WARN(message) LOG_WARN("tracker", message)
|
|
13
|
+
#define LOG_TRACKER_ERROR(message) LOG_ERROR("tracker", message)
|
|
14
|
+
|
|
15
|
+
namespace librats {
|
|
16
|
+
|
|
17
|
+
//=============================================================================
|
|
18
|
+
// Utility Functions
|
|
19
|
+
//=============================================================================
|
|
20
|
+
|
|
21
|
+
std::string tracker_event_to_string(TrackerEvent event) {
|
|
22
|
+
switch (event) {
|
|
23
|
+
case TrackerEvent::STARTED: return "started";
|
|
24
|
+
case TrackerEvent::STOPPED: return "stopped";
|
|
25
|
+
case TrackerEvent::COMPLETED: return "completed";
|
|
26
|
+
case TrackerEvent::NONE:
|
|
27
|
+
default: return "";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
TrackerEvent string_to_tracker_event(const std::string& event_str) {
|
|
32
|
+
if (event_str == "started") return TrackerEvent::STARTED;
|
|
33
|
+
if (event_str == "stopped") return TrackerEvent::STOPPED;
|
|
34
|
+
if (event_str == "completed") return TrackerEvent::COMPLETED;
|
|
35
|
+
return TrackerEvent::NONE;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//=============================================================================
|
|
39
|
+
// HttpTrackerClient Implementation
|
|
40
|
+
//=============================================================================
|
|
41
|
+
|
|
42
|
+
HttpTrackerClient::HttpTrackerClient(const std::string& tracker_url)
|
|
43
|
+
: tracker_url_(tracker_url), interval_(1800), is_working_(true) {
|
|
44
|
+
LOG_TRACKER_INFO("Created HTTP tracker client for: " << tracker_url_);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
HttpTrackerClient::~HttpTrackerClient() = default;
|
|
48
|
+
|
|
49
|
+
bool HttpTrackerClient::announce(const TrackerRequest& request, TrackerResponseCallback callback) {
|
|
50
|
+
LOG_TRACKER_INFO("Announcing to HTTP tracker: " << tracker_url_);
|
|
51
|
+
|
|
52
|
+
std::string announce_url = build_announce_url(request);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
std::vector<uint8_t> response_data = http_get(announce_url);
|
|
56
|
+
|
|
57
|
+
if (response_data.empty()) {
|
|
58
|
+
LOG_TRACKER_ERROR("Empty response from tracker: " << tracker_url_);
|
|
59
|
+
is_working_ = false;
|
|
60
|
+
|
|
61
|
+
TrackerResponse error_response;
|
|
62
|
+
error_response.success = false;
|
|
63
|
+
error_response.failure_reason = "Empty response from tracker";
|
|
64
|
+
if (callback) callback(error_response, tracker_url_);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
TrackerResponse response = parse_response(response_data);
|
|
69
|
+
|
|
70
|
+
if (response.success) {
|
|
71
|
+
last_announce_time_ = std::chrono::steady_clock::now();
|
|
72
|
+
interval_ = response.interval;
|
|
73
|
+
if (!response.tracker_id.empty()) {
|
|
74
|
+
tracker_id_ = response.tracker_id;
|
|
75
|
+
}
|
|
76
|
+
is_working_ = true;
|
|
77
|
+
|
|
78
|
+
LOG_TRACKER_INFO("Announce successful. Peers: " << response.peers.size()
|
|
79
|
+
<< ", Seeders: " << response.complete
|
|
80
|
+
<< ", Leechers: " << response.incomplete);
|
|
81
|
+
} else {
|
|
82
|
+
LOG_TRACKER_ERROR("Tracker announce failed: " << response.failure_reason);
|
|
83
|
+
is_working_ = false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (callback) {
|
|
87
|
+
callback(response, tracker_url_);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return response.success;
|
|
91
|
+
|
|
92
|
+
} catch (const std::exception& e) {
|
|
93
|
+
LOG_TRACKER_ERROR("Exception during announce: " << e.what());
|
|
94
|
+
is_working_ = false;
|
|
95
|
+
|
|
96
|
+
TrackerResponse error_response;
|
|
97
|
+
error_response.success = false;
|
|
98
|
+
error_response.failure_reason = std::string("Exception: ") + e.what();
|
|
99
|
+
if (callback) callback(error_response, tracker_url_);
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
bool HttpTrackerClient::scrape(const std::vector<InfoHash>& info_hashes, TrackerResponseCallback callback) {
|
|
106
|
+
LOG_TRACKER_INFO("Scraping HTTP tracker: " << tracker_url_);
|
|
107
|
+
|
|
108
|
+
std::string scrape_url = build_scrape_url(info_hashes);
|
|
109
|
+
if (scrape_url.empty()) {
|
|
110
|
+
LOG_TRACKER_WARN("Cannot build scrape URL for tracker: " << tracker_url_);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
std::vector<uint8_t> response_data = http_get(scrape_url);
|
|
116
|
+
|
|
117
|
+
if (response_data.empty()) {
|
|
118
|
+
LOG_TRACKER_ERROR("Empty scrape response from tracker: " << tracker_url_);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
TrackerResponse response = parse_response(response_data);
|
|
123
|
+
|
|
124
|
+
if (callback) {
|
|
125
|
+
callback(response, tracker_url_);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return response.success;
|
|
129
|
+
|
|
130
|
+
} catch (const std::exception& e) {
|
|
131
|
+
LOG_TRACKER_ERROR("Exception during scrape: " << e.what());
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
std::string HttpTrackerClient::build_announce_url(const TrackerRequest& request) {
|
|
137
|
+
std::ostringstream url;
|
|
138
|
+
url << tracker_url_;
|
|
139
|
+
|
|
140
|
+
// Add query separator
|
|
141
|
+
if (tracker_url_.find('?') == std::string::npos) {
|
|
142
|
+
url << "?";
|
|
143
|
+
} else {
|
|
144
|
+
url << "&";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Required parameters
|
|
148
|
+
url << "info_hash=" << url_encode_binary(request.info_hash.data(), request.info_hash.size());
|
|
149
|
+
url << "&peer_id=" << url_encode_binary(request.peer_id.data(), request.peer_id.size());
|
|
150
|
+
url << "&port=" << request.port;
|
|
151
|
+
url << "&uploaded=" << request.uploaded;
|
|
152
|
+
url << "&downloaded=" << request.downloaded;
|
|
153
|
+
url << "&left=" << request.left;
|
|
154
|
+
url << "&compact=1"; // Request compact peer list (BEP 23)
|
|
155
|
+
|
|
156
|
+
// Optional parameters
|
|
157
|
+
if (request.event != TrackerEvent::NONE) {
|
|
158
|
+
url << "&event=" << tracker_event_to_string(request.event);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!request.ip.empty()) {
|
|
162
|
+
url << "&ip=" << url_encode(request.ip);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (request.numwant > 0) {
|
|
166
|
+
url << "&numwant=" << request.numwant;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!request.tracker_id.empty()) {
|
|
170
|
+
url << "&trackerid=" << url_encode(request.tracker_id);
|
|
171
|
+
} else if (!tracker_id_.empty()) {
|
|
172
|
+
url << "&trackerid=" << url_encode(tracker_id_);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return url.str();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
std::string HttpTrackerClient::build_scrape_url(const std::vector<InfoHash>& info_hashes) {
|
|
179
|
+
// Convert announce URL to scrape URL by replacing "announce" with "scrape"
|
|
180
|
+
std::string scrape_url = tracker_url_;
|
|
181
|
+
|
|
182
|
+
size_t announce_pos = scrape_url.find("announce");
|
|
183
|
+
if (announce_pos == std::string::npos) {
|
|
184
|
+
return ""; // Cannot build scrape URL
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
scrape_url.replace(announce_pos, 8, "scrape");
|
|
188
|
+
|
|
189
|
+
// Add info_hash parameters
|
|
190
|
+
bool first = true;
|
|
191
|
+
for (const auto& info_hash : info_hashes) {
|
|
192
|
+
if (first) {
|
|
193
|
+
scrape_url += "?";
|
|
194
|
+
first = false;
|
|
195
|
+
} else {
|
|
196
|
+
scrape_url += "&";
|
|
197
|
+
}
|
|
198
|
+
scrape_url += "info_hash=" + url_encode_binary(info_hash.data(), info_hash.size());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return scrape_url;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
TrackerResponse HttpTrackerClient::parse_response(const std::vector<uint8_t>& data) {
|
|
205
|
+
TrackerResponse response;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
BencodeValue tracker_response = bencode::decode(data);
|
|
209
|
+
|
|
210
|
+
if (!tracker_response.is_dict()) {
|
|
211
|
+
response.failure_reason = "Invalid tracker response format";
|
|
212
|
+
return response;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check for failure reason
|
|
216
|
+
if (tracker_response.has_key("failure reason")) {
|
|
217
|
+
response.failure_reason = tracker_response["failure reason"].as_string();
|
|
218
|
+
response.success = false;
|
|
219
|
+
return response;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Parse warning message
|
|
223
|
+
if (tracker_response.has_key("warning message")) {
|
|
224
|
+
response.warning_message = tracker_response["warning message"].as_string();
|
|
225
|
+
LOG_TRACKER_WARN("Tracker warning: " << response.warning_message);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Parse interval
|
|
229
|
+
if (tracker_response.has_key("interval")) {
|
|
230
|
+
response.interval = static_cast<uint32_t>(tracker_response["interval"].as_integer());
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Parse min interval
|
|
234
|
+
if (tracker_response.has_key("min interval")) {
|
|
235
|
+
response.min_interval = static_cast<uint32_t>(tracker_response["min interval"].as_integer());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Parse tracker ID
|
|
239
|
+
if (tracker_response.has_key("tracker id")) {
|
|
240
|
+
response.tracker_id = tracker_response["tracker id"].as_string();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Parse complete (seeders)
|
|
244
|
+
if (tracker_response.has_key("complete")) {
|
|
245
|
+
response.complete = static_cast<uint32_t>(tracker_response["complete"].as_integer());
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Parse incomplete (leechers)
|
|
249
|
+
if (tracker_response.has_key("incomplete")) {
|
|
250
|
+
response.incomplete = static_cast<uint32_t>(tracker_response["incomplete"].as_integer());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Parse peers
|
|
254
|
+
if (tracker_response.has_key("peers")) {
|
|
255
|
+
const auto& peers_value = tracker_response["peers"];
|
|
256
|
+
|
|
257
|
+
if (peers_value.is_string()) {
|
|
258
|
+
// Compact peer list (BEP 23)
|
|
259
|
+
response.peers = parse_compact_peers(peers_value.as_string());
|
|
260
|
+
} else if (peers_value.is_list()) {
|
|
261
|
+
// Dictionary peer list
|
|
262
|
+
response.peers = parse_dict_peers(peers_value);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
response.success = true;
|
|
267
|
+
|
|
268
|
+
} catch (const std::exception& e) {
|
|
269
|
+
LOG_TRACKER_ERROR("Failed to parse tracker response: " << e.what());
|
|
270
|
+
response.failure_reason = std::string("Parse error: ") + e.what();
|
|
271
|
+
response.success = false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return response;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
std::vector<Peer> HttpTrackerClient::parse_compact_peers(const std::string& peer_data) {
|
|
278
|
+
std::vector<Peer> peers;
|
|
279
|
+
|
|
280
|
+
// Each peer is 6 bytes: 4 bytes IP + 2 bytes port
|
|
281
|
+
if (peer_data.length() % 6 != 0) {
|
|
282
|
+
LOG_TRACKER_WARN("Invalid compact peer list length: " << peer_data.length());
|
|
283
|
+
return peers;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (size_t i = 0; i < peer_data.length(); i += 6) {
|
|
287
|
+
const uint8_t* peer_bytes = reinterpret_cast<const uint8_t*>(peer_data.data() + i);
|
|
288
|
+
|
|
289
|
+
// Extract IP address
|
|
290
|
+
std::ostringstream ip_stream;
|
|
291
|
+
ip_stream << static_cast<int>(peer_bytes[0]) << "."
|
|
292
|
+
<< static_cast<int>(peer_bytes[1]) << "."
|
|
293
|
+
<< static_cast<int>(peer_bytes[2]) << "."
|
|
294
|
+
<< static_cast<int>(peer_bytes[3]);
|
|
295
|
+
|
|
296
|
+
// Extract port (big-endian)
|
|
297
|
+
uint16_t port = (peer_bytes[4] << 8) | peer_bytes[5];
|
|
298
|
+
|
|
299
|
+
peers.emplace_back(ip_stream.str(), port);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
LOG_TRACKER_DEBUG("Parsed " << peers.size() << " compact peers");
|
|
303
|
+
return peers;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
std::vector<Peer> HttpTrackerClient::parse_dict_peers(const BencodeValue& peers_list) {
|
|
307
|
+
std::vector<Peer> peers;
|
|
308
|
+
|
|
309
|
+
for (size_t i = 0; i < peers_list.size(); ++i) {
|
|
310
|
+
const auto& peer_dict = peers_list[i];
|
|
311
|
+
|
|
312
|
+
if (!peer_dict.is_dict()) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!peer_dict.has_key("ip") || !peer_dict.has_key("port")) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
std::string ip = peer_dict["ip"].as_string();
|
|
321
|
+
uint16_t port = static_cast<uint16_t>(peer_dict["port"].as_integer());
|
|
322
|
+
|
|
323
|
+
peers.emplace_back(ip, port);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
LOG_TRACKER_DEBUG("Parsed " << peers.size() << " dictionary peers");
|
|
327
|
+
return peers;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
std::vector<uint8_t> HttpTrackerClient::http_get(const std::string& url) {
|
|
331
|
+
LOG_TRACKER_DEBUG("HTTP GET: " << url);
|
|
332
|
+
|
|
333
|
+
// Parse URL to extract host, port, and path
|
|
334
|
+
std::string protocol, host, path;
|
|
335
|
+
uint16_t port = 80;
|
|
336
|
+
|
|
337
|
+
size_t protocol_end = url.find("://");
|
|
338
|
+
if (protocol_end != std::string::npos) {
|
|
339
|
+
protocol = url.substr(0, protocol_end);
|
|
340
|
+
size_t host_start = protocol_end + 3;
|
|
341
|
+
size_t path_start = url.find('/', host_start);
|
|
342
|
+
|
|
343
|
+
if (path_start != std::string::npos) {
|
|
344
|
+
std::string host_port = url.substr(host_start, path_start - host_start);
|
|
345
|
+
path = url.substr(path_start);
|
|
346
|
+
|
|
347
|
+
size_t port_pos = host_port.find(':');
|
|
348
|
+
if (port_pos != std::string::npos) {
|
|
349
|
+
host = host_port.substr(0, port_pos);
|
|
350
|
+
port = static_cast<uint16_t>(std::stoi(host_port.substr(port_pos + 1)));
|
|
351
|
+
} else {
|
|
352
|
+
host = host_port;
|
|
353
|
+
port = (protocol == "https") ? 443 : 80;
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
host = url.substr(host_start);
|
|
357
|
+
path = "/";
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
LOG_TRACKER_ERROR("Invalid URL format: " << url);
|
|
361
|
+
return std::vector<uint8_t>();
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Create TCP connection
|
|
365
|
+
socket_t socket = create_tcp_client(host, port, 15000); // 15 second timeout
|
|
366
|
+
if (!is_valid_socket(socket)) {
|
|
367
|
+
LOG_TRACKER_ERROR("Failed to connect to tracker: " << host << ":" << port);
|
|
368
|
+
return std::vector<uint8_t>();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Build HTTP request
|
|
372
|
+
std::ostringstream request;
|
|
373
|
+
request << "GET " << path << " HTTP/1.0\r\n";
|
|
374
|
+
request << "Host: " << host << "\r\n";
|
|
375
|
+
request << "User-Agent: librats/1.0\r\n";
|
|
376
|
+
request << "Accept: */*\r\n";
|
|
377
|
+
request << "Connection: close\r\n";
|
|
378
|
+
request << "\r\n";
|
|
379
|
+
|
|
380
|
+
std::string request_str = request.str();
|
|
381
|
+
|
|
382
|
+
// Send request
|
|
383
|
+
if (send_tcp_string(socket, request_str) <= 0) {
|
|
384
|
+
LOG_TRACKER_ERROR("Failed to send HTTP request");
|
|
385
|
+
close_socket(socket);
|
|
386
|
+
return std::vector<uint8_t>();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Receive response
|
|
390
|
+
std::vector<uint8_t> response_data;
|
|
391
|
+
bool headers_complete = false;
|
|
392
|
+
size_t content_start = 0;
|
|
393
|
+
|
|
394
|
+
while (true) {
|
|
395
|
+
std::vector<uint8_t> chunk = receive_tcp_data(socket, 4096);
|
|
396
|
+
if (chunk.empty()) {
|
|
397
|
+
break; // Connection closed or error
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
response_data.insert(response_data.end(), chunk.begin(), chunk.end());
|
|
401
|
+
|
|
402
|
+
// Find end of headers
|
|
403
|
+
if (!headers_complete) {
|
|
404
|
+
std::string response_str(response_data.begin(), response_data.end());
|
|
405
|
+
size_t header_end = response_str.find("\r\n\r\n");
|
|
406
|
+
if (header_end != std::string::npos) {
|
|
407
|
+
headers_complete = true;
|
|
408
|
+
content_start = header_end + 4;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
close_socket(socket);
|
|
414
|
+
|
|
415
|
+
if (!headers_complete || content_start >= response_data.size()) {
|
|
416
|
+
LOG_TRACKER_ERROR("Invalid HTTP response");
|
|
417
|
+
return std::vector<uint8_t>();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Extract body
|
|
421
|
+
std::vector<uint8_t> body(response_data.begin() + content_start, response_data.end());
|
|
422
|
+
|
|
423
|
+
LOG_TRACKER_DEBUG("HTTP response body size: " << body.size() << " bytes");
|
|
424
|
+
return body;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
std::string HttpTrackerClient::url_encode(const std::string& str) {
|
|
428
|
+
std::ostringstream escaped;
|
|
429
|
+
escaped.fill('0');
|
|
430
|
+
escaped << std::hex;
|
|
431
|
+
|
|
432
|
+
for (char c : str) {
|
|
433
|
+
// Keep alphanumeric and other safe characters
|
|
434
|
+
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
|
435
|
+
escaped << c;
|
|
436
|
+
} else {
|
|
437
|
+
// Percent-encode
|
|
438
|
+
escaped << '%' << std::setw(2) << int(static_cast<unsigned char>(c));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return escaped.str();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
std::string HttpTrackerClient::url_encode_binary(const uint8_t* data, size_t len) {
|
|
446
|
+
std::ostringstream escaped;
|
|
447
|
+
escaped.fill('0');
|
|
448
|
+
escaped << std::hex << std::uppercase;
|
|
449
|
+
|
|
450
|
+
for (size_t i = 0; i < len; ++i) {
|
|
451
|
+
uint8_t c = data[i];
|
|
452
|
+
// Keep alphanumeric and other safe characters
|
|
453
|
+
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
|
454
|
+
escaped << static_cast<char>(c);
|
|
455
|
+
} else {
|
|
456
|
+
// Percent-encode
|
|
457
|
+
escaped << '%' << std::setw(2) << static_cast<int>(c);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return escaped.str();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
//=============================================================================
|
|
465
|
+
// UdpTrackerClient Implementation
|
|
466
|
+
//=============================================================================
|
|
467
|
+
|
|
468
|
+
UdpTrackerClient::UdpTrackerClient(const std::string& tracker_url)
|
|
469
|
+
: tracker_url_(tracker_url), port_(0), socket_(INVALID_SOCKET_VALUE),
|
|
470
|
+
interval_(1800), is_working_(true), connection_id_(0) {
|
|
471
|
+
|
|
472
|
+
if (parse_url()) {
|
|
473
|
+
LOG_TRACKER_INFO("Created UDP tracker client for: " << hostname_ << ":" << port_);
|
|
474
|
+
} else {
|
|
475
|
+
LOG_TRACKER_ERROR("Failed to parse UDP tracker URL: " << tracker_url_);
|
|
476
|
+
is_working_ = false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
UdpTrackerClient::~UdpTrackerClient() {
|
|
481
|
+
std::lock_guard<std::mutex> lock(socket_mutex_);
|
|
482
|
+
if (is_valid_socket(socket_)) {
|
|
483
|
+
close_socket(socket_);
|
|
484
|
+
socket_ = INVALID_SOCKET_VALUE;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
bool UdpTrackerClient::parse_url() {
|
|
489
|
+
// Parse udp://hostname:port
|
|
490
|
+
if (tracker_url_.substr(0, 6) != "udp://") {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
std::string host_port = tracker_url_.substr(6);
|
|
495
|
+
size_t colon_pos = host_port.find(':');
|
|
496
|
+
|
|
497
|
+
if (colon_pos == std::string::npos) {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
hostname_ = host_port.substr(0, colon_pos);
|
|
502
|
+
|
|
503
|
+
// Extract port and remove any path
|
|
504
|
+
std::string port_str = host_port.substr(colon_pos + 1);
|
|
505
|
+
size_t slash_pos = port_str.find('/');
|
|
506
|
+
if (slash_pos != std::string::npos) {
|
|
507
|
+
port_str = port_str.substr(0, slash_pos);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
port_ = static_cast<uint16_t>(std::stoi(port_str));
|
|
512
|
+
} catch (const std::exception&) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
bool UdpTrackerClient::connect() {
|
|
520
|
+
// Create UDP socket if needed (protected by mutex)
|
|
521
|
+
{
|
|
522
|
+
std::lock_guard<std::mutex> lock(socket_mutex_);
|
|
523
|
+
if (!is_valid_socket(socket_)) {
|
|
524
|
+
socket_ = create_udp_socket();
|
|
525
|
+
if (!is_valid_socket(socket_)) {
|
|
526
|
+
LOG_TRACKER_ERROR("Failed to create UDP socket for tracker");
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
LOG_TRACKER_DEBUG("Connecting to UDP tracker: " << hostname_ << ":" << port_);
|
|
533
|
+
|
|
534
|
+
uint32_t transaction_id = generate_transaction_id();
|
|
535
|
+
std::vector<uint8_t> connect_request = build_connect_request(transaction_id);
|
|
536
|
+
|
|
537
|
+
// Send connect request and receive response (send_request handles its own locking)
|
|
538
|
+
std::vector<uint8_t> response = send_request(connect_request, 15000);
|
|
539
|
+
|
|
540
|
+
if (response.empty()) {
|
|
541
|
+
LOG_TRACKER_ERROR("No response from UDP tracker");
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
if (!parse_connect_response(response, transaction_id)) {
|
|
546
|
+
LOG_TRACKER_ERROR("Failed to parse connect response");
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Connection is valid for 1 minute
|
|
551
|
+
connection_expire_time_ = std::chrono::steady_clock::now() + std::chrono::seconds(60);
|
|
552
|
+
|
|
553
|
+
LOG_TRACKER_INFO("Successfully connected to UDP tracker");
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
bool UdpTrackerClient::is_connection_valid() {
|
|
558
|
+
return connection_id_ != 0 &&
|
|
559
|
+
std::chrono::steady_clock::now() < connection_expire_time_;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
bool UdpTrackerClient::announce(const TrackerRequest& request, TrackerResponseCallback callback) {
|
|
563
|
+
LOG_TRACKER_INFO("Announcing to UDP tracker: " << tracker_url_);
|
|
564
|
+
|
|
565
|
+
// Connect if needed
|
|
566
|
+
if (!is_connection_valid()) {
|
|
567
|
+
if (!connect()) {
|
|
568
|
+
is_working_ = false;
|
|
569
|
+
|
|
570
|
+
TrackerResponse error_response;
|
|
571
|
+
error_response.success = false;
|
|
572
|
+
error_response.failure_reason = "Failed to connect to UDP tracker";
|
|
573
|
+
if (callback) callback(error_response, tracker_url_);
|
|
574
|
+
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
uint32_t transaction_id = generate_transaction_id();
|
|
580
|
+
std::vector<uint8_t> announce_request = build_announce_request(request, transaction_id);
|
|
581
|
+
|
|
582
|
+
// Send announce request
|
|
583
|
+
std::vector<uint8_t> response = send_request(announce_request, 15000);
|
|
584
|
+
|
|
585
|
+
if (response.empty()) {
|
|
586
|
+
LOG_TRACKER_ERROR("No response from UDP tracker announce");
|
|
587
|
+
is_working_ = false;
|
|
588
|
+
|
|
589
|
+
TrackerResponse error_response;
|
|
590
|
+
error_response.success = false;
|
|
591
|
+
error_response.failure_reason = "No response from tracker";
|
|
592
|
+
if (callback) callback(error_response, tracker_url_);
|
|
593
|
+
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Check for error response
|
|
598
|
+
if (response.size() >= 8) {
|
|
599
|
+
uint32_t action = read_uint32_be(response.data());
|
|
600
|
+
if (action == ACTION_ERROR) {
|
|
601
|
+
std::string error_msg = parse_error_response(response);
|
|
602
|
+
LOG_TRACKER_ERROR("UDP tracker error: " << error_msg);
|
|
603
|
+
is_working_ = false;
|
|
604
|
+
|
|
605
|
+
TrackerResponse error_response;
|
|
606
|
+
error_response.success = false;
|
|
607
|
+
error_response.failure_reason = error_msg;
|
|
608
|
+
if (callback) callback(error_response, tracker_url_);
|
|
609
|
+
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
TrackerResponse tracker_response = parse_announce_response(response, transaction_id);
|
|
615
|
+
|
|
616
|
+
if (tracker_response.success) {
|
|
617
|
+
last_announce_time_ = std::chrono::steady_clock::now();
|
|
618
|
+
interval_ = tracker_response.interval;
|
|
619
|
+
is_working_ = true;
|
|
620
|
+
|
|
621
|
+
LOG_TRACKER_INFO("UDP announce successful. Peers: " << tracker_response.peers.size()
|
|
622
|
+
<< ", Seeders: " << tracker_response.complete
|
|
623
|
+
<< ", Leechers: " << tracker_response.incomplete);
|
|
624
|
+
} else {
|
|
625
|
+
LOG_TRACKER_ERROR("UDP tracker announce failed: " << tracker_response.failure_reason);
|
|
626
|
+
is_working_ = false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (callback) {
|
|
630
|
+
callback(tracker_response, tracker_url_);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return tracker_response.success;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
bool UdpTrackerClient::scrape(const std::vector<InfoHash>& info_hashes, TrackerResponseCallback callback) {
|
|
637
|
+
LOG_TRACKER_INFO("Scraping UDP tracker: " << tracker_url_);
|
|
638
|
+
|
|
639
|
+
// Connect if needed
|
|
640
|
+
if (!is_connection_valid()) {
|
|
641
|
+
if (!connect()) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
uint32_t transaction_id = generate_transaction_id();
|
|
647
|
+
std::vector<uint8_t> scrape_request = build_scrape_request(info_hashes, transaction_id);
|
|
648
|
+
|
|
649
|
+
// Send scrape request
|
|
650
|
+
std::vector<uint8_t> response = send_request(scrape_request, 15000);
|
|
651
|
+
|
|
652
|
+
if (response.empty()) {
|
|
653
|
+
LOG_TRACKER_ERROR("No response from UDP tracker scrape");
|
|
654
|
+
return false;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
TrackerResponse tracker_response = parse_scrape_response(response, transaction_id);
|
|
658
|
+
|
|
659
|
+
if (callback) {
|
|
660
|
+
callback(tracker_response, tracker_url_);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return tracker_response.success;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
std::vector<uint8_t> UdpTrackerClient::send_request(const std::vector<uint8_t>& request, int timeout_ms) {
|
|
667
|
+
std::lock_guard<std::mutex> lock(socket_mutex_);
|
|
668
|
+
|
|
669
|
+
if (!is_valid_socket(socket_)) {
|
|
670
|
+
return std::vector<uint8_t>();
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Send request
|
|
674
|
+
if (send_udp_data_to(socket_, request, hostname_, port_) <= 0) {
|
|
675
|
+
return std::vector<uint8_t>();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Receive response with timeout
|
|
679
|
+
std::vector<uint8_t> response = receive_udp_data_with_timeout(socket_, 2048, timeout_ms);
|
|
680
|
+
|
|
681
|
+
return response;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
std::vector<uint8_t> UdpTrackerClient::build_connect_request(uint32_t transaction_id) {
|
|
685
|
+
std::vector<uint8_t> request(16);
|
|
686
|
+
|
|
687
|
+
// Protocol ID (64-bit)
|
|
688
|
+
write_int64_be(request.data(), PROTOCOL_ID);
|
|
689
|
+
|
|
690
|
+
// Action: connect (32-bit)
|
|
691
|
+
write_uint32_be(request.data() + 8, ACTION_CONNECT);
|
|
692
|
+
|
|
693
|
+
// Transaction ID (32-bit)
|
|
694
|
+
write_uint32_be(request.data() + 12, transaction_id);
|
|
695
|
+
|
|
696
|
+
return request;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
std::vector<uint8_t> UdpTrackerClient::build_announce_request(const TrackerRequest& request, uint32_t transaction_id) {
|
|
700
|
+
std::vector<uint8_t> announce_req(98);
|
|
701
|
+
|
|
702
|
+
size_t offset = 0;
|
|
703
|
+
|
|
704
|
+
// Connection ID (64-bit)
|
|
705
|
+
write_int64_be(announce_req.data() + offset, connection_id_);
|
|
706
|
+
offset += 8;
|
|
707
|
+
|
|
708
|
+
// Action: announce (32-bit)
|
|
709
|
+
write_uint32_be(announce_req.data() + offset, ACTION_ANNOUNCE);
|
|
710
|
+
offset += 4;
|
|
711
|
+
|
|
712
|
+
// Transaction ID (32-bit)
|
|
713
|
+
write_uint32_be(announce_req.data() + offset, transaction_id);
|
|
714
|
+
offset += 4;
|
|
715
|
+
|
|
716
|
+
// Info hash (20 bytes)
|
|
717
|
+
std::memcpy(announce_req.data() + offset, request.info_hash.data(), 20);
|
|
718
|
+
offset += 20;
|
|
719
|
+
|
|
720
|
+
// Peer ID (20 bytes)
|
|
721
|
+
std::memcpy(announce_req.data() + offset, request.peer_id.data(), 20);
|
|
722
|
+
offset += 20;
|
|
723
|
+
|
|
724
|
+
// Downloaded (64-bit)
|
|
725
|
+
write_int64_be(announce_req.data() + offset, request.downloaded);
|
|
726
|
+
offset += 8;
|
|
727
|
+
|
|
728
|
+
// Left (64-bit)
|
|
729
|
+
write_int64_be(announce_req.data() + offset, request.left);
|
|
730
|
+
offset += 8;
|
|
731
|
+
|
|
732
|
+
// Uploaded (64-bit)
|
|
733
|
+
write_int64_be(announce_req.data() + offset, request.uploaded);
|
|
734
|
+
offset += 8;
|
|
735
|
+
|
|
736
|
+
// Event (32-bit)
|
|
737
|
+
write_uint32_be(announce_req.data() + offset, static_cast<uint32_t>(request.event));
|
|
738
|
+
offset += 4;
|
|
739
|
+
|
|
740
|
+
// IP address (32-bit, 0 for default)
|
|
741
|
+
write_uint32_be(announce_req.data() + offset, 0);
|
|
742
|
+
offset += 4;
|
|
743
|
+
|
|
744
|
+
// Key (32-bit, random)
|
|
745
|
+
write_uint32_be(announce_req.data() + offset, generate_transaction_id());
|
|
746
|
+
offset += 4;
|
|
747
|
+
|
|
748
|
+
// Num want (32-bit, -1 for default)
|
|
749
|
+
write_uint32_be(announce_req.data() + offset, request.numwant > 0 ? request.numwant : 50);
|
|
750
|
+
offset += 4;
|
|
751
|
+
|
|
752
|
+
// Port (16-bit)
|
|
753
|
+
announce_req[offset] = (request.port >> 8) & 0xFF;
|
|
754
|
+
announce_req[offset + 1] = request.port & 0xFF;
|
|
755
|
+
|
|
756
|
+
return announce_req;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
std::vector<uint8_t> UdpTrackerClient::build_scrape_request(const std::vector<InfoHash>& info_hashes, uint32_t transaction_id) {
|
|
760
|
+
std::vector<uint8_t> scrape_req(16 + info_hashes.size() * 20);
|
|
761
|
+
|
|
762
|
+
size_t offset = 0;
|
|
763
|
+
|
|
764
|
+
// Connection ID (64-bit)
|
|
765
|
+
write_int64_be(scrape_req.data() + offset, connection_id_);
|
|
766
|
+
offset += 8;
|
|
767
|
+
|
|
768
|
+
// Action: scrape (32-bit)
|
|
769
|
+
write_uint32_be(scrape_req.data() + offset, ACTION_SCRAPE);
|
|
770
|
+
offset += 4;
|
|
771
|
+
|
|
772
|
+
// Transaction ID (32-bit)
|
|
773
|
+
write_uint32_be(scrape_req.data() + offset, transaction_id);
|
|
774
|
+
offset += 4;
|
|
775
|
+
|
|
776
|
+
// Info hashes (20 bytes each)
|
|
777
|
+
for (const auto& info_hash : info_hashes) {
|
|
778
|
+
std::memcpy(scrape_req.data() + offset, info_hash.data(), 20);
|
|
779
|
+
offset += 20;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return scrape_req;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
bool UdpTrackerClient::parse_connect_response(const std::vector<uint8_t>& data, uint32_t expected_transaction_id) {
|
|
786
|
+
if (data.size() < 16) {
|
|
787
|
+
LOG_TRACKER_ERROR("Invalid connect response size: " << data.size());
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
uint32_t action = read_uint32_be(data.data());
|
|
792
|
+
uint32_t transaction_id = read_uint32_be(data.data() + 4);
|
|
793
|
+
|
|
794
|
+
if (action != ACTION_CONNECT) {
|
|
795
|
+
LOG_TRACKER_ERROR("Invalid action in connect response: " << action);
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (transaction_id != expected_transaction_id) {
|
|
800
|
+
LOG_TRACKER_ERROR("Transaction ID mismatch in connect response");
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
connection_id_ = read_int64_be(data.data() + 8);
|
|
805
|
+
|
|
806
|
+
LOG_TRACKER_DEBUG("Received connection ID: " << connection_id_);
|
|
807
|
+
return true;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
TrackerResponse UdpTrackerClient::parse_announce_response(const std::vector<uint8_t>& data, uint32_t expected_transaction_id) {
|
|
811
|
+
TrackerResponse response;
|
|
812
|
+
|
|
813
|
+
if (data.size() < 20) {
|
|
814
|
+
response.failure_reason = "Invalid announce response size";
|
|
815
|
+
return response;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
uint32_t action = read_uint32_be(data.data());
|
|
819
|
+
uint32_t transaction_id = read_uint32_be(data.data() + 4);
|
|
820
|
+
|
|
821
|
+
if (action != ACTION_ANNOUNCE) {
|
|
822
|
+
response.failure_reason = "Invalid action in announce response";
|
|
823
|
+
return response;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (transaction_id != expected_transaction_id) {
|
|
827
|
+
response.failure_reason = "Transaction ID mismatch";
|
|
828
|
+
return response;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Parse response fields
|
|
832
|
+
response.interval = read_uint32_be(data.data() + 8);
|
|
833
|
+
response.incomplete = read_uint32_be(data.data() + 12);
|
|
834
|
+
response.complete = read_uint32_be(data.data() + 16);
|
|
835
|
+
|
|
836
|
+
// Parse peer list (6 bytes per peer: 4 bytes IP + 2 bytes port)
|
|
837
|
+
size_t peers_offset = 20;
|
|
838
|
+
while (peers_offset + 6 <= data.size()) {
|
|
839
|
+
const uint8_t* peer_data = data.data() + peers_offset;
|
|
840
|
+
|
|
841
|
+
std::ostringstream ip_stream;
|
|
842
|
+
ip_stream << static_cast<int>(peer_data[0]) << "."
|
|
843
|
+
<< static_cast<int>(peer_data[1]) << "."
|
|
844
|
+
<< static_cast<int>(peer_data[2]) << "."
|
|
845
|
+
<< static_cast<int>(peer_data[3]);
|
|
846
|
+
|
|
847
|
+
uint16_t port = (peer_data[4] << 8) | peer_data[5];
|
|
848
|
+
|
|
849
|
+
response.peers.emplace_back(ip_stream.str(), port);
|
|
850
|
+
peers_offset += 6;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
response.success = true;
|
|
854
|
+
return response;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
TrackerResponse UdpTrackerClient::parse_scrape_response(const std::vector<uint8_t>& data, uint32_t expected_transaction_id) {
|
|
858
|
+
TrackerResponse response;
|
|
859
|
+
|
|
860
|
+
if (data.size() < 8) {
|
|
861
|
+
response.failure_reason = "Invalid scrape response size";
|
|
862
|
+
return response;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
uint32_t action = read_uint32_be(data.data());
|
|
866
|
+
uint32_t transaction_id = read_uint32_be(data.data() + 4);
|
|
867
|
+
|
|
868
|
+
if (action != ACTION_SCRAPE) {
|
|
869
|
+
response.failure_reason = "Invalid action in scrape response";
|
|
870
|
+
return response;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
if (transaction_id != expected_transaction_id) {
|
|
874
|
+
response.failure_reason = "Transaction ID mismatch";
|
|
875
|
+
return response;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Parse scrape data (12 bytes per torrent)
|
|
879
|
+
if (data.size() >= 20) {
|
|
880
|
+
response.complete = read_uint32_be(data.data() + 8);
|
|
881
|
+
response.downloaded = read_uint32_be(data.data() + 12);
|
|
882
|
+
response.incomplete = read_uint32_be(data.data() + 16);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
response.success = true;
|
|
886
|
+
return response;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
std::string UdpTrackerClient::parse_error_response(const std::vector<uint8_t>& data) {
|
|
890
|
+
if (data.size() < 8) {
|
|
891
|
+
return "Unknown error";
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
// Error message starts at offset 8
|
|
895
|
+
std::string error_msg(data.begin() + 8, data.end());
|
|
896
|
+
return error_msg;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
uint32_t UdpTrackerClient::generate_transaction_id() {
|
|
900
|
+
static std::random_device rd;
|
|
901
|
+
static std::mt19937 gen(rd());
|
|
902
|
+
static std::uniform_int_distribution<uint32_t> dis;
|
|
903
|
+
return dis(gen);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
uint32_t UdpTrackerClient::read_uint32_be(const uint8_t* data) {
|
|
907
|
+
return (static_cast<uint32_t>(data[0]) << 24) |
|
|
908
|
+
(static_cast<uint32_t>(data[1]) << 16) |
|
|
909
|
+
(static_cast<uint32_t>(data[2]) << 8) |
|
|
910
|
+
static_cast<uint32_t>(data[3]);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
void UdpTrackerClient::write_uint32_be(uint8_t* data, uint32_t value) {
|
|
914
|
+
data[0] = (value >> 24) & 0xFF;
|
|
915
|
+
data[1] = (value >> 16) & 0xFF;
|
|
916
|
+
data[2] = (value >> 8) & 0xFF;
|
|
917
|
+
data[3] = value & 0xFF;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
int64_t UdpTrackerClient::read_int64_be(const uint8_t* data) {
|
|
921
|
+
return (static_cast<int64_t>(data[0]) << 56) |
|
|
922
|
+
(static_cast<int64_t>(data[1]) << 48) |
|
|
923
|
+
(static_cast<int64_t>(data[2]) << 40) |
|
|
924
|
+
(static_cast<int64_t>(data[3]) << 32) |
|
|
925
|
+
(static_cast<int64_t>(data[4]) << 24) |
|
|
926
|
+
(static_cast<int64_t>(data[5]) << 16) |
|
|
927
|
+
(static_cast<int64_t>(data[6]) << 8) |
|
|
928
|
+
static_cast<int64_t>(data[7]);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
void UdpTrackerClient::write_int64_be(uint8_t* data, int64_t value) {
|
|
932
|
+
data[0] = (value >> 56) & 0xFF;
|
|
933
|
+
data[1] = (value >> 48) & 0xFF;
|
|
934
|
+
data[2] = (value >> 40) & 0xFF;
|
|
935
|
+
data[3] = (value >> 32) & 0xFF;
|
|
936
|
+
data[4] = (value >> 24) & 0xFF;
|
|
937
|
+
data[5] = (value >> 16) & 0xFF;
|
|
938
|
+
data[6] = (value >> 8) & 0xFF;
|
|
939
|
+
data[7] = value & 0xFF;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
//=============================================================================
|
|
943
|
+
// TrackerManager Implementation
|
|
944
|
+
//=============================================================================
|
|
945
|
+
|
|
946
|
+
TrackerManager::TrackerManager(const TorrentInfo& torrent_info)
|
|
947
|
+
: info_hash_(torrent_info.get_info_hash()), announce_interval_(1800) {
|
|
948
|
+
|
|
949
|
+
LOG_TRACKER_INFO("Creating tracker manager for torrent: " << torrent_info.get_name());
|
|
950
|
+
|
|
951
|
+
// Add primary announce URL
|
|
952
|
+
if (!torrent_info.get_announce().empty()) {
|
|
953
|
+
add_tracker(torrent_info.get_announce());
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Add announce list
|
|
957
|
+
for (const auto& tracker_url : torrent_info.get_announce_list()) {
|
|
958
|
+
if (tracker_url != torrent_info.get_announce()) {
|
|
959
|
+
add_tracker(tracker_url);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
LOG_TRACKER_INFO("Tracker manager initialized with " << trackers_.size() << " trackers");
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
TrackerManager::~TrackerManager() = default;
|
|
967
|
+
|
|
968
|
+
bool TrackerManager::add_tracker(const std::string& tracker_url) {
|
|
969
|
+
if (tracker_url.empty()) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
974
|
+
|
|
975
|
+
// Check if tracker already exists
|
|
976
|
+
for (const auto& tracker : trackers_) {
|
|
977
|
+
if (tracker->get_url() == tracker_url) {
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
auto tracker_client = create_tracker_client(tracker_url);
|
|
983
|
+
if (tracker_client) {
|
|
984
|
+
trackers_.push_back(tracker_client);
|
|
985
|
+
LOG_TRACKER_INFO("Added tracker: " << tracker_url);
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
return false;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
void TrackerManager::announce(const TrackerRequest& request, TrackerResponseCallback callback) {
|
|
993
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
994
|
+
|
|
995
|
+
LOG_TRACKER_INFO("Announcing to all trackers (" << trackers_.size() << " trackers)");
|
|
996
|
+
|
|
997
|
+
for (auto& tracker : trackers_) {
|
|
998
|
+
// Skip non-working trackers
|
|
999
|
+
if (!tracker->is_working()) {
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Announce in separate thread to avoid blocking
|
|
1004
|
+
std::thread([tracker, request, callback]() {
|
|
1005
|
+
tracker->announce(request, callback);
|
|
1006
|
+
}).detach();
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
last_announce_time_ = std::chrono::steady_clock::now();
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
void TrackerManager::announce_to_best(const TrackerRequest& request, TrackerResponseCallback callback) {
|
|
1013
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
1014
|
+
|
|
1015
|
+
// Sort trackers by priority
|
|
1016
|
+
sort_trackers_by_priority();
|
|
1017
|
+
|
|
1018
|
+
// Announce to first working tracker
|
|
1019
|
+
for (auto& tracker : trackers_) {
|
|
1020
|
+
if (tracker->is_working()) {
|
|
1021
|
+
LOG_TRACKER_INFO("Announcing to best tracker: " << tracker->get_url());
|
|
1022
|
+
|
|
1023
|
+
std::thread([tracker, request, callback]() {
|
|
1024
|
+
tracker->announce(request, callback);
|
|
1025
|
+
}).detach();
|
|
1026
|
+
|
|
1027
|
+
last_announce_time_ = std::chrono::steady_clock::now();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
LOG_TRACKER_WARN("No working trackers available for announce");
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
void TrackerManager::scrape(TrackerResponseCallback callback) {
|
|
1036
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
1037
|
+
|
|
1038
|
+
LOG_TRACKER_INFO("Scraping all trackers");
|
|
1039
|
+
|
|
1040
|
+
for (auto& tracker : trackers_) {
|
|
1041
|
+
if (!tracker->is_working()) {
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
std::thread([tracker, callback, this]() {
|
|
1046
|
+
tracker->scrape({info_hash_}, callback);
|
|
1047
|
+
}).detach();
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
size_t TrackerManager::get_working_tracker_count() const {
|
|
1052
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
1053
|
+
|
|
1054
|
+
size_t count = 0;
|
|
1055
|
+
for (const auto& tracker : trackers_) {
|
|
1056
|
+
if (tracker->is_working()) {
|
|
1057
|
+
++count;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
return count;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
std::vector<std::string> TrackerManager::get_tracker_urls() const {
|
|
1065
|
+
std::lock_guard<std::mutex> lock(trackers_mutex_);
|
|
1066
|
+
|
|
1067
|
+
std::vector<std::string> urls;
|
|
1068
|
+
for (const auto& tracker : trackers_) {
|
|
1069
|
+
urls.push_back(tracker->get_url());
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
return urls;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
bool TrackerManager::should_announce() const {
|
|
1076
|
+
auto now = std::chrono::steady_clock::now();
|
|
1077
|
+
auto time_since_last = std::chrono::duration_cast<std::chrono::seconds>(now - last_announce_time_).count();
|
|
1078
|
+
return time_since_last >= announce_interval_;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
std::chrono::steady_clock::time_point TrackerManager::get_next_announce_time() const {
|
|
1082
|
+
return last_announce_time_ + std::chrono::seconds(announce_interval_);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
std::shared_ptr<TrackerClient> TrackerManager::create_tracker_client(const std::string& tracker_url) {
|
|
1086
|
+
if (tracker_url.substr(0, 4) == "http") {
|
|
1087
|
+
// HTTP or HTTPS tracker
|
|
1088
|
+
return std::make_shared<HttpTrackerClient>(tracker_url);
|
|
1089
|
+
} else if (tracker_url.substr(0, 6) == "udp://") {
|
|
1090
|
+
// UDP tracker
|
|
1091
|
+
return std::make_shared<UdpTrackerClient>(tracker_url);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
LOG_TRACKER_WARN("Unsupported tracker protocol: " << tracker_url);
|
|
1095
|
+
return nullptr;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
void TrackerManager::sort_trackers_by_priority() {
|
|
1099
|
+
// Sort: working trackers first, then by last announce time
|
|
1100
|
+
std::sort(trackers_.begin(), trackers_.end(),
|
|
1101
|
+
[](const std::shared_ptr<TrackerClient>& a, const std::shared_ptr<TrackerClient>& b) {
|
|
1102
|
+
if (a->is_working() != b->is_working()) {
|
|
1103
|
+
return a->is_working();
|
|
1104
|
+
}
|
|
1105
|
+
return a->get_last_announce_time() < b->get_last_announce_time();
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
} // namespace librats
|
|
1110
|
+
|