librats 1.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -331
- package/binding.gyp +16 -3
- package/lib/index.d.ts +288 -696
- package/lib/index.js +407 -44
- package/native-src/3rdparty/android/ifaddrs-android.c +1 -0
- package/native-src/3rdparty/android/ifaddrs-android.h +1 -0
- package/native-src/CMakeLists.txt +404 -179
- package/native-src/LICENSE +1 -1
- package/native-src/src/librats/bindings/rats.cpp +762 -0
- package/native-src/src/librats/bindings/rats.h +380 -0
- package/native-src/src/librats/bittorrent/bencode.cpp +437 -0
- package/native-src/src/librats/bittorrent/bencode.h +176 -0
- package/native-src/src/librats/bittorrent/bitfield.cpp +97 -0
- package/native-src/src/librats/bittorrent/bitfield.h +76 -0
- package/native-src/src/librats/bittorrent/byte_io.h +58 -0
- package/native-src/src/librats/bittorrent/choker.cpp +25 -0
- package/native-src/src/librats/bittorrent/choker.h +46 -0
- package/native-src/src/librats/bittorrent/client.cpp +413 -0
- package/native-src/src/librats/bittorrent/client.h +227 -0
- package/native-src/src/librats/bittorrent/disk_io.cpp +209 -0
- package/native-src/src/librats/bittorrent/disk_io.h +150 -0
- package/native-src/src/librats/bittorrent/extensions.cpp +191 -0
- package/native-src/src/librats/bittorrent/extensions.h +94 -0
- package/native-src/src/librats/bittorrent/file_storage.cpp +77 -0
- package/native-src/src/librats/bittorrent/file_storage.h +79 -0
- package/native-src/src/librats/bittorrent/log.h +42 -0
- package/native-src/src/librats/bittorrent/magnet_uri.cpp +98 -0
- package/native-src/src/librats/bittorrent/magnet_uri.h +35 -0
- package/native-src/src/librats/bittorrent/peer_connection.cpp +502 -0
- package/native-src/src/librats/bittorrent/peer_connection.h +194 -0
- package/native-src/src/librats/bittorrent/peer_list.cpp +68 -0
- package/native-src/src/librats/bittorrent/peer_list.h +75 -0
- package/native-src/src/librats/bittorrent/piece_picker.cpp +352 -0
- package/native-src/src/librats/bittorrent/piece_picker.h +201 -0
- package/native-src/src/librats/bittorrent/reactor.cpp +97 -0
- package/native-src/src/librats/bittorrent/reactor.h +89 -0
- package/native-src/src/librats/bittorrent/resume_data.cpp +72 -0
- package/native-src/src/librats/bittorrent/resume_data.h +41 -0
- package/native-src/src/librats/bittorrent/store_buffer.cpp +48 -0
- package/native-src/src/librats/bittorrent/store_buffer.h +47 -0
- package/native-src/src/librats/bittorrent/torrent.cpp +870 -0
- package/native-src/src/librats/bittorrent/torrent.h +260 -0
- package/native-src/src/librats/bittorrent/torrent_creator.cpp +129 -0
- package/native-src/src/librats/bittorrent/torrent_creator.h +58 -0
- package/native-src/src/librats/bittorrent/torrent_info.cpp +314 -0
- package/native-src/src/librats/bittorrent/torrent_info.h +118 -0
- package/native-src/src/librats/bittorrent/tracker.cpp +374 -0
- package/native-src/src/librats/bittorrent/tracker.h +108 -0
- package/native-src/src/librats/bittorrent/types.cpp +206 -0
- package/native-src/src/librats/bittorrent/types.h +86 -0
- package/native-src/src/librats/core/address.cpp +35 -0
- package/native-src/src/librats/core/address.h +78 -0
- package/native-src/src/librats/core/bytes.h +69 -0
- package/native-src/src/librats/core/chained_send_buffer.cpp +172 -0
- package/native-src/src/librats/core/chained_send_buffer.h +183 -0
- package/native-src/src/librats/core/endpoint_parse.cpp +41 -0
- package/native-src/src/librats/core/endpoint_parse.h +31 -0
- package/native-src/src/librats/core/event_bus.h +70 -0
- package/native-src/src/librats/core/host_endpoint.h +56 -0
- package/native-src/src/{io_poller.cpp → librats/core/io_poller.cpp} +520 -65
- package/native-src/src/{io_poller.h → librats/core/io_poller.h} +12 -6
- package/native-src/src/librats/core/ip_address.cpp +120 -0
- package/native-src/src/librats/core/ip_address.h +109 -0
- package/native-src/src/librats/core/mpsc_queue.h +47 -0
- package/native-src/src/librats/core/notifier.h +74 -0
- package/native-src/src/librats/core/receive_buffer.cpp +219 -0
- package/native-src/src/librats/core/receive_buffer.h +171 -0
- package/native-src/src/librats/core/service_registry.h +58 -0
- package/native-src/src/{socket.cpp → librats/core/socket.cpp} +625 -118
- package/native-src/src/librats/core/socket.h +496 -0
- package/native-src/src/librats/core/timer_queue.h +105 -0
- package/native-src/src/librats/core/types.cpp +43 -0
- package/native-src/src/librats/core/types.h +103 -0
- package/native-src/src/librats/core/wakeup_pipe.h +83 -0
- package/native-src/src/{crypto → librats/crypto}/blake2_endian.h +21 -23
- package/native-src/src/{crypto → librats/crypto}/blake2b.c +34 -33
- package/native-src/src/{crypto → librats/crypto}/blake2b.h +7 -6
- package/native-src/src/{crypto → librats/crypto}/blake2s.c +55 -54
- package/native-src/src/{crypto → librats/crypto}/blake2s.h +13 -12
- package/native-src/src/{crypto → librats/crypto}/chacha.c +22 -21
- package/native-src/src/{crypto → librats/crypto}/chacha.h +14 -13
- package/native-src/src/{crypto → librats/crypto}/chachapoly.c +56 -56
- package/native-src/src/{crypto → librats/crypto}/chachapoly.h +24 -17
- package/native-src/src/{crc32.cpp → librats/crypto/crc32.cpp} +1 -1
- package/native-src/src/{crc32.h → librats/crypto/crc32.h} +3 -1
- package/native-src/src/{crypto → librats/crypto}/curve25519.c +6 -4
- package/native-src/src/{crypto → librats/crypto}/curve25519.h +6 -3
- package/native-src/src/librats/crypto/hkdf.c +266 -0
- package/native-src/src/{crypto → librats/crypto}/hkdf.h +19 -19
- package/native-src/src/{noise.cpp → librats/crypto/noise.cpp} +84 -73
- package/native-src/src/{noise.h → librats/crypto/noise.h} +18 -8
- package/native-src/src/{crypto → librats/crypto}/poly1305.c +47 -46
- package/native-src/src/librats/crypto/poly1305.h +37 -0
- package/native-src/src/{sha1.cpp → librats/crypto/sha1.cpp} +33 -1
- package/native-src/src/{sha1.h → librats/crypto/sha1.h} +14 -6
- package/native-src/src/{crypto → librats/crypto}/sha256.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha256.h +8 -7
- package/native-src/src/{crypto → librats/crypto}/sha512.c +15 -14
- package/native-src/src/{crypto → librats/crypto}/sha512.h +8 -7
- package/native-src/src/librats/dht/announce.cpp +37 -0
- package/native-src/src/librats/dht/announce.h +41 -0
- package/native-src/src/librats/dht/bep42.cpp +109 -0
- package/native-src/src/librats/dht/bep42.h +48 -0
- package/native-src/src/librats/dht/dht.cpp +501 -0
- package/native-src/src/librats/dht/dht.h +119 -0
- package/native-src/src/librats/dht/dht_runner.cpp +103 -0
- package/native-src/src/librats/dht/dht_runner.h +71 -0
- package/native-src/src/librats/dht/dos_blocker.cpp +42 -0
- package/native-src/src/librats/dht/dos_blocker.h +47 -0
- package/native-src/src/librats/dht/find_peers.cpp +52 -0
- package/native-src/src/librats/dht/find_peers.h +73 -0
- package/native-src/src/librats/dht/id.h +167 -0
- package/native-src/src/{krpc.cpp → librats/dht/krpc.cpp} +32 -81
- package/native-src/src/{krpc.h → librats/dht/krpc.h} +19 -23
- package/native-src/src/librats/dht/log.h +38 -0
- package/native-src/src/librats/dht/node.cpp +473 -0
- package/native-src/src/librats/dht/node.h +164 -0
- package/native-src/src/librats/dht/node_entry.h +81 -0
- package/native-src/src/librats/dht/observer.h +72 -0
- package/native-src/src/librats/dht/persistence.cpp +90 -0
- package/native-src/src/librats/dht/persistence.h +32 -0
- package/native-src/src/librats/dht/routing_table.cpp +559 -0
- package/native-src/src/librats/dht/routing_table.h +185 -0
- package/native-src/src/librats/dht/rpc_manager.cpp +127 -0
- package/native-src/src/librats/dht/rpc_manager.h +77 -0
- package/native-src/src/librats/dht/storage.cpp +92 -0
- package/native-src/src/librats/dht/storage.h +74 -0
- package/native-src/src/librats/dht/transport.h +27 -0
- package/native-src/src/librats/dht/traversal.cpp +326 -0
- package/native-src/src/librats/dht/traversal.h +120 -0
- package/native-src/src/librats/dht/udp_transport.cpp +49 -0
- package/native-src/src/librats/dht/udp_transport.h +51 -0
- package/native-src/src/librats/mdns/log.h +22 -0
- package/native-src/src/{mdns.cpp → librats/mdns/mdns.cpp} +75 -40
- package/native-src/src/{mdns.h → librats/mdns/mdns.h} +9 -8
- package/native-src/src/{natpmp.cpp → librats/nat/natpmp.cpp} +12 -9
- package/native-src/src/{natpmp.h → librats/nat/natpmp.h} +3 -3
- package/native-src/src/{port_mapping.h → librats/nat/port_mapping.h} +3 -2
- package/native-src/src/{stun.cpp → librats/nat/stun.cpp} +4 -4
- package/native-src/src/{stun.h → librats/nat/stun.h} +1 -1
- package/native-src/src/{upnp.cpp → librats/nat/upnp.cpp} +6 -6
- package/native-src/src/{upnp.h → librats/nat/upnp.h} +2 -2
- package/native-src/src/librats/node/circuit_service.h +84 -0
- package/native-src/src/librats/node/config.h +110 -0
- package/native-src/src/librats/node/dial_service.h +54 -0
- package/native-src/src/librats/node/dialer.cpp +264 -0
- package/native-src/src/librats/node/dialer.h +188 -0
- package/native-src/src/librats/node/host_events.h +26 -0
- package/native-src/src/librats/node/identify.cpp +130 -0
- package/native-src/src/librats/node/identify.h +71 -0
- package/native-src/src/librats/node/nat_status.cpp +103 -0
- package/native-src/src/librats/node/nat_status.h +118 -0
- package/native-src/src/librats/node/node.cpp +865 -0
- package/native-src/src/librats/node/node.h +344 -0
- package/native-src/src/librats/node/node_context.h +33 -0
- package/native-src/src/librats/node/peer_network.h +91 -0
- package/native-src/src/librats/peer/peer.h +49 -0
- package/native-src/src/librats/peer/peer_book.cpp +181 -0
- package/native-src/src/librats/peer/peer_book.h +88 -0
- package/native-src/src/librats/peer/peer_id.cpp +72 -0
- package/native-src/src/librats/peer/peer_id.h +62 -0
- package/native-src/src/librats/peer/peer_info.h +37 -0
- package/native-src/src/librats/peer/peer_table.cpp +170 -0
- package/native-src/src/librats/peer/peer_table.h +148 -0
- package/native-src/src/librats/security/handshaker.h +66 -0
- package/native-src/src/librats/security/identity.h +43 -0
- package/native-src/src/librats/security/noise_security.cpp +122 -0
- package/native-src/src/librats/security/noise_security.h +37 -0
- package/native-src/src/librats/security/plaintext_security.h +106 -0
- package/native-src/src/librats/security/session.h +37 -0
- package/native-src/src/{storage.cpp → librats/storage/storage.cpp} +369 -522
- package/native-src/src/{storage.h → librats/storage/storage.h} +135 -299
- package/native-src/src/librats/subsystems/bittorrent.cpp +211 -0
- package/native-src/src/librats/subsystems/bittorrent.h +136 -0
- package/native-src/src/librats/subsystems/dht_discovery.cpp +202 -0
- package/native-src/src/librats/subsystems/dht_discovery.h +123 -0
- package/native-src/src/librats/subsystems/dht_service.h +36 -0
- package/native-src/src/librats/subsystems/file_transfer.cpp +972 -0
- package/native-src/src/librats/subsystems/file_transfer.h +367 -0
- package/native-src/src/librats/subsystems/hole_punch.cpp +605 -0
- package/native-src/src/librats/subsystems/hole_punch.h +290 -0
- package/native-src/src/librats/subsystems/hole_punch_service.h +38 -0
- package/native-src/src/librats/subsystems/mdns_discovery.cpp +66 -0
- package/native-src/src/librats/subsystems/mdns_discovery.h +55 -0
- package/native-src/src/librats/subsystems/message_json.cpp +112 -0
- package/native-src/src/librats/subsystems/message_json.h +88 -0
- package/native-src/src/librats/subsystems/peer_exchange.cpp +241 -0
- package/native-src/src/librats/subsystems/peer_exchange.h +136 -0
- package/native-src/src/librats/subsystems/ping_service.cpp +98 -0
- package/native-src/src/librats/subsystems/ping_service.h +66 -0
- package/native-src/src/librats/subsystems/port_mapping_service.cpp +192 -0
- package/native-src/src/librats/subsystems/port_mapping_service.h +84 -0
- package/native-src/src/librats/subsystems/pubsub.cpp +567 -0
- package/native-src/src/librats/subsystems/pubsub.h +175 -0
- package/native-src/src/librats/subsystems/reconnection.cpp +239 -0
- package/native-src/src/librats/subsystems/reconnection.h +126 -0
- package/native-src/src/librats/subsystems/relay.cpp +1142 -0
- package/native-src/src/librats/subsystems/relay.h +211 -0
- package/native-src/src/librats/subsystems/relay_service.h +46 -0
- package/native-src/src/librats/transport/connection.cpp +343 -0
- package/native-src/src/librats/transport/connection.h +283 -0
- package/native-src/src/librats/transport/link.h +96 -0
- package/native-src/src/librats/transport/reactor.cpp +588 -0
- package/native-src/src/librats/transport/reactor.h +262 -0
- package/native-src/src/librats/transport/reactor_pool.h +81 -0
- package/native-src/src/librats/transport/relay_link.cpp +208 -0
- package/native-src/src/librats/transport/relay_link.h +303 -0
- package/native-src/src/librats/transport/tcp_link.cpp +49 -0
- package/native-src/src/librats/transport/tcp_link.h +43 -0
- package/native-src/src/librats/transport/udp_mux.cpp +617 -0
- package/native-src/src/librats/transport/udp_mux.h +363 -0
- package/native-src/src/librats/transport/udp_packet.cpp +121 -0
- package/native-src/src/librats/transport/udp_packet.h +190 -0
- package/native-src/src/librats/transport/udp_stream.cpp +1194 -0
- package/native-src/src/librats/transport/udp_stream.h +614 -0
- package/native-src/src/librats/util/features.h.in +51 -0
- package/native-src/src/{fs.cpp → librats/util/fs.cpp} +51 -3
- package/native-src/src/librats/util/fs.h +136 -0
- package/native-src/src/librats/util/json.cpp +1002 -0
- package/native-src/src/librats/util/json.h +444 -0
- package/native-src/src/{logger.cpp → librats/util/logger.cpp} +1 -1
- package/native-src/src/{logger.h → librats/util/logger.h} +43 -31
- package/native-src/src/{network_monitor.cpp → librats/util/network_monitor.cpp} +12 -4
- package/native-src/src/{network_monitor.h → librats/util/network_monitor.h} +2 -1
- package/native-src/src/{network_utils.cpp → librats/util/network_utils.cpp} +38 -23
- package/native-src/src/{network_utils.h → librats/util/network_utils.h} +15 -8
- package/native-src/src/{os.cpp → librats/util/os.cpp} +48 -18
- package/native-src/src/librats/util/rats_export.h +69 -0
- package/native-src/src/{version.cpp → librats/util/version.cpp} +2 -2
- package/native-src/src/{version.h.in → librats/util/version.h.in} +1 -1
- package/native-src/src/librats/wire/frame.cpp +76 -0
- package/native-src/src/librats/wire/frame.h +111 -0
- package/native-src/src/librats/wire/message_router.cpp +45 -0
- package/native-src/src/librats/wire/message_router.h +47 -0
- package/package.json +5 -4
- package/scripts/build-librats.js +1 -0
- package/scripts/postinstall.js +3 -3
- package/scripts/prepare-package.js +4 -4
- package/scripts/verify-installation.js +63 -105
- package/src/librats_node.cpp +1067 -1323
- package/native-src/src/bencode.cpp +0 -485
- package/native-src/src/bencode.h +0 -145
- package/native-src/src/bittorrent.cpp +0 -14
- package/native-src/src/bittorrent.h +0 -74
- package/native-src/src/bt_bitfield.cpp +0 -372
- package/native-src/src/bt_bitfield.h +0 -316
- package/native-src/src/bt_choker.cpp +0 -228
- package/native-src/src/bt_choker.h +0 -147
- package/native-src/src/bt_client.cpp +0 -1047
- package/native-src/src/bt_client.h +0 -445
- package/native-src/src/bt_create_torrent.cpp +0 -677
- package/native-src/src/bt_create_torrent.h +0 -473
- package/native-src/src/bt_extension.cpp +0 -469
- package/native-src/src/bt_extension.h +0 -309
- package/native-src/src/bt_file_storage.cpp +0 -261
- package/native-src/src/bt_file_storage.h +0 -298
- package/native-src/src/bt_handshake.cpp +0 -134
- package/native-src/src/bt_handshake.h +0 -157
- package/native-src/src/bt_messages.cpp +0 -364
- package/native-src/src/bt_messages.h +0 -324
- package/native-src/src/bt_network.cpp +0 -1007
- package/native-src/src/bt_network.h +0 -417
- package/native-src/src/bt_peer_connection.cpp +0 -742
- package/native-src/src/bt_peer_connection.h +0 -592
- package/native-src/src/bt_piece_picker.cpp +0 -786
- package/native-src/src/bt_piece_picker.h +0 -473
- package/native-src/src/bt_resume_data.cpp +0 -410
- package/native-src/src/bt_resume_data.h +0 -249
- package/native-src/src/bt_torrent.cpp +0 -2120
- package/native-src/src/bt_torrent.h +0 -641
- package/native-src/src/bt_torrent_info.cpp +0 -659
- package/native-src/src/bt_torrent_info.h +0 -418
- package/native-src/src/bt_types.h +0 -621
- package/native-src/src/chained_send_buffer.cpp +0 -75
- package/native-src/src/chained_send_buffer.h +0 -137
- package/native-src/src/crypto/hkdf.c +0 -266
- package/native-src/src/crypto/poly1305.h +0 -36
- package/native-src/src/dht.cpp +0 -3311
- package/native-src/src/dht.h +0 -717
- package/native-src/src/disk_io.cpp +0 -632
- package/native-src/src/disk_io.h +0 -315
- package/native-src/src/file_transfer.cpp +0 -1415
- package/native-src/src/file_transfer.h +0 -286
- package/native-src/src/fs.h +0 -108
- package/native-src/src/gossipsub.cpp +0 -1139
- package/native-src/src/gossipsub.h +0 -403
- package/native-src/src/ice.cpp +0 -893
- package/native-src/src/ice.h +0 -559
- package/native-src/src/json.hpp +0 -25526
- package/native-src/src/librats.cpp +0 -2378
- package/native-src/src/librats.h +0 -2324
- package/native-src/src/librats_bittorrent.cpp +0 -601
- package/native-src/src/librats_c.cpp +0 -1557
- package/native-src/src/librats_c.h +0 -323
- package/native-src/src/librats_discovery.cpp +0 -402
- package/native-src/src/librats_encryption.cpp +0 -275
- package/native-src/src/librats_file_transfer.cpp +0 -144
- package/native-src/src/librats_gossipsub.cpp +0 -289
- package/native-src/src/librats_ice.cpp +0 -213
- package/native-src/src/librats_log_macros.h +0 -36
- package/native-src/src/librats_logging.cpp +0 -173
- package/native-src/src/librats_mdns.cpp +0 -166
- package/native-src/src/librats_persistence.cpp +0 -796
- package/native-src/src/librats_portmap.cpp +0 -419
- package/native-src/src/librats_reconnection.cpp +0 -218
- package/native-src/src/librats_statistic.cpp +0 -105
- package/native-src/src/librats_storage.cpp +0 -189
- package/native-src/src/rats_export.h +0 -17
- package/native-src/src/receive_buffer.cpp +0 -82
- package/native-src/src/receive_buffer.h +0 -127
- package/native-src/src/socket.h +0 -228
- package/native-src/src/threadmanager.cpp +0 -105
- package/native-src/src/threadmanager.h +0 -53
- package/native-src/src/tracker.cpp +0 -1264
- package/native-src/src/tracker.h +0 -319
- package/native-src/src/turn.cpp +0 -762
- package/native-src/src/turn.h +0 -460
- package/native-src/src/wakeup_pipe.h +0 -60
- /package/native-src/src/{os.h → librats/util/os.h} +0 -0
|
@@ -1,1415 +0,0 @@
|
|
|
1
|
-
#include "file_transfer.h"
|
|
2
|
-
#include "librats.h"
|
|
3
|
-
#include "fs.h"
|
|
4
|
-
#include "logger.h"
|
|
5
|
-
#include "crypto/sha256.h"
|
|
6
|
-
|
|
7
|
-
#include <algorithm>
|
|
8
|
-
#include <cstring>
|
|
9
|
-
#include <random>
|
|
10
|
-
|
|
11
|
-
// Logging shorthand for this module.
|
|
12
|
-
#define LOG_FT_INFO(msg) LOG_INFO("filetransfer", msg)
|
|
13
|
-
#define LOG_FT_WARN(msg) LOG_WARN("filetransfer", msg)
|
|
14
|
-
#define LOG_FT_ERROR(msg) LOG_ERROR("filetransfer", msg)
|
|
15
|
-
#define LOG_FT_DEBUG(msg) LOG_DEBUG("filetransfer", msg)
|
|
16
|
-
|
|
17
|
-
namespace librats {
|
|
18
|
-
|
|
19
|
-
// =============================================================================
|
|
20
|
-
// Wire constants
|
|
21
|
-
// =============================================================================
|
|
22
|
-
|
|
23
|
-
namespace {
|
|
24
|
-
|
|
25
|
-
// Named control-message types exchanged on the JSON channel.
|
|
26
|
-
constexpr const char* MSG_OFFER = "ft_offer";
|
|
27
|
-
constexpr const char* MSG_RESPONSE = "ft_response";
|
|
28
|
-
constexpr const char* MSG_FILE_END = "ft_file_end";
|
|
29
|
-
constexpr const char* MSG_PROGRESS = "ft_progress";
|
|
30
|
-
constexpr const char* MSG_COMPLETE = "ft_complete";
|
|
31
|
-
constexpr const char* MSG_CONTROL = "ft_control";
|
|
32
|
-
|
|
33
|
-
// Magic prefix of a binary chunk frame.
|
|
34
|
-
constexpr char CHUNK_MAGIC[4] = {'R', 'F', 'T', '1'};
|
|
35
|
-
|
|
36
|
-
// How long a finished transfer is kept queryable before being purged.
|
|
37
|
-
constexpr auto FINISHED_RETENTION = std::chrono::minutes(5);
|
|
38
|
-
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Small helpers
|
|
41
|
-
// =============================================================================
|
|
42
|
-
|
|
43
|
-
// --- big-endian serialization ---
|
|
44
|
-
void put_u16(std::vector<uint8_t>& b, uint16_t v) {
|
|
45
|
-
b.push_back(uint8_t(v >> 8));
|
|
46
|
-
b.push_back(uint8_t(v));
|
|
47
|
-
}
|
|
48
|
-
void put_u32(std::vector<uint8_t>& b, uint32_t v) {
|
|
49
|
-
b.push_back(uint8_t(v >> 24));
|
|
50
|
-
b.push_back(uint8_t(v >> 16));
|
|
51
|
-
b.push_back(uint8_t(v >> 8));
|
|
52
|
-
b.push_back(uint8_t(v));
|
|
53
|
-
}
|
|
54
|
-
void put_u64(std::vector<uint8_t>& b, uint64_t v) {
|
|
55
|
-
for (int s = 56; s >= 0; s -= 8) b.push_back(uint8_t(v >> s));
|
|
56
|
-
}
|
|
57
|
-
uint16_t get_u16(const uint8_t* p) { return uint16_t(p[0]) << 8 | p[1]; }
|
|
58
|
-
uint32_t get_u32(const uint8_t* p) {
|
|
59
|
-
return uint32_t(p[0]) << 24 | uint32_t(p[1]) << 16 | uint32_t(p[2]) << 8 | p[3];
|
|
60
|
-
}
|
|
61
|
-
uint64_t get_u64(const uint8_t* p) {
|
|
62
|
-
uint64_t v = 0;
|
|
63
|
-
for (int i = 0; i < 8; ++i) v = (v << 8) | p[i];
|
|
64
|
-
return v;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// --- CRC32 (IEEE 802.3) for per-chunk integrity ---
|
|
68
|
-
struct Crc32Table {
|
|
69
|
-
uint32_t t[256];
|
|
70
|
-
Crc32Table() {
|
|
71
|
-
for (uint32_t i = 0; i < 256; ++i) {
|
|
72
|
-
uint32_t c = i;
|
|
73
|
-
for (int k = 0; k < 8; ++k) c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
|
|
74
|
-
t[i] = c;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
uint32_t crc32(const uint8_t* data, size_t len) {
|
|
79
|
-
static const Crc32Table tbl;
|
|
80
|
-
uint32_t c = 0xFFFFFFFFu;
|
|
81
|
-
for (size_t i = 0; i < len; ++i) c = tbl.t[(c ^ data[i]) & 0xFF] ^ (c >> 8);
|
|
82
|
-
return c ^ 0xFFFFFFFFu;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- hex encoding ---
|
|
86
|
-
std::string to_hex(const uint8_t* d, size_t n) {
|
|
87
|
-
static const char* h = "0123456789abcdef";
|
|
88
|
-
std::string s(n * 2, '0');
|
|
89
|
-
for (size_t i = 0; i < n; ++i) {
|
|
90
|
-
s[2 * i] = h[d[i] >> 4];
|
|
91
|
-
s[2 * i + 1] = h[d[i] & 0xF];
|
|
92
|
-
}
|
|
93
|
-
return s;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// SHA-256 of an empty input, used for zero-byte files.
|
|
97
|
-
std::string sha256_of_empty() {
|
|
98
|
-
uint8_t digest[SHA256_HASH_SIZE];
|
|
99
|
-
sha256_hash(digest, nullptr, 0);
|
|
100
|
-
return to_hex(digest, SHA256_HASH_SIZE);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// --- random transfer id ---
|
|
104
|
-
std::string random_transfer_id() {
|
|
105
|
-
static const char* h = "0123456789abcdef";
|
|
106
|
-
std::random_device rd;
|
|
107
|
-
std::mt19937 gen(rd() ^ uint32_t(std::chrono::steady_clock::now().time_since_epoch().count()));
|
|
108
|
-
std::uniform_int_distribution<int> dis(0, 15);
|
|
109
|
-
std::string id(32, '0');
|
|
110
|
-
for (char& c : id) c = h[dis(gen)];
|
|
111
|
-
return id;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// A relative path coming from a peer must not escape the destination directory.
|
|
115
|
-
bool is_safe_relative_path(const std::string& p) {
|
|
116
|
-
if (p.empty()) return false;
|
|
117
|
-
if (p.front() == '/' || p.front() == '\\') return false;
|
|
118
|
-
if (p.size() >= 2 && p[1] == ':') return false; // Windows drive letter
|
|
119
|
-
size_t start = 0;
|
|
120
|
-
for (size_t i = 0; i <= p.size(); ++i) {
|
|
121
|
-
if (i == p.size() || p[i] == '/' || p[i] == '\\') {
|
|
122
|
-
std::string comp = p.substr(start, i - start);
|
|
123
|
-
if (comp.empty() || comp == "." || comp == "..") return false;
|
|
124
|
-
start = i + 1;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
} // namespace
|
|
131
|
-
|
|
132
|
-
// =============================================================================
|
|
133
|
-
// Internal per-transfer state
|
|
134
|
-
// =============================================================================
|
|
135
|
-
|
|
136
|
-
// One file within a transfer.
|
|
137
|
-
struct TransferFile {
|
|
138
|
-
std::string relative_path; // POSIX path relative to the transfer root
|
|
139
|
-
uint64_t size = 0;
|
|
140
|
-
|
|
141
|
-
// sender side
|
|
142
|
-
std::string source_path; // local path the data is read from
|
|
143
|
-
|
|
144
|
-
// receiver side
|
|
145
|
-
std::string temp_path; // chunks are written here first
|
|
146
|
-
std::string final_path; // destination once verified
|
|
147
|
-
uint64_t received = 0; // bytes written so far
|
|
148
|
-
bool temp_created = false;
|
|
149
|
-
std::string expected_sha; // from ft_file_end
|
|
150
|
-
std::string computed_sha; // hashed while receiving
|
|
151
|
-
bool sha_known = false;
|
|
152
|
-
bool finalized = false;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// Full state of a single transfer. Always held through a shared_ptr; `mtx`
|
|
156
|
-
// guards every field below `total_bytes`.
|
|
157
|
-
struct Transfer {
|
|
158
|
-
// immutable after creation
|
|
159
|
-
std::string id;
|
|
160
|
-
std::string peer_id;
|
|
161
|
-
FileTransferDirection direction;
|
|
162
|
-
bool is_directory = false;
|
|
163
|
-
std::string name;
|
|
164
|
-
std::string local_root; // sender: source path; receiver: destination
|
|
165
|
-
std::vector<TransferFile> files;
|
|
166
|
-
uint64_t total_bytes = 0;
|
|
167
|
-
|
|
168
|
-
// mutable state
|
|
169
|
-
std::mutex mtx;
|
|
170
|
-
std::condition_variable cv;
|
|
171
|
-
FileTransferStatus status = FileTransferStatus::PENDING;
|
|
172
|
-
bool finished = false; // finish() has run (fired callbacks); distinct from status
|
|
173
|
-
std::string error;
|
|
174
|
-
uint64_t bytes_done = 0; // sender: streamed bytes; receiver: written bytes
|
|
175
|
-
uint64_t acked_bytes = 0; // sender: bytes confirmed by the peer
|
|
176
|
-
uint32_t files_done = 0;
|
|
177
|
-
|
|
178
|
-
// sender streaming cursor (survives pause)
|
|
179
|
-
size_t send_file = 0;
|
|
180
|
-
uint64_t send_offset = 0;
|
|
181
|
-
sha256_context_t send_hash;
|
|
182
|
-
bool worker_active = false;
|
|
183
|
-
|
|
184
|
-
// receiver cursor
|
|
185
|
-
size_t recv_file = 0;
|
|
186
|
-
sha256_context_t recv_hash;
|
|
187
|
-
uint64_t last_ack_sent = 0; // bytes_done at the last ft_progress emitted
|
|
188
|
-
|
|
189
|
-
// timing
|
|
190
|
-
std::chrono::steady_clock::time_point start_time;
|
|
191
|
-
std::chrono::steady_clock::time_point last_activity;
|
|
192
|
-
std::chrono::steady_clock::time_point last_progress_cb;
|
|
193
|
-
|
|
194
|
-
// throughput
|
|
195
|
-
double rate_bps = 0.0;
|
|
196
|
-
uint64_t rate_mark_bytes = 0;
|
|
197
|
-
std::chrono::steady_clock::time_point rate_mark_time;
|
|
198
|
-
|
|
199
|
-
bool is_terminal() const {
|
|
200
|
-
return status == FileTransferStatus::COMPLETED ||
|
|
201
|
-
status == FileTransferStatus::FAILED ||
|
|
202
|
-
status == FileTransferStatus::CANCELLED;
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
// =============================================================================
|
|
207
|
-
// Status name
|
|
208
|
-
// =============================================================================
|
|
209
|
-
|
|
210
|
-
const char* file_transfer_status_name(FileTransferStatus s) {
|
|
211
|
-
switch (s) {
|
|
212
|
-
case FileTransferStatus::PENDING: return "PENDING";
|
|
213
|
-
case FileTransferStatus::STARTING: return "STARTING";
|
|
214
|
-
case FileTransferStatus::IN_PROGRESS: return "IN_PROGRESS";
|
|
215
|
-
case FileTransferStatus::PAUSED: return "PAUSED";
|
|
216
|
-
case FileTransferStatus::COMPLETED: return "COMPLETED";
|
|
217
|
-
case FileTransferStatus::FAILED: return "FAILED";
|
|
218
|
-
case FileTransferStatus::CANCELLED: return "CANCELLED";
|
|
219
|
-
case FileTransferStatus::RESUMING: return "RESUMING";
|
|
220
|
-
}
|
|
221
|
-
return "UNKNOWN";
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// =============================================================================
|
|
225
|
-
// Construction / destruction
|
|
226
|
-
// =============================================================================
|
|
227
|
-
|
|
228
|
-
FileTransferManager::FileTransferManager(RatsClient& client, const FileTransferConfig& config)
|
|
229
|
-
: client_(client), config_(config) {
|
|
230
|
-
started_at_ = std::chrono::steady_clock::now();
|
|
231
|
-
create_directories(config_.temp_directory.c_str());
|
|
232
|
-
register_handlers();
|
|
233
|
-
|
|
234
|
-
uint32_t threads = std::max<uint32_t>(1, config_.worker_threads);
|
|
235
|
-
for (uint32_t i = 0; i < threads; ++i) {
|
|
236
|
-
workers_.emplace_back(&FileTransferManager::worker_loop, this);
|
|
237
|
-
}
|
|
238
|
-
maintenance_thread_ = std::thread(&FileTransferManager::maintenance_loop, this);
|
|
239
|
-
|
|
240
|
-
LOG_FT_INFO("FileTransferManager started (" << threads << " workers)");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
FileTransferManager::~FileTransferManager() {
|
|
244
|
-
running_.store(false);
|
|
245
|
-
queue_cv_.notify_all();
|
|
246
|
-
maintenance_cv_.notify_all();
|
|
247
|
-
|
|
248
|
-
// Wake any worker blocked on a transfer's condition variable.
|
|
249
|
-
{
|
|
250
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
251
|
-
for (auto& kv : transfers_) {
|
|
252
|
-
std::lock_guard<std::mutex> tk(kv.second->mtx);
|
|
253
|
-
kv.second->cv.notify_all();
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (auto& w : workers_) {
|
|
258
|
-
if (w.joinable()) w.join();
|
|
259
|
-
}
|
|
260
|
-
if (maintenance_thread_.joinable()) maintenance_thread_.join();
|
|
261
|
-
LOG_FT_INFO("FileTransferManager stopped");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
void FileTransferManager::register_handlers() {
|
|
265
|
-
client_.on(MSG_OFFER, [this](const std::string& p, const nlohmann::json& m) { on_offer(p, m); });
|
|
266
|
-
client_.on(MSG_RESPONSE, [this](const std::string& p, const nlohmann::json& m) { on_response(p, m); });
|
|
267
|
-
client_.on(MSG_FILE_END, [this](const std::string& p, const nlohmann::json& m) { on_file_end(p, m); });
|
|
268
|
-
client_.on(MSG_PROGRESS, [this](const std::string& p, const nlohmann::json& m) { on_progress(p, m); });
|
|
269
|
-
client_.on(MSG_COMPLETE, [this](const std::string& p, const nlohmann::json& m) { on_complete(p, m); });
|
|
270
|
-
client_.on(MSG_CONTROL, [this](const std::string& p, const nlohmann::json& m) { on_control(p, m); });
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// =============================================================================
|
|
274
|
-
// Configuration
|
|
275
|
-
// =============================================================================
|
|
276
|
-
|
|
277
|
-
void FileTransferManager::set_config(const FileTransferConfig& config) {
|
|
278
|
-
std::lock_guard<std::mutex> lk(config_mutex_);
|
|
279
|
-
config_ = config;
|
|
280
|
-
create_directories(config_.temp_directory.c_str());
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
FileTransferConfig FileTransferManager::get_config() const {
|
|
284
|
-
std::lock_guard<std::mutex> lk(config_mutex_);
|
|
285
|
-
return config_;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// =============================================================================
|
|
289
|
-
// Callbacks
|
|
290
|
-
// =============================================================================
|
|
291
|
-
|
|
292
|
-
void FileTransferManager::set_offer_callback(TransferOfferCallback cb) {
|
|
293
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
294
|
-
offer_callback_ = std::move(cb);
|
|
295
|
-
}
|
|
296
|
-
void FileTransferManager::set_progress_callback(TransferProgressCallback cb) {
|
|
297
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
298
|
-
progress_callback_ = std::move(cb);
|
|
299
|
-
}
|
|
300
|
-
void FileTransferManager::set_completed_callback(TransferCompletedCallback cb) {
|
|
301
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
302
|
-
completed_callback_ = std::move(cb);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// =============================================================================
|
|
306
|
-
// Lookup / progress snapshot
|
|
307
|
-
// =============================================================================
|
|
308
|
-
|
|
309
|
-
std::shared_ptr<Transfer> FileTransferManager::find(const std::string& id) const {
|
|
310
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
311
|
-
auto it = transfers_.find(id);
|
|
312
|
-
return it == transfers_.end() ? nullptr : it->second;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Builds a progress snapshot. Caller must hold t->mtx.
|
|
316
|
-
FileTransferProgress FileTransferManager::snapshot(const std::shared_ptr<Transfer>& t) const {
|
|
317
|
-
FileTransferProgress p;
|
|
318
|
-
p.transfer_id = t->id;
|
|
319
|
-
p.peer_id = t->peer_id;
|
|
320
|
-
p.direction = t->direction;
|
|
321
|
-
p.status = t->status;
|
|
322
|
-
p.filename = t->name;
|
|
323
|
-
p.local_path = t->local_root;
|
|
324
|
-
p.is_directory = t->is_directory;
|
|
325
|
-
p.bytes_transferred = t->bytes_done;
|
|
326
|
-
p.total_bytes = t->total_bytes;
|
|
327
|
-
p.files_completed = t->files_done;
|
|
328
|
-
p.total_files = static_cast<uint32_t>(t->files.size());
|
|
329
|
-
p.transfer_rate_bps = t->rate_bps;
|
|
330
|
-
p.error_message = t->error;
|
|
331
|
-
|
|
332
|
-
auto now = std::chrono::steady_clock::now();
|
|
333
|
-
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->start_time);
|
|
334
|
-
p.elapsed_time = elapsed;
|
|
335
|
-
if (elapsed.count() > 0) {
|
|
336
|
-
p.average_rate_bps = static_cast<double>(t->bytes_done) * 1000.0 / elapsed.count();
|
|
337
|
-
}
|
|
338
|
-
if (t->rate_bps > 1.0 && t->total_bytes > t->bytes_done) {
|
|
339
|
-
double secs = static_cast<double>(t->total_bytes - t->bytes_done) / t->rate_bps;
|
|
340
|
-
p.estimated_time_remaining = std::chrono::milliseconds(static_cast<int64_t>(secs * 1000.0));
|
|
341
|
-
}
|
|
342
|
-
return p;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
std::shared_ptr<FileTransferProgress>
|
|
346
|
-
FileTransferManager::get_progress(const std::string& id) const {
|
|
347
|
-
auto t = find(id);
|
|
348
|
-
if (!t) return nullptr;
|
|
349
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
350
|
-
return std::make_shared<FileTransferProgress>(snapshot(t));
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
std::vector<std::shared_ptr<FileTransferProgress>>
|
|
354
|
-
FileTransferManager::get_active_transfers() const {
|
|
355
|
-
std::vector<std::shared_ptr<Transfer>> all;
|
|
356
|
-
{
|
|
357
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
358
|
-
for (auto& kv : transfers_) all.push_back(kv.second);
|
|
359
|
-
}
|
|
360
|
-
std::vector<std::shared_ptr<FileTransferProgress>> out;
|
|
361
|
-
for (auto& t : all) {
|
|
362
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
363
|
-
if (!t->is_terminal()) {
|
|
364
|
-
out.push_back(std::make_shared<FileTransferProgress>(snapshot(t)));
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return out;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
nlohmann::json FileTransferManager::get_statistics() const {
|
|
371
|
-
nlohmann::json j;
|
|
372
|
-
{
|
|
373
|
-
std::lock_guard<std::mutex> lk(stats_mutex_);
|
|
374
|
-
auto uptime = std::chrono::duration_cast<std::chrono::seconds>(
|
|
375
|
-
std::chrono::steady_clock::now() - started_at_);
|
|
376
|
-
j["uptime_seconds"] = uptime.count();
|
|
377
|
-
j["total_bytes_sent"] = stat_bytes_sent_;
|
|
378
|
-
j["total_bytes_received"] = stat_bytes_received_;
|
|
379
|
-
j["total_files_sent"] = stat_files_sent_;
|
|
380
|
-
j["total_files_received"] = stat_files_received_;
|
|
381
|
-
j["completed_transfers"] = stat_completed_;
|
|
382
|
-
j["failed_transfers"] = stat_failed_;
|
|
383
|
-
uint64_t total = stat_completed_ + stat_failed_;
|
|
384
|
-
j["success_rate"] = total ? static_cast<double>(stat_completed_) / total : 0.0;
|
|
385
|
-
}
|
|
386
|
-
{
|
|
387
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
388
|
-
size_t active = 0;
|
|
389
|
-
for (auto& kv : transfers_) {
|
|
390
|
-
std::lock_guard<std::mutex> tk(kv.second->mtx);
|
|
391
|
-
if (!kv.second->is_terminal()) ++active;
|
|
392
|
-
}
|
|
393
|
-
j["active_transfers"] = active;
|
|
394
|
-
}
|
|
395
|
-
return j;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// =============================================================================
|
|
399
|
-
// Progress / completion notification
|
|
400
|
-
// =============================================================================
|
|
401
|
-
|
|
402
|
-
void FileTransferManager::emit_progress(const std::shared_ptr<Transfer>& t) {
|
|
403
|
-
FileTransferProgress snap;
|
|
404
|
-
{
|
|
405
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
406
|
-
auto now = std::chrono::steady_clock::now();
|
|
407
|
-
|
|
408
|
-
// Refresh the throughput estimate roughly twice a second.
|
|
409
|
-
auto dt = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->rate_mark_time);
|
|
410
|
-
if (dt.count() >= 500) {
|
|
411
|
-
t->rate_bps = static_cast<double>(t->bytes_done - t->rate_mark_bytes) * 1000.0 / dt.count();
|
|
412
|
-
t->rate_mark_bytes = t->bytes_done;
|
|
413
|
-
t->rate_mark_time = now;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// Throttle callbacks to ~10/s unless the transfer just finished.
|
|
417
|
-
auto since_cb = std::chrono::duration_cast<std::chrono::milliseconds>(now - t->last_progress_cb);
|
|
418
|
-
if (!t->is_terminal() && since_cb.count() < 100) return;
|
|
419
|
-
t->last_progress_cb = now;
|
|
420
|
-
|
|
421
|
-
snap = snapshot(t);
|
|
422
|
-
}
|
|
423
|
-
TransferProgressCallback cb;
|
|
424
|
-
{
|
|
425
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
426
|
-
cb = progress_callback_;
|
|
427
|
-
}
|
|
428
|
-
if (cb) cb(snap);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
void FileTransferManager::finish(const std::shared_ptr<Transfer>& t, bool success,
|
|
432
|
-
const std::string& error) {
|
|
433
|
-
FileTransferProgress snap;
|
|
434
|
-
bool is_receiver;
|
|
435
|
-
size_t file_count;
|
|
436
|
-
{
|
|
437
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
438
|
-
if (t->finished) return; // finish() already ran for this transfer
|
|
439
|
-
t->finished = true;
|
|
440
|
-
// Preserve an explicit CANCELLED status set by the cancel path.
|
|
441
|
-
if (!success && t->status == FileTransferStatus::CANCELLED) {
|
|
442
|
-
// keep CANCELLED
|
|
443
|
-
} else {
|
|
444
|
-
t->status = success ? FileTransferStatus::COMPLETED : FileTransferStatus::FAILED;
|
|
445
|
-
}
|
|
446
|
-
t->error = error;
|
|
447
|
-
t->last_activity = std::chrono::steady_clock::now();
|
|
448
|
-
t->last_progress_cb = {}; // force the final progress callback through
|
|
449
|
-
is_receiver = (t->direction == FileTransferDirection::RECEIVING);
|
|
450
|
-
file_count = t->files.size();
|
|
451
|
-
t->cv.notify_all();
|
|
452
|
-
snap = snapshot(t);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (!success && is_receiver) {
|
|
456
|
-
cleanup_temp_files(t);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
{
|
|
460
|
-
std::lock_guard<std::mutex> lk(stats_mutex_);
|
|
461
|
-
if (success) {
|
|
462
|
-
++stat_completed_;
|
|
463
|
-
if (is_receiver) stat_files_received_ += file_count;
|
|
464
|
-
else stat_files_sent_ += file_count;
|
|
465
|
-
} else {
|
|
466
|
-
++stat_failed_;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
TransferProgressCallback pcb;
|
|
471
|
-
TransferCompletedCallback ccb;
|
|
472
|
-
{
|
|
473
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
474
|
-
pcb = progress_callback_;
|
|
475
|
-
ccb = completed_callback_;
|
|
476
|
-
}
|
|
477
|
-
if (pcb) pcb(snap);
|
|
478
|
-
if (ccb) ccb(t->id, success, error);
|
|
479
|
-
|
|
480
|
-
LOG_FT_INFO("Transfer " << t->id << " " << (success ? "completed" : "ended")
|
|
481
|
-
<< (error.empty() ? "" : (": " + error)));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
void FileTransferManager::cleanup_temp_files(const std::shared_ptr<Transfer>& t) {
|
|
485
|
-
std::vector<std::string> temps;
|
|
486
|
-
{
|
|
487
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
488
|
-
for (auto& f : t->files) {
|
|
489
|
-
if (f.temp_created && !f.finalized) temps.push_back(f.temp_path);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
for (auto& p : temps) {
|
|
493
|
-
if (file_exists(p)) delete_file(p.c_str());
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// =============================================================================
|
|
498
|
-
// Sending: send_file / send_directory
|
|
499
|
-
// =============================================================================
|
|
500
|
-
|
|
501
|
-
namespace {
|
|
502
|
-
// Recursively collects regular files under `abs_dir` into `out`.
|
|
503
|
-
void scan_directory(const std::string& abs_dir, const std::string& rel_prefix,
|
|
504
|
-
std::vector<TransferFile>& out) {
|
|
505
|
-
std::vector<DirectoryEntry> entries;
|
|
506
|
-
if (!list_directory(abs_dir.c_str(), entries)) return;
|
|
507
|
-
for (const auto& e : entries) {
|
|
508
|
-
std::string rel = rel_prefix.empty() ? e.name : rel_prefix + "/" + e.name;
|
|
509
|
-
if (e.is_directory) {
|
|
510
|
-
scan_directory(e.path, rel, out);
|
|
511
|
-
} else {
|
|
512
|
-
TransferFile f;
|
|
513
|
-
f.relative_path = rel;
|
|
514
|
-
int64_t sz = get_file_size(e.path.c_str());
|
|
515
|
-
f.size = sz > 0 ? static_cast<uint64_t>(sz) : 0;
|
|
516
|
-
f.source_path = e.path;
|
|
517
|
-
out.push_back(std::move(f));
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
} // namespace
|
|
522
|
-
|
|
523
|
-
std::string FileTransferManager::send_file(const std::string& peer_id, const std::string& file_path,
|
|
524
|
-
const std::string& remote_name) {
|
|
525
|
-
if (!file_exists(file_path) || is_directory(file_path.c_str())) {
|
|
526
|
-
LOG_FT_ERROR("send_file: not a readable file: " << file_path);
|
|
527
|
-
return "";
|
|
528
|
-
}
|
|
529
|
-
int64_t sz = get_file_size(file_path.c_str());
|
|
530
|
-
if (sz < 0) {
|
|
531
|
-
LOG_FT_ERROR("send_file: cannot stat: " << file_path);
|
|
532
|
-
return "";
|
|
533
|
-
}
|
|
534
|
-
std::string name = remote_name.empty() ? get_filename_from_path(file_path) : remote_name;
|
|
535
|
-
|
|
536
|
-
auto t = std::make_shared<Transfer>();
|
|
537
|
-
t->id = random_transfer_id();
|
|
538
|
-
t->peer_id = peer_id;
|
|
539
|
-
t->direction = FileTransferDirection::SENDING;
|
|
540
|
-
t->is_directory = false;
|
|
541
|
-
t->name = name;
|
|
542
|
-
t->local_root = file_path;
|
|
543
|
-
t->total_bytes = static_cast<uint64_t>(sz);
|
|
544
|
-
|
|
545
|
-
TransferFile f;
|
|
546
|
-
f.relative_path = name;
|
|
547
|
-
f.size = t->total_bytes;
|
|
548
|
-
f.source_path = file_path;
|
|
549
|
-
t->files.push_back(std::move(f));
|
|
550
|
-
|
|
551
|
-
return start_send(peer_id, t, name);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
std::string FileTransferManager::send_directory(const std::string& peer_id,
|
|
555
|
-
const std::string& directory_path,
|
|
556
|
-
const std::string& remote_name) {
|
|
557
|
-
if (!directory_exists(directory_path)) {
|
|
558
|
-
LOG_FT_ERROR("send_directory: not a directory: " << directory_path);
|
|
559
|
-
return "";
|
|
560
|
-
}
|
|
561
|
-
std::string name = remote_name.empty() ? get_filename_from_path(directory_path) : remote_name;
|
|
562
|
-
|
|
563
|
-
auto t = std::make_shared<Transfer>();
|
|
564
|
-
t->id = random_transfer_id();
|
|
565
|
-
t->peer_id = peer_id;
|
|
566
|
-
t->direction = FileTransferDirection::SENDING;
|
|
567
|
-
t->is_directory = true;
|
|
568
|
-
t->name = name;
|
|
569
|
-
t->local_root = directory_path;
|
|
570
|
-
|
|
571
|
-
scan_directory(directory_path, "", t->files);
|
|
572
|
-
for (auto& f : t->files) t->total_bytes += f.size;
|
|
573
|
-
|
|
574
|
-
return start_send(peer_id, t, name);
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Registers a freshly-built sending transfer and sends its offer.
|
|
578
|
-
std::string FileTransferManager::start_send(const std::string& peer_id,
|
|
579
|
-
const std::shared_ptr<Transfer>& t,
|
|
580
|
-
const std::string& /*name*/) {
|
|
581
|
-
t->status = FileTransferStatus::STARTING;
|
|
582
|
-
t->start_time = std::chrono::steady_clock::now();
|
|
583
|
-
t->last_activity = t->start_time;
|
|
584
|
-
t->rate_mark_time = t->start_time;
|
|
585
|
-
|
|
586
|
-
{
|
|
587
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
588
|
-
transfers_[t->id] = t;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
nlohmann::json offer;
|
|
592
|
-
offer["transfer_id"] = t->id;
|
|
593
|
-
offer["name"] = t->name;
|
|
594
|
-
offer["is_directory"] = t->is_directory;
|
|
595
|
-
offer["total_size"] = t->total_bytes;
|
|
596
|
-
nlohmann::json files = nlohmann::json::array();
|
|
597
|
-
for (const auto& f : t->files) {
|
|
598
|
-
files.push_back({{"path", f.relative_path}, {"size", f.size}});
|
|
599
|
-
}
|
|
600
|
-
offer["files"] = files;
|
|
601
|
-
|
|
602
|
-
client_.send(peer_id, MSG_OFFER, offer);
|
|
603
|
-
LOG_FT_INFO("Offering " << (t->is_directory ? "directory '" : "file '") << t->name
|
|
604
|
-
<< "' (" << t->files.size() << " file(s), " << t->total_bytes
|
|
605
|
-
<< " bytes) to " << peer_id << " [" << t->id << "]");
|
|
606
|
-
return t->id;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
// =============================================================================
|
|
611
|
-
// Sending: streaming worker
|
|
612
|
-
// =============================================================================
|
|
613
|
-
|
|
614
|
-
void FileTransferManager::queue_send(const std::string& id) {
|
|
615
|
-
{
|
|
616
|
-
std::lock_guard<std::mutex> lk(queue_mutex_);
|
|
617
|
-
send_queue_.push(id);
|
|
618
|
-
}
|
|
619
|
-
queue_cv_.notify_one();
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
void FileTransferManager::worker_loop() {
|
|
623
|
-
while (running_.load()) {
|
|
624
|
-
std::string id;
|
|
625
|
-
{
|
|
626
|
-
std::unique_lock<std::mutex> lk(queue_mutex_);
|
|
627
|
-
queue_cv_.wait(lk, [this] { return !running_.load() || !send_queue_.empty(); });
|
|
628
|
-
if (!running_.load()) return;
|
|
629
|
-
id = send_queue_.front();
|
|
630
|
-
send_queue_.pop();
|
|
631
|
-
}
|
|
632
|
-
auto t = find(id);
|
|
633
|
-
if (!t) continue;
|
|
634
|
-
|
|
635
|
-
{
|
|
636
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
637
|
-
if (t->worker_active) continue; // another worker already owns it
|
|
638
|
-
t->worker_active = true;
|
|
639
|
-
}
|
|
640
|
-
run_send(t);
|
|
641
|
-
{
|
|
642
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
643
|
-
t->worker_active = false;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
void FileTransferManager::run_send(const std::shared_ptr<Transfer>& t) {
|
|
649
|
-
FileTransferConfig cfg = get_config();
|
|
650
|
-
std::vector<uint8_t> buf(cfg.chunk_size);
|
|
651
|
-
|
|
652
|
-
while (running_.load()) {
|
|
653
|
-
size_t file_index;
|
|
654
|
-
uint64_t offset;
|
|
655
|
-
uint64_t file_size;
|
|
656
|
-
std::string source_path;
|
|
657
|
-
{
|
|
658
|
-
std::unique_lock<std::mutex> lk(t->mtx);
|
|
659
|
-
if (t->status == FileTransferStatus::RESUMING) {
|
|
660
|
-
t->status = FileTransferStatus::IN_PROGRESS;
|
|
661
|
-
}
|
|
662
|
-
if (t->status == FileTransferStatus::PAUSED) return; // cursor preserved
|
|
663
|
-
if (t->status != FileTransferStatus::IN_PROGRESS) return; // cancelled / failed
|
|
664
|
-
if (t->send_file >= t->files.size()) break; // all data sent
|
|
665
|
-
|
|
666
|
-
file_index = t->send_file;
|
|
667
|
-
offset = t->send_offset;
|
|
668
|
-
file_size = t->files[file_index].size;
|
|
669
|
-
source_path = t->files[file_index].source_path;
|
|
670
|
-
if (offset == 0) sha256_reset(&t->send_hash);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Empty file: no chunks, just the end marker.
|
|
674
|
-
if (file_size == 0) {
|
|
675
|
-
nlohmann::json end{{"transfer_id", t->id}, {"file_index", file_index},
|
|
676
|
-
{"sha256", sha256_of_empty()}};
|
|
677
|
-
client_.send(t->peer_id, MSG_FILE_END, end);
|
|
678
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
679
|
-
t->send_file++;
|
|
680
|
-
t->send_offset = 0;
|
|
681
|
-
t->files_done++;
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(cfg.chunk_size, file_size - offset));
|
|
686
|
-
if (!read_file_chunk(source_path, offset, buf.data(), want)) {
|
|
687
|
-
nlohmann::json done{{"transfer_id", t->id}, {"success", false},
|
|
688
|
-
{"error", "sender failed to read file"}};
|
|
689
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
690
|
-
finish(t, false, "failed to read " + source_path);
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
uint32_t crc = crc32(buf.data(), want);
|
|
695
|
-
|
|
696
|
-
// Build the chunk frame: magic | id | file_index | offset | len | crc | data.
|
|
697
|
-
std::vector<uint8_t> frame;
|
|
698
|
-
frame.reserve(4 + 2 + t->id.size() + 4 + 8 + 4 + 4 + want);
|
|
699
|
-
frame.insert(frame.end(), CHUNK_MAGIC, CHUNK_MAGIC + 4);
|
|
700
|
-
put_u16(frame, static_cast<uint16_t>(t->id.size()));
|
|
701
|
-
frame.insert(frame.end(), t->id.begin(), t->id.end());
|
|
702
|
-
put_u32(frame, static_cast<uint32_t>(file_index));
|
|
703
|
-
put_u64(frame, offset);
|
|
704
|
-
put_u32(frame, want);
|
|
705
|
-
put_u32(frame, crc);
|
|
706
|
-
frame.insert(frame.end(), buf.data(), buf.data() + want);
|
|
707
|
-
|
|
708
|
-
bool sent = client_.send_binary_to_peer_id(t->peer_id, frame, MessageDataType::BINARY);
|
|
709
|
-
if (!sent) {
|
|
710
|
-
finish(t, false, "connection lost while sending");
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
bool file_done = false;
|
|
715
|
-
std::string sha_hex;
|
|
716
|
-
{
|
|
717
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
718
|
-
sha256_update(&t->send_hash, buf.data(), want);
|
|
719
|
-
t->send_offset += want;
|
|
720
|
-
t->bytes_done += want;
|
|
721
|
-
t->last_activity = std::chrono::steady_clock::now();
|
|
722
|
-
if (t->send_offset >= file_size) {
|
|
723
|
-
uint8_t digest[SHA256_HASH_SIZE];
|
|
724
|
-
sha256_finish(&t->send_hash, digest);
|
|
725
|
-
sha_hex = to_hex(digest, SHA256_HASH_SIZE);
|
|
726
|
-
t->send_file++;
|
|
727
|
-
t->send_offset = 0;
|
|
728
|
-
t->files_done++;
|
|
729
|
-
file_done = true;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
{
|
|
733
|
-
std::lock_guard<std::mutex> lk(stats_mutex_);
|
|
734
|
-
stat_bytes_sent_ += want;
|
|
735
|
-
}
|
|
736
|
-
if (file_done) {
|
|
737
|
-
nlohmann::json end{{"transfer_id", t->id}, {"file_index", file_index},
|
|
738
|
-
{"sha256", sha_hex}};
|
|
739
|
-
client_.send(t->peer_id, MSG_FILE_END, end);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
emit_progress(t);
|
|
743
|
-
|
|
744
|
-
// Backpressure: do not get more than `window_bytes` ahead of the peer.
|
|
745
|
-
{
|
|
746
|
-
std::unique_lock<std::mutex> lk(t->mtx);
|
|
747
|
-
while (running_.load() && t->status == FileTransferStatus::IN_PROGRESS &&
|
|
748
|
-
t->bytes_done - t->acked_bytes >= cfg.window_bytes) {
|
|
749
|
-
t->cv.wait_for(lk, std::chrono::milliseconds(200));
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (!running_.load()) return;
|
|
755
|
-
|
|
756
|
-
// All data streamed; wait for the receiver's ft_complete.
|
|
757
|
-
{
|
|
758
|
-
std::unique_lock<std::mutex> lk(t->mtx);
|
|
759
|
-
while (running_.load() && t->status == FileTransferStatus::IN_PROGRESS) {
|
|
760
|
-
t->cv.wait_for(lk, std::chrono::milliseconds(200));
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
// =============================================================================
|
|
766
|
-
// Receiving: offer handling
|
|
767
|
-
// =============================================================================
|
|
768
|
-
|
|
769
|
-
void FileTransferManager::on_offer(const std::string& peer_id, const nlohmann::json& msg) {
|
|
770
|
-
try {
|
|
771
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
772
|
-
if (find(id)) return; // duplicate offer
|
|
773
|
-
|
|
774
|
-
auto t = std::make_shared<Transfer>();
|
|
775
|
-
t->id = id;
|
|
776
|
-
t->peer_id = peer_id;
|
|
777
|
-
t->direction = FileTransferDirection::RECEIVING;
|
|
778
|
-
t->is_directory = msg.value("is_directory", false);
|
|
779
|
-
t->name = msg.value("name", std::string("transfer"));
|
|
780
|
-
t->total_bytes = msg.value("total_size", uint64_t(0));
|
|
781
|
-
t->status = FileTransferStatus::PENDING;
|
|
782
|
-
t->start_time = std::chrono::steady_clock::now();
|
|
783
|
-
t->last_activity = t->start_time;
|
|
784
|
-
t->rate_mark_time = t->start_time;
|
|
785
|
-
|
|
786
|
-
bool unsafe = false;
|
|
787
|
-
for (const auto& fj : msg.at("files")) {
|
|
788
|
-
TransferFile f;
|
|
789
|
-
f.relative_path = fj.at("path").get<std::string>();
|
|
790
|
-
f.size = fj.value("size", uint64_t(0));
|
|
791
|
-
if (!is_safe_relative_path(f.relative_path)) unsafe = true;
|
|
792
|
-
t->files.push_back(std::move(f));
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
if (unsafe) {
|
|
796
|
-
LOG_FT_WARN("Rejecting offer " << id << " from " << peer_id << ": unsafe path in manifest");
|
|
797
|
-
nlohmann::json resp{{"transfer_id", id}, {"accepted", false},
|
|
798
|
-
{"reason", "unsafe path in manifest"}};
|
|
799
|
-
client_.send(peer_id, MSG_RESPONSE, resp);
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
{
|
|
804
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
805
|
-
transfers_[id] = t;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
IncomingTransferOffer offer;
|
|
809
|
-
offer.transfer_id = id;
|
|
810
|
-
offer.peer_id = peer_id;
|
|
811
|
-
offer.name = t->name;
|
|
812
|
-
offer.is_directory = t->is_directory;
|
|
813
|
-
offer.total_size = t->total_bytes;
|
|
814
|
-
for (const auto& f : t->files) offer.files.push_back({f.relative_path, f.size});
|
|
815
|
-
|
|
816
|
-
LOG_FT_INFO("Incoming offer [" << id << "] '" << t->name << "' from " << peer_id
|
|
817
|
-
<< " (" << t->files.size() << " file(s), "
|
|
818
|
-
<< t->total_bytes << " bytes)");
|
|
819
|
-
|
|
820
|
-
TransferOfferCallback cb;
|
|
821
|
-
{
|
|
822
|
-
std::lock_guard<std::mutex> lk(callbacks_mutex_);
|
|
823
|
-
cb = offer_callback_;
|
|
824
|
-
}
|
|
825
|
-
if (cb) {
|
|
826
|
-
cb(offer);
|
|
827
|
-
} else {
|
|
828
|
-
reject(id, "no offer handler configured");
|
|
829
|
-
}
|
|
830
|
-
} catch (const std::exception& e) {
|
|
831
|
-
LOG_FT_ERROR("Malformed ft_offer from " << peer_id << ": " << e.what());
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
bool FileTransferManager::accept(const std::string& transfer_id, const std::string& local_path) {
|
|
836
|
-
auto t = find(transfer_id);
|
|
837
|
-
if (!t || t->direction != FileTransferDirection::RECEIVING) return false;
|
|
838
|
-
|
|
839
|
-
FileTransferConfig cfg = get_config();
|
|
840
|
-
bool empty_transfer = false;
|
|
841
|
-
{
|
|
842
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
843
|
-
if (t->status != FileTransferStatus::PENDING) return false;
|
|
844
|
-
t->local_root = local_path;
|
|
845
|
-
for (size_t i = 0; i < t->files.size(); ++i) {
|
|
846
|
-
TransferFile& f = t->files[i];
|
|
847
|
-
f.final_path = t->is_directory ? combine_paths(local_path, f.relative_path)
|
|
848
|
-
: local_path;
|
|
849
|
-
f.temp_path = combine_paths(cfg.temp_directory,
|
|
850
|
-
transfer_id + "." + std::to_string(i) + ".part");
|
|
851
|
-
}
|
|
852
|
-
t->status = FileTransferStatus::IN_PROGRESS;
|
|
853
|
-
t->start_time = std::chrono::steady_clock::now();
|
|
854
|
-
t->last_activity = t->start_time;
|
|
855
|
-
t->rate_mark_time = t->start_time;
|
|
856
|
-
empty_transfer = t->files.empty();
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
create_directories(cfg.temp_directory.c_str());
|
|
860
|
-
nlohmann::json resp{{"transfer_id", transfer_id}, {"accepted", true}};
|
|
861
|
-
client_.send(t->peer_id, MSG_RESPONSE, resp);
|
|
862
|
-
LOG_FT_INFO("Accepted transfer [" << transfer_id << "] -> " << local_path);
|
|
863
|
-
|
|
864
|
-
if (t->is_directory) create_directories(local_path.c_str());
|
|
865
|
-
|
|
866
|
-
emit_progress(t);
|
|
867
|
-
|
|
868
|
-
// A transfer with no files (e.g. an empty directory) is done immediately.
|
|
869
|
-
if (empty_transfer) {
|
|
870
|
-
nlohmann::json done{{"transfer_id", transfer_id}, {"success", true}};
|
|
871
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
872
|
-
finish(t, true, "");
|
|
873
|
-
}
|
|
874
|
-
return true;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
bool FileTransferManager::reject(const std::string& transfer_id, const std::string& reason) {
|
|
878
|
-
auto t = find(transfer_id);
|
|
879
|
-
if (!t || t->direction != FileTransferDirection::RECEIVING) return false;
|
|
880
|
-
{
|
|
881
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
882
|
-
if (t->status != FileTransferStatus::PENDING) return false;
|
|
883
|
-
t->status = FileTransferStatus::CANCELLED;
|
|
884
|
-
}
|
|
885
|
-
nlohmann::json resp{{"transfer_id", transfer_id}, {"accepted", false}, {"reason", reason}};
|
|
886
|
-
client_.send(t->peer_id, MSG_RESPONSE, resp);
|
|
887
|
-
finish(t, false, reason.empty() ? "rejected" : ("rejected: " + reason));
|
|
888
|
-
return true;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// =============================================================================
|
|
892
|
-
// Receiving: chunk handling
|
|
893
|
-
// =============================================================================
|
|
894
|
-
|
|
895
|
-
bool FileTransferManager::handle_binary_data(const std::string& peer_id,
|
|
896
|
-
const std::vector<uint8_t>& data) {
|
|
897
|
-
if (data.size() < 4 || std::memcmp(data.data(), CHUNK_MAGIC, 4) != 0) {
|
|
898
|
-
return false; // not a file-transfer frame
|
|
899
|
-
}
|
|
900
|
-
const uint8_t* p = data.data();
|
|
901
|
-
size_t n = data.size();
|
|
902
|
-
size_t pos = 4;
|
|
903
|
-
|
|
904
|
-
if (pos + 2 > n) return true;
|
|
905
|
-
uint16_t id_len = get_u16(p + pos);
|
|
906
|
-
pos += 2;
|
|
907
|
-
if (pos + id_len > n) return true;
|
|
908
|
-
std::string id(reinterpret_cast<const char*>(p + pos), id_len);
|
|
909
|
-
pos += id_len;
|
|
910
|
-
if (pos + 4 + 8 + 4 + 4 > n) return true;
|
|
911
|
-
uint32_t file_index = get_u32(p + pos); pos += 4;
|
|
912
|
-
uint64_t offset = get_u64(p + pos); pos += 8;
|
|
913
|
-
uint32_t data_len = get_u32(p + pos); pos += 4;
|
|
914
|
-
uint32_t crc = get_u32(p + pos); pos += 4;
|
|
915
|
-
if (pos + data_len != n) {
|
|
916
|
-
LOG_FT_WARN("Chunk frame from " << peer_id << " has inconsistent length");
|
|
917
|
-
return true;
|
|
918
|
-
}
|
|
919
|
-
on_chunk(id, peer_id, file_index, offset, p + pos, data_len, crc);
|
|
920
|
-
return true;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
void FileTransferManager::on_chunk(const std::string& transfer_id, const std::string& peer_id,
|
|
924
|
-
uint32_t file_index, uint64_t offset,
|
|
925
|
-
const uint8_t* data, uint32_t len, uint32_t crc) {
|
|
926
|
-
auto t = find(transfer_id);
|
|
927
|
-
if (!t || t->direction != FileTransferDirection::RECEIVING) return;
|
|
928
|
-
FileTransferConfig cfg = get_config();
|
|
929
|
-
|
|
930
|
-
// Acknowledge at least twice per window so the sender never stalls waiting
|
|
931
|
-
// for a progress update it will not receive.
|
|
932
|
-
uint64_t ack_interval = std::min<uint64_t>(
|
|
933
|
-
cfg.progress_interval, std::max<uint32_t>(1, cfg.window_bytes / 2));
|
|
934
|
-
|
|
935
|
-
std::string temp_path;
|
|
936
|
-
uint64_t file_size = 0;
|
|
937
|
-
std::string fail_error;
|
|
938
|
-
{
|
|
939
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
940
|
-
// Only accept chunks while the transfer is live (PAUSED still drains
|
|
941
|
-
// chunks already in flight).
|
|
942
|
-
if (t->status != FileTransferStatus::IN_PROGRESS &&
|
|
943
|
-
t->status != FileTransferStatus::PAUSED) {
|
|
944
|
-
return;
|
|
945
|
-
}
|
|
946
|
-
if (file_index >= t->files.size() || file_index != t->recv_file) {
|
|
947
|
-
// Strict ordering is guaranteed by the reliable transport.
|
|
948
|
-
fail_error = "out-of-order file index";
|
|
949
|
-
} else {
|
|
950
|
-
TransferFile& f = t->files[file_index];
|
|
951
|
-
if (offset != f.received) {
|
|
952
|
-
fail_error = "out-of-order chunk offset";
|
|
953
|
-
} else if (offset + len > f.size) {
|
|
954
|
-
fail_error = "chunk exceeds declared file size";
|
|
955
|
-
} else {
|
|
956
|
-
temp_path = f.temp_path;
|
|
957
|
-
file_size = f.size;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
if (!fail_error.empty()) {
|
|
961
|
-
t->status = FileTransferStatus::FAILED;
|
|
962
|
-
t->error = fail_error;
|
|
963
|
-
t->cv.notify_all();
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
if (!fail_error.empty()) {
|
|
967
|
-
nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
|
|
968
|
-
{"error", fail_error}};
|
|
969
|
-
client_.send(peer_id, MSG_COMPLETE, done);
|
|
970
|
-
finish(t, false, fail_error);
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
if (cfg.verify_integrity && crc32(data, len) != crc) {
|
|
975
|
-
nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
|
|
976
|
-
{"error", "chunk CRC mismatch"}};
|
|
977
|
-
client_.send(peer_id, MSG_COMPLETE, done);
|
|
978
|
-
finish(t, false, "chunk CRC mismatch");
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Create the temp file on first contact, pre-sized to the declared length.
|
|
983
|
-
{
|
|
984
|
-
bool need_create;
|
|
985
|
-
{
|
|
986
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
987
|
-
need_create = !t->files[file_index].temp_created;
|
|
988
|
-
}
|
|
989
|
-
if (need_create) {
|
|
990
|
-
if (!create_file_with_size(temp_path.c_str(), file_size)) {
|
|
991
|
-
nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
|
|
992
|
-
{"error", "cannot create destination temp file"}};
|
|
993
|
-
client_.send(peer_id, MSG_COMPLETE, done);
|
|
994
|
-
finish(t, false, "cannot create temp file");
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
998
|
-
t->files[file_index].temp_created = true;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
if (!write_file_chunk(temp_path, offset, data, len)) {
|
|
1003
|
-
nlohmann::json done{{"transfer_id", transfer_id}, {"success", false},
|
|
1004
|
-
{"error", "receiver failed to write to disk"}};
|
|
1005
|
-
client_.send(peer_id, MSG_COMPLETE, done);
|
|
1006
|
-
finish(t, false, "failed to write chunk to disk");
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
bool data_complete = false;
|
|
1011
|
-
bool send_ack = false;
|
|
1012
|
-
uint64_t ack_bytes = 0;
|
|
1013
|
-
{
|
|
1014
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1015
|
-
TransferFile& f = t->files[file_index];
|
|
1016
|
-
if (f.received == 0) sha256_reset(&t->recv_hash);
|
|
1017
|
-
sha256_update(&t->recv_hash, data, len);
|
|
1018
|
-
f.received += len;
|
|
1019
|
-
t->bytes_done += len;
|
|
1020
|
-
t->last_activity = std::chrono::steady_clock::now();
|
|
1021
|
-
|
|
1022
|
-
if (f.received >= f.size) {
|
|
1023
|
-
uint8_t digest[SHA256_HASH_SIZE];
|
|
1024
|
-
sha256_finish(&t->recv_hash, digest);
|
|
1025
|
-
f.computed_sha = to_hex(digest, SHA256_HASH_SIZE);
|
|
1026
|
-
data_complete = true;
|
|
1027
|
-
}
|
|
1028
|
-
if (data_complete || t->bytes_done - t->last_ack_sent >= ack_interval) {
|
|
1029
|
-
t->last_ack_sent = t->bytes_done;
|
|
1030
|
-
ack_bytes = t->bytes_done;
|
|
1031
|
-
send_ack = true;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
{
|
|
1035
|
-
std::lock_guard<std::mutex> lk(stats_mutex_);
|
|
1036
|
-
stat_bytes_received_ += len;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (send_ack) {
|
|
1040
|
-
nlohmann::json prog{{"transfer_id", transfer_id}, {"bytes_received", ack_bytes}};
|
|
1041
|
-
client_.send(peer_id, MSG_PROGRESS, prog);
|
|
1042
|
-
}
|
|
1043
|
-
emit_progress(t);
|
|
1044
|
-
|
|
1045
|
-
if (data_complete) try_finalize_file(t, file_index);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
void FileTransferManager::on_file_end(const std::string& /*peer_id*/, const nlohmann::json& msg) {
|
|
1049
|
-
try {
|
|
1050
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
1051
|
-
auto t = find(id);
|
|
1052
|
-
if (!t || t->direction != FileTransferDirection::RECEIVING) return;
|
|
1053
|
-
size_t file_index = msg.at("file_index").get<size_t>();
|
|
1054
|
-
std::string sha = msg.value("sha256", std::string());
|
|
1055
|
-
{
|
|
1056
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1057
|
-
if (t->is_terminal() || file_index >= t->files.size()) return;
|
|
1058
|
-
TransferFile& f = t->files[file_index];
|
|
1059
|
-
if (f.finalized) return;
|
|
1060
|
-
f.expected_sha = sha;
|
|
1061
|
-
f.sha_known = true;
|
|
1062
|
-
// A zero-byte file produces no chunks, so hash it here.
|
|
1063
|
-
if (f.size == 0 && f.computed_sha.empty()) f.computed_sha = sha256_of_empty();
|
|
1064
|
-
}
|
|
1065
|
-
try_finalize_file(t, file_index);
|
|
1066
|
-
} catch (const std::exception& e) {
|
|
1067
|
-
LOG_FT_ERROR("Malformed ft_file_end: " << e.what());
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
// Moves a fully-received, verified file to its destination. When the last file
|
|
1072
|
-
// of the transfer is finalized, completes the whole transfer.
|
|
1073
|
-
void FileTransferManager::try_finalize_file(const std::shared_ptr<Transfer>& t, size_t file_index) {
|
|
1074
|
-
FileTransferConfig cfg = get_config();
|
|
1075
|
-
std::string final_path, temp_path, rel_path;
|
|
1076
|
-
bool do_finalize = false;
|
|
1077
|
-
bool sha_mismatch = false;
|
|
1078
|
-
bool zero_byte = false;
|
|
1079
|
-
{
|
|
1080
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1081
|
-
if (t->is_terminal() || file_index >= t->files.size()) return;
|
|
1082
|
-
TransferFile& f = t->files[file_index];
|
|
1083
|
-
if (f.finalized) return;
|
|
1084
|
-
if (f.received < f.size) return; // data not complete yet
|
|
1085
|
-
if (!f.sha_known) return; // ft_file_end not received yet
|
|
1086
|
-
|
|
1087
|
-
if (cfg.verify_integrity && f.computed_sha != f.expected_sha) {
|
|
1088
|
-
sha_mismatch = true;
|
|
1089
|
-
} else {
|
|
1090
|
-
do_finalize = true;
|
|
1091
|
-
final_path = f.final_path;
|
|
1092
|
-
temp_path = f.temp_path;
|
|
1093
|
-
rel_path = f.relative_path;
|
|
1094
|
-
zero_byte = (f.size == 0);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
if (sha_mismatch) {
|
|
1099
|
-
nlohmann::json done{{"transfer_id", t->id}, {"success", false},
|
|
1100
|
-
{"error", "SHA-256 mismatch"}};
|
|
1101
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
1102
|
-
finish(t, false, "SHA-256 mismatch for " + rel_path);
|
|
1103
|
-
return;
|
|
1104
|
-
}
|
|
1105
|
-
if (!do_finalize) return;
|
|
1106
|
-
|
|
1107
|
-
// Place the file at its destination (parent directories created as needed).
|
|
1108
|
-
std::string parent = get_parent_directory(final_path.c_str());
|
|
1109
|
-
if (!parent.empty()) create_directories(parent.c_str());
|
|
1110
|
-
if (file_exists(final_path)) delete_file(final_path.c_str());
|
|
1111
|
-
|
|
1112
|
-
bool ok;
|
|
1113
|
-
if (zero_byte) {
|
|
1114
|
-
ok = create_file_with_size(final_path.c_str(), 0);
|
|
1115
|
-
if (file_exists(temp_path)) delete_file(temp_path.c_str());
|
|
1116
|
-
} else {
|
|
1117
|
-
ok = rename_file(temp_path, final_path);
|
|
1118
|
-
if (!ok) {
|
|
1119
|
-
// rename fails across volumes; fall back to copy + delete.
|
|
1120
|
-
ok = copy_file(temp_path.c_str(), final_path.c_str());
|
|
1121
|
-
if (ok) delete_file(temp_path.c_str());
|
|
1122
|
-
}
|
|
1123
|
-
}
|
|
1124
|
-
if (!ok) {
|
|
1125
|
-
nlohmann::json done{{"transfer_id", t->id}, {"success", false},
|
|
1126
|
-
{"error", "cannot write destination file"}};
|
|
1127
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
1128
|
-
finish(t, false, "cannot move file to " + final_path);
|
|
1129
|
-
return;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
bool all_done = false;
|
|
1133
|
-
{
|
|
1134
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1135
|
-
TransferFile& f = t->files[file_index];
|
|
1136
|
-
f.finalized = true;
|
|
1137
|
-
t->files_done++;
|
|
1138
|
-
if (file_index == t->recv_file) t->recv_file++;
|
|
1139
|
-
all_done = true;
|
|
1140
|
-
for (const auto& ff : t->files) {
|
|
1141
|
-
if (!ff.finalized) { all_done = false; break; }
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
emit_progress(t);
|
|
1145
|
-
|
|
1146
|
-
if (all_done) {
|
|
1147
|
-
nlohmann::json done{{"transfer_id", t->id}, {"success", true}};
|
|
1148
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
1149
|
-
finish(t, true, "");
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// =============================================================================
|
|
1154
|
-
// Control message handlers
|
|
1155
|
-
// =============================================================================
|
|
1156
|
-
|
|
1157
|
-
void FileTransferManager::on_response(const std::string& /*peer_id*/, const nlohmann::json& msg) {
|
|
1158
|
-
try {
|
|
1159
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
1160
|
-
auto t = find(id);
|
|
1161
|
-
if (!t || t->direction != FileTransferDirection::SENDING) return;
|
|
1162
|
-
bool accepted = msg.value("accepted", false);
|
|
1163
|
-
|
|
1164
|
-
if (accepted) {
|
|
1165
|
-
{
|
|
1166
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1167
|
-
if (t->status != FileTransferStatus::STARTING) return;
|
|
1168
|
-
t->status = FileTransferStatus::IN_PROGRESS;
|
|
1169
|
-
t->start_time = std::chrono::steady_clock::now();
|
|
1170
|
-
t->last_activity = t->start_time;
|
|
1171
|
-
t->rate_mark_time = t->start_time;
|
|
1172
|
-
}
|
|
1173
|
-
LOG_FT_INFO("Transfer " << id << " accepted by peer");
|
|
1174
|
-
queue_send(id);
|
|
1175
|
-
} else {
|
|
1176
|
-
std::string reason = msg.value("reason", std::string("rejected by peer"));
|
|
1177
|
-
finish(t, false, reason);
|
|
1178
|
-
}
|
|
1179
|
-
} catch (const std::exception& e) {
|
|
1180
|
-
LOG_FT_ERROR("Malformed ft_response: " << e.what());
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
void FileTransferManager::on_progress(const std::string& /*peer_id*/, const nlohmann::json& msg) {
|
|
1185
|
-
try {
|
|
1186
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
1187
|
-
auto t = find(id);
|
|
1188
|
-
if (!t || t->direction != FileTransferDirection::SENDING) return;
|
|
1189
|
-
uint64_t acked = msg.value("bytes_received", uint64_t(0));
|
|
1190
|
-
{
|
|
1191
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1192
|
-
if (acked > t->acked_bytes) t->acked_bytes = acked;
|
|
1193
|
-
t->last_activity = std::chrono::steady_clock::now();
|
|
1194
|
-
t->cv.notify_all(); // wake the streaming worker if it was throttled
|
|
1195
|
-
}
|
|
1196
|
-
} catch (const std::exception& e) {
|
|
1197
|
-
LOG_FT_ERROR("Malformed ft_progress: " << e.what());
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
void FileTransferManager::on_complete(const std::string& /*peer_id*/, const nlohmann::json& msg) {
|
|
1202
|
-
try {
|
|
1203
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
1204
|
-
auto t = find(id);
|
|
1205
|
-
if (!t) return;
|
|
1206
|
-
bool success = msg.value("success", false);
|
|
1207
|
-
std::string error = msg.value("error", std::string());
|
|
1208
|
-
finish(t, success, success ? "" : (error.empty() ? "peer reported failure" : error));
|
|
1209
|
-
} catch (const std::exception& e) {
|
|
1210
|
-
LOG_FT_ERROR("Malformed ft_complete: " << e.what());
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
void FileTransferManager::on_control(const std::string& /*peer_id*/, const nlohmann::json& msg) {
|
|
1215
|
-
try {
|
|
1216
|
-
std::string id = msg.at("transfer_id").get<std::string>();
|
|
1217
|
-
std::string action = msg.at("action").get<std::string>();
|
|
1218
|
-
auto t = find(id);
|
|
1219
|
-
if (!t) return;
|
|
1220
|
-
|
|
1221
|
-
if (action == "pause") {
|
|
1222
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1223
|
-
if (t->status == FileTransferStatus::IN_PROGRESS) {
|
|
1224
|
-
t->status = FileTransferStatus::PAUSED;
|
|
1225
|
-
t->cv.notify_all();
|
|
1226
|
-
}
|
|
1227
|
-
} else if (action == "resume") {
|
|
1228
|
-
bool requeue = false;
|
|
1229
|
-
{
|
|
1230
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1231
|
-
if (t->status == FileTransferStatus::PAUSED) {
|
|
1232
|
-
if (t->direction == FileTransferDirection::SENDING) {
|
|
1233
|
-
t->status = FileTransferStatus::RESUMING;
|
|
1234
|
-
requeue = true;
|
|
1235
|
-
} else {
|
|
1236
|
-
t->status = FileTransferStatus::IN_PROGRESS;
|
|
1237
|
-
}
|
|
1238
|
-
t->cv.notify_all();
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
if (requeue) queue_send(id);
|
|
1242
|
-
} else if (action == "cancel") {
|
|
1243
|
-
{
|
|
1244
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1245
|
-
if (t->is_terminal()) return;
|
|
1246
|
-
t->status = FileTransferStatus::CANCELLED;
|
|
1247
|
-
t->cv.notify_all();
|
|
1248
|
-
}
|
|
1249
|
-
finish(t, false, "cancelled by peer");
|
|
1250
|
-
}
|
|
1251
|
-
} catch (const std::exception& e) {
|
|
1252
|
-
LOG_FT_ERROR("Malformed ft_control: " << e.what());
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
// =============================================================================
|
|
1257
|
-
// Transfer control (local side)
|
|
1258
|
-
// =============================================================================
|
|
1259
|
-
|
|
1260
|
-
void FileTransferManager::send_control(const std::shared_ptr<Transfer>& t,
|
|
1261
|
-
const std::string& action) {
|
|
1262
|
-
nlohmann::json ctl{{"transfer_id", t->id}, {"action", action}};
|
|
1263
|
-
client_.send(t->peer_id, MSG_CONTROL, ctl);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
bool FileTransferManager::pause(const std::string& transfer_id) {
|
|
1267
|
-
auto t = find(transfer_id);
|
|
1268
|
-
if (!t) return false;
|
|
1269
|
-
{
|
|
1270
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1271
|
-
if (t->status != FileTransferStatus::IN_PROGRESS) return false;
|
|
1272
|
-
t->status = FileTransferStatus::PAUSED;
|
|
1273
|
-
t->cv.notify_all();
|
|
1274
|
-
}
|
|
1275
|
-
send_control(t, "pause");
|
|
1276
|
-
emit_progress(t);
|
|
1277
|
-
LOG_FT_INFO("Transfer " << transfer_id << " paused");
|
|
1278
|
-
return true;
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
bool FileTransferManager::resume(const std::string& transfer_id) {
|
|
1282
|
-
auto t = find(transfer_id);
|
|
1283
|
-
if (!t) return false;
|
|
1284
|
-
bool requeue = false;
|
|
1285
|
-
{
|
|
1286
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1287
|
-
if (t->status != FileTransferStatus::PAUSED) return false;
|
|
1288
|
-
if (t->direction == FileTransferDirection::SENDING) {
|
|
1289
|
-
t->status = FileTransferStatus::RESUMING;
|
|
1290
|
-
requeue = true;
|
|
1291
|
-
} else {
|
|
1292
|
-
t->status = FileTransferStatus::IN_PROGRESS;
|
|
1293
|
-
}
|
|
1294
|
-
t->last_activity = std::chrono::steady_clock::now();
|
|
1295
|
-
t->cv.notify_all();
|
|
1296
|
-
}
|
|
1297
|
-
send_control(t, "resume");
|
|
1298
|
-
if (requeue) queue_send(transfer_id);
|
|
1299
|
-
emit_progress(t);
|
|
1300
|
-
LOG_FT_INFO("Transfer " << transfer_id << " resumed");
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
bool FileTransferManager::cancel(const std::string& transfer_id) {
|
|
1305
|
-
auto t = find(transfer_id);
|
|
1306
|
-
if (!t) return false;
|
|
1307
|
-
{
|
|
1308
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1309
|
-
if (t->is_terminal()) return false;
|
|
1310
|
-
t->status = FileTransferStatus::CANCELLED;
|
|
1311
|
-
t->cv.notify_all();
|
|
1312
|
-
}
|
|
1313
|
-
send_control(t, "cancel");
|
|
1314
|
-
finish(t, false, "cancelled");
|
|
1315
|
-
LOG_FT_INFO("Transfer " << transfer_id << " cancelled");
|
|
1316
|
-
return true;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
// =============================================================================
|
|
1320
|
-
// Peer disconnect
|
|
1321
|
-
// =============================================================================
|
|
1322
|
-
|
|
1323
|
-
void FileTransferManager::on_peer_disconnected(const std::string& peer_id) {
|
|
1324
|
-
std::vector<std::shared_ptr<Transfer>> affected;
|
|
1325
|
-
{
|
|
1326
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
1327
|
-
for (auto& kv : transfers_) {
|
|
1328
|
-
if (kv.second->peer_id == peer_id) affected.push_back(kv.second);
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
for (auto& t : affected) {
|
|
1332
|
-
bool active;
|
|
1333
|
-
{
|
|
1334
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1335
|
-
active = !t->is_terminal();
|
|
1336
|
-
if (active) t->cv.notify_all();
|
|
1337
|
-
}
|
|
1338
|
-
if (active) finish(t, false, "peer disconnected");
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
// =============================================================================
|
|
1343
|
-
// Maintenance: timeouts and cleanup
|
|
1344
|
-
// =============================================================================
|
|
1345
|
-
|
|
1346
|
-
void FileTransferManager::maintenance_loop() {
|
|
1347
|
-
while (running_.load()) {
|
|
1348
|
-
{
|
|
1349
|
-
std::unique_lock<std::mutex> lk(maintenance_mutex_);
|
|
1350
|
-
maintenance_cv_.wait_for(lk, std::chrono::seconds(2),
|
|
1351
|
-
[this] { return !running_.load(); });
|
|
1352
|
-
}
|
|
1353
|
-
if (!running_.load()) return;
|
|
1354
|
-
|
|
1355
|
-
auto now = std::chrono::steady_clock::now();
|
|
1356
|
-
uint32_t timeout_secs = get_config().transfer_timeout_secs;
|
|
1357
|
-
|
|
1358
|
-
std::vector<std::shared_ptr<Transfer>> all;
|
|
1359
|
-
{
|
|
1360
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
1361
|
-
for (auto& kv : transfers_) all.push_back(kv.second);
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
std::vector<std::shared_ptr<Transfer>> timed_out;
|
|
1365
|
-
std::vector<std::string> to_purge;
|
|
1366
|
-
for (auto& t : all) {
|
|
1367
|
-
std::lock_guard<std::mutex> lk(t->mtx);
|
|
1368
|
-
auto idle = std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity);
|
|
1369
|
-
if (t->is_terminal()) {
|
|
1370
|
-
if (idle > FINISHED_RETENTION) to_purge.push_back(t->id);
|
|
1371
|
-
} else if (t->status != FileTransferStatus::PAUSED &&
|
|
1372
|
-
idle.count() > timeout_secs) {
|
|
1373
|
-
timed_out.push_back(t);
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
for (auto& t : timed_out) {
|
|
1378
|
-
LOG_FT_WARN("Transfer " << t->id << " timed out");
|
|
1379
|
-
nlohmann::json done{{"transfer_id", t->id}, {"success", false},
|
|
1380
|
-
{"error", "timed out"}};
|
|
1381
|
-
client_.send(t->peer_id, MSG_COMPLETE, done);
|
|
1382
|
-
finish(t, false, "timed out");
|
|
1383
|
-
}
|
|
1384
|
-
if (!to_purge.empty()) {
|
|
1385
|
-
std::lock_guard<std::mutex> lk(transfers_mutex_);
|
|
1386
|
-
for (auto& id : to_purge) transfers_.erase(id);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// =============================================================================
|
|
1392
|
-
// Utilities
|
|
1393
|
-
// =============================================================================
|
|
1394
|
-
|
|
1395
|
-
std::string FileTransferManager::compute_file_sha256(const std::string& path) {
|
|
1396
|
-
int64_t size = get_file_size(path.c_str());
|
|
1397
|
-
if (size < 0) return "";
|
|
1398
|
-
sha256_context_t ctx;
|
|
1399
|
-
sha256_reset(&ctx);
|
|
1400
|
-
std::vector<uint8_t> buf(64 * 1024);
|
|
1401
|
-
uint64_t offset = 0;
|
|
1402
|
-
uint64_t remaining = static_cast<uint64_t>(size);
|
|
1403
|
-
while (remaining > 0) {
|
|
1404
|
-
uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(buf.size(), remaining));
|
|
1405
|
-
if (!read_file_chunk(path, offset, buf.data(), want)) return "";
|
|
1406
|
-
sha256_update(&ctx, buf.data(), want);
|
|
1407
|
-
offset += want;
|
|
1408
|
-
remaining -= want;
|
|
1409
|
-
}
|
|
1410
|
-
uint8_t digest[SHA256_HASH_SIZE];
|
|
1411
|
-
sha256_finish(&ctx, digest);
|
|
1412
|
-
return to_hex(digest, SHA256_HASH_SIZE);
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
} // namespace librats
|