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,413 @@
|
|
|
1
|
+
#include "librats/bittorrent/client.h"
|
|
2
|
+
#include "librats/bittorrent/log.h"
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <chrono>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <utility>
|
|
8
|
+
|
|
9
|
+
namespace librats::bittorrent {
|
|
10
|
+
|
|
11
|
+
Client::Client() : Client(Config{}) {}
|
|
12
|
+
|
|
13
|
+
Client::Client(Config config)
|
|
14
|
+
: config_(std::move(config))
|
|
15
|
+
, peer_id_(generate_peer_id(config_.peer_id_prefix)) {}
|
|
16
|
+
|
|
17
|
+
Client::~Client() {
|
|
18
|
+
stop();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void Client::open() {
|
|
22
|
+
if (opened_) return;
|
|
23
|
+
opened_ = true;
|
|
24
|
+
open_listener();
|
|
25
|
+
schedule_reap();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void Client::start() {
|
|
29
|
+
open();
|
|
30
|
+
reactor_.start();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void Client::stop() {
|
|
34
|
+
if (!opened_) return;
|
|
35
|
+
// Disarm the DHT callbacks BEFORE the reactor goes away: the shared DHT keeps
|
|
36
|
+
// running after us (node teardown is reverse-attach order) and a get_peers reply
|
|
37
|
+
// for a torrent we are about to drop would otherwise post to a destroyed reactor.
|
|
38
|
+
{
|
|
39
|
+
std::lock_guard<std::mutex> lock(dht_guard_->mutex);
|
|
40
|
+
dht_guard_->alive = false;
|
|
41
|
+
}
|
|
42
|
+
reactor_.stop(); // join the loop thread first so nothing touches state concurrently
|
|
43
|
+
opened_ = false;
|
|
44
|
+
if (reap_timer_ != kInvalidTimerId) { reactor_.cancel(reap_timer_); reap_timer_ = kInvalidTimerId; }
|
|
45
|
+
for (auto& [hash, t] : torrents_) {
|
|
46
|
+
// Drop the traversal too: nothing consumes its result any more, and it would
|
|
47
|
+
// keep hammering the DHT for a torrent that no longer exists.
|
|
48
|
+
if (dht_) dht_->cancel_search(hash);
|
|
49
|
+
t->stop();
|
|
50
|
+
}
|
|
51
|
+
torrents_.clear();
|
|
52
|
+
connections_.clear();
|
|
53
|
+
// Reclaim any outbound sockets still mid-connect (their completion lambda will
|
|
54
|
+
// never run now that the reactor is stopped).
|
|
55
|
+
for (socket_t s : pending_connects_) { reactor_.remove(s); close_socket(s); }
|
|
56
|
+
pending_connects_.clear();
|
|
57
|
+
if (is_valid_socket(listener_)) { reactor_.remove(listener_); close_socket(listener_); listener_ = RATS_INVALID_SOCKET; }
|
|
58
|
+
// Fresh token, so a Client that is start()ed again gets DHT peers delivered.
|
|
59
|
+
// Safe to swap unsynchronised: the reactor thread is joined and the old token is
|
|
60
|
+
// kept alive by whatever callbacks still hold it.
|
|
61
|
+
dht_guard_ = std::make_shared<DhtCallbackGuard>();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void Client::open_listener() {
|
|
65
|
+
listener_ = create_tcp_server(config_.listen_port, 16, "", AddressFamily::IPv4);
|
|
66
|
+
if (!is_valid_socket(listener_)) {
|
|
67
|
+
LOG_ERROR("bt.client", "failed to bind listen port " << config_.listen_port
|
|
68
|
+
<< " — inbound peers disabled");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
set_socket_nonblocking(listener_);
|
|
72
|
+
actual_port_ = std::uint16_t(get_bound_port(listener_));
|
|
73
|
+
reactor_.add(listener_, PollIn, [this](std::uint32_t) { on_accept(); });
|
|
74
|
+
LOG_INFO("bt.client", "listening on port " << actual_port_);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void Client::on_accept() {
|
|
78
|
+
for (;;) {
|
|
79
|
+
// Raw accept so a non-blocking EWOULDBLOCK drain is quiet (accept_client
|
|
80
|
+
// logs it as an error). nullptr addr: we read the peer address elsewhere.
|
|
81
|
+
socket_t s = ::accept(listener_, nullptr, nullptr);
|
|
82
|
+
if (!is_valid_socket(s)) break; // drained (non-blocking listener)
|
|
83
|
+
suppress_sigpipe(s); // not inherited from the listener; raw accept()
|
|
84
|
+
// Cap total connections so an inbound flood can't exhaust memory / fds (H3).
|
|
85
|
+
// Keep draining the accept queue, but immediately drop anything over the cap.
|
|
86
|
+
if (connections_.size() >= kMaxConnections) {
|
|
87
|
+
LOG_DEBUG("bt.client", "connection cap " << kMaxConnections << " reached, dropping inbound");
|
|
88
|
+
close_socket(s);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
set_socket_nonblocking(s);
|
|
92
|
+
|
|
93
|
+
// Remote source endpoint (for logging / PEX). The peer's *listen* port is
|
|
94
|
+
// learned later from its extended handshake; this is the ephemeral source.
|
|
95
|
+
std::string ip;
|
|
96
|
+
std::uint16_t port = 0;
|
|
97
|
+
if (const std::string ep = get_peer_address(s); !ep.empty()) {
|
|
98
|
+
const std::size_t colon = ep.rfind(':');
|
|
99
|
+
if (colon != std::string::npos) {
|
|
100
|
+
ip = ep.substr(0, colon);
|
|
101
|
+
port = std::uint16_t(std::atoi(ep.c_str() + colon + 1));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
auto resolver = [this](const InfoHash& ih, PeerConnection::Binding& out) -> bool {
|
|
106
|
+
auto it = torrents_.find(ih);
|
|
107
|
+
if (it == torrents_.end() || !it->second) return false;
|
|
108
|
+
out.observer = it->second.get();
|
|
109
|
+
out.num_pieces = it->second->num_pieces();
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
auto pc = std::make_unique<PeerConnection>(reactor_, s, peer_id_, std::move(resolver),
|
|
113
|
+
std::move(ip), port);
|
|
114
|
+
LOG_DEBUG("bt.client", "inbound connection from " << ip << ':' << port);
|
|
115
|
+
PeerConnection* raw = pc.get();
|
|
116
|
+
connections_.push_back(std::move(pc));
|
|
117
|
+
raw->start();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
void Client::connect_peer(Torrent& torrent, const std::string& ip, std::uint16_t port) {
|
|
122
|
+
if (connections_.size() >= kMaxConnections) { torrent.on_connect_failed(ip, port); return; }
|
|
123
|
+
socket_t s = tcp_connect_start(ip, int(port));
|
|
124
|
+
if (!is_valid_socket(s)) { torrent.on_connect_failed(ip, port); return; }
|
|
125
|
+
|
|
126
|
+
// Capture the info-hash, not a raw Torrent*: the torrent may be removed before
|
|
127
|
+
// the connect completes, so we re-resolve it (and bail if it's gone) rather
|
|
128
|
+
// than dereference a dangling pointer (H10). The socket is tracked so a
|
|
129
|
+
// mid-connect stop() can reclaim it.
|
|
130
|
+
const InfoHash ih = torrent.info_hash();
|
|
131
|
+
pending_connects_.insert(s);
|
|
132
|
+
reactor_.add(s, PollOut, [this, ih, s, ip, port](std::uint32_t) {
|
|
133
|
+
reactor_.remove(s); // done watching for connect completion
|
|
134
|
+
pending_connects_.erase(s);
|
|
135
|
+
auto it = torrents_.find(ih);
|
|
136
|
+
Torrent* t = (it != torrents_.end()) ? it->second.get() : nullptr;
|
|
137
|
+
if (tcp_connect_result(s) != 0 || !t) {
|
|
138
|
+
close_socket(s);
|
|
139
|
+
if (t) t->on_connect_failed(ip, port);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
auto pc = std::make_unique<PeerConnection>(reactor_, s, /*outgoing=*/true,
|
|
143
|
+
t->info_hash(), peer_id_, t->num_pieces(), t,
|
|
144
|
+
ip, port);
|
|
145
|
+
PeerConnection* raw = pc.get();
|
|
146
|
+
connections_.push_back(std::move(pc));
|
|
147
|
+
raw->start();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
void Client::find_peers_via_dht(const InfoHash& info_hash,
|
|
152
|
+
std::function<void(const std::string&, std::uint16_t)> on_peer) {
|
|
153
|
+
if (!dht_ || !dht_->is_running()) return;
|
|
154
|
+
// DhtClient delivers results on its own thread; marshal them onto the reactor.
|
|
155
|
+
// Re-resolve the torrent by info-hash before invoking on_peer: the torrent may
|
|
156
|
+
// have been removed between the get_peers request and its (seconds-later) reply,
|
|
157
|
+
// and on_peer captures the Torrent by pointer — dereferencing it after removal is
|
|
158
|
+
// a use-after-free of peer_list_ (same H10 hazard fixed in connect_peer). Removal
|
|
159
|
+
// happens only on the reactor thread, so a torrent present here stays alive for
|
|
160
|
+
// the whole callback.
|
|
161
|
+
// The guard covers the *other* half of that hazard: the whole Client may be gone
|
|
162
|
+
// by the time the reply lands, since the shared DHT is torn down after us. Held
|
|
163
|
+
// for the post() only — the work itself still runs on the reactor.
|
|
164
|
+
dht_->find_peers(info_hash, [this, guard = dht_guard_, info_hash, on_peer](const std::vector<Address>& peers,
|
|
165
|
+
const InfoHash&) {
|
|
166
|
+
std::lock_guard<std::mutex> lock(guard->mutex);
|
|
167
|
+
if (!guard->alive) return; // Client stopped/destroyed — its reactor is gone
|
|
168
|
+
reactor_.post([this, info_hash, peers, on_peer] {
|
|
169
|
+
if (torrents_.find(info_hash) == torrents_.end()) return; // torrent gone
|
|
170
|
+
for (const Address& a : peers) on_peer(a.ip.to_string(), a.port);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void Client::announce_to_dht(const InfoHash& info_hash, std::uint16_t port) {
|
|
176
|
+
// Publish ourselves to the info-hash's DHT nodes so other clients' get_peers
|
|
177
|
+
// find us (BEP 5). DhtClient is the node's shared, thread-safe instance.
|
|
178
|
+
if (dht_ && dht_->is_running()) dht_->announce_peer(info_hash, port);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Torrent* Client::add_torrent(const TorrentInfo& info, const std::string& save_path) {
|
|
182
|
+
return run_on_reactor([&] { return add_torrent_impl(info, save_path); });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Torrent* Client::add_torrent_impl(const TorrentInfo& info, const std::string& save_path) {
|
|
186
|
+
if (!info.is_valid() || !info.has_metadata()) {
|
|
187
|
+
LOG_WARN("bt.client", "rejected invalid/incomplete torrent");
|
|
188
|
+
return nullptr;
|
|
189
|
+
}
|
|
190
|
+
const InfoHash ih = info.info_hash();
|
|
191
|
+
if (torrents_.count(ih)) return torrents_[ih].get();
|
|
192
|
+
|
|
193
|
+
const std::string path = save_path.empty() ? config_.download_path : save_path;
|
|
194
|
+
auto t = std::make_unique<Torrent>(reactor_, *this, info, path);
|
|
195
|
+
Torrent* raw = t.get();
|
|
196
|
+
torrents_.emplace(ih, std::move(t));
|
|
197
|
+
LOG_INFO("bt.client", "added torrent " << short_hash(ih) << " \"" << info.name() << "\" → " << path);
|
|
198
|
+
raw->start();
|
|
199
|
+
return raw;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Torrent* Client::add_magnet(const std::string& magnet_uri, const std::string& save_path) {
|
|
203
|
+
return run_on_reactor([&] { return add_magnet_impl(magnet_uri, save_path, /*resume=*/false); });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Torrent* Client::add_magnet_resumed(const std::string& magnet_uri, const std::string& save_path) {
|
|
207
|
+
return run_on_reactor([&] { return add_magnet_impl(magnet_uri, save_path, /*resume=*/true); });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
Torrent* Client::add_magnet_impl(const std::string& magnet_uri, const std::string& save_path, bool resume) {
|
|
211
|
+
auto info = TorrentInfo::from_magnet(magnet_uri);
|
|
212
|
+
if (!info || !info->is_valid()) {
|
|
213
|
+
LOG_WARN("bt.client", "rejected invalid magnet uri");
|
|
214
|
+
return nullptr;
|
|
215
|
+
}
|
|
216
|
+
const InfoHash ih = info->info_hash();
|
|
217
|
+
if (torrents_.count(ih)) return torrents_[ih].get();
|
|
218
|
+
|
|
219
|
+
const std::string path = save_path.empty() ? config_.download_path : save_path;
|
|
220
|
+
auto t = std::make_unique<Torrent>(reactor_, *this, *info, path);
|
|
221
|
+
Torrent* raw = t.get();
|
|
222
|
+
torrents_.emplace(ih, std::move(t));
|
|
223
|
+
// Resume must be applied before start(); it completes the metadata + trusted have
|
|
224
|
+
// set if a resume file exists next to the download.
|
|
225
|
+
if (resume && raw->try_load_resume_data())
|
|
226
|
+
LOG_INFO("bt.client", "restored resume data for " << short_hash(ih));
|
|
227
|
+
LOG_INFO("bt.client", "added magnet" << (resume ? " (resumed) " : " ") << short_hash(ih) << " → " << path);
|
|
228
|
+
raw->start();
|
|
229
|
+
return raw;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Torrent* Client::add_torrent_with_resume(const TorrentInfo& info, const ResumeData& resume,
|
|
233
|
+
const std::string& save_path) {
|
|
234
|
+
return run_on_reactor([&] { return add_torrent_with_resume_impl(info, resume, save_path); });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Torrent* Client::add_torrent_with_resume_impl(const TorrentInfo& info, const ResumeData& resume,
|
|
238
|
+
const std::string& save_path) {
|
|
239
|
+
if (!info.is_valid()) return nullptr;
|
|
240
|
+
const InfoHash ih = info.info_hash();
|
|
241
|
+
if (torrents_.count(ih)) return torrents_[ih].get();
|
|
242
|
+
|
|
243
|
+
const std::string path = save_path.empty() ? config_.download_path : save_path;
|
|
244
|
+
auto t = std::make_unique<Torrent>(reactor_, *this, info, path);
|
|
245
|
+
Torrent* raw = t.get();
|
|
246
|
+
torrents_.emplace(ih, std::move(t));
|
|
247
|
+
raw->load_resume_data(resume); // must precede start()
|
|
248
|
+
raw->start();
|
|
249
|
+
return raw;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Torrent* Client::add_torrent_for_seeding(const TorrentInfo& info, const std::string& save_path) {
|
|
253
|
+
return run_on_reactor([&]() -> Torrent* {
|
|
254
|
+
if (!info.is_valid() || !info.has_metadata()) return nullptr;
|
|
255
|
+
ResumeData rd;
|
|
256
|
+
rd.info_hash = info.info_hash();
|
|
257
|
+
rd.have = Bitfield(info.num_pieces(), true); // assume every piece is present
|
|
258
|
+
return add_torrent_with_resume_impl(info, rd, save_path);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void Client::save_all_resume_data() {
|
|
263
|
+
run_on_reactor([&] { for (auto& [hash, t] : torrents_) t->save_resume_data(); });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Torrent* Client::get_torrent(const InfoHash& info_hash) {
|
|
267
|
+
auto it = torrents_.find(info_hash);
|
|
268
|
+
return it == torrents_.end() ? nullptr : it->second.get();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
void Client::remove_torrent(const InfoHash& info_hash, bool /*delete_files*/) {
|
|
272
|
+
run_on_reactor([&] { remove_torrent_impl(info_hash); });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
void Client::remove_torrent_impl(const InfoHash& info_hash) {
|
|
276
|
+
auto it = torrents_.find(info_hash);
|
|
277
|
+
if (it == torrents_.end()) return;
|
|
278
|
+
LOG_INFO("bt.client", "removed torrent " << short_hash(info_hash));
|
|
279
|
+
it->second->stop(); // closes the peers the torrent has registered in its peer list
|
|
280
|
+
// stop() only closes connections the torrent knows about (its peers_ list). An
|
|
281
|
+
// *outgoing* connection that has completed connect() but not yet the wire
|
|
282
|
+
// handshake holds obs_ = this torrent, yet is not in peers_ until on_handshake —
|
|
283
|
+
// so stop() misses it. Erasing the torrent now would leave that live connection
|
|
284
|
+
// with a dangling observer, and its next message (handshake reply + payload in
|
|
285
|
+
// one segment) dereferences the freed Torrent → use-after-free crashing in
|
|
286
|
+
// dispatch(). Close every remaining live connection bound to this info-hash
|
|
287
|
+
// while the torrent is still alive (close() fires on_closed() on it).
|
|
288
|
+
for (auto& pc : connections_)
|
|
289
|
+
if (!pc->closed() && pc->info_hash() == info_hash) pc->close("torrent removed");
|
|
290
|
+
torrents_.erase(it);
|
|
291
|
+
// Stop the DHT traversal started for this torrent: its result has no consumer
|
|
292
|
+
// left, and letting it run keeps a lookup alive for a hash we no longer hold.
|
|
293
|
+
if (dht_) dht_->cancel_search(info_hash);
|
|
294
|
+
// File deletion is not yet implemented; the torrent's data is left on disk.
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
TorrentStatus Client::torrent_status(const InfoHash& info_hash) {
|
|
298
|
+
return run_on_reactor([&]() -> TorrentStatus {
|
|
299
|
+
TorrentStatus s;
|
|
300
|
+
auto it = torrents_.find(info_hash);
|
|
301
|
+
if (it == torrents_.end() || !it->second) return s;
|
|
302
|
+
const Torrent* t = it->second.get();
|
|
303
|
+
const TorrentInfo& info = t->torrent_info();
|
|
304
|
+
s.exists = true;
|
|
305
|
+
s.name = info.name();
|
|
306
|
+
s.has_metadata = t->has_metadata();
|
|
307
|
+
s.is_complete = t->is_complete();
|
|
308
|
+
s.paused = t->is_paused();
|
|
309
|
+
s.progress = t->progress();
|
|
310
|
+
s.downloaded = t->bytes_downloaded();
|
|
311
|
+
s.uploaded = t->bytes_uploaded();
|
|
312
|
+
s.num_peers = t->num_peers();
|
|
313
|
+
if (info.has_metadata()) {
|
|
314
|
+
s.total_size = std::uint64_t(info.total_size());
|
|
315
|
+
for (const FileEntry& f : info.files().files())
|
|
316
|
+
s.files.push_back({f.path, f.size});
|
|
317
|
+
}
|
|
318
|
+
return s;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
std::optional<TorrentInfo> Client::torrent_metadata(const InfoHash& info_hash) {
|
|
323
|
+
return run_on_reactor([&]() -> std::optional<TorrentInfo> {
|
|
324
|
+
auto it = torrents_.find(info_hash);
|
|
325
|
+
if (it == torrents_.end() || !it->second) return std::nullopt;
|
|
326
|
+
const Torrent* t = it->second.get();
|
|
327
|
+
if (!t->has_metadata()) return std::nullopt;
|
|
328
|
+
// Copy the info out under reactor ownership; TorrentInfo is a value type,
|
|
329
|
+
// so the caller gets a standalone snapshot safe to use off-thread.
|
|
330
|
+
return t->torrent_info();
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
void Client::pause_torrent(const InfoHash& info_hash) {
|
|
335
|
+
run_on_reactor([&] {
|
|
336
|
+
auto it = torrents_.find(info_hash);
|
|
337
|
+
if (it != torrents_.end() && it->second) it->second->pause();
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
void Client::resume_torrent(const InfoHash& info_hash) {
|
|
342
|
+
run_on_reactor([&] {
|
|
343
|
+
auto it = torrents_.find(info_hash);
|
|
344
|
+
if (it != torrents_.end() && it->second) it->second->resume();
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
bool Client::save_resume_data(const InfoHash& info_hash) {
|
|
349
|
+
return run_on_reactor([&]() -> bool {
|
|
350
|
+
auto it = torrents_.find(info_hash);
|
|
351
|
+
return it != torrents_.end() && it->second && it->second->save_resume_data();
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
std::vector<Torrent*> Client::torrents() {
|
|
356
|
+
std::vector<Torrent*> out;
|
|
357
|
+
out.reserve(torrents_.size());
|
|
358
|
+
for (auto& [hash, t] : torrents_) out.push_back(t.get());
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
Torrent* Client::add_torrent_file(const std::string& path, const std::string& save_path) {
|
|
363
|
+
auto info = TorrentInfo::from_file(path); // pure parse — safe off the reactor thread
|
|
364
|
+
if (!info) return nullptr;
|
|
365
|
+
return run_on_reactor([&] { return add_torrent_impl(*info, save_path); });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
std::size_t Client::total_peers() const {
|
|
369
|
+
std::size_t n = 0;
|
|
370
|
+
for (const auto& [hash, t] : torrents_) n += t->num_peers();
|
|
371
|
+
return n;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
void Client::schedule_reap() {
|
|
375
|
+
if (!opened_) return;
|
|
376
|
+
reap_timer_ = reactor_.schedule(std::chrono::seconds(1), [this] {
|
|
377
|
+
reap_closed();
|
|
378
|
+
sample_rates();
|
|
379
|
+
schedule_reap();
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
void Client::reap_closed() {
|
|
384
|
+
connections_.erase(
|
|
385
|
+
std::remove_if(connections_.begin(), connections_.end(),
|
|
386
|
+
[](const std::unique_ptr<PeerConnection>& pc) { return pc->closed(); }),
|
|
387
|
+
connections_.end());
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
void Client::sample_rates() {
|
|
391
|
+
std::uint64_t down = 0, up = 0;
|
|
392
|
+
for (auto& [hash, t] : torrents_) {
|
|
393
|
+
down += t->bytes_downloaded();
|
|
394
|
+
up += t->bytes_uploaded();
|
|
395
|
+
}
|
|
396
|
+
const auto now = std::chrono::steady_clock::now();
|
|
397
|
+
if (last_sample_.time_since_epoch().count() != 0) {
|
|
398
|
+
const double dt = std::chrono::duration<double>(now - last_sample_).count();
|
|
399
|
+
if (dt > 0) {
|
|
400
|
+
// Counters are monotonic, but guard against a torrent being removed
|
|
401
|
+
// between samples (which would make the aggregate drop).
|
|
402
|
+
const std::uint64_t d_down = down >= last_down_bytes_ ? down - last_down_bytes_ : 0;
|
|
403
|
+
const std::uint64_t d_up = up >= last_up_bytes_ ? up - last_up_bytes_ : 0;
|
|
404
|
+
down_rate_.store(std::uint64_t(double(d_down) / dt), std::memory_order_relaxed);
|
|
405
|
+
up_rate_.store(std::uint64_t(double(d_up) / dt), std::memory_order_relaxed);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
last_down_bytes_ = down;
|
|
409
|
+
last_up_bytes_ = up;
|
|
410
|
+
last_sample_ = now;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
} // namespace librats::bittorrent
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file client.h
|
|
5
|
+
* @brief The BitTorrent session: owns the reactor, the listen socket and the
|
|
6
|
+
* set of torrents, and brokers peer connections between them.
|
|
7
|
+
*
|
|
8
|
+
* Client is the top-level handle an application drives. It accepts incoming peers
|
|
9
|
+
* and routes each (by the info-hash in its handshake) to the matching Torrent;
|
|
10
|
+
* it dials outgoing peers on a Torrent's behalf. All connections are owned here
|
|
11
|
+
* in one pool and reaped once closed, so torrents only ever hold raw pointers.
|
|
12
|
+
*
|
|
13
|
+
* Lifecycle: open() wires up the listener and timers on the reactor thread;
|
|
14
|
+
* start() additionally runs the reactor on its own thread. Tests instead pump
|
|
15
|
+
* reactor().run_one() so everything stays single-threaded and deterministic.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include "librats/util/rats_export.h"
|
|
19
|
+
#include "librats/bittorrent/peer_connection.h"
|
|
20
|
+
#include "librats/bittorrent/reactor.h"
|
|
21
|
+
#include "librats/bittorrent/torrent.h"
|
|
22
|
+
#include "librats/bittorrent/torrent_info.h"
|
|
23
|
+
#include "librats/bittorrent/types.h"
|
|
24
|
+
#include "librats/core/socket.h"
|
|
25
|
+
#include "librats/core/types.h"
|
|
26
|
+
#include "librats/dht/dht.h"
|
|
27
|
+
|
|
28
|
+
#include <atomic>
|
|
29
|
+
#include <chrono>
|
|
30
|
+
#include <cstdint>
|
|
31
|
+
#include <functional>
|
|
32
|
+
#include <future>
|
|
33
|
+
#include <map>
|
|
34
|
+
#include <memory>
|
|
35
|
+
#include <mutex>
|
|
36
|
+
#include <string>
|
|
37
|
+
#include <type_traits>
|
|
38
|
+
#include <unordered_set>
|
|
39
|
+
#include <vector>
|
|
40
|
+
|
|
41
|
+
namespace librats::bittorrent {
|
|
42
|
+
|
|
43
|
+
/// A thread-safe snapshot of one torrent's state. Produced by Client::torrent_status
|
|
44
|
+
/// on the reactor thread so callers on any thread get a consistent view without
|
|
45
|
+
/// touching the (lock-free, reactor-owned) Torrent directly.
|
|
46
|
+
struct TorrentStatus {
|
|
47
|
+
struct File { std::string path; std::int64_t size = 0; };
|
|
48
|
+
|
|
49
|
+
bool exists = false; ///< false if no such torrent (rest is default)
|
|
50
|
+
std::string name;
|
|
51
|
+
bool has_metadata = false;
|
|
52
|
+
bool is_complete = false;
|
|
53
|
+
bool paused = false;
|
|
54
|
+
double progress = 0.0; ///< 0..1
|
|
55
|
+
std::uint64_t total_size = 0;
|
|
56
|
+
std::uint64_t downloaded = 0;
|
|
57
|
+
std::uint64_t uploaded = 0;
|
|
58
|
+
std::size_t num_peers = 0;
|
|
59
|
+
std::vector<File> files; ///< populated once metadata is known
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
class RATS_API Client final : public TorrentHost {
|
|
63
|
+
public:
|
|
64
|
+
struct Config {
|
|
65
|
+
std::uint16_t listen_port = 6881; ///< 0 = ephemeral
|
|
66
|
+
std::string download_path; ///< default save directory
|
|
67
|
+
std::string peer_id_prefix = "-LR0001-";
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
Client();
|
|
71
|
+
explicit Client(Config config);
|
|
72
|
+
~Client() override;
|
|
73
|
+
|
|
74
|
+
Client(const Client&) = delete;
|
|
75
|
+
Client& operator=(const Client&) = delete;
|
|
76
|
+
|
|
77
|
+
/// Wire up the listener + housekeeping timers. Must run on the reactor thread
|
|
78
|
+
/// (call directly before pumping in tests, or via start()).
|
|
79
|
+
void open();
|
|
80
|
+
/// open() + run the reactor on a background thread.
|
|
81
|
+
void start();
|
|
82
|
+
/// Stop the reactor (joining its thread) and tear everything down.
|
|
83
|
+
void stop();
|
|
84
|
+
|
|
85
|
+
bool is_running() const noexcept { return opened_; }
|
|
86
|
+
std::uint16_t listen_port() const noexcept { return actual_port_; }
|
|
87
|
+
Reactor& reactor() noexcept { return reactor_; }
|
|
88
|
+
|
|
89
|
+
Torrent* add_torrent(const TorrentInfo& info, const std::string& save_path = "");
|
|
90
|
+
/// Add a magnet link — the torrent starts metadata-less and fetches its info
|
|
91
|
+
/// dict from peers (BEP 9) before downloading.
|
|
92
|
+
Torrent* add_magnet(const std::string& magnet_uri, const std::string& save_path = "");
|
|
93
|
+
/// Like add_magnet, but first loads any resume file saved next to @p save_path
|
|
94
|
+
/// (see Torrent::try_load_resume_data) so a previously-downloaded torrent
|
|
95
|
+
/// resumes its pieces — and, if the resume file embeds the info dict, skips the
|
|
96
|
+
/// metadata re-fetch entirely. Used to restore torrents across restarts.
|
|
97
|
+
Torrent* add_magnet_resumed(const std::string& magnet_uri, const std::string& save_path = "");
|
|
98
|
+
/// Add a torrent and apply saved resume state (trusts the recorded pieces).
|
|
99
|
+
Torrent* add_torrent_with_resume(const TorrentInfo& info, const ResumeData& resume,
|
|
100
|
+
const std::string& save_path = "");
|
|
101
|
+
/// Add a freshly-created torrent whose files already exist at @p save_path,
|
|
102
|
+
/// trusting every piece so it starts seeding without re-hashing.
|
|
103
|
+
Torrent* add_torrent_for_seeding(const TorrentInfo& info, const std::string& save_path);
|
|
104
|
+
/// Load a .torrent file from disk and add it. Returns nullptr if the file
|
|
105
|
+
/// cannot be read or parsed. Convenience over TorrentInfo::from_file + add_torrent.
|
|
106
|
+
Torrent* add_torrent_file(const std::string& path, const std::string& save_path = "");
|
|
107
|
+
Torrent* get_torrent(const InfoHash& info_hash);
|
|
108
|
+
void remove_torrent(const InfoHash& info_hash, bool delete_files = false);
|
|
109
|
+
std::vector<Torrent*> torrents();
|
|
110
|
+
/// Persist resume data for every torrent to its default path.
|
|
111
|
+
void save_all_resume_data();
|
|
112
|
+
|
|
113
|
+
// ---- thread-safe control / inspection (marshalled onto the reactor) ----
|
|
114
|
+
// Prefer these over reaching into a Torrent* from another thread: Torrent state
|
|
115
|
+
// is lock-free and reactor-owned, so it must only be touched on the reactor.
|
|
116
|
+
|
|
117
|
+
/// Consistent snapshot of one torrent (exists=false if not found).
|
|
118
|
+
TorrentStatus torrent_status(const InfoHash& info_hash);
|
|
119
|
+
/// Thread-safe copy of a loaded torrent's full metadata, or nullopt when the
|
|
120
|
+
/// torrent is unknown or its metadata hasn't arrived yet. Marshalled onto the
|
|
121
|
+
/// reactor, so it is safe to call from any thread — unlike get_torrent(), which
|
|
122
|
+
/// hands back a reactor-owned Torrent* that must not be touched off-thread.
|
|
123
|
+
std::optional<TorrentInfo> torrent_metadata(const InfoHash& info_hash);
|
|
124
|
+
/// Pause / resume a torrent without re-hashing (see Torrent::pause).
|
|
125
|
+
void pause_torrent(const InfoHash& info_hash);
|
|
126
|
+
void resume_torrent(const InfoHash& info_hash);
|
|
127
|
+
/// Persist one torrent's resume data to its default path. Returns false if the
|
|
128
|
+
/// torrent is unknown or the write failed.
|
|
129
|
+
bool save_resume_data(const InfoHash& info_hash);
|
|
130
|
+
|
|
131
|
+
// ---- aggregate stats (for status lines / UI) ----
|
|
132
|
+
std::size_t num_torrents() const noexcept { return torrents_.size(); }
|
|
133
|
+
std::size_t total_peers() const;
|
|
134
|
+
/// Swarm-wide transfer rates in bytes/sec, sampled once per second by the
|
|
135
|
+
/// housekeeping timer. Atomic so they can be read from another thread.
|
|
136
|
+
std::uint64_t total_download_rate() const noexcept { return down_rate_.load(std::memory_order_relaxed); }
|
|
137
|
+
std::uint64_t total_upload_rate() const noexcept { return up_rate_.load(std::memory_order_relaxed); }
|
|
138
|
+
|
|
139
|
+
/// Share an externally-owned DHT for peer discovery (e.g. the node's). Its
|
|
140
|
+
/// lifetime is the caller's; Client never starts or stops it.
|
|
141
|
+
void set_external_dht(DhtClient* dht) noexcept { dht_ = dht; }
|
|
142
|
+
DhtClient* get_dht_client() const noexcept { return dht_; }
|
|
143
|
+
|
|
144
|
+
// ---- TorrentHost ----
|
|
145
|
+
void connect_peer(Torrent& torrent, const std::string& ip, std::uint16_t port) override;
|
|
146
|
+
const PeerId& peer_id() const override { return peer_id_; }
|
|
147
|
+
void find_peers_via_dht(const InfoHash& info_hash,
|
|
148
|
+
std::function<void(const std::string& ip, std::uint16_t port)> on_peer) override;
|
|
149
|
+
void announce_to_dht(const InfoHash& info_hash, std::uint16_t port) override;
|
|
150
|
+
|
|
151
|
+
/// Largest number of peer connections (in + out) the session will hold at once.
|
|
152
|
+
/// Beyond this, inbound sockets are accepted and immediately closed so a flood
|
|
153
|
+
/// of incoming connections can't exhaust memory / file descriptors.
|
|
154
|
+
static constexpr std::size_t kMaxConnections = 200;
|
|
155
|
+
|
|
156
|
+
private:
|
|
157
|
+
void open_listener();
|
|
158
|
+
void on_accept();
|
|
159
|
+
void schedule_reap();
|
|
160
|
+
void reap_closed();
|
|
161
|
+
void sample_rates(); ///< recompute down_rate_/up_rate_ from per-torrent byte counters
|
|
162
|
+
|
|
163
|
+
// Run @p f on the reactor thread and return its result. Executed inline when
|
|
164
|
+
// already on the reactor thread or when the reactor isn't running its own
|
|
165
|
+
// thread yet (tests pump run_one); otherwise posted and waited on. This makes
|
|
166
|
+
// the public mutators below safe to call from any thread (C2).
|
|
167
|
+
template <class F>
|
|
168
|
+
std::invoke_result_t<F> run_on_reactor(F f) {
|
|
169
|
+
using R = std::invoke_result_t<F>;
|
|
170
|
+
if (reactor_.on_reactor_thread() || !reactor_.running()) return f();
|
|
171
|
+
std::promise<R> prom;
|
|
172
|
+
auto fut = prom.get_future();
|
|
173
|
+
reactor_.post([&] {
|
|
174
|
+
if constexpr (std::is_void_v<R>) { f(); prom.set_value(); }
|
|
175
|
+
else { prom.set_value(f()); }
|
|
176
|
+
});
|
|
177
|
+
return fut.get();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Unsynchronised implementations — only ever run on the reactor thread (either
|
|
181
|
+
// directly in the single-threaded/test path or via run_on_reactor()).
|
|
182
|
+
Torrent* add_torrent_impl(const TorrentInfo& info, const std::string& save_path);
|
|
183
|
+
Torrent* add_magnet_impl(const std::string& magnet_uri, const std::string& save_path, bool resume);
|
|
184
|
+
Torrent* add_torrent_with_resume_impl(const TorrentInfo& info, const ResumeData& resume,
|
|
185
|
+
const std::string& save_path);
|
|
186
|
+
void remove_torrent_impl(const InfoHash& info_hash);
|
|
187
|
+
|
|
188
|
+
Reactor reactor_;
|
|
189
|
+
Config config_;
|
|
190
|
+
PeerId peer_id_;
|
|
191
|
+
socket_t listener_ = RATS_INVALID_SOCKET;
|
|
192
|
+
std::uint16_t actual_port_ = 0;
|
|
193
|
+
bool opened_ = false;
|
|
194
|
+
TimerId reap_timer_ = kInvalidTimerId;
|
|
195
|
+
|
|
196
|
+
DhtClient* dht_ = nullptr; ///< external, non-owning
|
|
197
|
+
|
|
198
|
+
/// Keeps DHT callbacks from touching a torn-down Client. A get_peers traversal
|
|
199
|
+
/// runs for seconds, and the shared DHT is stopped *after* us (node teardown is
|
|
200
|
+
/// reverse-attach order), so a reply can land once our reactor is gone and this
|
|
201
|
+
/// object destroyed — dereferencing it then is a use-after-free that hangs the
|
|
202
|
+
/// DHT loop thread inside Reactor::post, and with it the DHT's own join(). Every
|
|
203
|
+
/// callback we hand to the DHT captures this token by shared_ptr and does its
|
|
204
|
+
/// work under `mutex` with `alive` checked; stop() clears the flag under the same
|
|
205
|
+
/// mutex, so a callback either completes before teardown or does nothing at all.
|
|
206
|
+
struct DhtCallbackGuard {
|
|
207
|
+
std::mutex mutex;
|
|
208
|
+
bool alive = true;
|
|
209
|
+
};
|
|
210
|
+
std::shared_ptr<DhtCallbackGuard> dht_guard_ = std::make_shared<DhtCallbackGuard>();
|
|
211
|
+
|
|
212
|
+
std::map<InfoHash, std::unique_ptr<Torrent>> torrents_;
|
|
213
|
+
std::vector<std::unique_ptr<PeerConnection>> connections_;
|
|
214
|
+
/// Outbound sockets still waiting for connect() to complete. Tracked so a
|
|
215
|
+
/// mid-connect stop() can close them, and so the completion looks the torrent
|
|
216
|
+
/// up by info-hash rather than holding a raw Torrent* that may have been removed.
|
|
217
|
+
std::unordered_set<socket_t> pending_connects_;
|
|
218
|
+
|
|
219
|
+
// Rate sampling (updated on the reactor thread once per second).
|
|
220
|
+
std::atomic<std::uint64_t> down_rate_{0};
|
|
221
|
+
std::atomic<std::uint64_t> up_rate_{0};
|
|
222
|
+
std::uint64_t last_down_bytes_ = 0;
|
|
223
|
+
std::uint64_t last_up_bytes_ = 0;
|
|
224
|
+
std::chrono::steady_clock::time_point last_sample_{};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
} // namespace librats::bittorrent
|