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
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
#include "librats/subsystems/file_transfer.h"
|
|
2
|
+
#include "librats/node/node_context.h"
|
|
3
|
+
#include "librats/util/fs.h"
|
|
4
|
+
#include "librats/util/logger.h"
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <vector>
|
|
9
|
+
|
|
10
|
+
namespace librats {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
enum : uint8_t {
|
|
15
|
+
OP_OFFER = 1, OP_RESPONSE = 2, OP_CHUNK = 3, OP_FILE_END = 4,
|
|
16
|
+
OP_PROGRESS = 5, OP_COMPLETE = 6, OP_CANCEL = 7, OP_PAUSE = 8, OP_RESUME = 9,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
void put_u16(Bytes& b, uint16_t v) { b.push_back(v >> 8); b.push_back(v & 0xFF); }
|
|
20
|
+
void put_u32(Bytes& b, uint32_t v) { for (int i = 3; i >= 0; --i) b.push_back((v >> (i * 8)) & 0xFF); }
|
|
21
|
+
void put_u64(Bytes& b, uint64_t v) { for (int i = 7; i >= 0; --i) b.push_back((v >> (i * 8)) & 0xFF); }
|
|
22
|
+
|
|
23
|
+
struct Reader {
|
|
24
|
+
const uint8_t* p;
|
|
25
|
+
const uint8_t* end;
|
|
26
|
+
bool ok = true;
|
|
27
|
+
uint8_t u8() { if (p >= end) { ok = false; return 0; } return *p++; }
|
|
28
|
+
uint16_t u16() { if (end - p < 2) { ok = false; return 0; } uint16_t v = (uint16_t(p[0]) << 8) | p[1]; p += 2; return v; }
|
|
29
|
+
uint32_t u32() { if (end - p < 4) { ok = false; return 0; } uint32_t v = 0; for (int i = 0; i < 4; ++i) v = (v << 8) | *p++; return v; }
|
|
30
|
+
uint64_t u64() { if (end - p < 8) { ok = false; return 0; } uint64_t v = 0; for (int i = 0; i < 8; ++i) v = (v << 8) | *p++; return v; }
|
|
31
|
+
ByteView bytes(size_t n) { if (size_t(end - p) < n) { ok = false; return {}; } ByteView v(p, n); p += n; return v; }
|
|
32
|
+
ByteView rest() { ByteView v(p, size_t(end - p)); p = end; return v; }
|
|
33
|
+
size_t remaining() const { return size_t(end - p); }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Smallest possible OFFER manifest entry on the wire: [path_len:u16][size:u64]
|
|
37
|
+
// with a zero-length path.
|
|
38
|
+
constexpr uint64_t kMinManifestEntry = 2 + 8;
|
|
39
|
+
|
|
40
|
+
// Recursively collect regular files under abs_dir, recording POSIX relative paths.
|
|
41
|
+
void scan_directory(const std::string& abs_dir, const std::string& rel_prefix,
|
|
42
|
+
std::vector<FileTransfer::FileEntry>& files, std::vector<std::string>& sources) {
|
|
43
|
+
std::vector<DirectoryEntry> entries;
|
|
44
|
+
if (!list_directory(abs_dir.c_str(), entries)) return;
|
|
45
|
+
for (const auto& e : entries) {
|
|
46
|
+
const std::string rel = rel_prefix.empty() ? e.name : rel_prefix + "/" + e.name;
|
|
47
|
+
if (e.is_directory) {
|
|
48
|
+
scan_directory(e.path, rel, files, sources);
|
|
49
|
+
} else {
|
|
50
|
+
const int64_t sz = get_file_size(e.path.c_str());
|
|
51
|
+
files.push_back({rel, sz > 0 ? static_cast<uint64_t>(sz) : 0});
|
|
52
|
+
sources.push_back(e.path);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
} // namespace
|
|
58
|
+
|
|
59
|
+
// A relative path coming from a peer must not escape the destination directory.
|
|
60
|
+
bool FileTransfer::is_safe_relative_path(const std::string& p) {
|
|
61
|
+
if (p.empty()) return false;
|
|
62
|
+
if (p.front() == '/' || p.front() == '\\') return false;
|
|
63
|
+
if (p.size() >= 2 && p[1] == ':') return false; // Windows drive letter
|
|
64
|
+
size_t start = 0;
|
|
65
|
+
for (size_t i = 0; i <= p.size(); ++i) {
|
|
66
|
+
if (i == p.size() || p[i] == '/' || p[i] == '\\') {
|
|
67
|
+
std::string comp = p.substr(start, i - start);
|
|
68
|
+
if (comp.empty() || comp == "." || comp == "..") return false;
|
|
69
|
+
start = i + 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
FileTransfer::FileTransfer(std::string temp_dir) { config_.temp_directory = std::move(temp_dir); }
|
|
76
|
+
FileTransfer::FileTransfer(Config config) : config_(std::move(config)) {}
|
|
77
|
+
FileTransfer::~FileTransfer() { stop(); }
|
|
78
|
+
|
|
79
|
+
void FileTransfer::attach(NodeContext& ctx) {
|
|
80
|
+
network_ = &ctx.network;
|
|
81
|
+
network_->on(MessageType::FileChunk,
|
|
82
|
+
[this](const Peer& peer, ByteView payload) { on_message(peer, payload); });
|
|
83
|
+
network_->on_peer_disconnected([this](const PeerId& id) {
|
|
84
|
+
// Fail every in-flight transfer with the departed peer and reclaim temps.
|
|
85
|
+
std::vector<std::shared_ptr<Outgoing>> out;
|
|
86
|
+
std::vector<std::shared_ptr<Incoming>> in;
|
|
87
|
+
{
|
|
88
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
89
|
+
for (auto& [tid, t] : outgoing_) if (t->peer == id) out.push_back(t);
|
|
90
|
+
auto pit = incoming_.find(id);
|
|
91
|
+
if (pit != incoming_.end())
|
|
92
|
+
for (auto& [tid, t] : pit->second) in.push_back(t);
|
|
93
|
+
}
|
|
94
|
+
for (auto& t : out) finish_outgoing(t, false);
|
|
95
|
+
for (auto& t : in) finish_incoming(t, false, "peer disconnected");
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void FileTransfer::start() {
|
|
100
|
+
if (running_.exchange(true)) return;
|
|
101
|
+
const uint32_t n = std::max<uint32_t>(1, config_.worker_threads);
|
|
102
|
+
for (uint32_t i = 0; i < n; ++i) workers_.emplace_back(&FileTransfer::worker_loop, this);
|
|
103
|
+
const uint32_t d = std::max<uint32_t>(1, config_.disk_threads);
|
|
104
|
+
for (uint32_t i = 0; i < d; ++i) disk_workers_.emplace_back(&FileTransfer::disk_worker_loop, this);
|
|
105
|
+
maintenance_thread_ = std::thread(&FileTransfer::maintenance_loop, this);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void FileTransfer::stop() {
|
|
109
|
+
if (!running_.exchange(false)) return;
|
|
110
|
+
|
|
111
|
+
queue_cv_.notify_all();
|
|
112
|
+
maintenance_cv_.notify_all();
|
|
113
|
+
disk_cv_.notify_all();
|
|
114
|
+
|
|
115
|
+
// Wake any worker blocked on a transfer's window/wait so run_send returns.
|
|
116
|
+
{
|
|
117
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
118
|
+
for (auto& [id, t] : outgoing_) { std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; t->cv.notify_all(); }
|
|
119
|
+
}
|
|
120
|
+
for (auto& w : workers_) if (w.joinable()) w.join();
|
|
121
|
+
workers_.clear();
|
|
122
|
+
// Join the disk pool before dropping any Incoming, so no writer touches a temp
|
|
123
|
+
// file while ~Incoming reclaims it.
|
|
124
|
+
for (auto& w : disk_workers_) if (w.joinable()) w.join();
|
|
125
|
+
disk_workers_.clear();
|
|
126
|
+
if (maintenance_thread_.joinable()) maintenance_thread_.join();
|
|
127
|
+
|
|
128
|
+
// Drop remaining transfers. ~Incoming closes any open handle and reclaims
|
|
129
|
+
// un-finalized temp files.
|
|
130
|
+
std::unordered_map<PeerId, std::unordered_map<uint64_t, std::shared_ptr<Incoming>>, PeerId::Hash> in;
|
|
131
|
+
{
|
|
132
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
133
|
+
outgoing_.clear();
|
|
134
|
+
in.swap(incoming_);
|
|
135
|
+
}
|
|
136
|
+
in.clear();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
FileTransfer::Stats FileTransfer::stats() const {
|
|
140
|
+
std::lock_guard<std::mutex> lock(stats_mutex_);
|
|
141
|
+
return stats_;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── lookups ───────────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
std::shared_ptr<FileTransfer::Outgoing> FileTransfer::find_outgoing(uint64_t id) const {
|
|
147
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
148
|
+
auto it = outgoing_.find(id);
|
|
149
|
+
return it == outgoing_.end() ? nullptr : it->second;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
std::shared_ptr<FileTransfer::Incoming> FileTransfer::find_incoming(const PeerId& peer, uint64_t id) const {
|
|
153
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
154
|
+
auto pit = incoming_.find(peer);
|
|
155
|
+
if (pit == incoming_.end()) return nullptr;
|
|
156
|
+
auto it = pit->second.find(id);
|
|
157
|
+
return it == pit->second.end() ? nullptr : it->second;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── small senders ─────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
void FileTransfer::send_simple(const PeerId& peer, uint8_t op, uint64_t id) {
|
|
163
|
+
Bytes m; m.push_back(op); put_u64(m, id); send_to(peer, m);
|
|
164
|
+
}
|
|
165
|
+
void FileTransfer::send_complete(const PeerId& peer, uint64_t id, bool ok) {
|
|
166
|
+
Bytes m; m.push_back(OP_COMPLETE); put_u64(m, id); m.push_back(ok ? 1 : 0); send_to(peer, m);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── Sender: build + offer ───────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
uint64_t FileTransfer::send_file(const PeerId& to, const std::string& path) {
|
|
172
|
+
if (!is_file(path.c_str())) { LOG_WARN("filexfer", "send_file: not a file: " << path); return 0; }
|
|
173
|
+
const int64_t size = get_file_size(path.c_str());
|
|
174
|
+
if (size < 0) { LOG_WARN("filexfer", "send_file: cannot stat " << path); return 0; }
|
|
175
|
+
|
|
176
|
+
auto t = std::make_shared<Outgoing>();
|
|
177
|
+
t->peer = to;
|
|
178
|
+
t->name = get_filename_from_path(path.c_str());
|
|
179
|
+
t->root = path;
|
|
180
|
+
t->is_directory = false;
|
|
181
|
+
t->files.push_back({t->name, static_cast<uint64_t>(size)});
|
|
182
|
+
t->sources.push_back(path);
|
|
183
|
+
t->total_bytes = static_cast<uint64_t>(size);
|
|
184
|
+
return start_send(std::move(t));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
uint64_t FileTransfer::send_directory(const PeerId& to, const std::string& dir_path) {
|
|
188
|
+
if (!is_directory(dir_path.c_str())) { LOG_WARN("filexfer", "send_directory: not a directory: " << dir_path); return 0; }
|
|
189
|
+
|
|
190
|
+
auto t = std::make_shared<Outgoing>();
|
|
191
|
+
t->name = get_filename_from_path(dir_path.c_str());
|
|
192
|
+
t->root = dir_path;
|
|
193
|
+
t->is_directory = true;
|
|
194
|
+
scan_directory(dir_path, "", t->files, t->sources);
|
|
195
|
+
for (const auto& f : t->files) t->total_bytes += f.size;
|
|
196
|
+
t->peer = to;
|
|
197
|
+
return start_send(std::move(t));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
uint64_t FileTransfer::start_send(std::shared_ptr<Outgoing> t) {
|
|
201
|
+
const uint64_t id = next_id_.fetch_add(1);
|
|
202
|
+
t->id = id;
|
|
203
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
204
|
+
t->status = Status::Pending;
|
|
205
|
+
{
|
|
206
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
207
|
+
outgoing_.emplace(id, t);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
Bytes m;
|
|
211
|
+
m.push_back(OP_OFFER);
|
|
212
|
+
put_u64(m, id);
|
|
213
|
+
m.push_back(t->is_directory ? 1 : 0);
|
|
214
|
+
put_u64(m, t->total_bytes);
|
|
215
|
+
put_u16(m, static_cast<uint16_t>(t->name.size()));
|
|
216
|
+
m.insert(m.end(), t->name.begin(), t->name.end());
|
|
217
|
+
put_u32(m, static_cast<uint32_t>(t->files.size()));
|
|
218
|
+
for (const auto& f : t->files) {
|
|
219
|
+
put_u16(m, static_cast<uint16_t>(f.relative_path.size()));
|
|
220
|
+
m.insert(m.end(), f.relative_path.begin(), f.relative_path.end());
|
|
221
|
+
put_u64(m, f.size);
|
|
222
|
+
}
|
|
223
|
+
send_to(t->peer, m);
|
|
224
|
+
LOG_INFO("filexfer", "Offering " << (t->is_directory ? "directory '" : "file '") << t->name
|
|
225
|
+
<< "' (" << t->files.size() << " file(s), " << t->total_bytes << " B) [" << id << "]");
|
|
226
|
+
return id;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
void FileTransfer::queue_send(uint64_t id) {
|
|
230
|
+
{ std::lock_guard<std::mutex> lk(queue_mutex_); send_queue_.push(id); }
|
|
231
|
+
queue_cv_.notify_one();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
void FileTransfer::worker_loop() {
|
|
235
|
+
while (running_.load()) {
|
|
236
|
+
uint64_t id;
|
|
237
|
+
{
|
|
238
|
+
std::unique_lock<std::mutex> lk(queue_mutex_);
|
|
239
|
+
queue_cv_.wait(lk, [this] { return !running_.load() || !send_queue_.empty(); });
|
|
240
|
+
if (!running_.load()) return;
|
|
241
|
+
id = send_queue_.front();
|
|
242
|
+
send_queue_.pop();
|
|
243
|
+
}
|
|
244
|
+
auto t = find_outgoing(id);
|
|
245
|
+
if (!t) continue;
|
|
246
|
+
{
|
|
247
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
248
|
+
if (t->worker_active) continue; // another worker owns it
|
|
249
|
+
t->worker_active = true;
|
|
250
|
+
}
|
|
251
|
+
run_send(t);
|
|
252
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); t->worker_active = false; }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
void FileTransfer::run_send(const std::shared_ptr<Outgoing>& t) {
|
|
257
|
+
// One send buffer reused for every chunk: the fixed CHUNK header is written in
|
|
258
|
+
// place and the file data is read straight into the bytes that follow it, so a
|
|
259
|
+
// chunk's payload is never copied between the disk read and the wire (the only
|
|
260
|
+
// remaining copies are the transport's own framing + encryption). Reusing the
|
|
261
|
+
// buffer also drops the per-chunk allocation.
|
|
262
|
+
constexpr size_t kChunkHeader = 1 + 8 + 4 + 8; // op + id + file_index + offset
|
|
263
|
+
Bytes m;
|
|
264
|
+
m.reserve(kChunkHeader + config_.chunk_size);
|
|
265
|
+
|
|
266
|
+
// The source file is held open across all of its chunks (one open()/close()
|
|
267
|
+
// per file instead of one per chunk) and read sequentially straight into the
|
|
268
|
+
// reused send buffer.
|
|
269
|
+
FileStream fp;
|
|
270
|
+
size_t open_idx = SIZE_MAX;
|
|
271
|
+
|
|
272
|
+
while (running_.load()) {
|
|
273
|
+
size_t file_index;
|
|
274
|
+
uint64_t offset, file_size;
|
|
275
|
+
std::string source;
|
|
276
|
+
{
|
|
277
|
+
std::unique_lock<std::mutex> lk(t->mtx);
|
|
278
|
+
if (t->status != Status::Active) return; // paused / cancelled / done
|
|
279
|
+
if (t->cur_file >= t->files.size()) break; // all data streamed
|
|
280
|
+
file_index = t->cur_file;
|
|
281
|
+
offset = t->cur_offset;
|
|
282
|
+
file_size = t->files[file_index].size;
|
|
283
|
+
source = t->sources[file_index];
|
|
284
|
+
if (offset == 0) rats_sha256_reset(&t->hash);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Empty file: no chunks, just the per-file SHA (of the empty input).
|
|
288
|
+
if (file_size == 0) {
|
|
289
|
+
rats_sha256_context_t e; rats_sha256_reset(&e);
|
|
290
|
+
uint8_t digest[RATS_SHA256_HASH_SIZE]; rats_sha256_finish(&e, digest);
|
|
291
|
+
Bytes fe; fe.push_back(OP_FILE_END); put_u64(fe, t->id);
|
|
292
|
+
put_u32(fe, static_cast<uint32_t>(file_index));
|
|
293
|
+
fe.insert(fe.end(), digest, digest + RATS_SHA256_HASH_SIZE);
|
|
294
|
+
send_to(t->peer, fe);
|
|
295
|
+
fp.close(); open_idx = SIZE_MAX;
|
|
296
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
297
|
+
t->cur_file++; t->cur_offset = 0; t->files_done++;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// (Re)open the source when we start a new file, seeking to the resume offset.
|
|
302
|
+
if (open_idx != file_index) {
|
|
303
|
+
fp.close();
|
|
304
|
+
if (!fp.open_read(source.c_str()) || !fp.seek(offset)) {
|
|
305
|
+
LOG_ERROR("filexfer", "open/seek error on " << source << " at " << offset);
|
|
306
|
+
send_complete(t->peer, t->id, false);
|
|
307
|
+
finish_outgoing(t, false);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
open_idx = file_index;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const uint32_t want = static_cast<uint32_t>(std::min<uint64_t>(config_.chunk_size, file_size - offset));
|
|
314
|
+
m.clear();
|
|
315
|
+
m.push_back(OP_CHUNK);
|
|
316
|
+
put_u64(m, t->id);
|
|
317
|
+
put_u32(m, static_cast<uint32_t>(file_index));
|
|
318
|
+
put_u64(m, offset);
|
|
319
|
+
m.resize(kChunkHeader + want); // no realloc: capacity was reserved up front
|
|
320
|
+
if (fp.read(m.data() + kChunkHeader, want) != want) {
|
|
321
|
+
LOG_ERROR("filexfer", "read error on " << source << " at " << offset);
|
|
322
|
+
send_complete(t->peer, t->id, false);
|
|
323
|
+
finish_outgoing(t, false);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
send_to(t->peer, m);
|
|
327
|
+
|
|
328
|
+
bool file_done = false;
|
|
329
|
+
uint8_t digest[RATS_SHA256_HASH_SIZE];
|
|
330
|
+
{
|
|
331
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
332
|
+
rats_sha256_update(&t->hash, m.data() + kChunkHeader, want);
|
|
333
|
+
t->cur_offset += want;
|
|
334
|
+
t->bytes_done += want;
|
|
335
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
336
|
+
if (t->cur_offset >= file_size) {
|
|
337
|
+
rats_sha256_finish(&t->hash, digest);
|
|
338
|
+
t->cur_file++; t->cur_offset = 0; t->files_done++;
|
|
339
|
+
file_done = true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
{ std::lock_guard<std::mutex> lk(stats_mutex_); stats_.bytes_sent += want; }
|
|
343
|
+
|
|
344
|
+
if (file_done) {
|
|
345
|
+
Bytes e; e.push_back(OP_FILE_END); put_u64(e, t->id);
|
|
346
|
+
put_u32(e, static_cast<uint32_t>(file_index));
|
|
347
|
+
e.insert(e.end(), digest, digest + RATS_SHA256_HASH_SIZE);
|
|
348
|
+
send_to(t->peer, e);
|
|
349
|
+
}
|
|
350
|
+
emit_progress(t);
|
|
351
|
+
|
|
352
|
+
// Backpressure: stay within `window_bytes` of the receiver's last ack.
|
|
353
|
+
std::unique_lock<std::mutex> lk(t->mtx);
|
|
354
|
+
while (running_.load() && t->status == Status::Active &&
|
|
355
|
+
t->bytes_done - t->acked >= config_.window_bytes) {
|
|
356
|
+
t->cv.wait_for(lk, std::chrono::milliseconds(200));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// All data streamed; the transfer stays Active until the receiver's COMPLETE
|
|
360
|
+
// arrives (handled on the reactor thread), which calls finish_outgoing().
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
void FileTransfer::finish_outgoing(const std::shared_ptr<Outgoing>& t, bool success) {
|
|
364
|
+
bool cancelled;
|
|
365
|
+
{
|
|
366
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
367
|
+
if (t->finished) return;
|
|
368
|
+
t->finished = true;
|
|
369
|
+
cancelled = (t->status == Status::Cancelled);
|
|
370
|
+
if (!cancelled)
|
|
371
|
+
t->status = success ? Status::Completed : Status::Failed;
|
|
372
|
+
t->cv.notify_all();
|
|
373
|
+
}
|
|
374
|
+
{
|
|
375
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
376
|
+
outgoing_.erase(t->id);
|
|
377
|
+
}
|
|
378
|
+
{ std::lock_guard<std::mutex> lk(stats_mutex_); success ? ++stats_.completed : ++stats_.failed; }
|
|
379
|
+
emit_progress(t);
|
|
380
|
+
if (cancelled) LOG_INFO("filexfer", "Send [" << t->id << "] cancelled");
|
|
381
|
+
else if (success) LOG_INFO("filexfer", "Send [" << t->id << "] completed (" << t->root << ")");
|
|
382
|
+
else LOG_WARN("filexfer", "Send [" << t->id << "] failed");
|
|
383
|
+
if (complete_handler_) complete_handler_(t->id, success, t->root);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Receiver ──────────────────────────────────────────────────────────────────
|
|
387
|
+
|
|
388
|
+
void FileTransfer::accept(const PeerId& from, uint64_t id, const std::string& dest_path) {
|
|
389
|
+
auto t = find_incoming(from, id);
|
|
390
|
+
if (!t) return;
|
|
391
|
+
bool empty_transfer = false;
|
|
392
|
+
{
|
|
393
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
394
|
+
if (t->status != Status::Pending) return;
|
|
395
|
+
t->dest_root = dest_path;
|
|
396
|
+
for (size_t i = 0; i < t->files.size(); ++i) {
|
|
397
|
+
IncomingFile& f = t->files[i];
|
|
398
|
+
f.final_path = t->is_directory ? combine_paths(dest_path, f.relative_path) : dest_path;
|
|
399
|
+
// Transfer ids are only unique per sender (every node's first offer is
|
|
400
|
+
// id 1), so the sender's PeerId is what keeps two peers' temp files
|
|
401
|
+
// apart. Use the full hex: short_hex() is 32 bits, which a peer could
|
|
402
|
+
// grind a keypair to collide with on purpose.
|
|
403
|
+
f.temp_path = combine_paths(config_.temp_directory,
|
|
404
|
+
from.to_hex() + "." + std::to_string(id) + "."
|
|
405
|
+
+ std::to_string(i) + ".part");
|
|
406
|
+
}
|
|
407
|
+
t->status = Status::Active;
|
|
408
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
409
|
+
empty_transfer = t->files.empty();
|
|
410
|
+
}
|
|
411
|
+
create_directories(config_.temp_directory.c_str());
|
|
412
|
+
if (t->is_directory) create_directories(dest_path.c_str());
|
|
413
|
+
|
|
414
|
+
Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(1); send_to(from, m);
|
|
415
|
+
LOG_INFO("filexfer", "Accepted transfer [" << id << "] -> " << dest_path);
|
|
416
|
+
|
|
417
|
+
if (empty_transfer) { // e.g. an empty directory: done immediately
|
|
418
|
+
send_complete(from, id, true);
|
|
419
|
+
finish_incoming(t, true, "");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
void FileTransfer::reject(const PeerId& from, uint64_t id) {
|
|
424
|
+
auto t = find_incoming(from, id);
|
|
425
|
+
{
|
|
426
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
427
|
+
auto pit = incoming_.find(from);
|
|
428
|
+
if (pit != incoming_.end()) pit->second.erase(id);
|
|
429
|
+
}
|
|
430
|
+
Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(0); send_to(from, m);
|
|
431
|
+
if (t && complete_handler_) complete_handler_(id, false, "");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ── Receive-side disk writer (off the reactor thread) ─────────────────────────
|
|
435
|
+
//
|
|
436
|
+
// The reactor validates a chunk and pushes it into t->wq; the writer pool drains
|
|
437
|
+
// that queue on its own thread. `t->out`, `t->hash`, `t->out_idx` and
|
|
438
|
+
// `t->hashing_file` are writer-thread-only: a transfer's `scheduled` flag keeps
|
|
439
|
+
// exactly one worker draining it at a time, so these need no lock even though the
|
|
440
|
+
// worker touches them outside t->mtx.
|
|
441
|
+
|
|
442
|
+
FileTransfer::Incoming::~Incoming() {
|
|
443
|
+
out.close(); // must precede the temp-file delete on Windows (can't unlink an open file)
|
|
444
|
+
for (auto& f : files) if (f.temp_created && !f.finalized) delete_file(f.temp_path.c_str());
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
void FileTransfer::schedule_writer(const std::shared_ptr<Incoming>& t) {
|
|
448
|
+
{ std::lock_guard<std::mutex> lk(disk_mutex_); disk_ready_.push(t); }
|
|
449
|
+
disk_cv_.notify_one();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
void FileTransfer::disk_worker_loop() {
|
|
453
|
+
while (running_.load()) {
|
|
454
|
+
std::shared_ptr<Incoming> t;
|
|
455
|
+
{
|
|
456
|
+
std::unique_lock<std::mutex> lk(disk_mutex_);
|
|
457
|
+
disk_cv_.wait(lk, [this] { return !running_.load() || !disk_ready_.empty(); });
|
|
458
|
+
if (!running_.load()) return;
|
|
459
|
+
t = std::move(disk_ready_.front());
|
|
460
|
+
disk_ready_.pop();
|
|
461
|
+
}
|
|
462
|
+
if (t) drain_writes(t);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
void FileTransfer::drain_writes(const std::shared_ptr<Incoming>& t) {
|
|
467
|
+
for (;;) {
|
|
468
|
+
WriteJob job;
|
|
469
|
+
{
|
|
470
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
471
|
+
if (t->finished) { // torn down elsewhere: drop pending work
|
|
472
|
+
std::queue<WriteJob> empty; t->wq.swap(empty);
|
|
473
|
+
t->queued_bytes = 0; t->scheduled = false;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (t->wq.empty()) { t->scheduled = false; return; }
|
|
477
|
+
job = std::move(t->wq.front());
|
|
478
|
+
t->wq.pop();
|
|
479
|
+
}
|
|
480
|
+
if (job.is_file_end) process_file_end(t, job);
|
|
481
|
+
else process_data(t, job);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
void FileTransfer::process_data(const std::shared_ptr<Incoming>& t, WriteJob& job) {
|
|
486
|
+
const uint32_t fidx = job.fidx;
|
|
487
|
+
std::string temp_path;
|
|
488
|
+
{
|
|
489
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
490
|
+
if (t->finished || fidx >= t->files.size()) return;
|
|
491
|
+
temp_path = t->files[fidx].temp_path;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Open the temp file once per file, then write sequentially at the given offset.
|
|
495
|
+
if (t->out_idx != fidx) {
|
|
496
|
+
t->out.close();
|
|
497
|
+
if (!t->out.open_write(temp_path.c_str())) {
|
|
498
|
+
send_complete(t->peer, t->id, false);
|
|
499
|
+
finish_incoming(t, false, "cannot create temp file");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
t->out_idx = fidx;
|
|
503
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); t->files[fidx].temp_created = true; }
|
|
504
|
+
}
|
|
505
|
+
if (t->hashing_file != static_cast<int>(fidx)) { rats_sha256_reset(&t->hash); t->hashing_file = static_cast<int>(fidx); }
|
|
506
|
+
|
|
507
|
+
if (!t->out.write_at(job.offset, job.data.data(), job.data.size())) {
|
|
508
|
+
LOG_ERROR("filexfer", "transfer " << t->id << ": disk write failed");
|
|
509
|
+
send_complete(t->peer, t->id, false);
|
|
510
|
+
finish_incoming(t, false, "failed to write chunk to disk");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
rats_sha256_update(&t->hash, job.data.data(), job.data.size());
|
|
514
|
+
|
|
515
|
+
const uint64_t ack_interval =
|
|
516
|
+
std::min<uint64_t>(config_.progress_interval, std::max<uint32_t>(1, config_.window_bytes / 2));
|
|
517
|
+
bool send_ack = false; uint64_t ack_bytes = 0;
|
|
518
|
+
{
|
|
519
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
520
|
+
IncomingFile& f = t->files[fidx];
|
|
521
|
+
f.received += job.data.size();
|
|
522
|
+
t->bytes_done += job.data.size();
|
|
523
|
+
if (t->queued_bytes >= job.data.size()) t->queued_bytes -= job.data.size();
|
|
524
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
525
|
+
// Ack on the byte threshold, and always once a file's data is fully on disk
|
|
526
|
+
// (keeps the sender's window moving across file boundaries).
|
|
527
|
+
if (f.received >= f.size || t->bytes_done - t->last_ack >= ack_interval) {
|
|
528
|
+
t->last_ack = t->bytes_done; ack_bytes = t->bytes_done; send_ack = true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
{ std::lock_guard<std::mutex> lk(stats_mutex_); stats_.bytes_received += job.data.size(); }
|
|
532
|
+
|
|
533
|
+
if (send_ack) { Bytes m; m.push_back(OP_PROGRESS); put_u64(m, t->id); put_u64(m, ack_bytes); send_to(t->peer, m); }
|
|
534
|
+
emit_progress(t);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
void FileTransfer::process_file_end(const std::shared_ptr<Incoming>& t, WriteJob& job) {
|
|
538
|
+
const uint32_t fidx = job.fidx;
|
|
539
|
+
std::string final_path, temp_path, rel;
|
|
540
|
+
uint64_t fsize = 0;
|
|
541
|
+
{
|
|
542
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
543
|
+
if (t->finished || fidx >= t->files.size()) return;
|
|
544
|
+
IncomingFile& f = t->files[fidx];
|
|
545
|
+
if (f.finalized) return;
|
|
546
|
+
final_path = f.final_path; temp_path = f.temp_path; rel = f.relative_path; fsize = f.size;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Whole-file SHA-256 over exactly the bytes written to disk (a file with no
|
|
550
|
+
// data — an empty file — hashes the empty input).
|
|
551
|
+
if (t->hashing_file != static_cast<int>(fidx)) { rats_sha256_reset(&t->hash); t->hashing_file = static_cast<int>(fidx); }
|
|
552
|
+
uint8_t local[RATS_SHA256_HASH_SIZE];
|
|
553
|
+
rats_sha256_finish(&t->hash, local);
|
|
554
|
+
t->hashing_file = -1;
|
|
555
|
+
if (config_.verify_integrity && std::memcmp(local, job.sha, RATS_SHA256_HASH_SIZE) != 0) {
|
|
556
|
+
LOG_ERROR("filexfer", "SHA-256 mismatch for " << rel << " on transfer " << t->id);
|
|
557
|
+
send_complete(t->peer, t->id, false);
|
|
558
|
+
finish_incoming(t, false, "SHA-256 mismatch");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Close the temp handle before moving it into place (Windows can't rename an
|
|
563
|
+
// open file).
|
|
564
|
+
if (t->out_idx == fidx) { t->out.close(); t->out_idx = SIZE_MAX; }
|
|
565
|
+
|
|
566
|
+
const std::string parent = get_parent_directory(final_path.c_str());
|
|
567
|
+
if (!parent.empty()) create_directories(parent.c_str());
|
|
568
|
+
if (file_exists(final_path.c_str())) delete_file(final_path.c_str());
|
|
569
|
+
|
|
570
|
+
bool ok;
|
|
571
|
+
if (fsize == 0) {
|
|
572
|
+
ok = create_file_with_size(final_path.c_str(), 0);
|
|
573
|
+
if (file_exists(temp_path.c_str())) delete_file(temp_path.c_str());
|
|
574
|
+
} else {
|
|
575
|
+
ok = rename_file(temp_path.c_str(), final_path.c_str());
|
|
576
|
+
if (!ok) { // rename fails across volumes; fall back to copy + delete
|
|
577
|
+
ok = copy_file(temp_path.c_str(), final_path.c_str());
|
|
578
|
+
if (ok) delete_file(temp_path.c_str());
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (!ok) {
|
|
582
|
+
LOG_ERROR("filexfer", "cannot place file " << final_path);
|
|
583
|
+
send_complete(t->peer, t->id, false);
|
|
584
|
+
finish_incoming(t, false, "cannot write destination file");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
bool all_done = false;
|
|
589
|
+
{
|
|
590
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
591
|
+
IncomingFile& f = t->files[fidx];
|
|
592
|
+
f.finalized = true;
|
|
593
|
+
t->files_done++;
|
|
594
|
+
all_done = true;
|
|
595
|
+
for (const auto& ff : t->files) if (!ff.finalized) { all_done = false; break; }
|
|
596
|
+
}
|
|
597
|
+
emit_progress(t);
|
|
598
|
+
|
|
599
|
+
if (all_done) {
|
|
600
|
+
send_complete(t->peer, t->id, true);
|
|
601
|
+
finish_incoming(t, true, "");
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
void FileTransfer::finish_incoming(const std::shared_ptr<Incoming>& t, bool success, const std::string& error) {
|
|
606
|
+
std::string dest;
|
|
607
|
+
bool cancelled;
|
|
608
|
+
{
|
|
609
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
610
|
+
if (t->finished) return;
|
|
611
|
+
t->finished = true;
|
|
612
|
+
cancelled = (t->status == Status::Cancelled);
|
|
613
|
+
if (!cancelled)
|
|
614
|
+
t->status = success ? Status::Completed : Status::Failed;
|
|
615
|
+
dest = t->dest_root;
|
|
616
|
+
// Partial temp files are reclaimed by ~Incoming, once the disk writer has
|
|
617
|
+
// released its handle — deleting here could race an in-flight write.
|
|
618
|
+
}
|
|
619
|
+
{
|
|
620
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
621
|
+
auto pit = incoming_.find(t->peer);
|
|
622
|
+
if (pit != incoming_.end()) pit->second.erase(t->id);
|
|
623
|
+
}
|
|
624
|
+
{ std::lock_guard<std::mutex> lk(stats_mutex_); success ? ++stats_.completed : ++stats_.failed; }
|
|
625
|
+
emit_progress(t);
|
|
626
|
+
if (cancelled) LOG_INFO("filexfer", "Receive [" << t->id << "] cancelled");
|
|
627
|
+
else if (success) LOG_INFO("filexfer", "Receive [" << t->id << "] completed -> " << dest);
|
|
628
|
+
else if (error.empty()) LOG_WARN("filexfer", "Receive [" << t->id << "] failed");
|
|
629
|
+
else LOG_WARN("filexfer", "Receive [" << t->id << "] failed: " << error);
|
|
630
|
+
if (complete_handler_) complete_handler_(t->id, success, dest);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ── Message dispatch (reactor thread) ────────────────────────────────────────
|
|
634
|
+
|
|
635
|
+
void FileTransfer::on_message(const Peer& peer, ByteView payload) {
|
|
636
|
+
Reader r{payload.data(), payload.data() + payload.size()};
|
|
637
|
+
const uint8_t op = r.u8();
|
|
638
|
+
const PeerId from = peer.id();
|
|
639
|
+
|
|
640
|
+
switch (op) {
|
|
641
|
+
case OP_OFFER: {
|
|
642
|
+
const uint64_t id = r.u64();
|
|
643
|
+
const bool is_dir = r.u8() != 0;
|
|
644
|
+
const uint64_t total = r.u64();
|
|
645
|
+
const uint16_t nlen = r.u16();
|
|
646
|
+
ByteView nm = r.bytes(nlen);
|
|
647
|
+
const uint32_t count = r.u32();
|
|
648
|
+
if (!r.ok) return;
|
|
649
|
+
// `count` is the peer's word. Reserving on it directly lets a ~30-byte
|
|
650
|
+
// message ask for hundreds of GB; the resulting bad_alloc/length_error
|
|
651
|
+
// would unwind out of the reactor thread, which has no handler above it,
|
|
652
|
+
// and abort the process. Reject a count the rest of the payload cannot
|
|
653
|
+
// possibly hold, then cap the speculative reserve so memory stays
|
|
654
|
+
// proportional to the entries actually parsed.
|
|
655
|
+
if (static_cast<uint64_t>(count) * kMinManifestEntry > r.remaining()) {
|
|
656
|
+
LOG_WARN("filexfer", "Dropping offer " << id << " from " << from.short_hex()
|
|
657
|
+
<< ": manifest claims " << count << " file(s) but only "
|
|
658
|
+
<< r.remaining() << " B remain");
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
std::string name(reinterpret_cast<const char*>(nm.data()), nm.size());
|
|
662
|
+
std::vector<FileEntry> files;
|
|
663
|
+
files.reserve(std::min<uint32_t>(count, 4096));
|
|
664
|
+
for (uint32_t i = 0; i < count; ++i) {
|
|
665
|
+
const uint16_t plen = r.u16();
|
|
666
|
+
ByteView pb = r.bytes(plen);
|
|
667
|
+
const uint64_t sz = r.u64();
|
|
668
|
+
if (!r.ok) return;
|
|
669
|
+
files.push_back({std::string(reinterpret_cast<const char*>(pb.data()), pb.size()), sz});
|
|
670
|
+
}
|
|
671
|
+
handle_offer(from, id, is_dir, total, std::move(name), std::move(files));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
case OP_RESPONSE: {
|
|
675
|
+
const uint64_t id = r.u64();
|
|
676
|
+
const uint8_t accepted = r.u8();
|
|
677
|
+
if (!r.ok) return;
|
|
678
|
+
auto t = find_outgoing(id);
|
|
679
|
+
if (!t) return;
|
|
680
|
+
if (!accepted) { finish_outgoing(t, false); return; }
|
|
681
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Pending) t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); }
|
|
682
|
+
queue_send(id);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
case OP_CHUNK: {
|
|
686
|
+
const uint64_t id = r.u64();
|
|
687
|
+
const uint32_t fidx = r.u32();
|
|
688
|
+
const uint64_t offset = r.u64();
|
|
689
|
+
ByteView data = r.rest();
|
|
690
|
+
if (!r.ok) return;
|
|
691
|
+
handle_chunk(from, id, fidx, offset, data);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
case OP_FILE_END: {
|
|
695
|
+
const uint64_t id = r.u64();
|
|
696
|
+
const uint32_t fidx = r.u32();
|
|
697
|
+
ByteView sha = r.bytes(RATS_SHA256_HASH_SIZE);
|
|
698
|
+
if (!r.ok) return;
|
|
699
|
+
handle_file_end(from, id, fidx, sha.data());
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
case OP_PROGRESS: {
|
|
703
|
+
const uint64_t id = r.u64();
|
|
704
|
+
const uint64_t received = r.u64();
|
|
705
|
+
if (!r.ok) return;
|
|
706
|
+
auto t = find_outgoing(id);
|
|
707
|
+
if (!t) return;
|
|
708
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); t->acked = received; t->last_activity = std::chrono::steady_clock::now(); }
|
|
709
|
+
t->cv.notify_all();
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
case OP_COMPLETE: {
|
|
713
|
+
const uint64_t id = r.u64();
|
|
714
|
+
const uint8_t ok = r.u8();
|
|
715
|
+
if (!r.ok) return;
|
|
716
|
+
if (auto t = find_outgoing(id)) finish_outgoing(t, ok != 0);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
case OP_CANCEL: {
|
|
720
|
+
const uint64_t id = r.u64();
|
|
721
|
+
if (!r.ok) return;
|
|
722
|
+
if (auto t = find_outgoing(id)) {
|
|
723
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; t->cv.notify_all(); }
|
|
724
|
+
finish_outgoing(t, false);
|
|
725
|
+
}
|
|
726
|
+
if (auto t = find_incoming(from, id)) {
|
|
727
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; }
|
|
728
|
+
finish_incoming(t, false, "cancelled by peer");
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
case OP_PAUSE: {
|
|
733
|
+
const uint64_t id = r.u64();
|
|
734
|
+
if (!r.ok) return;
|
|
735
|
+
if (auto t = find_outgoing(id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; t->cv.notify_all(); }
|
|
736
|
+
if (auto t = find_incoming(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; }
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
case OP_RESUME: {
|
|
740
|
+
const uint64_t id = r.u64();
|
|
741
|
+
if (!r.ok) return;
|
|
742
|
+
if (auto t = find_outgoing(id)) {
|
|
743
|
+
bool requeue = false;
|
|
744
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); requeue = true; } t->cv.notify_all(); }
|
|
745
|
+
if (requeue) queue_send(id);
|
|
746
|
+
}
|
|
747
|
+
if (auto t = find_incoming(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); } }
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
default: return;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
void FileTransfer::handle_offer(const PeerId& from, uint64_t id, bool is_dir, uint64_t total,
|
|
755
|
+
std::string name, std::vector<FileEntry> files) {
|
|
756
|
+
if (find_incoming(from, id)) return; // duplicate offer
|
|
757
|
+
|
|
758
|
+
// Validate every peer-supplied relative path in a directory manifest before
|
|
759
|
+
// it can be used to build a destination path.
|
|
760
|
+
if (is_dir) {
|
|
761
|
+
for (const auto& f : files) {
|
|
762
|
+
if (!is_safe_relative_path(f.relative_path)) {
|
|
763
|
+
LOG_WARN("filexfer", "Rejecting offer " << id << " from " << from.short_hex()
|
|
764
|
+
<< ": unsafe path in manifest ('" << f.relative_path << "')");
|
|
765
|
+
Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(0); send_to(from, m);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
auto t = std::make_shared<Incoming>();
|
|
772
|
+
t->id = id; t->peer = from; t->name = std::move(name); t->is_directory = is_dir;
|
|
773
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
774
|
+
t->files.reserve(files.size());
|
|
775
|
+
for (auto& f : files) {
|
|
776
|
+
IncomingFile inf; inf.relative_path = f.relative_path; inf.size = f.size;
|
|
777
|
+
t->files.push_back(std::move(inf));
|
|
778
|
+
}
|
|
779
|
+
Offer offer{from, id, t->name, total, is_dir, std::move(files)};
|
|
780
|
+
{ std::lock_guard<std::mutex> lock(mutex_); incoming_[from].emplace(id, t); }
|
|
781
|
+
|
|
782
|
+
if (offer_handler_) offer_handler_(offer);
|
|
783
|
+
else reject(from, id); // no handler → auto-reject
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Reactor thread: validate ordering + copy the chunk into the transfer's write
|
|
787
|
+
// queue, then hand it to the disk-writer pool. No disk I/O or hashing happens
|
|
788
|
+
// here — those would block the reactor, which serves every peer on this shard.
|
|
789
|
+
void FileTransfer::handle_chunk(const PeerId& from, uint64_t id, uint32_t fidx, uint64_t offset,
|
|
790
|
+
ByteView data) {
|
|
791
|
+
auto t = find_incoming(from, id);
|
|
792
|
+
if (!t) return;
|
|
793
|
+
|
|
794
|
+
std::string fail;
|
|
795
|
+
bool need_schedule = false;
|
|
796
|
+
{
|
|
797
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
798
|
+
if (t->finished) return;
|
|
799
|
+
if (t->status != Status::Active && t->status != Status::Paused) return;
|
|
800
|
+
if (fidx >= t->files.size() || fidx != t->recv_file) {
|
|
801
|
+
fail = "out-of-order file index";
|
|
802
|
+
} else {
|
|
803
|
+
IncomingFile& f = t->files[fidx];
|
|
804
|
+
if (offset != f.enqueued) fail = "out-of-order chunk offset";
|
|
805
|
+
else if (offset + data.size() > f.size) fail = "chunk exceeds declared file size";
|
|
806
|
+
else {
|
|
807
|
+
WriteJob job;
|
|
808
|
+
job.fidx = fidx; job.offset = offset; job.data = data.to_bytes();
|
|
809
|
+
f.enqueued += data.size();
|
|
810
|
+
t->queued_bytes += data.size();
|
|
811
|
+
t->last_activity = std::chrono::steady_clock::now();
|
|
812
|
+
t->wq.push(std::move(job));
|
|
813
|
+
if (!t->scheduled) { t->scheduled = true; need_schedule = true; }
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (!fail.empty()) {
|
|
818
|
+
LOG_ERROR("filexfer", "transfer " << id << ": " << fail);
|
|
819
|
+
send_complete(from, id, false);
|
|
820
|
+
finish_incoming(t, false, fail);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (need_schedule) schedule_writer(t);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
void FileTransfer::handle_file_end(const PeerId& from, uint64_t id, uint32_t fidx, const uint8_t* sha) {
|
|
827
|
+
auto t = find_incoming(from, id);
|
|
828
|
+
if (!t) return;
|
|
829
|
+
std::string fail;
|
|
830
|
+
bool need_schedule = false;
|
|
831
|
+
{
|
|
832
|
+
std::lock_guard<std::mutex> lk(t->mtx);
|
|
833
|
+
if (t->finished || fidx >= t->files.size()) return;
|
|
834
|
+
if (fidx != t->recv_file) {
|
|
835
|
+
fail = "file-end out of order";
|
|
836
|
+
} else {
|
|
837
|
+
IncomingFile& f = t->files[fidx];
|
|
838
|
+
if (f.enqueued != f.size) { // all of the file's data must be queued first
|
|
839
|
+
fail = "file-end before all data";
|
|
840
|
+
} else {
|
|
841
|
+
WriteJob job;
|
|
842
|
+
job.fidx = fidx; job.is_file_end = true;
|
|
843
|
+
std::memcpy(job.sha, sha, RATS_SHA256_HASH_SIZE);
|
|
844
|
+
t->wq.push(std::move(job));
|
|
845
|
+
t->recv_file++; // subsequent chunks belong to the next file
|
|
846
|
+
if (!t->scheduled) { t->scheduled = true; need_schedule = true; }
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
if (!fail.empty()) {
|
|
851
|
+
LOG_ERROR("filexfer", "transfer " << id << ": " << fail);
|
|
852
|
+
send_complete(from, id, false);
|
|
853
|
+
finish_incoming(t, false, fail);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (need_schedule) schedule_writer(t);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// ── Control API (works from either side) ──────────────────────────────────────
|
|
860
|
+
|
|
861
|
+
bool FileTransfer::cancel(const PeerId& peer, uint64_t id) {
|
|
862
|
+
bool acted = false;
|
|
863
|
+
if (auto t = find_outgoing(id)) {
|
|
864
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (!t->finished) { t->status = Status::Cancelled; t->cv.notify_all(); acted = true; } }
|
|
865
|
+
if (acted) { send_simple(t->peer, OP_CANCEL, id); finish_outgoing(t, false); }
|
|
866
|
+
}
|
|
867
|
+
if (auto t = find_incoming(peer, id)) {
|
|
868
|
+
bool a = false;
|
|
869
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (!t->finished) { t->status = Status::Cancelled; a = true; } }
|
|
870
|
+
if (a) { send_simple(peer, OP_CANCEL, id); finish_incoming(t, false, "cancelled"); acted = true; }
|
|
871
|
+
}
|
|
872
|
+
return acted;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
bool FileTransfer::pause(const PeerId& peer, uint64_t id) {
|
|
876
|
+
if (auto t = find_outgoing(id)) {
|
|
877
|
+
bool ok = false;
|
|
878
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) { t->status = Status::Paused; ok = true; t->cv.notify_all(); } }
|
|
879
|
+
if (ok) { send_simple(t->peer, OP_PAUSE, id); return true; }
|
|
880
|
+
}
|
|
881
|
+
if (auto t = find_incoming(peer, id)) {
|
|
882
|
+
bool ok = false;
|
|
883
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) { t->status = Status::Paused; ok = true; } }
|
|
884
|
+
if (ok) { send_simple(peer, OP_PAUSE, id); return true; }
|
|
885
|
+
}
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
bool FileTransfer::resume(const PeerId& peer, uint64_t id) {
|
|
890
|
+
if (auto t = find_outgoing(id)) {
|
|
891
|
+
bool ok = false;
|
|
892
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); ok = true; t->cv.notify_all(); } }
|
|
893
|
+
if (ok) { send_simple(t->peer, OP_RESUME, id); queue_send(id); return true; }
|
|
894
|
+
}
|
|
895
|
+
if (auto t = find_incoming(peer, id)) {
|
|
896
|
+
bool ok = false;
|
|
897
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); ok = true; } }
|
|
898
|
+
if (ok) { send_simple(peer, OP_RESUME, id); return true; }
|
|
899
|
+
}
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// ── Progress callbacks ────────────────────────────────────────────────────────
|
|
904
|
+
|
|
905
|
+
void FileTransfer::emit_progress(const std::shared_ptr<Outgoing>& t) {
|
|
906
|
+
if (!progress_handler_) return;
|
|
907
|
+
const auto now = std::chrono::steady_clock::now();
|
|
908
|
+
Progress p;
|
|
909
|
+
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
910
|
+
t->rate.sample(t->bytes_done, now);
|
|
911
|
+
p.id = t->id; p.peer = t->peer; p.direction = Direction::Sending; p.status = t->status;
|
|
912
|
+
p.bytes_transferred = t->bytes_done; p.total_bytes = t->total_bytes;
|
|
913
|
+
p.files_completed = t->files_done; p.total_files = static_cast<uint32_t>(t->files.size());
|
|
914
|
+
t->rate.fill(p, t->bytes_done, t->total_bytes, now); }
|
|
915
|
+
progress_handler_(p);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
void FileTransfer::emit_progress(const std::shared_ptr<Incoming>& t) {
|
|
919
|
+
if (!progress_handler_) return;
|
|
920
|
+
const auto now = std::chrono::steady_clock::now();
|
|
921
|
+
Progress p;
|
|
922
|
+
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
923
|
+
uint64_t total = 0; for (auto& f : t->files) total += f.size;
|
|
924
|
+
t->rate.sample(t->bytes_done, now);
|
|
925
|
+
p.id = t->id; p.peer = t->peer; p.direction = Direction::Receiving; p.status = t->status;
|
|
926
|
+
p.bytes_transferred = t->bytes_done; p.total_bytes = total;
|
|
927
|
+
p.files_completed = t->files_done; p.total_files = static_cast<uint32_t>(t->files.size());
|
|
928
|
+
t->rate.fill(p, t->bytes_done, total, now); }
|
|
929
|
+
progress_handler_(p);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// ── Maintenance: idle timeout + purge ────────────────────────────────────────
|
|
933
|
+
|
|
934
|
+
void FileTransfer::maintenance_loop() {
|
|
935
|
+
while (running_.load()) {
|
|
936
|
+
{
|
|
937
|
+
std::unique_lock<std::mutex> lk(maintenance_mutex_);
|
|
938
|
+
maintenance_cv_.wait_for(lk, std::chrono::seconds(2), [this] { return !running_.load(); });
|
|
939
|
+
}
|
|
940
|
+
if (!running_.load()) return;
|
|
941
|
+
|
|
942
|
+
const auto now = std::chrono::steady_clock::now();
|
|
943
|
+
const uint32_t timeout = config_.transfer_timeout_secs;
|
|
944
|
+
|
|
945
|
+
std::vector<std::shared_ptr<Outgoing>> out;
|
|
946
|
+
std::vector<std::shared_ptr<Incoming>> in;
|
|
947
|
+
{
|
|
948
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
949
|
+
for (auto& [id, t] : outgoing_) out.push_back(t);
|
|
950
|
+
for (auto& [peer, m] : incoming_) for (auto& [id, t] : m) in.push_back(t);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
for (auto& t : out) {
|
|
954
|
+
bool stale = false;
|
|
955
|
+
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
956
|
+
if (!t->finished && t->status != Status::Paused &&
|
|
957
|
+
std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
|
|
958
|
+
stale = true; }
|
|
959
|
+
if (stale) { LOG_WARN("filexfer", "outgoing transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_outgoing(t, false); }
|
|
960
|
+
}
|
|
961
|
+
for (auto& t : in) {
|
|
962
|
+
bool stale = false;
|
|
963
|
+
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
964
|
+
if (!t->finished && t->status != Status::Paused && t->status != Status::Pending &&
|
|
965
|
+
std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
|
|
966
|
+
stale = true; }
|
|
967
|
+
if (stale) { LOG_WARN("filexfer", "incoming transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_incoming(t, false, "timed out"); }
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
} // namespace librats
|