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.
- package/binding.gyp +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
- package/native-src/CMakeLists.txt +360 -0
- package/native-src/LICENSE +21 -0
- package/native-src/src/bencode.cpp +485 -0
- package/native-src/src/bencode.h +145 -0
- package/native-src/src/bittorrent.cpp +3682 -0
- package/native-src/src/bittorrent.h +731 -0
- package/native-src/src/dht.cpp +2342 -0
- package/native-src/src/dht.h +501 -0
- package/native-src/src/encrypted_socket.cpp +817 -0
- package/native-src/src/encrypted_socket.h +239 -0
- package/native-src/src/file_transfer.cpp +1808 -0
- package/native-src/src/file_transfer.h +567 -0
- package/native-src/src/fs.cpp +639 -0
- package/native-src/src/fs.h +108 -0
- package/native-src/src/gossipsub.cpp +1137 -0
- package/native-src/src/gossipsub.h +403 -0
- package/native-src/src/ice.cpp +1386 -0
- package/native-src/src/ice.h +328 -0
- package/native-src/src/json.hpp +25526 -0
- package/native-src/src/krpc.cpp +558 -0
- package/native-src/src/krpc.h +145 -0
- package/native-src/src/librats.cpp +2715 -0
- package/native-src/src/librats.h +1729 -0
- package/native-src/src/librats_bittorrent.cpp +167 -0
- package/native-src/src/librats_c.cpp +1317 -0
- package/native-src/src/librats_c.h +237 -0
- package/native-src/src/librats_encryption.cpp +123 -0
- package/native-src/src/librats_file_transfer.cpp +226 -0
- package/native-src/src/librats_gossipsub.cpp +293 -0
- package/native-src/src/librats_ice.cpp +515 -0
- package/native-src/src/librats_logging.cpp +158 -0
- package/native-src/src/librats_mdns.cpp +171 -0
- package/native-src/src/librats_nat.cpp +571 -0
- package/native-src/src/librats_persistence.cpp +815 -0
- package/native-src/src/logger.h +412 -0
- package/native-src/src/mdns.cpp +1178 -0
- package/native-src/src/mdns.h +253 -0
- package/native-src/src/network_utils.cpp +598 -0
- package/native-src/src/network_utils.h +162 -0
- package/native-src/src/noise.cpp +981 -0
- package/native-src/src/noise.h +227 -0
- package/native-src/src/os.cpp +371 -0
- package/native-src/src/os.h +40 -0
- package/native-src/src/rats_export.h +17 -0
- package/native-src/src/sha1.cpp +163 -0
- package/native-src/src/sha1.h +42 -0
- package/native-src/src/socket.cpp +1376 -0
- package/native-src/src/socket.h +309 -0
- package/native-src/src/stun.cpp +484 -0
- package/native-src/src/stun.h +349 -0
- package/native-src/src/threadmanager.cpp +105 -0
- package/native-src/src/threadmanager.h +53 -0
- package/native-src/src/tracker.cpp +1110 -0
- package/native-src/src/tracker.h +268 -0
- package/native-src/src/version.cpp +24 -0
- package/native-src/src/version.h.in +45 -0
- package/native-src/version.rc.in +31 -0
- package/package.json +2 -8
- package/scripts/build-librats.js +59 -12
- package/scripts/prepare-package.js +133 -37
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "bencode.h"
|
|
4
|
+
#include "sha1.h"
|
|
5
|
+
#include "socket.h"
|
|
6
|
+
#include "dht.h"
|
|
7
|
+
#include "logger.h"
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include <map>
|
|
11
|
+
#include <unordered_map>
|
|
12
|
+
#include <unordered_set>
|
|
13
|
+
#include <memory>
|
|
14
|
+
#include <functional>
|
|
15
|
+
#include <mutex>
|
|
16
|
+
#include <thread>
|
|
17
|
+
#include <atomic>
|
|
18
|
+
#include <chrono>
|
|
19
|
+
#include <array>
|
|
20
|
+
#include <algorithm> // Add this for std::all_of
|
|
21
|
+
#include <condition_variable>
|
|
22
|
+
|
|
23
|
+
namespace librats {
|
|
24
|
+
|
|
25
|
+
// Forward declarations
|
|
26
|
+
class BitTorrentClient;
|
|
27
|
+
class TorrentDownload;
|
|
28
|
+
class PeerConnection;
|
|
29
|
+
class MetadataDownload;
|
|
30
|
+
class TrackerManager;
|
|
31
|
+
|
|
32
|
+
//=============================================================================
|
|
33
|
+
// LOCK ORDERING DOCUMENTATION
|
|
34
|
+
//=============================================================================
|
|
35
|
+
// To prevent deadlocks, always acquire locks in the following order:
|
|
36
|
+
//
|
|
37
|
+
// BitTorrentClient level:
|
|
38
|
+
// torrents_mutex_ -> metadata_mutex_
|
|
39
|
+
//
|
|
40
|
+
// TorrentDownload level:
|
|
41
|
+
// peers_mutex_ -> pieces_mutex_ -> files_mutex_
|
|
42
|
+
//
|
|
43
|
+
// PeerConnection level:
|
|
44
|
+
// requests_mutex_ (independent, only used within PeerConnection)
|
|
45
|
+
//
|
|
46
|
+
// MetadataDownload level:
|
|
47
|
+
// mutex_ (never call callbacks while holding this mutex!)
|
|
48
|
+
//
|
|
49
|
+
// Cross-class lock ordering:
|
|
50
|
+
// BitTorrentClient::torrents_mutex_
|
|
51
|
+
// -> TorrentDownload::peers_mutex_
|
|
52
|
+
// -> TorrentDownload::pieces_mutex_
|
|
53
|
+
// -> TorrentDownload::files_mutex_
|
|
54
|
+
// -> PeerConnection::requests_mutex_
|
|
55
|
+
//=============================================================================
|
|
56
|
+
|
|
57
|
+
// Type aliases
|
|
58
|
+
using InfoHash = std::array<uint8_t, 20>;
|
|
59
|
+
using PieceIndex = uint32_t;
|
|
60
|
+
using BlockIndex = uint32_t;
|
|
61
|
+
using PeerID = std::array<uint8_t, 20>;
|
|
62
|
+
|
|
63
|
+
// Constants
|
|
64
|
+
constexpr size_t BLOCK_SIZE = 16384; // 16KB standard block size
|
|
65
|
+
constexpr size_t MAX_PIECE_SIZE = 2 * 1024 * 1024; // 2MB max piece size
|
|
66
|
+
constexpr size_t HANDSHAKE_TIMEOUT_MS = 30000; // 30 seconds
|
|
67
|
+
constexpr size_t REQUEST_TIMEOUT_MS = 60000; // 60 seconds
|
|
68
|
+
constexpr size_t MAX_REQUESTS_PER_PEER = 10; // Maximum concurrent requests per peer
|
|
69
|
+
constexpr size_t MAX_PEERS_PER_TORRENT = 50; // Maximum peers per torrent
|
|
70
|
+
|
|
71
|
+
// BitTorrent protocol constants
|
|
72
|
+
constexpr uint8_t BITTORRENT_PROTOCOL_ID[] = "BitTorrent protocol";
|
|
73
|
+
constexpr size_t BITTORRENT_PROTOCOL_ID_LENGTH = 19;
|
|
74
|
+
|
|
75
|
+
// File information structure
|
|
76
|
+
struct FileInfo {
|
|
77
|
+
std::string path;
|
|
78
|
+
uint64_t length;
|
|
79
|
+
uint64_t offset; // Offset within the torrent
|
|
80
|
+
|
|
81
|
+
FileInfo(const std::string& p, uint64_t len, uint64_t off)
|
|
82
|
+
: path(p), length(len), offset(off) {}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Piece information
|
|
86
|
+
struct PieceInfo {
|
|
87
|
+
PieceIndex index;
|
|
88
|
+
std::array<uint8_t, 20> hash;
|
|
89
|
+
uint32_t length;
|
|
90
|
+
bool verified;
|
|
91
|
+
std::vector<bool> blocks_downloaded; // Track which blocks are downloaded (received)
|
|
92
|
+
std::vector<bool> blocks_requested; // Track which blocks are requested globally (sent but not received)
|
|
93
|
+
std::vector<uint8_t> data;
|
|
94
|
+
|
|
95
|
+
PieceInfo(PieceIndex idx, const std::array<uint8_t, 20>& h, uint32_t len)
|
|
96
|
+
: index(idx), hash(h), length(len), verified(false) {
|
|
97
|
+
uint32_t num_blocks = (length + BLOCK_SIZE - 1) / BLOCK_SIZE;
|
|
98
|
+
blocks_downloaded.resize(num_blocks, false);
|
|
99
|
+
blocks_requested.resize(num_blocks, false);
|
|
100
|
+
// Don't pre-allocate data - allocate lazily when downloading starts
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
bool is_complete() const {
|
|
104
|
+
return std::all_of(blocks_downloaded.begin(), blocks_downloaded.end(), [](bool b) { return b; });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
uint32_t get_num_blocks() const {
|
|
108
|
+
return static_cast<uint32_t>(blocks_downloaded.size());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check if all blocks have been requested (or downloaded)
|
|
112
|
+
bool all_blocks_requested() const {
|
|
113
|
+
for (size_t i = 0; i < blocks_downloaded.size(); ++i) {
|
|
114
|
+
if (!blocks_downloaded[i] && !blocks_requested[i]) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Reset requested status for blocks that weren't received (for timeout/disconnect)
|
|
122
|
+
void reset_unreceived_requests() {
|
|
123
|
+
for (size_t i = 0; i < blocks_requested.size(); ++i) {
|
|
124
|
+
if (blocks_requested[i] && !blocks_downloaded[i]) {
|
|
125
|
+
blocks_requested[i] = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Ensure data buffer is allocated
|
|
131
|
+
void ensure_data_allocated() {
|
|
132
|
+
if (data.empty()) {
|
|
133
|
+
data.resize(length);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Free data buffer after piece is written to disk
|
|
138
|
+
void free_data() {
|
|
139
|
+
data.clear();
|
|
140
|
+
data.shrink_to_fit();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Torrent file parser and information holder
|
|
145
|
+
class TorrentInfo {
|
|
146
|
+
public:
|
|
147
|
+
TorrentInfo();
|
|
148
|
+
~TorrentInfo();
|
|
149
|
+
|
|
150
|
+
// Parse torrent from file
|
|
151
|
+
bool load_from_file(const std::string& torrent_file);
|
|
152
|
+
|
|
153
|
+
// Parse torrent from bencode data
|
|
154
|
+
bool load_from_bencode(const BencodeValue& torrent_data);
|
|
155
|
+
|
|
156
|
+
// Parse torrent from raw data
|
|
157
|
+
bool load_from_data(const std::vector<uint8_t>& data);
|
|
158
|
+
|
|
159
|
+
// Getters
|
|
160
|
+
const InfoHash& get_info_hash() const { return info_hash_; }
|
|
161
|
+
const std::string& get_name() const { return name_; }
|
|
162
|
+
uint64_t get_total_length() const { return total_length_; }
|
|
163
|
+
uint32_t get_piece_length() const { return piece_length_; }
|
|
164
|
+
uint32_t get_num_pieces() const { return static_cast<uint32_t>(piece_hashes_.size()); }
|
|
165
|
+
const std::vector<std::array<uint8_t, 20>>& get_piece_hashes() const { return piece_hashes_; }
|
|
166
|
+
const std::vector<FileInfo>& get_files() const { return files_; }
|
|
167
|
+
const std::string& get_announce() const { return announce_; }
|
|
168
|
+
const std::vector<std::string>& get_announce_list() const { return announce_list_; }
|
|
169
|
+
bool is_single_file() const { return files_.size() == 1; }
|
|
170
|
+
bool is_private() const { return private_; }
|
|
171
|
+
|
|
172
|
+
// Calculate piece length for specific piece
|
|
173
|
+
uint32_t get_piece_length(PieceIndex piece_index) const;
|
|
174
|
+
|
|
175
|
+
// Validate torrent info
|
|
176
|
+
bool is_valid() const;
|
|
177
|
+
|
|
178
|
+
// Create a minimal torrent info for metadata exchange only (BEP 9)
|
|
179
|
+
static TorrentInfo create_for_metadata_exchange(const InfoHash& info_hash);
|
|
180
|
+
|
|
181
|
+
// Check if this is a metadata-only torrent (for BEP 9 metadata exchange)
|
|
182
|
+
bool is_metadata_only() const { return metadata_only_; }
|
|
183
|
+
|
|
184
|
+
private:
|
|
185
|
+
InfoHash info_hash_;
|
|
186
|
+
std::string name_;
|
|
187
|
+
uint64_t total_length_;
|
|
188
|
+
uint32_t piece_length_;
|
|
189
|
+
std::vector<std::array<uint8_t, 20>> piece_hashes_;
|
|
190
|
+
std::vector<FileInfo> files_;
|
|
191
|
+
std::string announce_;
|
|
192
|
+
std::vector<std::string> announce_list_;
|
|
193
|
+
bool private_;
|
|
194
|
+
bool metadata_only_; // True if this is a placeholder for metadata exchange
|
|
195
|
+
|
|
196
|
+
bool parse_info_dict(const BencodeValue& info_dict);
|
|
197
|
+
void calculate_info_hash(const BencodeValue& info_dict);
|
|
198
|
+
void build_file_list(const BencodeValue& info_dict);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// BitTorrent peer wire protocol message types
|
|
202
|
+
enum class MessageType : uint8_t {
|
|
203
|
+
CHOKE = 0,
|
|
204
|
+
UNCHOKE = 1,
|
|
205
|
+
INTERESTED = 2,
|
|
206
|
+
NOT_INTERESTED = 3,
|
|
207
|
+
HAVE = 4,
|
|
208
|
+
BITFIELD = 5,
|
|
209
|
+
REQUEST = 6,
|
|
210
|
+
PIECE = 7,
|
|
211
|
+
CANCEL = 8,
|
|
212
|
+
PORT = 9,
|
|
213
|
+
EXTENDED = 20
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Extended message types (BEP 10)
|
|
217
|
+
enum class ExtendedMessageType : uint8_t {
|
|
218
|
+
HANDSHAKE = 0,
|
|
219
|
+
UT_METADATA = 1 // Our local ID for ut_metadata extension
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Metadata message types (BEP 9)
|
|
223
|
+
enum class MetadataMessageType : uint8_t {
|
|
224
|
+
REQUEST = 0,
|
|
225
|
+
DATA = 1,
|
|
226
|
+
REJECT = 2
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// Metadata constants
|
|
230
|
+
constexpr size_t METADATA_PIECE_SIZE = 16384; // 16KB metadata pieces
|
|
231
|
+
constexpr size_t MAX_METADATA_SIZE = 10 * 1024 * 1024; // 10MB max metadata size
|
|
232
|
+
|
|
233
|
+
// BitTorrent peer wire protocol messages
|
|
234
|
+
struct PeerMessage {
|
|
235
|
+
MessageType type;
|
|
236
|
+
std::vector<uint8_t> payload;
|
|
237
|
+
|
|
238
|
+
PeerMessage(MessageType t) : type(t) {}
|
|
239
|
+
PeerMessage(MessageType t, const std::vector<uint8_t>& p) : type(t), payload(p) {}
|
|
240
|
+
|
|
241
|
+
// Serialize message to wire format
|
|
242
|
+
std::vector<uint8_t> serialize() const;
|
|
243
|
+
|
|
244
|
+
// Create specific message types
|
|
245
|
+
static PeerMessage create_choke();
|
|
246
|
+
static PeerMessage create_unchoke();
|
|
247
|
+
static PeerMessage create_interested();
|
|
248
|
+
static PeerMessage create_not_interested();
|
|
249
|
+
static PeerMessage create_have(PieceIndex piece_index);
|
|
250
|
+
static PeerMessage create_bitfield(const std::vector<bool>& bitfield);
|
|
251
|
+
static PeerMessage create_request(PieceIndex piece_index, uint32_t offset, uint32_t length);
|
|
252
|
+
static PeerMessage create_piece(PieceIndex piece_index, uint32_t offset, const std::vector<uint8_t>& data);
|
|
253
|
+
static PeerMessage create_cancel(PieceIndex piece_index, uint32_t offset, uint32_t length);
|
|
254
|
+
static PeerMessage create_port(uint16_t port);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Peer request tracking
|
|
258
|
+
struct PeerRequest {
|
|
259
|
+
PieceIndex piece_index;
|
|
260
|
+
uint32_t offset;
|
|
261
|
+
uint32_t length;
|
|
262
|
+
std::chrono::steady_clock::time_point requested_at;
|
|
263
|
+
|
|
264
|
+
PeerRequest(PieceIndex piece, uint32_t off, uint32_t len)
|
|
265
|
+
: piece_index(piece), offset(off), length(len),
|
|
266
|
+
requested_at(std::chrono::steady_clock::now()) {}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Peer connection state
|
|
270
|
+
enum class PeerState {
|
|
271
|
+
CONNECTING,
|
|
272
|
+
HANDSHAKING,
|
|
273
|
+
CONNECTED,
|
|
274
|
+
DISCONNECTED,
|
|
275
|
+
ERROR
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// Individual peer connection for BitTorrent protocol
|
|
279
|
+
class PeerConnection {
|
|
280
|
+
public:
|
|
281
|
+
PeerConnection(TorrentDownload* torrent, const Peer& peer_info, socket_t socket = INVALID_SOCKET_VALUE);
|
|
282
|
+
~PeerConnection();
|
|
283
|
+
|
|
284
|
+
// Connection management
|
|
285
|
+
bool connect();
|
|
286
|
+
void disconnect();
|
|
287
|
+
bool is_connected() const { return state_.load() == PeerState::CONNECTED; }
|
|
288
|
+
PeerState get_state() const { return state_.load(); }
|
|
289
|
+
|
|
290
|
+
// Message handling
|
|
291
|
+
bool send_message(const PeerMessage& message);
|
|
292
|
+
void process_messages();
|
|
293
|
+
|
|
294
|
+
// Seeding support
|
|
295
|
+
void send_bitfield(); // Send our bitfield to peer after handshake
|
|
296
|
+
|
|
297
|
+
// Piece requests
|
|
298
|
+
bool request_piece_block(PieceIndex piece_index, uint32_t offset, uint32_t length);
|
|
299
|
+
bool is_block_requested(PieceIndex piece_index, uint32_t offset) const;
|
|
300
|
+
void cancel_request(PieceIndex piece_index, uint32_t offset, uint32_t length);
|
|
301
|
+
void cancel_all_requests();
|
|
302
|
+
|
|
303
|
+
// Peer state
|
|
304
|
+
bool is_choked() const { return peer_choked_; }
|
|
305
|
+
bool is_interested() const { return am_interested_; }
|
|
306
|
+
bool peer_is_interested() const { return peer_interested_; }
|
|
307
|
+
bool am_choking() const { return am_choking_; }
|
|
308
|
+
|
|
309
|
+
void set_interested(bool interested);
|
|
310
|
+
void set_choke(bool choke);
|
|
311
|
+
|
|
312
|
+
// Bitfield management
|
|
313
|
+
bool has_piece(PieceIndex piece_index) const;
|
|
314
|
+
const std::vector<bool>& get_bitfield() const { return peer_bitfield_; }
|
|
315
|
+
void update_bitfield(const std::vector<bool>& bitfield);
|
|
316
|
+
|
|
317
|
+
// Statistics
|
|
318
|
+
uint64_t get_downloaded() const { return downloaded_bytes_; }
|
|
319
|
+
uint64_t get_uploaded() const { return uploaded_bytes_; }
|
|
320
|
+
size_t get_pending_requests() const { return pending_requests_.size(); }
|
|
321
|
+
|
|
322
|
+
// Speed calculation (bytes per second)
|
|
323
|
+
double get_download_speed() const;
|
|
324
|
+
double get_upload_speed() const;
|
|
325
|
+
void update_speed_stats(); // Call periodically to update speed
|
|
326
|
+
|
|
327
|
+
// Peer info
|
|
328
|
+
const Peer& get_peer_info() const { return peer_info_; }
|
|
329
|
+
const PeerID& get_peer_id() const { return peer_id_; }
|
|
330
|
+
|
|
331
|
+
// Extension protocol (BEP 10)
|
|
332
|
+
bool supports_extensions() const { return supports_extensions_; }
|
|
333
|
+
bool supports_metadata_exchange() const { return supports_metadata_exchange_; }
|
|
334
|
+
uint8_t get_peer_metadata_extension_id() const { return peer_ut_metadata_id_; }
|
|
335
|
+
|
|
336
|
+
// Metadata exchange (BEP 9)
|
|
337
|
+
void request_metadata_piece(uint32_t piece_index);
|
|
338
|
+
size_t get_peer_metadata_size() const { return peer_metadata_size_; }
|
|
339
|
+
|
|
340
|
+
private:
|
|
341
|
+
TorrentDownload* torrent_;
|
|
342
|
+
Peer peer_info_;
|
|
343
|
+
socket_t socket_;
|
|
344
|
+
std::atomic<PeerState> state_; // Atomic to allow thread-safe reads without lock
|
|
345
|
+
std::thread connection_thread_;
|
|
346
|
+
std::atomic<bool> should_disconnect_;
|
|
347
|
+
bool handshake_completed_; // Track if handshake is already done (for incoming connections)
|
|
348
|
+
|
|
349
|
+
// Conditional variables for immediate shutdown
|
|
350
|
+
std::condition_variable shutdown_cv_;
|
|
351
|
+
std::mutex shutdown_mutex_;
|
|
352
|
+
|
|
353
|
+
// Peer state
|
|
354
|
+
PeerID peer_id_;
|
|
355
|
+
bool peer_choked_;
|
|
356
|
+
bool am_choked_;
|
|
357
|
+
bool peer_interested_;
|
|
358
|
+
bool am_interested_;
|
|
359
|
+
bool am_choking_;
|
|
360
|
+
std::vector<bool> peer_bitfield_;
|
|
361
|
+
|
|
362
|
+
// Request tracking
|
|
363
|
+
std::vector<PeerRequest> pending_requests_;
|
|
364
|
+
mutable std::mutex requests_mutex_;
|
|
365
|
+
|
|
366
|
+
// Statistics
|
|
367
|
+
std::atomic<uint64_t> downloaded_bytes_;
|
|
368
|
+
std::atomic<uint64_t> uploaded_bytes_;
|
|
369
|
+
|
|
370
|
+
// Speed tracking
|
|
371
|
+
mutable std::mutex speed_mutex_;
|
|
372
|
+
std::chrono::steady_clock::time_point last_speed_update_time_;
|
|
373
|
+
uint64_t last_speed_downloaded_;
|
|
374
|
+
uint64_t last_speed_uploaded_;
|
|
375
|
+
std::atomic<double> download_speed_; // bytes per second
|
|
376
|
+
std::atomic<double> upload_speed_; // bytes per second
|
|
377
|
+
|
|
378
|
+
// Message buffer
|
|
379
|
+
std::vector<uint8_t> message_buffer_;
|
|
380
|
+
size_t expected_message_length_;
|
|
381
|
+
|
|
382
|
+
// Extension protocol support (BEP 10)
|
|
383
|
+
bool supports_extensions_;
|
|
384
|
+
bool supports_metadata_exchange_;
|
|
385
|
+
uint8_t peer_ut_metadata_id_; // Peer's extension ID for ut_metadata
|
|
386
|
+
size_t peer_metadata_size_;
|
|
387
|
+
|
|
388
|
+
void connection_loop();
|
|
389
|
+
bool perform_handshake();
|
|
390
|
+
bool send_handshake();
|
|
391
|
+
bool receive_handshake();
|
|
392
|
+
|
|
393
|
+
// Message parsing
|
|
394
|
+
std::unique_ptr<PeerMessage> parse_message(const std::vector<uint8_t>& data);
|
|
395
|
+
void handle_message(const PeerMessage& message);
|
|
396
|
+
|
|
397
|
+
// Specific message handlers
|
|
398
|
+
void handle_choke();
|
|
399
|
+
void handle_unchoke();
|
|
400
|
+
void handle_interested();
|
|
401
|
+
void handle_not_interested();
|
|
402
|
+
void handle_have(const std::vector<uint8_t>& payload);
|
|
403
|
+
void handle_bitfield(const std::vector<uint8_t>& payload);
|
|
404
|
+
void handle_request(const std::vector<uint8_t>& payload);
|
|
405
|
+
void handle_piece(const std::vector<uint8_t>& payload);
|
|
406
|
+
void handle_cancel(const std::vector<uint8_t>& payload);
|
|
407
|
+
void handle_extended(const std::vector<uint8_t>& payload);
|
|
408
|
+
|
|
409
|
+
// Extended message handlers (BEP 10)
|
|
410
|
+
void handle_extended_handshake(const std::vector<uint8_t>& payload);
|
|
411
|
+
void handle_metadata_message(const std::vector<uint8_t>& payload);
|
|
412
|
+
void send_extended_handshake();
|
|
413
|
+
|
|
414
|
+
// Utility
|
|
415
|
+
void cleanup_expired_requests();
|
|
416
|
+
bool read_data(std::vector<uint8_t>& buffer, size_t length);
|
|
417
|
+
bool write_data(const std::vector<uint8_t>& data);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Download progress callback types
|
|
421
|
+
using ProgressCallback = std::function<void(uint64_t downloaded, uint64_t total, double percentage)>;
|
|
422
|
+
using PieceCompleteCallback = std::function<void(PieceIndex piece_index)>;
|
|
423
|
+
using TorrentCompleteCallback = std::function<void(const std::string& torrent_name)>;
|
|
424
|
+
using PeerConnectedCallback = std::function<void(const Peer& peer)>;
|
|
425
|
+
using PeerDisconnectedCallback = std::function<void(const Peer& peer)>;
|
|
426
|
+
using MetadataCompleteCallback = std::function<void(const TorrentInfo& torrent_info)>;
|
|
427
|
+
|
|
428
|
+
// Metadata download coordinator (BEP 9)
|
|
429
|
+
class MetadataDownload {
|
|
430
|
+
public:
|
|
431
|
+
MetadataDownload(const InfoHash& info_hash, size_t metadata_size);
|
|
432
|
+
~MetadataDownload();
|
|
433
|
+
|
|
434
|
+
// Store a metadata piece from a peer
|
|
435
|
+
bool store_metadata_piece(uint32_t piece_index, const std::vector<uint8_t>& data);
|
|
436
|
+
|
|
437
|
+
// Check if we have all metadata pieces
|
|
438
|
+
bool is_complete() const;
|
|
439
|
+
|
|
440
|
+
// Get the reconstructed metadata
|
|
441
|
+
std::vector<uint8_t> get_metadata() const;
|
|
442
|
+
|
|
443
|
+
// Verify metadata hash matches expected info hash
|
|
444
|
+
bool verify_metadata() const;
|
|
445
|
+
|
|
446
|
+
// Get next piece to request
|
|
447
|
+
uint32_t get_next_piece_to_request() const;
|
|
448
|
+
|
|
449
|
+
// Check if a piece is complete
|
|
450
|
+
bool is_piece_complete(uint32_t piece_index) const;
|
|
451
|
+
|
|
452
|
+
// Get metadata info
|
|
453
|
+
const InfoHash& get_info_hash() const { return info_hash_; }
|
|
454
|
+
size_t get_metadata_size() const { return metadata_size_; }
|
|
455
|
+
uint32_t get_num_pieces() const { return num_pieces_; }
|
|
456
|
+
|
|
457
|
+
// Set completion callback
|
|
458
|
+
void set_completion_callback(MetadataCompleteCallback callback) { completion_callback_ = callback; }
|
|
459
|
+
|
|
460
|
+
private:
|
|
461
|
+
InfoHash info_hash_;
|
|
462
|
+
size_t metadata_size_;
|
|
463
|
+
uint32_t num_pieces_;
|
|
464
|
+
std::vector<std::vector<uint8_t>> pieces_;
|
|
465
|
+
std::vector<bool> pieces_complete_;
|
|
466
|
+
bool completion_triggered_; // Ensures callback is only invoked once
|
|
467
|
+
mutable std::mutex mutex_;
|
|
468
|
+
MetadataCompleteCallback completion_callback_;
|
|
469
|
+
|
|
470
|
+
// Internal versions without lock (called when mutex is already held)
|
|
471
|
+
bool verify_metadata_unlocked() const;
|
|
472
|
+
std::vector<uint8_t> get_metadata_unlocked() const;
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// Individual torrent download
|
|
476
|
+
class TorrentDownload {
|
|
477
|
+
public:
|
|
478
|
+
TorrentDownload(const TorrentInfo& torrent_info, const std::string& download_path);
|
|
479
|
+
~TorrentDownload();
|
|
480
|
+
|
|
481
|
+
// Control
|
|
482
|
+
bool start();
|
|
483
|
+
void stop();
|
|
484
|
+
void pause();
|
|
485
|
+
void resume();
|
|
486
|
+
bool is_running() const { return running_; }
|
|
487
|
+
bool is_paused() const { return paused_; }
|
|
488
|
+
bool is_complete() const;
|
|
489
|
+
|
|
490
|
+
// Peer management
|
|
491
|
+
bool add_peer(const Peer& peer);
|
|
492
|
+
bool add_peer(const Peer& peer, socket_t existing_socket); // For incoming connections
|
|
493
|
+
void remove_peer(const Peer& peer);
|
|
494
|
+
size_t get_peer_count() const;
|
|
495
|
+
std::vector<Peer> get_connected_peers() const;
|
|
496
|
+
|
|
497
|
+
// Piece management
|
|
498
|
+
bool is_piece_complete(PieceIndex piece_index) const;
|
|
499
|
+
bool is_piece_downloading(PieceIndex piece_index) const;
|
|
500
|
+
std::vector<PieceIndex> get_available_pieces() const;
|
|
501
|
+
std::vector<PieceIndex> get_needed_pieces(const std::vector<bool>& peer_bitfield) const;
|
|
502
|
+
|
|
503
|
+
// Piece data handling
|
|
504
|
+
bool store_piece_block(PieceIndex piece_index, uint32_t offset, const std::vector<uint8_t>& data);
|
|
505
|
+
bool verify_piece(PieceIndex piece_index);
|
|
506
|
+
void write_piece_to_disk(PieceIndex piece_index);
|
|
507
|
+
bool read_piece_from_disk(PieceIndex piece_index, std::vector<uint8_t>& data); // For seeding
|
|
508
|
+
void reset_block_request(PieceIndex piece_index, uint32_t block_index); // Reset timed-out block request
|
|
509
|
+
|
|
510
|
+
// Statistics and progress
|
|
511
|
+
uint64_t get_downloaded_bytes() const;
|
|
512
|
+
uint64_t get_uploaded_bytes() const;
|
|
513
|
+
double get_progress_percentage() const;
|
|
514
|
+
uint32_t get_completed_pieces() const;
|
|
515
|
+
std::vector<bool> get_piece_bitfield() const;
|
|
516
|
+
|
|
517
|
+
// Speed tracking
|
|
518
|
+
double get_download_speed() const; // bytes per second
|
|
519
|
+
double get_upload_speed() const; // bytes per second
|
|
520
|
+
void update_speed_stats(); // Call periodically to update speed
|
|
521
|
+
|
|
522
|
+
// Callbacks
|
|
523
|
+
void set_progress_callback(ProgressCallback callback) { progress_callback_ = callback; }
|
|
524
|
+
void set_piece_complete_callback(PieceCompleteCallback callback) { piece_complete_callback_ = callback; }
|
|
525
|
+
void set_torrent_complete_callback(TorrentCompleteCallback callback) { torrent_complete_callback_ = callback; }
|
|
526
|
+
void set_peer_connected_callback(PeerConnectedCallback callback) { peer_connected_callback_ = callback; }
|
|
527
|
+
void set_peer_disconnected_callback(PeerDisconnectedCallback callback) { peer_disconnected_callback_ = callback; }
|
|
528
|
+
void set_metadata_complete_callback(MetadataCompleteCallback callback) { metadata_complete_callback_ = callback; }
|
|
529
|
+
|
|
530
|
+
// Torrent info access
|
|
531
|
+
const TorrentInfo& get_torrent_info() const { return torrent_info_; }
|
|
532
|
+
const std::string& get_download_path() const { return download_path_; }
|
|
533
|
+
|
|
534
|
+
// DHT integration
|
|
535
|
+
void announce_to_dht(DhtClient* dht_client);
|
|
536
|
+
void request_peers_from_dht(DhtClient* dht_client);
|
|
537
|
+
|
|
538
|
+
// Tracker integration
|
|
539
|
+
void announce_to_trackers();
|
|
540
|
+
void request_peers_from_trackers();
|
|
541
|
+
TrackerManager* get_tracker_manager() const { return tracker_manager_.get(); }
|
|
542
|
+
|
|
543
|
+
// Metadata download (BEP 9)
|
|
544
|
+
MetadataDownload* get_metadata_download() const { return metadata_download_.get(); }
|
|
545
|
+
void set_metadata_download(std::shared_ptr<MetadataDownload> metadata_download);
|
|
546
|
+
|
|
547
|
+
private:
|
|
548
|
+
TorrentInfo torrent_info_;
|
|
549
|
+
std::string download_path_;
|
|
550
|
+
std::atomic<bool> running_;
|
|
551
|
+
std::atomic<bool> paused_;
|
|
552
|
+
|
|
553
|
+
// Piece management
|
|
554
|
+
std::vector<std::unique_ptr<PieceInfo>> pieces_;
|
|
555
|
+
std::vector<bool> piece_completed_;
|
|
556
|
+
std::vector<bool> piece_downloading_;
|
|
557
|
+
mutable std::mutex pieces_mutex_;
|
|
558
|
+
|
|
559
|
+
// Peer connections
|
|
560
|
+
std::vector<std::unique_ptr<PeerConnection>> peer_connections_;
|
|
561
|
+
mutable std::mutex peers_mutex_;
|
|
562
|
+
|
|
563
|
+
// Download management
|
|
564
|
+
std::thread download_thread_;
|
|
565
|
+
std::thread peer_management_thread_;
|
|
566
|
+
|
|
567
|
+
// Conditional variables for immediate shutdown
|
|
568
|
+
std::condition_variable shutdown_cv_;
|
|
569
|
+
std::mutex shutdown_mutex_;
|
|
570
|
+
|
|
571
|
+
// File handling (using fs module, no persistent handles needed)
|
|
572
|
+
mutable std::mutex files_mutex_;
|
|
573
|
+
|
|
574
|
+
// Callbacks
|
|
575
|
+
ProgressCallback progress_callback_;
|
|
576
|
+
PieceCompleteCallback piece_complete_callback_;
|
|
577
|
+
TorrentCompleteCallback torrent_complete_callback_;
|
|
578
|
+
PeerConnectedCallback peer_connected_callback_;
|
|
579
|
+
PeerDisconnectedCallback peer_disconnected_callback_;
|
|
580
|
+
MetadataCompleteCallback metadata_complete_callback_;
|
|
581
|
+
|
|
582
|
+
// Statistics
|
|
583
|
+
std::atomic<uint64_t> total_downloaded_;
|
|
584
|
+
std::atomic<uint64_t> total_uploaded_;
|
|
585
|
+
|
|
586
|
+
// Speed tracking
|
|
587
|
+
mutable std::mutex speed_mutex_;
|
|
588
|
+
std::chrono::steady_clock::time_point last_speed_update_time_;
|
|
589
|
+
uint64_t last_speed_downloaded_;
|
|
590
|
+
uint64_t last_speed_uploaded_;
|
|
591
|
+
std::atomic<double> download_speed_; // bytes per second
|
|
592
|
+
std::atomic<double> upload_speed_; // bytes per second
|
|
593
|
+
|
|
594
|
+
// Progress tracking (for change detection in logging)
|
|
595
|
+
double last_logged_progress_;
|
|
596
|
+
std::chrono::steady_clock::time_point last_state_log_time_;
|
|
597
|
+
|
|
598
|
+
// Metadata download (BEP 9)
|
|
599
|
+
std::shared_ptr<MetadataDownload> metadata_download_;
|
|
600
|
+
|
|
601
|
+
// Tracker manager
|
|
602
|
+
std::unique_ptr<TrackerManager> tracker_manager_;
|
|
603
|
+
PeerID our_peer_id_;
|
|
604
|
+
|
|
605
|
+
void download_loop();
|
|
606
|
+
void peer_management_loop();
|
|
607
|
+
void schedule_piece_requests();
|
|
608
|
+
void cleanup_disconnected_peers();
|
|
609
|
+
|
|
610
|
+
// Seeding support
|
|
611
|
+
void notify_peers_have_piece(PieceIndex piece_index); // Broadcast HAVE to all peers
|
|
612
|
+
|
|
613
|
+
// File operations
|
|
614
|
+
bool open_files();
|
|
615
|
+
void close_files();
|
|
616
|
+
bool create_directory_structure();
|
|
617
|
+
|
|
618
|
+
// Piece selection strategy
|
|
619
|
+
std::vector<PieceIndex> select_pieces_for_download();
|
|
620
|
+
PieceIndex select_rarest_piece(const std::vector<bool>& available_pieces);
|
|
621
|
+
|
|
622
|
+
// Progress tracking
|
|
623
|
+
void update_progress();
|
|
624
|
+
void on_piece_completed(PieceIndex piece_index);
|
|
625
|
+
void check_torrent_completion();
|
|
626
|
+
void log_detailed_state(); // Log detailed torrent state for debugging
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// Main BitTorrent client
|
|
630
|
+
class BitTorrentClient {
|
|
631
|
+
public:
|
|
632
|
+
BitTorrentClient();
|
|
633
|
+
~BitTorrentClient();
|
|
634
|
+
|
|
635
|
+
// Client control
|
|
636
|
+
bool start(int listen_port = 0);
|
|
637
|
+
void stop();
|
|
638
|
+
bool is_running() const { return running_; }
|
|
639
|
+
|
|
640
|
+
// Torrent management
|
|
641
|
+
std::shared_ptr<TorrentDownload> add_torrent(const std::string& torrent_file, const std::string& download_path);
|
|
642
|
+
std::shared_ptr<TorrentDownload> add_torrent(const TorrentInfo& torrent_info, const std::string& download_path);
|
|
643
|
+
std::shared_ptr<TorrentDownload> add_torrent_by_hash(const InfoHash& info_hash, const std::string& download_path);
|
|
644
|
+
std::shared_ptr<TorrentDownload> add_torrent_by_hash(const std::string& info_hash_hex, const std::string& download_path);
|
|
645
|
+
bool remove_torrent(const InfoHash& info_hash);
|
|
646
|
+
std::shared_ptr<TorrentDownload> get_torrent(const InfoHash& info_hash);
|
|
647
|
+
std::vector<std::shared_ptr<TorrentDownload>> get_all_torrents();
|
|
648
|
+
|
|
649
|
+
// DHT integration
|
|
650
|
+
void set_dht_client(DhtClient* dht_client) { dht_client_ = dht_client; }
|
|
651
|
+
DhtClient* get_dht_client() const { return dht_client_; }
|
|
652
|
+
|
|
653
|
+
// Peer discovery from DHT
|
|
654
|
+
void discover_peers_for_torrent(const InfoHash& info_hash);
|
|
655
|
+
void announce_torrent_to_dht(const InfoHash& info_hash);
|
|
656
|
+
|
|
657
|
+
// Statistics
|
|
658
|
+
size_t get_active_torrents_count() const;
|
|
659
|
+
uint64_t get_total_downloaded() const;
|
|
660
|
+
uint64_t get_total_uploaded() const;
|
|
661
|
+
|
|
662
|
+
// Configuration
|
|
663
|
+
void set_max_connections_per_torrent(size_t max_connections) { max_connections_per_torrent_ = max_connections; }
|
|
664
|
+
void set_download_rate_limit(uint64_t bytes_per_second) { download_rate_limit_ = bytes_per_second; }
|
|
665
|
+
void set_upload_rate_limit(uint64_t bytes_per_second) { upload_rate_limit_ = bytes_per_second; }
|
|
666
|
+
|
|
667
|
+
// Callbacks for torrent events
|
|
668
|
+
void set_torrent_added_callback(std::function<void(const InfoHash&)> callback) { torrent_added_callback_ = callback; }
|
|
669
|
+
void set_torrent_completed_callback(std::function<void(const InfoHash&)> callback) { torrent_completed_callback_ = callback; }
|
|
670
|
+
void set_torrent_removed_callback(std::function<void(const InfoHash&)> callback) { torrent_removed_callback_ = callback; }
|
|
671
|
+
|
|
672
|
+
// Metadata download management (BEP 9)
|
|
673
|
+
std::shared_ptr<MetadataDownload> get_metadata_download(const InfoHash& info_hash);
|
|
674
|
+
void register_metadata_download(const InfoHash& info_hash, std::shared_ptr<MetadataDownload> metadata_download);
|
|
675
|
+
void complete_metadata_download(const InfoHash& info_hash, const TorrentInfo& torrent_info, const std::string& download_path);
|
|
676
|
+
|
|
677
|
+
// Get torrent metadata without downloading (BEP 9)
|
|
678
|
+
using MetadataRetrievalCallback = std::function<void(const TorrentInfo& torrent_info, bool success, const std::string& error_message)>;
|
|
679
|
+
void get_torrent_metadata_by_hash(const InfoHash& info_hash, MetadataRetrievalCallback callback);
|
|
680
|
+
void get_torrent_metadata_by_hash(const std::string& info_hash_hex, MetadataRetrievalCallback callback);
|
|
681
|
+
|
|
682
|
+
private:
|
|
683
|
+
std::atomic<bool> running_;
|
|
684
|
+
int listen_port_;
|
|
685
|
+
socket_t listen_socket_;
|
|
686
|
+
|
|
687
|
+
// Torrents
|
|
688
|
+
std::map<InfoHash, std::shared_ptr<TorrentDownload>> torrents_;
|
|
689
|
+
mutable std::mutex torrents_mutex_;
|
|
690
|
+
|
|
691
|
+
// Metadata downloads (BEP 9)
|
|
692
|
+
std::map<InfoHash, std::shared_ptr<MetadataDownload>> metadata_downloads_;
|
|
693
|
+
std::map<InfoHash, std::string> metadata_download_paths_; // Store download path for later
|
|
694
|
+
std::map<InfoHash, std::shared_ptr<TorrentDownload>> metadata_only_torrents_; // Temporary torrents for metadata retrieval
|
|
695
|
+
std::map<InfoHash, MetadataRetrievalCallback> metadata_retrieval_callbacks_; // Callbacks for metadata retrieval
|
|
696
|
+
mutable std::mutex metadata_mutex_;
|
|
697
|
+
|
|
698
|
+
// DHT integration
|
|
699
|
+
DhtClient* dht_client_;
|
|
700
|
+
|
|
701
|
+
// Networking
|
|
702
|
+
std::thread incoming_connections_thread_;
|
|
703
|
+
|
|
704
|
+
// Conditional variables for immediate shutdown
|
|
705
|
+
std::condition_variable shutdown_cv_;
|
|
706
|
+
std::mutex shutdown_mutex_;
|
|
707
|
+
|
|
708
|
+
// Configuration
|
|
709
|
+
size_t max_connections_per_torrent_;
|
|
710
|
+
uint64_t download_rate_limit_;
|
|
711
|
+
uint64_t upload_rate_limit_;
|
|
712
|
+
|
|
713
|
+
// Callbacks
|
|
714
|
+
std::function<void(const InfoHash&)> torrent_added_callback_;
|
|
715
|
+
std::function<void(const InfoHash&)> torrent_completed_callback_;
|
|
716
|
+
std::function<void(const InfoHash&)> torrent_removed_callback_;
|
|
717
|
+
|
|
718
|
+
void handle_incoming_connections();
|
|
719
|
+
void handle_incoming_connection(socket_t client_socket);
|
|
720
|
+
bool perform_incoming_handshake(socket_t socket, InfoHash& info_hash, PeerID& peer_id);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
// Utility functions
|
|
724
|
+
InfoHash calculate_info_hash(const BencodeValue& info_dict);
|
|
725
|
+
std::string info_hash_to_hex(const InfoHash& hash);
|
|
726
|
+
InfoHash hex_to_info_hash(const std::string& hex);
|
|
727
|
+
PeerID generate_peer_id();
|
|
728
|
+
std::vector<uint8_t> create_handshake_message(const InfoHash& info_hash, const PeerID& peer_id);
|
|
729
|
+
bool parse_handshake_message(const std::vector<uint8_t>& data, InfoHash& info_hash, PeerID& peer_id);
|
|
730
|
+
|
|
731
|
+
} // namespace librats
|