librats 0.3.1 → 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 (69) hide show
  1. package/README.md +405 -405
  2. package/binding.gyp +96 -95
  3. package/lib/index.d.ts +522 -522
  4. package/lib/index.js +82 -82
  5. package/native-src/3rdparty/android/ifaddrs-android.c +600 -0
  6. package/native-src/3rdparty/android/ifaddrs-android.h +54 -0
  7. package/native-src/CMakeLists.txt +360 -0
  8. package/native-src/LICENSE +21 -0
  9. package/native-src/src/bencode.cpp +485 -0
  10. package/native-src/src/bencode.h +145 -0
  11. package/native-src/src/bittorrent.cpp +3682 -0
  12. package/native-src/src/bittorrent.h +731 -0
  13. package/native-src/src/dht.cpp +2342 -0
  14. package/native-src/src/dht.h +501 -0
  15. package/native-src/src/encrypted_socket.cpp +817 -0
  16. package/native-src/src/encrypted_socket.h +239 -0
  17. package/native-src/src/file_transfer.cpp +1808 -0
  18. package/native-src/src/file_transfer.h +567 -0
  19. package/native-src/src/fs.cpp +639 -0
  20. package/native-src/src/fs.h +108 -0
  21. package/native-src/src/gossipsub.cpp +1137 -0
  22. package/native-src/src/gossipsub.h +403 -0
  23. package/native-src/src/ice.cpp +1386 -0
  24. package/native-src/src/ice.h +328 -0
  25. package/native-src/src/json.hpp +25526 -0
  26. package/native-src/src/krpc.cpp +558 -0
  27. package/native-src/src/krpc.h +145 -0
  28. package/native-src/src/librats.cpp +2715 -0
  29. package/native-src/src/librats.h +1729 -0
  30. package/native-src/src/librats_bittorrent.cpp +167 -0
  31. package/native-src/src/librats_c.cpp +1317 -0
  32. package/native-src/src/librats_c.h +237 -0
  33. package/native-src/src/librats_encryption.cpp +123 -0
  34. package/native-src/src/librats_file_transfer.cpp +226 -0
  35. package/native-src/src/librats_gossipsub.cpp +293 -0
  36. package/native-src/src/librats_ice.cpp +515 -0
  37. package/native-src/src/librats_logging.cpp +158 -0
  38. package/native-src/src/librats_mdns.cpp +171 -0
  39. package/native-src/src/librats_nat.cpp +571 -0
  40. package/native-src/src/librats_persistence.cpp +815 -0
  41. package/native-src/src/logger.h +412 -0
  42. package/native-src/src/mdns.cpp +1178 -0
  43. package/native-src/src/mdns.h +253 -0
  44. package/native-src/src/network_utils.cpp +598 -0
  45. package/native-src/src/network_utils.h +162 -0
  46. package/native-src/src/noise.cpp +981 -0
  47. package/native-src/src/noise.h +227 -0
  48. package/native-src/src/os.cpp +371 -0
  49. package/native-src/src/os.h +40 -0
  50. package/native-src/src/rats_export.h +17 -0
  51. package/native-src/src/sha1.cpp +163 -0
  52. package/native-src/src/sha1.h +42 -0
  53. package/native-src/src/socket.cpp +1376 -0
  54. package/native-src/src/socket.h +309 -0
  55. package/native-src/src/stun.cpp +484 -0
  56. package/native-src/src/stun.h +349 -0
  57. package/native-src/src/threadmanager.cpp +105 -0
  58. package/native-src/src/threadmanager.h +53 -0
  59. package/native-src/src/tracker.cpp +1110 -0
  60. package/native-src/src/tracker.h +268 -0
  61. package/native-src/src/version.cpp +24 -0
  62. package/native-src/src/version.h.in +45 -0
  63. package/native-src/version.rc.in +31 -0
  64. package/package.json +62 -68
  65. package/scripts/build-librats.js +241 -194
  66. package/scripts/postinstall.js +52 -52
  67. package/scripts/prepare-package.js +187 -91
  68. package/scripts/verify-installation.js +119 -119
  69. package/src/librats_node.cpp +1174 -1174
