librats 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/binding.gyp +1 -0
  2. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  3. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  4. package/native-src/CMakeLists.txt +360 -0
  5. package/native-src/LICENSE +21 -0
  6. package/native-src/src/bencode.cpp +485 -0
  7. package/native-src/src/bencode.h +145 -0
  8. package/native-src/src/bittorrent.cpp +3682 -0
  9. package/native-src/src/bittorrent.h +731 -0
  10. package/native-src/src/dht.cpp +2342 -0
  11. package/native-src/src/dht.h +501 -0
  12. package/native-src/src/encrypted_socket.cpp +817 -0
  13. package/native-src/src/encrypted_socket.h +239 -0
  14. package/native-src/src/file_transfer.cpp +1808 -0
  15. package/native-src/src/file_transfer.h +567 -0
  16. package/native-src/src/fs.cpp +639 -0
  17. package/native-src/src/fs.h +108 -0
  18. package/native-src/src/gossipsub.cpp +1137 -0
  19. package/native-src/src/gossipsub.h +403 -0
  20. package/native-src/src/ice.cpp +1386 -0
  21. package/native-src/src/ice.h +328 -0
  22. package/native-src/src/json.hpp +25526 -0
  23. package/native-src/src/krpc.cpp +558 -0
  24. package/native-src/src/krpc.h +145 -0
  25. package/native-src/src/librats.cpp +2715 -0
  26. package/native-src/src/librats.h +1729 -0
  27. package/native-src/src/librats_bittorrent.cpp +167 -0
  28. package/native-src/src/librats_c.cpp +1317 -0
  29. package/native-src/src/librats_c.h +237 -0
  30. package/native-src/src/librats_encryption.cpp +123 -0
  31. package/native-src/src/librats_file_transfer.cpp +226 -0
  32. package/native-src/src/librats_gossipsub.cpp +293 -0
  33. package/native-src/src/librats_ice.cpp +515 -0
  34. package/native-src/src/librats_logging.cpp +158 -0
  35. package/native-src/src/librats_mdns.cpp +171 -0
  36. package/native-src/src/librats_nat.cpp +571 -0
  37. package/native-src/src/librats_persistence.cpp +815 -0
  38. package/native-src/src/logger.h +412 -0
  39. package/native-src/src/mdns.cpp +1178 -0
  40. package/native-src/src/mdns.h +253 -0
  41. package/native-src/src/network_utils.cpp +598 -0
  42. package/native-src/src/network_utils.h +162 -0
  43. package/native-src/src/noise.cpp +981 -0
  44. package/native-src/src/noise.h +227 -0
  45. package/native-src/src/os.cpp +371 -0
  46. package/native-src/src/os.h +40 -0
  47. package/native-src/src/rats_export.h +17 -0
  48. package/native-src/src/sha1.cpp +163 -0
  49. package/native-src/src/sha1.h +42 -0
  50. package/native-src/src/socket.cpp +1376 -0
  51. package/native-src/src/socket.h +309 -0
  52. package/native-src/src/stun.cpp +484 -0
  53. package/native-src/src/stun.h +349 -0
  54. package/native-src/src/threadmanager.cpp +105 -0
  55. package/native-src/src/threadmanager.h +53 -0
  56. package/native-src/src/tracker.cpp +1110 -0
  57. package/native-src/src/tracker.h +268 -0
  58. package/native-src/src/version.cpp +24 -0
  59. package/native-src/src/version.h.in +45 -0
  60. package/native-src/version.rc.in +31 -0
  61. package/package.json +2 -8
  62. package/scripts/build-librats.js +59 -12
  63. package/scripts/prepare-package.js +133 -37
