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,209 @@
|
|
|
1
|
+
#include "librats/bittorrent/disk_io.h"
|
|
2
|
+
|
|
3
|
+
#include "librats/bittorrent/log.h"
|
|
4
|
+
#include "librats/crypto/sha1.h"
|
|
5
|
+
#include "librats/util/fs.h"
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
namespace librats::bittorrent {
|
|
12
|
+
|
|
13
|
+
ThreadedDiskIo::ThreadedDiskIo(const TorrentInfo& info, std::string save_path,
|
|
14
|
+
CompletionPoster poster, Config config)
|
|
15
|
+
: files_(info.files())
|
|
16
|
+
, piece_hashes_(info.piece_hashes())
|
|
17
|
+
, save_path_(std::move(save_path))
|
|
18
|
+
, poster_(std::move(poster))
|
|
19
|
+
, ensured_(info.num_files(), 0) {
|
|
20
|
+
const int n = (std::max)(1, config.num_threads);
|
|
21
|
+
workers_.reserve(std::size_t(n));
|
|
22
|
+
for (int i = 0; i < n; ++i) workers_.emplace_back([this] { worker_loop(); });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ThreadedDiskIo::~ThreadedDiskIo() {
|
|
26
|
+
stop();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void ThreadedDiskIo::stop() {
|
|
30
|
+
{
|
|
31
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
32
|
+
if (stopping_) return;
|
|
33
|
+
stopping_ = true;
|
|
34
|
+
}
|
|
35
|
+
cv_.notify_all();
|
|
36
|
+
for (auto& t : workers_)
|
|
37
|
+
if (t.joinable()) t.join();
|
|
38
|
+
workers_.clear();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void ThreadedDiskIo::enqueue(std::function<void()> job) {
|
|
42
|
+
{
|
|
43
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
44
|
+
if (stopping_) return;
|
|
45
|
+
jobs_.push(std::move(job));
|
|
46
|
+
}
|
|
47
|
+
cv_.notify_one();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void ThreadedDiskIo::worker_loop() {
|
|
51
|
+
for (;;) {
|
|
52
|
+
std::function<void()> job;
|
|
53
|
+
{
|
|
54
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
55
|
+
cv_.wait(lock, [this] { return stopping_ || !jobs_.empty(); });
|
|
56
|
+
if (stopping_ && jobs_.empty()) return;
|
|
57
|
+
job = std::move(jobs_.front());
|
|
58
|
+
jobs_.pop();
|
|
59
|
+
}
|
|
60
|
+
job();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
void ThreadedDiskIo::complete(std::function<void()> handler) {
|
|
65
|
+
if (poster_) poster_(std::move(handler));
|
|
66
|
+
else handler();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
std::string ThreadedDiskIo::file_path(std::size_t file_index) const {
|
|
70
|
+
return combine_paths(save_path_, files_.file_at(file_index).path);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool ThreadedDiskIo::ensure_file(std::size_t file_index) {
|
|
74
|
+
std::lock_guard<std::mutex> lock(ensure_mutex_);
|
|
75
|
+
if (ensured_[file_index]) return true;
|
|
76
|
+
|
|
77
|
+
const std::string path = file_path(file_index);
|
|
78
|
+
const std::int64_t size = files_.file_at(file_index).size;
|
|
79
|
+
|
|
80
|
+
const std::string parent = get_parent_directory(path.c_str());
|
|
81
|
+
if (!parent.empty() && !directory_exists(parent.c_str())) {
|
|
82
|
+
if (!create_directories(parent.c_str())) {
|
|
83
|
+
LOG_ERROR("bt.disk", "✗ failed to create directory " << parent);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (file_exists(path.c_str())) {
|
|
89
|
+
// Extend a short (e.g. partially-downloaded) file without truncating it.
|
|
90
|
+
if (size > 0 && get_file_size(path.c_str()) < size) {
|
|
91
|
+
const std::uint8_t zero = 0;
|
|
92
|
+
if (!write_file_chunk(path, std::uint64_t(size - 1), &zero, 1)) {
|
|
93
|
+
LOG_ERROR("bt.disk", "✗ failed to extend file " << path << " to " << size << " bytes");
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} else if (!create_file_with_size(path, std::uint64_t(size))) {
|
|
98
|
+
LOG_ERROR("bt.disk", "✗ failed to create file " << path << " (" << size << " bytes)");
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ensured_[file_index] = 1;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
bool ThreadedDiskIo::write_range(std::uint32_t piece, std::uint32_t offset, const Bytes& data) {
|
|
107
|
+
auto slices = files_.map_block(piece, offset, std::int64_t(data.size()));
|
|
108
|
+
std::int64_t in_pos = 0;
|
|
109
|
+
for (const auto& s : slices) {
|
|
110
|
+
if (!ensure_file(s.file_index)) return false;
|
|
111
|
+
if (!write_file_chunk(file_path(s.file_index), std::uint64_t(s.offset),
|
|
112
|
+
data.data() + in_pos, std::size_t(s.size))) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
in_pos += s.size;
|
|
116
|
+
}
|
|
117
|
+
return in_pos == std::int64_t(data.size());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bool ThreadedDiskIo::read_range(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
|
|
121
|
+
Bytes& out, bool use_store, bool allow_short) {
|
|
122
|
+
out.assign(length, 0);
|
|
123
|
+
auto slices = files_.map_block(piece, offset, std::int64_t(length));
|
|
124
|
+
std::int64_t out_pos = 0;
|
|
125
|
+
for (const auto& s : slices) {
|
|
126
|
+
const std::string path = file_path(s.file_index);
|
|
127
|
+
// On the check path a missing/short file is expected (piece not yet on
|
|
128
|
+
// disk); probe the size first so we don't trip read_file_chunk's error log.
|
|
129
|
+
if (allow_short && get_file_size(path.c_str()) < s.offset + s.size) return false;
|
|
130
|
+
if (!read_file_chunk(path, std::uint64_t(s.offset), out.data() + out_pos, std::size_t(s.size)))
|
|
131
|
+
return false;
|
|
132
|
+
out_pos += s.size;
|
|
133
|
+
}
|
|
134
|
+
if (use_store) store_.overlay(piece, offset, out);
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
void ThreadedDiskIo::async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
|
|
139
|
+
DiskReadHandler handler) {
|
|
140
|
+
enqueue([this, piece, offset, length, handler = std::move(handler)]() mutable {
|
|
141
|
+
Bytes out;
|
|
142
|
+
const bool ok = read_range(piece, offset, length, out, /*use_store=*/true, /*allow_short=*/false);
|
|
143
|
+
complete([handler = std::move(handler), ok, out = std::move(out)]() mutable {
|
|
144
|
+
if (handler) handler(ok, std::move(out));
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void ThreadedDiskIo::async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
|
|
150
|
+
DiskWriteHandler handler) {
|
|
151
|
+
// Park the block so reads/hashes see it before the write reaches disk, and
|
|
152
|
+
// count it toward the pending-write total that drives backpressure (D-2).
|
|
153
|
+
const std::size_t sz = data.size();
|
|
154
|
+
store_.insert(piece, offset, data);
|
|
155
|
+
queued_bytes_.fetch_add(sz, std::memory_order_relaxed);
|
|
156
|
+
enqueue([this, piece, offset, sz, data = std::move(data), handler = std::move(handler)]() mutable {
|
|
157
|
+
const bool ok = write_range(piece, offset, data);
|
|
158
|
+
store_.erase(piece, offset);
|
|
159
|
+
queued_bytes_.fetch_sub(sz, std::memory_order_relaxed);
|
|
160
|
+
complete([handler = std::move(handler), ok]() mutable {
|
|
161
|
+
if (handler) handler(ok);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
void ThreadedDiskIo::async_hash(std::uint32_t piece, DiskHashHandler handler) {
|
|
167
|
+
enqueue([this, piece, handler = std::move(handler)]() mutable {
|
|
168
|
+
const std::uint32_t ps = files_.piece_size(piece);
|
|
169
|
+
Bytes buf;
|
|
170
|
+
const bool ok = read_range(piece, 0, ps, buf, /*use_store=*/true, /*allow_short=*/false);
|
|
171
|
+
std::array<std::uint8_t, 20> digest{};
|
|
172
|
+
if (ok) digest = SHA1::hash_raw(buf.data(), buf.size());
|
|
173
|
+
complete([handler = std::move(handler), ok, digest]() mutable {
|
|
174
|
+
if (handler) handler(ok, digest);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void ThreadedDiskIo::async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
|
|
180
|
+
DiskCheckHandler handler) {
|
|
181
|
+
enqueue([this, trusted_have = std::move(trusted_have), progress = std::move(progress),
|
|
182
|
+
handler = std::move(handler)]() mutable {
|
|
183
|
+
const std::uint32_t n = files_.num_pieces();
|
|
184
|
+
const bool have_trust = trusted_have.size() == n;
|
|
185
|
+
Bitfield have(n, false);
|
|
186
|
+
|
|
187
|
+
for (std::uint32_t p = 0; p < n; ++p) {
|
|
188
|
+
if (have_trust && trusted_have.get(p)) {
|
|
189
|
+
have.set(p); // fast resume: trust the saved bit, skip hashing
|
|
190
|
+
} else {
|
|
191
|
+
Bytes buf;
|
|
192
|
+
if (read_range(p, 0, files_.piece_size(p), buf, /*use_store=*/false, /*allow_short=*/true)) {
|
|
193
|
+
auto digest = SHA1::hash_raw(buf.data(), buf.size());
|
|
194
|
+
if (std::memcmp(digest.data(), piece_hashes_.data() + std::size_t(p) * 20, 20) == 0)
|
|
195
|
+
have.set(p);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (progress) {
|
|
199
|
+
const std::uint32_t done = p + 1;
|
|
200
|
+
complete([progress, done, n] { progress(done, n); });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
complete([handler = std::move(handler), have = std::move(have)]() mutable {
|
|
204
|
+
if (handler) handler(std::move(have));
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
} // namespace librats::bittorrent
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file disk_io.h
|
|
5
|
+
* @brief Asynchronous piece storage — the boundary between the network thread
|
|
6
|
+
* and the filesystem.
|
|
7
|
+
*
|
|
8
|
+
* The engine never blocks on disk: it submits async_read / async_write /
|
|
9
|
+
* async_hash / async_check_files jobs and is called back with the result. DiskIo
|
|
10
|
+
* is an interface so a torrent can be tested against an in-memory fake, and so
|
|
11
|
+
* the threading strategy is swappable.
|
|
12
|
+
*
|
|
13
|
+
* Completions are delivered through a CompletionPoster — a closure the owner
|
|
14
|
+
* supplies to marshal handlers onto its own thread (the reactor, in the full
|
|
15
|
+
* engine). With no poster, handlers run inline on the disk-worker thread.
|
|
16
|
+
*
|
|
17
|
+
* Offsets passed to read/write/hash are relative to the *piece* (piece index +
|
|
18
|
+
* byte offset within it); ThreadedDiskIo translates them to file regions via
|
|
19
|
+
* FileStorage::map_block.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
#include "librats/bittorrent/bitfield.h"
|
|
23
|
+
#include "librats/bittorrent/file_storage.h"
|
|
24
|
+
#include "librats/bittorrent/store_buffer.h"
|
|
25
|
+
#include "librats/bittorrent/torrent_info.h"
|
|
26
|
+
#include "librats/core/bytes.h"
|
|
27
|
+
|
|
28
|
+
#include <array>
|
|
29
|
+
#include <atomic>
|
|
30
|
+
#include <condition_variable>
|
|
31
|
+
#include <cstdint>
|
|
32
|
+
#include <functional>
|
|
33
|
+
#include <mutex>
|
|
34
|
+
#include <queue>
|
|
35
|
+
#include <string>
|
|
36
|
+
#include <thread>
|
|
37
|
+
#include <vector>
|
|
38
|
+
|
|
39
|
+
namespace librats::bittorrent {
|
|
40
|
+
|
|
41
|
+
/// Marshals a completion closure onto the owner's thread. If empty, handlers run
|
|
42
|
+
/// inline on the disk-worker thread.
|
|
43
|
+
using CompletionPoster = std::function<void(std::function<void()>)>;
|
|
44
|
+
|
|
45
|
+
using DiskReadHandler = std::function<void(bool ok, Bytes data)>;
|
|
46
|
+
using DiskWriteHandler = std::function<void(bool ok)>;
|
|
47
|
+
using DiskHashHandler = std::function<void(bool ok, std::array<std::uint8_t, 20> sha1)>;
|
|
48
|
+
using DiskCheckProgress = std::function<void(std::uint32_t piece, std::uint32_t total)>;
|
|
49
|
+
using DiskCheckHandler = std::function<void(Bitfield have)>;
|
|
50
|
+
|
|
51
|
+
class DiskIo {
|
|
52
|
+
public:
|
|
53
|
+
virtual ~DiskIo() = default;
|
|
54
|
+
|
|
55
|
+
/// Read @p length bytes at @p offset within @p piece.
|
|
56
|
+
virtual void async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
|
|
57
|
+
DiskReadHandler handler) = 0;
|
|
58
|
+
|
|
59
|
+
/// Write a block at @p offset within @p piece.
|
|
60
|
+
virtual void async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
|
|
61
|
+
DiskWriteHandler handler) = 0;
|
|
62
|
+
|
|
63
|
+
/// SHA-1 the whole of @p piece (for hash verification by the caller).
|
|
64
|
+
virtual void async_hash(std::uint32_t piece, DiskHashHandler handler) = 0;
|
|
65
|
+
|
|
66
|
+
/// Verify which pieces are already complete on disk. Pieces whose bit is set
|
|
67
|
+
/// in @p trusted_have are accepted without re-hashing (fast resume); the rest
|
|
68
|
+
/// are read and hashed. @p progress fires per piece (may be null).
|
|
69
|
+
virtual void async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
|
|
70
|
+
DiskCheckHandler handler) = 0;
|
|
71
|
+
|
|
72
|
+
/// Bytes of block data accepted for writing but not yet flushed to disk. The
|
|
73
|
+
/// Torrent uses this to apply backpressure — it stops requesting new blocks
|
|
74
|
+
/// while this is high — so a fast network can't grow the write queue without
|
|
75
|
+
/// bound when the disk is slow. Default 0 for fakes with no real queue.
|
|
76
|
+
virtual std::size_t queued_write_bytes() const noexcept { return 0; }
|
|
77
|
+
|
|
78
|
+
/// Stop workers and quiesce. Safe to call more than once.
|
|
79
|
+
virtual void stop() = 0;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/// Disk subsystem backed by a small pool of worker threads.
|
|
83
|
+
class ThreadedDiskIo final : public DiskIo {
|
|
84
|
+
public:
|
|
85
|
+
struct Config {
|
|
86
|
+
int num_threads = 2;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
ThreadedDiskIo(const TorrentInfo& info, std::string save_path,
|
|
90
|
+
CompletionPoster poster, Config config);
|
|
91
|
+
|
|
92
|
+
// Convenience overloads (defined inline so Config{} sits in a complete-class
|
|
93
|
+
// context — it cannot appear in a default argument of the primary ctor).
|
|
94
|
+
explicit ThreadedDiskIo(const TorrentInfo& info, std::string save_path)
|
|
95
|
+
: ThreadedDiskIo(info, std::move(save_path), CompletionPoster{}, Config{}) {}
|
|
96
|
+
ThreadedDiskIo(const TorrentInfo& info, std::string save_path, CompletionPoster poster)
|
|
97
|
+
: ThreadedDiskIo(info, std::move(save_path), std::move(poster), Config{}) {}
|
|
98
|
+
|
|
99
|
+
~ThreadedDiskIo() override;
|
|
100
|
+
|
|
101
|
+
ThreadedDiskIo(const ThreadedDiskIo&) = delete;
|
|
102
|
+
ThreadedDiskIo& operator=(const ThreadedDiskIo&) = delete;
|
|
103
|
+
|
|
104
|
+
void async_read(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
|
|
105
|
+
DiskReadHandler handler) override;
|
|
106
|
+
void async_write(std::uint32_t piece, std::uint32_t offset, Bytes data,
|
|
107
|
+
DiskWriteHandler handler) override;
|
|
108
|
+
void async_hash(std::uint32_t piece, DiskHashHandler handler) override;
|
|
109
|
+
void async_check_files(Bitfield trusted_have, DiskCheckProgress progress,
|
|
110
|
+
DiskCheckHandler handler) override;
|
|
111
|
+
std::size_t queued_write_bytes() const noexcept override { return queued_bytes_.load(std::memory_order_relaxed); }
|
|
112
|
+
void stop() override;
|
|
113
|
+
|
|
114
|
+
private:
|
|
115
|
+
void enqueue(std::function<void()> job);
|
|
116
|
+
void worker_loop();
|
|
117
|
+
void complete(std::function<void()> handler);
|
|
118
|
+
|
|
119
|
+
bool ensure_file(std::size_t file_index);
|
|
120
|
+
bool write_range(std::uint32_t piece, std::uint32_t offset, const Bytes& data);
|
|
121
|
+
/// Read [offset, offset+length) of @p piece into @p out. With @p use_store the
|
|
122
|
+
/// store-buffer is overlaid. With @p allow_short a missing/short file yields
|
|
123
|
+
/// false (used by check_files) instead of being treated as an error.
|
|
124
|
+
bool read_range(std::uint32_t piece, std::uint32_t offset, std::uint32_t length,
|
|
125
|
+
Bytes& out, bool use_store, bool allow_short);
|
|
126
|
+
|
|
127
|
+
std::string file_path(std::size_t file_index) const;
|
|
128
|
+
|
|
129
|
+
FileStorage files_;
|
|
130
|
+
Bytes piece_hashes_; ///< concatenated 20-byte SHA-1s
|
|
131
|
+
std::string save_path_;
|
|
132
|
+
CompletionPoster poster_;
|
|
133
|
+
StoreBuffer store_;
|
|
134
|
+
|
|
135
|
+
// File preallocation bookkeeping.
|
|
136
|
+
std::mutex ensure_mutex_;
|
|
137
|
+
std::vector<char> ensured_; ///< per-file "already sized on disk" flag
|
|
138
|
+
|
|
139
|
+
// Bytes of block data queued for writing but not yet flushed (backpressure).
|
|
140
|
+
std::atomic<std::size_t> queued_bytes_{0};
|
|
141
|
+
|
|
142
|
+
// Worker pool + job queue.
|
|
143
|
+
std::vector<std::thread> workers_;
|
|
144
|
+
std::queue<std::function<void()>> jobs_;
|
|
145
|
+
std::mutex mutex_;
|
|
146
|
+
std::condition_variable cv_;
|
|
147
|
+
bool stopping_ = false;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
} // namespace librats::bittorrent
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#include "librats/bittorrent/extensions.h"
|
|
2
|
+
#include "librats/bittorrent/bencode.h"
|
|
3
|
+
#include "librats/bittorrent/byte_io.h"
|
|
4
|
+
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
|
|
7
|
+
namespace librats::bittorrent::ext {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
/// Parse "a.b.c.d" into 4 bytes. Returns false on anything that isn't dotted IPv4.
|
|
12
|
+
bool ipv4_to_bytes(const std::string& ip, std::uint8_t out[4]) {
|
|
13
|
+
unsigned a, b, c, d;
|
|
14
|
+
char extra;
|
|
15
|
+
if (std::sscanf(ip.c_str(), "%u.%u.%u.%u%c", &a, &b, &c, &d, &extra) != 4) return false;
|
|
16
|
+
if (a > 255 || b > 255 || c > 255 || d > 255) return false;
|
|
17
|
+
out[0] = std::uint8_t(a); out[1] = std::uint8_t(b);
|
|
18
|
+
out[2] = std::uint8_t(c); out[3] = std::uint8_t(d);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
std::string ipv4_to_string(const std::uint8_t* b) {
|
|
23
|
+
char buf[16];
|
|
24
|
+
std::snprintf(buf, sizeof(buf), "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
|
|
25
|
+
return buf;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Pack peers into the 6-bytes-each compact form (4 IPv4 + 2 port BE).
|
|
29
|
+
std::string compact(const std::vector<PexPeer>& peers) {
|
|
30
|
+
std::string out;
|
|
31
|
+
for (const auto& p : peers) {
|
|
32
|
+
std::uint8_t ip[4];
|
|
33
|
+
if (!ipv4_to_bytes(p.ip, ip)) continue;
|
|
34
|
+
out.append(reinterpret_cast<const char*>(ip), 4);
|
|
35
|
+
std::uint8_t port[2];
|
|
36
|
+
write_u16_be(port, p.port);
|
|
37
|
+
out.append(reinterpret_cast<const char*>(port), 2);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
std::vector<PexPeer> uncompact(const std::string& s) {
|
|
43
|
+
std::vector<PexPeer> peers;
|
|
44
|
+
for (std::size_t i = 0; i + 6 <= s.size(); i += 6) {
|
|
45
|
+
const auto* b = reinterpret_cast<const std::uint8_t*>(s.data() + i);
|
|
46
|
+
peers.push_back(PexPeer{ipv4_to_string(b), read_u16_be(b + 4)});
|
|
47
|
+
}
|
|
48
|
+
return peers;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Length of the first bencoded value at the start of `d` (0 on malformed input).
|
|
52
|
+
// A small local scanner so we can split a ut_metadata header dict from the raw
|
|
53
|
+
// metadata block that follows it.
|
|
54
|
+
std::size_t bencode_value_length(const std::uint8_t* d, std::size_t n, std::size_t pos = 0) {
|
|
55
|
+
if (pos >= n) return 0;
|
|
56
|
+
const char c = char(d[pos]);
|
|
57
|
+
if (c == 'i') {
|
|
58
|
+
std::size_t e = pos + 1;
|
|
59
|
+
while (e < n && d[e] != 'e') ++e;
|
|
60
|
+
return e < n ? e + 1 - pos : 0;
|
|
61
|
+
}
|
|
62
|
+
if (c == 'l' || c == 'd') {
|
|
63
|
+
std::size_t p = pos + 1;
|
|
64
|
+
while (p < n && d[p] != 'e') {
|
|
65
|
+
const std::size_t len = bencode_value_length(d, n, p);
|
|
66
|
+
if (len == 0) return 0;
|
|
67
|
+
p += len;
|
|
68
|
+
}
|
|
69
|
+
return p < n ? p + 1 - pos : 0;
|
|
70
|
+
}
|
|
71
|
+
if (c >= '0' && c <= '9') {
|
|
72
|
+
std::size_t colon = pos;
|
|
73
|
+
while (colon < n && d[colon] != ':') ++colon;
|
|
74
|
+
if (colon >= n) return 0;
|
|
75
|
+
std::uint64_t len = 0;
|
|
76
|
+
for (std::size_t i = pos; i < colon; ++i) len = len * 10 + std::uint64_t(d[i] - '0');
|
|
77
|
+
const std::size_t total = (colon + 1 - pos) + len;
|
|
78
|
+
return (pos + total <= n) ? total : 0;
|
|
79
|
+
}
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const librats::BencodeValue* find(const librats::BencodeValue& dict, const char* key) {
|
|
84
|
+
return dict.find(key);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
std::optional<std::int64_t> find_int(const librats::BencodeValue& dict, const char* key) {
|
|
88
|
+
const auto* v = find(dict, key);
|
|
89
|
+
if (v && v->is_integer()) return v->as_integer();
|
|
90
|
+
return std::nullopt;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} // namespace
|
|
94
|
+
|
|
95
|
+
Bytes encode_handshake(std::uint32_t metadata_size, std::uint16_t listen_port) {
|
|
96
|
+
librats::BencodeValue d = librats::BencodeValue::create_dict();
|
|
97
|
+
librats::BencodeValue m = librats::BencodeValue::create_dict();
|
|
98
|
+
m["ut_metadata"] = librats::BencodeValue(std::int64_t(kUtMetadataLocalId));
|
|
99
|
+
m["ut_pex"] = librats::BencodeValue(std::int64_t(kUtPexLocalId));
|
|
100
|
+
d["m"] = m;
|
|
101
|
+
if (metadata_size > 0) d["metadata_size"] = librats::BencodeValue(std::int64_t(metadata_size));
|
|
102
|
+
if (listen_port > 0) d["p"] = librats::BencodeValue(std::int64_t(listen_port));
|
|
103
|
+
d["v"] = librats::BencodeValue(std::string("librats"));
|
|
104
|
+
return d.encode();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
std::optional<PeerExtensions> decode_handshake(ByteView payload) {
|
|
108
|
+
try {
|
|
109
|
+
librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), payload.size());
|
|
110
|
+
if (!d.is_dict()) return std::nullopt;
|
|
111
|
+
PeerExtensions ext;
|
|
112
|
+
if (const auto* m = find(d, "m"); m && m->is_dict()) {
|
|
113
|
+
if (auto v = find_int(*m, "ut_metadata")) ext.ut_metadata_id = std::uint8_t(*v);
|
|
114
|
+
if (auto v = find_int(*m, "ut_pex")) ext.ut_pex_id = std::uint8_t(*v);
|
|
115
|
+
}
|
|
116
|
+
if (auto v = find_int(d, "metadata_size"); v && *v > 0) ext.metadata_size = std::uint32_t(*v);
|
|
117
|
+
if (auto v = find_int(d, "p"); v && *v > 0) ext.listen_port = std::uint16_t(*v);
|
|
118
|
+
return ext;
|
|
119
|
+
} catch (const std::exception&) {
|
|
120
|
+
return std::nullopt;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Bytes encode_metadata_request(std::uint32_t piece) {
|
|
125
|
+
librats::BencodeValue d = librats::BencodeValue::create_dict();
|
|
126
|
+
d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Request));
|
|
127
|
+
d["piece"] = librats::BencodeValue(std::int64_t(piece));
|
|
128
|
+
return d.encode();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Bytes encode_metadata_reject(std::uint32_t piece) {
|
|
132
|
+
librats::BencodeValue d = librats::BencodeValue::create_dict();
|
|
133
|
+
d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Reject));
|
|
134
|
+
d["piece"] = librats::BencodeValue(std::int64_t(piece));
|
|
135
|
+
return d.encode();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Bytes encode_metadata_data(std::uint32_t piece, std::uint32_t total_size, ByteView block) {
|
|
139
|
+
librats::BencodeValue d = librats::BencodeValue::create_dict();
|
|
140
|
+
d["msg_type"] = librats::BencodeValue(std::int64_t(MetadataType::Data));
|
|
141
|
+
d["piece"] = librats::BencodeValue(std::int64_t(piece));
|
|
142
|
+
d["total_size"] = librats::BencodeValue(std::int64_t(total_size));
|
|
143
|
+
Bytes out = d.encode();
|
|
144
|
+
out.insert(out.end(), block.begin(), block.end());
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::optional<MetadataMessage> decode_metadata(ByteView payload) {
|
|
149
|
+
const std::size_t header_len = bencode_value_length(payload.data(), payload.size());
|
|
150
|
+
if (header_len == 0) return std::nullopt;
|
|
151
|
+
try {
|
|
152
|
+
librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), header_len);
|
|
153
|
+
if (!d.is_dict()) return std::nullopt;
|
|
154
|
+
const auto type = find_int(d, "msg_type");
|
|
155
|
+
const auto piece = find_int(d, "piece");
|
|
156
|
+
if (!type || !piece) return std::nullopt;
|
|
157
|
+
|
|
158
|
+
MetadataMessage msg;
|
|
159
|
+
msg.type = MetadataType(*type);
|
|
160
|
+
msg.piece = std::uint32_t(*piece);
|
|
161
|
+
if (msg.type == MetadataType::Data) {
|
|
162
|
+
if (auto ts = find_int(d, "total_size")) msg.total_size = std::uint32_t(*ts);
|
|
163
|
+
msg.block.assign(payload.begin() + std::ptrdiff_t(header_len), payload.end());
|
|
164
|
+
}
|
|
165
|
+
return msg;
|
|
166
|
+
} catch (const std::exception&) {
|
|
167
|
+
return std::nullopt;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Bytes encode_pex(const std::vector<PexPeer>& added, const std::vector<PexPeer>& dropped) {
|
|
172
|
+
librats::BencodeValue d = librats::BencodeValue::create_dict();
|
|
173
|
+
d["added"] = librats::BencodeValue(compact(added));
|
|
174
|
+
d["dropped"] = librats::BencodeValue(compact(dropped));
|
|
175
|
+
return d.encode();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
std::optional<PexMessage> decode_pex(ByteView payload) {
|
|
179
|
+
try {
|
|
180
|
+
librats::BencodeValue d = librats::BencodeDecoder::decode(payload.data(), payload.size());
|
|
181
|
+
if (!d.is_dict()) return std::nullopt;
|
|
182
|
+
PexMessage msg;
|
|
183
|
+
if (const auto* a = find(d, "added"); a && a->is_string()) msg.added = uncompact(a->as_string());
|
|
184
|
+
if (const auto* dr = find(d, "dropped"); dr && dr->is_string()) msg.dropped = uncompact(dr->as_string());
|
|
185
|
+
return msg;
|
|
186
|
+
} catch (const std::exception&) {
|
|
187
|
+
return std::nullopt;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
} // namespace librats::bittorrent::ext
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file extensions.h
|
|
5
|
+
* @brief BEP 10 extension protocol + BEP 9 metadata-exchange message codecs.
|
|
6
|
+
*
|
|
7
|
+
* BEP 10 layers named extensions on top of the wire's `extended` message
|
|
8
|
+
* (id 20). After the BitTorrent handshake each side sends an *extended
|
|
9
|
+
* handshake* (extended id 0) carrying a bencoded `m` dict that maps extension
|
|
10
|
+
* names to the message ids it wants them sent under — so the id you use to send
|
|
11
|
+
* a peer a `ut_metadata` message is the id *that peer* advertised, while the id
|
|
12
|
+
* you receive it under is the one *you* advertised (our fixed kUtMetadataLocalId).
|
|
13
|
+
*
|
|
14
|
+
* BEP 9 (`ut_metadata`) fetches the info-dictionary from peers for magnet links:
|
|
15
|
+
* the metadata is split into 16 KiB pieces requested/served via three message
|
|
16
|
+
* types (request / data / reject).
|
|
17
|
+
*
|
|
18
|
+
* These functions are pure (de)serialisation — the stateful request/serve/verify
|
|
19
|
+
* logic lives in the Torrent. That keeps the wire format unit-testable in
|
|
20
|
+
* isolation.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
#include "librats/bittorrent/types.h"
|
|
24
|
+
#include "librats/core/bytes.h"
|
|
25
|
+
|
|
26
|
+
#include <cstdint>
|
|
27
|
+
#include <optional>
|
|
28
|
+
#include <string>
|
|
29
|
+
#include <vector>
|
|
30
|
+
|
|
31
|
+
namespace librats::bittorrent::ext {
|
|
32
|
+
|
|
33
|
+
// Our local extended-message ids (what we tell peers to address us by).
|
|
34
|
+
constexpr std::uint8_t kUtMetadataLocalId = 1;
|
|
35
|
+
constexpr std::uint8_t kUtPexLocalId = 2;
|
|
36
|
+
|
|
37
|
+
constexpr char kUtMetadataName[] = "ut_metadata";
|
|
38
|
+
constexpr char kUtPexName[] = "ut_pex";
|
|
39
|
+
|
|
40
|
+
// ---- extended handshake (extended id 0) ----
|
|
41
|
+
|
|
42
|
+
/// Build our extended handshake. @p metadata_size is advertised only when > 0
|
|
43
|
+
/// (i.e. we actually hold the metadata); likewise @p listen_port when non-zero.
|
|
44
|
+
Bytes encode_handshake(std::uint32_t metadata_size, std::uint16_t listen_port);
|
|
45
|
+
|
|
46
|
+
struct PeerExtensions {
|
|
47
|
+
std::uint8_t ut_metadata_id = 0; ///< id to send ut_metadata under (0 = unsupported)
|
|
48
|
+
std::uint8_t ut_pex_id = 0;
|
|
49
|
+
std::uint32_t metadata_size = 0; ///< peer's info-dict size, if advertised
|
|
50
|
+
std::uint16_t listen_port = 0;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/// Parse a peer's extended handshake. nullopt if it isn't a valid bencoded dict.
|
|
54
|
+
std::optional<PeerExtensions> decode_handshake(ByteView payload);
|
|
55
|
+
|
|
56
|
+
// ---- ut_metadata (BEP 9) ----
|
|
57
|
+
|
|
58
|
+
enum class MetadataType : std::uint8_t { Request = 0, Data = 1, Reject = 2 };
|
|
59
|
+
|
|
60
|
+
Bytes encode_metadata_request(std::uint32_t piece);
|
|
61
|
+
Bytes encode_metadata_reject(std::uint32_t piece);
|
|
62
|
+
/// A `data` message: the bencoded header followed by the raw metadata @p block.
|
|
63
|
+
Bytes encode_metadata_data(std::uint32_t piece, std::uint32_t total_size, ByteView block);
|
|
64
|
+
|
|
65
|
+
struct MetadataMessage {
|
|
66
|
+
MetadataType type = MetadataType::Request;
|
|
67
|
+
std::uint32_t piece = 0;
|
|
68
|
+
std::uint32_t total_size = 0; ///< only set on Data
|
|
69
|
+
Bytes block; ///< only set on Data (the raw 16 KiB slice)
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/// Parse a ut_metadata message (header dict + optional trailing block). nullopt
|
|
73
|
+
/// if malformed.
|
|
74
|
+
std::optional<MetadataMessage> decode_metadata(ByteView payload);
|
|
75
|
+
|
|
76
|
+
// ---- ut_pex (BEP 11) ----
|
|
77
|
+
|
|
78
|
+
struct PexPeer {
|
|
79
|
+
std::string ip;
|
|
80
|
+
std::uint16_t port = 0;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/// Encode an IPv4 peer-exchange message (`added`/`dropped` compact lists).
|
|
84
|
+
Bytes encode_pex(const std::vector<PexPeer>& added, const std::vector<PexPeer>& dropped);
|
|
85
|
+
|
|
86
|
+
struct PexMessage {
|
|
87
|
+
std::vector<PexPeer> added;
|
|
88
|
+
std::vector<PexPeer> dropped;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/// Parse a ut_pex message. nullopt if malformed. (IPv4 `added`/`dropped` only.)
|
|
92
|
+
std::optional<PexMessage> decode_pex(ByteView payload);
|
|
93
|
+
|
|
94
|
+
} // namespace librats::bittorrent::ext
|