@@ -0,0 +1,1808 @@
1
+ #include "file_transfer.h"
2
+ #include "librats.h"
3
+ #include "fs.h"
4
+ #include "logger.h"
5
+ #include "sha1.h"
6
+
7
+ // Define logging module for this file
8
+ #define LOG_FILE_TRANSFER_INFO(message) LOG_INFO("filetransfer", message)
9
+ #define LOG_FILE_TRANSFER_ERROR(message) LOG_ERROR("filetransfer", message)
10
+ #define LOG_FILE_TRANSFER_WARN(message) LOG_WARN("filetransfer", message)
11
+ #define LOG_FILE_TRANSFER_DEBUG(message) LOG_DEBUG("filetransfer", message)
12
+ #include <algorithm>
13
+ #include <random>
14
+ #include <iomanip>
15
+ #include <sstream>
16
+ #include <cstring>
17
+ #include <cstdlib>
18
+
19
+ // Optional compression support
20
+ #ifdef LIBRATS_ENABLE_ZLIB
21
+ #include <zlib.h>
22
+ #endif
23
+
24
+ #ifdef LIBRATS_ENABLE_LZ4
25
+ #include <lz4.h>
26
+ #endif
27
+
28
+ namespace librats {
29
+
30
+ //=============================================================================
31
+ // DirectoryMetadata Implementation
32
+ //=============================================================================
33
+
34
+ uint64_t DirectoryMetadata::get_total_size() const {
35
+ uint64_t total = 0;
36
+
37
+ // Add size of files in this directory
38
+ for (const auto& file : files) {
39
+ total += file.file_size;
40
+ }
41
+
42
+ // Add size of subdirectories recursively
43
+ for (const auto& subdir : subdirectories) {
44
+ total += subdir.get_total_size();
45
+ }
46
+
47
+ return total;
48
+ }
49
+
50
+ size_t DirectoryMetadata::get_total_file_count() const {
51
+ size_t count = files.size();
52
+
53
+ // Add files from subdirectories recursively
54
+ for (const auto& subdir : subdirectories) {
55
+ count += subdir.get_total_file_count();
56
+ }
57
+
58
+ return count;
59
+ }
60
+
61
+ //=============================================================================
62
+ // FileTransferProgress Implementation
63
+ //=============================================================================
64
+
65
+ void FileTransferProgress::update_transfer_rates(uint64_t new_bytes_transferred) {
66
+ auto now = std::chrono::steady_clock::now();
67
+ auto time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update);
68
+
69
+ if (time_diff.count() > 0) {
70
+ uint64_t bytes_diff = new_bytes_transferred - bytes_transferred;
71
+ transfer_rate_bps = (static_cast<double>(bytes_diff) * 1000.0) / time_diff.count();
72
+
73
+ // Calculate average rate since start
74
+ auto total_time = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
75
+ if (total_time.count() > 0) {
76
+ average_rate_bps = (static_cast<double>(new_bytes_transferred) * 1000.0) / total_time.count();
77
+ }
78
+
79
+ // Estimate time remaining
80
+ if (transfer_rate_bps > 0 && total_bytes > new_bytes_transferred) {
81
+ uint64_t remaining_bytes = total_bytes - new_bytes_transferred;
82
+ estimated_time_remaining = std::chrono::milliseconds(
83
+ static_cast<int64_t>((remaining_bytes * 1000.0) / transfer_rate_bps)
84
+ );
85
+ } else {
86
+ estimated_time_remaining = std::chrono::milliseconds(0);
87
+ }
88
+ }
89
+
90
+ bytes_transferred = new_bytes_transferred;
91
+ last_update = now;
92
+ }
93
+
94
+ //=============================================================================
95
+ // FileTransferManager Implementation
96
+ //=============================================================================
97
+
98
+ FileTransferManager::FileTransferManager(RatsClient& client, const FileTransferConfig& config)
99
+ : client_(client), config_(config), running_(true),
100
+ total_bytes_sent_(0), total_bytes_received_(0),
101
+ total_files_sent_(0), total_files_received_(0) {
102
+
103
+ start_time_ = std::chrono::steady_clock::now();
104
+ initialize();
105
+ }
106
+
107
+ FileTransferManager::~FileTransferManager() {
108
+ shutdown();
109
+ }
110
+
111
+ void FileTransferManager::initialize() {
112
+ // Ensure temp directory exists
113
+ create_directories(config_.temp_directory.c_str());
114
+
115
+ // Register message handlers with RatsClient
116
+ client_.on("file_transfer_request", [this](const std::string& peer_id, const nlohmann::json& data) {
117
+ handle_transfer_request(peer_id, data);
118
+ });
119
+
120
+ client_.on("file_transfer_response", [this](const std::string& peer_id, const nlohmann::json& data) {
121
+ handle_transfer_response(peer_id, data);
122
+ });
123
+
124
+ client_.on("file_chunk_metadata", [this](const std::string& peer_id, const nlohmann::json& data) {
125
+ handle_chunk_metadata_message(peer_id, data);
126
+ });
127
+
128
+ // Note: Binary chunk data will be handled through the global binary_data_callback_
129
+ // The FileTransferManager will check for file chunk magic headers in the callback
130
+
131
+ client_.on("file_chunk_ack", [this](const std::string& peer_id, const nlohmann::json& data) {
132
+ handle_chunk_ack_message(peer_id, data);
133
+ });
134
+
135
+ client_.on("file_transfer_control", [this](const std::string& peer_id, const nlohmann::json& data) {
136
+ handle_transfer_control(peer_id, data);
137
+ });
138
+
139
+ client_.on("file_request", [this](const std::string& peer_id, const nlohmann::json& data) {
140
+ handle_file_request(peer_id, data);
141
+ });
142
+
143
+ client_.on("directory_request", [this](const std::string& peer_id, const nlohmann::json& data) {
144
+ handle_directory_request(peer_id, data);
145
+ });
146
+
147
+ // Start worker threads
148
+ for (uint32_t i = 0; i < config_.max_concurrent_chunks; ++i) {
149
+ worker_threads_.emplace_back(&FileTransferManager::worker_thread_loop, this);
150
+ }
151
+
152
+ // Start cleanup thread for pending chunks timeout
153
+ worker_threads_.emplace_back(&FileTransferManager::cleanup_thread_loop, this);
154
+
155
+ LOG_FILE_TRANSFER_INFO("FileTransferManager initialized with " << worker_threads_.size() << " worker threads");
156
+ }
157
+
158
+ void FileTransferManager::shutdown() {
159
+ LOG_FILE_TRANSFER_INFO("FileTransferManager stopping...");
160
+ running_.store(false);
161
+
162
+ // Notify all condition variables to wake up waiting threads immediately
163
+ work_condition_.notify_all();
164
+ cleanup_condition_.notify_all();
165
+ throttle_condition_.notify_all();
166
+
167
+ // Join all worker threads
168
+ for (auto& thread : worker_threads_) {
169
+ if (thread.joinable()) {
170
+ thread.join();
171
+ }
172
+ }
173
+
174
+ worker_threads_.clear();
175
+ LOG_FILE_TRANSFER_INFO("FileTransferManager stopped");
176
+ }
177
+
178
+ void FileTransferManager::worker_thread_loop() {
179
+ while (running_.load()) {
180
+ std::unique_lock<std::mutex> lock(work_mutex_);
181
+ work_condition_.wait(lock, [this] { return !work_queue_.empty() || !running_.load(); });
182
+
183
+ if (!running_.load()) {
184
+ break;
185
+ }
186
+
187
+ if (!work_queue_.empty()) {
188
+ std::string transfer_id = work_queue_.front();
189
+ work_queue_.pop();
190
+ lock.unlock();
191
+
192
+ process_transfer(transfer_id);
193
+ }
194
+ }
195
+ }
196
+
197
+ void FileTransferManager::cleanup_thread_loop() {
198
+ const auto cleanup_interval = std::chrono::seconds(5);
199
+ const auto pending_timeout = std::chrono::seconds(30);
200
+
201
+ while (running_.load()) {
202
+ // Use condition variable with timeout instead of sleep
203
+ std::unique_lock<std::mutex> lock(cleanup_mutex_);
204
+ cleanup_condition_.wait_for(lock, cleanup_interval, [this] { return !running_.load(); });
205
+
206
+ if (!running_.load()) {
207
+ break;
208
+ }
209
+
210
+ // Clean up expired pending chunks
211
+ auto now = std::chrono::steady_clock::now();
212
+ std::vector<std::string> expired_peers;
213
+
214
+ {
215
+ std::lock_guard<std::mutex> lock(chunks_mutex_);
216
+ for (auto it = pending_chunks_.begin(); it != pending_chunks_.end();) {
217
+ if (now - it->second.created_at > pending_timeout) {
218
+ LOG_FILE_TRANSFER_WARN("Pending chunk from peer " << it->first <<
219
+ " timed out (transfer: " << it->second.transfer_id <<
220
+ ", chunk: " << it->second.chunk_index << ")");
221
+ expired_peers.push_back(it->first);
222
+ it = pending_chunks_.erase(it);
223
+ } else {
224
+ ++it;
225
+ }
226
+ }
227
+ }
228
+
229
+ // Send negative acknowledgments for expired chunks
230
+ for (const auto& peer_id : expired_peers) {
231
+ // We don't have direct access to the transfer info here, but the timeout logged above
232
+ // provides sufficient information for debugging
233
+ LOG_FILE_TRANSFER_DEBUG("Cleaned up " << expired_peers.size() << " expired pending chunks");
234
+ }
235
+ }
236
+ }
237
+
238
+ void FileTransferManager::set_config(const FileTransferConfig& config) {
239
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
240
+ config_ = config;
241
+
242
+ // Ensure temp directory exists
243
+ create_directories(config_.temp_directory.c_str());
244
+ }
245
+
246
+ const FileTransferConfig& FileTransferManager::get_config() const {
247
+ return config_;
248
+ }
249
+
250
+ bool FileTransferManager::handle_binary_data(const std::string& peer_id, const std::vector<uint8_t>& binary_data) {
251
+ // Check if this is a file chunk binary message
252
+ const std::string magic = "FTCHUNK";
253
+
254
+ if (binary_data.size() >= magic.length() &&
255
+ std::memcmp(binary_data.data(), magic.c_str(), magic.length()) == 0) {
256
+
257
+ // This is a file transfer chunk - handle it
258
+ handle_chunk_binary_message(peer_id, binary_data);
259
+ return true;
260
+ }
261
+
262
+ // Not a file transfer chunk
263
+ return false;
264
+ }
265
+
266
+ void FileTransferManager::set_progress_callback(FileTransferProgressCallback callback) {
267
+ progress_callback_ = callback;
268
+ }
269
+
270
+ void FileTransferManager::set_completion_callback(FileTransferCompletedCallback callback) {
271
+ completion_callback_ = callback;
272
+ }
273
+
274
+ void FileTransferManager::set_request_callback(FileTransferRequestCallback callback) {
275
+ request_callback_ = callback;
276
+ }
277
+
278
+ void FileTransferManager::set_directory_progress_callback(DirectoryTransferProgressCallback callback) {
279
+ directory_progress_callback_ = callback;
280
+ }
281
+
282
+ void FileTransferManager::set_file_request_callback(FileRequestCallback callback) {
283
+ file_request_callback_ = callback;
284
+ }
285
+
286
+ void FileTransferManager::set_directory_request_callback(DirectoryRequestCallback callback) {
287
+ directory_request_callback_ = callback;
288
+ }
289
+
290
+ std::string FileTransferManager::send_file(const std::string& peer_id, const std::string& file_path,
291
+ const std::string& remote_filename) {
292
+ // Validate file
293
+ if (!validate_file_path(file_path, false)) {
294
+ LOG_FILE_TRANSFER_ERROR("Invalid file path: " << file_path);
295
+ return "";
296
+ }
297
+
298
+ // Get file metadata
299
+ FileMetadata metadata = get_file_metadata(file_path);
300
+ if (metadata.file_size == 0) {
301
+ LOG_FILE_TRANSFER_ERROR("Failed to get metadata for file: " << file_path);
302
+ return "";
303
+ }
304
+
305
+ // Use custom filename if provided
306
+ if (!remote_filename.empty()) {
307
+ metadata.filename = remote_filename;
308
+ }
309
+
310
+ return send_file_with_metadata(peer_id, file_path, metadata);
311
+ }
312
+
313
+ std::string FileTransferManager::send_file_with_metadata(const std::string& peer_id, const std::string& file_path,
314
+ const FileMetadata& metadata) {
315
+ std::string transfer_id = generate_transfer_id();
316
+
317
+ // Create transfer progress tracking
318
+ auto progress = std::make_shared<FileTransferProgress>();
319
+ progress->transfer_id = transfer_id;
320
+ progress->peer_id = peer_id;
321
+ progress->direction = FileTransferDirection::SENDING;
322
+ progress->status = FileTransferStatus::STARTING;
323
+ progress->filename = metadata.filename;
324
+ progress->local_path = file_path;
325
+ progress->file_size = metadata.file_size;
326
+ progress->total_bytes = metadata.file_size;
327
+ progress->total_chunks = (metadata.file_size + config_.chunk_size - 1) / config_.chunk_size;
328
+
329
+ {
330
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
331
+ active_transfers_[transfer_id] = progress;
332
+ }
333
+
334
+ // Send transfer request to peer
335
+ nlohmann::json request_msg = create_transfer_request_message(metadata, transfer_id);
336
+ client_.send(peer_id, "file_transfer_request", request_msg);
337
+
338
+ LOG_FILE_TRANSFER_INFO("Initiated file transfer request: " << transfer_id << " (" << metadata.filename << " -> " << peer_id << ")");
339
+ return transfer_id;
340
+ }
341
+
342
+ std::string FileTransferManager::send_directory(const std::string& peer_id, const std::string& directory_path,
343
+ const std::string& remote_directory_name, bool recursive) {
344
+ // Validate directory
345
+ if (!directory_exists(directory_path)) {
346
+ LOG_FILE_TRANSFER_ERROR("Invalid directory path: " << directory_path);
347
+ return "";
348
+ }
349
+
350
+ // Get directory metadata
351
+ DirectoryMetadata dir_metadata = get_directory_metadata(directory_path, recursive);
352
+ if (dir_metadata.files.empty() && dir_metadata.subdirectories.empty()) {
353
+ LOG_FILE_TRANSFER_ERROR("Directory is empty: " << directory_path);
354
+ return "";
355
+ }
356
+
357
+ // Use custom directory name if provided
358
+ if (!remote_directory_name.empty()) {
359
+ dir_metadata.directory_name = remote_directory_name;
360
+ }
361
+
362
+ std::string transfer_id = generate_transfer_id();
363
+
364
+ // Create transfer progress tracking for directory
365
+ auto progress = std::make_shared<FileTransferProgress>();
366
+ progress->transfer_id = transfer_id;
367
+ progress->peer_id = peer_id;
368
+ progress->direction = FileTransferDirection::SENDING;
369
+ progress->status = FileTransferStatus::STARTING;
370
+ progress->filename = dir_metadata.directory_name;
371
+ progress->local_path = directory_path;
372
+ progress->total_bytes = dir_metadata.get_total_size();
373
+ progress->file_size = progress->total_bytes;
374
+
375
+ {
376
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
377
+ active_transfers_[transfer_id] = progress;
378
+ }
379
+
380
+ // Store directory metadata for processing
381
+ {
382
+ std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
383
+ active_directory_transfers_[transfer_id] = dir_metadata;
384
+ }
385
+
386
+ // Create directory transfer request message
387
+ nlohmann::json request_msg;
388
+ request_msg["transfer_id"] = transfer_id;
389
+ request_msg["type"] = "directory";
390
+
391
+ // Serialize directory metadata with full file information
392
+ nlohmann::json dir_metadata_json;
393
+ dir_metadata_json["directory_name"] = dir_metadata.directory_name;
394
+ dir_metadata_json["relative_path"] = dir_metadata.relative_path;
395
+ dir_metadata_json["total_size"] = dir_metadata.get_total_size();
396
+ dir_metadata_json["total_files"] = dir_metadata.get_total_file_count();
397
+
398
+ // Serialize file metadata
399
+ nlohmann::json files_json = nlohmann::json::array();
400
+ for (const auto& file_meta : dir_metadata.files) {
401
+ nlohmann::json file_json;
402
+ file_json["filename"] = file_meta.filename;
403
+ file_json["relative_path"] = file_meta.relative_path;
404
+ file_json["file_size"] = file_meta.file_size;
405
+ file_json["last_modified"] = file_meta.last_modified;
406
+ file_json["mime_type"] = file_meta.mime_type;
407
+ file_json["checksum"] = file_meta.checksum;
408
+ files_json.push_back(file_json);
409
+ }
410
+ dir_metadata_json["files"] = files_json;
411
+
412
+ // For now, we'll handle single-level directories (can be extended for nested later)
413
+ dir_metadata_json["subdirectories"] = nlohmann::json::array();
414
+
415
+ request_msg["directory_metadata"] = dir_metadata_json;
416
+
417
+ client_.send(peer_id, "file_transfer_request", request_msg);
418
+
419
+ LOG_FILE_TRANSFER_INFO("Initiated directory transfer request: " << transfer_id << " (" << dir_metadata.directory_name << " -> " << peer_id << ")");
420
+ return transfer_id;
421
+ }
422
+
423
+ std::string FileTransferManager::request_file(const std::string& peer_id, const std::string& remote_file_path,
424
+ const std::string& local_path) {
425
+ std::string transfer_id = generate_transfer_id();
426
+
427
+ // Create file request message
428
+ nlohmann::json request_msg;
429
+ request_msg["transfer_id"] = transfer_id;
430
+ request_msg["type"] = "file_request";
431
+ request_msg["remote_path"] = remote_file_path;
432
+ request_msg["local_path"] = local_path;
433
+
434
+ client_.send(peer_id, "file_request", request_msg);
435
+
436
+ LOG_FILE_TRANSFER_INFO("Sent file request: " << transfer_id << " (" << remote_file_path << " from " << peer_id << ")");
437
+ return transfer_id;
438
+ }
439
+
440
+ std::string FileTransferManager::request_directory(const std::string& peer_id, const std::string& remote_directory_path,
441
+ const std::string& local_directory_path, bool recursive) {
442
+ std::string transfer_id = generate_transfer_id();
443
+
444
+ // Create directory request message
445
+ nlohmann::json request_msg;
446
+ request_msg["transfer_id"] = transfer_id;
447
+ request_msg["type"] = "directory_request";
448
+ request_msg["remote_path"] = remote_directory_path;
449
+ request_msg["local_path"] = local_directory_path;
450
+ request_msg["recursive"] = recursive;
451
+
452
+ client_.send(peer_id, "directory_request", request_msg);
453
+
454
+ LOG_FILE_TRANSFER_INFO("Sent directory request: " << transfer_id << " (" << remote_directory_path << " from " << peer_id << ")");
455
+ return transfer_id;
456
+ }
457
+
458
+ bool FileTransferManager::accept_file_transfer(const std::string& transfer_id, const std::string& local_path) {
459
+ std::lock_guard<std::mutex> lock(pending_mutex_);
460
+
461
+ auto it = pending_transfers_.find(transfer_id);
462
+ if (it == pending_transfers_.end()) {
463
+ LOG_FILE_TRANSFER_ERROR("Transfer not found in pending transfers: " << transfer_id);
464
+ return false;
465
+ }
466
+
467
+ PendingFileTransfer pending_transfer = it->second;
468
+ pending_transfers_.erase(it);
469
+
470
+ // Create transfer progress tracking
471
+ auto progress = std::make_shared<FileTransferProgress>();
472
+ progress->transfer_id = transfer_id;
473
+ progress->peer_id = pending_transfer.peer_id;
474
+ progress->direction = FileTransferDirection::RECEIVING;
475
+ progress->status = FileTransferStatus::STARTING;
476
+ progress->filename = pending_transfer.metadata.filename;
477
+ progress->local_path = local_path;
478
+ progress->file_size = pending_transfer.metadata.file_size;
479
+ progress->total_bytes = pending_transfer.metadata.file_size;
480
+ progress->total_chunks = (pending_transfer.metadata.file_size + config_.chunk_size - 1) / config_.chunk_size;
481
+
482
+ {
483
+ std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
484
+ active_transfers_[transfer_id] = progress;
485
+ }
486
+
487
+ // Send acceptance response
488
+ nlohmann::json response_msg = create_transfer_response_message(transfer_id, true);
489
+ client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
490
+
491
+ // Add to work queue for processing
492
+ {
493
+ std::lock_guard<std::mutex> work_lock(work_mutex_);
494
+ work_queue_.push(transfer_id);
495
+ }
496
+ work_condition_.notify_one();
497
+
498
+ LOG_FILE_TRANSFER_INFO("Accepted file transfer: " << transfer_id << " (" << pending_transfer.metadata.filename << " from " << pending_transfer.peer_id << " -> " << local_path << ")");
499
+ return true;
500
+ }
501
+
502
+ bool FileTransferManager::reject_file_transfer(const std::string& transfer_id, const std::string& reason) {
503
+ std::lock_guard<std::mutex> lock(pending_mutex_);
504
+
505
+ auto it = pending_transfers_.find(transfer_id);
506
+ if (it == pending_transfers_.end()) {
507
+ LOG_FILE_TRANSFER_ERROR("Transfer not found in pending transfers: " << transfer_id);
508
+ return false;
509
+ }
510
+
511
+ PendingFileTransfer pending_transfer = it->second;
512
+ pending_transfers_.erase(it);
513
+
514
+ // Send rejection response
515
+ nlohmann::json response_msg = create_transfer_response_message(transfer_id, false, reason);
516
+ client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
517
+
518
+ LOG_FILE_TRANSFER_INFO("Rejected file transfer: " << transfer_id << " (" << pending_transfer.metadata.filename << " from " << pending_transfer.peer_id << ", reason: " << reason << ")");
519
+ return true;
520
+ }
521
+
522
+ bool FileTransferManager::accept_directory_transfer(const std::string& transfer_id, const std::string& local_path) {
523
+ std::lock_guard<std::mutex> lock(pending_mutex_);
524
+
525
+ auto it = pending_directory_transfers_.find(transfer_id);
526
+ if (it == pending_directory_transfers_.end()) {
527
+ LOG_FILE_TRANSFER_ERROR("Directory transfer not found in pending transfers: " << transfer_id);
528
+ return false;
529
+ }
530
+
531
+ PendingDirectoryTransfer pending_transfer = it->second;
532
+ pending_directory_transfers_.erase(it);
533
+
534
+ // Create transfer progress tracking
535
+ auto progress = std::make_shared<FileTransferProgress>();
536
+ progress->transfer_id = transfer_id;
537
+ progress->peer_id = pending_transfer.peer_id;
538
+ progress->direction = FileTransferDirection::RECEIVING;
539
+ progress->status = FileTransferStatus::STARTING;
540
+ progress->filename = pending_transfer.metadata.directory_name;
541
+ progress->local_path = local_path;
542
+ progress->total_bytes = pending_transfer.metadata.get_total_size();
543
+ progress->file_size = progress->total_bytes;
544
+ progress->total_chunks = 0; // Will be calculated per file
545
+
546
+ {
547
+ std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
548
+ active_transfers_[transfer_id] = progress;
549
+ }
550
+
551
+ // Send acceptance response
552
+ nlohmann::json response_msg = create_transfer_response_message(transfer_id, true);
553
+ client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
554
+
555
+ // Store directory metadata for processing
556
+ {
557
+ std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
558
+ active_directory_transfers_[transfer_id] = pending_transfer.metadata;
559
+ }
560
+
561
+ // Add to work queue for processing
562
+ {
563
+ std::lock_guard<std::mutex> work_lock(work_mutex_);
564
+ work_queue_.push(transfer_id);
565
+ }
566
+ work_condition_.notify_one();
567
+
568
+ LOG_FILE_TRANSFER_INFO("Accepted directory transfer: " << transfer_id << " (" << pending_transfer.metadata.directory_name << " from " << pending_transfer.peer_id << " -> " << local_path << ")");
569
+ return true;
570
+ }
571
+
572
+ bool FileTransferManager::reject_directory_transfer(const std::string& transfer_id, const std::string& reason) {
573
+ std::lock_guard<std::mutex> lock(pending_mutex_);
574
+
575
+ auto it = pending_directory_transfers_.find(transfer_id);
576
+ if (it == pending_directory_transfers_.end()) {
577
+ LOG_FILE_TRANSFER_ERROR("Directory transfer not found in pending transfers: " << transfer_id);
578
+ return false;
579
+ }
580
+
581
+ PendingDirectoryTransfer pending_transfer = it->second;
582
+ pending_directory_transfers_.erase(it);
583
+
584
+ // Send rejection response
585
+ nlohmann::json response_msg = create_transfer_response_message(transfer_id, false, reason);
586
+ client_.send(pending_transfer.peer_id, "file_transfer_response", response_msg);
587
+
588
+ LOG_FILE_TRANSFER_INFO("Rejected directory transfer: " << transfer_id << " (" << pending_transfer.metadata.directory_name << " from " << pending_transfer.peer_id << ", reason: " << reason << ")");
589
+ return true;
590
+ }
591
+
592
+ bool FileTransferManager::pause_transfer(const std::string& transfer_id) {
593
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
594
+
595
+ auto it = active_transfers_.find(transfer_id);
596
+ if (it == active_transfers_.end()) {
597
+ return false;
598
+ }
599
+
600
+ auto& progress = it->second;
601
+ if (progress->status == FileTransferStatus::IN_PROGRESS) {
602
+ progress->status = FileTransferStatus::PAUSED;
603
+
604
+ // Send pause control message
605
+ nlohmann::json control_msg = create_control_message(transfer_id, "pause");
606
+ client_.send(progress->peer_id, "file_transfer_control", control_msg);
607
+
608
+ LOG_FILE_TRANSFER_INFO("Paused transfer: " << transfer_id);
609
+ return true;
610
+ }
611
+
612
+ return false;
613
+ }
614
+
615
+ bool FileTransferManager::resume_transfer(const std::string& transfer_id) {
616
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
617
+
618
+ auto it = active_transfers_.find(transfer_id);
619
+ if (it == active_transfers_.end()) {
620
+ return false;
621
+ }
622
+
623
+ auto& progress = it->second;
624
+ if (progress->status == FileTransferStatus::PAUSED) {
625
+ progress->status = FileTransferStatus::RESUMING;
626
+
627
+ // Add to work queue for processing
628
+ {
629
+ std::lock_guard<std::mutex> work_lock(work_mutex_);
630
+ work_queue_.push(transfer_id);
631
+ }
632
+ work_condition_.notify_one();
633
+
634
+ // Send resume control message
635
+ nlohmann::json control_msg = create_control_message(transfer_id, "resume");
636
+ client_.send(progress->peer_id, "file_transfer_control", control_msg);
637
+
638
+ LOG_FILE_TRANSFER_INFO("Resumed transfer: " << transfer_id);
639
+ return true;
640
+ }
641
+
642
+ return false;
643
+ }
644
+
645
+ bool FileTransferManager::cancel_transfer(const std::string& transfer_id) {
646
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
647
+
648
+ auto it = active_transfers_.find(transfer_id);
649
+ if (it == active_transfers_.end()) {
650
+ return false;
651
+ }
652
+
653
+ auto& progress = it->second;
654
+ progress->status = FileTransferStatus::CANCELLED;
655
+
656
+ // Send cancel control message
657
+ nlohmann::json control_msg = create_control_message(transfer_id, "cancel");
658
+ client_.send(progress->peer_id, "file_transfer_control", control_msg);
659
+
660
+ // Move to completed transfers
661
+ move_to_completed(transfer_id);
662
+
663
+ LOG_FILE_TRANSFER_INFO("Cancelled transfer: " << transfer_id);
664
+ return true;
665
+ }
666
+
667
+ bool FileTransferManager::retry_transfer(const std::string& transfer_id) {
668
+ // Look in completed transfers for failed ones
669
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
670
+
671
+ auto it = completed_transfers_.find(transfer_id);
672
+ if (it == completed_transfers_.end()) {
673
+ return false;
674
+ }
675
+
676
+ auto progress = it->second;
677
+ if (progress->status != FileTransferStatus::FAILED) {
678
+ return false;
679
+ }
680
+
681
+ // Reset progress and move back to active
682
+ progress->status = FileTransferStatus::STARTING;
683
+ progress->bytes_transferred = 0;
684
+ progress->chunks_completed = 0;
685
+ progress->retry_count++;
686
+ progress->error_message.clear();
687
+ progress->start_time = std::chrono::steady_clock::now();
688
+
689
+ active_transfers_[transfer_id] = progress;
690
+ completed_transfers_.erase(it);
691
+
692
+ // Add to work queue
693
+ {
694
+ std::lock_guard<std::mutex> work_lock(work_mutex_);
695
+ work_queue_.push(transfer_id);
696
+ }
697
+ work_condition_.notify_one();
698
+
699
+ LOG_FILE_TRANSFER_INFO("Retrying transfer: " << transfer_id);
700
+ return true;
701
+ }
702
+
703
+ std::shared_ptr<FileTransferProgress> FileTransferManager::get_transfer_progress(const std::string& transfer_id) const {
704
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
705
+
706
+ auto it = active_transfers_.find(transfer_id);
707
+ if (it != active_transfers_.end()) {
708
+ return it->second;
709
+ }
710
+
711
+ auto completed_it = completed_transfers_.find(transfer_id);
712
+ if (completed_it != completed_transfers_.end()) {
713
+ return completed_it->second;
714
+ }
715
+
716
+ return nullptr;
717
+ }
718
+
719
+ std::vector<std::shared_ptr<FileTransferProgress>> FileTransferManager::get_active_transfers() const {
720
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
721
+
722
+ std::vector<std::shared_ptr<FileTransferProgress>> transfers;
723
+ transfers.reserve(active_transfers_.size());
724
+
725
+ for (const auto& pair : active_transfers_) {
726
+ transfers.push_back(pair.second);
727
+ }
728
+
729
+ return transfers;
730
+ }
731
+
732
+ std::vector<std::shared_ptr<FileTransferProgress>> FileTransferManager::get_transfer_history(size_t limit) const {
733
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
734
+
735
+ std::vector<std::shared_ptr<FileTransferProgress>> transfers;
736
+
737
+ for (const auto& pair : completed_transfers_) {
738
+ transfers.push_back(pair.second);
739
+ }
740
+
741
+ // Sort by completion time (most recent first)
742
+ std::sort(transfers.begin(), transfers.end(),
743
+ [](const auto& a, const auto& b) {
744
+ return a->last_update > b->last_update;
745
+ });
746
+
747
+ if (limit > 0 && transfers.size() > limit) {
748
+ transfers.resize(limit);
749
+ }
750
+
751
+ return transfers;
752
+ }
753
+
754
+ void FileTransferManager::clear_transfer_history() {
755
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
756
+ completed_transfers_.clear();
757
+ LOG_FILE_TRANSFER_INFO("Cleared transfer history");
758
+ }
759
+
760
+ nlohmann::json FileTransferManager::get_transfer_statistics() const {
761
+ std::lock_guard<std::mutex> stats_lock(stats_mutex_);
762
+ std::lock_guard<std::mutex> transfers_lock(transfers_mutex_);
763
+
764
+ auto now = std::chrono::steady_clock::now();
765
+ auto uptime = std::chrono::duration_cast<std::chrono::seconds>(now - start_time_);
766
+
767
+ nlohmann::json stats;
768
+ stats["uptime_seconds"] = uptime.count();
769
+ stats["total_bytes_sent"] = total_bytes_sent_;
770
+ stats["total_bytes_received"] = total_bytes_received_;
771
+ stats["total_files_sent"] = total_files_sent_;
772
+ stats["total_files_received"] = total_files_received_;
773
+ stats["active_transfers"] = active_transfers_.size();
774
+ stats["completed_transfers"] = completed_transfers_.size();
775
+
776
+ // Calculate success rate
777
+ size_t successful_transfers = 0;
778
+ for (const auto& pair : completed_transfers_) {
779
+ if (pair.second->status == FileTransferStatus::COMPLETED) {
780
+ successful_transfers++;
781
+ }
782
+ }
783
+
784
+ if (!completed_transfers_.empty()) {
785
+ stats["success_rate"] = static_cast<double>(successful_transfers) / completed_transfers_.size();
786
+ } else {
787
+ stats["success_rate"] = 0.0;
788
+ }
789
+
790
+ // Average transfer rate
791
+ if (uptime.count() > 0) {
792
+ stats["average_send_rate_bps"] = static_cast<double>(total_bytes_sent_) / uptime.count();
793
+ stats["average_receive_rate_bps"] = static_cast<double>(total_bytes_received_) / uptime.count();
794
+ } else {
795
+ stats["average_send_rate_bps"] = 0.0;
796
+ stats["average_receive_rate_bps"] = 0.0;
797
+ }
798
+
799
+ return stats;
800
+ }
801
+
802
+ // Static utility functions
803
+ std::string FileTransferManager::calculate_file_checksum(const std::string& file_path, const std::string& algorithm) {
804
+ if (algorithm == "sha256") {
805
+ // Use a simple SHA1 implementation for now (we can extend this)
806
+ size_t file_size;
807
+ void* file_data = read_file_binary(file_path.c_str(), &file_size);
808
+ if (!file_data) {
809
+ return "";
810
+ }
811
+
812
+ SHA1 sha1;
813
+ sha1.update(reinterpret_cast<const uint8_t*>(file_data), file_size);
814
+ std::string result = sha1.finalize();
815
+
816
+ free_file_buffer(file_data);
817
+ return result;
818
+ }
819
+
820
+ // For MD5 or other algorithms, we would need additional implementations
821
+ return "";
822
+ }
823
+
824
+ FileMetadata FileTransferManager::get_file_metadata(const std::string& file_path) {
825
+ FileMetadata metadata;
826
+
827
+ try {
828
+ if (!file_or_directory_exists(file_path.c_str())) {
829
+ return metadata;
830
+ }
831
+
832
+ metadata.filename = get_filename_from_path(file_path.c_str());
833
+ int64_t file_size = get_file_size(file_path.c_str());
834
+ if (file_size >= 0) {
835
+ metadata.file_size = static_cast<uint64_t>(file_size);
836
+ }
837
+
838
+ // Get last modification time
839
+ metadata.last_modified = get_file_modified_time(file_path.c_str());
840
+
841
+ // Calculate checksum (optional, can be expensive for large files)
842
+ if (metadata.file_size < 100 * 1024 * 1024) { // Only for files < 100MB
843
+ metadata.checksum = calculate_file_checksum(file_path, "sha256");
844
+ }
845
+
846
+ // Determine MIME type based on extension
847
+ metadata.mime_type = get_mime_type(file_path);
848
+
849
+ } catch (const std::exception& e) {
850
+ LOG_FILE_TRANSFER_ERROR("Failed to get file metadata for " << file_path << ": " << e.what());
851
+ }
852
+
853
+ return metadata;
854
+ }
855
+
856
+ DirectoryMetadata FileTransferManager::get_directory_metadata(const std::string& directory_path, bool recursive) {
857
+ DirectoryMetadata metadata;
858
+
859
+ try {
860
+ metadata.directory_name = get_filename_from_path(directory_path.c_str());
861
+
862
+ std::vector<DirectoryEntry> entries;
863
+ if (list_directory(directory_path.c_str(), entries)) {
864
+ for (const auto& entry : entries) {
865
+ if (!entry.is_directory) {
866
+ FileMetadata file_meta = get_file_metadata(entry.path);
867
+ file_meta.relative_path = entry.name;
868
+ metadata.files.push_back(file_meta);
869
+ }
870
+ else if (recursive) {
871
+ DirectoryMetadata subdir_meta = get_directory_metadata(entry.path, true);
872
+ subdir_meta.relative_path = entry.name;
873
+ metadata.subdirectories.push_back(subdir_meta);
874
+ }
875
+ }
876
+ }
877
+
878
+ } catch (const std::exception& e) {
879
+ LOG_FILE_TRANSFER_ERROR("Failed to get directory metadata for " << directory_path << ": " << e.what());
880
+ }
881
+
882
+ return metadata;
883
+ }
884
+
885
+ bool FileTransferManager::validate_file_path(const std::string& file_path, bool check_write) {
886
+ return validate_path(file_path.c_str(), check_write);
887
+ }
888
+
889
+ // Private implementation methods
890
+
891
+ std::string FileTransferManager::generate_transfer_id() const {
892
+ std::random_device rd;
893
+ std::mt19937 gen(rd());
894
+ std::uniform_int_distribution<> dis(0, 15);
895
+
896
+ std::stringstream ss;
897
+ ss << std::hex;
898
+ for (int i = 0; i < 32; ++i) {
899
+ ss << dis(gen);
900
+ }
901
+
902
+ return ss.str();
903
+ }
904
+
905
+ void FileTransferManager::process_transfer(const std::string& transfer_id) {
906
+ // Exit immediately if shutting down
907
+ if (!running_.load()) {
908
+ return;
909
+ }
910
+
911
+ auto progress = get_transfer_progress(transfer_id);
912
+ if (!progress) {
913
+ return;
914
+ }
915
+
916
+ // Check again before processing (in case shutdown occurred during progress retrieval)
917
+ if (!running_.load()) {
918
+ return;
919
+ }
920
+
921
+ // Check if this is a directory transfer
922
+ bool is_directory_transfer = false;
923
+ {
924
+ std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
925
+ is_directory_transfer = active_directory_transfers_.find(transfer_id) != active_directory_transfers_.end();
926
+ }
927
+
928
+ if (is_directory_transfer) {
929
+ if (progress->direction == FileTransferDirection::SENDING) {
930
+ start_directory_send(transfer_id);
931
+ } else {
932
+ start_directory_receive(transfer_id);
933
+ }
934
+ } else {
935
+ if (progress->direction == FileTransferDirection::SENDING) {
936
+ start_file_send(transfer_id);
937
+ } else {
938
+ start_file_receive(transfer_id);
939
+ }
940
+ }
941
+ }
942
+
943
+ void FileTransferManager::start_file_send(const std::string& transfer_id) {
944
+ auto progress = get_transfer_progress(transfer_id);
945
+ if (!progress) {
946
+ return;
947
+ }
948
+
949
+ progress->status = FileTransferStatus::IN_PROGRESS;
950
+ update_transfer_progress(transfer_id);
951
+
952
+ // Read file and create chunks
953
+ uint64_t chunk_index = 0;
954
+ uint64_t file_offset = 0;
955
+
956
+ while (file_offset < progress->file_size && running_.load()) {
957
+ // Check if transfer was cancelled or paused
958
+ auto current_progress = get_transfer_progress(transfer_id);
959
+ if (!current_progress || current_progress->status == FileTransferStatus::CANCELLED ||
960
+ current_progress->status == FileTransferStatus::PAUSED) {
961
+ return;
962
+ }
963
+
964
+ uint32_t chunk_size = (std::min)(static_cast<uint64_t>(config_.chunk_size),
965
+ progress->file_size - file_offset);
966
+
967
+ FileChunk chunk;
968
+ chunk.transfer_id = transfer_id;
969
+ chunk.chunk_index = chunk_index;
970
+ chunk.total_chunks = progress->total_chunks;
971
+ chunk.chunk_size = chunk_size;
972
+ chunk.file_offset = file_offset;
973
+ chunk.data.resize(chunk_size);
974
+
975
+ // Read chunk data
976
+ if (!read_file_chunk(progress->local_path.c_str(), file_offset, chunk.data.data(), chunk_size)) {
977
+ complete_transfer(transfer_id, false, "Failed to read complete chunk from file");
978
+ return;
979
+ }
980
+
981
+ // Calculate checksum
982
+ if (config_.verify_checksums) {
983
+ chunk.checksum = calculate_chunk_checksum(chunk.data);
984
+ }
985
+
986
+ // Note: Compression removed as requested - only binary chunks needed
987
+
988
+ // Send chunk metadata first
989
+ nlohmann::json metadata_msg = create_chunk_metadata_message(chunk);
990
+ client_.send(progress->peer_id, "file_chunk_metadata", metadata_msg);
991
+
992
+ // Send chunk binary data
993
+ std::vector<uint8_t> binary_msg = create_chunk_binary_message(chunk);
994
+ client_.send_binary_to_peer_id(progress->peer_id, binary_msg, MessageDataType::BINARY);
995
+
996
+ chunk_index++;
997
+ file_offset += chunk_size;
998
+
999
+ // Update progress
1000
+ update_transfer_progress(transfer_id, chunk_size);
1001
+
1002
+ // Throttle sending to avoid overwhelming the peer - use condition variable for fast shutdown
1003
+ if (running_.load()) {
1004
+ std::unique_lock<std::mutex> lock(throttle_mutex_);
1005
+ throttle_condition_.wait_for(lock, std::chrono::milliseconds(10), [this] { return !running_.load(); });
1006
+ }
1007
+ }
1008
+
1009
+ LOG_FILE_TRANSFER_INFO("Completed sending file chunks for transfer: " << transfer_id);
1010
+ }
1011
+
1012
+ void FileTransferManager::start_file_receive(const std::string& transfer_id) {
1013
+ // Exit immediately if shutting down
1014
+ if (!running_.load()) {
1015
+ return;
1016
+ }
1017
+
1018
+ auto progress = get_transfer_progress(transfer_id);
1019
+ if (!progress) {
1020
+ return;
1021
+ }
1022
+
1023
+ progress->status = FileTransferStatus::IN_PROGRESS;
1024
+
1025
+ // Create temporary file for receiving
1026
+ if (!create_temp_file(transfer_id, progress->file_size)) {
1027
+ complete_transfer(transfer_id, false, "Failed to create temporary file");
1028
+ return;
1029
+ }
1030
+
1031
+ update_transfer_progress(transfer_id);
1032
+ LOG_FILE_TRANSFER_INFO("Started receiving file transfer: " << transfer_id);
1033
+ }
1034
+
1035
+ void FileTransferManager::start_directory_send(const std::string& transfer_id) {
1036
+ auto progress = get_transfer_progress(transfer_id);
1037
+ if (!progress) {
1038
+ return;
1039
+ }
1040
+
1041
+ DirectoryMetadata dir_metadata;
1042
+ {
1043
+ std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
1044
+ auto it = active_directory_transfers_.find(transfer_id);
1045
+ if (it == active_directory_transfers_.end()) {
1046
+ complete_transfer(transfer_id, false, "Directory metadata not found");
1047
+ return;
1048
+ }
1049
+ dir_metadata = it->second;
1050
+ }
1051
+
1052
+ progress->status = FileTransferStatus::IN_PROGRESS;
1053
+ update_transfer_progress(transfer_id);
1054
+
1055
+ // Send each file in the directory
1056
+ for (const auto& file_metadata : dir_metadata.files) {
1057
+ if (!running_.load()) {
1058
+ return;
1059
+ }
1060
+
1061
+ // Check if transfer was cancelled or paused
1062
+ auto current_progress = get_transfer_progress(transfer_id);
1063
+ if (!current_progress || current_progress->status == FileTransferStatus::CANCELLED ||
1064
+ current_progress->status == FileTransferStatus::PAUSED) {
1065
+ return;
1066
+ }
1067
+
1068
+ std::string full_file_path = combine_paths(progress->local_path, file_metadata.relative_path);
1069
+
1070
+ // Send individual file using existing file transfer logic
1071
+ std::string file_transfer_id = send_file_with_metadata(progress->peer_id, full_file_path, file_metadata);
1072
+
1073
+ if (file_transfer_id.empty()) {
1074
+ complete_transfer(transfer_id, false, "Failed to send file: " + file_metadata.filename);
1075
+ return;
1076
+ }
1077
+
1078
+ // Wait for individual file transfer to complete
1079
+ // Note: In a real implementation, we might want to track multiple concurrent file transfers
1080
+ // For now, we'll send files sequentially
1081
+ }
1082
+
1083
+ LOG_FILE_TRANSFER_INFO("Completed sending directory transfer: " << transfer_id);
1084
+ complete_transfer(transfer_id, true);
1085
+ }
1086
+
1087
+ void FileTransferManager::start_directory_receive(const std::string& transfer_id) {
1088
+ auto progress = get_transfer_progress(transfer_id);
1089
+ if (!progress) {
1090
+ return;
1091
+ }
1092
+
1093
+ DirectoryMetadata dir_metadata;
1094
+ {
1095
+ std::lock_guard<std::mutex> dir_lock(directory_transfers_mutex_);
1096
+ auto it = active_directory_transfers_.find(transfer_id);
1097
+ if (it == active_directory_transfers_.end()) {
1098
+ complete_transfer(transfer_id, false, "Directory metadata not found");
1099
+ return;
1100
+ }
1101
+ dir_metadata = it->second;
1102
+ }
1103
+
1104
+ progress->status = FileTransferStatus::IN_PROGRESS;
1105
+
1106
+ // Create directory structure
1107
+ std::string base_path = progress->local_path;
1108
+ if (!ensure_directory_exists(base_path)) {
1109
+ complete_transfer(transfer_id, false, "Failed to create local directory structure");
1110
+ return;
1111
+ }
1112
+
1113
+ // Create subdirectories if needed
1114
+ for (const auto& file_metadata : dir_metadata.files) {
1115
+ std::string file_dir = combine_paths(base_path, get_parent_directory(file_metadata.relative_path.c_str()));
1116
+ if (!file_dir.empty() && !ensure_directory_exists(file_dir)) {
1117
+ complete_transfer(transfer_id, false, "Failed to create subdirectory: " + file_dir);
1118
+ return;
1119
+ }
1120
+ }
1121
+
1122
+ update_transfer_progress(transfer_id);
1123
+ LOG_FILE_TRANSFER_INFO("Started receiving directory transfer: " << transfer_id);
1124
+
1125
+ // Note: Individual files will be received as separate file transfer requests
1126
+ // The directory transfer completion will be handled when all files are received
1127
+ }
1128
+
1129
+ bool FileTransferManager::create_temp_file(const std::string& transfer_id, uint64_t file_size) {
1130
+ std::string temp_path = get_temp_file_path(transfer_id, config_.temp_directory);
1131
+
1132
+ try {
1133
+ create_directories(config_.temp_directory.c_str());
1134
+
1135
+ // Create file with pre-allocated size
1136
+ if (!create_file_with_size(temp_path.c_str(), file_size)) {
1137
+ LOG_FILE_TRANSFER_ERROR("Failed to create temp file " << temp_path);
1138
+ return false;
1139
+ }
1140
+
1141
+ return true;
1142
+ } catch (const std::exception& e) {
1143
+ LOG_FILE_TRANSFER_ERROR("Failed to create temp file " << temp_path << ": " << e.what());
1144
+ return false;
1145
+ }
1146
+ }
1147
+
1148
+ std::string FileTransferManager::get_temp_file_path(const std::string& transfer_id, const std::string& temp_dir) {
1149
+ return combine_paths(temp_dir, transfer_id + ".tmp");
1150
+ }
1151
+
1152
+ bool FileTransferManager::ensure_directory_exists(const std::string& directory_path) {
1153
+ return create_directories(directory_path.c_str());
1154
+ }
1155
+
1156
+ std::string FileTransferManager::extract_filename(const std::string& file_path) {
1157
+ return get_filename_from_path(file_path);
1158
+ }
1159
+
1160
+ std::string FileTransferManager::get_mime_type(const std::string& file_path) {
1161
+ std::string extension = get_file_extension(file_path);
1162
+ std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
1163
+
1164
+ // Basic MIME type mapping
1165
+ static const std::unordered_map<std::string, std::string> mime_types = {
1166
+ {".txt", "text/plain"},
1167
+ {".json", "application/json"},
1168
+ {".xml", "application/xml"},
1169
+ {".html", "text/html"},
1170
+ {".css", "text/css"},
1171
+ {".js", "application/javascript"},
1172
+ {".pdf", "application/pdf"},
1173
+ {".png", "image/png"},
1174
+ {".jpg", "image/jpeg"},
1175
+ {".jpeg", "image/jpeg"},
1176
+ {".gif", "image/gif"},
1177
+ {".bmp", "image/bmp"},
1178
+ {".svg", "image/svg+xml"},
1179
+ {".mp3", "audio/mpeg"},
1180
+ {".wav", "audio/wav"},
1181
+ {".mp4", "video/mp4"},
1182
+ {".avi", "video/x-msvideo"},
1183
+ {".zip", "application/zip"},
1184
+ {".tar", "application/x-tar"},
1185
+ {".gz", "application/gzip"}
1186
+ };
1187
+
1188
+ auto it = mime_types.find(extension);
1189
+ return (it != mime_types.end()) ? it->second : "application/octet-stream";
1190
+ }
1191
+
1192
+ // Message handling methods
1193
+
1194
+ void FileTransferManager::handle_transfer_request(const std::string& peer_id, const nlohmann::json& message) {
1195
+ try {
1196
+ std::string transfer_id = message["transfer_id"];
1197
+
1198
+ if (message.contains("directory_metadata")) {
1199
+ // Directory transfer request
1200
+ DirectoryMetadata dir_metadata;
1201
+ auto dir_info = message["directory_metadata"];
1202
+ dir_metadata.directory_name = dir_info["directory_name"];
1203
+ dir_metadata.relative_path = dir_info["relative_path"];
1204
+
1205
+ // Parse files and subdirectories from the metadata
1206
+ if (dir_info.contains("files")) {
1207
+ for (const auto& file_json : dir_info["files"]) {
1208
+ FileMetadata file_meta;
1209
+ file_meta.filename = file_json["filename"];
1210
+ file_meta.relative_path = file_json["relative_path"];
1211
+ file_meta.file_size = file_json["file_size"];
1212
+ file_meta.mime_type = file_json.value("mime_type", "application/octet-stream");
1213
+ file_meta.checksum = file_json.value("checksum", "");
1214
+ file_meta.last_modified = file_json.value("last_modified", 0);
1215
+ dir_metadata.files.push_back(file_meta);
1216
+ }
1217
+ }
1218
+
1219
+ // For now, handle as single directory level (can be expanded for nested later)
1220
+
1221
+ {
1222
+ std::lock_guard<std::mutex> lock(pending_mutex_);
1223
+ PendingDirectoryTransfer pending_transfer;
1224
+ pending_transfer.metadata = dir_metadata;
1225
+ pending_transfer.peer_id = peer_id;
1226
+ pending_directory_transfers_[transfer_id] = pending_transfer;
1227
+ }
1228
+
1229
+ // Call user callback to approve/reject
1230
+ if (request_callback_) {
1231
+ // Create a dummy file metadata for compatibility with existing callback
1232
+ FileMetadata dummy_metadata;
1233
+ dummy_metadata.filename = dir_metadata.directory_name + " (directory)";
1234
+ dummy_metadata.file_size = dir_info.value("total_size", 0);
1235
+
1236
+ bool accepted = request_callback_(peer_id, dummy_metadata, transfer_id);
1237
+ if (accepted) {
1238
+ std::string local_path = "./" + dir_metadata.directory_name;
1239
+ accept_directory_transfer(transfer_id, local_path);
1240
+ } else {
1241
+ reject_directory_transfer(transfer_id, "Rejected by user");
1242
+ }
1243
+ } else {
1244
+ // Auto-reject if no callback is set
1245
+ reject_directory_transfer(transfer_id, "No request handler configured");
1246
+ }
1247
+
1248
+ LOG_FILE_TRANSFER_INFO("Received directory transfer request from " << peer_id << " for " << dir_metadata.directory_name);
1249
+ return;
1250
+ }
1251
+
1252
+ // Single file transfer request
1253
+ FileMetadata metadata;
1254
+ auto file_info = message["file_metadata"];
1255
+ metadata.filename = file_info["filename"];
1256
+ metadata.file_size = file_info["file_size"];
1257
+ metadata.mime_type = file_info.value("mime_type", "application/octet-stream");
1258
+ metadata.checksum = file_info.value("checksum", "");
1259
+
1260
+ {
1261
+ std::lock_guard<std::mutex> lock(pending_mutex_);
1262
+ PendingFileTransfer pending_transfer;
1263
+ pending_transfer.metadata = metadata;
1264
+ pending_transfer.peer_id = peer_id;
1265
+ pending_transfers_[transfer_id] = pending_transfer;
1266
+ }
1267
+
1268
+ // Call user callback to approve/reject
1269
+ if (request_callback_) {
1270
+ bool accepted = request_callback_(peer_id, metadata, transfer_id);
1271
+ if (accepted) {
1272
+ // We need a way to specify the local path - this should be part of the callback
1273
+ // For now, we'll create a default path
1274
+ std::string local_path = "./" + metadata.filename;
1275
+ accept_file_transfer(transfer_id, local_path);
1276
+ } else {
1277
+ reject_file_transfer(transfer_id, "Rejected by user");
1278
+ }
1279
+ } else {
1280
+ // Auto-reject if no callback is set
1281
+ reject_file_transfer(transfer_id, "No request handler configured");
1282
+ }
1283
+
1284
+ } catch (const std::exception& e) {
1285
+ LOG_FILE_TRANSFER_ERROR("Error handling transfer request: " << e.what());
1286
+ }
1287
+ }
1288
+
1289
+ void FileTransferManager::handle_transfer_response(const std::string& peer_id, const nlohmann::json& message) {
1290
+ try {
1291
+ std::string transfer_id = message["transfer_id"];
1292
+ bool accepted = message["accepted"];
1293
+
1294
+ auto progress = get_transfer_progress(transfer_id);
1295
+ if (!progress) {
1296
+ LOG_FILE_TRANSFER_ERROR("Received response for unknown transfer: " << transfer_id);
1297
+ return;
1298
+ }
1299
+
1300
+ if (accepted) {
1301
+ // Start sending file
1302
+ {
1303
+ std::lock_guard<std::mutex> work_lock(work_mutex_);
1304
+ work_queue_.push(transfer_id);
1305
+ }
1306
+ work_condition_.notify_one();
1307
+
1308
+ LOG_FILE_TRANSFER_INFO("Transfer accepted by peer: " << transfer_id);
1309
+ } else {
1310
+ std::string reason = message.value("reason", "No reason provided");
1311
+ complete_transfer(transfer_id, false, "Transfer rejected by peer: " + reason);
1312
+ }
1313
+
1314
+ } catch (const std::exception& e) {
1315
+ LOG_FILE_TRANSFER_ERROR("Error handling transfer response: " << e.what());
1316
+ }
1317
+ }
1318
+
1319
+ void FileTransferManager::handle_chunk_metadata_message(const std::string& peer_id, const nlohmann::json& message) {
1320
+ try {
1321
+ PendingChunk pending;
1322
+ pending.transfer_id = message["transfer_id"];
1323
+ pending.chunk_index = message["chunk_index"];
1324
+ pending.total_chunks = message["total_chunks"];
1325
+ pending.chunk_size = message["chunk_size"];
1326
+ pending.file_offset = message["file_offset"];
1327
+ pending.checksum = message.value("checksum", "");
1328
+ pending.created_at = std::chrono::steady_clock::now();
1329
+
1330
+ // Store pending chunk metadata waiting for binary data
1331
+ {
1332
+ std::lock_guard<std::mutex> lock(chunks_mutex_);
1333
+ pending_chunks_[peer_id] = pending;
1334
+ }
1335
+
1336
+ LOG_FILE_TRANSFER_DEBUG("Stored chunk metadata for transfer " << pending.transfer_id <<
1337
+ ", chunk " << pending.chunk_index << " from peer " << peer_id);
1338
+
1339
+ } catch (const std::exception& e) {
1340
+ LOG_FILE_TRANSFER_ERROR("Error handling chunk metadata message: " << e.what());
1341
+ }
1342
+ }
1343
+
1344
+ void FileTransferManager::handle_chunk_binary_message(const std::string& peer_id, const std::vector<uint8_t>& binary_data) {
1345
+ try {
1346
+ // Parse binary chunk header and get the chunk data
1347
+ FileChunk chunk;
1348
+ if (!parse_chunk_binary_header(binary_data, chunk)) {
1349
+ LOG_FILE_TRANSFER_ERROR("Failed to parse chunk binary header from peer " << peer_id);
1350
+ return;
1351
+ }
1352
+
1353
+ // Get pending chunk metadata
1354
+ PendingChunk pending;
1355
+ bool found_pending = false;
1356
+ {
1357
+ std::lock_guard<std::mutex> lock(chunks_mutex_);
1358
+ auto it = pending_chunks_.find(peer_id);
1359
+ if (it != pending_chunks_.end()) {
1360
+ pending = it->second;
1361
+ pending_chunks_.erase(it);
1362
+ found_pending = true;
1363
+ }
1364
+ }
1365
+
1366
+ if (!found_pending) {
1367
+ LOG_FILE_TRANSFER_ERROR("No pending chunk metadata found for peer " << peer_id);
1368
+ return;
1369
+ }
1370
+
1371
+ // Combine metadata with binary data
1372
+ chunk.transfer_id = pending.transfer_id;
1373
+ chunk.chunk_index = pending.chunk_index;
1374
+ chunk.total_chunks = pending.total_chunks;
1375
+ chunk.chunk_size = pending.chunk_size;
1376
+ chunk.file_offset = pending.file_offset;
1377
+ chunk.checksum = pending.checksum;
1378
+
1379
+ // Verify chunk size matches
1380
+ if (chunk.data.size() != pending.chunk_size) {
1381
+ LOG_FILE_TRANSFER_ERROR("Chunk size mismatch: expected " << pending.chunk_size <<
1382
+ ", got " << chunk.data.size() << " for transfer " << pending.transfer_id);
1383
+
1384
+ // Send negative acknowledgment
1385
+ nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, false, "Size mismatch");
1386
+ client_.send(peer_id, "file_chunk_ack", ack_msg);
1387
+ return;
1388
+ }
1389
+
1390
+ // Verify checksum if enabled
1391
+ if (config_.verify_checksums && !chunk.checksum.empty()) {
1392
+ if (!verify_chunk_checksum(chunk)) {
1393
+ LOG_FILE_TRANSFER_ERROR("Chunk checksum verification failed for transfer " << chunk.transfer_id <<
1394
+ ", chunk " << chunk.chunk_index);
1395
+
1396
+ // Send negative acknowledgment
1397
+ nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, false, "Checksum mismatch");
1398
+ client_.send(peer_id, "file_chunk_ack", ack_msg);
1399
+ return;
1400
+ }
1401
+ }
1402
+
1403
+ // Process the received chunk
1404
+ handle_chunk_received(chunk);
1405
+
1406
+ // Send positive acknowledgment
1407
+ nlohmann::json ack_msg = create_chunk_ack_message(chunk.transfer_id, chunk.chunk_index, true);
1408
+ client_.send(peer_id, "file_chunk_ack", ack_msg);
1409
+
1410
+ LOG_FILE_TRANSFER_DEBUG("Successfully processed chunk " << chunk.chunk_index <<
1411
+ " for transfer " << chunk.transfer_id << " from peer " << peer_id);
1412
+
1413
+ } catch (const std::exception& e) {
1414
+ LOG_FILE_TRANSFER_ERROR("Error handling chunk binary message: " << e.what());
1415
+ }
1416
+ }
1417
+
1418
+ void FileTransferManager::handle_chunk_ack_message(const std::string& peer_id, const nlohmann::json& message) {
1419
+ try {
1420
+ std::string transfer_id = message["transfer_id"];
1421
+ uint64_t chunk_index = message["chunk_index"];
1422
+ bool success = message["success"];
1423
+
1424
+ handle_chunk_ack(transfer_id, chunk_index, success);
1425
+
1426
+ } catch (const std::exception& e) {
1427
+ LOG_FILE_TRANSFER_ERROR("Error handling chunk ack message: " << e.what());
1428
+ }
1429
+ }
1430
+
1431
+ void FileTransferManager::handle_transfer_control(const std::string& peer_id, const nlohmann::json& message) {
1432
+ try {
1433
+ std::string transfer_id = message["transfer_id"];
1434
+ std::string action = message["action"];
1435
+
1436
+ if (action == "pause") {
1437
+ pause_transfer(transfer_id);
1438
+ } else if (action == "resume") {
1439
+ resume_transfer(transfer_id);
1440
+ } else if (action == "cancel") {
1441
+ cancel_transfer(transfer_id);
1442
+ }
1443
+
1444
+ } catch (const std::exception& e) {
1445
+ LOG_FILE_TRANSFER_ERROR("Error handling transfer control message: " << e.what());
1446
+ }
1447
+ }
1448
+
1449
+ // Message creation methods
1450
+
1451
+ nlohmann::json FileTransferManager::create_transfer_request_message(const FileMetadata& metadata, const std::string& transfer_id) {
1452
+ nlohmann::json message;
1453
+ message["transfer_id"] = transfer_id;
1454
+ message["type"] = "file";
1455
+ message["file_metadata"] = {
1456
+ {"filename", metadata.filename},
1457
+ {"file_size", metadata.file_size},
1458
+ {"mime_type", metadata.mime_type},
1459
+ {"checksum", metadata.checksum},
1460
+ {"last_modified", metadata.last_modified}
1461
+ };
1462
+ return message;
1463
+ }
1464
+
1465
+ nlohmann::json FileTransferManager::create_transfer_response_message(const std::string& transfer_id, bool accepted, const std::string& reason) {
1466
+ nlohmann::json message;
1467
+ message["transfer_id"] = transfer_id;
1468
+ message["accepted"] = accepted;
1469
+ if (!reason.empty()) {
1470
+ message["reason"] = reason;
1471
+ }
1472
+ return message;
1473
+ }
1474
+
1475
+ nlohmann::json FileTransferManager::create_chunk_metadata_message(const FileChunk& chunk) {
1476
+ nlohmann::json message;
1477
+ message["transfer_id"] = chunk.transfer_id;
1478
+ message["chunk_index"] = chunk.chunk_index;
1479
+ message["total_chunks"] = chunk.total_chunks;
1480
+ message["chunk_size"] = chunk.chunk_size;
1481
+ message["file_offset"] = chunk.file_offset;
1482
+ message["checksum"] = chunk.checksum;
1483
+
1484
+ return message;
1485
+ }
1486
+
1487
+ std::vector<uint8_t> FileTransferManager::create_chunk_binary_message(const FileChunk& chunk) {
1488
+ // Create binary message with header: "FTCHUNK" + chunk data
1489
+ const std::string magic = "FTCHUNK";
1490
+ std::vector<uint8_t> message;
1491
+
1492
+ // Reserve space for the entire message
1493
+ message.reserve(magic.length() + chunk.data.size());
1494
+
1495
+ // Add magic header
1496
+ message.insert(message.end(), magic.begin(), magic.end());
1497
+
1498
+ // Add chunk data
1499
+ message.insert(message.end(), chunk.data.begin(), chunk.data.end());
1500
+
1501
+ return message;
1502
+ }
1503
+
1504
+ nlohmann::json FileTransferManager::create_chunk_ack_message(const std::string& transfer_id, uint64_t chunk_index, bool success, const std::string& error) {
1505
+ nlohmann::json message;
1506
+ message["transfer_id"] = transfer_id;
1507
+ message["chunk_index"] = chunk_index;
1508
+ message["success"] = success;
1509
+ if (!error.empty()) {
1510
+ message["error"] = error;
1511
+ }
1512
+ return message;
1513
+ }
1514
+
1515
+ nlohmann::json FileTransferManager::create_control_message(const std::string& transfer_id, const std::string& action, const nlohmann::json& data) {
1516
+ nlohmann::json message;
1517
+ message["transfer_id"] = transfer_id;
1518
+ message["action"] = action;
1519
+ if (!data.empty()) {
1520
+ message["data"] = data;
1521
+ }
1522
+ return message;
1523
+ }
1524
+
1525
+ // Progress tracking methods
1526
+
1527
+ void FileTransferManager::update_transfer_progress(const std::string& transfer_id, uint64_t bytes_delta) {
1528
+ auto progress = get_transfer_progress(transfer_id);
1529
+ if (!progress) {
1530
+ return;
1531
+ }
1532
+
1533
+ if (bytes_delta > 0) {
1534
+ progress->update_transfer_rates(progress->bytes_transferred + bytes_delta);
1535
+
1536
+ // Update statistics
1537
+ {
1538
+ std::lock_guard<std::mutex> stats_lock(stats_mutex_);
1539
+ if (progress->direction == FileTransferDirection::SENDING) {
1540
+ total_bytes_sent_ += bytes_delta;
1541
+ } else {
1542
+ total_bytes_received_ += bytes_delta;
1543
+ }
1544
+ }
1545
+ }
1546
+
1547
+ // Call progress callback
1548
+ if (progress_callback_) {
1549
+ progress_callback_(*progress);
1550
+ }
1551
+ }
1552
+
1553
+ void FileTransferManager::complete_transfer(const std::string& transfer_id, bool success, const std::string& error_message) {
1554
+ auto progress = get_transfer_progress(transfer_id);
1555
+ if (!progress) {
1556
+ return;
1557
+ }
1558
+
1559
+ progress->status = success ? FileTransferStatus::COMPLETED : FileTransferStatus::FAILED;
1560
+ progress->error_message = error_message;
1561
+
1562
+ // Update statistics
1563
+ {
1564
+ std::lock_guard<std::mutex> stats_lock(stats_mutex_);
1565
+ if (progress->direction == FileTransferDirection::SENDING) {
1566
+ total_files_sent_++;
1567
+ } else {
1568
+ total_files_received_++;
1569
+ }
1570
+ }
1571
+
1572
+ // Move to completed transfers
1573
+ move_to_completed(transfer_id);
1574
+
1575
+ // Call completion callback
1576
+ if (completion_callback_) {
1577
+ completion_callback_(transfer_id, success, error_message);
1578
+ }
1579
+
1580
+ LOG_FILE_TRANSFER_INFO("Transfer " << (success ? "completed" : "failed") << ": " << transfer_id);
1581
+ }
1582
+
1583
+ void FileTransferManager::move_to_completed(const std::string& transfer_id) {
1584
+ std::lock_guard<std::mutex> lock(transfers_mutex_);
1585
+
1586
+ auto it = active_transfers_.find(transfer_id);
1587
+ if (it != active_transfers_.end()) {
1588
+ completed_transfers_[transfer_id] = it->second;
1589
+ active_transfers_.erase(it);
1590
+ }
1591
+ }
1592
+
1593
+ // Binary chunk transmission implementation (replaces base64 encoding)
1594
+ //
1595
+ // PERFORMANCE OPTIMIZATIONS:
1596
+ // - Replaced JSON + base64 encoding with direct binary transmission
1597
+ // - Eliminates 33% encoding overhead from base64
1598
+ // - Reduces CPU usage for encoding/decoding
1599
+ // - Minimizes memory allocations for string conversions
1600
+ // - Uses two-phase protocol: metadata (JSON) + binary data (raw)
1601
+ // - Added cleanup thread for timeout handling of split messages
1602
+ //
1603
+ // PROTOCOL CHANGES:
1604
+ // 1. Send file_chunk_metadata (JSON) with transfer info
1605
+ // 2. Send binary data with "FTCHUNK" magic header + raw file data
1606
+ // 3. Receive acknowledgments as before
1607
+ //
1608
+ // This approach provides maximum performance while maintaining protocol robustness.
1609
+
1610
+ void FileTransferManager::handle_chunk_received(const FileChunk& chunk) {
1611
+ // Write chunk to temporary file
1612
+ std::string temp_path = get_temp_file_path(chunk.transfer_id, config_.temp_directory);
1613
+
1614
+ if (!write_file_chunk(temp_path.c_str(), chunk.file_offset, chunk.data.data(), chunk.chunk_size)) {
1615
+ LOG_FILE_TRANSFER_ERROR("Failed to write chunk to temp file: " << temp_path);
1616
+ return;
1617
+ }
1618
+
1619
+ // Update progress
1620
+ update_transfer_progress(chunk.transfer_id, chunk.chunk_size);
1621
+
1622
+ // Check if transfer is complete
1623
+ auto progress = get_transfer_progress(chunk.transfer_id);
1624
+ if (progress) {
1625
+ progress->chunks_completed++;
1626
+ if (progress->chunks_completed == progress->total_chunks) {
1627
+ // Transfer complete - move temp file to final location
1628
+ if (finalize_received_file(chunk.transfer_id, progress->local_path)) {
1629
+ complete_transfer(chunk.transfer_id, true);
1630
+ } else {
1631
+ complete_transfer(chunk.transfer_id, false, "Failed to finalize received file");
1632
+ }
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ void FileTransferManager::handle_chunk_ack(const std::string& transfer_id, uint64_t chunk_index, bool success) {
1638
+ auto progress = get_transfer_progress(transfer_id);
1639
+ if (!progress) {
1640
+ return;
1641
+ }
1642
+
1643
+ if (success) {
1644
+ progress->chunks_completed++;
1645
+ if (progress->chunks_completed == progress->total_chunks) {
1646
+ complete_transfer(transfer_id, true);
1647
+ }
1648
+ } else {
1649
+ // Handle chunk failure - could retry or fail the transfer
1650
+ LOG_FILE_TRANSFER_ERROR("Chunk " << chunk_index << " failed for transfer " << transfer_id);
1651
+ }
1652
+ }
1653
+
1654
+ bool FileTransferManager::finalize_received_file(const std::string& transfer_id, const std::string& final_path) {
1655
+ std::string temp_path = get_temp_file_path(transfer_id, config_.temp_directory);
1656
+
1657
+ try {
1658
+ // Ensure destination directory exists
1659
+ std::string dest_dir = get_parent_directory(final_path.c_str());
1660
+ if (!dest_dir.empty()) {
1661
+ ensure_directory_exists(dest_dir);
1662
+ }
1663
+
1664
+ // Move temp file to final location
1665
+ if (!rename_file(temp_path.c_str(), final_path.c_str())) {
1666
+ LOG_FILE_TRANSFER_ERROR("Failed to rename temp file to final location: " << temp_path << " -> " << final_path);
1667
+ return false;
1668
+ }
1669
+
1670
+ return true;
1671
+ } catch (const std::exception& e) {
1672
+ LOG_FILE_TRANSFER_ERROR("Failed to finalize file " << final_path << ": " << e.what());
1673
+ return false;
1674
+ }
1675
+ }
1676
+
1677
+ std::string FileTransferManager::calculate_chunk_checksum(const std::vector<uint8_t>& data) {
1678
+ SHA1 sha1;
1679
+ sha1.update(data.data(), data.size());
1680
+ return sha1.finalize();
1681
+ }
1682
+
1683
+ bool FileTransferManager::parse_chunk_binary_header(const std::vector<uint8_t>& binary_data, FileChunk& chunk) {
1684
+ const std::string magic = "FTCHUNK";
1685
+
1686
+ // Check minimum size (magic header + at least some data)
1687
+ if (binary_data.size() < magic.length()) {
1688
+ return false;
1689
+ }
1690
+
1691
+ // Verify magic header
1692
+ if (std::memcmp(binary_data.data(), magic.c_str(), magic.length()) != 0) {
1693
+ return false;
1694
+ }
1695
+
1696
+ // Extract chunk data (everything after the magic header)
1697
+ size_t data_start = magic.length();
1698
+ size_t data_size = binary_data.size() - data_start;
1699
+
1700
+ chunk.data.resize(data_size);
1701
+ std::memcpy(chunk.data.data(), binary_data.data() + data_start, data_size);
1702
+
1703
+ return true;
1704
+ }
1705
+
1706
+ bool FileTransferManager::verify_chunk_checksum(const FileChunk& chunk) {
1707
+ if (!config_.verify_checksums || chunk.checksum.empty()) {
1708
+ return true;
1709
+ }
1710
+
1711
+ std::string calculated = calculate_chunk_checksum(chunk.data);
1712
+ return calculated == chunk.checksum;
1713
+ }
1714
+
1715
+ void FileTransferManager::handle_file_request(const std::string& peer_id, const nlohmann::json& message) {
1716
+ try {
1717
+ std::string transfer_id = message["transfer_id"];
1718
+ std::string remote_path = message["remote_path"];
1719
+
1720
+ // Check if file exists and is accessible
1721
+ if (!file_or_directory_exists(remote_path)) {
1722
+ LOG_FILE_TRANSFER_WARN("File request denied - file not found: " << remote_path);
1723
+ nlohmann::json response;
1724
+ response["transfer_id"] = transfer_id;
1725
+ response["accepted"] = false;
1726
+ response["reason"] = "File not found or not accessible";
1727
+ client_.send(peer_id, "file_transfer_response", response);
1728
+ return;
1729
+ }
1730
+
1731
+ // Call user callback to approve/reject
1732
+ if (file_request_callback_) {
1733
+ bool accepted = file_request_callback_(peer_id, remote_path, transfer_id);
1734
+ if (accepted) {
1735
+ // Start file transfer
1736
+ send_file(peer_id, remote_path);
1737
+ LOG_FILE_TRANSFER_INFO("Accepted file request: " << remote_path << " for " << peer_id);
1738
+ } else {
1739
+ nlohmann::json response;
1740
+ response["transfer_id"] = transfer_id;
1741
+ response["accepted"] = false;
1742
+ response["reason"] = "Request rejected by user";
1743
+ client_.send(peer_id, "file_transfer_response", response);
1744
+ LOG_FILE_TRANSFER_INFO("Rejected file request: " << remote_path << " for " << peer_id);
1745
+ }
1746
+ } else {
1747
+ // Auto-reject if no callback is set
1748
+ nlohmann::json response;
1749
+ response["transfer_id"] = transfer_id;
1750
+ response["accepted"] = false;
1751
+ response["reason"] = "No file request handler configured";
1752
+ client_.send(peer_id, "file_transfer_response", response);
1753
+ LOG_FILE_TRANSFER_WARN("Auto-rejected file request - no handler: " << remote_path);
1754
+ }
1755
+
1756
+ } catch (const std::exception& e) {
1757
+ LOG_FILE_TRANSFER_ERROR("Error handling file request: " << e.what());
1758
+ }
1759
+ }
1760
+
1761
+ void FileTransferManager::handle_directory_request(const std::string& peer_id, const nlohmann::json& message) {
1762
+ try {
1763
+ std::string transfer_id = message["transfer_id"];
1764
+ std::string remote_path = message["remote_path"];
1765
+ bool recursive = message.value("recursive", true);
1766
+
1767
+ // Check if directory exists and is accessible
1768
+ if (!directory_exists(remote_path)) {
1769
+ LOG_FILE_TRANSFER_WARN("Directory request denied - directory not found: " << remote_path);
1770
+ nlohmann::json response;
1771
+ response["transfer_id"] = transfer_id;
1772
+ response["accepted"] = false;
1773
+ response["reason"] = "Directory not found or not accessible";
1774
+ client_.send(peer_id, "file_transfer_response", response);
1775
+ return;
1776
+ }
1777
+
1778
+ // Call user callback to approve/reject
1779
+ if (directory_request_callback_) {
1780
+ bool accepted = directory_request_callback_(peer_id, remote_path, recursive, transfer_id);
1781
+ if (accepted) {
1782
+ // Start directory transfer
1783
+ send_directory(peer_id, remote_path, "", recursive);
1784
+ LOG_FILE_TRANSFER_INFO("Accepted directory request: " << remote_path << " for " << peer_id);
1785
+ } else {
1786
+ nlohmann::json response;
1787
+ response["transfer_id"] = transfer_id;
1788
+ response["accepted"] = false;
1789
+ response["reason"] = "Request rejected by user";
1790
+ client_.send(peer_id, "file_transfer_response", response);
1791
+ LOG_FILE_TRANSFER_INFO("Rejected directory request: " << remote_path << " for " << peer_id);
1792
+ }
1793
+ } else {
1794
+ // Auto-reject if no callback is set
1795
+ nlohmann::json response;
1796
+ response["transfer_id"] = transfer_id;
1797
+ response["accepted"] = false;
1798
+ response["reason"] = "No directory request handler configured";
1799
+ client_.send(peer_id, "file_transfer_response", response);
1800
+ LOG_FILE_TRANSFER_WARN("Auto-rejected directory request - no handler: " << remote_path);
1801
+ }
1802
+
1803
+ } catch (const std::exception& e) {
1804
+ LOG_FILE_TRANSFER_ERROR("Error handling directory request: " << e.what());
1805
+ }
1806
+ }
1807
+
1808
+ } // namespace librats