@@ -0,0 +1,3682 @@
1
+ #include "bittorrent.h"
2
+ #include "tracker.h"
3
+ #include "fs.h"
4
+ #include "network_utils.h"
5
+ #include "socket.h"
6
+ #include <iostream>
7
+ #include <algorithm>
8
+ #include <random>
9
+ #include <sstream>
10
+ #include <iomanip>
11
+ #include <cstring>
12
+ #include <climits>
13
+ #include <cerrno> // For errno on POSIX systems
14
+
15
+ #define LOG_BT_DEBUG(message) LOG_DEBUG("bittorrent", message)
16
+ #define LOG_BT_INFO(message) LOG_INFO("bittorrent", message)
17
+ #define LOG_BT_WARN(message) LOG_WARN("bittorrent", message)
18
+ #define LOG_BT_ERROR(message) LOG_ERROR("bittorrent", message)
19
+
20
+ namespace librats {
21
+
22
+ //=============================================================================
23
+ // TorrentInfo Implementation
24
+ //=============================================================================
25
+
26
+ TorrentInfo::TorrentInfo()
27
+ : total_length_(0), piece_length_(0), private_(false), metadata_only_(false) {
28
+ info_hash_.fill(0);
29
+ }
30
+
31
+ TorrentInfo::~TorrentInfo() = default;
32
+
33
+ bool TorrentInfo::load_from_file(const std::string& torrent_file) {
34
+ LOG_BT_INFO("Loading torrent from file: " << torrent_file);
35
+
36
+ std::string content = read_file_text_cpp(torrent_file);
37
+ if (content.empty()) {
38
+ LOG_BT_ERROR("Failed to read torrent file: " << torrent_file);
39
+ return false;
40
+ }
41
+
42
+ std::vector<uint8_t> data(content.begin(), content.end());
43
+ return load_from_data(data);
44
+ }
45
+
46
+ bool TorrentInfo::load_from_data(const std::vector<uint8_t>& data) {
47
+ LOG_BT_DEBUG("Parsing torrent data (" << data.size() << " bytes)");
48
+
49
+ try {
50
+ BencodeValue torrent = bencode::decode(data);
51
+ return load_from_bencode(torrent);
52
+ } catch (const std::exception& e) {
53
+ LOG_BT_ERROR("Failed to decode torrent bencode: " << e.what());
54
+ return false;
55
+ }
56
+ }
57
+
58
+ bool TorrentInfo::load_from_bencode(const BencodeValue& torrent_data) {
59
+ try {
60
+ if (!torrent_data.is_dict()) {
61
+ LOG_BT_ERROR("Torrent data is not a dictionary");
62
+ return false;
63
+ }
64
+
65
+ // Parse announce URL
66
+ if (torrent_data.has_key("announce")) {
67
+ announce_ = torrent_data["announce"].as_string();
68
+ }
69
+
70
+ // Parse announce-list
71
+ if (torrent_data.has_key("announce-list")) {
72
+ const auto& announce_list = torrent_data["announce-list"];
73
+ if (announce_list.is_list()) {
74
+ for (size_t i = 0; i < announce_list.size(); ++i) {
75
+ const auto& tier = announce_list[i];
76
+ if (tier.is_list() && tier.size() > 0) {
77
+ announce_list_.push_back(tier[0].as_string());
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ // Parse info dictionary
84
+ if (!torrent_data.has_key("info")) {
85
+ LOG_BT_ERROR("Torrent data missing 'info' dictionary");
86
+ return false;
87
+ }
88
+
89
+ const auto& info_dict = torrent_data["info"];
90
+ if (!parse_info_dict(info_dict)) {
91
+ return false;
92
+ }
93
+
94
+ // Calculate info hash
95
+ calculate_info_hash(info_dict);
96
+
97
+ LOG_BT_INFO("Successfully parsed torrent: " << name_);
98
+ LOG_BT_INFO(" Info hash: " << info_hash_to_hex(info_hash_));
99
+ LOG_BT_INFO(" Total size: " << total_length_ << " bytes");
100
+ LOG_BT_INFO(" Piece length: " << piece_length_ << " bytes");
101
+ LOG_BT_INFO(" Number of pieces: " << piece_hashes_.size());
102
+ LOG_BT_INFO(" Number of files: " << files_.size());
103
+
104
+ return true;
105
+
106
+ } catch (const std::exception& e) {
107
+ LOG_BT_ERROR("Failed to parse torrent: " << e.what());
108
+ return false;
109
+ }
110
+ }
111
+
112
+ bool TorrentInfo::parse_info_dict(const BencodeValue& info_dict) {
113
+ if (!info_dict.is_dict()) {
114
+ LOG_BT_ERROR("Info is not a dictionary");
115
+ return false;
116
+ }
117
+
118
+ // Parse name
119
+ if (!info_dict.has_key("name")) {
120
+ LOG_BT_ERROR("Info dictionary missing 'name'");
121
+ return false;
122
+ }
123
+ name_ = info_dict["name"].as_string();
124
+
125
+ // Parse piece length
126
+ if (!info_dict.has_key("piece length")) {
127
+ LOG_BT_ERROR("Info dictionary missing 'piece length'");
128
+ return false;
129
+ }
130
+ piece_length_ = static_cast<uint32_t>(info_dict["piece length"].as_integer());
131
+
132
+ // Parse pieces hashes
133
+ if (!info_dict.has_key("pieces")) {
134
+ LOG_BT_ERROR("Info dictionary missing 'pieces'");
135
+ return false;
136
+ }
137
+
138
+ const std::string& pieces_data = info_dict["pieces"].as_string();
139
+ if (pieces_data.length() % 20 != 0) {
140
+ LOG_BT_ERROR("Invalid pieces length: " << pieces_data.length());
141
+ return false;
142
+ }
143
+
144
+ size_t num_pieces = pieces_data.length() / 20;
145
+ piece_hashes_.reserve(num_pieces);
146
+
147
+ for (size_t i = 0; i < num_pieces; ++i) {
148
+ std::array<uint8_t, 20> hash;
149
+ std::memcpy(hash.data(), pieces_data.data() + i * 20, 20);
150
+ piece_hashes_.push_back(hash);
151
+ }
152
+
153
+ // Parse private flag
154
+ if (info_dict.has_key("private")) {
155
+ private_ = info_dict["private"].as_integer() != 0;
156
+ }
157
+
158
+ // Build file list
159
+ build_file_list(info_dict);
160
+
161
+ return true;
162
+ }
163
+
164
+ void TorrentInfo::calculate_info_hash(const BencodeValue& info_dict) {
165
+ // Encode the info dictionary and calculate SHA1 hash
166
+ std::vector<uint8_t> encoded = info_dict.encode();
167
+ std::string hash_string = SHA1::hash_bytes(encoded);
168
+
169
+ // Convert hex string to bytes
170
+ for (size_t i = 0; i < 20; ++i) {
171
+ std::string byte_str = hash_string.substr(i * 2, 2);
172
+ info_hash_[i] = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
173
+ }
174
+ }
175
+
176
+ void TorrentInfo::build_file_list(const BencodeValue& info_dict) {
177
+ files_.clear();
178
+ total_length_ = 0;
179
+
180
+ if (info_dict.has_key("files")) {
181
+ // Multi-file torrent
182
+ const auto& files_list = info_dict["files"];
183
+ if (!files_list.is_list()) {
184
+ LOG_BT_ERROR("Files is not a list");
185
+ return;
186
+ }
187
+
188
+ uint64_t offset = 0;
189
+ for (size_t i = 0; i < files_list.size(); ++i) {
190
+ const auto& file_info = files_list[i];
191
+ if (!file_info.is_dict()) {
192
+ continue;
193
+ }
194
+
195
+ if (!file_info.has_key("length") || !file_info.has_key("path")) {
196
+ continue;
197
+ }
198
+
199
+ uint64_t length = static_cast<uint64_t>(file_info["length"].as_integer());
200
+ const auto& path_list = file_info["path"];
201
+
202
+ std::string path;
203
+ if (path_list.is_list()) {
204
+ for (size_t j = 0; j < path_list.size(); ++j) {
205
+ if (j > 0) path += "/";
206
+ path += path_list[j].as_string();
207
+ }
208
+ }
209
+
210
+ files_.emplace_back(path, length, offset);
211
+ offset += length;
212
+ total_length_ += length;
213
+ }
214
+ } else {
215
+ // Single-file torrent
216
+ if (!info_dict.has_key("length")) {
217
+ LOG_BT_ERROR("Single-file torrent missing 'length'");
218
+ return;
219
+ }
220
+
221
+ uint64_t length = static_cast<uint64_t>(info_dict["length"].as_integer());
222
+ files_.emplace_back(name_, length, 0);
223
+ total_length_ = length;
224
+ }
225
+ }
226
+
227
+ uint32_t TorrentInfo::get_piece_length(PieceIndex piece_index) const {
228
+ if (piece_index >= piece_hashes_.size()) {
229
+ return 0;
230
+ }
231
+
232
+ if (piece_index == piece_hashes_.size() - 1) {
233
+ // Last piece might be smaller
234
+ uint32_t remainder = static_cast<uint32_t>(total_length_ % piece_length_);
235
+ return remainder > 0 ? remainder : piece_length_;
236
+ }
237
+
238
+ return piece_length_;
239
+ }
240
+
241
+ bool TorrentInfo::is_valid() const {
242
+ return !name_.empty() &&
243
+ !piece_hashes_.empty() &&
244
+ piece_length_ > 0 &&
245
+ total_length_ > 0 &&
246
+ !files_.empty();
247
+ }
248
+
249
+ TorrentInfo TorrentInfo::create_for_metadata_exchange(const InfoHash& info_hash) {
250
+ TorrentInfo torrent;
251
+
252
+ // Set the info hash directly
253
+ torrent.info_hash_ = info_hash;
254
+
255
+ // Mark as metadata-only (for BEP 9 metadata exchange)
256
+ torrent.metadata_only_ = true;
257
+
258
+ // Set minimal required fields - these are placeholders only
259
+ // No pieces or files are actually downloaded for metadata-only torrents
260
+ torrent.name_ = "metadata_download_" + info_hash_to_hex(info_hash).substr(0, 8);
261
+ torrent.piece_length_ = 0; // No real pieces
262
+ torrent.total_length_ = 0; // No real content
263
+ torrent.private_ = false;
264
+
265
+ // No piece hashes or files - we only need the info hash for peer connections
266
+ // Piece downloading will be skipped for metadata-only torrents
267
+
268
+ LOG_BT_DEBUG("Created metadata exchange TorrentInfo for hash: " << info_hash_to_hex(info_hash));
269
+
270
+ return torrent;
271
+ }
272
+
273
+ //=============================================================================
274
+ // PeerMessage Implementation
275
+ //=============================================================================
276
+
277
+ std::vector<uint8_t> PeerMessage::serialize() const {
278
+ std::vector<uint8_t> result;
279
+
280
+ // Length prefix (4 bytes)
281
+ uint32_t length = 1 + static_cast<uint32_t>(payload.size()); // 1 byte for message type + payload
282
+ result.push_back((length >> 24) & 0xFF);
283
+ result.push_back((length >> 16) & 0xFF);
284
+ result.push_back((length >> 8) & 0xFF);
285
+ result.push_back(length & 0xFF);
286
+
287
+ // Message type (1 byte)
288
+ result.push_back(static_cast<uint8_t>(type));
289
+
290
+ // Payload
291
+ result.insert(result.end(), payload.begin(), payload.end());
292
+
293
+ return result;
294
+ }
295
+
296
+ PeerMessage PeerMessage::create_choke() {
297
+ return PeerMessage(MessageType::CHOKE);
298
+ }
299
+
300
+ PeerMessage PeerMessage::create_unchoke() {
301
+ return PeerMessage(MessageType::UNCHOKE);
302
+ }
303
+
304
+ PeerMessage PeerMessage::create_interested() {
305
+ return PeerMessage(MessageType::INTERESTED);
306
+ }
307
+
308
+ PeerMessage PeerMessage::create_not_interested() {
309
+ return PeerMessage(MessageType::NOT_INTERESTED);
310
+ }
311
+
312
+ PeerMessage PeerMessage::create_have(PieceIndex piece_index) {
313
+ std::vector<uint8_t> payload(4);
314
+ payload[0] = (piece_index >> 24) & 0xFF;
315
+ payload[1] = (piece_index >> 16) & 0xFF;
316
+ payload[2] = (piece_index >> 8) & 0xFF;
317
+ payload[3] = piece_index & 0xFF;
318
+ return PeerMessage(MessageType::HAVE, payload);
319
+ }
320
+
321
+ PeerMessage PeerMessage::create_bitfield(const std::vector<bool>& bitfield) {
322
+ std::vector<uint8_t> payload((bitfield.size() + 7) / 8, 0);
323
+
324
+ for (size_t i = 0; i < bitfield.size(); ++i) {
325
+ if (bitfield[i]) {
326
+ size_t byte_index = i / 8;
327
+ size_t bit_index = 7 - (i % 8);
328
+ payload[byte_index] |= (1 << bit_index);
329
+ }
330
+ }
331
+
332
+ return PeerMessage(MessageType::BITFIELD, payload);
333
+ }
334
+
335
+ PeerMessage PeerMessage::create_request(PieceIndex piece_index, uint32_t offset, uint32_t length) {
336
+ std::vector<uint8_t> payload(12);
337
+
338
+ // Piece index
339
+ payload[0] = (piece_index >> 24) & 0xFF;
340
+ payload[1] = (piece_index >> 16) & 0xFF;
341
+ payload[2] = (piece_index >> 8) & 0xFF;
342
+ payload[3] = piece_index & 0xFF;
343
+
344
+ // Offset
345
+ payload[4] = (offset >> 24) & 0xFF;
346
+ payload[5] = (offset >> 16) & 0xFF;
347
+ payload[6] = (offset >> 8) & 0xFF;
348
+ payload[7] = offset & 0xFF;
349
+
350
+ // Length
351
+ payload[8] = (length >> 24) & 0xFF;
352
+ payload[9] = (length >> 16) & 0xFF;
353
+ payload[10] = (length >> 8) & 0xFF;
354
+ payload[11] = length & 0xFF;
355
+
356
+ return PeerMessage(MessageType::REQUEST, payload);
357
+ }
358
+
359
+ PeerMessage PeerMessage::create_piece(PieceIndex piece_index, uint32_t offset, const std::vector<uint8_t>& data) {
360
+ std::vector<uint8_t> payload(8 + data.size());
361
+
362
+ // Piece index
363
+ payload[0] = (piece_index >> 24) & 0xFF;
364
+ payload[1] = (piece_index >> 16) & 0xFF;
365
+ payload[2] = (piece_index >> 8) & 0xFF;
366
+ payload[3] = piece_index & 0xFF;
367
+
368
+ // Offset
369
+ payload[4] = (offset >> 24) & 0xFF;
370
+ payload[5] = (offset >> 16) & 0xFF;
371
+ payload[6] = (offset >> 8) & 0xFF;
372
+ payload[7] = offset & 0xFF;
373
+
374
+ // Data
375
+ std::copy(data.begin(), data.end(), payload.begin() + 8);
376
+
377
+ return PeerMessage(MessageType::PIECE, payload);
378
+ }
379
+
380
+ PeerMessage PeerMessage::create_cancel(PieceIndex piece_index, uint32_t offset, uint32_t length) {
381
+ std::vector<uint8_t> payload(12);
382
+
383
+ // Piece index
384
+ payload[0] = (piece_index >> 24) & 0xFF;
385
+ payload[1] = (piece_index >> 16) & 0xFF;
386
+ payload[2] = (piece_index >> 8) & 0xFF;
387
+ payload[3] = piece_index & 0xFF;
388
+
389
+ // Offset
390
+ payload[4] = (offset >> 24) & 0xFF;
391
+ payload[5] = (offset >> 16) & 0xFF;
392
+ payload[6] = (offset >> 8) & 0xFF;
393
+ payload[7] = offset & 0xFF;
394
+
395
+ // Length
396
+ payload[8] = (length >> 24) & 0xFF;
397
+ payload[9] = (length >> 16) & 0xFF;
398
+ payload[10] = (length >> 8) & 0xFF;
399
+ payload[11] = length & 0xFF;
400
+
401
+ return PeerMessage(MessageType::CANCEL, payload);
402
+ }
403
+
404
+ PeerMessage PeerMessage::create_port(uint16_t port) {
405
+ std::vector<uint8_t> payload(2);
406
+ payload[0] = (port >> 8) & 0xFF;
407
+ payload[1] = port & 0xFF;
408
+ return PeerMessage(MessageType::PORT, payload);
409
+ }
410
+
411
+ //=============================================================================
412
+ // PeerConnection Implementation
413
+ //=============================================================================
414
+
415
+ PeerConnection::PeerConnection(TorrentDownload* torrent, const Peer& peer_info, socket_t socket)
416
+ : torrent_(torrent), peer_info_(peer_info), socket_(socket),
417
+ state_(PeerState::CONNECTING), should_disconnect_(false), handshake_completed_(false),
418
+ peer_choked_(true), am_choked_(true), peer_interested_(false),
419
+ am_interested_(false), am_choking_(true),
420
+ downloaded_bytes_(0), uploaded_bytes_(0),
421
+ last_speed_update_time_(std::chrono::steady_clock::now()),
422
+ last_speed_downloaded_(0), last_speed_uploaded_(0),
423
+ download_speed_(0.0), upload_speed_(0.0),
424
+ expected_message_length_(0),
425
+ supports_extensions_(false), supports_metadata_exchange_(false),
426
+ peer_ut_metadata_id_(0), peer_metadata_size_(0) {
427
+
428
+ peer_id_.fill(0);
429
+
430
+ // If socket is already provided, it means handshake is already done (incoming connection)
431
+ if (is_valid_socket(socket)) {
432
+ handshake_completed_ = true;
433
+ state_.store(PeerState::HANDSHAKING); // Will move to CONNECTED after extended handshake
434
+ LOG_BT_DEBUG("Created peer connection for incoming connection from " << peer_info_.ip << ":" << peer_info_.port);
435
+ } else {
436
+ LOG_BT_DEBUG("Created peer connection to " << peer_info_.ip << ":" << peer_info_.port);
437
+ }
438
+ }
439
+
440
+ PeerConnection::~PeerConnection() {
441
+ disconnect();
442
+ }
443
+
444
+ bool PeerConnection::connect() {
445
+ if (state_.load() != PeerState::CONNECTING) {
446
+ return false;
447
+ }
448
+
449
+ LOG_BT_INFO("Connecting to peer " << peer_info_.ip << ":" << peer_info_.port);
450
+
451
+ // Start connection thread - socket creation happens inside the thread for async behavior
452
+ // This prevents blocking the caller when connecting to unresponsive peers
453
+ connection_thread_ = std::thread(&PeerConnection::connection_loop, this);
454
+
455
+ return true;
456
+ }
457
+
458
+ void PeerConnection::disconnect() {
459
+ should_disconnect_ = true;
460
+
461
+ // Reset all pending block requests so they can be requested from other peers
462
+ {
463
+ std::lock_guard<std::mutex> lock(requests_mutex_);
464
+ for (const auto& req : pending_requests_) {
465
+ uint32_t block_index = req.offset / BLOCK_SIZE;
466
+ torrent_->reset_block_request(req.piece_index, block_index);
467
+ }
468
+ pending_requests_.clear();
469
+ }
470
+
471
+ if (is_valid_socket(socket_)) {
472
+ // Force shutdown for TCP socket to ensure immediate disconnect
473
+ close_socket(socket_, true);
474
+ socket_ = INVALID_SOCKET_VALUE;
475
+ }
476
+
477
+ if (connection_thread_.joinable()) {
478
+ connection_thread_.join();
479
+ }
480
+
481
+ state_.store(PeerState::DISCONNECTED);
482
+ }
483
+
484
+ void PeerConnection::connection_loop() {
485
+ LOG_BT_DEBUG("Starting connection loop for peer " << peer_info_.ip << ":" << peer_info_.port);
486
+
487
+ // Create socket if not already provided (outgoing connection)
488
+ // This is done in the thread to avoid blocking the caller
489
+ if (!is_valid_socket(socket_)) {
490
+ socket_ = create_tcp_client(peer_info_.ip, peer_info_.port, 5000); // 5-second timeout for faster failure
491
+ if (!is_valid_socket(socket_)) {
492
+ LOG_BT_ERROR("Failed to create connection to " << peer_info_.ip << ":" << peer_info_.port);
493
+ state_.store(PeerState::ERROR);
494
+ return;
495
+ }
496
+ }
497
+
498
+ // Perform handshake only if not already completed (for outgoing connections)
499
+ if (!handshake_completed_) {
500
+ if (!perform_handshake()) {
501
+ LOG_BT_ERROR("Handshake failed with peer " << peer_info_.ip << ":" << peer_info_.port);
502
+ state_.store(PeerState::ERROR);
503
+ return;
504
+ }
505
+ } else {
506
+ LOG_BT_DEBUG("Handshake already completed for incoming peer " << peer_info_.ip << ":" << peer_info_.port);
507
+ // For incoming connections, send extended handshake if needed
508
+ send_extended_handshake();
509
+ }
510
+
511
+ state_.store(PeerState::CONNECTED);
512
+ LOG_BT_INFO("Successfully connected to peer " << peer_info_.ip << ":" << peer_info_.port);
513
+
514
+ // Initialize peer bitfield
515
+ const auto& torrent_info = torrent_->get_torrent_info();
516
+ peer_bitfield_.resize(torrent_info.get_num_pieces(), false);
517
+
518
+ // Send our bitfield to the peer (for seeding)
519
+ send_bitfield();
520
+
521
+ // Automatically unchoke the peer (optimistic unchoking for now)
522
+ // In a more sophisticated implementation, we'd have choke/unchoke algorithms
523
+ set_choke(false);
524
+
525
+ // Main message processing loop
526
+ while (!should_disconnect_.load() && state_.load() == PeerState::CONNECTED) {
527
+ process_messages();
528
+
529
+ // Cleanup expired requests
530
+ cleanup_expired_requests();
531
+
532
+ // Use conditional variable for responsive shutdown
533
+ {
534
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
535
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(10), [this] { return should_disconnect_.load(); })) {
536
+ break;
537
+ }
538
+ }
539
+ }
540
+
541
+ // Log reason for connection loop exit
542
+ PeerState exit_state = state_.load();
543
+ if (exit_state == PeerState::ERROR) {
544
+ LOG_BT_WARN("Connection loop ended for peer " << peer_info_.ip << ":" << peer_info_.port << " due to error");
545
+ } else if (should_disconnect_.load()) {
546
+ LOG_BT_DEBUG("Connection loop ended for peer " << peer_info_.ip << ":" << peer_info_.port << " - disconnect requested");
547
+ } else {
548
+ LOG_BT_DEBUG("Connection loop ended for peer " << peer_info_.ip << ":" << peer_info_.port
549
+ << " (state: " << static_cast<int>(exit_state) << ")");
550
+ }
551
+ }
552
+
553
+ bool PeerConnection::perform_handshake() {
554
+ state_.store(PeerState::HANDSHAKING);
555
+
556
+ if (!send_handshake()) {
557
+ return false;
558
+ }
559
+
560
+ if (!receive_handshake()) {
561
+ return false;
562
+ }
563
+
564
+ return true;
565
+ }
566
+
567
+ bool PeerConnection::send_handshake() {
568
+ const auto& torrent_info = torrent_->get_torrent_info();
569
+ PeerID our_peer_id = generate_peer_id();
570
+
571
+ auto handshake_data = create_handshake_message(torrent_info.get_info_hash(), our_peer_id);
572
+
573
+ // Set extension protocol bit (BEP 10) - bit 20 in reserved bytes (byte 5, bit 3)
574
+ handshake_data[25] |= 0x10; // 0x10 = 0001 0000 (bit 4 from right)
575
+
576
+ return write_data(handshake_data);
577
+ }
578
+
579
+ bool PeerConnection::receive_handshake() {
580
+ std::vector<uint8_t> handshake_data(68); // Fixed handshake size
581
+
582
+ if (!read_data(handshake_data, 68)) {
583
+ LOG_BT_ERROR("Failed to read handshake from peer");
584
+ return false;
585
+ }
586
+
587
+ InfoHash received_info_hash;
588
+ if (!parse_handshake_message(handshake_data, received_info_hash, peer_id_)) {
589
+ LOG_BT_ERROR("Failed to parse handshake from peer");
590
+ return false;
591
+ }
592
+
593
+ // Verify info hash matches
594
+ const auto& expected_info_hash = torrent_->get_torrent_info().get_info_hash();
595
+ if (received_info_hash != expected_info_hash) {
596
+ LOG_BT_ERROR("Info hash mismatch in handshake");
597
+ return false;
598
+ }
599
+
600
+ // Check if peer supports extension protocol (BEP 10) - bit 20 in reserved bytes (byte 5, bit 3)
601
+ supports_extensions_ = (handshake_data[25] & 0x10) != 0;
602
+
603
+ if (supports_extensions_) {
604
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " supports extension protocol");
605
+ // Send extended handshake
606
+ send_extended_handshake();
607
+ } else {
608
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " does not support extension protocol");
609
+ }
610
+
611
+ LOG_BT_DEBUG("Handshake successful with peer " << peer_info_.ip << ":" << peer_info_.port);
612
+ return true;
613
+ }
614
+
615
+ bool PeerConnection::send_message(const PeerMessage& message) {
616
+ auto data = message.serialize();
617
+ return write_data(data);
618
+ }
619
+
620
+ void PeerConnection::process_messages() {
621
+ // Check if we're still connected before processing
622
+ if (state_.load() != PeerState::CONNECTED) {
623
+ return;
624
+ }
625
+
626
+ // Try to read message length first
627
+ if (expected_message_length_ == 0) {
628
+ std::vector<uint8_t> length_buffer(4);
629
+ if (read_data(length_buffer, 4)) {
630
+ expected_message_length_ = (static_cast<size_t>(length_buffer[0]) << 24) |
631
+ (static_cast<size_t>(length_buffer[1]) << 16) |
632
+ (static_cast<size_t>(length_buffer[2]) << 8) |
633
+ static_cast<size_t>(length_buffer[3]);
634
+
635
+ if (expected_message_length_ == 0) {
636
+ // Keep-alive message
637
+ expected_message_length_ = 0;
638
+ return;
639
+ }
640
+
641
+ message_buffer_.clear();
642
+ message_buffer_.reserve(expected_message_length_);
643
+ } else {
644
+ return; // No data available or connection error (state already updated)
645
+ }
646
+ }
647
+
648
+ // Try to read the message payload
649
+ if (expected_message_length_ > 0 && message_buffer_.size() < expected_message_length_) {
650
+ // Check state again in case it changed during previous read
651
+ if (state_.load() != PeerState::CONNECTED) {
652
+ return;
653
+ }
654
+
655
+ size_t remaining = expected_message_length_ - message_buffer_.size();
656
+ std::vector<uint8_t> temp_buffer(remaining);
657
+
658
+ if (read_data(temp_buffer, remaining)) {
659
+ message_buffer_.insert(message_buffer_.end(), temp_buffer.begin(), temp_buffer.end());
660
+ } else {
661
+ return; // Read failed, state already updated if error
662
+ }
663
+ }
664
+
665
+ // Process complete message
666
+ if (message_buffer_.size() >= expected_message_length_) {
667
+ auto message = parse_message(message_buffer_);
668
+ if (message) {
669
+ handle_message(*message);
670
+ }
671
+
672
+ // Reset for next message
673
+ expected_message_length_ = 0;
674
+ message_buffer_.clear();
675
+ }
676
+ }
677
+
678
+ std::unique_ptr<PeerMessage> PeerConnection::parse_message(const std::vector<uint8_t>& data) {
679
+ if (data.empty()) {
680
+ return nullptr;
681
+ }
682
+
683
+ MessageType type = static_cast<MessageType>(data[0]);
684
+ std::vector<uint8_t> payload(data.begin() + 1, data.end());
685
+
686
+ return std::make_unique<PeerMessage>(type, payload);
687
+ }
688
+
689
+ void PeerConnection::handle_message(const PeerMessage& message) {
690
+ LOG_BT_DEBUG("Received message type " << static_cast<int>(message.type) << " from peer " << peer_info_.ip << ":" << peer_info_.port);
691
+
692
+ switch (message.type) {
693
+ case MessageType::CHOKE:
694
+ handle_choke();
695
+ break;
696
+ case MessageType::UNCHOKE:
697
+ handle_unchoke();
698
+ break;
699
+ case MessageType::INTERESTED:
700
+ handle_interested();
701
+ break;
702
+ case MessageType::NOT_INTERESTED:
703
+ handle_not_interested();
704
+ break;
705
+ case MessageType::HAVE:
706
+ handle_have(message.payload);
707
+ break;
708
+ case MessageType::BITFIELD:
709
+ handle_bitfield(message.payload);
710
+ break;
711
+ case MessageType::REQUEST:
712
+ handle_request(message.payload);
713
+ break;
714
+ case MessageType::PIECE:
715
+ handle_piece(message.payload);
716
+ break;
717
+ case MessageType::CANCEL:
718
+ handle_cancel(message.payload);
719
+ break;
720
+ case MessageType::EXTENDED:
721
+ handle_extended(message.payload);
722
+ break;
723
+ default:
724
+ LOG_BT_WARN("Unknown message type: " << static_cast<int>(message.type));
725
+ break;
726
+ }
727
+ }
728
+
729
+ void PeerConnection::handle_choke() {
730
+ peer_choked_ = true;
731
+ cancel_all_requests();
732
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " choked us");
733
+ }
734
+
735
+ void PeerConnection::handle_unchoke() {
736
+ peer_choked_ = false;
737
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " unchoked us");
738
+ }
739
+
740
+ void PeerConnection::handle_interested() {
741
+ peer_interested_ = true;
742
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " is interested");
743
+
744
+ // Automatically unchoke interested peers (optimistic unchoking)
745
+ // In a more sophisticated implementation, we'd have a proper choke algorithm
746
+ if (am_choking_) {
747
+ set_choke(false);
748
+ LOG_BT_DEBUG("Unchoked interested peer " << peer_info_.ip << ":" << peer_info_.port);
749
+ }
750
+ }
751
+
752
+ void PeerConnection::handle_not_interested() {
753
+ peer_interested_ = false;
754
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " is not interested");
755
+ }
756
+
757
+ void PeerConnection::handle_have(const std::vector<uint8_t>& payload) {
758
+ if (payload.size() != 4) {
759
+ LOG_BT_WARN("Invalid HAVE message size: " << payload.size());
760
+ return;
761
+ }
762
+
763
+ PieceIndex piece_index = (static_cast<uint32_t>(payload[0]) << 24) |
764
+ (static_cast<uint32_t>(payload[1]) << 16) |
765
+ (static_cast<uint32_t>(payload[2]) << 8) |
766
+ static_cast<uint32_t>(payload[3]);
767
+
768
+ if (piece_index < peer_bitfield_.size()) {
769
+ peer_bitfield_[piece_index] = true;
770
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " has piece " << piece_index);
771
+ }
772
+ }
773
+
774
+ void PeerConnection::handle_bitfield(const std::vector<uint8_t>& payload) {
775
+ const auto& torrent_info = torrent_->get_torrent_info();
776
+ uint32_t num_pieces = torrent_info.get_num_pieces();
777
+
778
+ peer_bitfield_.clear();
779
+ peer_bitfield_.resize(num_pieces, false);
780
+
781
+ for (uint32_t i = 0; i < num_pieces && i / 8 < payload.size(); ++i) {
782
+ size_t byte_index = i / 8;
783
+ size_t bit_index = 7 - (i % 8);
784
+ peer_bitfield_[i] = (payload[byte_index] & (1 << bit_index)) != 0;
785
+ }
786
+
787
+ LOG_BT_DEBUG("Received bitfield from peer " << peer_info_.ip << ":" << peer_info_.port);
788
+ }
789
+
790
+ void PeerConnection::handle_request(const std::vector<uint8_t>& payload) {
791
+ if (payload.size() != 12) {
792
+ LOG_BT_WARN("Invalid REQUEST message size: " << payload.size());
793
+ return;
794
+ }
795
+
796
+ PieceIndex piece_index = (static_cast<uint32_t>(payload[0]) << 24) |
797
+ (static_cast<uint32_t>(payload[1]) << 16) |
798
+ (static_cast<uint32_t>(payload[2]) << 8) |
799
+ static_cast<uint32_t>(payload[3]);
800
+ uint32_t offset = (static_cast<uint32_t>(payload[4]) << 24) |
801
+ (static_cast<uint32_t>(payload[5]) << 16) |
802
+ (static_cast<uint32_t>(payload[6]) << 8) |
803
+ static_cast<uint32_t>(payload[7]);
804
+ uint32_t length = (static_cast<uint32_t>(payload[8]) << 24) |
805
+ (static_cast<uint32_t>(payload[9]) << 16) |
806
+ (static_cast<uint32_t>(payload[10]) << 8) |
807
+ static_cast<uint32_t>(payload[11]);
808
+
809
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " requested piece " << piece_index << " offset " << offset << " length " << length);
810
+
811
+ // Check if we're choking this peer
812
+ if (am_choking_) {
813
+ LOG_BT_DEBUG("Ignoring request from choked peer " << peer_info_.ip << ":" << peer_info_.port);
814
+ return;
815
+ }
816
+
817
+ // Validate request parameters
818
+ const auto& torrent_info = torrent_->get_torrent_info();
819
+ if (piece_index >= torrent_info.get_num_pieces()) {
820
+ LOG_BT_WARN("Invalid piece index requested: " << piece_index);
821
+ return;
822
+ }
823
+
824
+ uint32_t piece_length = torrent_info.get_piece_length(piece_index);
825
+ if (offset >= piece_length || offset + length > piece_length) {
826
+ LOG_BT_WARN("Invalid offset/length in request: offset=" << offset << " length=" << length << " piece_length=" << piece_length);
827
+ return;
828
+ }
829
+
830
+ // Validate request length (up to BLOCK_SIZE, smaller is allowed for last block)
831
+ if (length > BLOCK_SIZE || length == 0) {
832
+ LOG_BT_WARN("Invalid request length: " << length);
833
+ return;
834
+ }
835
+
836
+ // Check if we have this piece
837
+ if (!torrent_->is_piece_complete(piece_index)) {
838
+ LOG_BT_DEBUG("Don't have piece " << piece_index << " yet, can't serve to peer");
839
+ return;
840
+ }
841
+
842
+ // Read the piece from disk
843
+ std::vector<uint8_t> piece_data;
844
+ if (!torrent_->read_piece_from_disk(piece_index, piece_data)) {
845
+ LOG_BT_ERROR("Failed to read piece " << piece_index << " from disk for upload");
846
+ return;
847
+ }
848
+
849
+ // Extract the requested block
850
+ if (offset + length > piece_data.size()) {
851
+ LOG_BT_ERROR("Block exceeds piece data size: offset=" << offset << " length=" << length << " piece_size=" << piece_data.size());
852
+ return;
853
+ }
854
+
855
+ std::vector<uint8_t> block_data(piece_data.begin() + offset, piece_data.begin() + offset + length);
856
+
857
+ // Send the piece message
858
+ auto piece_msg = PeerMessage::create_piece(piece_index, offset, block_data);
859
+ if (send_message(piece_msg)) {
860
+ uploaded_bytes_ += block_data.size();
861
+ LOG_BT_DEBUG("Sent piece " << piece_index << " offset " << offset << " length " << length << " to peer " << peer_info_.ip << ":" << peer_info_.port);
862
+ } else {
863
+ LOG_BT_ERROR("Failed to send piece data to peer " << peer_info_.ip << ":" << peer_info_.port);
864
+ }
865
+ }
866
+
867
+ void PeerConnection::handle_piece(const std::vector<uint8_t>& payload) {
868
+ if (payload.size() < 8) {
869
+ LOG_BT_WARN("Invalid PIECE message size: " << payload.size());
870
+ return;
871
+ }
872
+
873
+ PieceIndex piece_index = (static_cast<uint32_t>(payload[0]) << 24) |
874
+ (static_cast<uint32_t>(payload[1]) << 16) |
875
+ (static_cast<uint32_t>(payload[2]) << 8) |
876
+ static_cast<uint32_t>(payload[3]);
877
+ uint32_t offset = (static_cast<uint32_t>(payload[4]) << 24) |
878
+ (static_cast<uint32_t>(payload[5]) << 16) |
879
+ (static_cast<uint32_t>(payload[6]) << 8) |
880
+ static_cast<uint32_t>(payload[7]);
881
+
882
+ std::vector<uint8_t> block_data(payload.begin() + 8, payload.end());
883
+ downloaded_bytes_ += block_data.size();
884
+
885
+ LOG_BT_DEBUG("Received piece " << piece_index << " offset " << offset << " length " << block_data.size() << " from peer " << peer_info_.ip << ":" << peer_info_.port);
886
+
887
+ // Store the piece block
888
+ if (torrent_->store_piece_block(piece_index, offset, block_data)) {
889
+ // Remove corresponding request
890
+ std::lock_guard<std::mutex> lock(requests_mutex_);
891
+ pending_requests_.erase(
892
+ std::remove_if(pending_requests_.begin(), pending_requests_.end(),
893
+ [piece_index, offset](const PeerRequest& req) {
894
+ return req.piece_index == piece_index && req.offset == offset;
895
+ }),
896
+ pending_requests_.end());
897
+ }
898
+ }
899
+
900
+ void PeerConnection::handle_cancel(const std::vector<uint8_t>& payload) {
901
+ if (payload.size() != 12) {
902
+ LOG_BT_WARN("Invalid CANCEL message size: " << payload.size());
903
+ return;
904
+ }
905
+
906
+ PieceIndex piece_index = (static_cast<uint32_t>(payload[0]) << 24) |
907
+ (static_cast<uint32_t>(payload[1]) << 16) |
908
+ (static_cast<uint32_t>(payload[2]) << 8) |
909
+ static_cast<uint32_t>(payload[3]);
910
+ uint32_t offset = (static_cast<uint32_t>(payload[4]) << 24) |
911
+ (static_cast<uint32_t>(payload[5]) << 16) |
912
+ (static_cast<uint32_t>(payload[6]) << 8) |
913
+ static_cast<uint32_t>(payload[7]);
914
+ uint32_t length = (static_cast<uint32_t>(payload[8]) << 24) |
915
+ (static_cast<uint32_t>(payload[9]) << 16) |
916
+ (static_cast<uint32_t>(payload[10]) << 8) |
917
+ static_cast<uint32_t>(payload[11]);
918
+
919
+ LOG_BT_DEBUG("Peer " << peer_info_.ip << ":" << peer_info_.port << " cancelled request for piece " << piece_index << " offset " << offset << " length " << length);
920
+
921
+ // Note: For a simple implementation, we don't queue outgoing requests
922
+ // If we were queuing them, we'd remove this request from the queue here
923
+ // For now, just acknowledge the cancel
924
+ }
925
+
926
+ void PeerConnection::handle_extended(const std::vector<uint8_t>& payload) {
927
+ if (payload.empty()) {
928
+ LOG_BT_WARN("Empty extended message payload");
929
+ return;
930
+ }
931
+
932
+ uint8_t extended_id = payload[0];
933
+ std::vector<uint8_t> extended_payload(payload.begin() + 1, payload.end());
934
+
935
+ LOG_BT_DEBUG("Received extended message with ID " << static_cast<int>(extended_id) << " from peer " << peer_info_.ip << ":" << peer_info_.port);
936
+
937
+ if (extended_id == static_cast<uint8_t>(ExtendedMessageType::HANDSHAKE)) {
938
+ handle_extended_handshake(extended_payload);
939
+ } else if (extended_id == static_cast<uint8_t>(ExtendedMessageType::UT_METADATA) && supports_metadata_exchange_) {
940
+ // Note: Peers send messages using OUR extension ID (what we advertised in our handshake),
941
+ // not their ID. We use peer_ut_metadata_id_ only when SENDING to them.
942
+ handle_metadata_message(extended_payload);
943
+ } else {
944
+ LOG_BT_DEBUG("Unknown or unsupported extended message ID: " << static_cast<int>(extended_id));
945
+ }
946
+ }
947
+
948
+ void PeerConnection::send_extended_handshake() {
949
+ // Create extended handshake bencode dictionary
950
+ BencodeValue handshake = BencodeValue::create_dict();
951
+
952
+ // Add supported extensions
953
+ BencodeValue extensions = BencodeValue::create_dict();
954
+ extensions["ut_metadata"] = BencodeValue::create_integer(static_cast<uint8_t>(ExtendedMessageType::UT_METADATA));
955
+ handshake["m"] = extensions;
956
+
957
+ // Add metadata size if we have metadata download
958
+ auto* metadata_download = torrent_->get_metadata_download();
959
+ if (metadata_download) {
960
+ handshake["metadata_size"] = BencodeValue::create_integer(static_cast<int64_t>(metadata_download->get_metadata_size()));
961
+ }
962
+
963
+ // Encode the handshake
964
+ std::vector<uint8_t> encoded = handshake.encode();
965
+
966
+ // Create extended message payload: [extended_id][bencode_data]
967
+ std::vector<uint8_t> payload;
968
+ payload.push_back(static_cast<uint8_t>(ExtendedMessageType::HANDSHAKE));
969
+ payload.insert(payload.end(), encoded.begin(), encoded.end());
970
+
971
+ // Send as extended message
972
+ PeerMessage message(MessageType::EXTENDED, payload);
973
+ send_message(message);
974
+
975
+ LOG_BT_DEBUG("Sent extended handshake to peer " << peer_info_.ip << ":" << peer_info_.port);
976
+ }
977
+
978
+ void PeerConnection::send_bitfield() {
979
+ // Get our bitfield from the torrent
980
+ std::vector<bool> our_bitfield = torrent_->get_piece_bitfield();
981
+
982
+ // Don't send empty bitfield or if we have no pieces
983
+ bool has_any_piece = false;
984
+ for (bool has_piece : our_bitfield) {
985
+ if (has_piece) {
986
+ has_any_piece = true;
987
+ break;
988
+ }
989
+ }
990
+
991
+ if (!has_any_piece) {
992
+ LOG_BT_DEBUG("No pieces to advertise to peer " << peer_info_.ip << ":" << peer_info_.port);
993
+ return;
994
+ }
995
+
996
+ // Create and send bitfield message
997
+ auto bitfield_msg = PeerMessage::create_bitfield(our_bitfield);
998
+ if (send_message(bitfield_msg)) {
999
+ LOG_BT_DEBUG("Sent bitfield to peer " << peer_info_.ip << ":" << peer_info_.port);
1000
+ } else {
1001
+ LOG_BT_WARN("Failed to send bitfield to peer " << peer_info_.ip << ":" << peer_info_.port);
1002
+ }
1003
+ }
1004
+
1005
+ void PeerConnection::handle_extended_handshake(const std::vector<uint8_t>& payload) {
1006
+ LOG_BT_DEBUG("Handling extended handshake from peer " << peer_info_.ip << ":" << peer_info_.port);
1007
+
1008
+ try {
1009
+ BencodeValue handshake = bencode::decode(payload);
1010
+
1011
+ if (!handshake.is_dict()) {
1012
+ LOG_BT_WARN("Extended handshake is not a dictionary");
1013
+ return;
1014
+ }
1015
+
1016
+ // Check if peer supports ut_metadata extension
1017
+ if (handshake.has_key("m")) {
1018
+ const auto& extensions = handshake["m"];
1019
+ if (extensions.is_dict() && extensions.has_key("ut_metadata")) {
1020
+ peer_ut_metadata_id_ = static_cast<uint8_t>(extensions["ut_metadata"].as_integer());
1021
+ supports_metadata_exchange_ = true;
1022
+ LOG_BT_INFO("Peer " << peer_info_.ip << ":" << peer_info_.port
1023
+ << " supports metadata exchange (ID: " << static_cast<int>(peer_ut_metadata_id_) << ")");
1024
+ }
1025
+ }
1026
+
1027
+ // Get metadata size if available
1028
+ if (handshake.has_key("metadata_size")) {
1029
+ peer_metadata_size_ = static_cast<size_t>(handshake["metadata_size"].as_integer());
1030
+ LOG_BT_DEBUG("Peer has metadata size: " << peer_metadata_size_ << " bytes");
1031
+
1032
+ // Check if we need to create a metadata download
1033
+ auto* metadata_download = torrent_->get_metadata_download();
1034
+
1035
+ // Only create metadata download if:
1036
+ // 1. No existing metadata download in progress
1037
+ // 2. Peer has valid metadata size
1038
+ // 3. This is a metadata-only torrent (for BEP 9 metadata exchange)
1039
+ if (!metadata_download && peer_metadata_size_ > 0 && peer_metadata_size_ <= MAX_METADATA_SIZE
1040
+ && torrent_->get_torrent_info().is_metadata_only()) {
1041
+ // Create metadata download with the correct size
1042
+ const auto& info_hash = torrent_->get_torrent_info().get_info_hash();
1043
+ LOG_BT_INFO("Creating metadata download for hash " << info_hash_to_hex(info_hash)
1044
+ << " with size " << peer_metadata_size_ << " bytes");
1045
+
1046
+ auto new_metadata_download = std::make_shared<MetadataDownload>(info_hash, peer_metadata_size_);
1047
+
1048
+ // Set completion callback - need to use a custom approach since we need to call back through TorrentDownload
1049
+ // For now, we'll leave the completion callback to be set by add_torrent_by_hash after metadata download is created
1050
+ // The BitTorrentClient will need to monitor metadata downloads and set their callbacks appropriately
1051
+
1052
+ torrent_->set_metadata_download(new_metadata_download);
1053
+ metadata_download = new_metadata_download.get();
1054
+
1055
+ LOG_BT_INFO("Metadata download created and ready");
1056
+ }
1057
+
1058
+ // If we are downloading metadata, start requesting pieces
1059
+ if (metadata_download && supports_metadata_exchange_) {
1060
+ // Request first metadata piece
1061
+ uint32_t next_piece = metadata_download->get_next_piece_to_request();
1062
+ if (next_piece < metadata_download->get_num_pieces()) {
1063
+ request_metadata_piece(next_piece);
1064
+ }
1065
+ }
1066
+ }
1067
+
1068
+ } catch (const std::exception& e) {
1069
+ LOG_BT_ERROR("Failed to parse extended handshake: " << e.what());
1070
+ }
1071
+ }
1072
+
1073
+ void PeerConnection::handle_metadata_message(const std::vector<uint8_t>& payload) {
1074
+ LOG_BT_DEBUG("Handling metadata message from peer " << peer_info_.ip << ":" << peer_info_.port);
1075
+
1076
+ try {
1077
+ // Decode bencode dictionary at the start of payload
1078
+ BencodeValue msg_dict = bencode::decode(payload);
1079
+
1080
+ if (!msg_dict.is_dict()) {
1081
+ LOG_BT_WARN("Metadata message is not a dictionary");
1082
+ return;
1083
+ }
1084
+
1085
+ if (!msg_dict.has_key("msg_type") || !msg_dict.has_key("piece")) {
1086
+ LOG_BT_WARN("Metadata message missing required fields");
1087
+ return;
1088
+ }
1089
+
1090
+ uint8_t msg_type = static_cast<uint8_t>(msg_dict["msg_type"].as_integer());
1091
+ uint32_t piece_index = static_cast<uint32_t>(msg_dict["piece"].as_integer());
1092
+
1093
+ LOG_BT_DEBUG("Metadata message type: " << static_cast<int>(msg_type) << ", piece: " << piece_index);
1094
+
1095
+ auto* metadata_download = torrent_->get_metadata_download();
1096
+ if (!metadata_download) {
1097
+ LOG_BT_DEBUG("No metadata download in progress, ignoring metadata message");
1098
+ return;
1099
+ }
1100
+
1101
+ if (msg_type == static_cast<uint8_t>(MetadataMessageType::DATA)) {
1102
+ // Extract metadata piece data (comes after bencode dictionary)
1103
+ std::vector<uint8_t> encoded = msg_dict.encode();
1104
+ if (payload.size() <= encoded.size()) {
1105
+ LOG_BT_WARN("Metadata DATA message has no piece data");
1106
+ return;
1107
+ }
1108
+
1109
+ std::vector<uint8_t> piece_data(payload.begin() + encoded.size(), payload.end());
1110
+
1111
+ LOG_BT_INFO("Received metadata piece " << piece_index << " (" << piece_data.size() << " bytes) from peer "
1112
+ << peer_info_.ip << ":" << peer_info_.port);
1113
+
1114
+ // Store the piece
1115
+ if (metadata_download->store_metadata_piece(piece_index, piece_data)) {
1116
+ // Request next piece if available
1117
+ if (!metadata_download->is_complete()) {
1118
+ uint32_t next_piece = metadata_download->get_next_piece_to_request();
1119
+ if (next_piece < metadata_download->get_num_pieces()) {
1120
+ request_metadata_piece(next_piece);
1121
+ }
1122
+ }
1123
+ }
1124
+ } else if (msg_type == static_cast<uint8_t>(MetadataMessageType::REJECT)) {
1125
+ LOG_BT_WARN("Peer " << peer_info_.ip << ":" << peer_info_.port
1126
+ << " rejected metadata piece " << piece_index << " request");
1127
+ }
1128
+
1129
+ } catch (const std::exception& e) {
1130
+ LOG_BT_ERROR("Failed to parse metadata message: " << e.what());
1131
+ }
1132
+ }
1133
+
1134
+ void PeerConnection::request_metadata_piece(uint32_t piece_index) {
1135
+ if (!supports_metadata_exchange_) {
1136
+ LOG_BT_WARN("Peer does not support metadata exchange");
1137
+ return;
1138
+ }
1139
+
1140
+ LOG_BT_DEBUG("Requesting metadata piece " << piece_index << " from peer " << peer_info_.ip << ":" << peer_info_.port);
1141
+
1142
+ // Create metadata request message
1143
+ BencodeValue msg_dict = BencodeValue::create_dict();
1144
+ msg_dict["msg_type"] = BencodeValue::create_integer(static_cast<int>(MetadataMessageType::REQUEST));
1145
+ msg_dict["piece"] = BencodeValue::create_integer(piece_index);
1146
+
1147
+ // Encode the message
1148
+ std::vector<uint8_t> encoded = msg_dict.encode();
1149
+
1150
+ // Create extended message payload: [peer_ut_metadata_id][bencode_data]
1151
+ std::vector<uint8_t> payload;
1152
+ payload.push_back(peer_ut_metadata_id_);
1153
+ payload.insert(payload.end(), encoded.begin(), encoded.end());
1154
+
1155
+ // Send as extended message
1156
+ PeerMessage message(MessageType::EXTENDED, payload);
1157
+ send_message(message);
1158
+ }
1159
+
1160
+ bool PeerConnection::request_piece_block(PieceIndex piece_index, uint32_t offset, uint32_t length) {
1161
+ if (peer_choked_ || state_ != PeerState::CONNECTED) {
1162
+ return false;
1163
+ }
1164
+
1165
+ std::lock_guard<std::mutex> lock(requests_mutex_);
1166
+
1167
+ if (pending_requests_.size() >= MAX_REQUESTS_PER_PEER) {
1168
+ return false; // Too many pending requests
1169
+ }
1170
+
1171
+ auto request_msg = PeerMessage::create_request(piece_index, offset, length);
1172
+ if (send_message(request_msg)) {
1173
+ pending_requests_.emplace_back(piece_index, offset, length);
1174
+ LOG_BT_DEBUG("Requested piece " << piece_index << " offset " << offset << " length " << length << " from peer " << peer_info_.ip << ":" << peer_info_.port);
1175
+ return true;
1176
+ }
1177
+
1178
+ return false;
1179
+ }
1180
+
1181
+ bool PeerConnection::is_block_requested(PieceIndex piece_index, uint32_t offset) const {
1182
+ std::lock_guard<std::mutex> lock(requests_mutex_);
1183
+ for (const auto& req : pending_requests_) {
1184
+ if (req.piece_index == piece_index && req.offset == offset) {
1185
+ return true;
1186
+ }
1187
+ }
1188
+ return false;
1189
+ }
1190
+
1191
+ void PeerConnection::cancel_request(PieceIndex piece_index, uint32_t offset, uint32_t length) {
1192
+ auto cancel_msg = PeerMessage::create_cancel(piece_index, offset, length);
1193
+ send_message(cancel_msg);
1194
+
1195
+ {
1196
+ std::lock_guard<std::mutex> lock(requests_mutex_);
1197
+ pending_requests_.erase(
1198
+ std::remove_if(pending_requests_.begin(), pending_requests_.end(),
1199
+ [piece_index, offset, length](const PeerRequest& req) {
1200
+ return req.piece_index == piece_index && req.offset == offset && req.length == length;
1201
+ }),
1202
+ pending_requests_.end());
1203
+ }
1204
+
1205
+ // Reset block request status so block can be requested from other peers
1206
+ uint32_t block_index = offset / BLOCK_SIZE;
1207
+ torrent_->reset_block_request(piece_index, block_index);
1208
+ }
1209
+
1210
+ void PeerConnection::cancel_all_requests() {
1211
+ std::vector<PeerRequest> requests_to_reset;
1212
+
1213
+ {
1214
+ std::lock_guard<std::mutex> lock(requests_mutex_);
1215
+
1216
+ for (const auto& request : pending_requests_) {
1217
+ auto cancel_msg = PeerMessage::create_cancel(request.piece_index, request.offset, request.length);
1218
+ send_message(cancel_msg);
1219
+ requests_to_reset.push_back(request);
1220
+ }
1221
+
1222
+ pending_requests_.clear();
1223
+ }
1224
+
1225
+ // Reset block request status so blocks can be requested from other peers
1226
+ for (const auto& req : requests_to_reset) {
1227
+ uint32_t block_index = req.offset / BLOCK_SIZE;
1228
+ torrent_->reset_block_request(req.piece_index, block_index);
1229
+ }
1230
+ }
1231
+
1232
+ void PeerConnection::set_interested(bool interested) {
1233
+ if (am_interested_ != interested) {
1234
+ am_interested_ = interested;
1235
+ auto msg = interested ? PeerMessage::create_interested() : PeerMessage::create_not_interested();
1236
+ send_message(msg);
1237
+ }
1238
+ }
1239
+
1240
+ void PeerConnection::set_choke(bool choke) {
1241
+ if (am_choking_ != choke) {
1242
+ am_choking_ = choke;
1243
+ auto msg = choke ? PeerMessage::create_choke() : PeerMessage::create_unchoke();
1244
+ send_message(msg);
1245
+ }
1246
+ }
1247
+
1248
+ bool PeerConnection::has_piece(PieceIndex piece_index) const {
1249
+ return piece_index < peer_bitfield_.size() && peer_bitfield_[piece_index];
1250
+ }
1251
+
1252
+ void PeerConnection::update_bitfield(const std::vector<bool>& bitfield) {
1253
+ peer_bitfield_ = bitfield;
1254
+ }
1255
+
1256
+ void PeerConnection::cleanup_expired_requests() {
1257
+ std::vector<PeerRequest> expired_requests;
1258
+
1259
+ {
1260
+ std::lock_guard<std::mutex> lock(requests_mutex_);
1261
+
1262
+ auto now = std::chrono::steady_clock::now();
1263
+ auto timeout = std::chrono::milliseconds(REQUEST_TIMEOUT_MS);
1264
+
1265
+ // Collect expired requests before removing them
1266
+ for (const auto& req : pending_requests_) {
1267
+ if (now - req.requested_at > timeout) {
1268
+ expired_requests.push_back(req);
1269
+ }
1270
+ }
1271
+
1272
+ pending_requests_.erase(
1273
+ std::remove_if(pending_requests_.begin(), pending_requests_.end(),
1274
+ [now, timeout](const PeerRequest& req) {
1275
+ return now - req.requested_at > timeout;
1276
+ }),
1277
+ pending_requests_.end());
1278
+ }
1279
+
1280
+ // Reset block request status for expired requests (so they can be requested again)
1281
+ for (const auto& req : expired_requests) {
1282
+ uint32_t block_index = req.offset / BLOCK_SIZE;
1283
+ torrent_->reset_block_request(req.piece_index, block_index);
1284
+ LOG_BT_DEBUG("Request timeout: piece " << req.piece_index << " block " << block_index
1285
+ << " from peer " << peer_info_.ip << ":" << peer_info_.port);
1286
+ }
1287
+ }
1288
+
1289
+ void PeerConnection::update_speed_stats() {
1290
+ std::lock_guard<std::mutex> lock(speed_mutex_);
1291
+
1292
+ auto now = std::chrono::steady_clock::now();
1293
+ double elapsed_seconds = std::chrono::duration<double>(now - last_speed_update_time_).count();
1294
+
1295
+ // Update speed every second at minimum to avoid division by very small numbers
1296
+ if (elapsed_seconds >= 1.0) {
1297
+ uint64_t current_downloaded = downloaded_bytes_.load();
1298
+ uint64_t current_uploaded = uploaded_bytes_.load();
1299
+
1300
+ uint64_t downloaded_delta = current_downloaded - last_speed_downloaded_;
1301
+ uint64_t uploaded_delta = current_uploaded - last_speed_uploaded_;
1302
+
1303
+ download_speed_.store(static_cast<double>(downloaded_delta) / elapsed_seconds);
1304
+ upload_speed_.store(static_cast<double>(uploaded_delta) / elapsed_seconds);
1305
+
1306
+ last_speed_downloaded_ = current_downloaded;
1307
+ last_speed_uploaded_ = current_uploaded;
1308
+ last_speed_update_time_ = now;
1309
+ }
1310
+ }
1311
+
1312
+ double PeerConnection::get_download_speed() const {
1313
+ return download_speed_.load();
1314
+ }
1315
+
1316
+ double PeerConnection::get_upload_speed() const {
1317
+ return upload_speed_.load();
1318
+ }
1319
+
1320
+ bool PeerConnection::read_data(std::vector<uint8_t>& buffer, size_t length) {
1321
+ if (!is_valid_socket(socket_)) {
1322
+ return false;
1323
+ }
1324
+
1325
+ // Use the existing socket function to read exact bytes
1326
+ std::vector<uint8_t> data = receive_exact_bytes(socket_, length);
1327
+ if (data.size() != length) {
1328
+ // receive_exact_bytes returns empty on socket error or connection closed
1329
+ // If we got 0 bytes, the socket is likely dead - mark connection as error
1330
+ if (data.empty()) {
1331
+ LOG_BT_DEBUG("Socket read returned empty - connection likely closed by peer "
1332
+ << peer_info_.ip << ":" << peer_info_.port);
1333
+ state_.store(PeerState::ERROR);
1334
+ } else {
1335
+ LOG_BT_DEBUG("Failed to read exact amount of data from socket (got "
1336
+ << data.size() << " of " << length << " bytes)");
1337
+ }
1338
+ return false;
1339
+ }
1340
+
1341
+ buffer = std::move(data);
1342
+ return true;
1343
+ }
1344
+
1345
+ bool PeerConnection::write_data(const std::vector<uint8_t>& data) {
1346
+ if (!is_valid_socket(socket_)) {
1347
+ return false;
1348
+ }
1349
+
1350
+ // Use the existing socket function for binary data directly
1351
+ int sent = send_tcp_data(socket_, data);
1352
+ return sent > 0 && static_cast<size_t>(sent) == data.size();
1353
+ }
1354
+
1355
+ //=============================================================================
1356
+ // MetadataDownload Implementation
1357
+ //=============================================================================
1358
+
1359
+ MetadataDownload::MetadataDownload(const InfoHash& info_hash, size_t metadata_size)
1360
+ : info_hash_(info_hash), metadata_size_(metadata_size), completion_triggered_(false) {
1361
+
1362
+ // Calculate number of pieces needed
1363
+ num_pieces_ = (metadata_size + METADATA_PIECE_SIZE - 1) / METADATA_PIECE_SIZE;
1364
+
1365
+ // Initialize piece storage
1366
+ pieces_.resize(num_pieces_);
1367
+ pieces_complete_.resize(num_pieces_, false);
1368
+
1369
+ LOG_BT_INFO("Created metadata download for info hash " << info_hash_to_hex(info_hash)
1370
+ << " (size: " << metadata_size << " bytes, " << num_pieces_ << " pieces)");
1371
+ }
1372
+
1373
+ MetadataDownload::~MetadataDownload() = default;
1374
+
1375
+ bool MetadataDownload::store_metadata_piece(uint32_t piece_index, const std::vector<uint8_t>& data) {
1376
+ MetadataCompleteCallback callback_to_invoke;
1377
+ TorrentInfo completed_torrent_info;
1378
+ bool should_invoke_callback = false;
1379
+
1380
+ {
1381
+ std::lock_guard<std::mutex> lock(mutex_);
1382
+
1383
+ if (piece_index >= num_pieces_) {
1384
+ LOG_BT_ERROR("Invalid metadata piece index: " << piece_index << " (max: " << num_pieces_ - 1 << ")");
1385
+ return false;
1386
+ }
1387
+
1388
+ // Skip if this piece is already complete (received from another peer)
1389
+ if (pieces_complete_[piece_index]) {
1390
+ LOG_BT_DEBUG("Metadata piece " << piece_index << " already received, skipping duplicate");
1391
+ return true;
1392
+ }
1393
+
1394
+ // Skip if completion callback was already triggered
1395
+ if (completion_triggered_) {
1396
+ LOG_BT_DEBUG("Metadata download already completed, ignoring piece " << piece_index);
1397
+ return true;
1398
+ }
1399
+
1400
+ // Calculate expected piece size
1401
+ size_t expected_size;
1402
+ if (piece_index == num_pieces_ - 1) {
1403
+ // Last piece might be smaller
1404
+ expected_size = metadata_size_ - (piece_index * METADATA_PIECE_SIZE);
1405
+ } else {
1406
+ expected_size = METADATA_PIECE_SIZE;
1407
+ }
1408
+
1409
+ if (data.size() != expected_size) {
1410
+ LOG_BT_ERROR("Invalid metadata piece size for piece " << piece_index
1411
+ << ": expected " << expected_size << ", got " << data.size());
1412
+ return false;
1413
+ }
1414
+
1415
+ pieces_[piece_index] = data;
1416
+ pieces_complete_[piece_index] = true;
1417
+
1418
+ LOG_BT_DEBUG("Stored metadata piece " << piece_index << " (" << data.size() << " bytes)");
1419
+
1420
+ // Check if download is complete and prepare callback data
1421
+ // (callback will be invoked outside of lock to prevent deadlock)
1422
+ bool all_complete = std::all_of(pieces_complete_.begin(), pieces_complete_.end(),
1423
+ [](bool complete) { return complete; });
1424
+
1425
+ if (all_complete && !completion_triggered_) {
1426
+ completion_triggered_ = true; // Ensure callback is only triggered once
1427
+ LOG_BT_INFO("Metadata download complete for info hash " << info_hash_to_hex(info_hash_));
1428
+
1429
+ // Verify metadata (use unlocked version since we already hold the mutex)
1430
+ if (verify_metadata_unlocked()) {
1431
+ // Parse metadata into TorrentInfo (use unlocked version)
1432
+ std::vector<uint8_t> metadata = get_metadata_unlocked();
1433
+
1434
+ try {
1435
+ BencodeValue info_dict = bencode::decode(metadata);
1436
+
1437
+ // Create a fake torrent dictionary with just the info dict
1438
+ BencodeValue torrent_data = BencodeValue::create_dict();
1439
+ torrent_data["info"] = info_dict;
1440
+
1441
+ if (completed_torrent_info.load_from_bencode(torrent_data)) {
1442
+ callback_to_invoke = completion_callback_;
1443
+ should_invoke_callback = true;
1444
+ } else {
1445
+ LOG_BT_ERROR("Failed to parse metadata into TorrentInfo");
1446
+ }
1447
+ } catch (const std::exception& e) {
1448
+ LOG_BT_ERROR("Failed to decode metadata: " << e.what());
1449
+ }
1450
+ } else {
1451
+ LOG_BT_ERROR("Metadata verification failed - hash mismatch");
1452
+ }
1453
+ }
1454
+ } // mutex_ released here
1455
+
1456
+ // Invoke callback OUTSIDE of lock to prevent deadlock
1457
+ // (as documented: "never call callbacks while holding this mutex!")
1458
+ if (should_invoke_callback && callback_to_invoke) {
1459
+ callback_to_invoke(completed_torrent_info);
1460
+ }
1461
+
1462
+ return true;
1463
+ }
1464
+
1465
+ bool MetadataDownload::is_complete() const {
1466
+ std::lock_guard<std::mutex> lock(mutex_);
1467
+ return completion_triggered_ || std::all_of(pieces_complete_.begin(), pieces_complete_.end(), [](bool complete) { return complete; });
1468
+ }
1469
+
1470
+ std::vector<uint8_t> MetadataDownload::get_metadata_unlocked() const {
1471
+ // Internal version - must be called with mutex_ already held
1472
+ std::vector<uint8_t> metadata;
1473
+ metadata.reserve(metadata_size_);
1474
+
1475
+ for (const auto& piece : pieces_) {
1476
+ metadata.insert(metadata.end(), piece.begin(), piece.end());
1477
+ }
1478
+
1479
+ return metadata;
1480
+ }
1481
+
1482
+ std::vector<uint8_t> MetadataDownload::get_metadata() const {
1483
+ std::lock_guard<std::mutex> lock(mutex_);
1484
+ return get_metadata_unlocked();
1485
+ }
1486
+
1487
+ bool MetadataDownload::verify_metadata_unlocked() const {
1488
+ // Internal version - must be called with mutex_ already held
1489
+
1490
+ // Reconstruct full metadata
1491
+ std::vector<uint8_t> metadata;
1492
+ metadata.reserve(metadata_size_);
1493
+
1494
+ for (const auto& piece : pieces_) {
1495
+ metadata.insert(metadata.end(), piece.begin(), piece.end());
1496
+ }
1497
+
1498
+ // Calculate SHA1 hash
1499
+ std::string calculated_hash = SHA1::hash_bytes(metadata);
1500
+
1501
+ // Convert stored info hash to hex string for comparison
1502
+ std::ostringstream stored_hash_hex;
1503
+ for (size_t i = 0; i < info_hash_.size(); ++i) {
1504
+ stored_hash_hex << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(info_hash_[i]);
1505
+ }
1506
+
1507
+ bool verified = (calculated_hash == stored_hash_hex.str());
1508
+
1509
+ if (verified) {
1510
+ LOG_BT_INFO("Metadata verification PASSED for info hash " << info_hash_to_hex(info_hash_));
1511
+ } else {
1512
+ LOG_BT_ERROR("Metadata verification FAILED for info hash " << info_hash_to_hex(info_hash_));
1513
+ LOG_BT_ERROR(" Expected: " << stored_hash_hex.str());
1514
+ LOG_BT_ERROR(" Calculated: " << calculated_hash);
1515
+ }
1516
+
1517
+ return verified;
1518
+ }
1519
+
1520
+ bool MetadataDownload::verify_metadata() const {
1521
+ std::lock_guard<std::mutex> lock(mutex_);
1522
+ return verify_metadata_unlocked();
1523
+ }
1524
+
1525
+ uint32_t MetadataDownload::get_next_piece_to_request() const {
1526
+ std::lock_guard<std::mutex> lock(mutex_);
1527
+
1528
+ for (uint32_t i = 0; i < num_pieces_; ++i) {
1529
+ if (!pieces_complete_[i]) {
1530
+ return i;
1531
+ }
1532
+ }
1533
+
1534
+ return num_pieces_; // All pieces complete
1535
+ }
1536
+
1537
+ bool MetadataDownload::is_piece_complete(uint32_t piece_index) const {
1538
+ std::lock_guard<std::mutex> lock(mutex_);
1539
+
1540
+ if (piece_index >= num_pieces_) {
1541
+ return false;
1542
+ }
1543
+
1544
+ return pieces_complete_[piece_index];
1545
+ }
1546
+
1547
+ //=============================================================================
1548
+ // TorrentDownload Implementation
1549
+ //=============================================================================
1550
+
1551
+ TorrentDownload::TorrentDownload(const TorrentInfo& torrent_info, const std::string& download_path)
1552
+ : torrent_info_(torrent_info), download_path_(download_path),
1553
+ running_(false), paused_(false), total_downloaded_(0), total_uploaded_(0),
1554
+ last_speed_update_time_(std::chrono::steady_clock::now()),
1555
+ last_speed_downloaded_(0), last_speed_uploaded_(0),
1556
+ download_speed_(0.0), upload_speed_(0.0),
1557
+ last_logged_progress_(0.0), last_state_log_time_(std::chrono::steady_clock::now()) {
1558
+
1559
+ // Initialize pieces
1560
+ uint32_t num_pieces = torrent_info_.get_num_pieces();
1561
+ pieces_.reserve(num_pieces);
1562
+ piece_completed_.resize(num_pieces, false);
1563
+ piece_downloading_.resize(num_pieces, false);
1564
+
1565
+ const auto& piece_hashes = torrent_info_.get_piece_hashes();
1566
+ for (uint32_t i = 0; i < num_pieces; ++i) {
1567
+ uint32_t piece_length = torrent_info_.get_piece_length(i);
1568
+ pieces_.push_back(std::make_unique<PieceInfo>(i, piece_hashes[i], piece_length));
1569
+ }
1570
+
1571
+ // Generate peer ID
1572
+ our_peer_id_ = generate_peer_id();
1573
+
1574
+ // Create tracker manager
1575
+ tracker_manager_ = std::make_unique<TrackerManager>(torrent_info_);
1576
+
1577
+ LOG_BT_INFO("Created torrent download for: " << torrent_info_.get_name());
1578
+ LOG_BT_INFO(" Download path: " << download_path_);
1579
+ LOG_BT_INFO(" Total size: " << torrent_info_.get_total_length() << " bytes");
1580
+ LOG_BT_INFO(" Number of pieces: " << num_pieces);
1581
+ LOG_BT_INFO(" Trackers: " << tracker_manager_->get_tracker_urls().size());
1582
+ }
1583
+
1584
+ TorrentDownload::~TorrentDownload() {
1585
+ stop();
1586
+ }
1587
+
1588
+ bool TorrentDownload::start() {
1589
+ if (running_) {
1590
+ return true;
1591
+ }
1592
+
1593
+ LOG_BT_INFO("Starting torrent download: " << torrent_info_.get_name());
1594
+
1595
+ // Skip file/directory operations for metadata-only downloads (empty download path)
1596
+ if (!download_path_.empty()) {
1597
+ // Create directory structure
1598
+ if (!create_directory_structure()) {
1599
+ LOG_BT_ERROR("Failed to create directory structure");
1600
+ return false;
1601
+ }
1602
+
1603
+ // Open files
1604
+ if (!open_files()) {
1605
+ LOG_BT_ERROR("Failed to open files");
1606
+ return false;
1607
+ }
1608
+ } else {
1609
+ LOG_BT_DEBUG("Skipping file operations for metadata-only torrent");
1610
+ }
1611
+
1612
+ running_ = true;
1613
+ paused_ = false;
1614
+
1615
+ // Start download threads
1616
+ download_thread_ = std::thread(&TorrentDownload::download_loop, this);
1617
+ peer_management_thread_ = std::thread(&TorrentDownload::peer_management_loop, this);
1618
+
1619
+ LOG_BT_INFO("Torrent download started: " << torrent_info_.get_name());
1620
+ return true;
1621
+ }
1622
+
1623
+ void TorrentDownload::stop() {
1624
+ if (!running_) {
1625
+ return;
1626
+ }
1627
+
1628
+ LOG_BT_INFO("Stopping torrent download: " << torrent_info_.get_name());
1629
+
1630
+ running_ = false;
1631
+
1632
+ // Stop all peer connections
1633
+ {
1634
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1635
+ for (auto& peer : peer_connections_) {
1636
+ peer->disconnect();
1637
+ }
1638
+ peer_connections_.clear();
1639
+ }
1640
+
1641
+ // Wait for threads to finish
1642
+ if (download_thread_.joinable()) {
1643
+ download_thread_.join();
1644
+ }
1645
+ if (peer_management_thread_.joinable()) {
1646
+ peer_management_thread_.join();
1647
+ }
1648
+
1649
+ // Close files
1650
+ close_files();
1651
+
1652
+ LOG_BT_INFO("Torrent download stopped: " << torrent_info_.get_name());
1653
+ }
1654
+
1655
+ void TorrentDownload::pause() {
1656
+ paused_ = true;
1657
+ LOG_BT_INFO("Paused torrent download: " << torrent_info_.get_name());
1658
+ }
1659
+
1660
+ void TorrentDownload::resume() {
1661
+ paused_ = false;
1662
+ LOG_BT_INFO("Resumed torrent download: " << torrent_info_.get_name());
1663
+ }
1664
+
1665
+ bool TorrentDownload::is_complete() const {
1666
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
1667
+ return std::all_of(piece_completed_.begin(), piece_completed_.end(), [](bool completed) { return completed; });
1668
+ }
1669
+
1670
+ bool TorrentDownload::add_peer(const Peer& peer) {
1671
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1672
+
1673
+ // Check if peer already exists
1674
+ for (const auto& conn : peer_connections_) {
1675
+ if (conn->get_peer_info().ip == peer.ip && conn->get_peer_info().port == peer.port) {
1676
+ return false; // Peer already exists
1677
+ }
1678
+ }
1679
+
1680
+ // Check peer limit
1681
+ if (peer_connections_.size() >= MAX_PEERS_PER_TORRENT) {
1682
+ LOG_BT_DEBUG("Peer limit reached for torrent " << torrent_info_.get_name());
1683
+ return false;
1684
+ }
1685
+
1686
+ // Create new peer connection
1687
+ auto peer_conn = std::make_unique<PeerConnection>(this, peer);
1688
+ if (peer_conn->connect()) {
1689
+ peer_connections_.push_back(std::move(peer_conn));
1690
+
1691
+ if (peer_connected_callback_) {
1692
+ peer_connected_callback_(peer);
1693
+ }
1694
+
1695
+ LOG_BT_INFO("Added peer " << peer.ip << ":" << peer.port << " to torrent " << torrent_info_.get_name());
1696
+ return true;
1697
+ }
1698
+
1699
+ return false;
1700
+ }
1701
+
1702
+ bool TorrentDownload::add_peer(const Peer& peer, socket_t existing_socket) {
1703
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1704
+
1705
+ // Check if peer already exists
1706
+ for (const auto& conn : peer_connections_) {
1707
+ if (conn->get_peer_info().ip == peer.ip && conn->get_peer_info().port == peer.port) {
1708
+ LOG_BT_DEBUG("Peer already exists: " << peer.ip << ":" << peer.port);
1709
+ close_socket(existing_socket); // Close the duplicate incoming connection
1710
+ return false;
1711
+ }
1712
+ }
1713
+
1714
+ // Check peer limit
1715
+ if (peer_connections_.size() >= MAX_PEERS_PER_TORRENT) {
1716
+ LOG_BT_DEBUG("Peer limit reached for torrent " << torrent_info_.get_name());
1717
+ close_socket(existing_socket);
1718
+ return false;
1719
+ }
1720
+
1721
+ // Create peer connection with existing socket
1722
+ auto peer_conn = std::make_unique<PeerConnection>(this, peer, existing_socket);
1723
+ if (peer_conn->connect()) {
1724
+ peer_connections_.push_back(std::move(peer_conn));
1725
+
1726
+ if (peer_connected_callback_) {
1727
+ peer_connected_callback_(peer);
1728
+ }
1729
+
1730
+ LOG_BT_INFO("Added incoming peer " << peer.ip << ":" << peer.port << " to torrent " << torrent_info_.get_name());
1731
+ return true;
1732
+ }
1733
+
1734
+ return false;
1735
+ }
1736
+
1737
+ void TorrentDownload::remove_peer(const Peer& peer) {
1738
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1739
+
1740
+ peer_connections_.erase(
1741
+ std::remove_if(peer_connections_.begin(), peer_connections_.end(),
1742
+ [&peer](const std::unique_ptr<PeerConnection>& conn) {
1743
+ return conn->get_peer_info().ip == peer.ip && conn->get_peer_info().port == peer.port;
1744
+ }),
1745
+ peer_connections_.end());
1746
+
1747
+ if (peer_disconnected_callback_) {
1748
+ peer_disconnected_callback_(peer);
1749
+ }
1750
+
1751
+ LOG_BT_DEBUG("Removed peer " << peer.ip << ":" << peer.port << " from torrent " << torrent_info_.get_name());
1752
+ }
1753
+
1754
+ size_t TorrentDownload::get_peer_count() const {
1755
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1756
+ return peer_connections_.size();
1757
+ }
1758
+
1759
+ std::vector<Peer> TorrentDownload::get_connected_peers() const {
1760
+ std::lock_guard<std::mutex> lock(peers_mutex_);
1761
+
1762
+ std::vector<Peer> peers;
1763
+ for (const auto& conn : peer_connections_) {
1764
+ if (conn->is_connected()) {
1765
+ peers.push_back(conn->get_peer_info());
1766
+ }
1767
+ }
1768
+
1769
+ return peers;
1770
+ }
1771
+
1772
+ bool TorrentDownload::is_piece_complete(PieceIndex piece_index) const {
1773
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
1774
+ return piece_index < piece_completed_.size() && piece_completed_[piece_index];
1775
+ }
1776
+
1777
+ bool TorrentDownload::is_piece_downloading(PieceIndex piece_index) const {
1778
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
1779
+ return piece_index < piece_downloading_.size() && piece_downloading_[piece_index];
1780
+ }
1781
+
1782
+ bool TorrentDownload::store_piece_block(PieceIndex piece_index, uint32_t offset, const std::vector<uint8_t>& data) {
1783
+ bool piece_just_completed = false;
1784
+ bool verification_failed = false;
1785
+
1786
+ {
1787
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
1788
+
1789
+ if (piece_index >= pieces_.size()) {
1790
+ LOG_BT_ERROR("Invalid piece index: " << piece_index);
1791
+ return false;
1792
+ }
1793
+
1794
+ auto& piece = pieces_[piece_index];
1795
+
1796
+ // Validate offset and data size
1797
+ if (offset + data.size() > piece->length) {
1798
+ LOG_BT_ERROR("Block data exceeds piece length for piece " << piece_index);
1799
+ return false;
1800
+ }
1801
+
1802
+ // Calculate block index
1803
+ uint32_t block_index = offset / BLOCK_SIZE;
1804
+ if (block_index >= piece->get_num_blocks()) {
1805
+ LOG_BT_ERROR("Invalid block index: " << block_index << " for piece " << piece_index);
1806
+ return false;
1807
+ }
1808
+
1809
+ // Ensure piece data buffer is allocated (lazy allocation)
1810
+ piece->ensure_data_allocated();
1811
+
1812
+ // Store the block data
1813
+ std::copy(data.begin(), data.end(), piece->data.begin() + offset);
1814
+ piece->blocks_downloaded[block_index] = true;
1815
+
1816
+ LOG_BT_DEBUG("Stored block " << block_index << " for piece " << piece_index
1817
+ << " (offset: " << offset << ", size: " << data.size() << ")");
1818
+
1819
+ // Check if piece is complete
1820
+ if (piece->is_complete() && !piece->verified) {
1821
+ LOG_BT_INFO("Piece " << piece_index << " downloaded, verifying...");
1822
+ if (verify_piece(piece_index)) {
1823
+ piece_completed_[piece_index] = true;
1824
+ piece_downloading_[piece_index] = false;
1825
+
1826
+ // Write piece to disk (files_mutex_ is after pieces_mutex_ in lock order)
1827
+ write_piece_to_disk(piece_index);
1828
+
1829
+ // Update statistics
1830
+ total_downloaded_ += piece->length;
1831
+
1832
+ piece_just_completed = true;
1833
+ LOG_BT_INFO("Piece " << piece_index << " verified and saved");
1834
+ } else {
1835
+ LOG_BT_ERROR("Piece " << piece_index << " verification failed, requesting re-download");
1836
+ // Reset piece for re-download - must reset both downloaded and requested status
1837
+ std::fill(piece->blocks_downloaded.begin(), piece->blocks_downloaded.end(), false);
1838
+ std::fill(piece->blocks_requested.begin(), piece->blocks_requested.end(), false);
1839
+ piece_downloading_[piece_index] = false;
1840
+ verification_failed = true;
1841
+ }
1842
+ }
1843
+ } // pieces_mutex_ released here
1844
+
1845
+ // Notify completion AFTER releasing pieces_mutex_ to respect lock order:
1846
+ // peers_mutex_ -> pieces_mutex_
1847
+ if (piece_just_completed) {
1848
+ on_piece_completed(piece_index);
1849
+ }
1850
+
1851
+ return !verification_failed;
1852
+ }
1853
+
1854
+ bool TorrentDownload::verify_piece(PieceIndex piece_index) {
1855
+ if (piece_index >= pieces_.size()) {
1856
+ return false;
1857
+ }
1858
+
1859
+ auto& piece = pieces_[piece_index];
1860
+
1861
+ // Check that piece data is allocated
1862
+ if (piece->data.empty()) {
1863
+ LOG_BT_ERROR("Cannot verify piece " << piece_index << " - data not allocated");
1864
+ return false;
1865
+ }
1866
+
1867
+ // Calculate SHA1 hash of piece data
1868
+ std::string calculated_hash = SHA1::hash_bytes(piece->data);
1869
+
1870
+ // Convert stored hash to hex string for comparison
1871
+ std::ostringstream stored_hash_hex;
1872
+ for (size_t i = 0; i < piece->hash.size(); ++i) {
1873
+ stored_hash_hex << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(piece->hash[i]);
1874
+ }
1875
+
1876
+ bool verified = (calculated_hash == stored_hash_hex.str());
1877
+ piece->verified = verified;
1878
+
1879
+ LOG_BT_DEBUG("Piece " << piece_index << " verification: " << (verified ? "PASSED" : "FAILED"));
1880
+ return verified;
1881
+ }
1882
+
1883
+ void TorrentDownload::write_piece_to_disk(PieceIndex piece_index) {
1884
+ std::lock_guard<std::mutex> lock(files_mutex_);
1885
+
1886
+ // Skip for metadata-only torrents
1887
+ if (download_path_.empty()) {
1888
+ return;
1889
+ }
1890
+
1891
+ if (piece_index >= pieces_.size()) {
1892
+ LOG_BT_ERROR("Invalid piece index for disk write: " << piece_index);
1893
+ return;
1894
+ }
1895
+
1896
+ auto& piece = pieces_[piece_index];
1897
+ if (!piece->verified) {
1898
+ LOG_BT_ERROR("Attempting to write unverified piece " << piece_index << " to disk");
1899
+ return;
1900
+ }
1901
+
1902
+ // Calculate piece offset in the torrent
1903
+ uint64_t piece_offset = static_cast<uint64_t>(piece_index) * torrent_info_.get_piece_length();
1904
+ uint64_t remaining_data = piece->length;
1905
+ uint64_t data_offset = 0;
1906
+
1907
+ // Write piece data to the appropriate files
1908
+ for (const auto& file_info : torrent_info_.get_files()) {
1909
+ if (piece_offset >= file_info.offset + file_info.length) {
1910
+ continue; // This piece doesn't overlap with this file
1911
+ }
1912
+
1913
+ if (piece_offset + piece->length <= file_info.offset) {
1914
+ break; // No more files will be affected by this piece
1915
+ }
1916
+
1917
+ // Calculate overlap
1918
+ uint64_t file_start_in_piece = (file_info.offset > piece_offset) ?
1919
+ file_info.offset - piece_offset : 0;
1920
+ uint64_t piece_end = piece_offset + piece->length;
1921
+ uint64_t file_end = file_info.offset + file_info.length;
1922
+ uint64_t write_end = (std::min)(piece_end, file_end);
1923
+ uint64_t write_length = write_end - (piece_offset + file_start_in_piece);
1924
+
1925
+ if (write_length == 0) {
1926
+ continue;
1927
+ }
1928
+
1929
+ // Calculate file offset
1930
+ uint64_t file_offset = (piece_offset > file_info.offset) ?
1931
+ piece_offset - file_info.offset : 0;
1932
+
1933
+ // Write data chunk to file using fs module
1934
+ std::string file_path = download_path_ + "/" + file_info.path;
1935
+ const void* write_data = piece->data.data() + file_start_in_piece;
1936
+
1937
+ if (!write_file_chunk(file_path.c_str(), file_offset, write_data, write_length)) {
1938
+ LOG_BT_ERROR("Failed to write data to file: " << file_path);
1939
+ continue;
1940
+ }
1941
+
1942
+ LOG_BT_DEBUG("Wrote " << write_length << " bytes to file " << file_info.path
1943
+ << " at offset " << file_offset);
1944
+ }
1945
+
1946
+ // Free piece data from memory after successful write to disk
1947
+ piece->free_data();
1948
+ LOG_BT_DEBUG("Freed piece " << piece_index << " data from memory after write to disk");
1949
+ }
1950
+
1951
+ bool TorrentDownload::read_piece_from_disk(PieceIndex piece_index, std::vector<uint8_t>& data) {
1952
+ // Lock order: pieces_mutex_ -> files_mutex_
1953
+ std::lock_guard<std::mutex> pieces_lock(pieces_mutex_);
1954
+ std::lock_guard<std::mutex> files_lock(files_mutex_);
1955
+
1956
+ // Skip for metadata-only torrents
1957
+ if (download_path_.empty()) {
1958
+ return false;
1959
+ }
1960
+
1961
+ if (piece_index >= pieces_.size()) {
1962
+ LOG_BT_ERROR("Invalid piece index for disk read: " << piece_index);
1963
+ return false;
1964
+ }
1965
+
1966
+ // Check if piece is complete
1967
+ if (!piece_completed_[piece_index]) {
1968
+ LOG_BT_DEBUG("Piece " << piece_index << " is not complete, can't read from disk");
1969
+ return false;
1970
+ }
1971
+
1972
+ auto& piece = pieces_[piece_index];
1973
+
1974
+ // Allocate buffer for piece data
1975
+ data.resize(piece->length);
1976
+
1977
+ // Calculate piece offset in the torrent
1978
+ uint64_t piece_offset = static_cast<uint64_t>(piece_index) * torrent_info_.get_piece_length();
1979
+ uint64_t data_offset = 0;
1980
+
1981
+ // Read piece data from the appropriate files
1982
+ for (const auto& file_info : torrent_info_.get_files()) {
1983
+ if (piece_offset >= file_info.offset + file_info.length) {
1984
+ continue; // This piece doesn't overlap with this file
1985
+ }
1986
+
1987
+ if (piece_offset + piece->length <= file_info.offset) {
1988
+ break; // No more files will be affected by this piece
1989
+ }
1990
+
1991
+ // Calculate overlap
1992
+ uint64_t file_start_in_piece = (file_info.offset > piece_offset) ?
1993
+ file_info.offset - piece_offset : 0;
1994
+ uint64_t piece_end = piece_offset + piece->length;
1995
+ uint64_t file_end = file_info.offset + file_info.length;
1996
+ uint64_t read_end = (std::min)(piece_end, file_end);
1997
+ uint64_t read_length = read_end - (piece_offset + file_start_in_piece);
1998
+
1999
+ if (read_length == 0) {
2000
+ continue;
2001
+ }
2002
+
2003
+ // Calculate file offset
2004
+ uint64_t file_offset = (piece_offset > file_info.offset) ?
2005
+ piece_offset - file_info.offset : 0;
2006
+
2007
+ // Read data chunk from file using fs module
2008
+ std::string file_path = download_path_ + "/" + file_info.path;
2009
+ void* read_buffer = data.data() + file_start_in_piece;
2010
+
2011
+ if (!read_file_chunk(file_path.c_str(), file_offset, read_buffer, read_length)) {
2012
+ LOG_BT_ERROR("Failed to read data from file: " << file_path);
2013
+ return false;
2014
+ }
2015
+
2016
+ LOG_BT_DEBUG("Read " << read_length << " bytes from file " << file_info.path
2017
+ << " at offset " << file_offset);
2018
+ }
2019
+
2020
+ return true;
2021
+ }
2022
+
2023
+ void TorrentDownload::reset_block_request(PieceIndex piece_index, uint32_t block_index) {
2024
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2025
+
2026
+ if (piece_index >= pieces_.size()) {
2027
+ return;
2028
+ }
2029
+
2030
+ auto& piece = pieces_[piece_index];
2031
+ if (block_index < piece->blocks_requested.size()) {
2032
+ // Only reset if not already downloaded
2033
+ if (!piece->blocks_downloaded[block_index]) {
2034
+ piece->blocks_requested[block_index] = false;
2035
+ }
2036
+ }
2037
+ }
2038
+
2039
+ std::vector<PieceIndex> TorrentDownload::get_available_pieces() const {
2040
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2041
+ std::vector<PieceIndex> available_pieces;
2042
+
2043
+ for (PieceIndex i = 0; i < piece_completed_.size(); ++i) {
2044
+ if (piece_completed_[i]) {
2045
+ available_pieces.push_back(i);
2046
+ }
2047
+ }
2048
+
2049
+ return available_pieces;
2050
+ }
2051
+
2052
+ std::vector<PieceIndex> TorrentDownload::get_needed_pieces(const std::vector<bool>& peer_bitfield) const {
2053
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2054
+ std::vector<PieceIndex> needed_pieces;
2055
+
2056
+ size_t min_size = (std::min)(peer_bitfield.size(), piece_completed_.size());
2057
+ for (size_t i = 0; i < min_size; ++i) {
2058
+ // Include pieces that are not complete (even if downloading - peer can help complete them)
2059
+ if (peer_bitfield[i] && !piece_completed_[i]) {
2060
+ needed_pieces.push_back(static_cast<PieceIndex>(i));
2061
+ }
2062
+ }
2063
+
2064
+ return needed_pieces;
2065
+ }
2066
+
2067
+ uint64_t TorrentDownload::get_downloaded_bytes() const {
2068
+ return total_downloaded_.load();
2069
+ }
2070
+
2071
+ uint64_t TorrentDownload::get_uploaded_bytes() const {
2072
+ return total_uploaded_.load();
2073
+ }
2074
+
2075
+ double TorrentDownload::get_progress_percentage() const {
2076
+ if (torrent_info_.get_total_length() == 0) {
2077
+ return 0.0;
2078
+ }
2079
+
2080
+ uint64_t downloaded = get_downloaded_bytes();
2081
+ return (static_cast<double>(downloaded) / torrent_info_.get_total_length()) * 100.0;
2082
+ }
2083
+
2084
+ uint32_t TorrentDownload::get_completed_pieces() const {
2085
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2086
+ return static_cast<uint32_t>(std::count(piece_completed_.begin(), piece_completed_.end(), true));
2087
+ }
2088
+
2089
+ std::vector<bool> TorrentDownload::get_piece_bitfield() const {
2090
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2091
+ return piece_completed_;
2092
+ }
2093
+
2094
+ void TorrentDownload::update_speed_stats() {
2095
+ std::lock_guard<std::mutex> lock(speed_mutex_);
2096
+
2097
+ auto now = std::chrono::steady_clock::now();
2098
+ double elapsed_seconds = std::chrono::duration<double>(now - last_speed_update_time_).count();
2099
+
2100
+ // Update speed every second at minimum to avoid division by very small numbers
2101
+ if (elapsed_seconds >= 1.0) {
2102
+ uint64_t current_downloaded = total_downloaded_.load();
2103
+ uint64_t current_uploaded = total_uploaded_.load();
2104
+
2105
+ uint64_t downloaded_delta = current_downloaded - last_speed_downloaded_;
2106
+ uint64_t uploaded_delta = current_uploaded - last_speed_uploaded_;
2107
+
2108
+ download_speed_.store(static_cast<double>(downloaded_delta) / elapsed_seconds);
2109
+ upload_speed_.store(static_cast<double>(uploaded_delta) / elapsed_seconds);
2110
+
2111
+ last_speed_downloaded_ = current_downloaded;
2112
+ last_speed_uploaded_ = current_uploaded;
2113
+ last_speed_update_time_ = now;
2114
+ }
2115
+
2116
+ // Also update speed stats for all peer connections
2117
+ {
2118
+ std::lock_guard<std::mutex> peers_lock(peers_mutex_);
2119
+ for (auto& peer : peer_connections_) {
2120
+ peer->update_speed_stats();
2121
+ }
2122
+ }
2123
+ }
2124
+
2125
+ double TorrentDownload::get_download_speed() const {
2126
+ return download_speed_.load();
2127
+ }
2128
+
2129
+ double TorrentDownload::get_upload_speed() const {
2130
+ return upload_speed_.load();
2131
+ }
2132
+
2133
+ void TorrentDownload::download_loop() {
2134
+ LOG_BT_INFO("Download loop started for torrent: " << torrent_info_.get_name());
2135
+
2136
+ // For metadata-only torrents, we don't download pieces
2137
+ // The loop just waits until stopped (metadata completion triggers stop from callback)
2138
+ bool is_metadata_only = torrent_info_.is_metadata_only();
2139
+
2140
+ while (running_ && (is_metadata_only || !is_complete())) {
2141
+ if (paused_) {
2142
+ // Use conditional variable for responsive shutdown
2143
+ {
2144
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
2145
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(100), [this] { return !running_.load() || !paused_.load(); })) {
2146
+ if (!running_) break;
2147
+ }
2148
+ }
2149
+ continue;
2150
+ }
2151
+
2152
+ // Schedule piece requests to peers (skipped for metadata-only)
2153
+ schedule_piece_requests();
2154
+
2155
+ // Update progress (skipped for metadata-only since no pieces)
2156
+ if (!is_metadata_only) {
2157
+ update_progress();
2158
+
2159
+ // Check for completion
2160
+ check_torrent_completion();
2161
+ }
2162
+
2163
+ // Use conditional variable for responsive shutdown
2164
+ {
2165
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
2166
+ if (shutdown_cv_.wait_for(lock, std::chrono::milliseconds(50), [this] { return !running_.load(); })) {
2167
+ break;
2168
+ }
2169
+ }
2170
+ }
2171
+
2172
+ LOG_BT_INFO("Download loop ended for torrent: " << torrent_info_.get_name());
2173
+ }
2174
+
2175
+ void TorrentDownload::peer_management_loop() {
2176
+ LOG_BT_INFO("Peer management loop started for torrent: " << torrent_info_.get_name());
2177
+
2178
+ bool is_metadata_only = torrent_info_.is_metadata_only();
2179
+
2180
+ while (running_) {
2181
+ // Clean up disconnected peers
2182
+ cleanup_disconnected_peers();
2183
+
2184
+ // Skip detailed state logging for metadata-only torrents
2185
+ // (they don't have meaningful piece progress to report)
2186
+ if (!is_metadata_only) {
2187
+ // Periodically log detailed torrent state (every 10 seconds when actively downloading)
2188
+ auto now = std::chrono::steady_clock::now();
2189
+ bool is_actively_downloading = !is_complete() && !paused_.load();
2190
+
2191
+ // Log every 10 seconds when downloading, every 30 seconds when seeding/idle
2192
+ auto log_interval = is_actively_downloading ? std::chrono::seconds(10) : std::chrono::seconds(30);
2193
+
2194
+ if (now - last_state_log_time_ > log_interval) {
2195
+ last_state_log_time_ = now;
2196
+
2197
+ // Only log detailed state if we have peers or are actively doing something
2198
+ size_t peer_count = get_peer_count();
2199
+ if (peer_count > 0 || is_actively_downloading) {
2200
+ log_detailed_state();
2201
+ }
2202
+ }
2203
+ }
2204
+
2205
+ // Use conditional variable for responsive shutdown
2206
+ {
2207
+ std::unique_lock<std::mutex> lock(shutdown_mutex_);
2208
+ if (shutdown_cv_.wait_for(lock, std::chrono::seconds(1), [this] { return !running_.load(); })) {
2209
+ break;
2210
+ }
2211
+ }
2212
+ }
2213
+
2214
+ LOG_BT_INFO("Peer management loop ended for torrent: " << torrent_info_.get_name());
2215
+ }
2216
+
2217
+ void TorrentDownload::schedule_piece_requests() {
2218
+ // Skip piece scheduling for metadata-only torrents
2219
+ // These torrents only need to exchange metadata via BEP 9, not download pieces
2220
+ if (torrent_info_.is_metadata_only()) {
2221
+ return;
2222
+ }
2223
+
2224
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2225
+
2226
+ for (auto& peer : peer_connections_) {
2227
+ if (!peer->is_connected()) {
2228
+ continue;
2229
+ }
2230
+
2231
+ // Get pieces we need that this peer has
2232
+ std::vector<PieceIndex> needed_pieces = get_needed_pieces(peer->get_bitfield());
2233
+
2234
+ // IMPORTANT: Express interest BEFORE checking choke state
2235
+ // We must send INTERESTED to get unchoked, even if we're currently choked
2236
+ if (!needed_pieces.empty() && !peer->is_interested()) {
2237
+ peer->set_interested(true);
2238
+ LOG_BT_DEBUG("Expressed interest to peer " << peer->get_peer_info().ip << ":" << peer->get_peer_info().port);
2239
+ }
2240
+
2241
+ // Skip actual piece requests if we're choked or have no needed pieces
2242
+ if (peer->is_choked() || needed_pieces.empty()) {
2243
+ continue;
2244
+ }
2245
+
2246
+ // Select pieces to download using our piece selection strategy
2247
+ std::vector<PieceIndex> selected_pieces = select_pieces_for_download();
2248
+
2249
+ // Request blocks from selected pieces
2250
+ for (PieceIndex piece_index : selected_pieces) {
2251
+ if (peer->get_pending_requests() >= MAX_REQUESTS_PER_PEER) {
2252
+ break; // Don't overwhelm this peer
2253
+ }
2254
+
2255
+ if (!peer->has_piece(piece_index)) {
2256
+ continue;
2257
+ }
2258
+
2259
+ // Find blocks we need for this piece
2260
+ {
2261
+ std::lock_guard<std::mutex> pieces_lock(pieces_mutex_);
2262
+ if (piece_index >= pieces_.size() || piece_completed_[piece_index]) {
2263
+ continue;
2264
+ }
2265
+
2266
+ auto& piece = pieces_[piece_index];
2267
+ uint32_t block_size = BLOCK_SIZE;
2268
+
2269
+ for (uint32_t block_index = 0; block_index < piece->get_num_blocks(); ++block_index) {
2270
+ if (piece->blocks_downloaded[block_index]) {
2271
+ continue; // Already have this block
2272
+ }
2273
+
2274
+ // Check if this block is already requested globally (from any peer)
2275
+ if (piece->blocks_requested[block_index]) {
2276
+ continue; // Already requested from another peer
2277
+ }
2278
+
2279
+ uint32_t offset = block_index * BLOCK_SIZE;
2280
+ uint32_t length = (std::min)(block_size, piece->length - offset);
2281
+
2282
+ if (peer->request_piece_block(piece_index, offset, length)) {
2283
+ piece->blocks_requested[block_index] = true; // Mark as globally requested
2284
+ piece_downloading_[piece_index] = true;
2285
+ LOG_BT_DEBUG("Requested block " << block_index << " of piece " << piece_index
2286
+ << " from peer " << peer->get_peer_info().ip);
2287
+ break; // Request one block at a time per piece per peer
2288
+ }
2289
+ }
2290
+ }
2291
+ }
2292
+ }
2293
+ }
2294
+
2295
+ void TorrentDownload::cleanup_disconnected_peers() {
2296
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2297
+
2298
+ peer_connections_.erase(
2299
+ std::remove_if(peer_connections_.begin(), peer_connections_.end(),
2300
+ [](const std::unique_ptr<PeerConnection>& peer) {
2301
+ return !peer->is_connected();
2302
+ }),
2303
+ peer_connections_.end());
2304
+ }
2305
+
2306
+ bool TorrentDownload::open_files() {
2307
+ std::lock_guard<std::mutex> lock(files_mutex_);
2308
+
2309
+ const auto& files = torrent_info_.get_files();
2310
+
2311
+ for (const auto& file_info : files) {
2312
+ std::string file_path = download_path_ + "/" + file_info.path;
2313
+
2314
+ // Pre-allocate file with correct size using fs module
2315
+ if (!create_file_with_size(file_path.c_str(), file_info.length)) {
2316
+ LOG_BT_ERROR("Failed to create file: " << file_path);
2317
+ return false;
2318
+ }
2319
+
2320
+ LOG_BT_DEBUG("Created file: " << file_path << " (size: " << file_info.length << ")");
2321
+ }
2322
+
2323
+ return true;
2324
+ }
2325
+
2326
+ void TorrentDownload::close_files() {
2327
+ std::lock_guard<std::mutex> lock(files_mutex_);
2328
+
2329
+ // With fs module, files are opened/closed per operation
2330
+ // No persistent file handles to close
2331
+ LOG_BT_DEBUG("Files closed (using fs module, no persistent handles)");
2332
+ }
2333
+
2334
+ bool TorrentDownload::create_directory_structure() {
2335
+ const auto& files = torrent_info_.get_files();
2336
+
2337
+ for (const auto& file_info : files) {
2338
+ std::string file_path = download_path_ + "/" + file_info.path;
2339
+
2340
+ // Extract directory path
2341
+ size_t last_slash = file_path.find_last_of('/');
2342
+ if (last_slash != std::string::npos) {
2343
+ std::string dir_path = file_path.substr(0, last_slash);
2344
+
2345
+ // Create directory structure
2346
+ if (!create_directories(dir_path.c_str())) {
2347
+ LOG_BT_ERROR("Failed to create directory: " << dir_path);
2348
+ return false;
2349
+ }
2350
+ }
2351
+ }
2352
+
2353
+ return true;
2354
+ }
2355
+
2356
+ std::vector<PieceIndex> TorrentDownload::select_pieces_for_download() {
2357
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2358
+
2359
+ std::vector<PieceIndex> downloading_pieces;
2360
+ std::vector<PieceIndex> new_pieces;
2361
+
2362
+ // Find pieces we need - prioritize pieces already being downloaded
2363
+ for (PieceIndex i = 0; i < piece_completed_.size(); ++i) {
2364
+ if (!piece_completed_[i]) {
2365
+ if (piece_downloading_[i]) {
2366
+ // Prioritize completing pieces we've already started
2367
+ downloading_pieces.push_back(i);
2368
+ } else {
2369
+ new_pieces.push_back(i);
2370
+ }
2371
+ }
2372
+ }
2373
+
2374
+ // Implement piece selection strategy: complete existing pieces first, then start new ones
2375
+ std::vector<PieceIndex> selected_pieces;
2376
+
2377
+ // First, add pieces we're already downloading (prioritize completing them)
2378
+ for (PieceIndex piece : downloading_pieces) {
2379
+ selected_pieces.push_back(piece);
2380
+ if (selected_pieces.size() >= 10) break;
2381
+ }
2382
+
2383
+ // Then add new pieces if we have room
2384
+ for (PieceIndex piece : new_pieces) {
2385
+ if (selected_pieces.size() >= 10) break;
2386
+ selected_pieces.push_back(piece);
2387
+ }
2388
+
2389
+ return selected_pieces;
2390
+ }
2391
+
2392
+ PieceIndex TorrentDownload::select_rarest_piece(const std::vector<bool>& available_pieces) {
2393
+ // Lock order: peers_mutex_ -> pieces_mutex_
2394
+ std::lock_guard<std::mutex> peers_lock(peers_mutex_);
2395
+ std::lock_guard<std::mutex> pieces_lock(pieces_mutex_);
2396
+
2397
+ // Count how many peers have each piece
2398
+ std::vector<int> piece_counts(piece_completed_.size(), 0);
2399
+
2400
+ for (const auto& peer : peer_connections_) {
2401
+ if (!peer->is_connected()) {
2402
+ continue;
2403
+ }
2404
+
2405
+ const auto& peer_bitfield = peer->get_bitfield();
2406
+ for (size_t i = 0; i < peer_bitfield.size() && i < piece_counts.size(); ++i) {
2407
+ if (peer_bitfield[i]) {
2408
+ piece_counts[i]++;
2409
+ }
2410
+ }
2411
+ }
2412
+
2413
+ // Find the rarest piece we need and is available
2414
+ int min_count = INT_MAX;
2415
+ PieceIndex rarest_piece = static_cast<PieceIndex>(-1);
2416
+
2417
+ for (size_t i = 0; i < available_pieces.size() && i < piece_completed_.size(); ++i) {
2418
+ if (available_pieces[i] && !piece_completed_[i] && !piece_downloading_[i]) {
2419
+ if (piece_counts[i] < min_count) {
2420
+ min_count = piece_counts[i];
2421
+ rarest_piece = static_cast<PieceIndex>(i);
2422
+ }
2423
+ }
2424
+ }
2425
+
2426
+ return rarest_piece;
2427
+ }
2428
+
2429
+ // Helper function to format bytes per second as human-readable string
2430
+ static std::string format_speed(double bytes_per_second) {
2431
+ std::ostringstream oss;
2432
+ oss << std::fixed << std::setprecision(1);
2433
+
2434
+ if (bytes_per_second >= 1024 * 1024) {
2435
+ oss << (bytes_per_second / (1024 * 1024)) << " MB/s";
2436
+ } else if (bytes_per_second >= 1024) {
2437
+ oss << (bytes_per_second / 1024) << " KB/s";
2438
+ } else {
2439
+ oss << bytes_per_second << " B/s";
2440
+ }
2441
+
2442
+ return oss.str();
2443
+ }
2444
+
2445
+ // Helper function to format bytes as human-readable string
2446
+ static std::string format_bytes(uint64_t bytes) {
2447
+ std::ostringstream oss;
2448
+ oss << std::fixed << std::setprecision(2);
2449
+
2450
+ if (bytes >= 1024ULL * 1024 * 1024) {
2451
+ oss << (static_cast<double>(bytes) / (1024 * 1024 * 1024)) << " GB";
2452
+ } else if (bytes >= 1024 * 1024) {
2453
+ oss << (static_cast<double>(bytes) / (1024 * 1024)) << " MB";
2454
+ } else if (bytes >= 1024) {
2455
+ oss << (static_cast<double>(bytes) / 1024) << " KB";
2456
+ } else {
2457
+ oss << bytes << " B";
2458
+ }
2459
+
2460
+ return oss.str();
2461
+ }
2462
+
2463
+ void TorrentDownload::update_progress() {
2464
+ // Update speed statistics
2465
+ update_speed_stats();
2466
+
2467
+ uint64_t downloaded = get_downloaded_bytes();
2468
+ uint64_t total = torrent_info_.get_total_length();
2469
+ double percentage = get_progress_percentage();
2470
+
2471
+ // Call progress callback
2472
+ if (progress_callback_) {
2473
+ progress_callback_(downloaded, total, percentage);
2474
+ }
2475
+
2476
+ // Only log progress if it changed by at least 1% or we went from 0 to non-zero
2477
+ double progress_delta = percentage - last_logged_progress_;
2478
+ bool should_log_progress = (progress_delta >= 1.0) ||
2479
+ (last_logged_progress_ == 0.0 && percentage > 0.0);
2480
+
2481
+ if (should_log_progress) {
2482
+ double dl_speed = get_download_speed();
2483
+ double ul_speed = get_upload_speed();
2484
+
2485
+ LOG_BT_INFO("Progress [" << torrent_info_.get_name() << "]: "
2486
+ << std::fixed << std::setprecision(1) << percentage << "% "
2487
+ << "(" << format_bytes(downloaded) << " / " << format_bytes(total) << ") "
2488
+ << "DL: " << format_speed(dl_speed) << " "
2489
+ << "UL: " << format_speed(ul_speed));
2490
+
2491
+ last_logged_progress_ = percentage;
2492
+ }
2493
+ }
2494
+
2495
+ void TorrentDownload::on_piece_completed(PieceIndex piece_index) {
2496
+ if (piece_complete_callback_) {
2497
+ piece_complete_callback_(piece_index);
2498
+ }
2499
+
2500
+ // Notify all peers that we have this piece (for seeding)
2501
+ notify_peers_have_piece(piece_index);
2502
+
2503
+ double dl_speed = get_download_speed();
2504
+ LOG_BT_INFO("Piece " << piece_index << " completed. Progress: "
2505
+ << std::fixed << std::setprecision(1) << get_progress_percentage() << "% ("
2506
+ << get_completed_pieces() << "/" << torrent_info_.get_num_pieces() << " pieces) "
2507
+ << "@ " << format_speed(dl_speed));
2508
+ }
2509
+
2510
+ void TorrentDownload::notify_peers_have_piece(PieceIndex piece_index) {
2511
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2512
+
2513
+ // Send HAVE message to all connected peers
2514
+ auto have_msg = PeerMessage::create_have(piece_index);
2515
+
2516
+ for (auto& peer : peer_connections_) {
2517
+ if (peer->is_connected()) {
2518
+ peer->send_message(have_msg);
2519
+ }
2520
+ }
2521
+
2522
+ LOG_BT_DEBUG("Notified " << peer_connections_.size() << " peers that we have piece " << piece_index);
2523
+ }
2524
+
2525
+ void TorrentDownload::check_torrent_completion() {
2526
+ if (is_complete() && torrent_complete_callback_) {
2527
+ torrent_complete_callback_(torrent_info_.get_name());
2528
+ LOG_BT_INFO("Torrent download completed: " << torrent_info_.get_name());
2529
+ }
2530
+ }
2531
+
2532
+ void TorrentDownload::log_detailed_state() {
2533
+ // Get current stats - be careful with lock ordering (peers_mutex_ -> pieces_mutex_)
2534
+ uint32_t completed_pieces = 0;
2535
+ uint32_t downloading_pieces = 0;
2536
+ uint32_t total_pieces = 0;
2537
+ std::vector<PieceIndex> currently_downloading;
2538
+
2539
+ {
2540
+ std::lock_guard<std::mutex> lock(pieces_mutex_);
2541
+ total_pieces = static_cast<uint32_t>(piece_completed_.size());
2542
+ completed_pieces = static_cast<uint32_t>(std::count(piece_completed_.begin(), piece_completed_.end(), true));
2543
+
2544
+ for (size_t i = 0; i < piece_downloading_.size(); ++i) {
2545
+ if (piece_downloading_[i] && !piece_completed_[i]) {
2546
+ downloading_pieces++;
2547
+ if (currently_downloading.size() < 10) { // Limit to first 10
2548
+ currently_downloading.push_back(static_cast<PieceIndex>(i));
2549
+ }
2550
+ }
2551
+ }
2552
+ }
2553
+
2554
+ // Get peer information
2555
+ size_t total_peers = 0;
2556
+ size_t connected_peers = 0;
2557
+ size_t unchoked_peers = 0;
2558
+ size_t interested_peers = 0;
2559
+ std::vector<std::tuple<std::string, double, double, size_t, bool>> peer_stats; // ip:port, dl_speed, ul_speed, pending_requests, choked
2560
+
2561
+ {
2562
+ std::lock_guard<std::mutex> lock(peers_mutex_);
2563
+ total_peers = peer_connections_.size();
2564
+
2565
+ for (const auto& peer : peer_connections_) {
2566
+ if (peer->is_connected()) {
2567
+ connected_peers++;
2568
+ if (!peer->is_choked()) {
2569
+ unchoked_peers++;
2570
+ }
2571
+ if (peer->peer_is_interested()) {
2572
+ interested_peers++;
2573
+ }
2574
+
2575
+ std::string peer_addr = peer->get_peer_info().ip + ":" + std::to_string(peer->get_peer_info().port);
2576
+ peer_stats.emplace_back(
2577
+ peer_addr,
2578
+ peer->get_download_speed(),
2579
+ peer->get_upload_speed(),
2580
+ peer->get_pending_requests(),
2581
+ peer->is_choked()
2582
+ );
2583
+ }
2584
+ }
2585
+ }
2586
+
2587
+ // Calculate overall speeds
2588
+ double dl_speed = get_download_speed();
2589
+ double ul_speed = get_upload_speed();
2590
+ uint64_t downloaded = get_downloaded_bytes();
2591
+ uint64_t uploaded = get_uploaded_bytes();
2592
+ uint64_t total_size = torrent_info_.get_total_length();
2593
+ double progress = get_progress_percentage();
2594
+
2595
+ // Calculate ETA
2596
+ std::string eta_str = "N/A";
2597
+ if (dl_speed > 0 && !is_complete()) {
2598
+ uint64_t remaining = total_size - downloaded;
2599
+ double eta_seconds = static_cast<double>(remaining) / dl_speed;
2600
+
2601
+ if (eta_seconds < 60) {
2602
+ std::ostringstream oss;
2603
+ oss << std::fixed << std::setprecision(0) << eta_seconds << "s";
2604
+ eta_str = oss.str();
2605
+ } else if (eta_seconds < 3600) {
2606
+ int minutes = static_cast<int>(eta_seconds / 60);
2607
+ int seconds = static_cast<int>(eta_seconds) % 60;
2608
+ std::ostringstream oss;
2609
+ oss << minutes << "m " << seconds << "s";
2610
+ eta_str = oss.str();
2611
+ } else {
2612
+ int hours = static_cast<int>(eta_seconds / 3600);
2613
+ int minutes = static_cast<int>((static_cast<int>(eta_seconds) % 3600) / 60);
2614
+ std::ostringstream oss;
2615
+ oss << hours << "h " << minutes << "m";
2616
+ eta_str = oss.str();
2617
+ }
2618
+ } else if (is_complete()) {
2619
+ eta_str = "Complete";
2620
+ }
2621
+
2622
+ // Log detailed state
2623
+ LOG_BT_INFO("=== Torrent State: " << torrent_info_.get_name() << " ===");
2624
+ LOG_BT_INFO(" Progress: " << std::fixed << std::setprecision(1) << progress << "% "
2625
+ << "(" << format_bytes(downloaded) << " / " << format_bytes(total_size) << ")");
2626
+ LOG_BT_INFO(" Pieces: " << completed_pieces << "/" << total_pieces << " complete, "
2627
+ << downloading_pieces << " downloading");
2628
+ LOG_BT_INFO(" Speed: DL " << format_speed(dl_speed) << " | UL " << format_speed(ul_speed));
2629
+ LOG_BT_INFO(" Uploaded: " << format_bytes(uploaded) << " | ETA: " << eta_str);
2630
+ LOG_BT_INFO(" Peers: " << connected_peers << "/" << total_peers << " connected, "
2631
+ << unchoked_peers << " unchoked, " << interested_peers << " interested in us");
2632
+
2633
+ // Log pieces being downloaded (if any)
2634
+ if (!currently_downloading.empty()) {
2635
+ std::ostringstream pieces_oss;
2636
+ pieces_oss << " Downloading pieces: [";
2637
+ for (size_t i = 0; i < currently_downloading.size(); ++i) {
2638
+ if (i > 0) pieces_oss << ", ";
2639
+ pieces_oss << currently_downloading[i];
2640
+ }
2641
+ if (downloading_pieces > currently_downloading.size()) {
2642
+ pieces_oss << ", ... +" << (downloading_pieces - currently_downloading.size()) << " more";
2643
+ }
2644
+ pieces_oss << "]";
2645
+ LOG_BT_INFO(pieces_oss.str());
2646
+ }
2647
+
2648
+ // Log per-peer stats if we have peers
2649
+ if (!peer_stats.empty()) {
2650
+ LOG_BT_INFO(" Per-peer stats:");
2651
+ for (const auto& [addr, peer_dl, peer_ul, pending, choked] : peer_stats) {
2652
+ LOG_BT_INFO(" " << addr << ": DL " << format_speed(peer_dl)
2653
+ << " | UL " << format_speed(peer_ul)
2654
+ << " | Pending: " << pending
2655
+ << " | " << (choked ? "CHOKED" : "unchoked"));
2656
+ }
2657
+ }
2658
+
2659
+ LOG_BT_INFO("=== End Torrent State ===");
2660
+ }
2661
+
2662
+ void TorrentDownload::announce_to_dht(DhtClient* dht_client) {
2663
+ if (!dht_client || !dht_client->is_running()) {
2664
+ LOG_BT_WARN("DHT client not available for torrent announcement");
2665
+ return;
2666
+ }
2667
+
2668
+ // Convert info hash to DHT format
2669
+ InfoHash dht_info_hash;
2670
+ const auto& torrent_hash = torrent_info_.get_info_hash();
2671
+ std::copy(torrent_hash.begin(), torrent_hash.end(), dht_info_hash.begin());
2672
+
2673
+ // Announce with default BitTorrent port (we don't have a BitTorrent listen port yet)
2674
+ uint16_t announce_port = 6881; // Default BitTorrent port
2675
+
2676
+ if (dht_client->announce_peer(dht_info_hash, announce_port)) {
2677
+ LOG_BT_INFO("Announced torrent to DHT: " << torrent_info_.get_name());
2678
+ } else {
2679
+ LOG_BT_WARN("Failed to announce torrent to DHT: " << torrent_info_.get_name());
2680
+ }
2681
+ }
2682
+
2683
+ void TorrentDownload::request_peers_from_dht(DhtClient* dht_client) {
2684
+ if (!dht_client || !dht_client->is_running()) {
2685
+ LOG_BT_WARN("DHT client not available for peer discovery");
2686
+ return;
2687
+ }
2688
+
2689
+ // Convert info hash to DHT format
2690
+ InfoHash dht_info_hash;
2691
+ const auto& torrent_hash = torrent_info_.get_info_hash();
2692
+ std::copy(torrent_hash.begin(), torrent_hash.end(), dht_info_hash.begin());
2693
+
2694
+ LOG_BT_INFO("Requesting peers from DHT for torrent: " << torrent_info_.get_name());
2695
+
2696
+ dht_client->find_peers(dht_info_hash, [this](const std::vector<Peer>& peers, const InfoHash& info_hash) {
2697
+ // Check if torrent is still running
2698
+ if (!running_.load()) {
2699
+ LOG_BT_DEBUG("Torrent stopped, ignoring DHT peer discovery results");
2700
+ return;
2701
+ }
2702
+
2703
+ LOG_BT_INFO("DHT discovered " << peers.size() << " peers for torrent: " << torrent_info_.get_name());
2704
+
2705
+ if (peers.empty()) {
2706
+ LOG_BT_WARN("No peers found via DHT for torrent: " << torrent_info_.get_name());
2707
+ return;
2708
+ }
2709
+
2710
+ size_t peers_added = 0;
2711
+ for (const auto& peer : peers) {
2712
+ if (get_peer_count() >= MAX_PEERS_PER_TORRENT) {
2713
+ LOG_BT_DEBUG("Peer limit reached, stopping peer additions");
2714
+ break;
2715
+ }
2716
+
2717
+ if (add_peer(peer)) {
2718
+ peers_added++;
2719
+ }
2720
+ }
2721
+
2722
+ LOG_BT_INFO("Added " << peers_added << " of " << peers.size() << " DHT peers for torrent: " << torrent_info_.get_name());
2723
+ });
2724
+ }
2725
+
2726
+ void TorrentDownload::announce_to_trackers() {
2727
+ if (!tracker_manager_) {
2728
+ LOG_BT_WARN("No tracker manager available");
2729
+ return;
2730
+ }
2731
+
2732
+ LOG_BT_INFO("Announcing to trackers for torrent: " << torrent_info_.get_name());
2733
+
2734
+ // Prepare tracker request
2735
+ TrackerRequest request;
2736
+ request.info_hash = torrent_info_.get_info_hash();
2737
+ request.peer_id = our_peer_id_;
2738
+ request.port = 6881; // Default BitTorrent port (should come from BitTorrentClient)
2739
+ request.uploaded = total_uploaded_.load();
2740
+ request.downloaded = total_downloaded_.load();
2741
+ request.left = torrent_info_.get_total_length() - total_downloaded_.load();
2742
+
2743
+ // Determine event
2744
+ if (total_downloaded_.load() == 0 && !running_) {
2745
+ request.event = TrackerEvent::STARTED;
2746
+ } else if (is_complete()) {
2747
+ request.event = TrackerEvent::COMPLETED;
2748
+ } else {
2749
+ request.event = TrackerEvent::NONE;
2750
+ }
2751
+
2752
+ // Announce to all trackers
2753
+ tracker_manager_->announce(request, [this](const TrackerResponse& response, const std::string& tracker_url) {
2754
+ if (!response.success) {
2755
+ LOG_BT_WARN("Tracker announce failed (" << tracker_url << "): " << response.failure_reason);
2756
+ return;
2757
+ }
2758
+
2759
+ LOG_BT_INFO("Tracker announce successful (" << tracker_url << ")");
2760
+ LOG_BT_INFO(" Peers: " << response.peers.size()
2761
+ << ", Seeders: " << response.complete
2762
+ << ", Leechers: " << response.incomplete);
2763
+
2764
+ // Add discovered peers
2765
+ for (const auto& peer : response.peers) {
2766
+ if (get_peer_count() >= MAX_PEERS_PER_TORRENT) {
2767
+ break;
2768
+ }
2769
+
2770
+ add_peer(peer);
2771
+ }
2772
+ });
2773
+ }
2774
+
2775
+ void TorrentDownload::request_peers_from_trackers() {
2776
+ if (!tracker_manager_) {
2777
+ LOG_BT_WARN("No tracker manager available");
2778
+ return;
2779
+ }
2780
+
2781
+ // Same as announce - trackers provide peer lists in announce responses
2782
+ announce_to_trackers();
2783
+ }
2784
+
2785
+ void TorrentDownload::set_metadata_download(std::shared_ptr<MetadataDownload> metadata_download) {
2786
+ metadata_download_ = metadata_download;
2787
+
2788
+ // Set up the completion callback to forward to our metadata_complete_callback_
2789
+ if (metadata_download_) {
2790
+ metadata_download_->set_completion_callback([this](const TorrentInfo& torrent_info) {
2791
+ LOG_BT_INFO("Metadata download completed, notifying callback");
2792
+ if (metadata_complete_callback_) {
2793
+ metadata_complete_callback_(torrent_info);
2794
+ }
2795
+ });
2796
+ }
2797
+ }
2798
+
2799
+ //=============================================================================
2800
+ // BitTorrentClient Implementation
2801
+ //=============================================================================
2802
+
2803
+ BitTorrentClient::BitTorrentClient()
2804
+ : running_(false), listen_port_(0), listen_socket_(INVALID_SOCKET_VALUE),
2805
+ dht_client_(nullptr), max_connections_per_torrent_(MAX_PEERS_PER_TORRENT),
2806
+ download_rate_limit_(0), upload_rate_limit_(0) {
2807
+
2808
+ LOG_BT_INFO("BitTorrent client created");
2809
+ }
2810
+
2811
+ BitTorrentClient::~BitTorrentClient() {
2812
+ stop();
2813
+ }
2814
+
2815
+ bool BitTorrentClient::start(int listen_port) {
2816
+ if (running_.load()) {
2817
+ LOG_BT_WARN("BitTorrent client is already running");
2818
+ return false;
2819
+ }
2820
+
2821
+ listen_port_ = listen_port;
2822
+ if (listen_port_ == 0) {
2823
+ listen_port_ = 6881; // Default BitTorrent port
2824
+ }
2825
+
2826
+ LOG_BT_INFO("Starting BitTorrent client on port " << listen_port_);
2827
+
2828
+ // Initialize socket library (safe to call multiple times)
2829
+ if (!init_socket_library()) {
2830
+ LOG_BT_ERROR("Failed to initialize socket library");
2831
+ return false;
2832
+ }
2833
+
2834
+ // Create listen socket
2835
+ listen_socket_ = create_tcp_server(listen_port_);
2836
+ if (!is_valid_socket(listen_socket_)) {
2837
+ LOG_BT_ERROR("Failed to create BitTorrent listen socket on port " << listen_port_);
2838
+ return false;
2839
+ }
2840
+
2841
+ running_.store(true);
2842
+
2843
+ // Start incoming connections handler
2844
+ incoming_connections_thread_ = std::thread(&BitTorrentClient::handle_incoming_connections, this);
2845
+
2846
+ LOG_BT_INFO("BitTorrent client started successfully");
2847
+ return true;
2848
+ }
2849
+
2850
+ void BitTorrentClient::stop() {
2851
+ if (!running_.load()) {
2852
+ return;
2853
+ }
2854
+
2855
+ LOG_BT_INFO("Stopping BitTorrent client");
2856
+ running_.store(false);
2857
+
2858
+ // Stop all torrents
2859
+ {
2860
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
2861
+ for (auto& pair : torrents_) {
2862
+ pair.second->stop();
2863
+ }
2864
+ torrents_.clear();
2865
+ }
2866
+
2867
+ // Close listen socket
2868
+ if (is_valid_socket(listen_socket_)) {
2869
+ close_socket(listen_socket_);
2870
+ listen_socket_ = INVALID_SOCKET_VALUE;
2871
+ }
2872
+
2873
+ // Wait for threads
2874
+ if (incoming_connections_thread_.joinable()) {
2875
+ incoming_connections_thread_.join();
2876
+ }
2877
+
2878
+ LOG_BT_INFO("BitTorrent client stopped");
2879
+ }
2880
+
2881
+ std::shared_ptr<TorrentDownload> BitTorrentClient::add_torrent(const std::string& torrent_file,
2882
+ const std::string& download_path) {
2883
+ TorrentInfo torrent_info;
2884
+ if (!torrent_info.load_from_file(torrent_file)) {
2885
+ LOG_BT_ERROR("Failed to load torrent file: " << torrent_file);
2886
+ return nullptr;
2887
+ }
2888
+
2889
+ return add_torrent(torrent_info, download_path);
2890
+ }
2891
+
2892
+ std::shared_ptr<TorrentDownload> BitTorrentClient::add_torrent(const TorrentInfo& torrent_info,
2893
+ const std::string& download_path) {
2894
+ if (!running_.load()) {
2895
+ LOG_BT_ERROR("BitTorrent client is not running");
2896
+ return nullptr;
2897
+ }
2898
+
2899
+ const InfoHash& info_hash = torrent_info.get_info_hash();
2900
+
2901
+ {
2902
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
2903
+
2904
+ // Check if torrent already exists
2905
+ if (torrents_.find(info_hash) != torrents_.end()) {
2906
+ LOG_BT_WARN("Torrent already exists: " << torrent_info.get_name());
2907
+ return torrents_[info_hash];
2908
+ }
2909
+
2910
+ // Create new torrent download
2911
+ auto torrent_download = std::make_shared<TorrentDownload>(torrent_info, download_path);
2912
+
2913
+ // Set up callbacks
2914
+ torrent_download->set_progress_callback([this, info_hash](uint64_t downloaded, uint64_t total, double percentage) {
2915
+ LOG_BT_DEBUG("Torrent progress: " << percentage << "% (" << downloaded << "/" << total << " bytes)");
2916
+ });
2917
+
2918
+ torrent_download->set_torrent_complete_callback([this, info_hash](const std::string& torrent_name) {
2919
+ LOG_BT_INFO("Torrent completed: " << torrent_name);
2920
+ if (torrent_completed_callback_) {
2921
+ torrent_completed_callback_(info_hash);
2922
+ }
2923
+ });
2924
+
2925
+ torrents_[info_hash] = torrent_download;
2926
+
2927
+ // Start the download
2928
+ if (!torrent_download->start()) {
2929
+ LOG_BT_ERROR("Failed to start torrent download: " << torrent_info.get_name());
2930
+ torrents_.erase(info_hash);
2931
+ return nullptr;
2932
+ }
2933
+
2934
+ // Announce to trackers
2935
+ torrent_download->announce_to_trackers();
2936
+
2937
+ // Announce to DHT if available
2938
+ if (dht_client_) {
2939
+ torrent_download->announce_to_dht(dht_client_);
2940
+ torrent_download->request_peers_from_dht(dht_client_);
2941
+ }
2942
+
2943
+ LOG_BT_INFO("Added torrent: " << torrent_info.get_name());
2944
+
2945
+ if (torrent_added_callback_) {
2946
+ torrent_added_callback_(info_hash);
2947
+ }
2948
+
2949
+ return torrent_download;
2950
+ }
2951
+ }
2952
+
2953
+ std::shared_ptr<TorrentDownload> BitTorrentClient::add_torrent_by_hash(const InfoHash& info_hash,
2954
+ const std::string& download_path) {
2955
+ if (!running_.load()) {
2956
+ LOG_BT_ERROR("BitTorrent client is not running");
2957
+ return nullptr;
2958
+ }
2959
+
2960
+ if (!dht_client_ || !dht_client_->is_running()) {
2961
+ LOG_BT_ERROR("DHT client is required for adding torrents by hash. DHT is not available.");
2962
+ return nullptr;
2963
+ }
2964
+
2965
+ LOG_BT_INFO("Adding torrent by hash: " << info_hash_to_hex(info_hash));
2966
+ LOG_BT_INFO("This will use DHT to find peers and download metadata via BEP 9...");
2967
+
2968
+ {
2969
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
2970
+
2971
+ // Check if torrent already exists
2972
+ if (torrents_.find(info_hash) != torrents_.end()) {
2973
+ LOG_BT_WARN("Torrent already exists with hash: " << info_hash_to_hex(info_hash));
2974
+ return torrents_[info_hash];
2975
+ }
2976
+ }
2977
+
2978
+ // Check if metadata download already in progress
2979
+ {
2980
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
2981
+ if (metadata_downloads_.find(info_hash) != metadata_downloads_.end()) {
2982
+ LOG_BT_WARN("Metadata download already in progress for hash: " << info_hash_to_hex(info_hash));
2983
+ return nullptr;
2984
+ }
2985
+
2986
+ // Store download path for later use
2987
+ metadata_download_paths_[info_hash] = download_path;
2988
+ }
2989
+
2990
+ // Create a minimal TorrentInfo for metadata exchange
2991
+ // We need a valid TorrentInfo to create peer connections, but we don't have the real metadata yet
2992
+ TorrentInfo metadata_torrent_info = TorrentInfo::create_for_metadata_exchange(info_hash);
2993
+
2994
+ // Create a temporary torrent download for metadata exchange only
2995
+ // Use empty download path since we don't download any pieces for metadata-only torrents
2996
+ auto metadata_torrent = std::make_shared<TorrentDownload>(metadata_torrent_info, "");
2997
+
2998
+ // Set up metadata completion callback
2999
+ // When metadata download completes, we'll stop the temporary torrent and create the real one
3000
+ // IMPORTANT: This callback is invoked from within a PeerConnection's thread, so we must NOT
3001
+ // call stop() directly (which would try to join the calling thread, causing a deadlock).
3002
+ // Instead, we defer the cleanup to a detached thread.
3003
+ metadata_torrent->set_metadata_complete_callback([this, info_hash, download_path, metadata_torrent](const TorrentInfo& torrent_info) {
3004
+ LOG_BT_INFO("Metadata download completed for " << torrent_info.get_name());
3005
+
3006
+ // Capture torrent_info by value since we're deferring to another thread
3007
+ TorrentInfo captured_info = torrent_info;
3008
+
3009
+ // Defer stop() and cleanup to a separate thread to avoid deadlock
3010
+ // (callback is called from PeerConnection::connection_thread_ which would try to join itself)
3011
+ std::thread([this, info_hash, download_path, metadata_torrent, captured_info]() {
3012
+ // Stop the temporary metadata torrent
3013
+ metadata_torrent->stop();
3014
+
3015
+ // Complete the metadata download and create the real torrent
3016
+ complete_metadata_download(info_hash, captured_info, download_path);
3017
+ }).detach();
3018
+ });
3019
+
3020
+ // Note: We don't create MetadataDownload here yet
3021
+ // It will be created dynamically when we get the metadata size from a peer's extended handshake
3022
+ // This happens in handle_extended_handshake when peer sends metadata_size
3023
+ // When set_metadata_download() is called, it will automatically set up the completion callback chain
3024
+
3025
+ // Start the metadata torrent (just for peer connections)
3026
+ if (!metadata_torrent->start()) {
3027
+ LOG_BT_ERROR("Failed to start metadata download torrent");
3028
+ return nullptr;
3029
+ }
3030
+
3031
+ // Use DHT to find peers for this info hash
3032
+ LOG_BT_INFO("Discovering peers via DHT for hash: " << info_hash_to_hex(info_hash));
3033
+
3034
+ // Find peers via DHT
3035
+ dht_client_->find_peers(info_hash, [this, info_hash, metadata_torrent](const std::vector<Peer>& peers, const InfoHash& hash) {
3036
+ // Check if metadata torrent is still running (may have completed or been stopped)
3037
+ if (!metadata_torrent->is_running()) {
3038
+ LOG_BT_DEBUG("Metadata torrent already stopped, ignoring DHT peer discovery results");
3039
+ return;
3040
+ }
3041
+
3042
+ // Check if metadata is already downloaded
3043
+ if (auto* md = metadata_torrent->get_metadata_download()) {
3044
+ if (md->is_complete()) {
3045
+ LOG_BT_DEBUG("Metadata already downloaded, ignoring DHT peer discovery results");
3046
+ return;
3047
+ }
3048
+ }
3049
+
3050
+ LOG_BT_INFO("DHT found " << peers.size() << " peers for hash " << info_hash_to_hex(info_hash));
3051
+
3052
+ if (peers.empty()) {
3053
+ LOG_BT_WARN("No peers found via DHT for hash " << info_hash_to_hex(info_hash));
3054
+ return;
3055
+ }
3056
+
3057
+ // Connect to peers to get metadata
3058
+ size_t max_peers_to_try = (std::min)(peers.size(), static_cast<size_t>(10));
3059
+ size_t peers_added = 0;
3060
+ for (size_t i = 0; i < max_peers_to_try; ++i) {
3061
+ // Check again if metadata is complete (could have completed during loop)
3062
+ if (auto* md = metadata_torrent->get_metadata_download()) {
3063
+ if (md->is_complete()) {
3064
+ LOG_BT_DEBUG("Metadata download completed, stopping peer additions");
3065
+ break;
3066
+ }
3067
+ }
3068
+
3069
+ // Check if torrent is still running
3070
+ if (!metadata_torrent->is_running()) {
3071
+ LOG_BT_DEBUG("Metadata torrent stopped, stopping peer additions");
3072
+ break;
3073
+ }
3074
+
3075
+ const auto& peer = peers[i];
3076
+ LOG_BT_INFO("Connecting to peer " << peer.ip << ":" << peer.port << " for metadata exchange");
3077
+
3078
+ // Add peer to the metadata torrent
3079
+ // The peer connection will automatically:
3080
+ // 1. Perform BitTorrent handshake with the info hash
3081
+ // 2. Exchange extended handshake
3082
+ // 3. Detect metadata support and size
3083
+ // 4. Request metadata pieces
3084
+ // 5. Complete metadata download via callback
3085
+ if (metadata_torrent->add_peer(peer)) {
3086
+ peers_added++;
3087
+ } else {
3088
+ LOG_BT_DEBUG("Failed to add peer " << peer.ip << ":" << peer.port);
3089
+ }
3090
+ }
3091
+
3092
+ LOG_BT_INFO("Added " << peers_added << " peers for metadata exchange");
3093
+ });
3094
+
3095
+ LOG_BT_INFO("Metadata download initiated for hash " << info_hash_to_hex(info_hash));
3096
+ LOG_BT_INFO("Waiting for DHT peer discovery and metadata exchange...");
3097
+ LOG_BT_INFO("Note: Metadata download will complete asynchronously via BEP 9");
3098
+
3099
+ return nullptr; // Will return the real torrent via completion callback
3100
+ }
3101
+
3102
+ std::shared_ptr<TorrentDownload> BitTorrentClient::add_torrent_by_hash(const std::string& info_hash_hex,
3103
+ const std::string& download_path) {
3104
+ InfoHash info_hash = hex_to_info_hash(info_hash_hex);
3105
+
3106
+ // Validate the parsed hash
3107
+ bool is_zero = true;
3108
+ for (const auto& byte : info_hash) {
3109
+ if (byte != 0) {
3110
+ is_zero = false;
3111
+ break;
3112
+ }
3113
+ }
3114
+
3115
+ if (is_zero && info_hash_hex.length() == 40) {
3116
+ LOG_BT_ERROR("Invalid info hash format: " << info_hash_hex);
3117
+ return nullptr;
3118
+ }
3119
+
3120
+ return add_torrent_by_hash(info_hash, download_path);
3121
+ }
3122
+
3123
+ bool BitTorrentClient::remove_torrent(const InfoHash& info_hash) {
3124
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3125
+
3126
+ auto it = torrents_.find(info_hash);
3127
+ if (it == torrents_.end()) {
3128
+ return false;
3129
+ }
3130
+
3131
+ // Stop the torrent
3132
+ it->second->stop();
3133
+ torrents_.erase(it);
3134
+
3135
+ if (torrent_removed_callback_) {
3136
+ torrent_removed_callback_(info_hash);
3137
+ }
3138
+
3139
+ LOG_BT_INFO("Removed torrent with info hash: " << info_hash_to_hex(info_hash));
3140
+ return true;
3141
+ }
3142
+
3143
+ std::shared_ptr<TorrentDownload> BitTorrentClient::get_torrent(const InfoHash& info_hash) {
3144
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3145
+
3146
+ auto it = torrents_.find(info_hash);
3147
+ return (it != torrents_.end()) ? it->second : nullptr;
3148
+ }
3149
+
3150
+ std::vector<std::shared_ptr<TorrentDownload>> BitTorrentClient::get_all_torrents() {
3151
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3152
+
3153
+ std::vector<std::shared_ptr<TorrentDownload>> result;
3154
+ result.reserve(torrents_.size());
3155
+
3156
+ for (const auto& pair : torrents_) {
3157
+ result.push_back(pair.second);
3158
+ }
3159
+
3160
+ return result;
3161
+ }
3162
+
3163
+ void BitTorrentClient::discover_peers_for_torrent(const InfoHash& info_hash) {
3164
+ if (!dht_client_ || !dht_client_->is_running()) {
3165
+ LOG_BT_WARN("DHT client not available for peer discovery");
3166
+ return;
3167
+ }
3168
+
3169
+ auto torrent = get_torrent(info_hash);
3170
+ if (!torrent) {
3171
+ LOG_BT_WARN("Torrent not found for peer discovery");
3172
+ return;
3173
+ }
3174
+
3175
+ torrent->request_peers_from_dht(dht_client_);
3176
+ }
3177
+
3178
+ void BitTorrentClient::announce_torrent_to_dht(const InfoHash& info_hash) {
3179
+ if (!dht_client_ || !dht_client_->is_running()) {
3180
+ LOG_BT_WARN("DHT client not available for torrent announcement");
3181
+ return;
3182
+ }
3183
+
3184
+ auto torrent = get_torrent(info_hash);
3185
+ if (!torrent) {
3186
+ LOG_BT_WARN("Torrent not found for DHT announcement");
3187
+ return;
3188
+ }
3189
+
3190
+ torrent->announce_to_dht(dht_client_);
3191
+ }
3192
+
3193
+ size_t BitTorrentClient::get_active_torrents_count() const {
3194
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3195
+ return torrents_.size();
3196
+ }
3197
+
3198
+ uint64_t BitTorrentClient::get_total_downloaded() const {
3199
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3200
+
3201
+ uint64_t total = 0;
3202
+ for (const auto& pair : torrents_) {
3203
+ total += pair.second->get_downloaded_bytes();
3204
+ }
3205
+
3206
+ return total;
3207
+ }
3208
+
3209
+ uint64_t BitTorrentClient::get_total_uploaded() const {
3210
+ std::lock_guard<std::mutex> lock(torrents_mutex_);
3211
+
3212
+ uint64_t total = 0;
3213
+ for (const auto& pair : torrents_) {
3214
+ total += pair.second->get_uploaded_bytes();
3215
+ }
3216
+
3217
+ return total;
3218
+ }
3219
+
3220
+ void BitTorrentClient::handle_incoming_connections() {
3221
+ LOG_BT_INFO("BitTorrent incoming connections handler started");
3222
+
3223
+ while (running_.load()) {
3224
+ socket_t client_socket = accept_client(listen_socket_);
3225
+ if (!is_valid_socket(client_socket)) {
3226
+ if (running_.load()) {
3227
+ LOG_BT_ERROR("Failed to accept BitTorrent client connection");
3228
+ }
3229
+ continue;
3230
+ }
3231
+
3232
+ // Handle the connection in a separate thread
3233
+ std::thread([this, client_socket]() {
3234
+ handle_incoming_connection(client_socket);
3235
+ }).detach();
3236
+ }
3237
+
3238
+ LOG_BT_INFO("BitTorrent incoming connections handler stopped");
3239
+ }
3240
+
3241
+ void BitTorrentClient::handle_incoming_connection(socket_t client_socket) {
3242
+ LOG_BT_DEBUG("Handling incoming BitTorrent connection");
3243
+
3244
+ InfoHash info_hash;
3245
+ PeerID peer_id;
3246
+
3247
+ if (!perform_incoming_handshake(client_socket, info_hash, peer_id)) {
3248
+ LOG_BT_WARN("Failed to perform incoming handshake");
3249
+ close_socket(client_socket);
3250
+ return;
3251
+ }
3252
+
3253
+ // Find the torrent for this info hash
3254
+ auto torrent = get_torrent(info_hash);
3255
+ if (!torrent) {
3256
+ LOG_BT_WARN("Received connection for unknown torrent");
3257
+ close_socket(client_socket);
3258
+ return;
3259
+ }
3260
+
3261
+ // Get peer address
3262
+ std::string peer_address = get_peer_address(client_socket);
3263
+
3264
+ // Parse peer address to get IP and port
3265
+ std::string ip;
3266
+ int port = 0;
3267
+ size_t colon_pos = peer_address.find_last_of(':');
3268
+ if (colon_pos != std::string::npos) {
3269
+ ip = peer_address.substr(0, colon_pos);
3270
+ try {
3271
+ port = std::stoi(peer_address.substr(colon_pos + 1));
3272
+ } catch (const std::exception&) {
3273
+ port = 0;
3274
+ }
3275
+ }
3276
+
3277
+ if (ip.empty() || port == 0) {
3278
+ LOG_BT_WARN("Failed to parse peer address: " << peer_address);
3279
+ close_socket(client_socket);
3280
+ return;
3281
+ }
3282
+
3283
+ // Create peer object and add to torrent with the existing socket
3284
+ Peer peer_info{ip, static_cast<uint16_t>(port)};
3285
+ if (torrent->add_peer(peer_info, client_socket)) {
3286
+ LOG_BT_INFO("Added incoming peer: " << peer_address);
3287
+ // Socket ownership transferred to PeerConnection
3288
+ } else {
3289
+ LOG_BT_WARN("Failed to add incoming peer: " << peer_address);
3290
+ // Socket already closed by add_peer in failure case
3291
+ }
3292
+ }
3293
+
3294
+ bool BitTorrentClient::perform_incoming_handshake(socket_t socket, InfoHash& info_hash, PeerID& peer_id) {
3295
+ // Receive handshake
3296
+ std::vector<uint8_t> handshake_data(68); // Fixed handshake size
3297
+ std::string received_data = receive_tcp_string(socket, 68);
3298
+
3299
+ if (received_data.length() != 68) {
3300
+ LOG_BT_ERROR("Invalid handshake size received: " << received_data.length());
3301
+ return false;
3302
+ }
3303
+
3304
+ std::copy(received_data.begin(), received_data.end(), handshake_data.begin());
3305
+
3306
+ if (!parse_handshake_message(handshake_data, info_hash, peer_id)) {
3307
+ LOG_BT_ERROR("Failed to parse incoming handshake");
3308
+ return false;
3309
+ }
3310
+
3311
+ // Send our handshake response
3312
+ PeerID our_peer_id = generate_peer_id();
3313
+ auto response_data = create_handshake_message(info_hash, our_peer_id);
3314
+
3315
+ std::string response_str(response_data.begin(), response_data.end());
3316
+ if (send_tcp_string(socket, response_str) <= 0) {
3317
+ LOG_BT_ERROR("Failed to send handshake response");
3318
+ return false;
3319
+ }
3320
+
3321
+ LOG_BT_DEBUG("Incoming handshake completed successfully");
3322
+ return true;
3323
+ }
3324
+
3325
+ std::shared_ptr<MetadataDownload> BitTorrentClient::get_metadata_download(const InfoHash& info_hash) {
3326
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3327
+
3328
+ auto it = metadata_downloads_.find(info_hash);
3329
+ if (it != metadata_downloads_.end()) {
3330
+ return it->second;
3331
+ }
3332
+
3333
+ return nullptr;
3334
+ }
3335
+
3336
+ void BitTorrentClient::register_metadata_download(const InfoHash& info_hash, std::shared_ptr<MetadataDownload> metadata_download) {
3337
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3338
+ metadata_downloads_[info_hash] = metadata_download;
3339
+ LOG_BT_INFO("Registered metadata download for info hash " << info_hash_to_hex(info_hash));
3340
+ }
3341
+
3342
+ void BitTorrentClient::complete_metadata_download(const InfoHash& info_hash, const TorrentInfo& torrent_info, const std::string& download_path) {
3343
+ LOG_BT_INFO("Completing metadata download for info hash " << info_hash_to_hex(info_hash));
3344
+
3345
+ // Remove from metadata downloads
3346
+ {
3347
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3348
+ metadata_downloads_.erase(info_hash);
3349
+ metadata_download_paths_.erase(info_hash);
3350
+ }
3351
+
3352
+ // Add as regular torrent
3353
+ add_torrent(torrent_info, download_path);
3354
+ }
3355
+
3356
+ void BitTorrentClient::get_torrent_metadata_by_hash(const InfoHash& info_hash, MetadataRetrievalCallback callback) {
3357
+ if (!running_.load()) {
3358
+ LOG_BT_ERROR("BitTorrent client is not running");
3359
+ if (callback) {
3360
+ callback(TorrentInfo(), false, "BitTorrent client is not running");
3361
+ }
3362
+ return;
3363
+ }
3364
+
3365
+ if (!dht_client_ || !dht_client_->is_running()) {
3366
+ LOG_BT_ERROR("DHT client is required for metadata retrieval. DHT is not available.");
3367
+ if (callback) {
3368
+ callback(TorrentInfo(), false, "DHT client is required but not available");
3369
+ }
3370
+ return;
3371
+ }
3372
+
3373
+ LOG_BT_INFO("Retrieving metadata for hash: " << info_hash_to_hex(info_hash));
3374
+ LOG_BT_INFO("This will use DHT to find peers and download metadata via BEP 9...");
3375
+
3376
+ // Check if metadata retrieval already in progress
3377
+ {
3378
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3379
+ if (metadata_only_torrents_.find(info_hash) != metadata_only_torrents_.end()) {
3380
+ LOG_BT_WARN("Metadata retrieval already in progress for hash: " << info_hash_to_hex(info_hash));
3381
+ if (callback) {
3382
+ callback(TorrentInfo(), false, "Metadata retrieval already in progress");
3383
+ }
3384
+ return;
3385
+ }
3386
+
3387
+ // Store the callback
3388
+ metadata_retrieval_callbacks_[info_hash] = callback;
3389
+ }
3390
+
3391
+ // Create a minimal TorrentInfo for metadata exchange
3392
+ TorrentInfo metadata_torrent_info = TorrentInfo::create_for_metadata_exchange(info_hash);
3393
+
3394
+ // Create a temporary torrent download for metadata exchange only
3395
+ auto metadata_torrent = std::make_shared<TorrentDownload>(metadata_torrent_info, ""); // No download path needed
3396
+
3397
+ // Set up metadata completion callback
3398
+ // IMPORTANT: This callback is invoked from within a PeerConnection's thread, so we must NOT
3399
+ // call stop() directly (which would try to join the calling thread, causing a deadlock).
3400
+ // Instead, we defer the cleanup to a detached thread.
3401
+ metadata_torrent->set_metadata_complete_callback([this, info_hash](const TorrentInfo& torrent_info) {
3402
+ LOG_BT_INFO("Metadata retrieval completed for " << torrent_info.get_name());
3403
+
3404
+ // Capture torrent_info by value since we're deferring to another thread
3405
+ TorrentInfo captured_info = torrent_info;
3406
+
3407
+ // Defer stop() and cleanup to a separate thread to avoid deadlock
3408
+ // (callback is called from PeerConnection::connection_thread_ which would try to join itself)
3409
+ std::thread([this, info_hash, captured_info]() {
3410
+ MetadataRetrievalCallback user_callback;
3411
+ std::shared_ptr<TorrentDownload> temp_torrent;
3412
+
3413
+ // Get callback and cleanup
3414
+ {
3415
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3416
+ auto it = metadata_retrieval_callbacks_.find(info_hash);
3417
+ if (it != metadata_retrieval_callbacks_.end()) {
3418
+ user_callback = it->second;
3419
+ metadata_retrieval_callbacks_.erase(it);
3420
+ }
3421
+
3422
+ // Get temporary torrent to stop it
3423
+ auto torrent_it = metadata_only_torrents_.find(info_hash);
3424
+ if (torrent_it != metadata_only_torrents_.end()) {
3425
+ temp_torrent = torrent_it->second;
3426
+ metadata_only_torrents_.erase(torrent_it);
3427
+ }
3428
+ }
3429
+
3430
+ // Stop the temporary torrent
3431
+ if (temp_torrent) {
3432
+ temp_torrent->stop();
3433
+ }
3434
+
3435
+ // Call user callback with success
3436
+ if (user_callback) {
3437
+ user_callback(captured_info, true, "");
3438
+ }
3439
+ }).detach();
3440
+ });
3441
+
3442
+ // Store the temporary torrent
3443
+ {
3444
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3445
+ metadata_only_torrents_[info_hash] = metadata_torrent;
3446
+ }
3447
+
3448
+ // Start the metadata torrent (just for peer connections)
3449
+ if (!metadata_torrent->start()) {
3450
+ LOG_BT_ERROR("Failed to start metadata retrieval torrent");
3451
+
3452
+ // Cleanup and call callback with error
3453
+ {
3454
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3455
+ metadata_only_torrents_.erase(info_hash);
3456
+ auto it = metadata_retrieval_callbacks_.find(info_hash);
3457
+ if (it != metadata_retrieval_callbacks_.end()) {
3458
+ if (it->second) {
3459
+ it->second(TorrentInfo(), false, "Failed to start metadata retrieval");
3460
+ }
3461
+ metadata_retrieval_callbacks_.erase(it);
3462
+ }
3463
+ }
3464
+ return;
3465
+ }
3466
+
3467
+ // Use DHT to find peers for this info hash
3468
+ LOG_BT_INFO("Discovering peers via DHT for hash: " << info_hash_to_hex(info_hash));
3469
+
3470
+ // Find peers via DHT
3471
+ dht_client_->find_peers(info_hash, [this, info_hash, metadata_torrent](const std::vector<Peer>& peers, const InfoHash& hash) {
3472
+ // Check if metadata torrent is still running (may have completed or been stopped)
3473
+ if (!metadata_torrent->is_running()) {
3474
+ LOG_BT_DEBUG("Metadata torrent already stopped, ignoring DHT peer discovery results");
3475
+ return;
3476
+ }
3477
+
3478
+ // Check if metadata is already downloaded
3479
+ if (auto* md = metadata_torrent->get_metadata_download()) {
3480
+ if (md->is_complete()) {
3481
+ LOG_BT_DEBUG("Metadata already downloaded, ignoring DHT peer discovery results");
3482
+ return;
3483
+ }
3484
+ }
3485
+
3486
+ LOG_BT_INFO("DHT found " << peers.size() << " peers for hash " << info_hash_to_hex(info_hash));
3487
+
3488
+ if (peers.empty()) {
3489
+ LOG_BT_WARN("No peers found via DHT for hash " << info_hash_to_hex(info_hash));
3490
+
3491
+ // Call callback with error
3492
+ MetadataRetrievalCallback user_callback;
3493
+ {
3494
+ std::lock_guard<std::mutex> lock(metadata_mutex_);
3495
+ auto it = metadata_retrieval_callbacks_.find(info_hash);
3496
+ if (it != metadata_retrieval_callbacks_.end()) {
3497
+ user_callback = it->second;
3498
+ metadata_retrieval_callbacks_.erase(it);
3499
+ }
3500
+ metadata_only_torrents_.erase(info_hash);
3501
+ }
3502
+
3503
+ metadata_torrent->stop();
3504
+
3505
+ if (user_callback) {
3506
+ user_callback(TorrentInfo(), false, "No peers found via DHT");
3507
+ }
3508
+ return;
3509
+ }
3510
+
3511
+ // Connect to peers to get metadata
3512
+ size_t max_peers_to_try = (std::min)(peers.size(), static_cast<size_t>(10));
3513
+ size_t peers_added = 0;
3514
+ for (size_t i = 0; i < max_peers_to_try; ++i) {
3515
+ // Check again if metadata is complete (could have completed during loop)
3516
+ if (auto* md = metadata_torrent->get_metadata_download()) {
3517
+ if (md->is_complete()) {
3518
+ LOG_BT_DEBUG("Metadata download completed, stopping peer additions");
3519
+ break;
3520
+ }
3521
+ }
3522
+
3523
+ // Check if torrent is still running
3524
+ if (!metadata_torrent->is_running()) {
3525
+ LOG_BT_DEBUG("Metadata torrent stopped, stopping peer additions");
3526
+ break;
3527
+ }
3528
+
3529
+ const auto& peer = peers[i];
3530
+ LOG_BT_INFO("Connecting to peer " << peer.ip << ":" << peer.port << " for metadata retrieval");
3531
+
3532
+ if (metadata_torrent->add_peer(peer)) {
3533
+ peers_added++;
3534
+ } else {
3535
+ LOG_BT_DEBUG("Failed to add peer " << peer.ip << ":" << peer.port);
3536
+ }
3537
+ }
3538
+
3539
+ LOG_BT_INFO("Added " << peers_added << " peers for metadata retrieval");
3540
+ });
3541
+
3542
+ LOG_BT_INFO("Metadata retrieval initiated for hash " << info_hash_to_hex(info_hash));
3543
+ LOG_BT_INFO("Waiting for DHT peer discovery and metadata exchange...");
3544
+ }
3545
+
3546
+ void BitTorrentClient::get_torrent_metadata_by_hash(const std::string& info_hash_hex, MetadataRetrievalCallback callback) {
3547
+ InfoHash info_hash = hex_to_info_hash(info_hash_hex);
3548
+
3549
+ // Validate the parsed hash
3550
+ bool is_zero = true;
3551
+ for (const auto& byte : info_hash) {
3552
+ if (byte != 0) {
3553
+ is_zero = false;
3554
+ break;
3555
+ }
3556
+ }
3557
+
3558
+ if (is_zero && info_hash_hex.length() == 40) {
3559
+ LOG_BT_ERROR("Invalid info hash format: " << info_hash_hex);
3560
+ if (callback) {
3561
+ callback(TorrentInfo(), false, "Invalid info hash format");
3562
+ }
3563
+ return;
3564
+ }
3565
+
3566
+ get_torrent_metadata_by_hash(info_hash, callback);
3567
+ }
3568
+
3569
+ //=============================================================================
3570
+ // Utility Functions Implementation
3571
+ //=============================================================================
3572
+
3573
+ InfoHash calculate_info_hash(const BencodeValue& info_dict) {
3574
+ // Encode the info dictionary and calculate SHA1 hash
3575
+ std::vector<uint8_t> encoded = info_dict.encode();
3576
+ std::string hash_string = SHA1::hash_bytes(encoded);
3577
+
3578
+ InfoHash result;
3579
+ // Convert hex string to bytes
3580
+ for (size_t i = 0; i < 20; ++i) {
3581
+ std::string byte_str = hash_string.substr(i * 2, 2);
3582
+ result[i] = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
3583
+ }
3584
+
3585
+ return result;
3586
+ }
3587
+
3588
+ std::string info_hash_to_hex(const InfoHash& hash) {
3589
+ std::ostringstream hex_stream;
3590
+ for (size_t i = 0; i < hash.size(); ++i) {
3591
+ hex_stream << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(hash[i]);
3592
+ }
3593
+ return hex_stream.str();
3594
+ }
3595
+
3596
+ InfoHash hex_to_info_hash(const std::string& hex) {
3597
+ InfoHash result;
3598
+ result.fill(0);
3599
+
3600
+ if (hex.length() != 40) { // 20 bytes * 2 hex chars per byte
3601
+ return result;
3602
+ }
3603
+
3604
+ for (size_t i = 0; i < 20; ++i) {
3605
+ std::string byte_str = hex.substr(i * 2, 2);
3606
+ try {
3607
+ result[i] = static_cast<uint8_t>(std::stoul(byte_str, nullptr, 16));
3608
+ } catch (const std::exception&) {
3609
+ result.fill(0);
3610
+ return result;
3611
+ }
3612
+ }
3613
+
3614
+ return result;
3615
+ }
3616
+
3617
+ PeerID generate_peer_id() {
3618
+ PeerID peer_id;
3619
+
3620
+ // Use librats- prefix to identify our client
3621
+ std::string prefix = "-LR0001-"; // LR = LibRats, 0001 = version
3622
+ std::copy(prefix.begin(), prefix.end(), peer_id.begin());
3623
+
3624
+ // Fill the rest with random data
3625
+ std::random_device rd;
3626
+ std::mt19937 gen(rd());
3627
+ std::uniform_int_distribution<> dis(0, 255);
3628
+
3629
+ for (size_t i = prefix.length(); i < peer_id.size(); ++i) {
3630
+ peer_id[i] = static_cast<uint8_t>(dis(gen));
3631
+ }
3632
+
3633
+ return peer_id;
3634
+ }
3635
+
3636
+ std::vector<uint8_t> create_handshake_message(const InfoHash& info_hash, const PeerID& peer_id) {
3637
+ std::vector<uint8_t> handshake(68);
3638
+
3639
+ // Protocol identifier length
3640
+ handshake[0] = BITTORRENT_PROTOCOL_ID_LENGTH;
3641
+
3642
+ // Protocol identifier
3643
+ std::copy_n(BITTORRENT_PROTOCOL_ID, BITTORRENT_PROTOCOL_ID_LENGTH, handshake.begin() + 1);
3644
+
3645
+ // Reserved bytes (8 bytes of zeros)
3646
+ std::fill_n(handshake.begin() + 20, 8, 0);
3647
+
3648
+ // Info hash (20 bytes)
3649
+ std::copy(info_hash.begin(), info_hash.end(), handshake.begin() + 28);
3650
+
3651
+ // Peer ID (20 bytes)
3652
+ std::copy(peer_id.begin(), peer_id.end(), handshake.begin() + 48);
3653
+
3654
+ return handshake;
3655
+ }
3656
+
3657
+ bool parse_handshake_message(const std::vector<uint8_t>& data, InfoHash& info_hash, PeerID& peer_id) {
3658
+ if (data.size() != 68) {
3659
+ return false;
3660
+ }
3661
+
3662
+ // Check protocol identifier length
3663
+ if (data[0] != BITTORRENT_PROTOCOL_ID_LENGTH) {
3664
+ return false;
3665
+ }
3666
+
3667
+ // Check protocol identifier
3668
+ std::string protocol_id(data.begin() + 1, data.begin() + 1 + BITTORRENT_PROTOCOL_ID_LENGTH);
3669
+ if (protocol_id != std::string(reinterpret_cast<const char*>(BITTORRENT_PROTOCOL_ID), BITTORRENT_PROTOCOL_ID_LENGTH)) {
3670
+ return false;
3671
+ }
3672
+
3673
+ // Extract info hash
3674
+ std::copy(data.begin() + 28, data.begin() + 48, info_hash.begin());
3675
+
3676
+ // Extract peer ID
3677
+ std::copy(data.begin() + 48, data.begin() + 68, peer_id.begin());
3678
+
3679
+ return true;
3680
+ }
3681
+
3682
+ } // namespace librats