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
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* }
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
#include "
|
|
24
|
+
#include "librats/util/rats_export.h"
|
|
25
|
+
#include "librats/core/socket.h"
|
|
25
26
|
|
|
26
27
|
#include <memory>
|
|
27
28
|
#include <cstdint>
|
|
@@ -67,12 +68,14 @@ struct PollResult {
|
|
|
67
68
|
* @brief Abstract I/O multiplexer
|
|
68
69
|
*
|
|
69
70
|
* Thread-safety:
|
|
70
|
-
* - add/modify/remove: Safe to call from any thread
|
|
71
|
-
*
|
|
71
|
+
* - add/modify/remove: Safe to call from any thread. epoll_ctl is thread-safe on
|
|
72
|
+
* its own; the backends that keep a registration table of their own (kqueue,
|
|
73
|
+
* IOCP, poll) guard it with a mutex that wait() never takes, so a control call
|
|
74
|
+
* cannot stall the event loop.
|
|
72
75
|
* - wait: Should be called from a single I/O thread.
|
|
73
76
|
* - add/modify/remove can be called concurrently with wait().
|
|
74
77
|
*/
|
|
75
|
-
class IOPoller {
|
|
78
|
+
class RATS_API IOPoller {
|
|
76
79
|
public:
|
|
77
80
|
virtual ~IOPoller() = default;
|
|
78
81
|
|
|
@@ -88,10 +91,13 @@ public:
|
|
|
88
91
|
|
|
89
92
|
/**
|
|
90
93
|
* @brief Add a socket to the poll set
|
|
91
|
-
*
|
|
94
|
+
*
|
|
95
|
+
* A socket that is already in the set is rejected rather than re-registered:
|
|
96
|
+
* use modify() to change what an existing registration watches for.
|
|
97
|
+
*
|
|
92
98
|
* @param fd Socket to monitor
|
|
93
99
|
* @param events Bitmask of PollFlags to watch for
|
|
94
|
-
* @return true on success
|
|
100
|
+
* @return true on success, false if fd is already registered or on error
|
|
95
101
|
*/
|
|
96
102
|
virtual bool add(socket_t fd, uint32_t events) = 0;
|
|
97
103
|
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#include "librats/core/ip_address.h"
|
|
2
|
+
#include "librats/core/socket.h" // brings in the platform sockaddr / inet_ntop / inet_pton
|
|
3
|
+
|
|
4
|
+
#include <cstring>
|
|
5
|
+
|
|
6
|
+
namespace librats {
|
|
7
|
+
|
|
8
|
+
IpAddress IpAddress::from_v4(const std::array<uint8_t, 4>& b) {
|
|
9
|
+
IpAddress a;
|
|
10
|
+
std::memcpy(a.bytes_.data(), b.data(), 4);
|
|
11
|
+
a.family_ = Family::V4;
|
|
12
|
+
return a;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
IpAddress IpAddress::from_v6(const std::array<uint8_t, 16>& b) {
|
|
16
|
+
IpAddress a;
|
|
17
|
+
std::memcpy(a.bytes_.data(), b.data(), 16);
|
|
18
|
+
a.family_ = Family::V6;
|
|
19
|
+
return a;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
std::optional<IpAddress> IpAddress::from_bytes(ByteView bytes) {
|
|
23
|
+
IpAddress a;
|
|
24
|
+
if (bytes.size() == 4) {
|
|
25
|
+
std::memcpy(a.bytes_.data(), bytes.data(), 4);
|
|
26
|
+
a.family_ = Family::V4;
|
|
27
|
+
} else if (bytes.size() == 16) {
|
|
28
|
+
std::memcpy(a.bytes_.data(), bytes.data(), 16);
|
|
29
|
+
a.family_ = Family::V6;
|
|
30
|
+
} else {
|
|
31
|
+
return std::nullopt;
|
|
32
|
+
}
|
|
33
|
+
return a;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::optional<IpAddress> IpAddress::parse(std::string_view literal) {
|
|
37
|
+
// inet_pton needs a NUL-terminated string; string_view may not be terminated.
|
|
38
|
+
std::string s(literal);
|
|
39
|
+
|
|
40
|
+
std::array<uint8_t, 4> v4{};
|
|
41
|
+
if (inet_pton(AF_INET, s.c_str(), v4.data()) == 1)
|
|
42
|
+
return from_v4(v4);
|
|
43
|
+
|
|
44
|
+
std::array<uint8_t, 16> v6{};
|
|
45
|
+
if (inet_pton(AF_INET6, s.c_str(), v6.data()) == 1)
|
|
46
|
+
return from_v6(v6);
|
|
47
|
+
|
|
48
|
+
return std::nullopt;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
std::optional<IpAddress> IpAddress::from_sockaddr(const sockaddr* sa) {
|
|
52
|
+
if (!sa) return std::nullopt;
|
|
53
|
+
if (sa->sa_family == AF_INET) {
|
|
54
|
+
const auto* in = reinterpret_cast<const sockaddr_in*>(sa);
|
|
55
|
+
std::array<uint8_t, 4> b{};
|
|
56
|
+
std::memcpy(b.data(), &in->sin_addr, 4);
|
|
57
|
+
return from_v4(b);
|
|
58
|
+
}
|
|
59
|
+
if (sa->sa_family == AF_INET6) {
|
|
60
|
+
const auto* in6 = reinterpret_cast<const sockaddr_in6*>(sa);
|
|
61
|
+
const auto* raw = reinterpret_cast<const uint8_t*>(&in6->sin6_addr);
|
|
62
|
+
// Unwrap IPv4-mapped IPv6 (::ffff:a.b.c.d) into a native IPv4 so a dual-stack
|
|
63
|
+
// socket's view of an IPv4 peer compares/hashes identically to a v4 one.
|
|
64
|
+
static const uint8_t v4_mapped_prefix[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff};
|
|
65
|
+
if (std::memcmp(raw, v4_mapped_prefix, 12) == 0) {
|
|
66
|
+
std::array<uint8_t, 4> b{};
|
|
67
|
+
std::memcpy(b.data(), raw + 12, 4);
|
|
68
|
+
return from_v4(b);
|
|
69
|
+
}
|
|
70
|
+
std::array<uint8_t, 16> b{};
|
|
71
|
+
std::memcpy(b.data(), raw, 16);
|
|
72
|
+
return from_v6(b);
|
|
73
|
+
}
|
|
74
|
+
return std::nullopt;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
std::string IpAddress::to_string() const {
|
|
78
|
+
if (family_ == Family::V4) {
|
|
79
|
+
char buf[INET_ADDRSTRLEN] = {0};
|
|
80
|
+
in_addr a{};
|
|
81
|
+
std::memcpy(&a, bytes_.data(), 4);
|
|
82
|
+
if (inet_ntop(AF_INET, &a, buf, sizeof(buf))) return buf;
|
|
83
|
+
} else if (family_ == Family::V6) {
|
|
84
|
+
char buf[INET6_ADDRSTRLEN] = {0};
|
|
85
|
+
in6_addr a{};
|
|
86
|
+
std::memcpy(&a, bytes_.data(), 16);
|
|
87
|
+
if (inet_ntop(AF_INET6, &a, buf, sizeof(buf))) return buf;
|
|
88
|
+
}
|
|
89
|
+
return std::string();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
size_t IpAddress::to_sockaddr(sockaddr* sa, uint16_t port) const {
|
|
93
|
+
if (family_ == Family::V4) {
|
|
94
|
+
auto* in = reinterpret_cast<sockaddr_in*>(sa);
|
|
95
|
+
std::memset(in, 0, sizeof(*in));
|
|
96
|
+
in->sin_family = AF_INET;
|
|
97
|
+
in->sin_port = htons(port);
|
|
98
|
+
std::memcpy(&in->sin_addr, bytes_.data(), 4);
|
|
99
|
+
return sizeof(sockaddr_in);
|
|
100
|
+
}
|
|
101
|
+
if (family_ == Family::V6) {
|
|
102
|
+
auto* in6 = reinterpret_cast<sockaddr_in6*>(sa);
|
|
103
|
+
std::memset(in6, 0, sizeof(*in6));
|
|
104
|
+
in6->sin6_family = AF_INET6;
|
|
105
|
+
in6->sin6_port = htons(port);
|
|
106
|
+
std::memcpy(&in6->sin6_addr, bytes_.data(), 16);
|
|
107
|
+
return sizeof(sockaddr_in6);
|
|
108
|
+
}
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
size_t IpAddress::hash() const noexcept {
|
|
113
|
+
// FNV-1a over the family tag and all 16 bytes.
|
|
114
|
+
uint64_t h = 1469598103934665603ull;
|
|
115
|
+
h = (h ^ static_cast<uint8_t>(family_)) * 1099511628211ull;
|
|
116
|
+
for (uint8_t b : bytes_) h = (h ^ b) * 1099511628211ull;
|
|
117
|
+
return static_cast<size_t>(h);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} // namespace librats
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ip_address.h
|
|
5
|
+
* @brief A numeric IP address (IPv4 or IPv6) stored as raw bytes.
|
|
6
|
+
*
|
|
7
|
+
* The building block of a dialable endpoint. Unlike a textual "1.2.3.4" this is
|
|
8
|
+
* a fixed 16-byte value + a family tag — trivially copyable, allocation-free, and
|
|
9
|
+
* cheap to hash/compare (it is just its bytes). Text only appears at the edges:
|
|
10
|
+
* parse() on input, to_string() for logs/serialisation. Raw bytes flow straight
|
|
11
|
+
* through the hot paths (sockaddr conversion, BEP 42, the DHT compact codec).
|
|
12
|
+
*
|
|
13
|
+
* An IpAddress is strictly NUMERIC — it never holds a hostname. An unresolved
|
|
14
|
+
* "host:port" the user typed is a HostEndpoint (core/host_endpoint.h); it becomes
|
|
15
|
+
* one or more IpAddress only after the resolver runs at the network boundary.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
#include "librats/util/rats_export.h"
|
|
19
|
+
#include "librats/core/bytes.h"
|
|
20
|
+
|
|
21
|
+
#include <array>
|
|
22
|
+
#include <cstddef>
|
|
23
|
+
#include <cstdint>
|
|
24
|
+
#include <functional>
|
|
25
|
+
#include <optional>
|
|
26
|
+
#include <string>
|
|
27
|
+
#include <string_view>
|
|
28
|
+
|
|
29
|
+
struct sockaddr;
|
|
30
|
+
|
|
31
|
+
namespace librats {
|
|
32
|
+
|
|
33
|
+
class RATS_API IpAddress {
|
|
34
|
+
public:
|
|
35
|
+
enum class Family : uint8_t { None, V4, V6 };
|
|
36
|
+
|
|
37
|
+
/// The unspecified address (is_unspecified() == true).
|
|
38
|
+
IpAddress() = default;
|
|
39
|
+
|
|
40
|
+
/// Parse a bare numeric literal — "1.2.3.4" or "2001:db8::1" (no brackets, no
|
|
41
|
+
/// port). nullopt for anything that is not a valid IPv4/IPv6 literal (including
|
|
42
|
+
/// hostnames — those are a HostEndpoint, not an IpAddress).
|
|
43
|
+
static std::optional<IpAddress> parse(std::string_view literal);
|
|
44
|
+
|
|
45
|
+
static IpAddress from_v4(const std::array<uint8_t, 4>& b);
|
|
46
|
+
static IpAddress from_v6(const std::array<uint8_t, 16>& b);
|
|
47
|
+
|
|
48
|
+
/// Build from raw address bytes: exactly 4 (IPv4) or 16 (IPv6). nullopt for any
|
|
49
|
+
/// other length. This is the compact/wire form used by the DHT and PEX.
|
|
50
|
+
static std::optional<IpAddress> from_bytes(ByteView bytes);
|
|
51
|
+
|
|
52
|
+
/// Build from a sockaddr (AF_INET / AF_INET6). nullopt for other families.
|
|
53
|
+
/// An IPv4-mapped IPv6 sockaddr (::ffff:a.b.c.d) is unwrapped to a plain IPv4.
|
|
54
|
+
static std::optional<IpAddress> from_sockaddr(const sockaddr* sa);
|
|
55
|
+
|
|
56
|
+
bool is_v4() const noexcept { return family_ == Family::V4; }
|
|
57
|
+
bool is_v6() const noexcept { return family_ == Family::V6; }
|
|
58
|
+
bool is_unspecified() const noexcept { return family_ == Family::None; }
|
|
59
|
+
Family family() const noexcept { return family_; }
|
|
60
|
+
|
|
61
|
+
/// Not a dialable endpoint: either unspecified, or the all-zero wildcard
|
|
62
|
+
/// (0.0.0.0 / ::) that INADDR_ANY-style "any interface" bindings carry.
|
|
63
|
+
bool is_any() const noexcept {
|
|
64
|
+
if (family_ == Family::None) return true;
|
|
65
|
+
for (size_t i = 0, n = size(); i < n; ++i)
|
|
66
|
+
if (bytes_[i] != 0) return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// The significant address bytes: 4 for IPv4, 16 for IPv6, empty otherwise.
|
|
71
|
+
/// The view is valid for the lifetime of this IpAddress.
|
|
72
|
+
ByteView bytes() const noexcept { return ByteView(bytes_.data(), size()); }
|
|
73
|
+
size_t size() const noexcept {
|
|
74
|
+
return family_ == Family::V6 ? 16 : (family_ == Family::V4 ? 4 : 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Textual form via inet_ntop ("" for the unspecified address).
|
|
78
|
+
std::string to_string() const;
|
|
79
|
+
|
|
80
|
+
/// Fill *sa (must point to storage at least sizeof(sockaddr_in6)) with this
|
|
81
|
+
/// address and `port`. Returns the sockaddr length written, or 0 if unspecified.
|
|
82
|
+
size_t to_sockaddr(sockaddr* sa, uint16_t port) const;
|
|
83
|
+
|
|
84
|
+
bool operator==(const IpAddress& o) const noexcept {
|
|
85
|
+
return family_ == o.family_ && bytes_ == o.bytes_;
|
|
86
|
+
}
|
|
87
|
+
bool operator!=(const IpAddress& o) const noexcept { return !(*this == o); }
|
|
88
|
+
bool operator<(const IpAddress& o) const noexcept {
|
|
89
|
+
return family_ != o.family_ ? family_ < o.family_ : bytes_ < o.bytes_;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Hash over (family, all 16 bytes). Unused tail bytes are always zero, so this
|
|
93
|
+
/// is well-defined without masking.
|
|
94
|
+
size_t hash() const noexcept;
|
|
95
|
+
|
|
96
|
+
private:
|
|
97
|
+
// v4 occupies the first 4 bytes (tail kept zero); v6 uses all 16.
|
|
98
|
+
std::array<uint8_t, 16> bytes_{};
|
|
99
|
+
Family family_ = Family::None;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
} // namespace librats
|
|
103
|
+
|
|
104
|
+
namespace std {
|
|
105
|
+
template <>
|
|
106
|
+
struct hash<librats::IpAddress> {
|
|
107
|
+
size_t operator()(const librats::IpAddress& a) const noexcept { return a.hash(); }
|
|
108
|
+
};
|
|
109
|
+
} // namespace std
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file mpsc_queue.h
|
|
5
|
+
* @brief Multi-producer / single-consumer task queue used to hand work to a Reactor.
|
|
6
|
+
*
|
|
7
|
+
* Many threads call push(); exactly one (the reactor thread) calls drain().
|
|
8
|
+
* drain() swaps the entire backlog out under a single lock, so the consumer
|
|
9
|
+
* pays one lock per *batch*, not per item — the lock guards the handoff, never
|
|
10
|
+
* the work itself. This is deliberately simple and correct; it can be swapped
|
|
11
|
+
* for an intrusive lock-free MPSC later without touching callers.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
#include <mutex>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
namespace librats {
|
|
18
|
+
|
|
19
|
+
template <typename T>
|
|
20
|
+
class MpscQueue {
|
|
21
|
+
public:
|
|
22
|
+
/// Enqueue an item. Safe to call from any thread.
|
|
23
|
+
void push(T item) {
|
|
24
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
25
|
+
queue_.push_back(std::move(item));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// Move the entire backlog into `out` (cleared first). Single consumer only.
|
|
29
|
+
/// Returns the number of items handed over.
|
|
30
|
+
size_t drain(std::vector<T>& out) {
|
|
31
|
+
out.clear();
|
|
32
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
33
|
+
out.swap(queue_);
|
|
34
|
+
return out.size();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
bool empty() const {
|
|
38
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
39
|
+
return queue_.empty();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private:
|
|
43
|
+
mutable std::mutex mutex_;
|
|
44
|
+
std::vector<T> queue_;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
} // namespace librats
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file notifier.h
|
|
5
|
+
* @brief Reliable cross-platform wakeup for a poller-driven reactor.
|
|
6
|
+
*
|
|
7
|
+
* A reactor blocks in IOPoller::wait(); to make it run a freshly-posted task
|
|
8
|
+
* promptly we need to interrupt that wait from another thread. Notifier is a
|
|
9
|
+
* self-pipe built from a *connected TCP loopback pair*:
|
|
10
|
+
*
|
|
11
|
+
* reader_ ── registered in the poller for PollIn, drained on wake
|
|
12
|
+
* writer_ ── signal() writes one byte, making reader_ readable
|
|
13
|
+
*
|
|
14
|
+
* A connected TCP socket is the happy path for every backend — epoll, kqueue,
|
|
15
|
+
* and crucially IOCP (where a bare UDP socket does not give reliable readiness).
|
|
16
|
+
* If construction fails the notifier degrades to a no-op: fd() is invalid and
|
|
17
|
+
* signal() does nothing, so the reactor simply falls back to its poll timeout.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
#include "librats/core/socket.h"
|
|
21
|
+
|
|
22
|
+
#include <cstdint>
|
|
23
|
+
|
|
24
|
+
namespace librats {
|
|
25
|
+
|
|
26
|
+
class Notifier {
|
|
27
|
+
public:
|
|
28
|
+
Notifier() {
|
|
29
|
+
socket_t listener = create_tcp_server(0, 1, "127.0.0.1", AddressFamily::IPv4);
|
|
30
|
+
if (!is_valid_socket(listener)) return;
|
|
31
|
+
|
|
32
|
+
const int port = get_bound_port(listener);
|
|
33
|
+
writer_ = create_tcp_client("127.0.0.1", port, /*timeout_ms=*/2000);
|
|
34
|
+
if (is_valid_socket(writer_)) {
|
|
35
|
+
reader_ = accept_client(listener); // accept our own loopback connect
|
|
36
|
+
}
|
|
37
|
+
close_socket(listener);
|
|
38
|
+
|
|
39
|
+
if (is_valid_socket(reader_)) set_socket_nonblocking(reader_);
|
|
40
|
+
if (is_valid_socket(writer_)) set_socket_nonblocking(writer_);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
~Notifier() {
|
|
44
|
+
if (is_valid_socket(reader_)) close_socket(reader_);
|
|
45
|
+
if (is_valid_socket(writer_)) close_socket(writer_);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Notifier(const Notifier&) = delete;
|
|
49
|
+
Notifier& operator=(const Notifier&) = delete;
|
|
50
|
+
|
|
51
|
+
/// The pollable end; register this for PollIn. INVALID if construction failed.
|
|
52
|
+
socket_t fd() const noexcept { return reader_; }
|
|
53
|
+
|
|
54
|
+
/// Wake the reactor. Coalescing-friendly: many signals drain as one. Any thread.
|
|
55
|
+
void signal() {
|
|
56
|
+
if (!is_valid_socket(writer_)) return;
|
|
57
|
+
const uint8_t byte = 1;
|
|
58
|
+
::send(writer_, reinterpret_cast<const char*>(&byte), 1, 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Discard pending wake bytes. Called by the reactor when reader_ is readable.
|
|
62
|
+
void drain() {
|
|
63
|
+
uint8_t scratch[64];
|
|
64
|
+
while (::recv(reader_, reinterpret_cast<char*>(scratch), sizeof(scratch), 0) > 0) {
|
|
65
|
+
// discard
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private:
|
|
70
|
+
socket_t reader_ = RATS_INVALID_SOCKET;
|
|
71
|
+
socket_t writer_ = RATS_INVALID_SOCKET;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
} // namespace librats
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#include "librats/core/receive_buffer.h"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cassert>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
|
|
7
|
+
namespace librats {
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
/// Weight of one sample in the usage average: watermark += (peak - watermark) / 16.
|
|
12
|
+
/// Slow enough that a single quiet moment can't shrink a busy buffer, fast enough
|
|
13
|
+
/// that a connection that saw one big message releases it within a few messages.
|
|
14
|
+
constexpr size_t kUsageAverageShift = 4; // 1/16
|
|
15
|
+
|
|
16
|
+
/// Allocations are rounded up to this, so a chain of 1.5x growth steps lands on
|
|
17
|
+
/// allocator-friendly sizes rather than 17496-byte oddities.
|
|
18
|
+
constexpr size_t kAllocGranularity = 256;
|
|
19
|
+
|
|
20
|
+
constexpr size_t round_up(size_t n, size_t granularity) noexcept {
|
|
21
|
+
return (n + granularity - 1) / granularity * granularity;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
} // namespace
|
|
25
|
+
|
|
26
|
+
ReceiveBuffer::ReceiveBuffer(size_t initial_capacity) {
|
|
27
|
+
if (initial_capacity > 0) grow_to(round_up(initial_capacity, kAllocGranularity));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ReceiveBuffer& ReceiveBuffer::operator=(ReceiveBuffer&& other) noexcept {
|
|
31
|
+
if (this == &other) return *this;
|
|
32
|
+
|
|
33
|
+
// Every cursor moves with the allocation. Zeroing the source is what makes it a
|
|
34
|
+
// valid *empty* buffer rather than one that still claims cap_/end_ bytes it no
|
|
35
|
+
// longer owns — the state the compiler-generated move would have left behind.
|
|
36
|
+
buf_ = std::move(other.buf_);
|
|
37
|
+
cap_ = std::exchange(other.cap_, 0);
|
|
38
|
+
start_ = std::exchange(other.start_, 0);
|
|
39
|
+
end_ = std::exchange(other.end_, 0);
|
|
40
|
+
peak_ = std::exchange(other.peak_, 0);
|
|
41
|
+
watermark_ = std::exchange(other.watermark_, 0);
|
|
42
|
+
received_since_decay_ = std::exchange(other.received_since_decay_, false);
|
|
43
|
+
return *this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Write side ──────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
ByteSpan ReceiveBuffer::prepare(size_t min_bytes) {
|
|
49
|
+
if (cap_ - end_ < min_bytes) make_room(min_bytes);
|
|
50
|
+
|
|
51
|
+
// Fold the *demand* into the peak, not just the bytes that end up arriving. A
|
|
52
|
+
// caller reads with a fixed chunk size (16 KiB here, 64 KiB in the BitTorrent
|
|
53
|
+
// peer), so an allocation too small to serve the next prepare() is not idle
|
|
54
|
+
// memory — no matter how little the last message actually used. Sampling only
|
|
55
|
+
// the live bytes would let the average sink below the read size, shrink the
|
|
56
|
+
// buffer, and have the very next prepare() grow it straight back.
|
|
57
|
+
// (libtorrent samples max(recv_end, packet_size) for the same reason.)
|
|
58
|
+
peak_ = (std::max)(peak_, size() + min_bytes);
|
|
59
|
+
|
|
60
|
+
return ByteSpan(buf_.get() + end_, cap_ - end_);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void ReceiveBuffer::commit(size_t bytes) {
|
|
64
|
+
assert(bytes <= cap_ - end_ && "commit() past the span handed out by prepare()");
|
|
65
|
+
end_ += bytes;
|
|
66
|
+
peak_ = (std::max)(peak_, size());
|
|
67
|
+
received_since_decay_ = true; // this tick saw traffic; decay() must leave us alone
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void ReceiveBuffer::make_room(size_t bytes) {
|
|
71
|
+
const size_t live = size();
|
|
72
|
+
|
|
73
|
+
// Reclaiming the consumed prefix is enough: a memmove of the live bytes beats
|
|
74
|
+
// an allocation. Only reachable when something has been consumed, so it costs
|
|
75
|
+
// at most one memmove per bufferful of traffic.
|
|
76
|
+
if (cap_ - live >= bytes) {
|
|
77
|
+
compact();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
grow_to(grown_capacity(live + bytes));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void ReceiveBuffer::grow_to(size_t new_capacity) {
|
|
84
|
+
reallocate(new_capacity);
|
|
85
|
+
// Restart the usage average at the new size: the buffer just grew for a reason,
|
|
86
|
+
// and judging it oversized on the next drained message would only make it flap
|
|
87
|
+
// between two sizes. Demand has to fall for a while before it shrinks again.
|
|
88
|
+
watermark_ = cap_;
|
|
89
|
+
peak_ = size();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
size_t ReceiveBuffer::grown_capacity(size_t needed) const noexcept {
|
|
93
|
+
size_t next = cap_ + cap_ / 2; // 1.5x, as libtorrent does
|
|
94
|
+
if (next < kMinCapacity) next = kMinCapacity;
|
|
95
|
+
if (next < needed) next = needed; // a single big message may outrun 1.5x
|
|
96
|
+
return round_up(next, kAllocGranularity);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
void ReceiveBuffer::reallocate(size_t new_capacity) {
|
|
100
|
+
const size_t live = size();
|
|
101
|
+
assert(new_capacity >= live);
|
|
102
|
+
|
|
103
|
+
// Deliberately uninitialised (`new uint8_t[n]` default-initialises scalars,
|
|
104
|
+
// i.e. does nothing): every byte is about to be overwritten by recv().
|
|
105
|
+
std::unique_ptr<uint8_t[]> fresh(new uint8_t[new_capacity]);
|
|
106
|
+
if (live > 0) std::memcpy(fresh.get(), data(), live);
|
|
107
|
+
|
|
108
|
+
buf_ = std::move(fresh);
|
|
109
|
+
cap_ = new_capacity;
|
|
110
|
+
start_ = 0;
|
|
111
|
+
end_ = live;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Read side ───────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
void ReceiveBuffer::consume(size_t bytes) {
|
|
117
|
+
assert(bytes <= size() && "consume() past the live data");
|
|
118
|
+
start_ += bytes;
|
|
119
|
+
if (start_ != end_) return;
|
|
120
|
+
|
|
121
|
+
// Fully drained — the common case. Rewinding both cursors keeps the buffer
|
|
122
|
+
// normalised for free, so the steady state never memmoves.
|
|
123
|
+
start_ = end_ = 0;
|
|
124
|
+
sample_usage();
|
|
125
|
+
maybe_shrink();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Maintenance ─────────────────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
void ReceiveBuffer::compact() {
|
|
131
|
+
if (start_ == 0) return;
|
|
132
|
+
|
|
133
|
+
const size_t live = size();
|
|
134
|
+
if (live > 0) std::memmove(buf_.get(), buf_.get() + start_, live);
|
|
135
|
+
start_ = 0;
|
|
136
|
+
end_ = live;
|
|
137
|
+
sample_usage();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void ReceiveBuffer::decay() {
|
|
141
|
+
// Nothing to reclaim below the floor.
|
|
142
|
+
if (cap_ <= kMinCapacity) return;
|
|
143
|
+
|
|
144
|
+
// Traffic since the last tick: consume() already sampled it (or a partial message
|
|
145
|
+
// is still streaming in), and this connection is sized for a reason. Aging it as
|
|
146
|
+
// well would shrink it below its read size only for the next prepare() to grow it
|
|
147
|
+
// straight back.
|
|
148
|
+
if (std::exchange(received_since_decay_, false)) return;
|
|
149
|
+
|
|
150
|
+
// Silent for a whole tick. Halving (rather than folding in one more zero sample)
|
|
151
|
+
// is what makes this converge in seconds rather than minutes: maybe_shrink() wants
|
|
152
|
+
// the allocation to be more than twice the watermark, so halving the watermark
|
|
153
|
+
// halves the allocation every couple of ticks until it is back at the floor.
|
|
154
|
+
//
|
|
155
|
+
// This runs even when a partial message is still live (empty() is false). A peer
|
|
156
|
+
// that sends a length prefix — or the first slice of a large frame — and then
|
|
157
|
+
// stalls would otherwise pin the whole eager allocation for the life of the
|
|
158
|
+
// connection, and decay() is the only thing that ever visits an idle one.
|
|
159
|
+
// maybe_shrink() never drops below the live bytes, so the stalled data is kept;
|
|
160
|
+
// only the unused tail is handed back.
|
|
161
|
+
//
|
|
162
|
+
// Unless the live bytes already fill more than half the allocation: the tail worth
|
|
163
|
+
// reclaiming is then smaller than the memcpy of the data we have to keep, and the
|
|
164
|
+
// next byte the peer sends grows us straight back — copying it a second time. A
|
|
165
|
+
// stalled peer could otherwise trickle one byte per few ticks and have us memcpy its
|
|
166
|
+
// half-arrived block up and down forever. Pinning only ever comes from the *eager*
|
|
167
|
+
// reserve (a declared length that never arrived), where the live bytes are a tiny
|
|
168
|
+
// fraction of cap_, so bailing here costs no reclaim that mattered — it merely caps
|
|
169
|
+
// the unreclaimed slack at the size of the data itself, the same 2x any geometric
|
|
170
|
+
// buffer accepts. (Drained buffers reach maybe_shrink() via consume(), where
|
|
171
|
+
// size() == 0, so this never holds them up.)
|
|
172
|
+
if (size() * 2 > cap_) return;
|
|
173
|
+
|
|
174
|
+
watermark_ /= 2;
|
|
175
|
+
maybe_shrink();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
void ReceiveBuffer::clear() noexcept {
|
|
179
|
+
start_ = end_ = 0;
|
|
180
|
+
peak_ = 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
void ReceiveBuffer::reset() noexcept {
|
|
184
|
+
buf_.reset();
|
|
185
|
+
cap_ = start_ = end_ = peak_ = watermark_ = 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void ReceiveBuffer::sample_usage() noexcept {
|
|
189
|
+
// Exponential average; the +(N-1) rounds up so the average still tracks a
|
|
190
|
+
// rising peak instead of stalling below it on integer division.
|
|
191
|
+
constexpr size_t n = size_t{1} << kUsageAverageShift;
|
|
192
|
+
watermark_ = (watermark_ * (n - 1) + peak_ + (n - 1)) / n;
|
|
193
|
+
peak_ = size();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void ReceiveBuffer::maybe_shrink() {
|
|
197
|
+
// Only when the allocation is worth reclaiming *and* recent demand has clearly
|
|
198
|
+
// moved on — otherwise a buffer merely sitting between two messages would free
|
|
199
|
+
// and re-allocate itself on every single message.
|
|
200
|
+
if (cap_ <= kMinCapacity || cap_ / 2 <= watermark_) return;
|
|
201
|
+
|
|
202
|
+
// Fall back to what recent demand actually says, not to a padded version of it:
|
|
203
|
+
// the trigger above guarantees that halves the allocation at least, so a buffer
|
|
204
|
+
// that ballooned for one huge message is back to normal in a handful of steps
|
|
205
|
+
// (at most log2(cap) reallocations), not by creeping down a few percent at a
|
|
206
|
+
// time. Growth is geometric and cheap if demand returns.
|
|
207
|
+
//
|
|
208
|
+
// Never below the bytes still live: a partial message caught mid-stream keeps its
|
|
209
|
+
// storage — reallocate() moves those bytes to the front — so a stalled peer's data
|
|
210
|
+
// is preserved while its unused tail is reclaimed. On a drained buffer size() is 0,
|
|
211
|
+
// so this is exactly the plain watermark target.
|
|
212
|
+
// The watermark is deliberately NOT reset here (unlike after a grow): it is the
|
|
213
|
+
// decaying memory of demand that has to keep falling to shrink us further.
|
|
214
|
+
const size_t floor = (std::max)(kMinCapacity, size());
|
|
215
|
+
const size_t target = round_up((std::max)(floor, watermark_), kAllocGranularity);
|
|
216
|
+
if (target < cap_) reallocate(target);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
} // namespace librats
|