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,352 @@
|
|
|
1
|
+
#include "librats/bittorrent/piece_picker.h"
|
|
2
|
+
#include "librats/bittorrent/log.h"
|
|
3
|
+
#include "librats/bittorrent/types.h" // kBlockSize
|
|
4
|
+
|
|
5
|
+
#include <algorithm>
|
|
6
|
+
|
|
7
|
+
namespace librats::bittorrent {
|
|
8
|
+
|
|
9
|
+
PiecePicker::PiecePicker(std::uint32_t num_pieces, std::uint32_t piece_length, std::int64_t total_size)
|
|
10
|
+
: num_pieces_(num_pieces)
|
|
11
|
+
, piece_length_(piece_length)
|
|
12
|
+
, total_size_(total_size)
|
|
13
|
+
, availability_(num_pieces, 0)
|
|
14
|
+
, priority_(num_pieces, PiecePriority::Normal)
|
|
15
|
+
, have_(num_pieces, false)
|
|
16
|
+
, pieces_left_(num_pieces)
|
|
17
|
+
, order_pos_(num_pieces, 0)
|
|
18
|
+
, in_order_(num_pieces, 0)
|
|
19
|
+
, rng_(std::random_device{}()) {
|
|
20
|
+
// Every piece starts wanted (Normal priority, not-have, availability 0), so it
|
|
21
|
+
// all begins life in the single bucket {Normal, 0}.
|
|
22
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p) order_insert(p);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---- geometry ----
|
|
26
|
+
|
|
27
|
+
std::uint32_t PiecePicker::piece_size(std::uint32_t piece) const noexcept {
|
|
28
|
+
if (piece + 1 < num_pieces_) return piece_length_;
|
|
29
|
+
const std::int64_t tail = total_size_ - std::int64_t(piece) * piece_length_;
|
|
30
|
+
if (tail <= 0) return 0;
|
|
31
|
+
return std::uint32_t(std::min<std::int64_t>(tail, piece_length_));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
std::uint32_t PiecePicker::blocks_in_piece(std::uint32_t piece) const noexcept {
|
|
35
|
+
return (piece_size(piece) + kBlockSize - 1) / kBlockSize;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
std::uint32_t PiecePicker::block_size(std::uint32_t piece, std::uint32_t block) const noexcept {
|
|
39
|
+
const std::uint32_t ps = piece_size(piece);
|
|
40
|
+
const std::uint32_t off = block * kBlockSize;
|
|
41
|
+
return (std::min)(kBlockSize, ps - off);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---- our progress ----
|
|
45
|
+
|
|
46
|
+
void PiecePicker::we_have(std::uint32_t piece) {
|
|
47
|
+
if (have_.get(piece)) return;
|
|
48
|
+
order_remove(piece); // no longer wanted — pull it out of the rarest index
|
|
49
|
+
have_.set(piece);
|
|
50
|
+
++num_have_;
|
|
51
|
+
if (priority_[piece] != PiecePriority::DontDownload && pieces_left_ > 0) --pieces_left_;
|
|
52
|
+
downloading_.erase(piece);
|
|
53
|
+
// Advance the sequential cursor over any now-contiguous prefix of have pieces.
|
|
54
|
+
while (seq_cursor_ < num_pieces_ && have_.get(seq_cursor_)) ++seq_cursor_;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void PiecePicker::we_have_all() {
|
|
58
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p) we_have(p);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void PiecePicker::set_have_bitfield(const Bitfield& have) {
|
|
62
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p)
|
|
63
|
+
if (have.size() > p && have.get(p)) we_have(p);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---- priorities ----
|
|
67
|
+
|
|
68
|
+
void PiecePicker::set_piece_priority(std::uint32_t piece, PiecePriority priority) {
|
|
69
|
+
const PiecePriority old = priority_[piece];
|
|
70
|
+
if (old == priority) return;
|
|
71
|
+
order_remove(piece); // remove under the OLD key before the priority changes
|
|
72
|
+
if (!have_.get(piece)) {
|
|
73
|
+
const bool was_wanted = old != PiecePriority::DontDownload;
|
|
74
|
+
const bool now_wanted = priority != PiecePriority::DontDownload;
|
|
75
|
+
if (was_wanted && !now_wanted) { --pieces_left_; downloading_.erase(piece); }
|
|
76
|
+
else if (!was_wanted && now_wanted) ++pieces_left_;
|
|
77
|
+
}
|
|
78
|
+
priority_[piece] = priority;
|
|
79
|
+
order_insert(piece); // re-file under the new key (no-op if it is now unwanted)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- availability ----
|
|
83
|
+
|
|
84
|
+
void PiecePicker::peer_has_piece(std::uint32_t piece) { avail_add(piece, +1); }
|
|
85
|
+
void PiecePicker::peer_lost_piece(std::uint32_t piece) { avail_add(piece, -1); }
|
|
86
|
+
void PiecePicker::inc_availability(const Bitfield& peer_have) {
|
|
87
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p)
|
|
88
|
+
if (peer_have.size() > p && peer_have.get(p)) avail_add(p, +1);
|
|
89
|
+
}
|
|
90
|
+
void PiecePicker::dec_availability(const Bitfield& peer_have) {
|
|
91
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p)
|
|
92
|
+
if (peer_have.size() > p && peer_have.get(p)) avail_add(p, -1);
|
|
93
|
+
}
|
|
94
|
+
// A seed has every piece. Bumping every piece's availability (O(n) plus a bucket
|
|
95
|
+
// move each) is wasted work: since a seed shifts *all* pieces equally, the
|
|
96
|
+
// rarest-first ordering is unchanged, so the bucket index (keyed on the per-peer
|
|
97
|
+
// count) needs no update at all. Track seeds as one counter instead — the
|
|
98
|
+
// effective availability of a piece is availability_[p] + seeds_ — making a seed
|
|
99
|
+
// joining or leaving O(1). (Mirrors libtorrent's m_seeds.)
|
|
100
|
+
void PiecePicker::inc_availability_all() { ++seeds_; }
|
|
101
|
+
void PiecePicker::dec_availability_all() { if (seeds_ > 0) --seeds_; }
|
|
102
|
+
|
|
103
|
+
// ---- block state machine ----
|
|
104
|
+
|
|
105
|
+
PiecePicker::DownloadingPiece& PiecePicker::downloading_for(std::uint32_t piece) {
|
|
106
|
+
auto it = downloading_.find(piece);
|
|
107
|
+
if (it != downloading_.end()) return it->second;
|
|
108
|
+
DownloadingPiece dp;
|
|
109
|
+
dp.blocks.resize(blocks_in_piece(piece));
|
|
110
|
+
return downloading_.emplace(piece, std::move(dp)).first->second;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
void PiecePicker::mark_requested(const PieceBlock& b, const void* peer) {
|
|
114
|
+
DownloadingPiece& dp = downloading_for(b.piece);
|
|
115
|
+
Block& blk = dp.blocks[b.block];
|
|
116
|
+
if (blk.state == BlockState::None) {
|
|
117
|
+
blk.state = BlockState::Requested;
|
|
118
|
+
++dp.num_requested;
|
|
119
|
+
}
|
|
120
|
+
if (std::find(blk.peers.begin(), blk.peers.end(), peer) == blk.peers.end())
|
|
121
|
+
blk.peers.push_back(peer);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
std::vector<const void*> PiecePicker::mark_writing(const PieceBlock& b, const void* peer) {
|
|
125
|
+
std::vector<const void*> others;
|
|
126
|
+
auto it = downloading_.find(b.piece);
|
|
127
|
+
if (it == downloading_.end()) return others;
|
|
128
|
+
Block& blk = it->second.blocks[b.block];
|
|
129
|
+
if (blk.state == BlockState::Requested) {
|
|
130
|
+
--it->second.num_requested;
|
|
131
|
+
blk.state = BlockState::Writing;
|
|
132
|
+
++it->second.num_writing;
|
|
133
|
+
// Hand back the *other* requesters (end-game) so the caller can CANCEL the
|
|
134
|
+
// now-redundant duplicate requests, then drop the requester set — the block
|
|
135
|
+
// is in flight to disk and must not be picked or re-requested again.
|
|
136
|
+
for (const void* p : blk.peers)
|
|
137
|
+
if (p != peer) others.push_back(p);
|
|
138
|
+
blk.peers.clear();
|
|
139
|
+
}
|
|
140
|
+
return others;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
bool PiecePicker::mark_finished(const PieceBlock& b) {
|
|
144
|
+
DownloadingPiece& dp = downloading_for(b.piece);
|
|
145
|
+
Block& blk = dp.blocks[b.block];
|
|
146
|
+
if (blk.state == BlockState::Requested) --dp.num_requested;
|
|
147
|
+
else if (blk.state == BlockState::Writing) --dp.num_writing;
|
|
148
|
+
if (blk.state != BlockState::Finished) { blk.state = BlockState::Finished; ++dp.num_finished; }
|
|
149
|
+
blk.peers.clear();
|
|
150
|
+
return dp.num_finished == dp.blocks.size();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void PiecePicker::abort_block(const PieceBlock& b, const void* peer) {
|
|
154
|
+
auto it = downloading_.find(b.piece);
|
|
155
|
+
if (it == downloading_.end()) return;
|
|
156
|
+
Block& blk = it->second.blocks[b.block];
|
|
157
|
+
blk.peers.erase(std::remove(blk.peers.begin(), blk.peers.end(), peer), blk.peers.end());
|
|
158
|
+
if (blk.state == BlockState::Requested && blk.peers.empty()) {
|
|
159
|
+
blk.state = BlockState::None;
|
|
160
|
+
--it->second.num_requested;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
void PiecePicker::cancel_peer(const void* peer) {
|
|
165
|
+
for (auto& [piece, dp] : downloading_) {
|
|
166
|
+
for (auto& blk : dp.blocks) {
|
|
167
|
+
blk.peers.erase(std::remove(blk.peers.begin(), blk.peers.end(), peer), blk.peers.end());
|
|
168
|
+
if (blk.state == BlockState::Requested && blk.peers.empty()) {
|
|
169
|
+
blk.state = BlockState::None;
|
|
170
|
+
--dp.num_requested;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
void PiecePicker::restore_piece(std::uint32_t piece) {
|
|
177
|
+
downloading_.erase(piece); // drop all block progress; piece becomes fresh
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
BlockState PiecePicker::block_state(std::uint32_t piece, std::uint32_t block) const {
|
|
181
|
+
if (have_.get(piece)) return BlockState::Finished;
|
|
182
|
+
auto it = downloading_.find(piece);
|
|
183
|
+
if (it == downloading_.end()) return BlockState::None;
|
|
184
|
+
return it->second.blocks[block].state;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
bool PiecePicker::is_interesting(const Bitfield& peer_have) const {
|
|
188
|
+
for (std::uint32_t p = 0; p < num_pieces_; ++p)
|
|
189
|
+
if (wanted(p) && peer_have.size() > p && peer_have.get(p)) return true;
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// ---- picking ----
|
|
194
|
+
|
|
195
|
+
void PiecePicker::append_free_blocks(std::uint32_t piece, int count, const void* peer,
|
|
196
|
+
std::vector<PieceBlock>& out) const {
|
|
197
|
+
const std::uint32_t nblocks = blocks_in_piece(piece);
|
|
198
|
+
auto it = downloading_.find(piece);
|
|
199
|
+
for (std::uint32_t b = 0; b < nblocks && int(out.size()) < count; ++b) {
|
|
200
|
+
if (it == downloading_.end() || it->second.blocks[b].state == BlockState::None)
|
|
201
|
+
out.push_back(PieceBlock{piece, b});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
void PiecePicker::order_insert(std::uint32_t piece) {
|
|
206
|
+
if (in_order_[piece] || !wanted(piece)) return;
|
|
207
|
+
std::vector<std::vector<std::uint32_t>>& col = buckets_[prio_idx(priority_[piece])];
|
|
208
|
+
const std::uint32_t av = availability_[piece];
|
|
209
|
+
if (col.size() <= av) col.resize(av + 1); // grow the availability dimension on demand
|
|
210
|
+
std::vector<std::uint32_t>& bucket = col[av];
|
|
211
|
+
order_pos_[piece] = std::uint32_t(bucket.size());
|
|
212
|
+
bucket.push_back(piece);
|
|
213
|
+
in_order_[piece] = 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
void PiecePicker::order_remove(std::uint32_t piece) {
|
|
217
|
+
if (!in_order_[piece]) return;
|
|
218
|
+
// Bucket coords must match the ones used at insert — callers remove *before*
|
|
219
|
+
// mutating priority_/availability_ (see avail_add / set_piece_priority).
|
|
220
|
+
std::vector<std::uint32_t>& bucket = buckets_[prio_idx(priority_[piece])][availability_[piece]];
|
|
221
|
+
const std::uint32_t pos = order_pos_[piece];
|
|
222
|
+
const std::uint32_t last = bucket.back();
|
|
223
|
+
bucket[pos] = last; // swap the tail piece into the vacated slot...
|
|
224
|
+
order_pos_[last] = pos; // ...and fix up its recorded position
|
|
225
|
+
bucket.pop_back(); // empty buckets simply stay empty — no alloc churn
|
|
226
|
+
in_order_[piece] = 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
void PiecePicker::avail_add(std::uint32_t piece, int delta) {
|
|
230
|
+
// Remove under the current key, change availability, then re-file under the new
|
|
231
|
+
// key — an O(log n) bucket move. (For a have/unwanted piece the index ops are
|
|
232
|
+
// no-ops; we still track its availability count.)
|
|
233
|
+
order_remove(piece);
|
|
234
|
+
if (delta >= 0) {
|
|
235
|
+
availability_[piece] += std::uint32_t(delta);
|
|
236
|
+
} else {
|
|
237
|
+
const std::uint32_t d = std::uint32_t(-delta);
|
|
238
|
+
availability_[piece] = availability_[piece] > d ? availability_[piece] - d : 0;
|
|
239
|
+
}
|
|
240
|
+
order_insert(piece);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
std::vector<PieceBlock> PiecePicker::pick_blocks(const Bitfield& peer_have, int count, const void* peer) {
|
|
244
|
+
std::vector<PieceBlock> result;
|
|
245
|
+
if (count <= 0) return result;
|
|
246
|
+
|
|
247
|
+
auto peer_has = [&](std::uint32_t p) { return peer_have.size() > p && peer_have.get(p); };
|
|
248
|
+
|
|
249
|
+
// 1) Finish pieces already in progress that this peer can serve. In sequential
|
|
250
|
+
// mode finish the earliest partial first (streaming order); otherwise the map
|
|
251
|
+
// order is fine. (downloading_ is small — bounded by peers × pieces-in-flight.)
|
|
252
|
+
auto take_partial = [&](std::uint32_t piece) {
|
|
253
|
+
if (int(result.size()) >= count) return;
|
|
254
|
+
if (!wanted(piece) || !peer_has(piece)) return;
|
|
255
|
+
append_free_blocks(piece, count, peer, result);
|
|
256
|
+
};
|
|
257
|
+
if (mode_ == PickMode::Sequential) {
|
|
258
|
+
std::vector<std::uint32_t> partials;
|
|
259
|
+
partials.reserve(downloading_.size());
|
|
260
|
+
for (const auto& kv : downloading_) partials.push_back(kv.first);
|
|
261
|
+
std::sort(partials.begin(), partials.end());
|
|
262
|
+
for (std::uint32_t piece : partials) {
|
|
263
|
+
if (int(result.size()) >= count) break;
|
|
264
|
+
take_partial(piece);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
for (const auto& [piece, dp] : downloading_) {
|
|
268
|
+
if (int(result.size()) >= count) break;
|
|
269
|
+
take_partial(piece);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (int(result.size()) >= count) return result;
|
|
273
|
+
|
|
274
|
+
// 2) Start new pieces, ordered by the active strategy.
|
|
275
|
+
auto try_new_piece = [&](std::uint32_t p) {
|
|
276
|
+
if (!wanted(p) || !peer_has(p) || downloading_.count(p)) return;
|
|
277
|
+
append_free_blocks(p, count, peer, result);
|
|
278
|
+
};
|
|
279
|
+
switch (mode_) {
|
|
280
|
+
case PickMode::Sequential:
|
|
281
|
+
// Start at the cursor: every piece below it is already have, so there
|
|
282
|
+
// is nothing to pick there. This keeps the hot refill path off the
|
|
283
|
+
// completed prefix instead of rescanning it from 0 every time.
|
|
284
|
+
for (std::uint32_t p = seq_cursor_; p < num_pieces_ && int(result.size()) < count; ++p)
|
|
285
|
+
try_new_piece(p);
|
|
286
|
+
break;
|
|
287
|
+
case PickMode::Random: {
|
|
288
|
+
// Walk the rarest-first buckets — which already hold exactly the wanted
|
|
289
|
+
// pieces — in a shuffled order, each entered at a random offset. That is
|
|
290
|
+
// a random spread without scanning or allocating across all pieces (the
|
|
291
|
+
// bucket set is small: one per distinct (priority, availability)).
|
|
292
|
+
std::vector<std::vector<std::uint32_t>*> bkts;
|
|
293
|
+
for (auto& col : buckets_)
|
|
294
|
+
for (auto& bucket : col)
|
|
295
|
+
if (!bucket.empty()) bkts.push_back(&bucket);
|
|
296
|
+
std::shuffle(bkts.begin(), bkts.end(), rng_);
|
|
297
|
+
for (auto* bucket : bkts) {
|
|
298
|
+
if (int(result.size()) >= count) break;
|
|
299
|
+
const std::size_t n = bucket->size();
|
|
300
|
+
const std::size_t start = std::uniform_int_distribution<std::size_t>(0, n - 1)(rng_);
|
|
301
|
+
for (std::size_t k = 0; k < n && int(result.size()) < count; ++k)
|
|
302
|
+
try_new_piece((*bucket)[(start + k) % n]);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case PickMode::RarestFirst:
|
|
307
|
+
// Visit buckets best-first: High priority before Normal, and within a
|
|
308
|
+
// priority ascending availability (rarest first). Within a bucket every
|
|
309
|
+
// piece is equally good, so start at a random offset and wrap around:
|
|
310
|
+
// this spreads concurrent peers across the rarest pieces instead of all
|
|
311
|
+
// converging on the same one — without any sorting here.
|
|
312
|
+
for (int pi = 0; pi < kNumPrio && int(result.size()) < count; ++pi) {
|
|
313
|
+
std::vector<std::vector<std::uint32_t>>& col = buckets_[pi];
|
|
314
|
+
for (std::uint32_t av = 0; av < col.size() && int(result.size()) < count; ++av) {
|
|
315
|
+
std::vector<std::uint32_t>& bucket = col[av];
|
|
316
|
+
if (bucket.empty()) continue;
|
|
317
|
+
const std::size_t n = bucket.size();
|
|
318
|
+
const std::size_t start = std::uniform_int_distribution<std::size_t>(0, n - 1)(rng_);
|
|
319
|
+
for (std::size_t k = 0; k < n && int(result.size()) < count; ++k)
|
|
320
|
+
try_new_piece(bucket[(start + k) % n]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
if (int(result.size()) >= count) return result;
|
|
326
|
+
|
|
327
|
+
// 3) End-game. Only enter it once *every* wanted piece is already in progress
|
|
328
|
+
// (downloading_ covers all remaining wanted pieces) — i.e. near completion —
|
|
329
|
+
// so we don't start duplicating requests early in the download (PP-1). Before
|
|
330
|
+
// that, a peer whose blocks are all taken simply gets nothing this round.
|
|
331
|
+
if (downloading_.size() < pieces_left_) return result; // fresh pieces still remain
|
|
332
|
+
|
|
333
|
+
// Duplicate at most ONE already-requested block per call (not this peer's whole
|
|
334
|
+
// budget) so an in-progress block isn't fetched from many peers at once.
|
|
335
|
+
for (const auto& [piece, dp] : downloading_) {
|
|
336
|
+
if (!wanted(piece) || !peer_has(piece)) continue;
|
|
337
|
+
const std::uint32_t nblocks = blocks_in_piece(piece);
|
|
338
|
+
for (std::uint32_t b = 0; b < nblocks; ++b) {
|
|
339
|
+
const Block& blk = dp.blocks[b];
|
|
340
|
+
if (blk.state != BlockState::Requested) continue;
|
|
341
|
+
if (std::find(blk.peers.begin(), blk.peers.end(), peer) != blk.peers.end()) continue;
|
|
342
|
+
result.push_back(PieceBlock{piece, b});
|
|
343
|
+
if (!endgame_) LOG_DEBUG("bt.picker", "entering end-game (" << pieces_left_
|
|
344
|
+
<< " piece(s) left, all in progress)");
|
|
345
|
+
endgame_ = true;
|
|
346
|
+
return result; // one busy block per pick
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
} // namespace librats::bittorrent
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file piece_picker.h
|
|
5
|
+
* @brief Decides which blocks to request next — the download strategy core.
|
|
6
|
+
*
|
|
7
|
+
* The picker tracks, for every piece: how many connected peers have it
|
|
8
|
+
* (availability), our download priority for it, whether we already have it, and
|
|
9
|
+
* — for pieces in progress — the per-block state machine
|
|
10
|
+
* (None → Requested → Writing → Finished). pick_blocks() turns all of that into
|
|
11
|
+
* a concrete list of blocks to ask a given peer for, honouring:
|
|
12
|
+
*
|
|
13
|
+
* 1. partial pieces first — finish what's already started;
|
|
14
|
+
* 2. the active strategy — rarest-first (default), sequential or random;
|
|
15
|
+
* 3. end-game — once every needed block is already in flight,
|
|
16
|
+
* re-request outstanding blocks from other peers.
|
|
17
|
+
*
|
|
18
|
+
* pick_blocks() is a pure query: it never mutates state. The caller commits its
|
|
19
|
+
* choices with mark_requested(), so the two-queue request pipeline (reserved vs
|
|
20
|
+
* in-flight) stays in the peer connection. Owned by one torrent on the network
|
|
21
|
+
* thread — not thread-safe by design.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
#include "librats/bittorrent/bitfield.h"
|
|
25
|
+
|
|
26
|
+
#include <array>
|
|
27
|
+
#include <cstdint>
|
|
28
|
+
#include <random>
|
|
29
|
+
#include <unordered_map>
|
|
30
|
+
#include <utility>
|
|
31
|
+
#include <vector>
|
|
32
|
+
|
|
33
|
+
namespace librats::bittorrent {
|
|
34
|
+
|
|
35
|
+
enum class PiecePriority : std::uint8_t {
|
|
36
|
+
DontDownload = 0, ///< excluded from picking
|
|
37
|
+
Normal = 1,
|
|
38
|
+
High = 2,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
enum class PickMode : std::uint8_t {
|
|
42
|
+
RarestFirst, ///< default — fewest-available pieces first
|
|
43
|
+
Sequential, ///< lowest piece index first (streaming)
|
|
44
|
+
Random,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
enum class BlockState : std::uint8_t {
|
|
48
|
+
None, ///< not requested
|
|
49
|
+
Requested, ///< asked a peer, awaiting data
|
|
50
|
+
Writing, ///< received, being written to disk
|
|
51
|
+
Finished, ///< on disk
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/// A single 16 KiB block, identified by its piece and its index within it.
|
|
55
|
+
struct PieceBlock {
|
|
56
|
+
std::uint32_t piece = 0;
|
|
57
|
+
std::uint32_t block = 0;
|
|
58
|
+
bool operator==(const PieceBlock& o) const noexcept { return piece == o.piece && block == o.block; }
|
|
59
|
+
bool operator!=(const PieceBlock& o) const noexcept { return !(*this == o); }
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
class PiecePicker {
|
|
63
|
+
public:
|
|
64
|
+
PiecePicker(std::uint32_t num_pieces, std::uint32_t piece_length, std::int64_t total_size);
|
|
65
|
+
|
|
66
|
+
// ---- geometry ----
|
|
67
|
+
std::uint32_t num_pieces() const noexcept { return num_pieces_; }
|
|
68
|
+
std::uint32_t piece_length() const noexcept { return piece_length_; }
|
|
69
|
+
std::uint32_t piece_size(std::uint32_t piece) const noexcept;
|
|
70
|
+
std::uint32_t blocks_in_piece(std::uint32_t piece) const noexcept;
|
|
71
|
+
std::uint32_t block_size(std::uint32_t piece, std::uint32_t block) const noexcept;
|
|
72
|
+
|
|
73
|
+
// ---- our progress ----
|
|
74
|
+
void we_have(std::uint32_t piece);
|
|
75
|
+
void we_have_all();
|
|
76
|
+
bool have_piece(std::uint32_t piece) const noexcept { return have_.get(piece); }
|
|
77
|
+
std::uint32_t num_have() const noexcept { return num_have_; }
|
|
78
|
+
bool is_finished() const noexcept { return pieces_left_ == 0; }
|
|
79
|
+
Bitfield have_bitfield() const { return have_; }
|
|
80
|
+
void set_have_bitfield(const Bitfield& have);
|
|
81
|
+
|
|
82
|
+
// ---- priorities ----
|
|
83
|
+
void set_piece_priority(std::uint32_t piece, PiecePriority priority);
|
|
84
|
+
PiecePriority piece_priority(std::uint32_t piece) const noexcept { return priority_[piece]; }
|
|
85
|
+
|
|
86
|
+
// ---- strategy ----
|
|
87
|
+
void set_mode(PickMode mode) noexcept { mode_ = mode; }
|
|
88
|
+
PickMode mode() const noexcept { return mode_; }
|
|
89
|
+
|
|
90
|
+
// ---- peer availability ----
|
|
91
|
+
void peer_has_piece(std::uint32_t piece);
|
|
92
|
+
void peer_lost_piece(std::uint32_t piece);
|
|
93
|
+
void inc_availability(const Bitfield& peer_have);
|
|
94
|
+
void dec_availability(const Bitfield& peer_have);
|
|
95
|
+
void inc_availability_all(); ///< a seed joined
|
|
96
|
+
void dec_availability_all();
|
|
97
|
+
/// Effective availability: per-peer count plus the number of connected seeds
|
|
98
|
+
/// (seeds are tracked as one counter — see inc_availability_all).
|
|
99
|
+
std::uint32_t availability(std::uint32_t piece) const noexcept { return availability_[piece] + seeds_; }
|
|
100
|
+
|
|
101
|
+
// ---- block state machine ----
|
|
102
|
+
/// Up to @p count blocks that @p peer (holding @p peer_have) can serve and we
|
|
103
|
+
/// still need. Pure query — commit with mark_requested().
|
|
104
|
+
std::vector<PieceBlock> pick_blocks(const Bitfield& peer_have, int count, const void* peer);
|
|
105
|
+
|
|
106
|
+
void mark_requested(const PieceBlock& b, const void* peer);
|
|
107
|
+
/// Mark a received block as being written to disk. Returns the *other* peers
|
|
108
|
+
/// that also had this block requested (end-game duplicates) so the caller can
|
|
109
|
+
/// send them CANCEL; the block's requester set is then cleared. Empty in the
|
|
110
|
+
/// common, non-end-game case where only the delivering peer had it.
|
|
111
|
+
std::vector<const void*> mark_writing(const PieceBlock& b, const void* peer);
|
|
112
|
+
/// Mark a block on disk. Returns true if this completed the piece (all blocks
|
|
113
|
+
/// finished → ready to hash).
|
|
114
|
+
bool mark_finished(const PieceBlock& b);
|
|
115
|
+
/// A request failed/was rejected: drop @p peer from the block, freeing it if no
|
|
116
|
+
/// other peer is still on it.
|
|
117
|
+
void abort_block(const PieceBlock& b, const void* peer);
|
|
118
|
+
/// A peer vanished: free every block it had outstanding.
|
|
119
|
+
void cancel_peer(const void* peer);
|
|
120
|
+
/// Hash check failed: throw away a piece's block progress so it is re-downloaded.
|
|
121
|
+
void restore_piece(std::uint32_t piece);
|
|
122
|
+
|
|
123
|
+
BlockState block_state(std::uint32_t piece, std::uint32_t block) const;
|
|
124
|
+
bool is_interesting(const Bitfield& peer_have) const;
|
|
125
|
+
/// True if @p piece is one we still want (not have, not DontDownload). O(1).
|
|
126
|
+
/// A peer that has this piece is therefore interesting to us — this lets the
|
|
127
|
+
/// interest update on a single HAVE stay O(1) instead of rescanning the whole
|
|
128
|
+
/// bitfield through is_interesting().
|
|
129
|
+
bool piece_interesting(std::uint32_t piece) const noexcept {
|
|
130
|
+
return piece < num_pieces_ && wanted(piece);
|
|
131
|
+
}
|
|
132
|
+
bool in_endgame() const noexcept { return endgame_; }
|
|
133
|
+
std::size_t num_downloading() const noexcept { return downloading_.size(); }
|
|
134
|
+
|
|
135
|
+
private:
|
|
136
|
+
struct Block {
|
|
137
|
+
BlockState state = BlockState::None;
|
|
138
|
+
std::vector<const void*> peers; ///< requesters (>1 only in end-game)
|
|
139
|
+
};
|
|
140
|
+
struct DownloadingPiece {
|
|
141
|
+
std::vector<Block> blocks;
|
|
142
|
+
std::uint16_t num_requested = 0;
|
|
143
|
+
std::uint16_t num_writing = 0;
|
|
144
|
+
std::uint16_t num_finished = 0;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
bool wanted(std::uint32_t piece) const noexcept {
|
|
148
|
+
return priority_[piece] != PiecePriority::DontDownload && !have_.get(piece);
|
|
149
|
+
}
|
|
150
|
+
DownloadingPiece& downloading_for(std::uint32_t piece);
|
|
151
|
+
void append_free_blocks(std::uint32_t piece, int count, const void* peer,
|
|
152
|
+
std::vector<PieceBlock>& out) const;
|
|
153
|
+
|
|
154
|
+
// ---- incremental rarest-first index ----
|
|
155
|
+
// Every wanted & not-yet-have piece sits in a bucket addressed directly by
|
|
156
|
+
// (priority level, availability). A single availability or priority change is
|
|
157
|
+
// an O(1) bucket move (order_remove + order_insert) — no tree, no node
|
|
158
|
+
// allocation. pick_blocks() walks the buckets best-first (High priority first,
|
|
159
|
+
// then ascending availability = rarest). availability is bounded by the peer
|
|
160
|
+
// count (seeds live in seeds_, not here), so the inner dimension stays small.
|
|
161
|
+
static constexpr int kNumPrio = 2; ///< High (idx 0, picked first) and Normal (idx 1)
|
|
162
|
+
static int prio_idx(PiecePriority p) noexcept {
|
|
163
|
+
return p == PiecePriority::High ? 0 : 1; // DontDownload is never bucketed
|
|
164
|
+
}
|
|
165
|
+
void order_insert(std::uint32_t piece); ///< add piece to its bucket (if wanted & absent)
|
|
166
|
+
void order_remove(std::uint32_t piece); ///< pull piece out of its current bucket (if present)
|
|
167
|
+
void avail_add(std::uint32_t piece, int delta); ///< change availability, keeping the index sorted
|
|
168
|
+
|
|
169
|
+
std::uint32_t num_pieces_;
|
|
170
|
+
std::uint32_t piece_length_;
|
|
171
|
+
std::int64_t total_size_;
|
|
172
|
+
|
|
173
|
+
std::vector<std::uint32_t> availability_;
|
|
174
|
+
std::vector<PiecePriority> priority_;
|
|
175
|
+
Bitfield have_;
|
|
176
|
+
std::uint32_t num_have_ = 0;
|
|
177
|
+
std::uint32_t pieces_left_ = 0; ///< wanted & not-yet-have
|
|
178
|
+
PickMode mode_ = PickMode::RarestFirst;
|
|
179
|
+
bool endgame_ = false;
|
|
180
|
+
// Sequential cursor: every piece with index < seq_cursor_ is already `have`,
|
|
181
|
+
// so a sequential pick can start here instead of rescanning the completed
|
|
182
|
+
// prefix from 0 on every refill. Maintained only in we_have() (advances
|
|
183
|
+
// monotonically, O(n) total over the whole download).
|
|
184
|
+
std::uint32_t seq_cursor_ = 0;
|
|
185
|
+
// Number of connected seeds (peers that have everything). Tracked as one
|
|
186
|
+
// counter instead of +1 on every piece; folded into availability().
|
|
187
|
+
std::uint32_t seeds_ = 0;
|
|
188
|
+
|
|
189
|
+
std::unordered_map<std::uint32_t, DownloadingPiece> downloading_;
|
|
190
|
+
|
|
191
|
+
// Incremental rarest-first index: buckets_[prio_idx][availability] -> pieces.
|
|
192
|
+
// The inner vector grows on demand as higher availabilities appear (bounded by
|
|
193
|
+
// the peer count). order_pos_[p] is p's slot within its bucket (for O(1)
|
|
194
|
+
// swap-removal); in_order_[p] records whether p is currently bucketed at all.
|
|
195
|
+
std::array<std::vector<std::vector<std::uint32_t>>, kNumPrio> buckets_;
|
|
196
|
+
std::vector<std::uint32_t> order_pos_;
|
|
197
|
+
std::vector<std::uint8_t> in_order_;
|
|
198
|
+
mutable std::mt19937 rng_;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
} // namespace librats::bittorrent
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#include "librats/bittorrent/reactor.h"
|
|
2
|
+
|
|
3
|
+
namespace librats::bittorrent {
|
|
4
|
+
|
|
5
|
+
Reactor::Reactor() : poller_(IOPoller::create()) {
|
|
6
|
+
if (is_valid_socket(notifier_.fd())) poller_->add(notifier_.fd(), PollIn);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
Reactor::~Reactor() {
|
|
10
|
+
stop();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
void Reactor::start() {
|
|
14
|
+
if (running_.exchange(true)) return;
|
|
15
|
+
thread_ = std::thread([this] {
|
|
16
|
+
loop_thread_ = std::this_thread::get_id();
|
|
17
|
+
while (running_.load()) run_one(1000);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void Reactor::run() {
|
|
22
|
+
running_.store(true);
|
|
23
|
+
loop_thread_ = std::this_thread::get_id();
|
|
24
|
+
while (running_.load()) run_one(1000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void Reactor::stop() {
|
|
28
|
+
if (!running_.exchange(false)) {
|
|
29
|
+
if (thread_.joinable()) thread_.join();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
notifier_.signal(); // break the poll wait so the loop sees running_ == false
|
|
33
|
+
if (thread_.joinable()) thread_.join();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void Reactor::post(Task task) {
|
|
37
|
+
tasks_.push(std::move(task));
|
|
38
|
+
notifier_.signal();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void Reactor::run_one(int timeout_ms) {
|
|
42
|
+
if (loop_thread_ == std::thread::id{}) loop_thread_ = std::this_thread::get_id();
|
|
43
|
+
|
|
44
|
+
const int wait_ms = timers_.next_timeout_ms(timeout_ms);
|
|
45
|
+
PollResult results[128];
|
|
46
|
+
const int n = poller_->wait(results, 128, wait_ms);
|
|
47
|
+
|
|
48
|
+
for (int i = 0; i < n; ++i) {
|
|
49
|
+
if (results[i].fd == notifier_.fd()) {
|
|
50
|
+
notifier_.drain();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
auto it = handlers_.find(results[i].fd);
|
|
54
|
+
if (it == handlers_.end()) continue;
|
|
55
|
+
// Copy the callback: it may remove this very fd (and destroy the stored
|
|
56
|
+
// std::function) while running.
|
|
57
|
+
IoCallback cb = it->second;
|
|
58
|
+
cb(results[i].events);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
run_tasks();
|
|
62
|
+
timers_.run_due();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void Reactor::run_tasks() {
|
|
66
|
+
tasks_.drain(task_scratch_);
|
|
67
|
+
for (auto& t : task_scratch_) t();
|
|
68
|
+
task_scratch_.clear();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
bool Reactor::add(socket_t fd, std::uint32_t events, IoCallback cb) {
|
|
72
|
+
handlers_[fd] = std::move(cb);
|
|
73
|
+
if (!poller_->add(fd, events)) {
|
|
74
|
+
handlers_.erase(fd);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
bool Reactor::modify(socket_t fd, std::uint32_t events) {
|
|
81
|
+
return poller_->modify(fd, events);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
void Reactor::remove(socket_t fd) {
|
|
85
|
+
poller_->remove(fd);
|
|
86
|
+
handlers_.erase(fd);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
TimerId Reactor::schedule(std::chrono::milliseconds delay, TimerCallback cb) {
|
|
90
|
+
return timers_.schedule(delay, std::move(cb));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void Reactor::cancel(TimerId id) {
|
|
94
|
+
timers_.cancel(id);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
} // namespace librats::bittorrent
|